我們之前已經實現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();}
}