我在小型嵌入式设备重新设计(PID 控制器)中解决的主要问题是设备参数存储。我在这里部分介绍的旧解决方案是节省空间的,但在添加新参数时维护起来很笨拙。它基于必须与 EEPROM 地址匹配的设备参数 ID,如下面的示例所示:
// EEPROM variable addresses
#define EE_CRC 0 // EEPROM CRC-16 value
#define EE_PROCESS_BIAS 1 // FLOAT, -100.00 - 100.00 U
#define EE_SETPOINT_VALUE 3 // FLOAT, -9999 - 9999.9
#define EE_SETPOINT_BIAS 5 // CHAR, -100 - 100 U
#define EE_PID_USED 6 // BYTE, 1 - 3
#define EE_OUTPUT_ACTION 7 // LIST, DIRE/OBRNU
#define EE_OUTPUT_TYPE 8 // LIST, GRIJA/MOTOR
#define EE_PROCESS_BIAS2 9 // FLOAT, -100.00 - 100.00 U
#define EE_SETPOINT_VALUE2 11 // FLOAT, -9999 - 9999.9
#define EE_SETPOINT_BIAS2 13 // CHAR, -100 - 100 U
#define EE_PID_USED2 14 // BYTE, 1 - 3
#define EE_OUTPUT_ACTION2 15 // LIST, DIRE/OBRNU
#define EE_OUTPUT_TYPE2 16 // LIST, GRIJA/MOTOR
#define EE_LINOUT_CALIB_ZERO 17 // FLOAT, -100.0 - 100.0
#define EE_LINOUT_CALIB_GAIN 19 // FLOAT, -2.0 - 2.0
每个地址都是硬编码的,下一个地址是根据之前的数据大小定义的(注意地址之间的间距不均匀)。它很有效,因为没有浪费 EEPROM 数据存储,但很难在不引入错误的情况下进行扩展。
在代码的其他部分(即 HMI 菜单、数据存储...),代码将使用与刚刚给出的地址匹配的参数列表,如下所示:
// Parameter identification, NEVER USE 0 (zero) as ID since it's NULL
// Sequence is not important, but MUST be same as in setparam structure
#define ID_ENTER_PASSWORD_OPER 1
#define ID_ENTER_PASSWORD_PROGRAM 2
#define ID_ENTER_PASSWORD_CONFIG 3
#define ID_ENTER_PASSWORD_CALIB 4
#define ID_ENTER_PASSWORD_TEST 5
#define ID_ENTER_PASSWORD_TREGU 6
#define ID_PROCESS_BIAS 7
#define ID_SETPOINT_VALUE 8
#define ID_SETPOINT_BIAS 9
#define ID_PID_USED 10
#define ID_OUTPUT_ACTION 11
#define ID_OUTPUT_TYPE 12
#define ID_PROCESS_BIAS2 13
...
然后在使用这些参数的代码中,例如在下面给出的用户菜单结构中,我使用我自己的 PARAM 类型(结构)构建了项目:
struct param { // Parametar decription
WORD ParamID; // Unique parameter ID, never use zero value
BYTE ParamType; // Parametar type
char Lower[EDITSIZE]; // Lowest value string
char Upper[EDITSIZE]; // Highest value string
char Default[EDITSIZE]; // Default value string
BYTE ParamAddr; // Parametar address (in it's media)
};
typedef struct param PARAM;
现在参数列表构建为结构数组:
PARAM code setparam[] = {
{NULL, NULL, NULL, NULL, NULL, NULL}, // ID 0 doesn't exist
{ID_ENTER_PASSWORD_OPER, T_PASS, "0", "9999", "0", NULL},
{ID_ENTER_PASSWORD_PROGRAM, T_PASS, "0", "9999", "0", NULL},
{ID_ENTER_PASSWORD_CONFIG, T_PASS, "0", "9999", "0", NULL},
{ID_ENTER_PASSWORD_CALIB, T_PASS, "0", "9999", "0", NULL},
{ID_ENTER_PASSWORD_TEST, T_PASS, "0", "9999", "0", NULL},
{ID_ENTER_PASSWORD_TREGU, T_PASS, "0", "9999", "0", NULL},
{ID_PROCESS_BIAS, T_FLOAT, "-100.0", "100.0", "0", EE_PROCESS_BIAS},
{ID_SETPOINT_VALUE, T_FLOAT, "-999", "9999", "0.0", EE_SETPOINT_VALUE},
{ID_SETPOINT_BIAS, T_CHAR, "-100", "100", "0", EE_SETPOINT_BIAS},
{ID_PID_USED, T_BYTE, "1", "3", "1", EE_PID_USED},
{ID_OUTPUT_ACTION, T_LIST, "0", "1", "dIrE", EE_OUTPUT_ACTION},
{ID_OUTPUT_TYPE, T_LIST, "0", "1", "GrIJA", EE_OUTPUT_TYPE},
{ID_PROCESS_BIAS2, T_FLOAT, "-100.0", "100.0", "0", EE_PROCESS_BIAS2},
...
本质上,每个参数都有其唯一的 ID,并且该 ID 必须与硬编码的 EEPROM 地址相匹配。由于参数大小不固定,我无法将参数 ID 本身用作 EEPROM(或其他媒体)地址。上例中的 EEPROM 组织是 16 位字,但原则上没关系(更多的空间被浪费在字符上,所以我将来更喜欢 8 位组织)
问题:
有没有更优雅的方法来做到这一点?一些哈希表,众所周知的模式,类似问题的标准解决方案?现在 EEPROM 的大小要大得多,我不介意使用固定参数大小(为布尔参数浪费 32 位)来换取更优雅的解决方案。看起来使用固定大小的参数,我可以使用参数 ID 作为地址。这种方法有我看不到的明显缺点吗?
我现在使用的是分布式硬件(HMI、I/O和主控制器是分开的),我想使用所有设备都知道这个参数结构的结构,例如远程I/O知道如何扩展输入值,HMI 知道如何显示和格式化数据,所有这些都仅基于参数 ID。换句话说,我需要一个定义所有参数的地方。
我做了我的谷歌研究,很少能找到不包括一些数据库的小型设备。我什至在考虑一些 XML 定义,它会为我的数据结构生成一些 C 代码,但也许有一些更适合小型设备(高达 512 K 闪存,32 K RAM)的优雅解决方案?