在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 空间主要存储的是应用程序,是非常关键的数据,为了防止误操作修改了这些内容,芯片复位后默认会给控制寄存器 FLASH_CR 上锁,这个时候不允许设置 FLASH 的控制寄存器,从而不能修改 FLASH 中的内容。
所以对 FLASH 写入数据前,需要先给它解锁。解锁的操作步骤如下:
在写入新的数据前,需要先擦除存储区域,STM32 提供了页(扇区)擦除指令和整个 FLASH 擦除(批量擦除)的指令,批量擦除指令仅针对主存储区。
页擦除的过程如下:
擦除完毕后即可写入数据,写入数据的过程并不是仅仅使用指针向地址赋值,赋值前还需要配置一系列的寄存器,步骤如下:
/* 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;
}
/**
@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读写