GD32入门到实战32--产品配置参数存储方案 (NORFLASH)
我们之前已经实现norflash的驱动了,我们在应用层实现产品配置参数存储方案
我们要实现:原本设定的modebus从机(单片机)地址是01,存储在norflash里,按下按键后修改地址为02,重新上电modebus从机(单片机)地址仍然是02
我们在app这个文件夹里创建store_app.c
/********************************************************************************** @file sys_param.c* @brief 基于 SPI-NOR-Flash 的系统参数管理(双区备份 + CRC 校验)* 主参数区:0x00000000* 备份区: 0x00001000(4 KB 偏移)* 结构体末尾带 CRC8,保证完整性********************************************************************************/#include <stdint.h>
#include <stdbool.h>
#include <stdio.h>
#include "norflash_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 4096U /* 一个扇区 4 KB */
#define SYSPARAM_START_ADDR 0x00000000U
#define BACKUP_START_ADDR 0x00001000U /* 主区 + 4 KB *//* -------------------- 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;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, uint16_t numToRead)
{ReadNorflashData(readAddr, pBuffer, numToRead);uint8_t crcCalc = CalcCrc8(pBuffer, numToRead - 1);return (crcCalc == pBuffer[numToRead - 1]);
}/* -------------------- 带 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, uint16_t numToWrite)
{/* 计算 CRC 并填充到末尾 */pBuffer[numToWrite - 1] = CalcCrc8(pBuffer, numToWrite - 1);/* 擦除 + 写入 */EraseNorflashForWrite(writeAddr, numToWrite);WriteNorflashData(writeAddr, pBuffer, numToWrite);return true;
}/* -------------------- 参数读取 -------------------- */
/*** @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;
}/* -------------------- 参数写入 -------------------- */
/*** @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){/* Flash 参数有效 */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;
}
人机交互函数:
#include <stdint.h>
#include <stdio.h>
#include "rtc_drv.h"
#include "sensor_drv.h"
#include "led_drv.h"
#include "key_drv.h"
#include "store_app.h"/**
***********************************************************
* @brief 人机交互任务处理函数
* @param
* @return
***********************************************************
*/
void HmiTask(void)
{
// SensorData_t sensorData;
// GetSensorData(&sensorData);
// printf("\n temp is %.1f, humi is %d.\n", sensorData.temp, sensorData.humi);uint8_t keyVal;keyVal = GetKeyVal();switch (keyVal){case KEY1_SHORT_PRESS:TurnOnLed(LED1);if (SetModbusParam(1)){printf("SetModbusParam sucess1\n");}else{printf("SetModbusParam fail\n");}break;case KEY1_LONG_PRESS:TurnOffLed(LED1);break;case KEY2_SHORT_PRESS:if (SetModbusParam(2)){printf("SetModbusParam sucess2\n");}else{printf("SetModbusParam fail\n");}TurnOnLed(LED2);break;case KEY2_LONG_PRESS:TurnOffLed(LED2);break;case KEY3_SHORT_PRESS:TurnOnLed(LED3);break;case KEY3_LONG_PRESS:TurnOffLed(LED3);break;default:break;}
}