关于MCU产品开发参数存储的几种方案

关于MCU产品开发参数存储的几种方案

  • Chapter1 关于MCU产品开发参数存储的几种方案
  • Chapter2 单片机参数处理[保存与读取]
  • Chapter3 嵌入式设备参数存储技巧
  • Chapter4 STM32硬件I2C的一点心得(AT24C32C和AT24C64C)


Chapter1 关于MCU产品开发参数存储的几种方案

原文链接

在工作中,几乎所有参与产品开发的产品都将实现参数存储功能。

通常,参数存储将使用以下存储介质,例如:eeprom,spi闪存,nand闪存,SD卡等,至于如何存储,现在有很多种类。

1.使用eeprom(以at24c02为例)定义结构,然后定义两个结构变量,一个用于读取参数,一个用于立即写入修改的参数。

参考:2.使用spi_flash(以w25q64为例)方法1与使用eeprom方法相同。

方法2使用文件系统并创建一个ini文件来获取参数。

Chapter2 单片机参数处理[保存与读取]

原文链接:https://blog.csdn.net/WangSanHuai2010/article/details/6988583

/*------------------------------------------------------------Func: 加载参数到系统Time: 2011-11-13Ver.: V1.0Note:
------------------------------------------------------------*/
void WFS_LoadParams(uint16 Addr,uint16 *Buffer,uint16 Length)
{Addr<<=1;Length<<=1;EEPROM_Read(Addr+2,(uint8 *)Buffer,Length);
}

参数按以上方法加载到内存,注意参数的起始地址为2,这是因为前两个字节区域要用来做校验用。

/*------------------------------------------------------------Func: 保存参数Time: 2011-11-13Ver.: V1.0Note:
------------------------------------------------------------*/
void WFS_SaveParams(uint16 Addr,uint16 *Buffer,uint16 Length)
{Addr<<=1;Length<<=1;EEPROM_Write(Addr+2,(uint8 *)Buffer,Length);
}

以上方法保存参数到EEPROM中,实际上与Load方法一一对应。

/*------------------------------------------------------------Func: 参数系统初始化Time: 2011-11-13Ver.: V1.0Note:
------------------------------------------------------------*/
uint8 WFS_InitParams(void *DefaultValues,uint16 Length)
{uint16 D;EEPROM_Read(0,(uint8 *)(&D),2);if(D!=0x55AA){D=0x55AA;EEPROM_Write(0,(uint8 *)(&D),2);EEPROM_Write(2,(uint8 *)DefaultValues,Length);return 0xFF;}return 0x00;
}

参数的初始化方法,首先读取EEPROM的0位置处的数据,判断是否为0x55AA合法标志,若不是0x55AA,则说明参数区为首次使用,需要进行初始化默认参数填充,于是将DefaultValues所指的默认值填入EEPROM中,并设置0x55AA标志,以后每次上电便会检测到参数的合法性。

以下为使用示例,存储了地址码,波特率,数据位,停止位四个参数,以及一个18字的数组。

const uint16 WFS_ParmasValue_Default[]=
{1,9600,8,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
};

以下为参数进行初始化并加载到内存:


WFS_InitParams(WFS_ParmasValue_Default,sizeof(WFS_ParmasValue_Default));
WFS_LoadParams(0,&DevAddr,1);
WFS_LoadParams(1,&BaudRate,1);
WFS_LoadParams(2,&DataLength,1);
WFS_LoadParams(3,&StopBits,1);
WFS_LoadParams(4,Array,18);

以下为参数修改后进行保存:

BaudRate=115200;
StopBits=2;
WFS_SaveParams(1,&BaudRate,1);
WFS_LoadParams(3,&StopBits,1);

Chapter3 嵌入式设备参数存储技巧

原文链接

1、前言
想必做嵌入式产品开发都遇到过设备需要保存参数,常用的方式就是按照结构体的方式管理参数,保存时将整个结构体数据保存在 Flash 中,方便下次读取。

