# cotParam
**Repository Path**: MagicBude/cot_param
## Basic Information
- **Project Name**: cotParam
- **Description**: 轻量级参数管理框架(C语言)
- **Primary Language**: C
- **License**: MIT
- **Default Branch**: master
- **Homepage**: None
- **GVP Project**: No
## Statistics
- **Stars**: 0
- **Forks**: 0
- **Created**: 2025-06-16
- **Last Updated**: 2025-06-17
## Categories & Tags
**Categories**: Uncategorized
**Tags**: None
## README
# 轻量级参数管理框架(增强注释版)
嵌入式软件中的系统数据参数是指在嵌入式系统中用于实现系统功能和控制的各种数据,如用户参数、状态、配置信息等;那么如何管理这些数据对于嵌入式系统的正确运行和维护非常重要。
该参数管理框架代码就是如何统一管理软件中的各类系统数据参数。
> 该参数管理并不涉及数据是如何储存的。因为有些系统数据并不需要储存起来,只需要进行管理而已。
## 介绍
本项目是基于[大橙子疯](https://blog.csdn.net/qq_24130227)的[轻量级参数管理框架](https://gitee.com/cot_package/cot_param),在原有代码基础上增加了更详细的注释和使用说明,便于学习和理解。
### 参数管理
* [X] 通过将已定义变量(**全局变量**)添加到参数表进行参数的统一管理,单个参数包括了当前值、缺省值、最小值、最大值、参数名和属性等信息。
> - 当前值:已定义的变量
> - 缺省值:默认值,并非变量初值
> - 最小值:该参数的最小值
> - 最大值:该参数的最大值
> - 参数名:对该参数的描述
> - 属性:有多种属性信息,方便后续功能扩展
> - 读/写权限:参数模块中无具体作用,可用于UI或者其他方式显示时使用
> - 重置权限:设置默认值后则存在该属性
> - 校验权限:设置最大最小值后则存在该属性
>
* [X] 根据不同的场景管理不同的参数
> - 无缺省值、最小值和最大值限制,适合于记录类型的参数,比如状态数据或历史数据等
> - 有缺省值,但无最小值和最大值限制,适合于配置类型的参数
> - 有缺省值,最小值和最大值限制,适合于关键性类型的参数,比如用户参数或者关键的状态数据等
>
* [X] 同时若单个参数表无法满足参数数目或者参数分类管理,可定义多张参数表
> - 每张参数表中的参数ID唯一,不可重复;
> - 不同参数表ID可以重复定义
>
### 参数类型
* [X] 数值类型参数
> `int`、`float`、`double` 等基本类型的参数
>
* [X] 字符串类型参数
> `char` 定义用来储存字符串的数组
>
### 参数校验
为了更好的统一管理参数,可以对参数设置的范围进行校验,防止参数设置超出预期范围,导致程序不可控。
* [X] 范围校验
> 根据参数的最大和最小值进行判断,数值类型的参数则根据数值超出范围判断。而字符串则是根据字符串长度超出范围判断。
>
* [X] 自定义校验
> 提供回调函数,每个参数可设置自定义的校验方式,比如某个参数需要设置为多少的倍数,或者根据其他参数决定当前参数的取值范围等。
>
上述两种校验方式均需要参数设置缺省值,最小值和最大值后才有效。
### 兼容性
* [X] 提供了参数表的序列化和反序列化操作。
> - 方便在本地储存设备(如flash、eeprom等)保存/读取二进制数据,甚至还可以跨设备传输使用
> - 提供了两种方式:
> - 第一种:只需要提供参数数据保存/加载的回调函数,调用相关接口函数完成参数的序列化保存和反序列化加载;该方式会多次触发回调函数保存数据和读取数据,适用于小内存的平台使用(不需要额外申请内存)
> - 第二种:需要提前申请内存用来保存参数表序列化的数据或者读取即将反序列化的数据;一次性完成操作(需要申请较大的内存完成)
>
* [X] 支持启用键值对功能
> - 每个参数都需要指定唯一的ID,在后期版本迭代对参数表删除、插入或添加参数时也能向下兼容,不会影响其他参数。
> - 即使更新了参数表的最大容纳范围,也能保证兼容,比如上个版本配置参数只支持16个,当前版本配置支持了256个,也能正确处理。
> - 启用键值对后序列化的数据长度也会比较大,因为每个参数序列化时包含了ID和长度信息。
>
### 可裁剪
根据不同的平台,可以对部分功能裁剪,或者修改配置适用于不同容量的芯片进行开发。
| 配置选项 | 描述 |
| ------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
| `COT_PARAM_USE_KEY_VALUE` | 是否采用键值对方式序列化和反序列化参数数据 |
| `COT_PARAM_USE_CUSTOM_CHECK` | 是否启用参数自定义校验功能 |
| `COT_PARAM_USE_STRING_TYPE` | 是否启用字符串参数类型 |
| `COT_PARAM_USE_64_BIT_LENGTH` | 是否启用64bit的参数类型 |
| `COT_PARAM_NAME_MAX_LENGTH` | 参数名字最大定义长度,小于或等于1则禁用参数名功能 |
| `COT_PARAM_STRING_MAX_LENGTH` | 字符串类型的参数取值最大定义长度(包括结束符),需启用 `COT_PARAM_USE_STRING_TYPE` |
| `COT_PARAM_SUPPORT_NUM` | 单张参数表最多添加多少个参数,需启用 `COT_PARAM_USE_KEY_VALUE`,可选:
`COT_PARAM_SUPPORT_16`:ID取值范围0-15,即最多16个参数
`COT_PARAM_SUPPORT_256`:ID取值范围0-255,即最多256个参数
`COT_PARAM_SUPPORT_4096`:ID取值范围0-4095,即最多4096个参数
注:若没有启用 `COT_PARAM_USE_KEY_VALUE` 键值对方式,则无限制 |
## 文件结构
- `cot_param.h`/`cot_param.c`: 核心参数管理模块
- `cot_param_cfg.h`: 参数管理配置选项
- `cot_param_type.h`: 参数管理类型定义
- `examples/`: 示例代码
- `24CXX/`: AT24CXX系列EEPROM驱动及参数存储示例
- `sys_param/`: 系统参数管理示例
- `IIC/`: IIC接口驱动示例
## 使用说明
### 参数表定义
以下是基于sys_param示例的参数定义方式:
```c
/**
* @brief 参数演示结构体
* @details 包含了各种类型的参数用于演示参数管理框架的功能
*/
typedef struct
{
uint16_t usValue; /* 无符号16位整型参数 */
uint8_t ucValue; /* 无符号8位整型参数 */
uint32_t uiValue; /* 无符号32位整型参数 */
float fValue; /* 单精度浮点型参数 */
char szString_1[12]; /* 字符串参数1 */
double dValue; /* 双精度浮点型参数 */
int16_t sValue; /* 有符号16位整型参数 */
int8_t cValue; /* 有符号8位整型参数 */
int32_t iValue; /* 有符号32位整型参数 */
char szString_2[10]; /* 字符串参数2 */
} ParamDemo_t;
/* 全局测试参数 */
int8_t g_cTest = 50; /* 全局测试参数1 */
char g_szString[10] = "qwer"; /* 全局测试参数2 */
ParamDemo_t g_tTestVal = /* 参数演示结构体实例,包含了各种类型参数的初始值 */
{
.usValue = 20,
.ucValue = 10,
.uiValue = 5000,
.fValue = 3.14,
.szString_1 = "abcd",
.dValue = 5.12,
.sValue = -100,
.cValue = -2,
.iValue = 300,
.szString_2 = "1234",
};
/* 自定义参数校验函数,检查参数值是否为偶数 */
static int CheckSValue(const void *pCurParam)
{
const int16_t *p_sValue = (const int16_t *)pCurParam;
if ((*p_sValue) % 2 != 0)
{
return -1;
}
return 0;
}
/* 参数管理器实例 */
static cotParamManager_t sg_tParamManager;
/* 参数表定义 */
static cotParamInfo_t sg_tParamTable[] =
{
/* ID, 变量, 类型, 属性, [默认值], [最小值], [最大值], [校验函数] */
COT_PARAM_ITEM_BIND(1, g_tTestVal.usValue, COT_PARAM_UINT16, COT_PARAM_ATTR_WR),
COT_PARAM_ITEM_BIND(2, g_tTestVal.ucValue, COT_PARAM_UINT8, COT_PARAM_ATTR_WR, 20),
COT_PARAM_ITEM_BIND(3, g_tTestVal.uiValue, COT_PARAM_UINT32, COT_PARAM_ATTR_WR, 4500, 1000, 10000),
COT_PARAM_ITEM_BIND(4, g_tTestVal.fValue, COT_PARAM_FLOAT, COT_PARAM_ATTR_WR, 10, -10.5, 10.5),
COT_PARAM_ITEM_BIND(5, g_tTestVal.szString_1, COT_PARAM_STRING, COT_PARAM_ATTR_WR, "abcd", 3, sizeof(g_tTestVal.szString_1)),
COT_PARAM_ITEM_BIND(6, g_tTestVal.dValue, COT_PARAM_DOUBLE, COT_PARAM_ATTR_WR, 0, -90.10, 100.10),
COT_PARAM_ITEM_BIND(7, g_tTestVal.sValue, COT_PARAM_INT16, COT_PARAM_ATTR_WR, 100, -200, 200, CheckSValue), /* 添加自定义校验 */
COT_PARAM_ITEM_BIND_WITH_NAME(8, "g_cTest", g_cTest, COT_PARAM_INT8, COT_PARAM_ATTR_WR, 50, -100, 100), /* 另取参数名 */
COT_PARAM_ITEM_BIND(9, g_szString, COT_PARAM_STRING, COT_PARAM_ATTR_WR, "XXX", 3, 6),
};
/* 初始化参数管理器 */
void InitParam(bool isReset)
{
/* 初始化参数管理器 */
cotParam_Init(&sg_tParamManager, sg_tParamTable, COT_PARAM_TABLE_SIZE(sg_tParamTable));
/* 其他初始化代码... */
}
```
### 参数保存/加载
将参数表的部分信息(包括当前值)进行序列化,方便进行数据保存,等下次开机再读取数据后反序列化,还原成参数表的这部分信息。
> 甚至将这部分数据传输给另一台设备,进行参数数据备份或同步等功能
将参数表序列化完成后,可以对数据进行二次封装,比如可以增加头信息,crc校验等再进行数据保存,同样在加载时也需要解析,将额外添加的信息去除后才能进行反序列化。
#### 第一种方式:使用回调函数
提供参数数据保存/加载的回调函数,调用相关接口函数完成参数的序列化保存和反序列化加载;该方式会多次触发回调函数保存数据和读取数据,适用于小内存的平台使用(不需要额外申请内存)。
以下是基于AT24C02 EEPROM的示例:
```c
/**
* @brief 参数加载回调函数
* @param pBuf 数据缓冲区指针
* @param bufSize 缓冲区大小
* @param pLength 实际读取的数据长度
* @return 0:成功 其他:失败
* @details 从存储设备读取数据,支持分段读取
*/
int OnLoadCallback(uint8_t *pBuf, uint16_t bufSize, uint16_t *pLength)
{
uint16_t length;
static uint32_t s_offset = 0;
/* 检查是否还有数据可读 */
if (s_offset < sg_length)
{
/* 判断剩余数据长度是否足够填满缓冲区 */
if (s_offset + bufSize <= sg_length)
{
length = bufSize;
}
else
{
length = sg_length - s_offset;
}
/* 从EEPROM读取数据 */
at24cxx_read(s_offset + PARAM_DATA_START_ADDR, pBuf, length);
s_offset += length;
}
else
{
/* 已无数据可读,重置状态并返回长度0 */
length = 0;
s_offset = 0;
}
/* 返回实际读取的长度 */
*pLength = length;
return 0;
}
/**
* @brief 参数保存回调函数
* @param pBuf 数据缓冲区指针
* @param len 数据长度
* @return 0:成功 其他:失败
* @details 向存储设备写入数据,支持分段写入
* 当len为0时表示写入完成,会保存总长度信息
*/
int OnSaveCallback(const uint8_t *pBuf, uint16_t len)
{
static uint32_t s_offset = 0;
/* 检查是否有数据需要写入 */
if (len > 0)
{
/* 将数据写入EEPROM */
at24cxx_write(s_offset + PARAM_DATA_START_ADDR, (uint8_t *)pBuf, len);
/* 更新写入位置和总数据长度 */
s_offset += len;
sg_length = s_offset;
}
else
{
/* len为0表示写入完成,保存总数据长度到EEPROM */
at24cxx_write(PARAM_LENGTH_ADDR, (uint8_t *)&sg_length, sizeof(sg_length));
/* 重置写入位置,为下次写入做准备 */
s_offset = 0;
}
return 0;
}
/**
* @brief 参数校验错误时的处理函数
* @param pParamInfo 参数信息指针
* @param eCheckResult 校验结果
* @return 0:成功
* @details 打印错误参数信息并重置为默认值
*/
int OnCheckErrorResetHandle(const cotParamInfo_t *pParamInfo, cotParamCheckRet_e eCheckResult)
{
/* 打印错误信息 */
printf("参数校验失败: ID=%d\n", pParamInfo->id);
/* 重置为默认值 */
cotParam_SingleParamResetDefValue(pParamInfo);
return 0;
}
/**
* @brief 从EEPROM加载参数
* @param isReset 是否在加载后进行参数校验
*/
void ReloadParam(bool isReset)
{
/* 从EEPROM读取数据长度 */
at24cxx_read(PARAM_LENGTH_ADDR, (uint8_t *)&sg_length, sizeof(sg_length));
/* 检查数据长度有效性 */
printf("Loading parameters, data length=%lu\n", sg_length);
/* 从存储设备加载参数 */
cotParam_Load(&sg_tParamManager, OnLoadCallback);
/* 如果需要进行参数校验 */
if (isReset)
{
cotParam_Check(&sg_tParamManager, OnCheckErrorResetHandle);
}
}
/**
* @brief 保存参数到EEPROM
* @param isReset 是否在保存前进行参数校验
*/
void SaveParam(bool isReset)
{
/* 如果需要进行参数校验 */
if (isReset)
{
cotParam_Check(&sg_tParamManager, OnCheckErrorResetHandle);
}
/* 保存参数到存储设备 */
cotParam_Save(&sg_tParamManager, OnSaveCallback);
}
```
#### 第二种方式:一次性完成
需要提前申请内存用来保存参数表序列化的数据或者读取即将反序列化的数据。
```c
/**
* @brief 一次性保存参数
*/
void SaveParamOnce(void)
{
uint8_t *pBuf = (uint8_t *)malloc(cotParam_GetSerializeSize(&sg_tParamManager));
uint32_t length;
/* 保存前全部参数进行校验(可选) */
cotParam_Check(&sg_tParamManager, OnCheckErrorResetHandle);
/* 序列化参数到内存缓冲区 */
length = cotParam_Serialize(&sg_tParamManager, pBuf);
/* 写入EEPROM */
at24cxx_write(PARAM_DATA_START_ADDR, pBuf, length);
/* 保存数据长度 */
at24cxx_write(PARAM_LENGTH_ADDR, (uint8_t *)&length, sizeof(length));
free(pBuf);
}
/**
* @brief 一次性加载参数
*/
void LoadParamOnce(void)
{
uint32_t length;
uint8_t *pBuf;
/* 读取数据长度 */
at24cxx_read(PARAM_LENGTH_ADDR, (uint8_t *)&length, sizeof(length));
/* 分配内存缓冲区 */
pBuf = (uint8_t *)malloc(length);
/* 从EEPROM读取数据 */
at24cxx_read(PARAM_DATA_START_ADDR, pBuf, length);
/* 反序列化参数 */
cotParam_Deserialization(&sg_tParamManager, pBuf, length);
/* 加载后全部参数进行校验(可选) */
cotParam_Check(&sg_tParamManager, OnCheckErrorResetHandle);
free(pBuf);
}
```
### 参数操作
以下是常用的参数操作函数:
```c
/* 重置所有参数为默认值 */
void ResetParam(void)
{
cotParam_ResetDefault(&sg_tParamManager);
}
/* 修改单个参数 */
void SingleParamChange(const void *pCurParam, ...)
{
va_list args;
va_start(args, pCurParam);
cotParam_SingleParamChangeImpl(&sg_tParamManager, pCurParam, args);
va_end(args);
}
/* 校验参数并处理 */
int SingleParamCheckProcess(const void *pCurParam, cotParamResetOpt_e eResetOpt)
{
return cotParam_SingleParamCheckProcess(
cotParam_FindParamByParamPtr(&sg_tParamManager, pCurParam),
eResetOpt
);
}
/* 检查参数值是否合法 */
cotParamCheckRet_e SingleParamCheck(const void *pCurParam, const void *pCheckValue)
{
cotParamCheckRet_e eCheckResult;
cotParam_SingleParamCheckInput(
cotParam_FindParamByParamPtr(&sg_tParamManager, pCurParam),
pCheckValue,
&eCheckResult
);
return eCheckResult;
}
/* 参数自检 */
cotParamCheckRet_e SingleParamSelfCheck(const void *pCurParam)
{
cotParamCheckRet_e eCheckResult;
cotParam_SingleParamSelfCheck(
cotParam_FindParamByParamPtr(&sg_tParamManager, pCurParam),
&eCheckResult
);
return eCheckResult;
}
/* 重置参数为默认值 */
void SingleParamResetResetDefValue(const void *pCurParam)
{
cotParam_SingleParamResetDefValue(
cotParam_FindParamByParamPtr(&sg_tParamManager, pCurParam)
);
}
```
### 使用示例
下面是一个完整的使用流程示例:
```c
/**
* @brief 参数保存测试示例函数
* @details 演示参数管理框架的常用操作,包括:
* - 参数校验与处理
* - 参数值修改
* - 参数自检
* - 参数值更新前的检查
* - 参数保存和加载
*/
void ParamSaveTestExample(void)
{
/* 修改参数值并进行范围校验和处理 */
g_tTestVal.uiValue = 10001;
SingleParamCheckProcess(&g_tTestVal.uiValue, COT_PARAM_RESET_MIN_MAX); /* 修改后检查并处理:如果小于最小值则恢复最小值,大于最大值则恢复最大值 */
/* 对某个变量参数变更后(当前值已经变化)进行校验处理,若超出范围则恢复默认 */
g_tTestVal.uiValue = 50;
if (SingleParamSelfCheck(&g_tTestVal.uiValue) != COT_PARAM_CHECK_OK) /* 修改后检查 */
{
SingleParamResetResetDefValue(&g_tTestVal.uiValue); /* 如果校验失败,则恢复为默认值 */
}
/* 对某个变量参数在需要变更前(当前值没有变化)进行校验处理,得到校验结果后自行处理 */
uint32_t tmp = 50;
if (SingleParamCheck(&g_tTestVal.uiValue, &tmp) == COT_PARAM_CHECK_OK) /* 修改前检查(参数和被检查变量值类型需要一样) */
{
g_tTestVal.uiValue = tmp; /* 如果校验成功,则修改 */
}
/* 修改字符串参数和整型参数 */
sprintf(g_szString, "zxcv");
SingleParamChange(&g_tTestVal.uiValue, 3000);
SingleParamChange(g_tTestVal.szString_1, "hello");
/* 保存参数到存储设备并重新加载 */
SaveParam(true);
ReloadParam(true);
/* 显示所有参数信息 */
ShowAllParam();
}
int main(void)
{
/* 系统初始化代码... */
/* 初始化参数管理器 */
InitParam(true);
/* 测试参数管理功能 */
ParamSaveTestExample();
while(1)
{
/* 主循环 */
}
}
```
## 移植说明
要将参数管理系统适配到不同平台,主要需要考虑以下几点:
1. **配置选项**:根据目标平台资源调整 `cot_param_cfg.h` 中的配置选项
2. **存储介质**:根据实际硬件选择合适的存储介质(Flash、EEPROM等)
3. **接口实现**:实现参数加载和保存的回调函数,适配目标存储介质
对于使用AT24C02等EEPROM的情况:
- 确保正确实现I2C通信接口(硬件或软件I2C)
- 注意EEPROM的写入时间和页写入大小限制
- 考虑添加数据校验机制(如CRC)以确保数据完整性