第七章 STM32内部FLASH读写

芯片:STM32F103C8T6

Flash:64K 从0x08000000~0x08010000

SRAM:20K 从0x02000000~0x020005000

1、FLASH

Flash存储在code区,从0x08000000开始的地址。

整个Cortex-M3,系统分区如图所示:

1.1 Code

代码区,0x0000_0000 - 0x1FFF_FFFF,512MB

Code代码区主要用来存放用户代码数据和bootloader。

程序指令:代码

常量数据:由const 修饰的变量(const uint8_t table[] = {1,2,3})、const int i=3

初始化数据模板:全局变量的初始值,程序运行后会复制到SRAM中

BootLoader代码

由STM32F103RCT6为例

地址范围 用途 物理实现
0x00000000--0x07FFFFFF Flash 别名(通过 BOOT 引脚重映射) 指向主 Flash 或系统存储器
0x08000000--0x0803FFFF 主 Flash(用户程序存储区,256KB) 实际物理 Flash
0x1FFF0000--0x1FFF0FFF 系统存储器(内置 Bootloader) 只读 ROM
0x1FFFF800--0x1FFFF80F 选项字节(Flash 配置参数) 特殊 Flash 区域
其他地址(如 0x08040000--0x1FFFFFFF) 保留或未实现 无物理存储

1.2 存储器映射

0x0000 0000 - 0x0800 0000 根据Boot引脚配置,映射Flash/Sysmem/SRAM当中的128MB空间

0x0800 0000 - 0x0801 FFFF Flash Memory闪存存储空间128KB

0x1FFF F800 - 0x1FFF F7FE System Memory系统存储空间2KB

0x2000 0000 - 0x3FFF FFFF SRAM 存储区

2、FLASH操作

对于Flash操作,就是读、写。

这里的flash是指单片机自带的内部Flash,这个Flash是用来存储用户开发的程序代码。

STM32的闪存编程是由FPEC(闪存编程和查出控制器)模块处理的,这个模块包含7个32位寄存器,分别是:

  • FPEC 键寄存器(FLASH_KEYR) :负责对内置闪存的写操作

  • 选择字节键寄存器(FLASH_OPTKEYR)

  • 闪存控制寄存器(FLASH_CR)

  • 闪存状态寄存器(FLASH_SR)

  • 闪存地址寄存器(FLASH_AR)

  • 选择字节寄存器(FLASH_OBR)

  • 写保护寄存器(FLASH_WRPR)

FPEC键寄存器总共有三个键值:

RDPRT键=0x000000A5

KEY1=0x45670123

KEY2=0xCDEF89AB

在程序中,官方已经给封装好了,只需要调用flash的上锁和解锁函数即可

2.1 Flash编程事项

1字=32位

半字=16位

1字节=8位

  • STM32 复位后, FPEC 模块是被保护的,不能写入 FLASH_CR 寄存器;通过写入特定的序列到 FLASH_KEYR 寄存器可以打开 FPEC 模块(即写入 KEY1 和 KEY2),只有在写保护被解除后,我们才能操作相关寄存器。

  • STM32 闪存的编程每次必须写入 16 位,当 FLASH_CR 寄存器的PG位为' 1'时,在一个闪存地址写入一个半字将启动一次编程;写入任何非半字的数据,FPEC 都会产生总线错误。在编程过程中(BSY 位为' 1' ),任何读写闪存的操作都会使 CPU暂停,直到此次闪存编程结束。

  • STM32 的 FLASH 在编程的时候,也必须要求其写入地址的FLASH是被擦除了的(也就是其值必须是 0XFFFF),否则无法写入,在 FLASH_SR寄存器的PGERR位将得到一个警告。

2.2 Flash编程过程

  1. 检查FLASH_CR的LOCK是否解锁,如果没有先解锁

  2. 检测FLASH_SR寄存器的BSY位,确认没有其它正在进行的编程操作

  3. 设置FLASH_CR寄存器的PG位为1,在指定的地址写入要编程的半字

  4. 等待BSY位变为0

  5. 读出写入的地址并验证数据

FLASH_Status FLASH_GetStatus(void); // 获取Flash状态
FLASH_Status FLASH_WaitForLastOperation(uint32_t Timeout) // 等待操作完成函数

写入半字操作

FLASH_Status FLASH_ProgramWord(uint32_t Address, uint32_t Data); // 字

FLASH_Status FLASH_ProgramHalfWord(uint32_t Address, uint16_t Data); // 半字

FLASH_Status FLASH_ProgramOptionByteData(uint32_t Address, uint8_t Data); // 字节

读出半字操作

u16 STMFLASH_ReadHalfWord(u32 faddr)

