表驱动法在串行通讯协议解析中的应用

在《单片机高级裸编程思想》和《代码大全2》中,均提到了数据驱动程序的编程思想,认为数据是宇宙的语言。虽然我们可以通过代码逻辑实现任务需求,但容易出现代码分支众多,程序难以理解和维护的困境,但如果我们选择了正确的数据结构并进行有条理的整理,并秉持数据是易变的而逻辑是稳定的认识,将数据和实现分离,算法则不言自明。数据比程序更易驾驭。尽可能把设计的复杂度从代码转移至数据是个好的实践方式。
所谓表驱动法,也称C语言映射表(有点函数中X和Y的取值映射的味道),简而言之就是通过查表的方式得到数据对应的实现。此处“表”通常的表现形式是数组,也认为是数据库的一种实现方式。
查表的理念在NTC热敏电阻的测温和CRC校验算法中可见一斑。NTC热敏电阻通常原厂会给出一张温度-阻值对应表,程序的一般实现方式:ADC测量电压àNTC电阻à查表获取温度值。CRC校验算法通常不会每次采用对数据采用逐位异或,反复迭代计算CRC,而是采用查表计算得到CRC的方式求解CRC值(当然这里涉及到的空间换时间的理念就不展开了)。举个简单的例子,如下。
考试根据考分计算成绩等级。低于60,计等级E;60~70,计等级D;70~80,计等级C;80~90,计等级B;90~100,计等级A。一般思路关键代码可实现如下。
很显然,按照上述的编程思路,如果分数段划分得足够细,增加更多的分数等级,每增加一个等级,就要增加一个流程分支。但查表的思路,则代码逻辑简洁,关键代码如下。
很显然,上述代码整体观感上简洁很多。同样的问题,如果细分更多等级,仅仅只需要修改score_map这个数组本身和数组长度。相比较而言,第二种查表方法,只需要修改数据,不需要对算法的逻辑进行增删操作,当然上述伪代码的数组长度是需要宏定义的。显然上述的思路优势很明显:1.可读性更强,消息流程处理一目了然;2.易于维护,如果要增加新的等级,只需要修改数据,不需要修改流程;3.良好的重用性,将相同的逻辑提取出来,而把容易发生改变的部分提取到外部。
查表的方式分为直接查找、索引查找和二分查找等。直接查找即通过数组下标直接获取数据,可以看出这种方式就是哈希表的直接访问法。很显然,表驱动法是适用于无需有序遍历数据,且数据量大小可提前预知的情况。索引查找的基本思想是:首先查找索引表,可用二分查找或顺序查找,然后在确定的块中进行顺序查找。二分查找则是通过确定数据所处的范围确定分类,但是需要注意边界,将每一分段范围的上限值都考虑在内。
表驱动在CAN通讯协议中的应用
背景:CAN总线上挂载若干设备,其中一个设备人为指定为主控,其余设备人为指定为从控。制定如下通讯协议:在其扩展帧ID中约定了一些位,定义了地址、功能码、指令码等功能。那么如何解析主机可能收到的数据帧呢?常规思路可能有如下伪代码。
从上述代码中可以看到,对于这种需求,随着总线上挂载设备的增多,核心解码函数需要不断增加case分支。以上代码缺点很多:1.可读性不高,解析一条CAN指令的处理部分代码需要跳转3层代码,随着分支的增加也给阅读带来一定的障碍;2.过多的swicth分支,其实这也是一种重复代码,都有共同特性,可以进一步提炼;3.程序缺少核心主干,主干被淹没在大量的代码逻辑之中。针对以上问题,用表驱动法来实现如下。根据上述功能要求,定义3个枚举类型:设备地址、功能类型和指令类型等,定义如下函数跳转列表(伪代码)。
这种方法好处很明显:1.提高了程序的可读性,消息如何处理,查阅驱动表即可,非常明显;2.减少了重复代码,将switch分支进行了抽象,将公共的要素进行抽象形成了一个函数外加一个可扩展的驱动表;3.可扩展性,驱动表中的函数指针就是一个接口,当需要更改功能时,只需要对驱动表进行增删处理,隔离了变化;4.程序拥有了明显的主干;5.降低了程序复杂度,将程序逻辑的复杂度转移到了数据中来,达到控制复杂度的目的。
表驱动在ASCII串口通讯协议中的应用
背景:在某一些通信协议中,可能存在使用纯ASCII作为通信协议的情形。比如MODBUS ASCII以':’开始,以OD和OA作为结束符;常用语DTU或者NB-IOT模块中的AT指令集以“AT+xxx”作为指令等都是较为常见的ASCII指令。这里以若干条指令作为例子,简述如下。解析如下指令:信号强度指令CSQ:17,0; IP地址读取指令STAIP:192.168.0.1;开机指令SON,以上指令均以0D和0A作为结束符。按照常规的思路代码如下。
以上指令借用常规的MODBUS ASCII协议,为了简便取消以':’开头,很显然实际项目中消息还会不断增加。此处只列举了3条指令作为示意,单屏已无法查看全部代码,阅读起来十分不便。再者,如果不断增加消息类型,几乎很难阅读这段“意大利面条代码”了。那么按照数据驱动编程的思路(表驱动法),可以改造成如下代码。
显然这样的表现形式是比较容易阅读和维护的。上述2个例子利用表驱法在一般的思路上进行优化,使程序逻辑更清晰。总结一下,数据驱动编程背后的实现思想包括:
  1. 1.     控制复杂度,通过把程序逻辑的复杂度转移到数据中,达到了控制复杂度的目的。
  2. 2.     隔离变化,把容易变化的消息和不容易变化的逻辑进行隔离。
  3. 3.     机制和策略的分离,机制就是消息的处理逻辑,策略就是不同的消息处理。这一点可以参考《Unix编程艺术》的第一章,对此做了探讨。
当然,表驱法的应用还可在GUI菜单逻辑交互、传感器驱动流程、设备参数设置和修改(多国语言实现)等等得到应用。最后,由于本人太懒(当然水平也有限),可能伪代码逻辑不够严谨,代码主用于表述表驱法的实现思想。下一期内容:浅述DES和AES加密。
(0)

相关推荐