1.1、目的
本文时分析嵌入式/单片机中参数保存的几种方式的优点和缺点(仅针对单片机/嵌入式开发而言),同时针对以结构体的方式解决一些弊端问题(重点在第 3 节)。

2、参数保存格式
2.1、结构体格式
该方式是嵌入式/单片机中开发最常用的,将所有的系统参数通过结构体的方式定义,然后保存数据,介绍一下该方式的优缺点。

储存方式:二进制 bin 文件格式
1
优点:

管理简单:无需额外的代码直接就能很方便的管理参数
内存最小:通过结构体的形式保存在Flash中,占用内存最小
缺点:

1.扩展性差:

从产品角度来说,产品需要升级,若是涉及增加参数,则升级后参数通常无法校验通过(通常包含长度校验等),导致参数被恢复默认
若是每个模块都存在自己的独有结构体参数定义,删除/新增时势必影响到其他的,导致设备升级后参数错乱(结构体中的变量地址在 bin 文件中是固定的)
2.阅读性差:
若参数需要导出,bin文件没有可读性

改进措施:
结构体增加预留定义,若之后需要新增参数,则在预留空间新增即可,能在一定程度上解决扩展性差的问题,即新增不影响原有的结构体大小和其他成员变量的位置,删除恢复成预留即可。

为啥说只能在一定程度上解决该问题,因为之后的升级某些模块可能很长时间或者从不需要增加新的参数,这种势必就会造成内存的无效占用,或者有些模块频繁增加参数导致预留大小不够等问题,只能在前期设计时多加思考预留的分配情况(毕竟内存只有那么大)

/*****************************改进之前
*****************************/typedef struct
{uint8_t testParam;uint8_t testParam2;
} TestParam_t;    /* 某模块参数 */typedef struct
{uint8_t testParam;uint8_t testParam2;TestParam_t tTestParam;
} SystemParam_t; /* 系统参数 *//*****************************改进之后
*****************************/typedef struct
{uint8_t testParam;uint8_t testParam2;uint8_t reserve[6];    // 预留
} TestParam_t;    /* 某模块参数 */typedef struct
{uint8_t testParam;uint8_t testParam2;TestParam_t tTestParam;uint8_t reserve[50];   // 预留
} SystemParam_t; /* 系统参数 */

2.2、JSON格式
最近Json格式很是流行使用,特别是数据交换中用的很多,但是它也可以用来保存参数使用,JSON 的是 “{键:值}” 的方式。

储存方式:字符串格式,即文本的形式
1
优点:

扩展性好:由于Json的格式,找到对应键值(一般都是该变量的标识),就能找到对应的值
阅读性好:有标识所以导出参数文件通过普通的文本文件打开都能看懂
缺点:

管理相对复杂:没有结构体那么简单,不熟还得先学习 JSON 的写法
内存占用较大:内容不只有值,而且都按照字符串的形式保存的
使用相关困难:需要解析,C语言虽然有开源库,但是由于语言性质使用不方便,C++ 反而使用简单

{"SYS":{"testParam" : 2,"testParam2" : 5,"tTestParam":{"testParam" : 2,"testParam2" : 5}}
}//压缩字符串为:
{"SYS":{"testParam":2,"testParam2":5,"tTestParam":{"testParam":2,"testParam2":5}}}

2.3、键值格式
和上述的 JSON 格式很类似,都是键值对的格式,但是比JSON简单

储存方式:字符串格式,即文本的形式
1
优点:

扩展性好:找到对应键值(一般都是该变量的标识),就能找到对应的值
阅读性好:有标识所以导出参数文件通过普通的文本文件打开都能看懂
缺点:

内存占用较大:内容不只有值,而且都按照字符串的形式保存的
使用稍微困难:需要简单解析处理
管理不变:不方便按照一定的规则管理各模块的参数

testParam=2
testParam2=5
T_testParam=2
T_testParam2=5

2.4 其他
还有其他,如 xml (类似JSON)等,就不多介绍了

