表驱动法在STM32中的应用

1、概念所谓表驱动法(Table-Driven Approach)简而言之就是用查表的方法获取数据 。此处的“表”通常为数组 , 但可视为数据库的一种体现 。根据字典中的部首检字表查找读音未知的汉字就是典型的表驱动法,即以每个字的字形为依据,计算出一个索引值,并映射到对应的页数 。相比一页一页地顺序翻字典查字,部首检字法效率极高 。
具体到编程方面,在数据不多时可用逻辑判断语句(if…else或switch…case)来获取值;但随着数据的增多,逻辑语句会越来越长,此时表驱动法的优势就开始显现 。
2、简单示例上面讲概念总是枯燥的,我们简单写一个C语言的例子 。下面例子功能:传入不同的数字打印不同字符串 。
使用if…else逐级判断的写法如下
void fun(int day){if (day == 1){printf("Monday\n");}else if (day == 2){printf("Tuesday\n");}else if (day == 3){printf("Wednesday\n");}else if (day == 4){printf("Thursday\n");}else if (day == 5){printf("Friday\n");}else if (day == 6){printf("Saturday\n");}else if (day == 7){printf("Sunday\n");}}使用switch…case的方法写
void fun(int day){switch (day){case 1:printf("Monday\n");break;case 2:printf("Tuesday\n");break;case 3:printf("Wednesday\n");break;case 4;printf("Thursday\n");break;case 5:printf("Friday\n");break;case 6:printf("Saturday\n");break;case 7:printf("Sunday\n");break;default:break;}}使用表驱动法实现
char weekDay[] = {Monday,Tuesday,Wednesday,Thursday,Friday,Saturday,Sunday};void fun(int day){printf("%s\n",weekDay[day]);}看完示例,可能“恍然大悟” , 一拍大腿 , 原来表驱动法就是这么简单啊 。是的,它的核心原理就是这个简单,如上面例子一样 。
如果上面的例子还没get这种用法的好处,那么再举一个栗子 。
统计用户输入的一串数字中每个数字出现的次数 。
常规写法
int32_t aDigitCharNum[10] = {0}; /* 输入字符串中各数字字符出现的次数 */int32_t dwStrLen = strlen(szDigits);int32_t dwStrIdx = 0;for (; dwStrIdx < dwStrLen; dwStrIdx++){switch (szDigits[dwStrIdx]){case '1':aDigitCharNum[0]++;break;case '2':aDigitCharNum[1]++;break;//... ...case '9':aDigitCharNum[8]++;break;}}表驱动法
for(; dwStrIdx < dwStrLen; dwStrIdx++){aDigitCharNum[szDigits[dwStrIdx] - '0']++;}偶尔在一些开源项目中看到类似的操作,惊呼“骚操作”,其实他们有规范的叫法:表驱动法 。
3、在MCU中应用在MCU中的应用示例,怎么少的了点灯大师操作呢?首先来点一下流水LED灯吧 。
常规写法
void LED_Ctrl(void){static uint32_t sta = 0;if (0 == sta){LED1_On();}else{LED1_Off();}if (1 == sta){LED2_On();}else{LED2_Off();}/* 两个灯,最大不超过2 */sta = (sta + 1) % 2;}/* 主函数运行 */int main(void){while (1){LED_Ctrl();os_delay(200);}}表驱动法
extern void LED1_On(void);extern void LED1_Off(void);extern void LED2_On(void);extern void LED2_Off(void);/* 把同一个灯的操作封装起来 */struct tagLEDFuncCB{void (*LedOn)(void);void (*LedOff)(void);};/* 定义需要操作到的灯的表 */const static struct tagLEDFuncCB LedOpTable[] ={{LED1_On, LED1_Off},{LED2_On, LED2_Off},};void LED_Ctrl(void){static uint32_t sta = 0;uint8_t i;for (i = 0; i < sizeof(LedOpTable) / sizeof(LedOpTable[0]); i++){(sta == i) ? (LedOpTable[i].LED_On()) : (LedOpTable[i].LED_Off());}/* 跑下个灯 */sta = (sta + 1) % (sizeof(LedOpTable) / sizeof(LedOpTable[0]));}int main(void){while (1){LED_Ctrl();os_delay(200);}}这样的代码结构紧凑,因为和结构体结合起来了,方便添加下一个LED灯到流水灯序列中,这其中涉及到函数指针 , 详细请看《回调函数》 , 只需要修改LedOpTable如下
const static struct tagLEDFuncCB LedOpTable[] ={{LED1_On, LED1_Off},{LED2_On, LED2_Off},{LED3_On, LED3_Off},};这年头谁还把流水灯搞的这么花里胡哨的啊,那么就举例在串口解析中的应用,之前的文章推送过《回调函数在命令解析中的应用》,下面只贴一下代码
typedef struct{rt_uint8_t CMD;rt_uint8_t (*callback_func)(rt_uint8_t cmd, rt_uint8_t *msg, uint8_t len);} _FUNCCALLBACK;_FUNCCALLBACK callback_list[] ={{cmd1, func_callback1},{cmd2, func_callback2},{cmd3, func_callback3},{cmd4, func_callback41},...};void poll_task(rt_uint8_t cmd, rt_uint8_t *msg, uint8_t len){int cmd_indexmax = sizeof(callback_list) / sizeof(_FUNCCALLBACK);int cmd_index = 0;for (cmd_index = 0; cmd_index < cmd_indexmax; cmd_index++){if (callback_list[cmd_index].CMD == cmd){if (callback_list[cmd_index]){/* 处理逻辑*/callback_list[cmd_index].callback_func(cmd, msg, len);}}}}除上述例子,表驱动法在UI界面中也有良好的应用,如下
结构体封装
typedef enum{stage1 = 0,stage2,stage3,stage4,stage5,stage6,stage7,stage8,stage9,} SCENE;typedef struct{void (*current_operate)(); //当前场景的处理函数SCENE Index;//当前场景的标签SCENE Up;//按下Up键跳转的场景SCENE Down;//按下Down键跳转的场景SCENE Right;//按下Left键跳转的场景SCENE Left;//按下Right键跳转的场景} STAGE_TAB;

推荐阅读