C语言 - 宏列表批量定义结构体数组、枚举

文摘   科技   2023-03-11 12:35   广东  

Keep Moving 

保持·热爱

MCU

预编译宏定义

C语言

宏列表



C语言宏列表的奇怪用法



实际场景

示例的数据结构

嵌入式软件开发中,我们精心设计数据结构,利用结构体高效管理MCU运行时各变量的值。


那么,总是有那么一些数据,数量众多、结构一致,且读写操作也相同,甚至名称上存在规律。

#define Channel1_ADDR      0x1234#define Channel2_ADDR      0x2134#define Channel1           1#define Channel2           2static CalPoint_t Channel1_Table[] ={  {1000, 23432},  {2000, 34453},};static CalPoint_t Channel2_Table[] ={  {100, 8356},};static CalTable_t XXX_CalTable[] ={  {    Channel1_ADDR,    2,    sizeof(Channel1_Table)/sizeof(CalPoint_t),    Channel1,    Channel1_Table},  {    Channel2_ADDR,    2,    sizeof(Channel2_Table)/sizeof(CalPoint_t),    Channel2,    Channel2_Table},};


宏替换,进行简单优化

针对“XXX_CalTable”结构体数组,利用宏的特性,以及关键的连接符“##”,提取数组中重复出现的标识符:

#define __GET_NUM(_ch)  (sizeof(_ch)/sizeof(CalPoint_t))#define __TABLE_REG(_ch, _n) \          {_ch##_ADDR,\          _n,\          __GET_NUM(_ch##_Table),\          _ch,\          _ch##_Table}

接着结构体数组就可以简化了,重复性的工作抛给了编译器:

static CalTable_t XXX_CalTable[] ={  __TABLE_REG(Channel1, 2),  __TABLE_REG(Channel2, 2),};


还能不能优化

上面精简了“XXX_CalTable”数组,但是:

  • “CalPoint_t”结构体未处理,占用篇幅

  • 一些宏,名称上与结构体数组有关联

  • 无法避免人为错误,使得名称不再关联


因而还可以考虑,用宏简化结构,并从根本上杜绝人为错误。



宏列表

举例下文             

              保存应用

示例,先定义数据结构

根据实际需求,设计三层结构:

typedef struct{    int32_t     actual;      /* 实际的目标值 */    uint32_t    original;    /* 原始值 */}CalPoint_t;typedef struct{    uint16_t    start_addr; /* EEPROM上的存储地址 */    uint8_t     point_size; /* 校准点的大小 */    uint8_t     number;     /* 校准点数 */    uint8_t     ch;         /* 通道 */    uint8_t     linkage_ch; /* 联动通道 */    CalPoint_t  *point;     /* 校准点 */}CalTable_t;typedef struct{    uint8_t     ch_number;  /* 通道数 */    CalTable_t  *table;     /* 校准表格 */}CalObj_t;


定义宏列表

宏列表用来注册结构体、数组、通道枚举等,我们只需要维护它即可:

/* 注册校准表格。每个通道至少要有一个点,否则编译报错 */#define  CAL_TABLE(__table_name, __ch, __p, __pend, __end) \__table_name(ADC_CalTable)\__ch(CH_VOLT_ADC_OVP, 2, CH_VOLT_ADC_OVP)\__p(500, 100)\__p(3500, 800)\__pend()\__ch(CH_VOLT_ADC_PACK, 2, CH_VOLT_ADC_PACK)\__p(-100, 10)\__p(0,    3276)\__p(200,  5000)\__pend()\__end()\__table_name(DAC_CalTable)\__ch(CH_SET_CC1_10A, 2, CH_SET_CC1_10A)\__p(500, 100)\__p(4000, 1000)\__pend()\__end()


自动生成通道参数

依据宏列表的格式,编写宏展开规则

  • 这里通道采用枚举来定义

  • 提取宏列表中的通道信息,定义枚举项

  • 其余宏列表参数,宏展开为空

