接着以前的文章(UART学习笔记一),继续聊聊这个话题。上次的文章分析了如果去
接收数据,这章分析下如果去check一帧数据。如果想要check一帧数据,就要考虑两种情况:
1)数据帧接收完毕,意思就是说buffer里面有一帧完整的数据;
2)数据帧暂时没有接收完毕,比如:帧头、校验、帧尾,目前只收到了帧头;
上面两种情况和底层驱动实现方式直接相关:
1)Timer_out方式;
2)边接收边解析,队列缓存方式;
首先来说第一种情况:
底层驱动利用TIMER_OUT方式,这个是时候,check_function(...)可以认为已经收到一帧
“完整”数据。然后就去按着常规的方式去解析:
1)找帧头,如果成功则执行步骤2,否则丢弃;
2)找帧尾,如果成功则执行步骤三,否则丢弃;
3)检查校验,如果成功则通知协议解析,否则丢弃;
如果成功,丢给协议解析函数,否则直接丢弃,并清空缓存;
这种方式很简单,基本没什么难度,按着帧格式校验就可以。
再来看看另外一种:
底层驱动负责入队操作,check_function负责出队操作,同时负责检查,其编写逻辑就不能
按着上面的步骤直接操作了。要利用状态机的思维,首先定义出几个状态,然后根据状态去
做相应的事情。
enum{
FSM_START = 0,
FSM_WAIT_HEAD,
FSM_WAIT_TAIL,
FSM_WAIT_USER_READ
};
FSM_START:做一些初始化工作,必须保留。
FSM_WAIT_HEAD:查找帧头,其逻辑如下:
如果接收数据 < 帧头数据个数,把数据保存到缓存,直接返回;
如果没有找到帧头,则保留缓存区最后的帧头数据个数-1个数据,其它丢弃,返回;
如果找到帧头,则直接丢弃帧头前面数据,同时把帧头以后数据复制到缓存头开始处
(这是为了处理方便,当然也可以用环形的处理方式),状态机跳到FSM_WAIT_TAIL。
FSM_WAIT_TAIL:等待帧尾,其逻辑如下:
如果缓存数据<计算出的帧长总个数 保存数据,直接返回;
检验帧尾,如果错误,则丢弃缓存第一个字节,goto到FSM_WAIT_HEAD;
校验帧校验位,如果错误,则丢弃缓存第一个字节,goto到FSM_WAIT_HEAD;
跳转到FSM_WAIT_USER_READ:什么都不干,不能出队,也不能对缓存进行操作,
就是直接返回 true;(为了做同步)
用户检查到true,就会调用read_data(。。。)把检查到一帧数据,拷贝到用户缓存区,
同时自动复位状态机;
注意点:
1、丢弃缓存第一个字节,goto到FSM_WAIT_HEAD,是为了防止由于上帧数据接收到
一部分(后面的数据丢失),引起下帧数据错误问题;
2、最好有个接收超时,然后整帧校验,复位状态机;
3、用goto是为了更好的解决逻辑问题,使逻辑更清晰;
有时间再去说说如果去做协议分析。以上观点都是本人愚见,还请大神们不吝指教。