自学内容网 自学内容网

STM32补充——FLASH

目录

1.内部FLASH构成(F1)

2.FLASH读写过程(F1)

2.1内存的读取

2.2闪存的写入

2.3FLASH接口寄存器(写入 & 擦除相关)

3.FLASH相关HAL库函数简介(F1/F4/F7/H7)

4.编程实战

代码:


1.内部FLASH构成(F1)

闪存模块的子部分

作用

主存储器

用来存放代码和数据常数(如const类型到的数据)

信息块

分为两个部分:系统存储(启动程序代码)、选项字节(用户选择字节)

闪存存储器接口寄存器

用于控制闪存读写等,是整个闪存模块的控制结构

主存储器:地址范围为0x0800 0000~0x0807 FFFF,分为256页(代码中把页或扇区统称为扇区),每页2KB(小/中容量为1K)(F4/ F7/H7一个扇区为16K)。当BOOT0接地,系统将从0x0800 0000地址处开始读取代码(从主存储器启动)

信息块:
(1)系统存储大小为2KB,用来存储ST自带的启动程序,用来串口下载代码。
(2)选项字节大小为16B,一般用于设置内存的写保护、读保护当BOOT0接VCC,BOOT1接GND (串口下载程序) ,系统运行的就是这部分代码


2.FLASH读写过程(F1)

对FLASH的核心操作就是读和写。

FLASH的物理特性:只能写0,不能写1,写1靠擦除
 

2.1内存的读取

CPU运行速度比FLASH快得多,STM32F103的FLASH最快访问速度≤24MHz,CPU频率超过这个速度,得加入等待时间,否则读写FLASH可能出错,导致死机等情况。

频率范围

等待周期数(LATENCY)

 SYSCLK < 24MHz

0个等待周期

24MHz  SYSCLK ≤ 48MHz

1个等待周期

48MHz  SYSCLK ≤ 72MHz

2个等待周期

正确设置好等待周期后,从地址addr,读取数据(字节为8位,半字为16位,字为32位)

data = *(volatile uint8_t *)addr;/* 读取一个字节数据 */
data = *(volatile uint16_t *)addr;/* 读取一个半字数据 */
data = *(volatile uint32_t *)addr;/* 读取一个字数据 */

 将addr强制转换为uintx_t指针,然后取该指针所指向地址的值,即可获得addr地址的数据。

注意: 在进行写或擦除操作时,不能进行代码或数据的读取操作。


2.2闪存的写入

闪存编程是由FPEC(闪存编程和擦除控制器)模块处理的。

写操作有四步:解锁、擦除、写数据、上锁:

  1. 解锁:将两个特定的解锁序列号(KEY1:0x45670123、KEY2:0xCDEF89AB)依次写入FLASH_KEYR
  2. 擦除:FLASH物理特性(只能写0,不能写1),所以写FLASH之前需要擦除,将要写入的区域变为0xFFFF。擦除操作分为:页擦除和批量擦除
  3. 写数据:擦除完成,可以向FLASH写数据,每次只能以16位方式写入
  4. 上锁:写入数据完成,需要设置FLASH_CR[LOCK]位1,重新上锁,以防数据不小心被修改。

 闪存的擦除:

