首页 理论教育 串口通信的堆栈实现的分析介绍

串口通信的堆栈实现的分析介绍

时间:2023-06-28 理论教育 版权反馈
【摘要】:在TinyOS 2.x串口协议栈分为4个功能组件,自底向上分别是:原始UART、编码器/装帧器、传输协议、分派器。UART的HIL组件为UartC,它为串口通信提供了字节级的接口SerialByteComm。SerialP会对收到的帧发出确认信息。其次,增加确认机制会增加串口通信栈的代码长度和复杂度。当然,任何对可靠性要求较高的应用程序都会将其任务计划基于串口协议栈。

串口通信的堆栈实现的分析介绍

在TinyOS 2.x串口协议栈分为4个功能组件,自底向上分别是:原始UART、编码器/装帧器、传输协议、分派器。

978-7-111-40722-5-Chapter07-41.jpg

图7-2 堆栈结构

堆栈结构如图7-2所示:

1.原始UART

协议栈的最下一层是原始UART组件,HIL层组件提供了配置UART(速率,停止位等)、收发字节、刷新UART缓冲区的功能。

UART的HIL组件为UartC,它为串口通信提供了字节级的接口SerialByteComm。SerialByteComm接口代码如下:

978-7-111-40722-5-Chapter07-42.jpg

另外,UartC提供接口以便在UART空闲时发出通知信号。有时我们需要知道发送到串口的数据是否已传输完毕(比如无线收发机需要从发送转换到接收状态时),这时我们就可以通过这个接口来进行判断。代码如下:

978-7-111-40722-5-Chapter07-43.jpg

但是如果MCU使用的是双缓冲UART通信机制,那么putDone事件只意味着它已经准备好接收下一字节,而不是UART空闲。

2.编码器/装帧器

编码器/装帧器组件建于原始UART层之上,将原始字节数据转换为串口协议数据包格式。编码/装帧器假定有两种类型的字节:分隔符和数据字节,分别用不同的事件向上层组件发出信号。

HdlcTranslaterC是串口协议栈中的编码和解码组件,它使用SerialByteComm接口,提供SerialFrameComm接口。SerialFrameComm接口代码如下:

978-7-111-40722-5-Chapter07-44.jpg

编码器/装帧器使用HDLC协议作为编码方式,0x7e作为帧分隔符,0x7d作为转义符。HdlcTranslaterC中维护了10位的状态码。在接收和发送通路上各使用了一位状态码用于表示是否使用了转义符,另外在传输路径上还有一个字节用于决定何时发送转义符。

当HdlcTranslaterC收到分隔符时,它设置receiveEscape位为true,当它接收到其他字节时,先检测receiveEscape位是否已设置。若是,则数据字节与0x20异或并清除receiveEscape位,通过触发dateReceived()完成数据字节接收。

HdlcTranslaterC在发送端的行为与接收端类似。当需要发送一个与分隔符或转义符相同的字节时,它设置transmitEscape标识位为true,同时存储数据字节与0x20异或的值,然后发送转义符。当转义符发送完成后,接着发送刚才存储的数据字节。

3.传输协议

协议组件负责读入并发送所有的协议控制包。如果协议组件开始接收数据包,则它会向分派器组件发送信号并附上接收到的数据字节作为参数。当数据包接收完毕时,协议组件通知分派器组件,数据包已接收完成,同时也告诉它CRC校验是否通过。

SerialP组件用类似于PPP/HDLC的帧结构实现了串口协议(具体参考RFC1662)。消息分派和缓冲区管理就留给了上层去处理,主机到节点的通信使用的停等的方式,而节点到主机用尽力而为(best effort)的传输方式。

SerialP提供了两个面向字节的接口用于收发数据包,分别是SendBytePacket和ReceiveBytePacket。

在发送方,SerialP负责封装上层的数据包。上层组件如SerialDispatcherC调用startSend()初始化包的发送并传递第一个要发送的字节。SerialP通过触发nextByte()来收集接下来要发送的数据。在nextByte运行期间或者在调用nextByte()的间隙,协议上层调用completeSend()来表明已经到数据包的结尾。如果completeSend是在nextByte()运行期间被调用的,SerialP会忽略调用nextByte()的返回值。SendBytePacket接口代码如下:

978-7-111-40722-5-Chapter07-45.jpg

SerialP用一个小的窗口来存放上层接收的字节以及还没有被发送到UART的字节。窗口的大小取决于UART的时间需求,同时通过反复的调用nextByte()来填充窗口。SerialP使用SerialFrameComm来发送帧间的分隔符、数据包字节和两字节的循环冗余校验(CRC)码。一帧数据发送结束并且接收到底层的最后一个putDone()事件后,SerialP调用sendCompleted()表明此帧发送是否成功。SerialP还负责数据包的接收工作,并且向高层提供ReceiveBytePacket接口。ReceiveBytePacket接口代码如下:

978-7-111-40722-5-Chapter07-46.jpg(www.xing528.com)

978-7-111-40722-5-Chapter07-47.jpg

