STM32H7使用HAL驱动写Flash的HardFault及数据读取异常问题
STM32H7 Flash 结构体读写问题排查与修复
问题概述
需将约680字节的Persistent_data结构体写入STM32H757XIH6的Bank 2 Sector 1 Flash(起始地址0x08100000),基于HAL驱动开发时遇到两个问题:
- 初始代码调用
HAL_FLASH_Program触发HardFault,追踪到stm32h7xx_hal_flash.c的*dest_addr = *src_addr;处,栈信息显示非法访问0xffffffec地址。 - 按256位/32字节对齐要求修改代码后,写入无异常,但读取回的数据中
data_header不匹配,指针或内存遍历逻辑存在问题。
初始代码片段
typedef struct { uint32_t data_header; Parameter parameter[NUMBER_PARAMETERS]; int screen_paras[MAX_SCREENS][MAX_WIDGETS]; } Persistent_data; void save_data() { Persistent_data save_data; uint32_t data_header = DATA_HEADER; save_data.data_header = data_header; memcpy(save_data.parameter, parameter, sizeof(save_data.parameter)); memcpy(save_data.screen_paras, screen_paras, sizeof(save_data.screen_paras)); size_t size = sizeof(Persistent_data); uint32_t word_count = (size + 3) / 4; //32bit对齐 Flash_Write(FLASH_START_ADDRESS, (uint32_t*)&save_data, word_count); } HAL_StatusTypeDef Flash_Write(uint32_t address, uint32_t* data, uint32_t length) { HAL_StatusTypeDef status = HAL_OK; // 检查地址是否在Flash范围内 if (address < FLASH_USER_START_ADDR || (address + length) > FLASH_USER_END_ADDR) { return HAL_ERROR; } // 解锁Flash HAL_FLASH_Unlock(); // 擦除Flash扇区 FLASH_EraseInitTypeDef eraseInitStruct = { .TypeErase = FLASH_TYPEERASE_SECTORS, .Banks = FLASH_BANK_2, .Sector = FLASH_SECTOR_1, .NbSectors = 1, .VoltageRange = FLASH_VOLTAGE_RANGE_3, }; uint32_t sectorError = 0; status = HAL_FLASHEx_Erase(&eraseInitStruct, §orError); if (status != HAL_OK) { HAL_FLASH_Lock(); return status; } // 写入数据 for (int i = 0; i < length / 4; i++) { // 写入32位字 status = HAL_FLASH_Program(FLASH_TYPEPROGRAM_FLASHWORD, address, data[i]); if (status != HAL_OK) { break; } address += 4; // 地址增加4字节(32位) } // 锁定Flash HAL_FLASH_Lock(); return status; }
更新后代码片段
typedef struct { uint32_t data_header; Parameter parameter[NUMBER_PARAMETERS]; int screen_paras[MAX_SCREENS][MAX_WIDGETS]; } Persistent_data; void save_data() { Persistent_data save_data; uint32_t data_header</think_never_used_51bce0c785ca2f68081bfa7d91973934>= DATA_HEADER; save_data.data_header = data_header; memcpy(save_data.parameter, parameter, sizeof(save_data.parameter)); memcpy(save_data.screen_paras, screen_paras, sizeof(save_data.screen_paras)); size_t size = sizeof(Persistent_data); uint32_t word_count = (size + 31) / 32; //32字节对齐 Flash_Write(FLASH_START_ADDRESS, (uint32_t*)&save_data, word_count); } void load_data() { Persistent_data load_data; size_t size = sizeof(Persistent_data); uint32_t word_count = (size + 31) / 32; //对齐 Flash_Read(FLASH_START_ADDRESS, (uint32_t*)&load_data, word_count); if (load_data.data_header == DATA_HEADER) { memcpy(parameter, load_data.parameter, sizeof(parameter)); memcpy(screen_paras, load_data.screen_paras, sizeof(screen_paras)); } } HAL_StatusTypeDef Flash_Write(uint32_t address, uint32_t* data, uint32_t length) { HAL_StatusTypeDef status = HAL_OK; // 检查地址是否在Flash范围内 if (address < FLASH_USER_START_ADDR || (address + length) > FLASH_USER_END_ADDR) { return HAL_ERROR; } // 解锁Flash HAL_FLASH_Unlock(); // 擦除Flash扇区 FLASH_EraseInitTypeDef eraseInitStruct = { .TypeErase = FLASH_TYPEERASE_SECTORS, .Banks = FLASH_BANK_2, .Sector = FLASH_SECTOR_1, .NbSectors = 1, .VoltageRange = FLASH_VOLTAGE_RANGE_3, }; uint32_t sectorError = 0; status = HAL_FLASHEx_Erase(&eraseInitStruct, §orError); if (status != HAL_OK) { HAL_FLASH_Lock(); return status; } // 写入数据 for (int i = 0; i < length / 32; i++) { // 写入32字节(256位)字 status = HAL_FLASH_Program(FLASH_TYPEPROGRAM_FLASHWORD, address, (uint32_t)&data[i * 8]); if (status != HAL_OK) { break; } address += 32; // 地址增加32字节(256位) } // 锁定Flash HAL_FLASH_Lock(); return status; } /** * @brief 从Flash读取数据 * @param address 起始读取地址(必须256位对齐) * @param data 存储读取数据的缓冲区指针 * @param length 读取字节数(必须是32的倍数) */ void Flash_Read(uint32_t address, uint32_t* data, uint32_t length) { if (address < FLASH_USER_START_ADDR || (address + length) > FLASH_USER_END_ADDR) { return; // 地址越界 } for (uint32_t i = 0; i < length / 32; i++) { // 读取32字节(256位)字 // 从指定地址读取32字节(256位)数据 for (uint32_t j = 0; j < 8; j++) { data[i * 8 + j] = *(__IO uint32_t*)(address + j * 4); // 每个uint32_t占4字节 } address += 32; // 地址增加32字节(256位) } }
问题分析与修复方案
1. 初始HardFault根源
FLASH_TYPEPROGRAM_FLASHWORD是STM32H7的256位编程模式,要求传入的data参数是指向32字节对齐缓冲区的指针。初始代码中直接传入data[i](单个32位数值),HAL内部会将该数值当作指针解析,导致访问非法地址0xffffffec,触发HardFault。
2. 更新后读取数据错误的核心问题
- 参数定义不一致:
save_data和load_data中计算的word_count是32字节块数,但Flash_Write和Flash_Read的length参数被当作字节数处理,导致循环次数计算错误,读写数据量不匹配。 - 结构体对齐缺失:
Persistent_data结构体未显式设置32字节对齐,内存布局可能存在填充,导致Flash中的数据偏移与结构体字段不对应,读取后data_header错位。 - Flash_Write循环逻辑错误:循环条件
i < length / 32错误,若length是块数,应直接使用i < length;另外(uint32_t)&data[i * 8]的指针转换未确保缓冲区对齐,存在风险。 - Flash_Read调用参数错误:
load_data传入的word_count是块数,但函数注释要求length是字节数,导致读取总字节数不足,结构体数据不完整。
3. 修复后的关键调整
(1)确保结构体对齐
修改结构体定义,添加32字节对齐属性:
typedef struct __attribute__((aligned(32))) { uint32_t data_header; Parameter parameter[NUMBER_PARAMETERS]; int screen_paras[MAX_SCREENS][MAX_WIDGETS]; } Persistent_data;
(2)统一参数定义
将Flash_Write和Flash_Read的length参数改为总字节数,内部计算32字节块数:
// Flash_Write修改 uint32_t num_blocks = (length + 31) / 32; for (int i = 0; i < num_blocks; i++) { status = HAL_FLASH_Program(FLASH_TYPEPROGRAM_FLASHWORD, address, (uint32_t)(data + i * 8)); if (status != HAL_OK) break; address += 32; } // Flash_Read修改 uint32_t num_blocks = (length + 31) / 32; for (uint32_t i = 0; i < num_blocks; i++) { for (uint32_t j = 0; j < 8; j++) { if (address + j*4 > FLASH_USER_END_ADDR) break; // 边界检查 data[i * 8 + j] = *(__IO uint32_t*)(address + j * 4); } address += 32; }
(3)修正读写调用参数
// save_data中调用 Flash_Write(FLASH_START_ADDRESS, (uint32_t*)&save_data, sizeof(Persistent_data)); // load_data中调用 Flash_Read(FLASH_START_ADDRESS, (uint32_t*)&load_data, sizeof(Persistent_data));
(4)地址边界检查优化
在Flash_Write中修正地址合法性判断:
if (address < FLASH_USER_START_ADDR || (address + length) > FLASH_USER_END_ADDR) { return HAL_ERROR; }
内容的提问来源于stack exchange,提问作者Graeme Coard
相关产品推荐
相关产品推荐