3、编译器检查结构体的大小和成员变量的偏移
在第 2 节中介绍了关于参数保存的三种方式,但是对于嵌入式单片机开发而言,Flash 大小不富裕,所以通常都是通过二进制的形式保存的,所以这节重点解决结构体管理保存参数的扩展性问题。

先说一下痛点(虽然对扩展性问题做了改进措施,除了前面讲到的问题,还有其他痛点,虽不算问题,但是一旦出现往往最要命)

在原来的预留空间中新增参数,要确保新增后结构体的大小不变,否则会导致后面的其他参数偏移,最后升级设备后参数出现异常(如果客户升级那就是要命啊)
确保第一点,就必须在每次新增参数都要计算检查一下结构体的大小有没有发生变化,而且有没有对结构体中的其他成员也产生影响
每次新增参数,手动计算和校验 99% 可以检查出来,但是人总有粗心的时候(加班多了,状态不好…),且结构体存在填充,一不留神就以为没问题,提交代码,出版本(测试不一定能发现),给客户,升级后异常,客户投诉、扣工资(难啊…)
遇到这种问题后:难道编译器就不能在编译的时候检查这个大小或者结构体成员的偏移吗,每次手动计算校验好麻烦啊,一不留神还容易算错 # _ #

按照正常情况,编译器可不知道你写的结构体大小和你想要的多大,所以检查不出来(天啊,崩溃了0.0…)

别急,有另类的方式可以达到这种功能,在编译时让编译器为你检查,而且准确性 100%(当然,这个添加新参数时你还得简单根据新增的参数大小减少预留的大小,这个是必须要的)

见代码:

/*** @brief 检查结构体大小是否符合*        在编译时会进行检查* @param type 结构体类型* @param size 结构体检查大小*/
#define TYPE_CHECK_SIZE(type, size) extern int sizeof_##type##_is_error [!!(sizeof(type)==(size_t)(size)) - 1]/*** @brief 结构体成员* @param type   结构体类型* @param member 成员变量*/
#define TYPE_MEMBER(type, member) (((type *)0)->member)/*** @brief 检查结构体成员大小是否符合*        在编译时会进行检查* @param type 结构体类型* @param member 结构体类型* @param size 结构体检查大小*/
#define TYPE_MEMBER_CHECK_SIZE(type, member, size) extern int sizeof_##type##_##member##_is_error \[!!(sizeof(TYPE_MEMBER(type, member))==(size_t)(size)) - 1]/*** @brief 检查结构体中结构体成员大小是否符合*        在编译时会进行检查* @param type 结构体类型* @param member 结构体类型* @param size 结构体检查大小*/
#define TYPE_CHILDTYPE_MEMBER_CHECK_SIZE(type, childtype, member, size) extern int sizeof_##type##_##childtype##_##member##_is_error \[!!(sizeof(TYPE_MEMBER(type, childtype.member))==(size_t)(size)) - 1]/*** @brief 检查结构体成员偏移位置是否符合*        在编译时会进行检查* @param type 结构体类型* @param member 结构体成员* @param value 成员偏移*/
#define TYPE_MEMBER_CHECK_OFFSET(type, member, value) \extern int offset_of_##member##_in_##type##_is_error \[!!(__builtin_offsetof(type, member)==((size_t)(value))) - 1]/*** @brief 检查结构体成员偏移位置是否符合*        在编译时会进行检查* @param type 结构体类型* @param member 结构体成员* @param value 成员偏移*/
#define TYPE_CHILDTYPE_MEMBER_CHECK_OFFSET(type, childtype, member, value) \extern int offset_of_##member##_in_##type##_##childtype##_is_error \[!!(__builtin_offsetof(type, childtype.member)==((size_t)(value))) - 1]

通过以上代码,就能解决这个问题,这个写法只占用文本大小,编译后不占内存!!!

用法:

