文章首发于同名微信公众号:DigCore

欢迎关注同名微信公众号:DigCore,及时获取最新技术博文。

原文链接:https://mp.weixin.qq.com/s/amO7rGkqFJtCzuyjyIVwDw

串口实现了两个终端设备之间进行可靠的通信,串口在这中间完成了传输层的作用。本次要讲的是关于数据的协议。

  • 类似场景


洞幺!洞幺!我是洞拐!收到请回答!收到请回答!over!

在战争题材影视剧中经常能够看到这样的对白,在通过对讲机等相关无线设备呼叫队友时,先呼叫对方名称,然后告知自己身份,说完内容最后再说over,表示一次呼叫结束。

是的,没错,这就是本节要讲的在串口通信中发挥重要作用的起止式协议!

UART的时序本身就是起止式协议,具体可参考《嵌入式硬件通信接口协议-UART(一)协议基础》这一篇内容的介绍。

事实上串口实现了数据通信过程中的传输层,而应用层就系统功能的业务逻辑,应用层控制需要收发的各种数据内容。

数据解析的前提是通信双方都是用统一的数据帧格式,因此在这里将设计一个简单的起止式的数据帧格式,保证设备之间进行可靠的通信。

现在的很多无线模块,为了使用简单和易于集成,模块对外使用UART接口,并采用AT指令来完成配置和使用,常见的有ESP8266的WiFi模块、HC-05蓝牙串口模块。

AT指令的特点是易于人机交互,使用者对其发AT指令时,都是用ASCII字符发送,对于模块的处理,也是以字符来处理。这样的AT指令,它的起止式特点是以“AT”两个字符开头,并以回车换行“\r\n”字符结束。

HC-05蓝牙模块指令示例

但是项目工程中,数据在嵌入式设备是以HEX数据(16进制)运算和处理,如果参考AT指令去设计帧结构,那么在收发处理时候,必然要将收到的纯数据(16进制)按照字符处理。

比如一个终端设备,其功能就是环境检测,可能包含温湿度、光照强度、二氧化碳浓度、PM2.5浓度等等,如果要发出一个温度采集结果24℃数据,采集设备将数据24分成2个字节发送,因为ASCII字符’2’对应的16进制是0x32、ASCII字符’4’对应的16进制是0x34,这样的一个温度数据就需要2个字节来发送。接收端接收到的是0x32、0x34后,再以查表方式逆向换算出原温度数据’24’,这个过程就是采用字符处理的麻烦之一。

因此不考虑使用ASCII字符来组帧结构。

  • 精简起止式结构


最简单的帧,就是有开头+结尾做起止标志。

比如0x55 + 数据包 + 0xAA

在一长串的数据流中,接收端逐字节接收,并判断是否存在0x55,如果存在则开始存入数据包缓冲器,直到接收了0xAA数据,认为完成一帧数据的接收。

这个方法确实相当简单,不用太多的处理,只需要判断开头和结尾即可。

而这样存在很大的问题,如果传输的内容也有0xAA这样的数据,这个0xAA并非结尾标志,而程序接收过程就提前结束,这样就不能保证完整接收一帧数据包了。

  • 增加长度限制


在精简起止式结构基础上,增加一数据来标志数据包长度。

比如0x55 + 长度 + 数据包 + 0xAA

这样一来,接收端判断接收到了0x55的开头标志,紧接着再接收一个“长度”的字节,基于这个长度来继续接收后续剩余的数据。

可见如果有了长度的约束,那么最后都不需要0xAA作为结尾标志了。

这样的接口,即使有开头、长度、结尾,还存在风险。比如传输数据时,物理线路受到未知干扰,导致数据内容出现了异常,那么接收端即使完整接收所有数量的数据下来,也是错误的内容。

  • 增加校验检查


解决在发送过程中出现的未知错误问题,必然需要对数据进行校验。再增加一字段来标志数据内容的校验计算结果。

比如0x55 + 长度 + 校验值 + 数据内容 + 0xAA