{

return *(vu16*)faddr;

}

2.3 Flash擦除操作

Flash擦除操作:页擦除和整片擦除

页擦除

  1. 检查 FLASH_CR 的 LOCK 是否解锁,如果没有则先解锁

  2. 检查 FLASH_SR 寄存器的 BSY 位,以确认没有其他正在进行的闪存操作

  3. 设置 FLASH_CR 寄存器的 PER 位为' 1'

Flash擦除操作:

FLASH_Status FLASH_ErasePage(uint32_t Page_Address); // 页

FLASH_Status FLASH_EraseAllPages(void); // 所有页

FLASH_Status FLASH_EraseOptionBytes(void); // 字节数据擦除

3、 STM32实现对Flash操作

3.1 从Flash读数据

前面描述了,对于读flash操作来说,就是简单传入要读数据的地址,然后要读取数据的大小即可

FlashResult STMFLASH_ReadHalfWord(u32 ReadAddr, u16 *pData)

{

if(ReadAddr & 0x01) {

return FLASH_ERROR_INVALID_ADDR;

}

*pData = *(vu16*)ReadAddr;

return FLASH_SUCCESS;

}

FlashResult MyFlash_Read(u32 ReadAddr, u16 *pBuffer, u16 NumToRead)

{

u16 i;

FlashResult result;

if((ReadAddr < STM32_FLASH_BASE) || (ReadAddr >= (STM32_FLASH_BASE + 1024 * STM32_FLASH_SIZE))) {

return FLASH_ERROR_INVALID_ADDR;

}

for(i = 0; i < NumToRead;i++) {

result = STMFLASH_ReadHalfWord(ReadAddr, &pBuffer[i]);

if(result != FLASH_SUCCESS)

return result;

ReadAddr += 2;

}

return FLASH_SUCCESS;

}

传入的地址是有效的

3.2 向Flash写数据

从Flash写数据是一个复杂的过程,对Flash的操作,写也是最重要的部分。

由于Flash物理特性决定了向flash某个区域写数据,必须将其所在的页擦除才可以操作,所有就要涉及到判断,传入的地址是在那一页,那个位置,前面有多少数据需要保存,后面有多少空间支持写入,如果写入的数据太大,当前页装不完,就需要考虑写入下一页。

比如:

图中,黑色为flash的起始地址,红色为要写入数据的起始地址,写入数据大小为size,刚好需要存到第三页的中间位置(蓝色为写入数据的结束地址)。

1、offset = size-0x08000000 得到内存偏移地址

2、secpos = offset / 1024 得到在哪一页

3、secoff = offset%1024 得到在该页的起始地址

4、secremain = 1024/2 - secoff 得到内存剩余空间
要写入的数据地址为:0x08000804 写入数据为:数据[1044]个

1、内存偏移地址为 = 0x08000804 - 0x08000000 = 0x804

2、得到那一页 = 0x804==2052/1024 = 2页

3、得到页内偏移地址 = 0x804==2052%1024 = 4

写数据:

得到要写入数据的页数和页内偏移地址,将该页起始到写入数据的首地址的数据读出

擦除该页

将要写入数据和之前的数据存入从新写入到该页

如果该页存不完数据,就增加页数,继续存储数据
FlashResult STMFLASH_Write_NoCheck(u32 WriteAddr,u16 *pBuffer,u16 NumToWrite)

{

u16 i;

for(i = 0; i < NumToWrite; i++) {

if(FLASH_ProgramHalfWord(WriteAddr, pBuffer[i]) != FLASH_COMPLETE) {

return FLASH_ERROR_PROGRAM_FAILED;

}

WriteAddr+=2;

}

return FLASH_SUCCESS;

}

FlashResult MyFlash_Write(u32 WriteAddr, u16 *pBuffer, u16 NumToWrite)

