stm32cubeide 内部flash以结构体形式读写数据
在FLASH中读写结构体
⚠ 注意事项
-
编程(写数据)地址要对齐
写数据时,我们要指定写入的地址,如果写入地址为非对齐,则会出现编程对齐错误。
比如遵循32位(4字节)地址对齐,你的地址只能是4的倍数。0x08001000正确,0x08001001错误。
不同型号对齐宽度可能不同,有的32位、有的128位等,可通过“取余”判断地址。比如我遇到在
EEPROM
中写一个结构体时,下面这种会有问题,最后一个数据会写入失败。将uint8_t ID;
改为uint32_t ID;
则正常。typedef struct { // uint8_t ID; uint32_t ID; float zero; float dutyCorr; float fittingCorr; float initialTemp; } usrflash;
main.c
usrflash dtl645Config = {0};
...
// 读
FLASH_EEPROM_Read_struct(0x0801FC00, &dtl645Config);
// 写
FLASH_EEPROM_Write_struct(0x0801FC00, &dtl645Config);
flash.h
#ifndef __FLASH_H
#define __FLASH_H
#ifdef __cplusplus
extern "C"
{
#endif
#include "main.h"
// 在 FLASH 写入的结构体变量的类型。
// 第一个成员变量的变量名(ID)不要随便改;
// 变量类型也千万别瞎改。
typedef struct
{
uint8_t ID; // ID
uint8_t addr[6]; // 终端地址
uint8_t baud; // 波特率
uint8_t residualCurrent; // 剩余电流值
uint8_t warningValue; // 剩余电流预警值(百分比)
uint8_t actionValue; // 剩余电流动作值
uint8_t lndt; // 极限不驱动时间
uint16_t trippingTimes; // 跳闸次数mi
uint8_t password[3]; // 密码
uint8_t sSwitch; // 动作状态(0为断开,1为闭合)(分闸/合闸)
uint8_t sStatus; // 运行状态(0为正常,1为警告,2为越限)
} usrflash;
// 在FLASH中写一个字(32bit)
void FLASH_EEPROM_Write(uint32_t a, uint32_t n);
// 从FLASH中读取一个字(32bit)
uint32_t FLASH_EEPROM_Read(uint32_t addr);
// 在FLASH中写一个结构体(usrflash 类型的结构体)
void FLASH_EEPROM_Write_struct(uint32_t addr, usrflash *userinfo);
// 从FLASH中读取一个结构体(usrflash 类型的结构体)
void FLASH_EEPROM_Read_struct(uint32_t addr, usrflash *userinfo);
#ifdef __cplusplus
}
#endif
#endif /* __FLASH_H */
flash.c
//***************************************************************************************************
// flash.c 串口相关功能实现
//
// Includes
// ******************************************************************************************
#include "flash.h"
//***************************************************************************************************
// 在FLASH中写一个字(32bit)
// 传入参数为写入的地址和要写入的数据, 写入数据以字为单位,每个字占32bit即4Byte.
// 擦除时将擦除此地址所在的一整页。
//
void FLASH_EEPROM_Write(uint32_t addr, uint32_t n)
{
// FLASH 擦除操作 --------------------------------------------------------------/
HAL_FLASH_Unlock(); //解锁
uint32_t PageError = 0; // 如果出现错误这个变量会被置为出错的FLASH地址
FLASH_EraseInitTypeDef EraseInitStruct; // 定义结构体
EraseInitStruct.TypeErase = FLASH_TYPEERASE_PAGES; // Flash执行页面只做擦除操作
EraseInitStruct.PageAddress = addr; // 要擦除的地址
EraseInitStruct.NbPages = 1; // 要擦除的页数
if (HAL_FLASHEx_Erase(&EraseInitStruct, &PageError) == HAL_OK)
; // 擦除此地址所在的一整页
// FLASH 写入操作 --------------------------------------------------------------/
HAL_FLASH_Program(FLASH_TYPEPROGRAM_WORD, addr, n); // 向FLASH中写入
HAL_FLASH_Lock(); // 锁住FLASH
}
//***************************************************************************************************
// 从FLASH中读取一个字(32bit)
// 入口参数为要读取的FLASH的地址,返回值为uint32_t变量
//
uint32_t FLASH_EEPROM_Read(uint32_t addr)
{
uint32_t pValue = *(__IO uint32_t*)(addr);
return pValue;
}
//***************************************************************************************************
// 在FLASH中写一个结构体(usrflash 类型的结构体,这个类型在 flash.h 中定义的).
// 传入参数为要写入的地址和 usrflash 型结构体变量的取地址。
// 擦除时将擦除此地址所在的一整页。
// STM32F103C8 的 FLASH 中每页的大小为 1k, 写入时需注意数据量不要太大。
//
void FLASH_EEPROM_Write_struct(uint32_t addr, usrflash* userinfo)
{
uint8_t y = 0;
// FLASH 擦除操作 --------------------------------------------------------------/
HAL_FLASH_Unlock(); //解锁
uint32_t PageError = 0; // 如果出现错误这个变量会被置为出错的FLASH地址
FLASH_EraseInitTypeDef EraseInitStruct; // 定义结构体
EraseInitStruct.TypeErase = FLASH_TYPEERASE_PAGES; // Flash执行页面只做擦除操作
EraseInitStruct.PageAddress = addr; // 要擦除的地址
EraseInitStruct.NbPages = 1; // 要擦除的页数
if (HAL_FLASHEx_Erase(&EraseInitStruct, &PageError) == HAL_OK); // 擦除此地址所在的一整页
// FLASH 写入操作 --------------------------------------------------------------/
for (uint8_t i = 0; i <= sizeof(usrflash); i += 4) {
if (HAL_FLASH_Program(FLASH_TYPEPROGRAM_WORD, addr + i, *((__IO uint32_t*)(&userinfo->ID + (y++)))) != HAL_OK) //这里的数据由于是结构体,需要从里面取值,一定要以第一个成员的地址开始偏移,不能使用结构体自身的地址来偏移,否则数据会出错
//结构体是连续的地址,存到flash里的还要必须字节对齐,所以不能用结构体的连续地址进行循环,写入的流程是flash的第一个地址写入结构体第一个地址,flash的第二个地址+4,写入结构体第二个地址里的数据一直循环直到存完毕,还有一个方法是在结构体中写入没有用的数据让结构体存有用的数据的地址与flash里写的数据一致就可以把y++替换成i,并且把i的循环次数修改对即可
{
HAL_FLASH_Lock();
return;
}
}
HAL_FLASH_Lock();
}
//***************************************************************************************************
// 从FLASH中读取一个结构体(usrflash 类型的结构体,这个类型在 flash.h 中定义的).
// 入口参数为要读取的 FLASH 的地址和接收数据的 usrflash 型结构体变量的取地址。无返回值。
// 为了避免数据出错,读操作一定要和写操作对应,写是按双字写,读就要按双字读,否则就需要解决大小端的问题。
//
void FLASH_EEPROM_Read_struct(uint32_t addr, usrflash* userinfo)
{
uint8_t y = 0;
for (uint8_t i = 0; i <= sizeof(usrflash); i += 4) {
*((uint32_t*)(&userinfo->ID + (y++))) = *(__IO uint32_t*)(addr + i); //注意赋值的左边,必须要用结构体第一个成员的地址来偏移,双字偏移量是8
}
//这样获取的结构体内容,可以直接通过结构体变量或者结构体指针来访问了.
}