校验值是对数据包采用算法计算而得,接收方完整收下所有数量的数据,再对数据包采用同样的算法计算出校验值,从而对比校验值来确定数据包的准确性。

对于校验值的运算,采用CRC-16运算的方式,检错能力强,开销小。

  • 设计协议帧结构


综上所述,基于起止式的帧结构可以设计成:0x55 + 长度 + CRC校验 + 数据包

在这里,帧头标志采用0x55一个字节。

0x55二进制是01010101,这样在UART物理线路上输出的信号将会是占空比50%的方波,方波是最容易进行测量和诊断的,在实际波形观测时可以确定稳定性、噪声毛刺等。

要说0xAA(二进制10101010)也是可以,但是UART发送时候是有一个起始位0,并且是以LSB方式先发送bit0的最低位,0xAA的bit0已经是0,而0x55的bit0是1,因此想得到方波当然优先考虑用0x55。

长度采用一个字节表示,则后续的CRC校验 + 数据包的总数量最多能放255个字节。

CRC校验采用CRC-16算法,占2个字节,此时后续的数据包最多能放253个字节。

终上所述,得出最终的起止式帧结构:

接下来开始设计处理程序。

根据帧结构,可以定义如下的结构体:

typedef struct{

    uint8_t head;

    uint8_t len;

    uint8_t crc16L;

    uint8_t crc16H;

    uint8_t packet[253];

}sst_frame_t;

其中要特别说明的:

packet数据包最大长度设为253,是因为len是uint8_t类型,len最大255,而CRC校验值占了2个字节,因此packet数据包最多可253个字节。

CRC校验值采用的是CRC-16标准,校验值是个uint16_t类型的数据,传输时采用的是LSB模式,因此将CRC校验值设为两个uint8_t类型的数据,这样做便于在源码移植过程中,不同平台的大小端差异能够得到正确处理。

简述嵌入式设备内存大小端差异在结构体定义以及使用时存在的问题:

假如对帧结构定义了如下的结构体:

typedef struct{

    uint8_t head;

    uint8_t len;

    uint16_t  crc16;

    uint8_t packet[253];

}sst_frame_t;

计算后得到某一次的校验值结果是 0xDC66,这是一个uint16_t类型的数据,如果直接使用这个结构体来处理数据发送,那么:

在LSB的小端模式平台下,数据的发送顺序是

head、len、0x66、0xDC、packet[0]、packet[1]、...

反之在MSB大端模式的平台里,数据的发送顺序是

head、len、0xDC、0x66、packet[0]、packet[1]、...

因此采用2个字节uint8_t数据类型代替uint16_t来定义结构体中的CRC校验值,使得在跨平台收发数据时无需做差异化处理。

  • 构建帧结构


使用起止式进行数据传输时,把应用层的数据包进行组帧,这样可构造一个完整的数据帧,便于在应用层将完整的一帧数据传递给传输层发出。

这里的构造过程,事实上是对帧结构的“填充”过程。

首先是计算数据包的CRC校验值,随后就是“填充”的过程。

为了防止应用层调用接口时,传进来的数据包的地址、组帧结果的首地址指向同一个内存地址,所以在组帧前需要将源数据内容单独缓存,再进行“填充”的操作。

  • 解析帧结构


解析帧结构其实就是对一长串的数据流进行解析处理,从而提取出数据包。

这里被解析的数据来源是一个循环缓冲区,对循环缓冲区内的可读数据进行解析。因此需要使用循环缓冲区配合。

代码截图:

解析思路是:

1.确保环形缓冲区有足够一个帧结构的数据量,否则返数据量不足的错误;

2.接着读出一个字节判断帧头标志是否为0x55,否则返帧头错误;

3.再次读一个字节作为帧长度数据,且长度至少3个字节(2个CRC校验值+至少1字节数据包),否则返帧长度错误;

4.读出帧长度数据,如果此时环形缓冲区的可读数量比长度数值小,出现这情况的原因可能是帧长度字段在发送期间出现异常,或是对端设备串口传输慢而未完整传输一帧,此时可做适当的延时等待,如果超时退出,且返帧长度错误;

