FLASH模块提供了灵活的非易失存储能力,支持高效的程序存储与数据保存。其保护机制(读/写保护)和选项字节配置增强了系统安全性,而编程与擦除操作的硬件流程需严格遵循时序和电源要求。合理使用FLASH功能,可满足嵌入式系统对代码存储、参数保存及固件升级的核心需求。
1. FLASH存储器容量与结构
- 容量:STM32F103C8T6内置64KB的FLASH存储器,用于存储用户程序代码和常量数据。
- 架构:
- 主存储块(Main Memory):包含64KB的存储空间,分为若干页。对于中容量产品(如C8T6),每页大小为1KB,共64页(0x0800 0000 - 0x0800 FFFF)。
- 系统存储器(System Memory):位于独立区域(0x1FFF F000起),存储出厂预置的BootLoader程序,用于通过串口/USB等接口下载程序。
- 选项字节(Option Bytes):位于0x1FFF F800,用于配置芯片的保护机制和功能参数(如读保护、写保护、硬件看门狗等)。
2. FLASH访问模式
- CPU直接访问:FLASH支持通过总线接口直接读取数据,CPU可像访问RAM一样读取FLASH中的代码或常量。
- 编程与擦除:需通过控制寄存器(FLASH_CR)发送特定命令,操作期间CPU暂停执行或等待操作完成。
3. 编程与擦除操作
- 页擦除:最小擦除单位为1页(1KB),擦除操作将整页数据置为全1(0xFF)。
- 编程操作:以半字(16位)或字(32位)为单位写入数据。写入前需确保目标区域已擦除。
- 操作流程:
- 解锁FLASH:向FLASH_KEYR寄存器写入特定密钥(0x45670123和0xCDEF89AB)以解除写保护。
- 发送命令:通过FLASH_CR寄存器选择擦除或编程操作。
- 等待完成:轮询FLASH_SR寄存器的BSY位,直到操作结束。
- 锁定FLASH:操作完成后重新锁定FLASH_CR寄存器,防止误操作。
4. 保护机制
- 读保护(RDP, Read Protection):
- 激活后禁止通过调试接口(如JTAG/SWD)读取FLASH内容。
- 通过选项字节的RDP位配置,解除保护需执行全片擦除。
- 写保护(WRP, Write Protection):
- 防止意外修改FLASH内容,可针对特定页设置写保护。
- 通过选项字节的WRPx位配置受保护的页范围。
- 硬件看门狗:选项字节可配置硬件看门狗在程序启动后自动启用。
5. 选项字节(Option Bytes)
- 功能配置:
- RDP(读保护级别):0xAA表示禁用读保护,其他值激活保护。
- USER:配置硬件看门狗、停机/待机模式复位等。
- WRPx:设置受写保护的页范围(如WRP0~WRP3对应不同的页组)。
- 修改流程:
- 解锁选项字节(向FLASH_OPTKEYR写入密钥)。
- 擦除选项字节(触发全片擦除)。
- 重新编程选项字节并锁定。
6. 关键特性与限制
- 操作时间:
- 页擦除时间约40ms,半字编程时间约40μs。
- 操作期间需保持电源稳定,否则可能导致数据损坏。
- 耐久性:FLASH支持约10,000次擦写循环,超出后可能导致存储单元失效。
- 数据保留:在-40°C至85°C环境下,数据可保存至少10年。
7. 应用场景
- 程序存储:存放用户代码和常量数据(如查找表、配置参数)。
- 数据存储:通过编程操作保存运行时数据(需注意擦写次数限制)。
- 固件升级:结合BootLoader实现远程固件更新。
8.上完整程序模版,复制可用,已详细注释
#include "stm32f10x.h" // STM32标准库头文件
#include "stm32f10x_flash.h" // FLASH库头文件
/*======== 用户配置区 ========*/
#define FLASH_START_ADDR 0x08008000 // FLASH操作起始地址(需对齐页地址)
#define FLASH_PAGE_SIZE 0x400 // 页大小(1KB,STM32F103C8T6中容量型号)
#define FLASH_DATA_SIZE 256 // 待写入数据长度(单位:半字,即16位)
uint16_t flashWriteData[FLASH_DATA_SIZE] = {0x1234, 0x5678}; // 示例数据(需为半字数组)
uint16_t flashReadData[FLASH_DATA_SIZE]; // 数据读取缓冲区
/*======== FLASH解锁函数 ========*/
void FLASH_Unlock(void) {
FLASH_Unlock(); // 解锁FLASH控制寄存器(必须调用)
FLASH_ClearFlag(FLASH_FLAG_EOP | FLASH_FLAG_PGERR | FLASH_FLAG_WRPRTERR); // 清除所有错误标志
}
/*======== FLASH页擦除函数 ========*/
uint8_t FLASH_ErasePage(uint32_t pageAddress) {
FLASH_Status status;
// 检查地址是否页对齐(地址必须能被FLASH_PAGE_SIZE整除)
if (pageAddress % FLASH_PAGE_SIZE != 0) return 1; // 地址错误
status = FLASH_ErasePage(pageAddress); // 执行页擦除
if (status != FLASH_COMPLETE) return 2; // 擦除失败
return 0; // 成功
}
/*======== FLASH数据写入函数 ========*/
uint8_t FLASH_WriteData(uint32_t startAddr, uint16_t *data, uint16_t len) {
uint16_t i;
FLASH_Status status;
// 检查地址和长度有效性(地址需为偶数字节,长度不超过FLASH容量)
if (startAddr < 0x08000000 || startAddr + len*2 > 0x0800FFFF) return 1;
if (len == 0) return 2;
for (i = 0; i < len; i++) {
status = FLASH_ProgramHalfWord(startAddr + i*2, data[i]); // 半字写入
if (status != FLASH_COMPLETE) return 3; // 写入失败
}
return 0; // 成功
}
/*======== FLASH数据读取函数 ========*/
void FLASH_ReadData(uint32_t startAddr, uint16_t *buffer, uint16_t len) {
uint16_t i;
for (i = 0; i < len; i++) {
buffer[i] = *(__IO uint16_t*)(startAddr + i*2); // 强制转换为半字指针读取
}
}
/*======== 主函数示例 ========*/
int main(void) {
uint8_t ret;
// 1. 初始化系统时钟(需根据实际情况配置)
SystemInit();
// 2. 解锁FLASH并擦除目标页
FLASH_Unlock();
ret = FLASH_ErasePage(FLASH_START_ADDR);
if (ret != 0) {
// 擦除失败处理(如记录错误码)
while(1);
}
// 3. 写入数据到FLASH
ret = FLASH_WriteData(FLASH_START_ADDR, flashWriteData, FLASH_DATA_SIZE);
if (ret != 0) {
// 写入失败处理
while(1);
}
// 4. 锁定FLASH以防止意外修改
FLASH_Lock();
// 5. 验证数据:读取FLASH并比较
FLASH_ReadData(FLASH_START_ADDR, flashReadData, FLASH_DATA_SIZE);
if (memcmp(flashWriteData, flashReadData, FLASH_DATA_SIZE*2) != 0) {
// 数据校验失败处理
while(1);
}
while(1) {
// 主程序逻辑(如使用存储的数据)
}
}
9.关键参数详解
-
FLASH_START_ADDR
- 操作地址:必须为页对齐地址(如0x08008000),建议避开程序代码区(通常从0x08000000开始存放程序)。
-
FLASH_PAGE_SIZE
- 页大小:STM32F103C8T6为中容量型号,页大小为1KB(0x400)。高容量型号(如1MB FLASH)页大小为2KB。
-
FLASH_ProgramHalfWord
- 写入单位:STM32 FLASH仅支持半字(16位)或字(32位)写入,需避免单字节操作。
-
FLASH_FLAG_EOP
- 操作完成标志:每次FLASH操作(擦除/写入)完成后需检查此标志,或调用
FLASH_ClearFlag
清除。
- 操作完成标志:每次FLASH操作(擦除/写入)完成后需检查此标志,或调用
10.功能说明
- 安全解锁/锁定:通过
FLASH_Unlock()
和FLASH_Lock()
防止误操作,确保数据安全。 - 页擦除管理:擦除最小单位为1页,擦除后所有位变为1(0xFFFF)。
- 数据校验:写入后通过
memcmp
比对数据完整性,防止写入错误。 - 错误处理:函数返回错误码,便于调试和异常恢复。
11.使用流程
- 配置起始地址:确保
FLASH_START_ADDR
位于用户FLASH区域(非代码区)。 - 数据对齐:写入数据需为半字数组(16位对齐),地址必须为偶数。
- 操作顺序:必须按
解锁→擦除→写入→锁定
的顺序操作,否则会触发硬件错误。 - 功耗管理:FLASH操作期间需保持稳定电源,避免电压跌落导致数据损坏。
12.注意事项
- 中断处理:FLASH操作期间需禁用全局中断(
__disable_irq()
),防止打断关键操作。 - 擦写次数:FLASH典型擦写寿命约10,000次,频繁操作需配合磨损均衡算法。
- 写保护:通过选项字节(Option Bytes)可设置页写保护,防止误擦除关键数据。
- 代码保护:启用读保护(RDP)后,调试器无法读取FLASH内容,需谨慎操作。
13.选项字节配置示例
void FLASH_ConfigureOptionBytes(void) {
FLASH_Unlock(); // 解锁FLASH
FLASH_OB_Unlock(); // 解锁选项字节
FLASH_OB_Erase(); // 擦除选项字节(触发全片擦除!)
// 配置读保护级别(RDP Level 1:启用保护)
FLASH_OB_RDPConfig(OB_RDP_Level_1);
// 配置硬件看门狗(上电后自动启用)
FLASH_OB_USERConfig(OB_IWDG_SW, ENABLE); // 启用硬件看门狗
FLASH_OB_Launch(); // 应用选项字节配置
FLASH_Lock(); // 锁定FLASH
}