表驱动法在STM32中的应用

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

概念

所谓表驱动法(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");
    }
}
  • 1.
  • 2.
  • 3.
  • 4.
  • 5.
  • 6.
  • 7.
  • 8.
  • 9.
  • 10.
  • 11.
  • 12.
  • 13.
  • 14.
  • 15.
  • 16.
  • 17.
  • 18.
  • 19.
  • 20.
  • 21.
  • 22.
  • 23.
  • 24.
  • 25.
  • 26.
  • 27.
  • 28.
  • 29.
  • 30.
  • 31.

使用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;
    }
}
  • 1.
  • 2.
  • 3.
  • 4.
  • 5.
  • 6.
  • 7.
  • 8.
  • 9.
  • 10.
  • 11.
  • 12.
  • 13.
  • 14.
  • 15.
  • 16.
  • 17.
  • 18.
  • 19.
  • 20.
  • 21.
  • 22.
  • 23.
  • 24.
  • 25.
  • 26.
  • 27.
  • 28.

使用表驱动法实现。

char weekDay[] = {Monday,Tuesday,Wednesday,Thursday,Friday,Saturday,Sunday};
void fun(int day)
{
  printf("%s\n",weekDay[day]);
}
  • 1.
  • 2.
  • 3.
  • 4.
  • 5.

看完示例,可能“恍然大悟”,一拍大腿,原来表驱动法就是这么简单啊。是的,它的核心原理就是这个简单,如上面例子一样。

如果上面的例子还没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;
    }
}
  • 1.
  • 2.
  • 3.
  • 4.
  • 5.
  • 6.
  • 7.
  • 8.
  • 9.
  • 10.
  • 11.
  • 12.
  • 13.
  • 14.
  • 15.
  • 16.
  • 17.
  • 18.
  • 19.
  • 20.

表驱动法


for(; dwStrIdx < dwStrLen; dwStrIdx++)
{
    aDigitCharNum[szDigits[dwStrIdx] - '0']++;
}
  • 1.
  • 2.
  • 3.
  • 4.
  • 5.

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

在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);
    }
}
  • 1.
  • 2.
  • 3.
  • 4.
  • 5.
  • 6.
  • 7.
  • 8.
  • 9.
  • 10.
  • 11.
  • 12.
  • 13.
  • 14.
  • 15.
  • 16.
  • 17.
  • 18.
  • 19.
  • 20.
  • 21.
  • 22.
  • 23.
  • 24.
  • 25.
  • 26.
  • 27.
  • 28.
  • 29.
  • 30.
  • 31.
  • 32.
  • 33.
  • 34.
  • 35.
  • 36.
  • 37.
  • 38.
  • 39.

表驱动法

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);
    }
}
  • 1.
  • 2.
  • 3.
  • 4.
  • 5.
  • 6.
  • 7.
  • 8.
  • 9.
  • 10.
  • 11.
  • 12.
  • 13.
  • 14.
  • 15.
  • 16.
  • 17.
  • 18.
  • 19.
  • 20.
  • 21.
  • 22.
  • 23.
  • 24.
  • 25.
  • 26.
  • 27.
  • 28.
  • 29.
  • 30.
  • 31.
  • 32.
  • 33.
  • 34.
  • 35.
  • 36.
  • 37.
  • 38.
  • 39.
  • 40.
  • 41.
  • 42.
  • 43.
  • 44.

这样的代码结构紧凑,因为和结构体结合起来了,方便添加下一个LED灯到流水灯序列中,这其中涉及到函数指针,详细请看《回调函数》,只需要修改LedOpTable如下:

const static struct tagLEDFuncCB LedOpTable[] =
{
    {LED1_On, LED1_Off},
    {LED2_On, LED2_Off},
    {LED3_On, LED3_Off},
};
  • 1.
  • 2.
  • 3.
  • 4.
  • 5.
  • 6.

这年头谁还把流水灯搞的这么花里胡哨的啊,那么就举例在串口解析中的应用,之前的文章推送过《回调函数在命令解析中的应用》,下面只贴一下代码:


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);
            }
        }
    }
}
  • 1.
  • 2.
  • 3.
  • 4.
  • 5.
  • 6.
  • 7.
  • 8.
  • 9.
  • 10.
  • 11.
  • 12.
  • 13.
  • 14.
  • 15.
  • 16.
  • 17.
  • 18.
  • 19.
  • 20.
  • 21.
  • 22.
  • 23.
  • 24.
  • 25.
  • 26.
  • 27.
  • 28.
  • 29.
  • 30.
  • 31.
  • 32.
  • 33.

除上述例子,表驱动法在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;
  • 1.
  • 2.
  • 3.
  • 4.
  • 5.
  • 6.
  • 7.
  • 8.
  • 9.
  • 10.
  • 11.
  • 12.
  • 13.
  • 14.
  • 15.
  • 16.
  • 17.
  • 18.
  • 19.
  • 20.
  • 21.

函数映射表

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},
};
  • 1.
  • 2.
  • 3.
  • 4.
  • 5.
  • 6.
  • 7.
  • 8.
  • 9.
  • 10.
  • 11.
  • 12.

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

char current_stage=stage1;
char prev_stage=current_stage;
  • 1.
  • 2.

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

current_stage =stage_tab[current_stage].Up;
  • 1.

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

if(current_stage!=prev_stage)
{
  stage_tab[current_stage].current_operate();
  prev_stage=current_stage;
}
  • 1.
  • 2.
  • 3.
  • 4.
  • 5.

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

后记

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

责任编辑:武晓燕 来源: 知晓编程
相关推荐

2021-12-22 06:56:07

STM32C语言内存

2022-01-07 08:24:13

STM32枚举结构体

2021-08-26 06:29:47

STM32DWT数据观察点触发

2022-07-25 14:31:55

LiteOS开发工具

2021-08-31 08:01:40

STM32DSP指令

2021-05-20 07:26:22

DMASTM32数据

2021-05-06 08:54:39

串口DMASTM32

2021-02-05 08:41:44

STM32网络中断

2021-01-20 09:51:25

STM32网络接口

2021-03-06 21:21:11

STM32单片机追踪库

2021-07-21 11:25:17

机器学习?AI人工智能

2022-01-11 11:15:54

优化逻辑控制

2021-02-03 13:04:24

STM32网络控制器

2021-04-22 08:39:23

哈佛结构冯洛伊曼结构ARM架构

2021-06-26 07:50:20

STM32串口开发环形缓冲区

2018-10-15 10:15:30

STM32Linux经验

2020-01-10 22:51:30

硬件软件人生第一份工作

2020-12-03 06:32:21

STM32单片机通信

2021-11-15 09:53:16

STM32PSPMSP

2011-08-22 15:47:27

Oracle临时表存储过程
点赞
收藏

51CTO技术栈公众号