表驱动法在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;
函数映射表
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中表驱动法有很多很多用处,本文的例子已经过多了,如果在通勤路上用手机看到这里,已经很难了。关于UI操作,大神figght在github开源了zBitsView仓库,单片机实现屏幕界面,多层菜单。很牛,很优秀的代码,有兴趣的同学可以学习一下。https://github.com/figght/zBitsView
4、后记
这篇文章我也看到网上一遍表驱动法的后总结的笔记,可能也有很多同学和我一样,在自己的项目中熟练应用了这种“技巧”,但今天才知道名字:表驱动法。
这篇文章多数都是代码示例,实在因为表驱动法大家应该都熟练应用了,这篇文章算是总结一下吧。
学习知识,可以像在学校从概念一点点学习,也可以在工作中慢慢积累,然后总结记录,回归最初的概念,丰富自己的知识框架。
祝大家变得更强!
点击查看:C语言进阶专辑
表驱动法在STM32中的应用的更多相关文章
- 大话设计模式C++版——表驱动法改造简单工厂
上回<大话设计模式C++版——简单工厂模式>中指出了简单工厂模式的缺陷,即违背了开发—封闭原则,其主要原因是由于switch的判断结构的使用,使修改或添加新的对象时需要改动简单工厂类的代码 ...
- 黑盒测试用例设计方法&理论结合实际 -> 判定表驱动法
一. 概念 判定表是分析和表达多逻辑条件下执行不同操作的情况的工具. 二. 判定表驱动法的应用 判定表的优点: a. 能够将复杂的问题按照各种可能的情况全部列举出来,简明并避免遗漏.因此,利用判定表能 ...
- C语言表驱动法编程实践
数据压倒一切.如果选择了正确的数据结构并把一切组织的井井有条,正确的算法就不言自明.编程的核心是数据结构,而不是算法. ——Rob Pike 说明 本文基于这样的认识:数据是易变的,逻辑是稳定的. ...
- (1)消灭初级程序员常用的多层if-else嵌套--------------【表驱动法】
表驱动法 1.相信很多刚从事工作的程序员或一些初级程序员在写代码的时候会出现对一些逻辑判断写成多层if-else嵌套的经历,这种方式在一些简单的层次中运用起来确实可行,但对于一些大型项目逻辑判断比较多 ...
- sqlserver 插入数据时异常,仅当使用了列列表并且 IDENTITY_INSERT 为 ON 时,才能为表'XXXXX.dbo.XXXXXXXXX'中的标识列指定显式值。
INSERT INTO XXXXXXXXX.dbo.XXXXXXXXX select * from XXXXXXXXX 仅当使用了列列表并且 IDENTITY_INSERT 为 ON 时,才能为表'X ...
- ora-01445:无法从不带保留关键字的表的连接视图中选择ROWID或采样
系统要创建一个物化试图,用到很多张表,执行的时候报错: ora-01445:无法从不带保留关键字的表的连接视图中选择ROWID或采样 网上搜了下,有多种原因和解决方法,最终我选择先尝试一下修改 ...
- C++的表驱动法
目的:使用表驱动法,替换复杂的if/else和switch/case语句. 说明:JS 等其他语言也都支持的. 表驱动发示例:http://blog.csdn.net/zhouyulu/article ...
- ora-01445 无法从不带保留关键字的表的联接视图中选择 ROWID 或采样
ora-01445无法从不带保留关键字的表的联接视图中选择 ROWID 或采样 从网上找了很多资料,许多都是没结贴的,说什么的都有,排查了一下sql 发现各个段的left join都没有错误. 有一个 ...
- oracle 表导入到powerDesigner 中
最近不忙,之前一直是用powerDesigner看表结构,还没自己导入过,今天试试 oracle 表导入到powerDesigner 中步骤: 1.File--->reverse Enginne ...
随机推荐
- Windows批量修改文件
如图我是建立了壁纸文件夹 Windows自带的排序方式 如何不用自带的呢? 在这个文件夹里面建一个.txt文件 如下 ok第二步骤 将UTF-8格式改为ANSI格式 点击文件-另存为ANSI格式-替换 ...
- Maven中使用ssm框架出现:org.apache.tomcat.util.modeler.BaseModelMBean.invoke 调用方法[manageApp]时发生异常
org.apache.tomcat.util.modeler.BaseModelMBean.invoke 调用方法[manageApp]时发生异常 首先可以排查一下像: @RequestMapping ...
- 典型C内存空间分布图
下图是一个典型的C内存空间分布图 这是Linux下32位环境的用户空间内存分布情况 内核空间 :一部分核心软件独立于普通应用程序,运行在较高的特权级别上,驻留在被保护的内存空间上,拥有访问硬件设备的所 ...
- 自定义View4-塔防小游戏第一篇:一个防御塔+多个野怪(简易版)*
塔防小游戏 第一篇:一个防御塔+多个野怪(简易版) 1.canvas画防御塔,妖怪大道,妖怪行走路线 2.防御塔攻击范围是按照妖怪与防御塔中心距离计算的,大于防御塔半径则不攻击,小于则攻击 ...
- KingbaseES V8R6 ksql 关闭自动提交
背景 用过oracle或mysql的人都知道,做一个dml语句,如果发现做错了,还可以rollback,但在Kingbase ksql 中,如果执行一个dml,没有先运行begin; 的话,一执行完就 ...
- android 逆向 smali手写helloworld
编写Hello.smali文件 .class public LHelloWorld; .super Ljava/lang/Object; .method public static main([Lja ...
- 【Vue学习笔记】—— vue的基础语法 { }
学习笔记 作者:oMing vue v-on: 简称 @ <div id='app'> <button v-on:click='Show1'> </button> ...
- 第六章:Django 综合篇 - 14:Django 日志
Django使用Python内置的logging模块实现它自己的日志系统. 如果你没有使用过logging模块,请参考Python教程中的相关章节. 直达链接<logging模块详解>. ...
- Docker和containerd在容器日志及相关参数配置方面的一些差异
- 第六章:Django 综合篇 - 2:核心配置项
Django的默认配置文件中,包含上百条配置项目,其中很多是我们'一辈子'都不碰到或者不需要单独配置的,这些项目在需要的时候再去查手册. 强调:配置的默认值不是在settings.py文件中!不要以为 ...