表驱动法在STM32中的应用

admin 行业动态 2024-07-14 33

摘要:概念所谓表驱动法(Table-Driven Approach)简而言之就是用查表的方法获取数据。此处的“表”通常为数组,但可视为数据库的一种体现。 根据字典中的部首检字表查找读音未知的汉字就是典型的表驱动法,即以每个字的字形为依据,计算出一个索引值,并映射到对应的页数。相比一页一页地顺序翻字典查字,部...

概念

所谓表驱动法(Table-Driven Approach)简而言之就是用查表的方法获取数据。此处的“表”通常为数组,但可视为数据库的一种体现。

根据字典中的部首检字表查找读音未知的汉字就是典型的表驱动法,即以每个字的字形为依据,计算出一个索引值,并映射到对应的页数。相比一页一页地顺序翻字典查字,部首检字法效率极高。

具体到编程方面,在数据不多时可用逻辑判断语句(if…else或switch…case)来获取值;但随着数据的增多,逻辑语句会越来越长,此时表驱动法的优势就开始显现。

简单示例

上面讲概念总是枯燥的,我们简单写一个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]++;

}

偶尔在一些开源项目中看到类似的操作,惊呼“骚操作”,其实他们有规范的叫法:表驱动法。

在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;

函数映射表

STAGE_TAB stage_tab[] = {

//operate Index Up Down Left Right

{Stage1_Handler, stage1, stage4, stage7, stage3, stage2},

{Stage2_Handler, stage2, stage5, stage8, stage1, stage3},

{Stage3_Handler, stage3, stage6, stage9, stage2, stage1},

{Stage4_Handler, stage4, stage7, stage1, stage6, stage5},

{Stage5_Handler, stage5, stage8, stage2, stage4, stage6},

{Stage6_Handler, stage6, stage9, stage3, stage5, stage4},

{Stage7_Handler, stage7, stage1, stage4, stage9, stage8},

{Stage8_Handler, stage8, stage2, stage5, stage7, stage9},

{Stage9_Handler, stage9, stage3, stage6, stage8, stage7},

};

定义两个变量保存当前场景和上一个场景。

char current_stage=stage1;

char prev_stage=current_stage;

按下Up按键 跳转到指定场景current_stage的值根据映射表改变。

current_stage =stage_tab[current_stage].Up;

场景改变后 根据映射表执行相应的函数Handler。

if(current_stage!=prev_stage)

{

stage_tab[current_stage].current_operate();

prev_stage=current_stage;

}

这是一个简单的菜单操作,结合了表驱动法。在MCU中表驱动法有很多很多用处,本文的例子已经过多了,如果在通勤路上用手机看到这里,已经很难了。

后记

这篇文章我也看到网上一遍表驱动法的后总结的笔记,可能也有很多同学和我一样,在自己的项目中熟练应用了这种“技巧”,但今天才知道名字:表驱动法。

相关推荐

评论列表
关闭

用微信“扫一扫”