STM32F103学习笔记(3)——读写内部Flash

一、简介

在STM32芯片内部有一个 FLASH 存储器,它主要用于存储代码,我们在电脑上编写好应用程序后,使用下载器把编译后的代码文件烧录到该内部 FLASH 中,由于 FLASH 存储器的内容在掉电后不会丢失,芯片重新上电复位后,内核可从内部 FLASH 中加载代码并运行。


STM32 的内部 FLASH 包含主存储器、系统存储器以及选项字节区域,它们的地址分布及大小见下表


  • 主存储器

一般我们说 STM32 内部 FLASH 的时候,都是指这个主存储器区域,它是存储用户应用程序的空间,芯片型号说明中的 256K FLASH、512K FLASH 都是指这个区域的大小。

主存储器分为 256 页,每页大小为 2KB,共 512KB。这个分页的概念,实质就是 FLASH 存储器的扇区,与其它 FLASH 一样,在写入数据前,要先按页(扇区)擦除。

注意上表中的主存储器是本实验板使用的 STM32VET6 型号芯片的参数,即 STM32F1 大容量产品。若使用超大容量、中容量或小容量产品,它们主存储器的页数量、页大小均有不同,使用的时候要注意区分。
主存储器是以页为单位划分的。stm32根据FLASH主存储块容量、页面的不同,系统存储器的不同,分为小容量、中容量、大容量、互联型,共四类产品。

  • 小容量产品:主存储块1-32KB, 每页1KB。系统存储器2KB
  • 中容量产品:主存储块64-128KB, 每页1KB。系统存储器2KB
  • 大容量产品:主存储块256KB以上, 每页2KB。系统存储器2KB
  • 互联型产品:主存储块256KB以上, 每页2KB。系统存储器18KB
  • 系统存储区

系统存储区是用户不能访问的区域,它在芯片出厂时已经固化了启动代码,它负责实现串口、USB 以及 CAN 等 ISP 烧录功能。

  • 选项字节

选项字节用于配置 FLASH 的读写保护、待机/停机复位、软件/硬件看门狗等功能,这部分共 16 字节。可以通过修改 FLASH 的选项控制寄存器修改。

二、查看工程的空间分布

由于内部 FLASH 本身存储有程序数据,若不是有意删除某段程序代码,一般不应修改程序空间的内容,所以在使用内部 FLASH 存储其它数据前需要了解哪一些空间已经写入了程序代码,存储了程序代码的扇区都不应作任何修改。通过查询应用程序编译时产生
的“*.map”后缀文件,可以了解程序存储到了哪些区域。


打开 map 文件后,查看文件最后部分的区域,可以看到一段以 “Memory Map of the image” 开头的记录(若找不到可用查找功能定位)

观察表中的最后一项,它的基地址是 0x0800175c,大小为 0x00000020,可知它占用的
最高的地址空间为 0x0800177c,跟执行区域的最高地址 0x0000177c 一样,但它们比加载
区域说明中的最高地址 0x80017a8 要小,所以我们以加载区域的大小为准。对比表 45-1 的
内部 FLASH 页地址分布表,可知仅使用页 0 至页 2 就可以完全存储本应用程序,所以从页
3(地址 0x08001800)后的存储空间都可以作其它用途,使用这些存储空间时不会篡改应用程
序空间的数据。

三、写入Flash

3.1 写入过程

3.1.1 解锁

由于内部 FLASH 空间主要存储的是应用程序,是非常关键的数据,为了防止误操作修改了这些内容,芯片复位后默认会给控制寄存器 FLASH_CR 上锁,这个时候不允许设置 FLASH 的控制寄存器,从而不能修改 FLASH 中的内容。

所以对 FLASH 写入数据前,需要先给它解锁。解锁的操作步骤如下:

  1. 往 FPEC 键寄存器 FLASH_KEYR 中写入 KEY1 = 0x45670123
  2. 再往 FPEC 键寄存器 FLASH_KEYR 中写入 KEY2 = 0xCDEF89AB

3.1.2 页擦除

在写入新的数据前,需要先擦除存储区域,STM32 提供了页(扇区)擦除指令和整个 FLASH 擦除(批量擦除)的指令,批量擦除指令仅针对主存储区。
页擦除的过程如下:

  1. 检查 FLASH_SR 寄存器中的“忙碌寄存器位 BSY”,以确认当前未执行任何 Flash 操作;
  2. 在 FLASH_CR 寄存器中,将“激活页擦除寄存器位 PER ”置 1;
  3. 用 FLASH_AR 寄存器选择要擦除的页;
  4. 将 FLASH_CR 寄存器中的“开始擦除寄存器位 STRT ”置 1,开始擦除;
  5. 等待 BSY 位被清零时,表示擦除完成。

3.1.3 写入数据

