完善资料让更多小伙伴认识你,还能领取20积分哦, 立即完善>
电子发烧友论坛|
前言
今天我要分享一种单片机中分离URC数据的方法,和上文废话连篇不同的是,这次全篇干货哦! 文章涉及到的表驱动法,可以说是本次的精髓,这里不对表驱动法做过多解释,有兴趣的朋友可以自行查阅下相关资料,我这里只是用它实现个功能罢了,这个设计方法真的很香。 在表驱动法中还带有URC之间的粘包处理和命令回复与URC之间的粘包处理。 为什么要做这个呢?主要是因为,之前刚毕业时,买淘宝的板子,发现他们的代码都是写死的,就是说,发AT给模块,模块本身是回OK的,但是他们的实现方法是不等OK而是等某个URC到了之后,再进行下一步,说实话太捞了,效果如下: AT (主机发送) OK (命令返回) +CMD1: XX (URC1) +CMD2: XX (URC2) ... +CMDn: XX (URCn) 在发送AT之后,等待的不是OK,而是某个URCn,这样做在经过调试之后确实可以达到目标效果,但是将AT响应与URC混在一起,是我极不喜欢的。 将各部分分离之后,可以有效的管理模块的运行状态,比如在测试时,需要长期测试模块的稳定性,那我发完AT之后,得不到URCn,但是OK已经给我了,我当然就不可能认为,这个AT步骤是有问题的,而且,这样也很难定位到为题所在,中间的URCx在哪? URC 下面进入正题,让我们先看下,什么叫做URC? 咳咳,百度没找到对应的词条,那我来翻译下吧 URC: Unsolicited Result Code 可以翻译为未经请求的结果码,或者未请示的结果码。 那么这是啥意思呢,和发送AT返回OK的问答模式不同,URC是主机也就是单片机未向模块发送指令,但是模块主动向主机发送某些具有特定含义的指令的一个形式。 由于通讯模块每种协议的复杂度不同,厂商封装的AT指令各有差异,或在登录云平台时,模块下发URC告知主机当前运行到了哪一步骤,或网络状态发生改变时,模块主动下发指令通知主机,又或是平台下发数据,模块转发给主机等等等等。可以说除了AT指令问答交互,其余消息全部是通过URC来完成。 看到没有,这么多的URC消息,各个含义还都不同,让我如何是好呢?不着急,让我们一步一步的往下分析。 首先,要确定URC消息格式,一般的通讯模组URC格式如下: +CMD1: param1,param2rn +CMD2: param1,param2rn ... 没错,这样看起来还蛮简单的。但是我在程序里怎么处理这些数据呢,细心的朋友已经发现,URC消息都时候以 +XXX为起始的,那我找到这个 +XXX不就找到这条数据了吗?对了!!!找到这个头部就成功一半了,这个也是我今天要讲述的重点,表驱动法实现解析URC消息。 表驱动法 何谓表驱动法?表驱动法(Table-Driven Approach)简而言之就是用查表的方法获取数据。此处的“表”通常为数组,但可视为数据库的一种体现。(抄的) 其实我们都使用过表驱动法的实例。举一个大家都用过的东西,字典,根据字典的部首表检索字的拼音未知的汉字就是我们常见得表驱动法,即以某个字的字形为依据,计算出一个索引值,并映射到相应的页码。相比一页一页的翻看字典,部首检字法效率极高。 现实生活还有好多例子,例如超市货架分类、菜市场分区、包括键盘都有表驱动法的身影。 对应到编程方面时,在当数据不多的时候,可以用(if…else)或(switch…case)来完成,但是当数据量开始变大时,逻辑判断语句会越来越多,导致代码越来越繁杂,此时表驱动法的优势就显现出来了。 表驱动法本身有很多方式实现,但是这里由于博主经验有限,没专业看过相关书籍,数据结构及算法都是按自己常用的方案来做的,有些不对的地方还请各位多多指教。 实现方案 这里我以EC616系列模组连接CTWing为例,EC616在对接CTWing平台时,有几条必要的URC消息需要我们处理,分别如下 +CTM2MRECV: +CTM2M: +CTM2MCMD: 因为URC上述2、3两条URC消息参数过多,仅保留前面几个参数(参数啥的不是今天的重点)。 这时候根据上面的说法,我只要找到前面的几个URC头部就可以了,是的,这就是今天的任务。但是,在第二条消息体里 reg obsrv update ping dereg send lwstatus 也就是说,第二条URC实际上又可以分成7条不同的URC,虽然,我也可以把第二条消息分解成7份,只建一个表格,进行维护,但是为了形象的表示,主体URC只有3条,我又多建了一个表格,单独维护 +CTM2M: 的 分析完本次要解析的URC之后,下面就要开始对表格的数据结构进行规划,因为URC处理只需要查找对应的索引,所以,数据结构的建立也很简单,我这里以此方式建立结构体: typedef struct{ char *cmd; void (*CmdCallback)(void); }Type_CmdCallback; 数据结构建立的非常简单,一个关键字加上对应的回调函数即可。当然也可以将此数据结构再进行优化,例如: typedef struct{ char *cmd; void (*CmdCallback)(char *urcbuf, uint32_t urc_len, void *arg); }Type_CmdCallback; 若以第二种方式建表的话,不需要维护特意urc全局数组,在判断是谁索引后,直接将当前的urc数据传入回调函数中即可,并且预留一个空指针,以供用户使用。 为了简单说明,我这里以第一种方式建立维护整体URC的表格: Type_CmdCallback urc_cmd_table[] = { {"+CTM2MRECV:", ctm2mrecv_handle }, {"+CTM2M:", ctm2m_handle }, {"+CTM2MCMD:", ctm2mcmd_handle }, }; 至于 +CTM2M: 的分支表格,这里不作啰嗦,直接给出相应的表格: Type_Ctm2mCallback urc_ctm2m_table[] = { {"reg", ctm2m_reg_handle }, {"obsrv", ctm2m_obsrv_handle }, {"update", ctm2m_update_handle }, {"ping", ctm2m_ping_handle }, {"dereg", ctm2m_dereg_handle }, {"send", ctm2m_send_handle }, {"lwstatus", ctm2m_lwstatus_handle }, }; 在 +CTM2M: 的回调函数中,按照同样的方法判断urcbuf存在的分支索引,继续再进行数据处理就可以了。 根据上次我们学习的如何接收模组响应的一整包数据,在串口认为接收完成以后,开始进行判断,当前的响应是AT指令的期望返回还是错误代码,如果都不是,则认为此次是URC数据,就将urc_flag置位,并将当前数据包复制到urcbuf中去。在urc_loop函数中,不停的判断urc_flag是否被置位,若被置位就开始查找,表格中的cmd字段是否在urcbuf中,若在,则根据不同的索引,进入注册的回调函数。(说的有点乱) ~ps: strstr是查找b字符串在a字符串中第一次出现的位置.~ void urc_loop(void ) { uint8_t _cmd_index = 0 ; if(scp5_info.urc_flag == 1) { while(strstr((const char *)scp5_info.urc_rxbuf, urc_cmd_table[_cmd_index].cmd) == NULL) { _cmd_index++; if(_cmd_index >= 3) goto next; } urc_cmd_table[_cmd_index].CmdCallback(); scp5_urc_clear(); } next: ; } 以上就是在urcbuf中查找所注册过的urc头部的办法,当然,这个办法有局限性,比如在使用超时中断来判断接收完成时,可能会将两条消息当成一条消息,这样找到的只有在表格中注册的靠前的那一条urc,靠后的一条就无法找到了,这就是后面要提到的urc与urc之间的粘包拆包问题。 上述现象如下: +CTM2M: XXX +CTM2MCMD: XXX 若遇到此现象,则上述办法无法生效,只能检测到表格中的第一条消息,第二条消息无法感知,此时,则需要另寻办法对数据包再次解析,以确保,消息不会丢掉。 因为一般的粘包最多会在两条urc之间产生,所以暂时不考虑三个以上的场景,我的做法如下: void urc_loop(void ) { int _cmd_index = 0 ; if(scp5_info.urc_flag == 1) { while(strstr((const char *)scp5_info.urc_rxbuf, urc_cmd_table[_cmd_index].cmd) == NULL) { _cmd_index++; if(_cmd_index >= 3) goto next; } urc_cmd_table[_cmd_index].CmdCallback(); if(++_cmd_index > 3) goto clear; while(strstr((const char *)urc_buf, urc_cmd_table[_cmd_index].cmd) == NULL) { _cmd_index++; if(_cmd_index >= 3) goto clear; } urc_cmd_table[_cmd_index].CmdCallback(); clear: scp5_urc_clear(); } next: ; } 处理方法,很简单,只是做了重复的工作,但是当初实现时,却是在++_cmd_index上花费了不少功夫(大坑),不过,如果你使用的是前文中的 串口中断+rn 形式,则不会出现这个问题,这就是需要取舍的地方。 对于AT指令响应和URC之间的粘包问题,方法和此处类似,只是在判断完AT响应后,需要再次判断数据包中是否含有URC部分,再进行处理。这里不再详细说明。 从这里就可以看出,表驱动法的好处: 1、提高程序的可读性。一个消息如何处理,只要看一下驱动表就知道,非常明显。说是全篇干货,结果到最后又是废话连篇,希望以后在文章写得多的时候,会有些进步吧! |
|
|
|
|
只有小组成员才能发言,加入小组>>
2043 浏览 0 评论
imx6ull 和 lan8742 工作起来不正常, ping 老是丢包
4791 浏览 0 评论
4273 浏览 9 评论
3861 浏览 16 评论
4433 浏览 1 评论
4260浏览 3评论
3435浏览 0评论
1202浏览 0评论
2888浏览 0评论
3951浏览 0评论
/9
小黑屋| 手机版| Archiver| 电子发烧友 ( 湘ICP备2023018690号 )
GMT+8, 2025-12-13 18:04 , Processed in 0.891742 second(s), Total 44, Slave 34 queries .
Powered by 电子发烧友网
© 2015 bbs.elecfans.com
关注我们的微信
下载发烧友APP
电子发烧友观察
版权所有 © 湖南华秋数字科技有限公司
电子发烧友 (电路图) 湘公网安备 43011202000918 号 电信与信息服务业务经营许可证:合字B2-20210191

淘帖
1318