第七章 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–0x07FFFFFFFlash 别名(通过 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存入的数据

<think>好的,用户想了解如何使用STM32 HAL库进行FLASH读写操作,需要教程或示例代码。首先,我需要回忆一下STM32FLASH操作流程。记得HAL库提供了一些函数来简化FLASH的编程,比如解锁、擦除、写入和上锁的步骤。用户提供的引用中提到了HAL_FLASH_Unlock()和HAL_FLASH_Program()函数,这应该是最关键的部分。 接下来,需要整理正确的操作步骤。首先,必须解锁FLASH,因为默认情况下FLASH是被锁定的,防止误操作。然后,擦除对应的扇区,因为FLASH写入前必须先擦除。擦除时要注意扇区的地址和大小,不同型号的STM32可能有不同的扇区划分,用户需要根据具体型号调整。例如,引用[1]和[2]中的例子可能使用了不同的芯片,但流程是类似的。 写入数据时,用户需要选择正确的编程类型,比如32位、16位等,这取决于使用的HAL_FLASH_Program函数的参数。例如,引用[2]中的示例使用了FLASH_TYPEPROGRAM_WORD,也就是32位写入。如果用户想写入其他类型的数据,需要调整这个参数。 读取数据相对简单,直接通过指针访问对应的地址即可。但要注意地址是否有效,以及数据是否对齐。此外,还需要处理可能的错误,比如擦除失败或写入失败,这时候需要检查错误标志,HAL库提供了HAL_FLASH_GetError()函数来获取错误信息。 用户提供的引用中还提到了SPI/QSPI操作外部Flash的情况,但用户的问题是关于内部FLASH读写,所以可以暂时忽略外部Flash的部分,专注于内部FLASH的操作步骤。 可能用户在实际操作中会遇到的问题包括地址错误、未正确擦除扇区导致写入失败,或者没有解锁FLASH导致编程错误。需要在示例代码中加入错误处理的部分,比如检查擦除是否完成,编程是否成功,并在必要时加入延时或重试机制。 另外,需要注意不同系列的STM32芯片在FLASH操作上的差异,比如F1、F4、H7等系列可能有不同的函数或参数。用户需要根据自己使用的芯片型号查找对应的HAL库文档,确保代码的正确性。 最后,生成相关问题时,应该围绕FLASH读写的常见问题、优化方法、应用场景以及错误处理等方面,帮助用户进一步理解和解决可能遇到的困难。</think>### STM32 HAL库FLASH读写操作详解 #### 一、操作流程与原理 STM32内部FLASH操作需要遵循$3 \geq 1$个关键步骤: 1. 解锁FLASH控制器:使用$HAL\_FLASH\_Unlock()$函数解除写保护 2. 擦除目标扇区:FLASH编程前必须擦除对应扇区(擦除后数据全为$0xFF$) 3. 数据编程:通过$HAL\_FLASH\_Program()$写入数据 4. 重新上锁:使用$HAL\_FLASH\_Lock()$恢复保护机制[^1] #### 二、核心代码实现(STM32F4系列) ```c // 扇区擦除(以第5扇区为例) void Flash_EraseSector5(void) { FLASH_EraseInitTypeDef EraseInit; uint32_t SectorError = 0; HAL_FLASH_Unlock(); EraseInit.TypeErase = FLASH_TYPEERASE_SECTORS; EraseInit.Sector = FLASH_SECTOR_5; EraseInit.NbSectors = 1; EraseInit.VoltageRange = FLASH_VOLTAGE_RANGE_3; if(HAL_FLASHEx_Erase(&EraseInit, &SectorError) != HAL_OK) { Error_Handler(); // 错误处理 } HAL_FLASH_Lock(); } // 32位数据写入 void Flash_Write(uint32_t StartAddress, uint32_t data) { if(StartAddress < FLASH_BASE || StartAddress > (FLASH_BASE + FLASH_SIZE)) return; // 地址有效性检查 HAL_FLASH_Unlock(); HAL_FLASH_Program(FLASH_TYPEPROGRAM_WORD, StartAddress, data); HAL_FLASH_Lock(); // 操作完成后立即上锁[^2] } // 数据读取 uint32_t Flash_Read(uint32_t address) { return *(__IO uint32_t*)address; // 直接指针访问 } ``` #### 三、关键注意事项 1. **地址对齐**:32位写入要求地址是$4 \mid address$,16位写入要求$2 \mid address$ 2. **电压范围**:需根据实际工作电压选择$FLASH\_VOLTAGE\_RANGE\_x$ 3. **中断处理**:编程期间应关闭中断,避免触发FLASH操作错误 4. **数据验证**:建议写入后读取验证,保证$data_{written} = data_{read}$ #### 四、典型应用场景 1. 参数存储:保存设备校准参数(如$ADC_{offset}$) 2. 固件升级:实现IAP(In-Application Programming)功能 3. 数据日志:存储关键事件记录(需配合磨损均衡算法)
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值