|

关注公众号,回复“入门资料”获取单片机入门到高级开挂教程
开发板带你入门,我们带你飞
文 | 无际(微信:2777492857)
全文约3287字,阅读大约需要 10 分钟
雷猴啊~我是无际,一个即将秃顶的技术朋友。
刚做开发那几年,每次领导或客户提新需求,我都心慌慌,表面点头,心里已经在算要重写多少代码了。
那会没有程序架构的概念,写程序都是if-else无限套娃,隔一段时间再回去看,想改个地方得先花大量时间理清头绪,边改代码边骂项目经理。
后面像捡垃圾一样,大佬代码捡一点,网上捡一点,慢慢才有了系统的程序架构思维,写程序也有框架了。
今天就来介绍一个我经常用的架构技巧:表驱动法。
有幸之前看过一个大佬的代码,可惜我去的时候,他已经离职了,我接受了他的项目进行维护,看到了大量表驱动的用法,比如矩阵按键,矩阵LED控制。
我第一次看到这代码,都震惊了,啥玩意?这么复杂?
这玩意儿听起来像是高大上的学术名词,但其实就是个“查表解决问题”的骚操作,能让你的代码从一团乱麻变成整齐划一的艺术品。
别慌,我保证用最接地气的语言带你搞懂这东西,全篇3000字以上,咬碎嚼烂喂你嘴里,保管你看完能直接上手,写代码时嘴角都不自觉上扬!
1.你是不是也烦透了乱糟糟的代码?
先说个场景,咱们假设你在搞一个单片机项目,比如花式点灯。
需求很简单:按按钮A,灯亮;按按钮B,灯灭;按按钮C,灯闪烁。
你脑子一热,立马甩出一堆if-else或者switch-case,代码可能是这样的:
if (button == 'A') { light_on();} else if (button == 'B') { light_off();} else if (button == 'C') { light_blink();} else { handle_error();}看着挺顺眼,对吧?逻辑清晰,小项目里用用也没啥毛病。但现实的需求总是变态脑洞大开的,项目经理冷不丁跑过来说:“加个按钮D,灯要渐变亮!”你咋整?硬着头皮在else if后面再塞一行?
然后改完还得测半天,生怕手抖改错了把整个逻辑搞崩。更别提需求再变几次,代码里全是条件判断,维护起来简直是边骂项目经理,边想砸键盘。
所以,表驱动法,其实就能解决这些问题。
2.表驱动法:从“码农”到“码神”的捷径
啥是表驱动法?别被名字吓尿,说白了就是把那些乱七八糟的逻辑判断塞进一张表里,程序跑的时候直接查表办事,就跟查字典一样。
单片机里用这招,能把复杂的代码变得简单到飞起,想加功能?改表就行,核心代码稳如泰山不动。这不比你手动改一堆if-else爽多了?
这招的精髓在于:把容易变的东西(比如逻辑、数据)抽出来,扔到表里隔离起来,程序就只管查表干活。既优雅又实用,简直是单片机开发里的降维打击。
3.啥时候使用表驱动法?
表驱动法不是万能钥匙,但有些场景用它那是真的香。咱们列几个单片机开发里的常见情况,你看看是不是似曾相识:
?命令解析:比如串口收到不同指令,要执行不同操作,像“ON”开灯,“OFF”关灯。
?状态机:设备在“待机”“运行”“故障”之间跳来跳去,每次状态切换还得干点啥。
?事件处理:根据不同事件调用不同函数,比如按键触发、传感器报警。
?配置管理:一堆参数需要统一管理,查起来方便。
这些场景有个共同点:逻辑多、容易变、改起来烦。表驱动法就像个“收纳大师”,把这些乱七八糟的东西整整齐齐塞进表里,代码瞬间清爽,维护起来也跟喝水一样简单。
4.表驱动法怎么玩?
表驱动法其实就三步:
1.搭个表:根据需求弄个数组或者结构体数组,把逻辑和数据塞进去。
2.查表:程序跑的时候,根据输入或者状态去表里找对应的条目。
3.干活:找到后按表里的指示执行操作,完事。
听起来是不是有点“so easy”?别光点头,咱们直接上例子,无际单片机从不整虚的,直接带你实操落地。
实战1:命令解析
还记得前面那个灯控系统吗?咱们用表驱动法给它整一波优化。先看看传统的路子:
void process_button(char button) { switch (button) { case 'A': light_on(); break; case 'B': light_off(); break; case 'C': light_blink(); break; default: handle_error(); break; }}
这代码小项目里凑合,但一加新功能就暴露短板。咱们换表驱动法试试:
第一步:建表
定义一个结构体数组,把按钮和对应的操作塞进去,用函数指针来把逼装起来:
// 定义函数指针类型typedef void (*ButtonHandler)(void);// 表结构typedef struct { char button; // 按钮 ButtonHandler handler; // 操作函数} ButtonCommand;// 命令表ButtonCommand commands[] = { {'A', light_on}, // 按钮A:开灯 {'B', light_off}, // 按钮B:关灯 {'C', light_blink}, // 按钮C:闪烁 // 加新功能?在这加一行就行!};
第二步:查表+执行
写个函数,收到按钮信号就去表里找,然后执行:
void process_button(char button) { int size = sizeof(commands) / sizeof(ButtonCommand); for (int i = 0; i if (commands.button == button) { commands.handler(); // 找到就干活 return; } } handle_error(); // 没找到就报错}
这招有啥好?
?扩展无压力:加个按钮D渐变亮?表里加一行{'D', light_fade},完事!
?代码不乱套:核心逻辑不动,改需求只改表,维护起来跟玩儿似的。
?一目了然:所有命令都在表里列着,比一堆switch-case直观多了。
怎么样,是不是有种“代码瞬间高级了”的感觉?
实战2:状态机也能这么丝滑
单片机里状态机用得老多了,比如设备有“待机”“运行”“错误”三种状态,根据事件跳来跳去。传统写法是这样的:
switch (current_state) { case IDLE: if (event == START) { start_running(); current_state = RUNNING; } break; case RUNNING: if (event == STOP) { stop_running(); current_state = IDLE; } else if (event == ERROR) { handle_error(); current_state = ERROR; } break; // 后面还有一堆}
这代码看着就头晕,改起来更头大,尤其是状态和事件一多,嵌套得让人想哭。咱们用表驱动法整一波:
第一步:建表
定义状态和事件的枚举,再搞个状态转移表:
// 状态和事件typedef enum { IDLE, RUNNING, ERROR } State;typedef enum { START, STOP, ERROR_EVENT } Event;// 表结构typedef struct { State current; // 当前状态 Event event; // 触发事件 State next; // 下一状态 void (*action)(void); // 执行的操作} Transition;// 状态转移表Transition transitions[] = { {IDLE, START, RUNNING, start_running}, {RUNNING, STOP, IDLE, stop_running}, {RUNNING, ERROR_EVENT, ERROR, handle_error}, // 加新状态?在这加一行};
第二步:查表+执行
写个函数,根据当前状态和事件查表,更新状态并干活:
void process_event(Event event) { int size = sizeof(transitions) / sizeof(Transition); for (int i = 0; i { if (transitions.current == current_state && transitions.event == event) { current_state = transitions.next; if (transitions.action) transitions.action(); return; } } invalid_transition(); // 没找到就报个错}
这招有啥妙处?
?加状态超轻松:新状态新事件?表里加一行,逻辑自动生效。
?规则全在表里:一打开代码,所有状态转移一清二楚。
?告别嵌套地狱:再也不用盯着层层switch找bug了。
这状态机一用表驱动法,丝滑得像刚抹了润滑油,调试起来都心情舒畅。
以上几种我们无际单片机项目用的也非常多,比如自己任务调度、LED特效、按键检测、菜单、几种防盗报警模式等等。
yy4d41wrffl6402385117.png
nthueywmfsm6402385217.png
所以项目不是关键,关键是实现的水平,在什么高度。
把这些实现细节讲给面试官听,内行人能听得出来你是有水平的,对于入行来说,完全够了,哪怕做了嵌入式工程师4-5年,都不见得听过表驱动法,更别说用了。
5.用表驱动法的小心机
表驱动法这么好用,但也不是没坑,下面的坑不要踩:
?表得全:漏了个情况,程序就懵了,查表查不到可不会自己猜。
?性能得掂量:单片机那点资源,像51单片机项目就没必要用了,表太大了查找慢,必要时可以用二分查找或者哈希优化。
?调试别偷懒:表里的逻辑不像if-else那么直白,多写点注释,不然回头自己都看不懂。
我第一次看到那些大佬写的代码,没注释,也边看边骂,写这么复杂干吊?还是if-else看起来轻松。
不过这些小毛病比起它的优点,简直不值一提。只要用得顺手,表驱动法绝对是你代码里的得力助手。
6. 总结下
说到底,程序架构这东西,它不仅是一种编程技术,更是一种设计思维,同一个项目,不同段位的人去做,稳定性各方面都不同,就取决于这种思维和技术的经验积累。
而表驱动法只是其中之一,在单片机开发中,无论是命令解析、状态机设计,还是配置管理,表驱动法都能帮你写出更优雅的代码
接下来,我强烈建议你亲自下场,感受“改表不改码”的快感。
下次领导再改需求,你先苦巴巴说,这个功能不好做呀,可能要2周。然后暗地里笑笑眯眯地改张表,几分钟搞定,其它时间,当然是用来摸鱼啦,以前我就经常干这种事。
end
yfmr0gzrz0h6402385317.jpg
下面是更多无际原创的个人成长经历、行业经验、技术干货。
1.电子工程师是怎样的成长之路?10年5000字总结
2.如何快速看懂别人的代码和思维
3.单片机开发项目全局变量太多怎么管理?
4.C语言开发单片机为什么大多数都采用全局变量的形式?
5.单片机怎么实现模块化编程?实用程度让人发指!
6.c语言回调函数的使用及实际作用详解
7.手把手教你c语言队列实现代码,通俗易懂超详细!
8.c语言指针用法详解,通俗易懂超详细! |
|