我们之前已经实现eeprom的驱动了,我们在应用层实现产品配置参数存储方案
我们要实现:原本设定的modebus从机(单片机)地址是01,存储在eeprom里,按下按键后修改地址为03,重新上电modebus从机(单片机)地址仍然是03
我们在app这个文件夹里创建store_app.c
#include <stdint.h>
#include <stdbool.h>
#include <stdio.h>
#include "eeprom_drv.h"
#include "mb.h"/**********EEPROM256个字节,128个为主区域,128个位备份区域**************/
/************************************************************** @brief 系统参数结构体定义* @note 保存在 EEPROM / Flash 中的配置表,整表以 magicCode 做* 有效性标记,末尾用 crcVal 做校验,便于升级或恢复默认。***********************************************************/
typedef struct
{uint16_t magicCode; /**< 魔数 0x5A5A:用于识别参数区是否有效 *//* ---------------- 用户配置参数开始 ---------------- */uint8_t modbusAddr; /**< Modbus 从机地址 1~247 *//* ---------------- 用户配置参数结束 ---------------- */uint8_t crcVal; /**< 整表 CRC8/校验和,用于完整性校验 */
} SysParam_t;/** 默认参数常量,首次烧录或恢复出厂时使用 */
#define MAGIC_CODE 0x5A5A
static const SysParam_t g_sysParamDefault =
{.magicCode = MAGIC_CODE,.modbusAddr = 1
};
/*==============================================================* 全局变量*============================================================*/
static SysParam_t g_sysParamCurrent; /* 当前运行时的系统参数副本 *//*==============================================================* EEPROM 参数存储布局*============================================================*/
#define SYSPARAM_MAX_SIZE 128 /* 参数区最大长度(字节) */
#define SYSPARAM_START_ADDR 0 /* 主参数区起始地址 */
#define BACKUP_START_ADDR 128 /* 备份参数区起始地址 */
/*==============================================================* CRC8 计算函数(多项式 0x31)* buf : 数据首地址* len : 参与计算的数据长度* return: 8 位 CRC 值*============================================================*/
static uint8_t CalcCrc8(uint8_t *buf, uint32_t len)
{uint8_t crc = 0xFF; /* 初值 0xFF */for (uint8_t byte = 0; byte < len; byte++){crc ^= buf[byte]; /* 异或当前字节 */for (uint8_t i = 8; i > 0; --i){if (crc & 0x80) /* 最高位为 1 */crc = (crc << 1) ^ 0x31;else /* 最高位为 0 */crc <<= 1;}}return crc;
}/*==============================================================* 带 CRC 校验的读数据* readAddr : EEPROM 起始地址* pBuffer : 数据缓冲区* numToRead: 读取长度(含末尾 CRC 字节)* return : true-成功 false-失败(读失败或 CRC 不符)*============================================================*/
static bool ReadDataWithCheck(uint8_t readAddr, uint8_t *pBuffer, uint16_t numToRead)
{if (!ReadEepromData(readAddr, pBuffer, numToRead)) /* 读原始数据 */return false;uint8_t crcVal = CalcCrc8(pBuffer, numToRead - 1); /* 计算 CRC */if (crcVal != pBuffer[numToRead - 1]) /* 与末尾 CRC 比较 */return false;return true;
}
/*==============================================================* 读取系统参数(先主区,失败后备份区)* sysParam: 输出参数结构体指针* return : true-成功 false-两区均失败*============================================================*/
static bool ReadSysParam(SysParam_t *sysParam)
{uint16_t sysParamLen = sizeof(SysParam_t);/* 先尝试主参数区 */if (ReadDataWithCheck(SYSPARAM_START_ADDR, (uint8_t *)sysParam, sysParamLen))return true;/* 主区失败,再尝试备份区 */if (ReadDataWithCheck(BACKUP_START_ADDR, (uint8_t *)sysParam, sysParamLen))return true;return false; /* 两区均失败 */
}
/*==============================================================* 带 CRC 校验的写数据* writeAddr : EEPROM 起始地址* pBuffer : 数据缓冲区(最后 1 字节留空给 CRC)* numToWrite: 写入长度(含末尾 CRC 字节)* return : true-成功 false-失败*============================================================*/
static bool WriteDataWithCheck(uint8_t writeAddr, uint8_t *pBuffer, uint16_t numToWrite)
{pBuffer[numToWrite - 1] = CalcCrc8(pBuffer, numToWrite - 1); /* 计算并填充 CRC */return WriteEepromData(writeAddr, pBuffer, numToWrite); /* 写入 EEPROM */
}
/*==============================================================* 写系统参数到 EEPROM(主区 + 备份区)* sysParam: 待写入参数* return : true-成功 false-失败*============================================================*/
static bool WriteSysParam(SysParam_t *sysParam)
{uint16_t sysParamLen = sizeof(SysParam_t);if (sysParamLen > SYSPARAM_MAX_SIZE) /* 长度越界检查 */return false;/* 先写主区,失败立即返回 */if (!WriteDataWithCheck(SYSPARAM_START_ADDR, (uint8_t *)sysParam, sysParamLen))return false;/* 主区成功后写备份区,忽略备份区单独失败 */WriteDataWithCheck(BACKUP_START_ADDR, (uint8_t *)sysParam, sysParamLen);return true;
}
/*==============================================================* 系统参数初始化:* 1) 从 EEPROM 读取有效参数 → 使用之* 2) 读取失败 → 载入默认参数* 3) 设置 Modbus 当前地址*============================================================*/
void InitSysParam(void)
{SysParam_t sysParam;/* 读取成功且魔数正确 → 使用存储参数 */if (ReadSysParam(&sysParam) && sysParam.magicCode == MAGIC_CODE){g_sysParamCurrent = sysParam; /* 复制到运行区 */eMBSetSlaveAddr(g_sysParamCurrent.modbusAddr);/* 更新 Modbus 地址 */return;}/* EEPROM 无效 → 使用默认参数 */g_sysParamCurrent = g_sysParamDefault;eMBSetSlaveAddr(g_sysParamCurrent.modbusAddr); /* 设置默认地址 */
}/*==============================================================* 在线修改 Modbus 地址* addr: 新地址(1~247)* return: true-成功 false-失败* 注意:先写 EEPROM,成功后更新协议栈;失败则回滚*============================================================*/
bool SetModbusParam(uint8_t addr)
{if (addr == g_sysParamCurrent.modbusAddr) /* 地址未变 */return true;SysParam_t sysParam = g_sysParamCurrent; /* 复制当前参数 */sysParam.modbusAddr = addr; /* 修改地址 *//* 协议栈先试用新地址 */if (eMBSetSlaveAddr(addr) != MB_ENOERR)return false;/* 写入 EEPROM(主+备) */if (!WriteSysParam(&sysParam)){/* 写失败 → 回滚地址 */eMBSetSlaveAddr(g_sysParamCurrent.modbusAddr);return false;}/* 成功 → 更新运行副本 */g_sysParamCurrent = sysParam;return true;
}
.h
#ifndef _STORE_APP_H_
#define _STORE_APP_H_#include <stdint.h>
#include <stdbool.h>bool SetModbusParam(uint8_t addr);
void InitSysParam(void);#endif
这是有人问了up,up这个要怎么才可以实现修改从机地址呀?
那我们当然是要写个修改从机地址的函数,我们在mb.c添加
/************************************************************* @brief 设置 Modbus 从机地址* @param ucSlaveAddress:新地址(1~247)* @return MB_ENOERR 成功* MB_EINVAL 地址非法(广播地址或越界)* @note 仅更新全局变量,立即生效;无需重启协议栈**********************************************************/
eMBErrorCode eMBSetSlaveAddr(UCHAR ucSlaveAddress)
{eMBErrorCode eStatus = MB_ENOERR;/* 地址合法性检查:禁止广播地址与越界值 */if ((ucSlaveAddress == MB_ADDRESS_BROADCAST) ||(ucSlaveAddress < MB_ADDRESS_MIN) ||(ucSlaveAddress > MB_ADDRESS_MAX)){eStatus = MB_EINVAL; /* 参数错误 */}else{ucMBAddress = ucSlaveAddress; /* 更新全局从机地址 */}return eStatus;
}
我们在用户交互函数里进行修改:
#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(2)){printf("SetModbusParam sucess\n");}else{printf("SetModbusParam fail\n");}break;case KEY1_LONG_PRESS:TurnOffLed(LED1);break;case KEY2_SHORT_PRESS: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;}
}
main
#include <stdint.h>
#include <stdio.h>
#include "led_drv.h"
#include "key_drv.h"
#include "systick.h"
#include "usb2com_drv.h"
#include "rtc_drv.h"
#include "delay.h"
#include "sensor_drv.h"
#include "eeprom_drv.h"
#include "hmi_app.h"
#include "sensor_app.h"
#include "modbus_app.h"
#include "store_app.h"
/******实验现象:原本设定的modebus从机(单片机)地址是01,存储在eeprom里,按下按键后修改地址为03******/
typedef struct
{uint8_t run; // 调度标志,1:调度,0:挂起uint16_t timCount; // 时间片计数值uint16_t timRload; // 时间片重载值void (*pTaskFuncCb)(void); // 函数指针变量,用来保存业务功能模块函数地址
} TaskComps_t;static TaskComps_t g_taskComps[] =
{{0, 5, 5, HmiTask},{0, 1000, 1000, SensorTask},{0, 1, 1, ModbusTask},/* 添加业务功能模块 */
};#define TASK_NUM_MAX (sizeof(g_taskComps) / sizeof(g_taskComps[0]))static void TaskHandler(void)
{for (uint8_t i = 0; i < TASK_NUM_MAX; i++){if (g_taskComps[i].run) // 判断时间片标志{g_taskComps[i].run = 0; // 标志清零g_taskComps[i].pTaskFuncCb(); // 执行调度业务功能模块}}
}/**
***********************************************************
* @brief 在定时器中断服务函数中被间接调用,设置时间片标记,需要定时器1ms产生1次中断
* @param
* @return
***********************************************************
*/
static void TaskScheduleCb(void)
{for (uint8_t i = 0; i < TASK_NUM_MAX; i++){if (g_taskComps[i].timCount){g_taskComps[i].timCount--;if (g_taskComps[i].timCount == 0){g_taskComps[i].run = 1;g_taskComps[i].timCount = g_taskComps[i].timRload;}}}
}static void DrvInit(void)
{DelayInit();LedDrvInit();KeyDrvInit();Usb2ComDrvInit();RtcDrvInit();SensorDrvInit();EepromDrvInit();SystickInit();
}
static void AppInit(void)
{TaskScheduleCbReg(TaskScheduleCb);ModbusAppInit();InitSysParam();
}int main(void)
{ DrvInit();AppInit();while (1){TaskHandler();}
}