5.继续读出2个字节作为CRC校验值,且需要注意先收到的是crc16L,先收到小端数值;

6.紧接着把数据包读出,此时读的长度应该是第4步中的帧长度数据少2个字节;

7.最后对数据包计算一个CRC校验值,对比接收到的校验值,校验值不一致则返错误校验码。

函数返回值符合以下枚举的错误码:

  • 被解析数据源


看到这里也许仍有疑问,用于解析的数据源哪来?数据什么时候被写进环形缓冲区内?

参考上一篇《嵌入式硬件通信接口-使用RingBuffer处理数据(二)详细设计过程》介绍的关于向环形缓冲区写入一个字节,但dclib_ringbuffer这个模块属于应用库模块层,而如果直接把dclib_rb_writebyte这一个接口放在串口接收中断里执行,这就破坏了系统的架构层次,对工程代码的维护和移植是个麻烦事,因此采用回调函数的方式。

嵌入式开发工程师都知道,一般在使用官方的库时,经常会遇到需要自己实现一些回调函数,从而利用注册接口把回调函数传递给库或者驱动层,使库或者驱动层在执行时调用该回调函数。

根据这个思路,同样的这里也采用回调函数的形式,回调函数内完成了把串口接收到的数据写入环形缓冲区内。

回调函数的实现源码截图:

事实上仅仅调用了dclib_ringbuffer功能里的写一字节接口dclib_rb_writebyte,回调函数传进来的参数dat就是串口接收到的数据。

有了回调函数,还要把这个回调函数的地址传给底层驱动,这也就是常说的“注册”的过程,注册接口在固件板级接口层里串口模块dcbsp_uart实现,注册接口时dclib_uart_callback_reg函数:

又偏题了,关于回调函数在此不做深入论述。

简而言之,环形缓冲区写入一字节的执行过程,放在回调函数里,当串口接收中断触发后,中断里会根据注册的回调函数地址,进而执行回调函数,实现对环形缓冲区写入一个字节数据。如此操作的理由是不改变工程代码的分层架构,并且便于维护与移植!

为了缩减篇幅,最后贴上测试代码的部分:

最后也附上调试期间串口打印的解析结果:

起止式帧结构的讲解稍有匆忙,篇幅也略大,文中基础技术要点未能细致讲解,后续统筹规划再做单独介绍!

接下来在此帧结构基础上,讲述如何设计在数据包放置应用层的交互指令,敬请期待下回分!


《ASCII》@百度百科

https://baike.baidu.com/item/ASCII

《CRC》@百度百科

https://baike.baidu.com/item/CRC/1453359

《CRC开源项目》@github

https://github.com/lammertb/libcrc

长按二维码关注DigCore


关注公众号,发消息:

下载&StartStopType

获取本文PDF原文源代码下载链接