typedef struct
{uint8_t testParam;uint8_t testParam2;uint8_t reserve[6];    // 预留
} TestParam_t;    /* 某模块参数 */TYPE_CHECK_SIZE(TestParam_t, 8); // 检查结构体的大小是否符合预期typedef struct
{uint8_t testParam;uint8_t testParam2;TestParam_t tTestParam;uint8_t reserve[54];   // 预留
} SystemParam_t; /* 系统参数 */TYPE_CHECK_SIZE(SystemParam_t, 64); // 检查结构体的大小是否符合预期
TYPE_MEMBER_CHECK_OFFSET(SystemParam_t, tTestParam, 2); // 检查结构体成员tTestParam偏移是否符合预期

假设新增了参数,预留写错了,导致结构体的大小不符合,则编译时报错,且提示内容也能快速定位问题。
在这里插入图片描述

Chapter4 STM32硬件I2C的一点心得(AT24C32C和AT24C64C)

原文链接:https://blog.csdn.net/whitefish520/article/details/110070972

从设备读写
一般的EEPROM,像AT24C02这种小容量的设备,地址都只需要8位,页大小一般是16字节一个页
而像AT24C32C、AT24C64C这种32K、64K字节的大容量EEPROM,8位地址是不够的,使用了16位地址,页大小在这两个器件中也变成了32字节
正是由于容量的不同,导致代码上需要做差异化处理,才能正确读取EEPROM芯片

以下代码可以参考,写的时候无法跨页,因此写大量数据的时候,只能一页一页的写,两次写之间保证5MS的间隔
读没有跨页的影响,可以一次性把全部数据读出来,但是要注意,读和写之间,是要有5MS的间隔的,否则读不到数据。也就是说每次写完,延迟5MS,就能保证后续的程序没有问题。