{

u32 offaddr; //去掉0X08000000后的地址(偏移地址)

u32 secpos; //扇区地址

u16 secoff; //扇区内偏移地址(16位字计算)

u16 secremain; //扇区内剩余地址(16位字计算)

u16 i;

FlashResult result;

if((WriteAddr < STM32_FLASH_BASE)||(WriteAddr >= (STM32_FLASH_BASE + 1024*STM32_FLASH_SIZE))) {

return FLASH_ERROR_INVALID_ADDR;

}

FLASH_Unlock(); // 解锁

offaddr = WriteAddr - STM32_FLASH_BASE;

secpos = offaddr/STM_SECTOR_SIZE;

secoff = (offaddr % STM_SECTOR_SIZE)/2;

secremain = STM_SECTOR_SIZE/2 - secoff;

if(NumToWrite <= secremain) secremain = NumToWrite;

while(1) {

result = MyFlash_Read(secpos*STM_SECTOR_SIZE+STM32_FLASH_BASE, flashread_buffer.STMFLASH_BUF, STM_SECTOR_SIZE/2);

if(result != FLASH_SUCCESS) {

FLASH_Lock();

return result;

}

for(i = 0; i < secremain; i++) {

if(flashread_buffer.STMFLASH_BUF[secoff + i] != 0xFFFF) break;

}

if(i < secremain) {

if(FLASH_ErasePage(secpos * STM_SECTOR_SIZE + STM32_FLASH_BASE) != FLASH_COMPLETE) {

FLASH_Lock();

return FLASH_ERROR_ERASE_FAILED;

}

for(i = 0; i < secremain; i++) {

flashread_buffer.STMFLASH_BUF[i+secoff] = pBuffer[i];

}

result = STMFLASH_Write_NoCheck(secpos*STM_SECTOR_SIZE+STM32_FLASH_BASE,flashread_buffer.STMFLASH_BUF, STM_SECTOR_SIZE/2);

if(result != FLASH_SUCCESS) {

FLASH_Lock();

return result;

}

} else {

result = STMFLASH_Write_NoCheck(WriteAddr,pBuffer,secremain);//写已经擦除了的,直接写入扇区剩余区间.

if(result != FLASH_SUCCESS) {

FLASH_Lock();

return result;

}

}

if(NumToWrite == secremain) break;

else {

secpos++;

secoff=0;

pBuffer += secremain;

WriteAddr+= secremain; //写地址偏移

NumToWrite -= secremain;

if(NumToWrite > (STM_SECTOR_SIZE/2)) secremain=STM_SECTOR_SIZE/2;//下一个扇区还是写不完

else secremain = NumToWrite;

}

}

FLASH_Lock();

return FLASH_SUCCESS;

}
向flash写入数据的流程为:

1、拿到偏移地址,和页地址,页内地址

2、解锁

3、从内存中读出数据

4、擦除该页

5、向该页写入数据(如果该页写不完就修改页地址,指向下一页)

6、上锁

3.3 结果显示

在main函数中打印输出

printf("Data to write: %s\r\n", TEXT_Buffer);

write_res = MyFlash_Write(FLASH_SAVE_ADDR,(u16*)TEXT_Buffer,(SIZE + 1)/2);

if (write_res != FLASH_SUCCESS) {

printf("Write failed! Error: %d\r\n", write_res);

}

printf("write success\r\n");

delay_ms(20);

MyFlash_Read(FLASH_SAVE_ADDR,(u16*)datatemp,SIZE);

printf("Data read from flash: %s\r\n", datatemp);

printf("read success\r\n");

write_res = MyFlash_Write(FLASH_SAVE_ADDR2,(u16*)TEXT2_Buffer,20/2);

if (write_res != FLASH_SUCCESS) {

printf("Write failed! Error: %d\r\n", write_res);

}

printf("write success\r\n");

delay_ms(20);

MyFlash_Read(FLASH_SAVE_ADDR2,(u16*)datatemp,SIZE);

printf("Data read from flash: %s\r\n", datatemp);

printf("read success\r\n");

使用专业工具,查看stm32f103c8t6中flash存入的数据

相关推荐
森焱森1 小时前
无人机三轴稳定控制(2)____根据目标俯仰角,实现俯仰稳定化控制,计算出升降舵输出
c语言·单片机·算法·架构·无人机
白鱼不小白1 小时前
stm32 USART串口协议与外设(程序)——江协教程踩坑经验分享
stm32·单片机·嵌入式硬件
S,D1 小时前
MCU引脚的漏电流、灌电流、拉电流区别是什么
驱动开发·stm32·单片机·嵌入式硬件·mcu·物联网·硬件工程
芯岭技术4 小时前
PY32F002A单片机 低成本控制器解决方案,提供多种封装
单片机·嵌入式硬件
youmdt5 小时前
Arduino IDE ESP8266连接0.96寸SSD1306 IIC单色屏显示北京时间
单片机·嵌入式硬件
Meraki.Zhang5 小时前
【STM32实践篇】:I2C驱动编写
stm32·单片机·iic·驱动·i2c
几个几个n7 小时前
STM32-第二节-GPIO输入(按键,传感器)
单片机·嵌入式硬件
Despacito0o11 小时前
ESP32-s3摄像头驱动开发实战:从零搭建实时图像显示系统
人工智能·驱动开发·嵌入式硬件·音视频·嵌入式实时数据库
门思科技11 小时前
设计可靠 LoRaWAN 设备时需要考虑的关键能力
运维·服务器·网络·嵌入式硬件·物联网