GD32入门到实战33--用单片机内部FLASH保护产品参数
inflash.c
#include <stdint.h>
#include <stdbool.h>
#include <stdio.h>
#include "gd32f30x.h"#define FLASH_PAGE_SIZE 0x800 // 2K(1页大小)
#define FLASH_END_ADDRESS 0x0807FFFF // 512K(结束地址)/**
*******************************************************************
* @function 指定地址开始读出指定个数的数据
* @param readAddr,读取地址
* @param pBuffer,数组首地址
* @param numToRead,要读出的数据个数
* @return
*******************************************************************
*/
bool FlashRead(uint32_t readAddr, uint8_t *pBuffer, uint32_t numToRead)
{ if ((readAddr + numToRead) > FLASH_END_ADDRESS)//如果超过结束地址{return false;}uint32_t addr = readAddr; for (uint32_t i = 0; i < numToRead; i++) //读数据{*pBuffer = *(uint8_t *)addr;//强转成U8的指针,再访问地址addr = addr + 1; //读取地址+1pBuffer++; //数组+1}return true;
}/**
*******************************************************************
* @function 指定地址开始写入指定个数的数据
* @param writeAddr,写入地址
* @param pBuffer,数组首地址
* @param numToWrite,要写入的数据个数
* @return
*******************************************************************
*/
bool FlashWrite(uint32_t writeAddr, uint8_t *pBuffer, uint32_t numToWrite)
{ if ((writeAddr + numToWrite) > FLASH_END_ADDRESS)//如果超过结束地址{return false;}if (writeAddr % 2 == 1) // 半字(2字节)写入,地址要对齐{return false;}uint16_t temp;fmc_state_enum fmcState = FMC_READY;fmc_unlock();for (uint32_t i = 0; i < numToWrite / 2; i++) {fmc_flag_clear(FMC_FLAG_BANK0_END | FMC_FLAG_BANK0_WPERR | FMC_FLAG_BANK0_PGERR);//清除标志位fmcState = fmc_halfword_program(writeAddr, *(uint16_t *)pBuffer);//传入要写入的地址if (fmcState != FMC_READY){fmc_lock(); //flash上锁return false;}pBuffer += 2;writeAddr += 2;}if (numToWrite % 2)//如果写入数据为奇数个{fmc_flag_clear(FMC_FLAG_BANK0_END | FMC_FLAG_BANK0_WPERR | FMC_FLAG_BANK0_PGERR);temp = *pBuffer | 0xff00;fmcState = fmc_halfword_program(writeAddr, temp);//写入低字节}fmc_lock();//flash上锁return true;
}/**
*******************************************************************
* @function 擦除从eraseAddr开始到eraseAddr + numToErase的页
* @param eraseAddr,地址
* @param numToErase,对应写入数据时的个数
* @return
*******************************************************************
*/
bool FlashErase(uint32_t eraseAddr, uint32_t numToErase)
{if (numToErase == 0 || (eraseAddr + numToErase) > FLASH_END_ADDRESS)//如果超过结束地址{return false;} uint8_t pageNum;uint8_t addrOffset = eraseAddr % FLASH_PAGE_SIZE; // mod运算求余在一页内的偏移,若eraseAddr是FLASH_PAGE_SIZE整数倍,运算结果为0fmc_state_enum fmcState = FMC_READY;fmc_unlock();if (numToErase > (FLASH_PAGE_SIZE - addrOffset)) // 跨页{fmc_flag_clear(FMC_FLAG_BANK0_END | FMC_FLAG_BANK0_WPERR | FMC_FLAG_BANK0_PGERR);fmcState = fmc_page_erase(eraseAddr); // 擦本页if (fmcState != FMC_READY){goto erase_err;}eraseAddr += FLASH_PAGE_SIZE - addrOffset; // 对齐到页地址numToErase -= FLASH_PAGE_SIZE - addrOffset;pageNum = numToErase / FLASH_PAGE_SIZE; //计算还有多少个没擦除的页while (pageNum--) //不断擦除剩余页的数据{fmc_flag_clear(FMC_FLAG_BANK0_END | FMC_FLAG_BANK0_WPERR | FMC_FLAG_BANK0_PGERR);fmcState = fmc_page_erase(eraseAddr);if (fmcState != FMC_READY){goto erase_err;}eraseAddr += FLASH_PAGE_SIZE;}if (numToErase % FLASH_PAGE_SIZE != 0)//擦除剩下不是整页的数据{fmc_flag_clear(FMC_FLAG_BANK0_END | FMC_FLAG_BANK0_WPERR | FMC_FLAG_BANK0_PGERR);fmcState = fmc_page_erase(eraseAddr); if (fmcState != FMC_READY){goto erase_err;}}}else // 没有跨页{fmc_flag_clear(FMC_FLAG_BANK0_END | FMC_FLAG_BANK0_WPERR | FMC_FLAG_BANK0_PGERR);fmcState = fmc_page_erase(eraseAddr);if (fmcState != FMC_READY){goto erase_err;}}/* lock the main FMC after the erase operation */fmc_lock();return true;erase_err:/* lock the main FMC after the erase operation */fmc_lock();return false;
}#define BUFFER_SIZE 3000
#define FLASH_TEST_ADDRESS 0x0807F004
void FlashDrvTest(void)
{uint8_t bufferWrite[BUFFER_SIZE];uint8_t bufferRead[BUFFER_SIZE];printf("flash writing data:\n");for (uint16_t i = 0; i < BUFFER_SIZE; i++){ bufferWrite[i] = i + 1;printf("0x%02X ", bufferWrite[i]);}printf("\n开始写入\n");if (!FlashErase(FLASH_TEST_ADDRESS, BUFFER_SIZE)){printf("Flash写数据故障,请排查!\n");return;}if (!FlashWrite(FLASH_TEST_ADDRESS, bufferWrite, BUFFER_SIZE)){printf("Flash写数据故障,请排查!\n");return;}printf("开始读取\n");if (!FlashRead(FLASH_TEST_ADDRESS, bufferRead, BUFFER_SIZE)){printf("Flash读数据故障,请排查!\n");return;}for (uint16_t i = 0; i < BUFFER_SIZE; i++){if (bufferRead[i] != bufferWrite[i]){printf("0x%02X ", bufferRead[i]);printf("Flash测试故障,请排查!\n");return;}printf("0x%02X ", bufferRead[i]);}printf("\nFlash测试通过!\n");
}
store_app.c
/********************************************************************************** @file sys_param.c* @brief 基于 **片上 Flash** 的系统参数管理(双区备份 + CRC 校验)* 主参数区:0x0807F000(2 KB)* 备份区: 0x0807F800(2 KB)* 结构体末尾带 CRC8,保证完整性********************************************************************************/#include <stdint.h>
#include <stdbool.h>
#include <stdio.h>
#include "inflash_drv.h"
#include "mb.h"/* -------------------- 参数表定义 -------------------- */
/*** @brief 系统参数结构体* @note 整个结构体最后 1 字节为 CRC8 校验*/
typedef struct
{uint16_t magicCode; /**< 魔数 0x5A5A:标记参数区有效 */uint8_t modbusAddr; /**< Modbus 从机地址 1~247 */uint8_t crcVal; /**< 整表 CRC8(含本字节前的所有数据) */
} SysParam_t;/* 出厂默认参数 */
static const SysParam_t g_sysParamDefault =
{.magicCode = 0x5A5A,.modbusAddr = 1
};/* 运行期副本 */
static SysParam_t g_sysParamCurrent;/* -------------------- 存储布局 -------------------- */
#define SYSPARAM_MAX_SIZE 2048U /* 一页 2 KB */
#define SYSPARAM_START_ADDR 0x0807F000U /* 主参数区 */
#define BACKUP_START_ADDR 0x0807F800U /* 备份区 *//* -------------------- CRC8 计算 -------------------- */
/*** @brief CRC8 计算(多项式 0x31)* @param buf 数据指针* @param len 数据长度(不含 CRC 本身)* @return CRC8 值*/
static uint8_t CalcCrc8(uint8_t *buf, uint32_t len)
{uint8_t crc = 0xFF; /* 初值 0xFF */for (uint32_t i = 0; i < len; i++){crc ^= buf[i]; /* 异或当前字节 */for (uint8_t j = 0; j < 8; j++){if (crc & 0x80)crc = (crc << 1) ^ 0x31;elsecrc <<= 1;}}return crc;
}/* -------------------- 带 CRC 的读 -------------------- */
/*** @brief 读取并校验数据* @param readAddr Flash 起始地址* @param pBuffer 接收缓冲区* @param numToRead 读取长度(含末尾 CRC)* @return true 校验成功;false 失败*/
static bool ReadDataWithCheck(uint32_t readAddr, uint8_t *pBuffer, uint32_t numToRead)
{/* 读原始数据 */if (!FlashRead(readAddr, pBuffer, numToRead))return false;/* 计算 CRC 并比较 */uint8_t crcCalc = CalcCrc8(pBuffer, numToRead - 1);return (crcCalc == pBuffer[numToRead - 1]);
}/* -------------------- 参数读取 -------------------- */
/*** @brief 从 Flash 读取参数(先主区,失败后备份区)* @param sysParam 输出参数结构体指针* @return true 成功;false 两区均失效*/
static bool ReadSysParam(SysParam_t *sysParam)
{uint16_t len = sizeof(SysParam_t);/* 先尝试主区 */if (ReadDataWithCheck(SYSPARAM_START_ADDR, (uint8_t *)sysParam, len))return true;/* 主区失败 → 尝试备份区 */if (ReadDataWithCheck(BACKUP_START_ADDR, (uint8_t *)sysParam, len))return true;return false; /* 两区均失败 */
}/* -------------------- 带 CRC 的写 -------------------- */
/*** @brief 写入数据并自动计算 CRC* @param writeAddr Flash 起始地址* @param pBuffer 数据缓冲区(最后 1 字节留空给 CRC)* @param numToWrite 写入长度(含 CRC)* @return true 成功;false 失败*/
static bool WriteDataWithCheck(uint32_t writeAddr, uint8_t *pBuffer, uint32_t numToWrite)
{/* 计算 CRC 并填充到末尾 */pBuffer[numToWrite - 1] = CalcCrc8(pBuffer, numToWrite - 1);/* 擦除 + 写入 */if (!FlashErase(writeAddr, numToWrite))return false;if (!FlashWrite(writeAddr, pBuffer, numToWrite))return false;return true;
}/* -------------------- 参数写入 -------------------- */
/*** @brief 同时写入主区与备份区* @param sysParam 待写入参数* @return true 成功;false 失败*/
static bool WriteSysParam(SysParam_t *sysParam)
{uint16_t len = sizeof(SysParam_t);if (len > SYSPARAM_MAX_SIZE) /* 长度越界检查 */return false;/* 先写主区,失败立即返回 */if (!WriteDataWithCheck(SYSPARAM_START_ADDR, (uint8_t *)sysParam, len))return false;/* 主区成功 → 写备份区(备份区单独失败不影响整体) */WriteDataWithCheck(BACKUP_START_ADDR, (uint8_t *)sysParam, len);return true;
}/* -------------------- 系统初始化 -------------------- */
/*** @brief 系统上电时初始化参数* @note 1) 优先读取 Flash 有效参数* 2) 失败则使用默认参数* 3) 更新 Modbus 地址*/
void InitSysParam(void)
{SysParam_t temp;/* 读取成功且魔数正确 → 使用存储参数 */if (ReadSysParam(&temp) && temp.magicCode == 0x5A5A){g_sysParamCurrent = temp;}else{/* 使用默认参数 */g_sysParamCurrent = g_sysParamDefault;}/* 设置 Modbus 从机地址 */eMBSetSlaveAddr(g_sysParamCurrent.modbusAddr);
}/* -------------------- 在线修改地址 -------------------- */
/*** @brief 在线修改 Modbus 地址(带掉电保存)* @param addr 新地址 1~247* @return true 成功;false 失败(地址未变或写入失败)*/
bool SetModbusParam(uint8_t addr)
{/* 地址未变 */if (addr == g_sysParamCurrent.modbusAddr)return true;SysParam_t temp = g_sysParamCurrent;temp.modbusAddr = addr;/* 先让协议栈试用新地址 */if (eMBSetSlaveAddr(addr) != MB_ENOERR)return false;/* 写入 Flash(主+备) */if (!WriteSysParam(&temp)){/* 失败 → 回滚地址 */eMBSetSlaveAddr(g_sysParamCurrent.modbusAddr);return false;}/* 成功 → 更新运行副本 */g_sysParamCurrent = temp;return true;
}