一旦接收到帧间分隔符和一个新帧的帧头,SerialP向高层发出信息表明一个数据包已经到达。每收到一个字节,SerialP调用一次byteReceived()。当收到一个完整的帧时,SerialP调用endPacket并返回SUCCESS。反之,若在接收过程中失去同步,SerialP调用endPacket并返回FAIL。SerialP会对收到的帧发出确认信息。确认信息的优先级比数据传输高,因此,数据帧会被延迟发送。然而,确认信息存储在与数据缓冲区不同的队列中,因此当SerialP正在发送确认信息时,其他待发送的数据包进入后台运行。仅有PC到mote方向的通信才支持确认功能。SeriaP不需要获得PC的确认信息,原因有两个。首先,确认机制不是很可靠,在PC到节点的通信中使用确认机制是为了将可靠性提高到可用级别。在PC到节点方向的通信路径中,典型的UART接收缓存仅有一个字节大小,因此高负荷中断很容易丢失字节。PC的接收缓存则要大得多,并且不用处理溢出问题。其次,增加确认机制会增加串口通信栈的代码长度复杂度。这样的结果就是以消耗宝贵的代码空间来换取很少需要的功能。当然,任何对可靠性要求较高的应用程序都会将其任务计划基于串口协议栈。确认协议采用停等机制以最少占用节点的缓存资源。在PC到节点方向的通信过程中,利用停等机制节省缓存,在存储受限的设备上要比增加吞吐量更加重要,绝大多数的应用程序仅偶尔进行传输控制的时候才使用后者。

4.分派器

分派器组件处理数据包字节与分隔符。它负责将数据读入message_t并告知上层组件数据包已接收完毕。Dispatcher组件支持多种包格式,基于message_t的工作方式需要知道数据包的包头大小以计算出数据的偏移。

SerialDispatcherC负责处理协议组件接收的数据包。它使用SendBytePacket和ReceiveBytePacket接口,提供带参数的Send和Receive接口。Send和Receive接口的参数(uart_id_t)决定了message_t中数据包的格式。SerialDispatcherC在通过SerialP发送和接收的数据包中放置了一字节的包头和包格式标识符。通过使用带参数的SerialPacketInfo接口,SerialDispatcherC能够处理各种数据包格式。SerialPacketInfo接口代码如下:

978-7-111-40722-5-Chapter07-48.jpg

SerialDispatcherC收到由SerialP发出的第一个字节后,先将其存储为数据包类型,同时调用offset()以在message_t中确定偏移量。然后将数据字节导入并填充到message_t的缓冲区中。发送过程与此类似,SerialDispatcherC先发送类型字节并导出数据字节,数据字节的起始位置由调用offest()函数得到的索引值确定。SerialDispatcherC使用了两个长度命令:dataLinkLength()和upperLength()以在两种数据包类型中进行转换。向上,长度为不含数据包头部的有效载荷长度;向下,长度则为包含了数据包头的长度。在使用了串口端口参数:uart_id_t U提供通信服务的组件中,必须在组件实现中通过uart_id_t U将SerialPacketInfo连接到SerialDispatcherC。目前,仅有平台独立的活动消息(TOS_SERIAL_ACTIVE_MESSAGE_ID,在3.5节中有详细描述)、802.15.4的活动消息(TOS_SERIAL_802_15_4_ID)、mica2平台的CC1000数据包(TOS_SERIAL_CC1000_ID)和错误代码TOS_SERIAL_UNKNOWN_ID是保留标识符,新的数据包格式绝对不可以使用任何保留标识符。

5.SerialActiveMessageC

SerialActiveMessgaeC是平台独立的活动消息层组件,工作于串口通信栈顶端的配置文件,它将SerialActiveMessageP连接到带uart_id_t参数的SerialDispatcherC,同时将SerialPackerInfoActiveMessgaP连接到带参数uart_id_t TOS_SERIAL_ACTIVE_MESSAGE_ID的SerialDispatcherC。

978-7-111-40722-5-Chapter07-49.jpg

SerialActiveMessageP是一个通用组件,因此可用于各种数据包级的通信层顶端。它不以目的地址或组号过滤数据包,而是假设从串口端口接收的数据包目的地址就是本节点。这样PC端的工具就不用再寻找和考虑节点的ID和组号了。平台无关的活动消息没有CRC(通常假设其串口栈的CRC是足够的),其头部格式如下:

978-7-111-40722-5-Chapter07-50.jpg

978-7-111-40722-5-Chapter07-51.jpg

6.数据包格式

TinyOS 2.x的串口栈里,数据包在连接过程中有如下的格式。各协议字段分别和特定的组件对应,如图7-3所示。

978-7-111-40722-5-Chapter07-52.jpg

图7-3 数据包格式示意图

F=帧字节:HdlcTranslateC

P=协议字节:SerialP

S=序列字节:SerialP

D=数据包格式分派字节:SerialDispatcherC

Payload=有效负载:SerialDispatcherC

CR=两字节的CRC校验:SerialP

F=帧字节:HdlcTranslateC

Payload是由组件SerialDispatcherC读入的连续数据包。需要注意:payload中任何值为0x7e和0x7d的字节都将被相应转化为0x7d 0x5e或者0x7d 0x5d。例如,一个目标地址为0xbeef,类型为6,组号为0x7d,长度为5的平台独立活动数据包为:7e 400900 be ef 05 7d 5d 06 01 02 03 04 05 7e注意:组号0x7d被转化为0x7d 0x5d。协议字段(P)值为0x40(64),与SERIAL_PROTO_ACK一致(SERIAL_PROTO_ACK在Serial.h文件中有说明)。

免责声明:以上内容源自网络,版权归原作者所有,如有侵犯您的原创版权请告知,我们将尽快删除相关内容。

我要反馈