我们之前已经实现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();}
}

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。
如若转载,请注明出处:http://www.pswp.cn/diannao/97982.shtml
繁体地址,请注明出处:http://hk.pswp.cn/diannao/97982.shtml
英文地址,请注明出处:http://en.pswp.cn/diannao/97982.shtml

如若内容造成侵权/违法违规/事实不符,请联系英文站点网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

find_code 插件 react_vite

find_code 插件 react_vite const fs require("fs"); const path require("path"); const parser require("babel/parser"); const traverse require("babel/traverse").default; const generate require("babel/generator&…

手机秒变全栈IDE:Claude Code UI的深度体验

还在为只能在命令行中使用Claude Code而苦恼吗&#xff1f;想在移动设备上继续你的AI编程对话吗&#xff1f;Claude Code UI的出现彻底改变了这一切。这个开源项目为Anthropic官方的Claude Code CLI工具提供了现代化的Web界面&#xff0c;让你能够在任何设备、任何地方与AI编程…

F5发布后量子API安全解决方案,以AI驱动全面防护应对量子计算威胁

量子计算的飞速演进&#xff0c;正对传统加密体系构成日益严峻的安全威胁。Gartner预测显示&#xff0c;到2029年&#xff0c;量子计算机有望攻破目前普遍采用的公钥加密算法&#xff0c;这一风险正倒逼全球企业加速密码体系的更迭与升级。面对这一挑战&#xff0c;F5公司——应…

深度剖析 DC - DC 转换器在新能源汽车中的关键应用

在新能源汽车的发展进程中&#xff0c;DC - DC 转换器扮演着至关重要的角色。以下将详细介绍其在新能源汽车上的应用&#xff0c;包括作用、电路组成以及工作原理等方面。DC - DC 转换器的作用简单来说&#xff0c;新能源汽车上的 DC - DC 转换器是一个 “降压型电压变换器”。…

【标准项目】在线五子棋对决(下)

在线五子棋对决一. 项目介绍及链接二. 项目结构设计项目模块划分业务处理模块的子模块划分项目流程图玩家流程图服务器流程图三. 数据管理模块数据库设计创建 user_table 类四. 在线用户管理模块五. 游戏房间管理模块游戏房间类实现游戏房间管理类实现六. Session 管理模块Sess…

重构导航之核:高德地图的深度学习架构解析 导论:从数字化世界到可计算世界

导论&#xff1a;从数字化世界到可计算世界 数字地图的演进&#xff0c;本质上是一场关于“世界可计算性”的持续探索。第一代地图的核心任务是数字化转录&#xff08;Digital Transcription&#xff09;&#xff0c;它成功地将物理世界的静态元素——道路、建筑、兴趣点&#…

逻辑回归(sigmoid函数、混淆矩阵、精确率召回率F1)

目录 一、概述 1、逻辑回归 2、激活函数 sigmoid函数 3、最大似然估计 二、逻辑回归 1、原理 2、损失函数 3、代码 三、混淆矩阵 1、定义 2、举例 3、代码 四、分类评估方法 1、精确率&#xff08;Precision&#xff09; 2、召回率&#xff08;Recall&#xff09; 3、F1&#…

Redis底层实现原理之五大基础结构

文章目录1. 基础结构和编码类型2. 编码类型和数据结构实现2.1 字符串&#xff08;String&#xff09;2.2 压缩列表&#xff08;listpack&#xff09;2.3 哈希表&#xff08;hashtable&#xff09;2.4 快速列表&#xff08;quicklist&#xff09;2.5 整数集合&#xff08;intset…

火山引擎数据智能体DataAgent总结分享

数据的冰山:看得见的资产与看不见的鸿沟 这张图片用“冰山”类比的方式展示了数据资产管理中的可见与不可见问题,并突出了数据利用的核心挑战与潜在陷阱。 1. 冰山之上的“看得见的资产” 内容:数据库、报表、指标等结构化、显性的数据资源。 核心挑战: 需要从“采集存储”…

100种高级数据结构 (速查表)

一、 基础结构的扩展与组合 (Advanced Linear Structures) 这些结构在数组、链表、队列、栈等基础结构上增加了特定功能或约束。双端队列 (Deque - Double-Ended Queue) 介绍&#xff1a;允许在队列的前后两端都进行插入和删除操作的线性结构。应用场景&#xff1a;工作窃取算法…

一个开源的企业官网简介

简介一个完美的企业官网系统,支持手机端和电脑端展示企业风采,还可以展示企业产品/企业新闻资讯等等.普通用户PC端展示普通用户手机端展示管理后台

TCP实现线程池竞争任务

服务端&#xff1a;#include<stdio.h> #include<sys/types.h> #include<sys/socket.h> #include<netinet/in.h> #include<netinet/ip.h> #include<strings.h> #include<unistd.h> #include<ctype.h> #include<arpa/inet.h&…

Redis C++ 实现笔记(F篇)

Implementing Redis in C : F Redis C 实现笔记&#xff08;F篇&#xff09; 前言 本章代码及思路均来自Build Your Own Redis with C/C 本文章只阐述我的理解想法&#xff0c;以及需要注意的地方。 本文章为续<<Implementing Redis in C : E>>所以本文章不再…

finally 与 return的执行顺序

一、第一次试验public static void main(String[] args) throws InterruptedException {System.out.println(aaa(null));}private static StringBuilder aaa(Integer i) throws InterruptedException {StringBuilder sb new StringBuilder();try {i.toString();return sb;} ca…

Git安装教程

简介 Git 是目前全球最流行的分布式版本控制系统&#xff08;Distributed Version Control System, DVCS&#xff09;&#xff0c;核心作用是追踪文件修改历史、支持多人协同开发&#xff0c;并能高效管理代码&#xff08;或任何文本类文件&#xff09;的版本迭代。它由 Linux…

Linux安装RTL8821CE无线网卡驱动

1. 查看网卡芯片$ lspci | grep Net 01:00.0 Network controller: Realtek Semiconductor Co., Ltd. RTL8821CE 802.11ac PCIe Wireless Network Adapter2. 预备配套sudo apt install -y dkms git3. 下载驱动并安装git clone https://github.com/tomaspinho/rtl8821ce.git cd r…

vue3存储/获取本地或会话存储,封装存储工具,结合pina使用存储

目录 一、基本用法&#xff08;原生 API&#xff09; 1. 存储数据 2. 获取数据 3. 删除数据 二、Vue3 中封装成工具函数&#xff08;推荐&#xff09; 三、以上工具函数在 Vue3 组件中使用 1. 在选项式 API 中使用 2. 在组合式 API&#xff08;setup 语法糖&#xff09;…

【Flink】DataStream API:基本转换算子、聚合算子

目录基本转换算子映射&#xff08;map&#xff09;过滤&#xff08;filter&#xff09;扁平映射聚合算子按键分区&#xff08;keyBy&#xff09;简单聚合&#xff08;sum/min/max/minBy/maxBy&#xff09;规约聚合&#xff08;reduce&#xff09;基本转换算子 有如下POJO类用来…

从淘宝推荐到微信搜索:查找算法如何支撑亿级用户——动画可视化

本篇技术博文摘要 &#x1f31f; 本文通过动画可视化深入解析数据结构中的核心查找算法&#xff0c;从基础概念到高阶应用&#xff0c;全面覆盖顺序查找、折半查找、分块查找、B树/B树及散列查找的核心原理与实现细节。文章以动态演示为核心工具&#xff0c;直观展现算法执行过…

图像正向扭曲反向扭曲

在图像处理领域&#xff0c;正向扭曲&#xff08;Forward Warping&#xff09;和反向扭曲&#xff08;Backward Warping&#xff09;是两种核心的图像坐标映射与像素重采样技术&#xff0c;核心区别在于“像素映射的方向”——是从“原始图像”到“目标图像”&#xff0c;还是从…