嵌入式硬件通信接口协议-UART(四)设计起止式的应用层协议相关推荐

  1. 《图解TCP/IP》学习日志----四、TCP与UDP、应用层协议、WWW(万维网)

    UDP UDP不提供复杂的控制机制,利用IP提供面向无连接的通信服务.并且它是将应用程序发来的数据在收到的那一刻,立即按照 原样发送到网络上的一种机制.即使是出现网络堵塞的情况下,UDP也无法进行流量 ...

  2. 常用的嵌入式硬件通信接口协议(UART、IIC、SPI、RS-232、RS-485、RS-422、CAN、USB、IRDA)(一)

    一.概述 通信协议是两个(或多个)设备之间进行通信,必须要遵循的一种协议.通讯协议分为物理层和协议层. 物理层:规定通讯系统中具有机械.电子功能部分的特性,确保原始数据在物理媒体的传输: 协议层:规定 ...

  3. 常用的嵌入式硬件通信接口协议(UART、IIC、SPI、RS-232、RS-485、RS-422、CAN、USB、IRDA)(二)

    2.IIC (1)基本概念 IIC总线是一种串行.半双工总线,同步通信.主要用于近距离.低速的芯片之间的通信. IIC串行总线一般有两根信号线,一根是双向的数据线SDA收发数据,另一根是时钟线SCL双 ...

  4. 基于tcp的应用层协议还原

    基于tcp的应用层协议还原技术是网络安全每个领域都需要的一种基础技术. 首先,我们需要认识到tcp协议的两个特征: tcp是一种流协议.发送者以字节流的形式传递给接收者. tcp协议本身没有固有的&q ...

  5. Day4:应用层——网络应用层内容概述、网络应用基本原理(体系结构、进程通信、套接字socket、应用层协议)、Web应用与HTTP

    加油!寒假偷博人 一.网络应用层内容概述. 1本篇内容:(原理.实例.编程) 网络应用体系结构 客户机/服务器  P2P  混合结构 网络应用的服务需求 可靠性 带宽 时延 Inte ...

  6. 读书笔记之应用层协议

    网络之间通过把报文发送进套接字使网络进程间实现相互通信.但是如何构造这些报文?在这些报文中的各个字段的含义是什么?进程什么时候发送这些报文?这些问题将带我们进应用层协议的范围.应用层协议定义了运行在不 ...

  7. 计算机网络应用层协议分析总结

    1.应用层协议原理 1.1.网络应用程序体系结构 C/S结构,有一个总是打开的主机称为服务器,它服务于来自许多其他称为客户机的主机请求.客户机主机既可能有时打开,也可能总是打开.C/S结构之下,客户机 ...

  8. 【回眸】牛客网刷刷刷!嵌入式软件中也会遇到的嵌入式硬件,通讯,通讯协议专题(一)

    前言 最近继续刷题,看看嵌入式软件还需要了解一些嵌入式硬件中的通讯协议和常用接口协议 比如说SPI CAN I2C 通讯协议专题 1.波特率 波特率= 每秒传送的字符数 * 字符位数.串口的工作模式为 ...

  9. 嵌入式硬件设计-常见处理器介绍

    一.嵌入式处理器 嵌入式处理器是嵌入式系统的核心,是控制.辅助系统运行的硬件单元.范围极其广阔,从最初的4位处理器,仍在大规模应用的8位单片机,到最新的受到广泛青睐的32位,64位嵌入式CPU. 世界 ...

最新文章

  1. oracle相关操作
  2. 任务的定义、任务切换的原理及实现
  3. boost库 python_Boost.Python简介-阿里云开发者社区
  4. 利用OpenCvSharp处理图片并在winformd的pictureBox中显示
  5. 用一句话证明你是程序员,你会怎么说
  6. C++11 std::function, std::bind, std::ref, std::cref
  7. 虚拟化系列-VMware vSphere 5.1 虚拟机管理
  8. mysql collate
  9. Extjs4.2或以上 使用自定义事件时报错问题
  10. RocketMq中MessageQueue的分配
  11. Sklearn提供的常用数据集
  12. Qt多功能计算器(一)——基本功能
  13. 模式匹配与正则表达式(一)
  14. Ajax清晰请求步骤与代码
  15. Java 执行 JS 脚本工具
  16. office2016安装记录
  17. 完全立方和差和立方和差公式
  18. 图片识别——差异哈希算法
  19. Selenium+Python3爬取微博我发出的评论信息
  20. 超碰网站源码搭建教程超碰在线视频搭建超碰网站搭建教程

热门文章

  1. 计算机B类会议排名,计算机学科会议排名
  2. matplotlib漫画风格(XKCD)
  3. lambda中FirstOrDefault和First
  4. 2019的百度网盘下载速度太慢老是限速怎么解决?
  5. 目标检测中的BBox 回归损失函数-L2,smooth L1,IoU,GIoU,DIoU,CIoU,Focal-EIoU,Alpha-IoU,SIoU
  6. Python学习(二)列表,for循环,切片,元组
  7. 【操作系统】BIOS篇
  8. 史上最长的介绍BI(商业智能)的干货
  9. 即时通讯 视频会议开源技术选择
  10. Mysql数据库学习笔记[完结]