/* 注册参数全为空 */#define __NULL(...) /* 注册通道的枚举 */#define TABLE_NAME_REG(_name) enum E_##_name##_t{#define CHANNEL_ENUM_REG(ch, ...) ch,#define END_ENUM_CFG()            };

生成枚举:

/* 定义各个通道的枚举 */CAL_TABLE(TABLE_NAME_REG, CHANNEL_ENUM_REG, __NULL, __NULL, END_ENUM_CFG);

宏展开如下,该枚举可作为结构体数组的索引:

enum E_ADC_CalTable_t{  CH_VOLT_ADC_OVP,  CH_VOLT_ADC_PACK,};enum E_DAC_CalTable_t{  CH_SET_C1_10A,};


自动生成结构体数组

这里我们要生成多个“CalPoint_t”类型的结构体数组:

/* 注册独立的校准通道 */#define CHANNEL_POINT_REG(_ch, ...) \        static CalPoint_t _ch##_Table[] = {#define POINT_CFG(x, y)             {x, y},#define POINT_END()                 };

生成“CalPoint_t”类型的结构体数组:

/* 定义各个通道的校准点 */CAL_TABLE(__NULL, CHANNEL_POINT_REG, POINT_CFG, POINT_END, __NULL);


自动生成复杂的结构体数组

/* 注册校准表格 */#define TABLE_REG(_t) \          static CalTable_t _t##Item[] = {#define CHANNEL_REG(_ch, _n, _lch) \              {_ch##_ADDR,\              _n,\              GET_POINT_NUM(_ch##_Table),\              _ch,\              _lch,\              _ch##_Table},#define END_CFG()       };

生成"CalTable_t"结构体数组:

/* 定义校准表格 */CAL_TABLE(TABLE_REG, CHANNEL_REG, __NULL, __NULL, END_CFG);

宏展开如下:

static CalTable_t ADC_CalTableItem[] = {  {    CH_VOLT_ADC_OVP_ADDR,   /* 存储地址 */    2,                      /* 校准点的大小 */    2,                      /* 校准点数 */    CH_VOLT_ADC_OVP,        /* 通道 */    CH_VOLT_ADC_OVP,        /* 联动通道 */    CH_VOLT_ADC_OVP_Table}, /* 校准点 */  {    CH_VOLT_ADC_PACK_ADDR,    2,    2,    CH_VOLT_ADC_PACK,    CH_VOLT_ADC_PACK,    CH_VOLT_ADC_PACK_Table},};static CalTable_t DAC_CalTableItem[] = {  {    CH_SET_CC1_10A_ADDR,    2,    1,    CH_SET_CC1_10A,    CH_SET_CC1_10A,    CH_SET_CC1_10A_Table},};


生成结构体数组、索引下标

利用宏列表,生成:

  • “CalObj_t”类型的结构体数组

  • 该数组的索引下标,枚举形式

/* 注册表格的枚举 */#define  TABLE_ENUM_REG(_table)  _table,/* 注册用户校准表格 */#define  TABLE_USER_REG(_t) \        {GET_CH_NUM(_t##Item), _t##Item},

生成表格的枚举:

/* 定义校准表格的通道 */typedef enum{  CAL_TABLE(TABLE_ENUM_REG, __NULL, __NULL, __NULL, __NULL)}E_CalTable_t;

生成用户校准表格:

/* 用户校准表格控制块。结构已固定 */static CalObj_t __userCalTable[] = {  CAL_TABLE(TABLE_USER_REG, __NULL, __NULL, __NULL, __NULL)};

宏展开如下:

static CalObj_t __userCalTable[] = {  {2, ADC_CalTableItem},  {1, DAC_CalTableItem},};



总结

总结

为了满足程序需求,我们定义了许多数据,这些分散且独立的数据非常不好维护。采用宏列表:

  • 接口统一注册

  • 用宏展开自动定义数据

  • 可避免细微误动作埋下的逻辑BUG

  • 后期能快速增加、删减数据,灵活度高


宏列表不好的地方也很明显,框架固化,宏定义设计繁琐。






-END-


碎片聚合
求真务实
 最新文章