闪存的写入:(注意是写半字

2.3FLASH接口寄存器(写入 & 擦除相关)

FLASH寄存器

作用

FLASH_ACR

用于使能/关闭加速功能(DCEN/ICEN/PRFTEN),且可根据fCPU控制FLASH访问时间(LATENCY)

FLASH_KEYR

用来解锁FLASH_CR,需依次写入特定序列(KEY1:0x45670123KEY2:0xCDEF89AB)

FLASH_SR

用来提供正在执行的编程和擦除操作的相关信息(BSY位指示FLASH操作正在进行)

FLASH_CR

用于配置和启动FLASH操作(LOCK/STRT/PSIZE/SNB/SER/PG

对于H7还有FLASH_CCR 清除与控制寄存器用于清除相关错误。 

 FLASH_CR各位:

  1. LOCK:指示FLASH_CR寄存器是否被锁住(1锁 0未锁)
  2. STRT:用于开始一次擦除操作(1开始)
  3. PSIZE:用于设置编程宽度(3.3V PSIZE为2)
  4. SNB:用于选择要擦除的扇区编号 SER:用于选择扇区擦除操作(页擦除置1)
  5. PG:用于选择编程操作,往FLASH写数据需置1



3.FLASH相关HAL库函数简介(F1/F4/F7/H7)

驱动函数

关联寄存器

功能描述

HAL_FLASH_Unlock(…)

FLASH_KEYR

用于解锁FLASH_CR的访问

HAL_FLASH_Lock(…)

FLASH_CR

用于锁定FLASH_CR的访问

HAL_FLASH_Program(…)

FLASH_CR

用于FLASH的写入

HAL_FLASHEx_Erase(…)

FLASH_CR/AR

用于大量擦除或擦除指定的闪存扇区

FLASH_WaitForLastOperation(…)

FLASH_SR

等待操作完成

FLASH相关结构体:FLASH_EraseInitTypeDef

F1: 
uint32_t    TypeErase         /* 擦除类型 */
uint32_t    Banks                /* 擦除的bank编号(整片擦除) */
uint32_t    PageAddress    /* 擦除页面地址 */

uint32_t    NbPages           /* 擦除的页面数 */

F4/F7/H7:
uint32_t    TypeErase         
uint32_t    Banks        
uint32_t     Sector    
uint32_t     NbSectors
uint32_t    VoltageRange    



4.编程实战

F103ZET6 stmflash_write思路:

每个扇区(页)是2KB,也就是2048个地址

写任何一个地址前,如果该地址的值并不是0xFF,需先擦除再写


  1. 根据w_addr,确定No.sector号以及w_addr在该sector的偏移
  2. 根据w_addr和length,确定写入的内容是否跨sector
  3. 确定好要操作的sector以及sector的地址范围
  4. 遍历要写的地址区域数据是否都是0xFF,如果都是不用擦除,否则需要先读出保存在buf后擦除
  5. 把该sector要操作的数据,也写到buf,最后一次性把buf写入到这个对应的sector即可

跨扇区:需要注意偏移(扇区地址 / 扇区中的偏移 / 写入数据的偏移 / 写地址偏移 / 写入长度) 


代码:

/**
 * @brief       从指定地址读取一个半字 (16位数据)
 * @param       faddr   : 读取地址 (此地址必须为2的倍数!!)
 * @retval      读取到的数据 (16位)
 */
uint16_t stmflash_read_halfword(uint32_t faddr)
{
    return *(volatile uint16_t *)faddr;
}
/**
 * @brief       从指定地址开始读出指定长度的数据
 * @param       raddr : 起始地址
 * @param       pbuf  : 数据指针
 * @param       length: 要读取的半字(16位)数,即2个字节的整数倍
 * @retval      无
 */
void stmflash_read(uint32_t raddr, uint16_t *pbuf, uint16_t length)
{
    uint16_t i;

    for (i = 0; i < length; i++)
    {
        pbuf[i] = stmflash_read_halfword(raddr);    /* 读取2个字节 */
        raddr += 2; /* 偏移2个字节 */
    }
}
/**
 * @brief       不检查的写入
                这个函数的假设已经把原来的扇区擦除过再写入
 * @param       waddr   : 起始地址 (此地址必须为2的倍数!!,否则写入出错!)
 * @param       pbuf    : 数据指针
 * @param       length  : 要写入的 半字(16位)数
 * @retval      无
 */
void stmflash_write_nocheck(uint32_t waddr, uint16_t *pbuf, uint16_t length)
{
    uint16_t i;
    for (i = 0; i < length; i++)
    {
        HAL_FLASH_Program(FLASH_TYPEPROGRAM_HALFWORD, waddr, pbuf[i]);
        waddr += 2;     /* 指向下一个半字 */
    }
}
/**
 * @brief       在FLASH 指定位置, 写入指定长度的数据(自动擦除)
 *   @note      该函数往 STM32 内部 FLASH 指定位置写入指定长度的数据
 *              该函数会先检测要写入的扇区是否是空(全0XFFFF)的?, 如果
 *              不是, 则先擦除, 如果是, 则直接往扇区里面写入数据.
 *              数据长度不足扇区时,自动被回擦除前的数据
 * @param       waddr   : 起始地址 (此地址必须为2的倍数!!,否则写入出错!)
 * @param       pbuf    : 数据指针
 * @param       length  : 要写入的 半字(16位)数
 * @retval      无
 */
uint16_t g_flashbuf[STM32_SECTOR_SIZE / 2]; /* 最多是2K字节 */
void stmflash_write(uint32_t waddr, uint16_t *pbuf, uint16_t length)
{
    uint32_t secpos;        /* 扇区地址 */
    uint16_t secoff;        /* 扇区内偏移地址(16位字计算) */
    uint16_t secremain;     /* 扇区内剩余地址(16位字计算) */
    uint16_t i;
    uint32_t offaddr;       /* 去掉0X08000000后的地址 */
    FLASH_EraseInitTypeDef flash_eraseop;
    uint32_t erase_addr;    /* 擦除错误,这个值为发生错误的扇区地址 */

    if (waddr < STM32_FLASH_BASE || (waddr >= (STM32_FLASH_BASE + 1024 * STM32_FLASH_SIZE)))
    {
        return;     /* 非法地址 */
    }

    HAL_FLASH_Unlock();     /* FLASH解锁 */

    offaddr = waddr - STM32_FLASH_BASE;             /* 实际偏移地址. */
    secpos = offaddr / STM32_SECTOR_SIZE;           /* 扇区地址  0~255 for STM32F103ZET6 */
    secoff = (offaddr % STM32_SECTOR_SIZE) / 2;     /* 在扇区内的偏移(2个字节为基本单位.) */
    secremain = STM32_SECTOR_SIZE / 2 - secoff;     /* 扇区剩余空间大小 */
    if (length <= secremain)
    {
        secremain = length; /* 不大于该扇区范围 */
    }

    while (1)
    {
        stmflash_read(secpos * STM32_SECTOR_SIZE + STM32_FLASH_BASE, g_flashbuf, STM32_SECTOR_SIZE / 2); /* 读出整个扇区的内容 */
        for (i = 0; i < secremain; i++)                                                              /* 校验数据 */
        {
            if (g_flashbuf[secoff + i] != 0XFFFF)
            {
                break;      /* 需要擦除 */
            }
        }
        if (i < secremain)  /* 需要擦除 */
        { 
            flash_eraseop.TypeErase = FLASH_TYPEERASE_PAGES;     /* 选择页擦除 */
            flash_eraseop.NbPages = 1;
            flash_eraseop.PageAddress = secpos * STM32_SECTOR_SIZE + STM32_FLASH_BASE;  /* 要擦除的扇区 */
            HAL_FLASHEx_Erase( &flash_eraseop, &erase_addr);

            for (i = 0; i < secremain; i++)         /* 复制 */
            {
                g_flashbuf[i + secoff] = pbuf[i];
            }
            stmflash_write_nocheck(secpos * STM32_SECTOR_SIZE + STM32_FLASH_BASE, g_flashbuf, STM32_SECTOR_SIZE / 2); /* 写入整个扇区 */
        }
        else
        {
            stmflash_write_nocheck(waddr, pbuf, secremain); /* 写已经擦除了的,直接写入扇区剩余区间. */
        }
        if (length == secremain)
        {
            break; /* 写入结束了 */
        }
        else       /* 写入未结束 */
        {
            secpos++;               /* 扇区地址增1 */
            secoff = 0;             /* 偏移位置为0 */
            pbuf += secremain;      /* 指针偏移 */
            waddr += secremain * 2; /* 写地址偏移(16位数据地址,需要*2) */
            length -= secremain;    /* 字节(16位)数递减 */
            
            if (length > (STM32_SECTOR_SIZE / 2))
            {
                secremain = STM32_SECTOR_SIZE / 2; /* 下一个扇区还是写不完 */
            }
            else
            {
                secremain = length; /* 下一个扇区可以写完了 */
            }
        }
    }

    HAL_FLASH_Lock(); /* 上锁 */
}


原文地址:https://blog.csdn.net/m0_74375685/article/details/145244506

免责声明:本站文章内容转载自网络资源,如侵犯了原著者的合法权益,可联系本站删除。更多内容请关注自学内容网(zxcms.com)!