stm32cubeide 内部flash以结构体形式读写数据

发布时间 2023-07-24 14:24:30作者: 虎啸岳林

在FLASH中读写结构体

⚠ 注意事项

  1. 编程(写数据)地址要对齐

    写数据时,我们要指定写入的地址,如果写入地址为非对齐,则会出现编程对齐错误。
    比如遵循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)
{
    // 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 + i))) != HAL_OK) //这里的数据由于是结构体,需要从里面取值,一定要以第一个成员的地址开始偏移,不能使用结构体自身的地址来偏移,否则数据会出错
        {
            HAL_FLASH_Lock();
            return;
        }
    }
    HAL_FLASH_Lock();
}

//***************************************************************************************************
// 从FLASH中读取一个结构体(usrflash 类型的结构体,这个类型在 flash.h 中定义的).
// 入口参数为要读取的 FLASH 的地址和接收数据的 usrflash 型结构体变量的取地址。无返回值。
// 为了避免数据出错,读操作一定要和写操作对应,写是按双字写,读就要按双字读,否则就需要解决大小端的问题。
// 
void FLASH_EEPROM_Read_struct(uint32_t addr, usrflash* userinfo)
{
    for (uint8_t i = 0; i < sizeof(usrflash); i += 4) {
        *((uint32_t*)(&userinfo->ID + i)) = *(__IO uint32_t*)(addr + i); //注意赋值的左边,必须要用结构体第一个成员的地址来偏移,双字偏移量是8
    }
    //这样获取的结构体内容,可以直接通过结构体变量或者结构体指针来访问了.
}