第七章 STM32内部FLASH读写

发布于:2025-07-09 ⋅ 阅读:(12) ⋅ 点赞:(0)

芯片: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存入的数据


网站公告

今日签到

点亮在社区的每一天
去签到