擦除完毕后即可写入数据,写入数据的过程并不是仅仅使用指针向地址赋值,赋值前还需要配置一系列的寄存器,步骤如下:

  1. 检查 FLASH_SR 中的 BSY 位,以确认当前未执行任何其它的内部 Flash 操作;
  2. 将 FLASH_CR 寄存器中的 “激活编程寄存器位 PG” 置 1;
  3. 向指定的 FLASH 存储器地址执行数据写入操作,每次只能以 16 位的方式写入;
  4. 等待 BSY 位被清零时,表示写入完成。

3.2 写入函数

/* STM32大容量产品每页大小2KByte,中、小容量产品每页大小1KByte */
#if defined (STM32F10X_HD) || defined (STM32F10X_HD_VL) || defined (STM32F10X_CL) || defined (STM32F10X_XL)
  #define FLASH_PAGE_SIZE       ((uint16_t)0x800)   // 2048
#else
  #define FLASH_PAGE_SIZE       ((uint16_t)0x400)   // 1024
#endif

#define WRITE_START_ADDR        ((uint32_t)0x08008000)
#define WRITE_END_ADDR          ((uint32_t)0x0800C000)

/**
 @brief 内部Flash写入
 @param address -[in] 写入的地址
 @param pData -[in&out] 指向需要操作的数据
 @param dataLen -[in] 数据长度
 @return true - 成功;false - 失败
*/
bool Internal_WriteFlash(uint32_t addrStart, uint32_t *pData, uint32_t dataLen)
{   
    uint32_t i = 0;
    uint32_t eraseCounter = 0x00;                                                               // 记录要擦除多少页
    uint32_t address = 0x00;                                                                    // 记录写入的地址
    uint32_t numberOfPage = 0x00;                                                               // 记录写入多少页
    FLASH_Status flashStatus = FLASH_COMPLETE;                                                  // 记录每次擦除的结果
    
    address = addrStart;
    
    FLASH_Unlock();                                                                             // 解锁
    numberOfPage = (WRITE_END_ADDR - address) / FLASH_PAGE_SIZE;                                // 计算要擦除多少页
    FLASH_ClearFlag(FLASH_FLAG_BSY | FLASH_FLAG_EOP | FLASH_FLAG_PGERR | FLASH_FLAG_WRPRTERR);  // 清除所有标志
    
    // 按页擦除
    for(eraseCounter = 0; (eraseCounter < numberOfPage) && (flashStatus == FLASH_COMPLETE); eraseCounter++)
    {
        flashStatus = FLASH_ErasePage(address + (FLASH_PAGE_SIZE * eraseCounter));
    }
    
    for(i = 0; (i < dataLen)&&(flashStatus == FLASH_COMPLETE); i++)
    {
        flashStatus = FLASH_ProgramWord(address, pData[i]);                                     // 写入一个字(32位)的数据入指定地址
        address = address + 4;                                                                  // 地址偏移4个字节 
    }
    
    FLASH_Lock();                                                                               // 重新上锁
    
    if(flashStatus == FLASH_COMPLETE)
    {
        return true;
    }
    return false;
}

四、读取Flash

4.1 读取函数

/**
 @brief 内部Flash读取
 @param address -[in] 读取的地址
 @param pData -[in&out] 指向需要操作的数据
 @param dataLen -[in] 数据长度
 @return true - 成功;false - 失败
*/
bool Internal_ReadFlash(uint32_t addrStart, uint32_t *pData, uint32_t dataLen)
{
    uint32_t i = 0;
    uint32_t address = 0x00;
    
    address = addrStart;
    
    for(i = 0; i < dataLen; i++)
    {
        pData[i] = (*(__IO uint32_t*) address);                                                 // 读指定地址的一个字的数据
        address += 4;                                                                           // 地址偏移4个字节        
    }
    
    return true;
}

五、举例

int main(void)
{    
    u32 in_data[5]={11,22,33,44,55};//要写入的数据
    u32 out_data[5];//读存放
    int i;
    u8 STATUS=0;
    USART1_Config();//串口1配置
    GPIO_Configuration();//GPIO配置,用于点亮led
    STATUS=Internal_WriteFlash(0x08001800,in_data,5);
    Delay(0x02FFFF);
    if(STATUS)
    {
            GPIO_SetBits(GPIOD, GPIO_Pin_13);//点亮led1
            Internal_ReadFlash(0x08001800,out_data,5);
            printf("\r\n The Five Data Is : \r\n");
            for(i=0;i<5;i++)
            {
                    printf("\r %d \r",out_data[i]);
            }
    }
    while(1);
}

• 由 Leung 写于 2020 年 7 月 13 日

• 参考:[零死角玩转STM32——基于野火F103【指南者】开发板]
    stm32——Flash读写

你可能感兴趣的:(STM32F103学习笔记(3)——读写内部Flash)