1. 物理结构与电气特性
2. 存储器组织方式
3. 访问权限与保护机制
1. 编程算法详解
2. 擦除过程分析
3. Flash 控制器寄存器详解
1. 基于 HAL 库的 Flash 操作示例
/* 包含必要的头文件 */
#include "main.h"
#include "flash.h"
/* 定义Flash操作参数 */
#define FLASH_START_ADDR 0x08070000 // 选择第11扇区(128KB)作为数据存储区
#define FLASH_SECTOR FLASH_SECTOR_11
#define FLASH_DATA_SIZE sizeof(osc_run_msg_def)
/* 定义数据结构 */
typedef struct {
uint8_t run_mode; // 运行模式(0:停止, 1:单触发, 2:连续触发)
uint8_t trig_type; // 触发类型(0:上升沿, 1:下降沿, 2:双边沿)
uint16_t trig_vol_level_ch[2]; // 触发电压阈值(CH1, CH2)
uint16_t sample_rate; // 采样率(100k-10M)
uint16_t record_length; // 记录长度(100-10000点)
uint8_t display_mode; // 显示模式(0:正常, 1:滚动, 2:XY)
uint8_t reserved[10]; // 保留字节
} osc_run_msg_def;
/* 全局变量 */
osc_run_msg_def osc_run_msg; // RAM中的数据缓冲区
uint32_t flash_errors = 0; // Flash操作错误计数
/* 初始化Flash操作 */
void Flash_Init(void)
{
// 使能Flash时钟
__HAL_RCC_FLASH_CLK_ENABLE();
// 配置Flash等待周期(根据CPU频率调整)
// 例如: 168MHz时需配置为5个等待周期
FLASH->ACR = FLASH_ACR_PRFTEN | FLASH_ACR_ICEN | FLASH_ACR_DCEN | FLASH_ACR_LATENCY_5WS;
// 从Flash读取配置数据到RAM
Flash_ReadConfig(&osc_run_msg);
}
/* 解锁Flash */
HAL_StatusTypeDef Flash_Unlock(void)
{
return HAL_FLASH_Unlock();
}
/* 锁定Flash */
HAL_StatusTypeDef Flash_Lock(void)
{
return HAL_FLASH_Lock();
}
/* 擦除Flash扇区 */
HAL_StatusTypeDef Flash_EraseSector(uint32_t sector, uint8_t nSectors)
{
HAL_StatusTypeDef status;
FLASH_EraseInitTypeDef eraseInit;
uint32_t sectorError = 0;
eraseInit.TypeErase = FLASH_TYPEERASE_SECTORS;
eraseInit.Sector = sector;
eraseInit.NbSectors = nSectors;
eraseInit.VoltageRange = FLASH_VOLTAGE_RANGE_3; // 2.7V-3.6V
status = HAL_FLASHEx_Erase(&eraseInit, §orError);
if(status != HAL_OK) {
flash_errors++;
return status;
}
return HAL_OK;
}
/* 写入数据到Flash */
HAL_StatusTypeDef Flash_WriteData(uint32_t address, uint32_t *data, uint32_t size)
{
HAL_StatusTypeDef status;
uint32_t i;
for(i = 0; i < size; i++) {
status = HAL_FLASH_Program(FLASH_TYPEPROGRAM_WORD, address + (i * 4), data[i]);
if(status != HAL_OK) {
flash_errors++;
return status;
}
}
return HAL_OK;
}
/* 从Flash读取数据 */
void Flash_ReadData(uint32_t address, uint32_t *data, uint32_t size)
{
uint32_t i;
for(i = 0; i < size; i++) {
data[i] = *(__IO uint32_t*)(address + (i * 4));
}
}
/* 保存配置到Flash */
HAL_StatusTypeDef Flash_SaveConfig(osc_run_msg_def *config)
{
HAL_StatusTypeDef status;
uint32_t *pConfig = (uint32_t*)config;
uint32_t configSize = FLASH_DATA_SIZE / 4;
// 确保结构体大小是4的倍数
if(FLASH_DATA_SIZE % 4 != 0) {
configSize++;
}
// 解锁Flash
status = Flash_Unlock();
if(status != HAL_OK) {
return status;
}
// 擦除扇区
status = Flash_EraseSector(FLASH_SECTOR, 1);
if(status != HAL_OK) {
Flash_Lock();
return status;
}
// 写入数据
status = Flash_WriteData(FLASH_START_ADDR, pConfig, configSize);
// 锁定Flash
Flash_Lock();
return status;
}
/* 从Flash读取配置 */
void Flash_ReadConfig(osc_run_msg_def *config)
{
uint32_t *pConfig = (uint32_t*)config;
uint32_t configSize = FLASH_DATA_SIZE / 4;
// 确保结构体大小是4的倍数
if(FLASH_DATA_SIZE % 4 != 0) {
configSize++;
}
// 从Flash读取数据
Flash_ReadData(FLASH_START_ADDR, pConfig, configSize);
// 验证数据有效性(简单检查)
if(config->run_mode > 2 || config->trig_type > 2) {
// 数据无效,使用默认值
memset(config, 0, FLASH_DATA_SIZE);
config->run_mode = 1; // 默认单触发模式
config->trig_type = 0; // 默认上升沿触发
config->trig_vol_level_ch[0] = 2048; // 默认触发阈值为中点(12位ADC)
config->trig_vol_level_ch[1] = 2048;
config->sample_rate = 1000; // 默认1MHz采样率
config->record_length = 1000; // 默认1000点记录长度
config->display_mode = 0; // 默认正常显示模式
}
}
/* 示例: 在主程序中使用Flash存储功能 */
void Main_Function(void)
{
// 初始化Flash
Flash_Init();
// 主循环
while(1) {
// 检查是否需要保存配置(例如按键触发)
if(config_change_flag) {
// 保存当前配置到Flash
if(Flash_SaveConfig(&osc_run_msg) == HAL_OK) {
// 保存成功,更新状态LED
HAL_GPIO_WritePin(LED_SUCCESS_GPIO_Port, LED_SUCCESS_Pin, GPIO_PIN_SET);
HAL_Delay(200);
HAL_GPIO_WritePin(LED_SUCCESS_GPIO_Port, LED_SUCCESS_Pin, GPIO_PIN_RESET);
} else {
// 保存失败,错误处理
HAL_GPIO_WritePin(LED_ERROR_GPIO_Port, LED_ERROR_Pin, GPIO_PIN_SET);
// 增加重试逻辑...
}
config_change_flag = 0;
}
// 其他主程序逻辑...
}
}
2. 基于寄存器操作的 Flash 底层实现
/* Flash操作底层函数 */
/* 解锁Flash控制器 */
void FLASH_Unlock(void)
{
// 检查是否已锁定
if(FLASH->CR & FLASH_CR_LOCK) {
// 写入解锁序列
FLASH->KEYR = FLASH_KEY1;
FLASH->KEYR = FLASH_KEY2;
}
}
/* 锁定Flash控制器 */
void FLASH_Lock(void)
{
FLASH->CR |= FLASH_CR_LOCK;
}
/* 等待Flash操作完成 */
uint8_t FLASH_WaitForLastOperation(uint32_t Timeout)
{
uint8_t status = FLASH_COMPLETE;
// 等待操作完成或超时
while((FLASH->SR & FLASH_SR_BSY) && (Timeout != 0)) {
Timeout--;
}
if(Timeout == 0) {
status = FLASH_TIMEOUT;
} else {
// 检查错误标志
if(FLASH->SR & FLASH_SR_PGERR) {
status = FLASH_ERROR_PROGRAM;
}
if(FLASH->SR & FLASH_SR_WRPERR) {
status = FLASH_ERROR_WRITE_PROTECTION;
}
// 清除错误标志
FLASH->SR = FLASH_SR_PGERR | FLASH_SR_WRPERR | FLASH_SR_OPERR;
}
return status;
}
/* 擦除Flash扇区 */
uint8_t FLASH_EraseSector(uint32_t Sector, uint8_t VoltageRange)
{
uint8_t status = FLASH_COMPLETE;
// 检查参数
if((Sector > FLASH_SECTOR_11) ||
(VoltageRange > FLASH_VOLTAGE_RANGE_3)) {
return FLASH_ERROR_INVAL_PARAM;
}
// 等待上一个操作完成
status = FLASH_WaitForLastOperation(FLASH_TIMEOUT_VALUE);
if(status == FLASH_COMPLETE) {
// 设置电压范围
FLASH->CR &= ~FLASH_CR_VRP;
FLASH->CR |= VoltageRange << 14;
// 选择扇区
FLASH->CR &= ~FLASH_CR_SNB;
FLASH->CR |= Sector << 3;
// 使能扇区擦除
FLASH->CR |= FLASH_CR_SER;
// 开始擦除
FLASH->CR |= FLASH_CR_STRT;
// 等待操作完成
status = FLASH_WaitForLastOperation(FLASH_TIMEOUT_VALUE);
// 清除SER位
FLASH->CR &= ~FLASH_CR_SER;
}
return status;
}
/* 写入半字(16位)到Flash */
uint8_t FLASH_ProgramHalfWord(uint32_t Address, uint16_t Data)
{
uint8_t status = FLASH_COMPLETE;
// 等待上一个操作完成
status = FLASH_WaitForLastOperation(FLASH_TIMEOUT_VALUE);
if(status == FLASH_COMPLETE) {
// 使能编程
FLASH->CR |= FLASH_CR_PG;
// 写入数据
*(__IO uint16_t*)Address = Data;
// 等待操作完成
status = FLASH_WaitForLastOperation(FLASH_TIMEOUT_VALUE);
// 清除PG位
FLASH->CR &= ~FLASH_CR_PG;
}
return status;
}
/* 写入字(32位)到Flash */
uint8_t FLASH_ProgramWord(uint32_t Address, uint32_t Data)
{
uint8_t status = FLASH_COMPLETE;
// 等待上一个操作完成
status = FLASH_WaitForLastOperation(FLASH_TIMEOUT_VALUE);
if(status == FLASH_COMPLETE) {
// 使能编程
FLASH->CR |= FLASH_CR_PG;
// 写入低16位
*(__IO uint16_t*)Address = (uint16_t)Data;
// 等待低16位写入完成
status = FLASH_WaitForLastOperation(FLASH_TIMEOUT_VALUE);
if(status == FLASH_COMPLETE) {
// 写入高16位
*(__IO uint16_t*)(Address + 2) = (uint16_t)(Data >> 16);
// 等待高16位写入完成
status = FLASH_WaitForLastOperation(FLASH_TIMEOUT_VALUE);
}
// 清除PG位
FLASH->CR &= ~FLASH_CR_PG;
}
return status;
}
/* 写入双字(64位)到Flash */
uint8_t FLASH_ProgramDoubleWord(uint32_t Address, uint64_t Data)
{
uint8_t status = FLASH_COMPLETE;
// 等待上一个操作完成
status = FLASH_WaitForLastOperation(FLASH_TIMEOUT_VALUE);
if(status == FLASH_COMPLETE) {
// 使能编程
FLASH->CR |= FLASH_CR_PG;
// 写入低32位
*(__IO uint32_t*)Address = (uint32_t)Data;
// 等待低32位写入完成
status = FLASH_WaitForLastOperation(FLASH_TIMEOUT_VALUE);
if(status == FLASH_COMPLETE) {
// 写入高32位
*(__IO uint32_t*)(Address + 4) = (uint32_t)(Data >> 32);
// 等待高32位写入完成
status = FLASH_WaitForLastOperation(FLASH_TIMEOUT_VALUE);
}
// 清除PG位
FLASH->CR &= ~FLASH_CR_PG;
}
return status;
}
/* 读取Flash数据 */
uint32_t FLASH_ReadWord(uint32_t Address)
{
return *(__IO uint32_t*)Address;
}
/* 读取Flash半字 */
uint16_t FLASH_ReadHalfWord(uint32_t Address)
{
return *(__IO uint16_t*)Address;
}
1. 提高写入效率的技巧
// 使能预取缓冲区
FLASH->ACR |= FLASH_ACR_PRFTEN;
// 使能指令缓存
FLASH->ACR |= FLASH_ACR_ICEN;
// 使能数据缓存
FLASH->ACR |= FLASH_ACR_DCEN;
// 例如: 72MHz时设置为2个等待周期
FLASH->ACR &= ~FLASH_ACR_LATENCY;
FLASH->ACR |= FLASH_ACR_LATENCY_2WS;
2. 提高可靠性的方法
uint32_t CalculateCRC32(uint32_t *data, uint32_t size)
{
uint32_t i;
uint32_t crc = 0xFFFFFFFF;
for(i = 0; i < size; i++) {
crc = HAL_CRC_Calculate(&hcrc, &data[i], 1);
}
return crc;
}
uint16_t CalculateChecksum(uint8_t *data, uint32_t size)
{
uint32_t i;
uint16_t checksum = 0;
for(i = 0; i < size; i++) {
checksum += data[i];
}
return checksum;
}
// 配置扇区写保护
void FLASH_EnableSectorWriteProtection(uint32_t SectorMask)
{
FLASH_OBProgramInitTypeDef obInit;
HAL_FLASHEx_OBGetConfig(&obInit);
obInit.WRPState = OB_WRPSTATE_ENABLE;
obInit.WRPSector = SectorMask;
HAL_FLASHEx_OBProgram(&obInit);
}
if(__HAL_PWR_GET_FLAG(PWR_FLAG_VREFINTRDY) == RESET) {
// 电压不稳定,推迟写入操作
return;
}
3. 延长 Flash 寿命的策略
// 简化的磨损均衡实现
uint32_t GetNextFlashAddress(void)
{
static uint32_t currentSector = 0;
uint32_t address;
// 循环使用不同扇区
address = FLASH_START_ADDR + (currentSector * FLASH_SECTOR_SIZE);
currentSector = (currentSector + 1) % NUM_FLASH_SECTORS;
return address;
}
if(dirty_flag) {
Flash_SaveConfig(&config);
dirty_flag = 0;
}
#include "lz4.h"
void CompressAndSaveData(uint8_t *srcData, uint32_t srcSize)
{
uint8_t compressedData[1024];
int compressedSize;
// 压缩数据
compressedSize = LZ4_compress_default((char*)srcData, (char*)compressedData,
srcSize, sizeof(compressedData));
if(compressedSize > 0) {
// 保存压缩后的数据和大小
Flash_WriteData(FLASH_START_ADDR, (uint32_t*)&compressedSize, 1);
Flash_WriteData(FLASH_START_ADDR + 4, (uint32_t*)compressedData,
(compressedSize + 3) / 4);
}
}
1. 写入失败的常见原因及解决
问题表现 | 可能原因 | 解决方案 |
---|---|---|
BSY 位长时间置位 | 电压不稳定 | 检查电源稳定性,添加滤波电容 |
PGERR 错误 | 尝试对已编程位再次编程 | 确保先擦除再写入 |
WRPRTERR 错误 | 扇区被写保护 | 检查选项字节,解除写保护 |
写入数据错误 | 数据对齐问题 | 确保按字 (4 字节) 对齐写入 |
擦除时间过长 | Flash 温度过高 | 检查散热条件,降低工作温度 |
2. 数据损坏的检测与恢复
// 读取数据并验证CRC
uint32_t storedCRC, calculatedCRC;
// 读取存储的CRC
storedCRC = FLASH_ReadWord(FLASH_START_ADDR + FLASH_DATA_SIZE);
// 读取并计算数据CRC
calculatedCRC = CalculateCRC32((uint32_t*)FLASH_START_ADDR, FLASH_DATA_SIZE / 4);
if(storedCRC != calculatedCRC) {
// 数据损坏,使用默认值
LoadDefaultConfig();
}
// 双备份存储方案
#define FLASH_BACKUP_ADDR (FLASH_START_ADDR + FLASH_SECTOR_SIZE)
void SaveConfigWithBackup(osc_run_msg_def *config)
{
// 先写入主存储区
Flash_SaveConfig(config);
// 再写入备份区
Flash_SaveConfigAtAddress(config, FLASH_BACKUP_ADDR);
}
void LoadConfigWithRecovery(osc_run_msg_def *config)
{
// 先尝试从主存储区读取
Flash_ReadConfig(config);
// 验证数据有效性
if(!IsConfigValid(config)) {
// 主存储区数据无效,从备份区恢复
Flash_ReadConfigAtAddress(config, FLASH_BACKUP_ADDR);
// 若备份区也无效,使用默认值
if(!IsConfigValid(config)) {
LoadDefaultConfig(config);
}
}
}
3. Flash 操作对系统性能的影响
void LowPriorityTask(void)
{
// 检查是否需要保存配置
if(config_save_pending) {
Flash_SaveConfig(&config);
config_save_pending = 0;
}
}
// 配置DMA传输
HAL_DMA_Start(&hdma_memtomem_dma2_stream0,
(uint32_t)srcBuffer,
(uint32_t)dstBuffer,
dataSize);
// 等待DMA传输完成
HAL_DMA_PollForTransfer(&hdma_memtomem_dma2_stream0, HAL_DMA_FULL_TRANSFER, 100);
1. 存储配置参数的应用
2. 日志记录系统
#define LOG_BUFFER_SIZE 1024
#define LOG_FLASH_SECTOR FLASH_SECTOR_10
typedef struct {
uint32_t timestamp;
uint8_t logType;
uint16_t data[8];
} log_entry_t;
static log_entry_t logBuffer[LOG_BUFFER_SIZE];
static uint16_t logIndex = 0;
static uint8_t logDirty = 0;
void AddLogEntry(uint8_t type, uint16_t *data, uint8_t dataSize)
{
// 添加日志条目到缓冲区
logBuffer[logIndex].timestamp = HAL_GetTick();
logBuffer[logIndex].logType = type;
// 复制数据
for(uint8_t i = 0; i < dataSize && i < 8; i++) {
logBuffer[logIndex].data[i] = data[i];
}
// 更新索引
logIndex = (logIndex + 1) % LOG_BUFFER_SIZE;
logDirty = 1;
}
void SaveLogsToFlash(void)
{
if(logDirty) {
uint32_t flashAddr = FLASH_BASE + (LOG_FLASH_SECTOR * FLASH_SECTOR_SIZE);
// 解锁Flash
FLASH_Unlock();
// 擦除扇区
FLASH_EraseSector(LOG_FLASH_SECTOR, FLASH_VOLTAGE_RANGE_3);
// 写入日志数量
FLASH_ProgramWord(flashAddr, logIndex);
flashAddr += 4;
// 写入日志数据
for(uint16_t i = 0; i < logIndex; i++) {
FLASH_ProgramWord(flashAddr, logBuffer[i].timestamp);
flashAddr += 4;
FLASH_ProgramHalfWord(flashAddr, logBuffer[i].logType);
flashAddr += 2;
for(uint8_t j = 0; j < 8; j++) {
FLASH_ProgramHalfWord(flashAddr, logBuffer[i].data[j]);
flashAddr += 2;
}
}
// 锁定Flash
FLASH_Lock();
logDirty = 0;
}
}
3. 固件更新应用
#define BOOTLOADER_SIZE 0x4000 // 16KB Bootloader
#define APP1_START_ADDR 0x08004000 // 应用程序1起始地址
#define APP2_START_ADDR 0x08040000 // 应用程序2起始地址
#define APP_SIZE 0x3C000 // 每个应用程序最大60KB
typedef enum {
BOOT_APP1,
BOOT_APP2,
UPDATE_APP1,
UPDATE_APP2
} boot_mode_t;
// 启动模式存储在特定Flash位置
#define BOOT_MODE_ADDR 0x1FFF7800 // OTP区域起始地址
void SetBootMode(boot_mode_t mode)
{
FLASH_Unlock();
FLASH_ProgramHalfWord(BOOT_MODE_ADDR, (uint16_t)mode);
FLASH_Lock();
}
boot_mode_t GetBootMode(void)
{
return (boot_mode_t)FLASH_ReadHalfWord(BOOT_MODE_ADDR);
}
void JumpToApplication(uint32_t appAddress)
{
typedef void (*pFunction)(void);
uint32_t JumpAddress;
pFunction Jump_To_Application;
// 检查栈顶地址是否合法
if(((uint32_t*)appAddress)[0] > 0x20000000 &&
((uint32_t*)appAddress)[0] < 0x20040000) {
// 关闭所有中断
__disable_irq();
// 清除所有挂起的中断
NVIC->ICER[0] = 0xFFFFFFFF;
NVIC->ICER[1] = 0xFFFFFFFF;
NVIC->ICPR[0] = 0xFFFFFFFF;
NVIC->ICPR[1] = 0xFFFFFFFF;
// 设置MSP
__set_MSP(*(uint32_t*)appAddress);
// 跳转到应用程序
JumpAddress = *(uint32_t*)(appAddress + 4);
Jump_To_Application = (pFunction)JumpAddress;
Jump_To_Application();
}
}