//#define I2C_MEMADD_SIZE		I2C_MEMADD_SIZE_8BIT			//小容量EEPROM芯片8位地址用此参数
#define I2C_MEMADD_SIZE		I2C_MEMADD_SIZE_16BIT		//大容量EEPROM芯片16位地址用此参数
#define ADDR_AT24C02_Write 0xA0		//EEPROM I2C写地址
#define ADDR_AT24C02_Read 0xA0+1	//EEPROM I2C读地址typedef enum SYS_PARA_ENUM {LOCAL_IP = 0,UDP_LOCAL_PORT,UDP_PC_PORT,NETMASK,GATEWAY,SERVER_IP,SERVER_PORT,VERSION,SN_NUM,	//16字节SENSOR_TYPE = SN_NUM + 4,SENSOR_DATA_TYPE,SENSOR_INTERVAL,SYS_PARA_MAX,		//end
}sys_para_e;//请注意,为了方便flash读写操作,此处的每一项均设为uint32_t类型
//如果不为uint32_t类型,则flash_write函数将出现错误
typedef struct SYS_PARA_TYPE {uint32_t local_ip;			//本机IP,大端模式uint32_t udp_local_port;	//本机端口号uint32_t udp_pc_port;		//PC端口号uint32_t netmask;			//本机子网掩码,大端模式uint32_t gateway;			//本机网关,大端模式uint32_t server_ip;			//服务器IP,大端模式uint32_t server_port;		//服务器端口号uint32_t version;			//stm32软件版本号uint32_t sn_num[4];			//SN号	5 6 7 8uint32_t sensor_type;		//传感器类型 9uint32_t sensor_data_type;	//传感器数据类型uint32_t sensor_interval;	//传感器采集时间间隔
}sys_para_t;/* -----------------------------------------------------------------------------
函数名:  	i2c_write
作者:    	glx
日期:    	2020-11-10
功能:    	数据写入eeprom
输入参数:	pData:数据指针
返回值:  	类型:HAL_StatusTypeDefHAL_OK:操作成功HAL_ERROR:操作失败
修改记录:
------------------------------------------------------------------------------*/
HAL_StatusTypeDef i2c_write(void *pData)
{HAL_StatusTypeDef ret = HAL_ERROR;uint8_t i, page, pageSize;uint8_t *p = (uint8_t *)pData;if(pData != NULL){if(I2C_MEMADD_SIZE == I2C_MEMADD_SIZE_8BIT){pageSize = 16;			//一页16字节,不能跨页写page = SYS_PARA_MAX/4;}else if(I2C_MEMADD_SIZE == I2C_MEMADD_SIZE_16BIT){pageSize = 32;			//一页32字节,不能跨页写page = SYS_PARA_MAX/8;}//写完整的页for(i = 0; i<page; i++){ret = HAL_I2C_Mem_Write(&hi2c1, ADDR_AT24C02_Write, i*pageSize, I2C_MEMADD_SIZE, p, pageSize, 100);p += pageSize;if(ret != HAL_OK){printf("I2C_Write Sys Para write error\r\n");return HAL_ERROR;}HAL_Delay(5);}//写残缺的页if(SYS_PARA_MAX > (page*4))	{ret = HAL_I2C_Mem_Write(&hi2c1, ADDR_AT24C02_Write, i*pageSize, I2C_MEMADD_SIZE, p, 4*SYS_PARA_MAX-pageSize*page, 100);if(ret != HAL_OK){printf("I2C_Write Sys Para write error\r\n");return HAL_ERROR;}HAL_Delay(5);}}else{printf("I2C_Write pData NULL\r\n");return HAL_ERROR;}return HAL_OK;
}/* -----------------------------------------------------------------------------
函数名:  	i2c_read
作者:    	glx
日期:    	2020-11-10
功能:    	读取eeprom数据
输入参数:	pData:数据指针
返回值:  	类型:HAL_StatusTypeDefHAL_OK:操作成功HAL_ERROR:操作失败
修改记录:
------------------------------------------------------------------------------*/
HAL_StatusTypeDef i2c_read(void *pData)
{HAL_StatusTypeDef ret = HAL_ERROR;if(pData != NULL){ret = HAL_I2C_Mem_Read(&hi2c1, ADDR_AT24C02_Read, 0, I2C_MEMADD_SIZE, (uint8_t *)pData, SYS_PARA_MAX*4, 100);if(ret != HAL_OK){printf("I2C_Read Sys Para read error\r\n");return HAL_ERROR;}		}else{printf("I2C_Read pData NULL\r\n");return HAL_ERROR;}return HAL_OK;
}

读写的代码


//用于保存系统参数
sys_para_t sys_para = {0};	
//用于保存系统默认参数
sys_para_t default_para = {.local_ip = 107<<24 | 10<<16 | 168<<8 | 192,.udp_local_port = 18080,.udp_pc_port = 18081,.netmask = 0<<24 | 255<<16 | 255<<8 | 255,.gateway = 1<<24 | 10<<16 | 168<<8 | 192,.server_ip = 9<<24 | 10<<16 | 168<<8 | 192,.server_port = 18082,.version = 20201124,.sn_num[0] = 0xffffffff,.sn_num[1] = 0xffffffff,.sn_num[2] = 0xffffffff,.sn_num[3] = 0xffffffff,.sensor_type = SENSOR_DOOR,.sensor_data_type = SENSOR_DATA_GPIO,.sensor_interval = 1000,
};i2c_read(&sys_para);
i2c_write(&default_para);

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

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

相关文章

Python-VBA函数之旅-chr函数

目录 1、chr函数&#xff1a; 1-1、Python&#xff1a; 1-2、VBA&#xff1a; 2、相关文章&#xff1a; 个人主页&#xff1a;非风V非雨-CSDN博客 chr函数在Python编程中有多种应用场景&#xff0c;它主要用于将Unicode码点转换为对应的字符。常见的应用场景有&#xff1a…

性能优化“万金油”:缓存Cache

1、首次请求数据时,先从缓存中获取,如果没有,则继续向数据库中获取。获取到数据后,将数据保存到缓存中。再次请求数据,一样先从缓存中获取,成功获取,“缓存命中”。多次请求中,命中次数占全部请求次数的比例,叫“命中率”。如果数据源的数据发生变化,而缓存中的数据没…

流媒体的安全谁来保障

流媒体的安全谁来保障 说起媒体&#xff0c;我们马上就会想到报纸新闻、广播、电视。 其实所谓的流媒体同我们通常所指的媒体是不一样的&#xff0c; 它只是一个技术名词。流媒体到底是什么&#xff1f;能给我们的生活带来什么&#xff1f;跟小德一起来看看。 流媒体是什么&a…

Harmony鸿蒙南向驱动开发-HDMI接口使用

功能简介 HDMI&#xff08;High Definition Multimedia Interface&#xff09;&#xff0c;即高清多媒体接口&#xff0c;主要用于DVD、机顶盒等音视频Source到TV、显示器等Sink设备的传输。 HDMI以主从方式工作&#xff0c;通常有一个Source端和一个Sink端。 HDMI接口定义了…

谷歌查问题

1&#xff0c;打开 it工具箱-里面啥都有 2&#xff0c;找到谷歌 3&#xff0c;访问gpt

构建强大的物联网平台系统架构:关键步骤与最佳实践

随着物联网&#xff08;IoT&#xff09;技术的快速发展和广泛应用&#xff0c;越来越多的企业开始意识到搭建一个强大而可靠的物联网平台系统架构的重要性。一个完善的物联网平台可以帮助企业高效地管理和监控各种连接设备&#xff0c;并实现数据的收集、处理和分析。在本文中&…

最新ChatGPT网站系统源码Midjourney-AI绘画,GPTs,AI换脸支持

一、前言 SparkAi创作系统是基于ChatGPT进行开发的Ai智能问答系统和Midjourney绘画系统&#xff0c;支持OpenAI-GPT全模型国内AI全模型。本期针对源码系统整体测试下来非常完美&#xff0c;那么如何搭建部署AI创作ChatGPT&#xff1f;小编这里写一个详细图文教程吧。已支持GPT…

行测——资料分析

行测——资料分析 0. 资料分析必背内容0.1 专业名词0.2 分数转化百分数 1. 占比问题1.0 简单占比1.1 前期比重与本期比重例子 1.2 隔期占比例子 1.3 比重趋势例子 1.4 比重差例子 2. 盐水问题2.1 定性类例子 2.2 定量类例子 3. 比较问题3.0 简单例子 4. 平均量问题4.1 年均增长…

Unity单个物体绑定多个相机在轨道上移动,录制不同角度视频

环境搭建 下载Cinemachine插件安装 打开包管理器 下载cinemachine插件 创建轨道 使用dolly track 创建轨道 右侧可以删减关键点&#xff0c;注意调整y坐标 创建cart 把前面的轨道拖到path中&#xff0c;注意这里的speed要设定不为0才会动 设置VItual Camera 根据需…

HWOD:二维数组下标合法性判断

一、知识点 1、停止读取的判定条件有两种写法 (1)&#xff1a;while(scanf()!EOF) (2)&#xff1a;while(scanf()>0) 在代码提交平台(1)和(2)都可以用 在本地&#xff0c;只能用(2)&#xff0c;且要有非法输入的配合。比如要读取的是整数&#xff0c;但输入了字符串&am…

Freemarker 的配置

Freemarker是一种用于生成动态模板的Java模板引擎。它可以将数据与模板文件结合&#xff0c;生成最终的输出内容。以下是使用Freemarker的实现和配置步骤&#xff1a; 引入Freemarker库&#xff1a;首先需要将Freemarker库添加到项目的依赖中。可以通过在Maven或Gradle中添加相…

PS入门|如何让模糊的图片变得清晰?

前言 前段时间的PS入门讲的都是如何抠图、抠图、抠图。小白都快抠出三室一厅了&#xff0c;不知道学习的小伙伴如何了。 如果在学习过程中没有练习的照片&#xff0c;那直接使用每一篇文章的照片即可&#xff0c;学PS最忌讳的就是光看不练&#xff0c;眼睛会了&#xff0c;手…