您的位置:首页 > 其它

duBand源码分析-底层通讯部分

2015-08-03 16:08 330 查看

一、编写说明

duBand源码中的通讯部分源码较多,但细细阅读发现其分层结构清晰,很值得借鉴。为了深入对duBand通讯源码的学习,并理解通讯分层设计的思想,决定对duBand源码的通讯部分进行分析,本文档用于记录分析过程。

二、源码分析

1. 层次说明

通讯部分主要分为三个层,这三个层主要的功能划分如下:

L0层:负责从硬件电路上读、写数据。移植时主要重写该层函数。

L1层:负责对协议包的接收,保证包的完整性、正确性,并返回相应的应答信号。

L2层:负责对协议包的解析,进行相应处理并返回数据。



2. 接收数据

1) 数据/资源说明

a) 包、帧

协议包为一组完成的协议数据,里面包含起始码、命令码、数据码和校验码等。由于蓝牙传输一次最多传输20个字节,因此对于一些大于20字节的协议包会分成多帧发送。如一个45字节的协议包,会为成两个20的帧和一个5字节的帧,分3次发送。

b) 接收状态-receive_state

接收状态有三个,分别为WAIT_START、WAIT_MESSAGE与MESSAGE_RESOLVE。WAIT_START为等待接收协议包的起始帧,几前20个字节(或小于20个字节的协议包的全部字节)。接收到起始包后,接收状态变成WAIT_MESSAGE,表示等接收当前协议包剩下的帧。MESSAGE_RESOLVE,表示正在 等待对方回答ACK/NACK信号。

c) 接收超时定时器-receive_time_out_timer

超时定时器的作用是检测同一包内的多帧是否接收完成。在WAIT_MESSAGE和ACK/NACK的状态下,需要启动该定时器,等待目录数据。超时定时器超时时,会将receive_state设置为WAIT_START,重新等待接收新的协议包。

2) L0层

duBand为蓝牙通讯的设备,因为L0层实现上用是蓝牙数据的传输。见源码ble_nus_service_init()中,通过nus_init.data_handler = L1_receive_data将L1的接收函数设置为蓝牙接收的回调函数。因此nordic的蓝牙串口服务已经将L0层的接收函数做好了。但是其有一个重要的特性,就是nordic的BLE每次传输时,最大只能传输20个字节,即20个字节为一个协议包中的小帧。如果需要将协议移植到非蓝牙传输的系统中,这个地方需要做一个中间层函数转换。

3) L1层

L1层的接收函数为L1_receive_data(),输入参数为接收到的数据缓存和接收的数据长度。在蓝牙串口接收到数据时,会调用此函数。

i. 先判断是否接收等待定时器是否有启动,如果定时器启动,则停止其,表示在定时到达时间内接收到了数据。



ii. 接着根据receive_state进行相应的处理。

iii. 状态为WAIT_START时,先判断首字节是否为L1_HEADER_MAGIC(协议包起始码),如果不是,则丢弃该数据帧。如果是,则获取其协议包总长度字段(注意,因为蓝牙数据是20字节一个包的,所以第一个数据帧肯定有包含长度字段。但对于掉数据的情况下,可能有起始码,但没剩下的数据,这样可能会导致有BUG出现),计算是否有接收剩余的数据帧。如有剩余帧,则设置状态为WAIT_MESSAGE,等待接收剩余帧。如无剩余帧,则判断是否为应答包,是的话则进行等待应答信号的回调处理,并将receive_state设置为WAIT_START。如果为非应答包,则将receive_state设置为MESSAGE_RESOLVE(这样做感觉没什么意义,因为在后面的L2层数据解析的return前,会将receive_state重新设置为WAIT_START,CRC校验错误的处理也会重新设置为WAIT_START),接着进行CRC检验,并且根据校验结果返回ACK或NACK,并进行L2层数据解析。

iv. 状态为WAIT_MESSAGE时,先判断实际接收的字节数是否等于协议包总字节数,如果未接收完成,则继续接收,并启动超时接收定时器。如果接收完成,则再次判断当前接收的数据包(注意,是数据包,而不是数据帧)是否为应答包,是的话则进行等待应答信号的回调处理,并将receive_state设置为WAIT_START。如果为非应答包,则将receive_state设置为MESSAGE_RESOLVE,接着进行CRC检验,并且根据校验结果返回ACK或NACK,并进行L2层数据解析。此部分操作与在WAIT_START一样。

v. 当状态为MESSAGE_RESOLVE时,则表示目标接收的协议包应该为应答包,接收到数据后进行相应的回调处理。这个地方好像永远都不会进入的,因为在后面的L2层数据解析的最后,会将receive_state重新设置为WAIT_START,CRC校验错误的处理也会重新设置为WAIT_START。



3. 发送数据

1) 数据/资源说明

a) 包、帧

协议包为一组完成的协议数据,里面包含起始码、命令码、数据码和校验码等。由于蓝牙传输一次最多传输20个字节,因此对于一些大于20字节的协议包会分成多帧发送。如一个45字节的协议包,会为成两个20的帧和一个5字节的帧,分3次发送。

b) 首帧说明

Nordic蓝牙芯片的蓝牙数据发送也是一样,一次最多只能发送20个字节。duBand的发送协议传输再在此基础进行进一步的拆分,即先发送L1 Header(协议包的前8个字节),根据协议文档,这8个字节包括起始码、标志码、长度字段(2字节)、CRC(2字节)和协议序列号(2字节)。

c) 首帧缓冲-global_L1_header_buffer[]

此数据组中存储着协议包的首帧8个字节的数据,每一个数据包的传输先传输这个帧。也可以理解为该缓冲用于存放协议L1层的数据。

d) 数据缓冲-global_reponse_buffer[]

首帧之后的协议指令、数据存储的缓冲。这里存储着命令码、KEY码、KEY数据长度及数据,即首包后的剩下的数据。也可以理解为该缓冲用于存放协议L2层的数据。

e) g_ack_package_buffer

用于存储当前发送的ACK协议包数据。

f) g_next_L1_send_content_buffer

指向下一个待发送的缓冲数组。

g) static L1_Send_Content sendContent[MAX_SEND_TASK]

该结构为发送数据缓冲,目前MAX_SEND_TASK的大小定义为1。该结构里面包括isUsed元素,用于指示该缓冲是否被使用。

h) 发送等待定时器

每一个数据包发送完成后,都需要等待一个ACK响应。通过register_wait_response()函数设置响应的ACK数据(主要是序列号),如果在定时器时间超时时都未收到正确的响应,则定时器delay_send_wait_timer调用相关函数重发。

i) 发送成功回调

通过set_complete_callback()设置蓝牙L0层数据发送成功的回调(有点类似于发送完成中断),主要是处理有数据等待发送时的队列。

j) current_task_type

记录当前发送程序的状态,分别为TASK_NONE-空闲状态,TASK_DATA-发送数据包状态和TASK_ACK-发送应答包状态。

k) L1层发送数据内容-L1_Send_Content

l) L2层发送数据内容- L2_Send_Content

2) L0层

L0层的发送调用ble_nus_send_string()进行发送,此函数主要的两个参数为,发送缓存指令与发送的数据长度。进行移植时,主要实现这个函数的即可。

3) L1层

a) L1_send()应用

以下以return_sys_time()操作为例,分析L1_send()的应用,并通过实例去理解发送数据的流程。如下图所示为return_sys_time()函数的实体。



程序先设置数据缓冲-global_reponse_buffer[]的值,将返回系统时间的指令、KEY、长度和当前时间值放入该缓冲。也可以当作为L2层的协议数据。

接着设置L2_Send_Content,并以其作地参数,调用L1_send()。

b) uint32_t L1_send(L2_Send_Content * content)

发送数据主要由于L1_send()开始。如下图所示,先判断是否有可用的缓冲,如没有,则直接返回。有可用缓冲,则设置err_code为0,表示无错误,然后继续运行。



如有缓冲可用,则先设置global_L1_header_buffer[]数组,此数组发协议包的首帧数组,根据协议文档,这8个字节包括起始码、标志码、长度字段(2字节)、CRC(2字节)和协议序列号(2字节)。



设置接下来的帧的数据指针,将发送的数据内容传到下级函数调用。

最后调用schedule_async_send()函数,进行发送同步。



c) void schedule_async_send(void * contenxt,SEND_TASK_TYPE_T task)

函数内部先判断发送的类型是普通数据或ACK,通过输入参数2判断。所以,该函数也主要是用来判断是发送普通协议包还是发送ACK/NACK回应包。

d) async_send()

schedule_async_send()再调用此函数进行进一步的数据处理,并在这里调用L0层的发送函数ble_nus_send_string()。

如下图所示,程序先判断当前发送程序的状态,如为空闲状态,则直接赋予新的状态(输入参数2的值);如当前程序状态为发送ACK,新的状态为发送DATA,则将要发送的内容赋值给g_next_L1_send_content_buffer指针,并将next_task_type设置为发送数据,下次进行调度发送;如上当前发送状态为DATA,新的状态为ACK,则,直接设置next_task_type为ACK,下次进行调度发送。这样就会出现一种情况,则如果当前为发送DATA,新的发送状态也为DATA时,程序是不会返回。



如当前发送状态为空闲状态,则马上进行发送。程序先根据current_task_type的类型进行DATA、ACK的发送流程。

先分析发送ACK的流程,如下图所示,(1)先调用construct_response_package()函数组装ACK协议包数据。接着调用L0层发送函数ble_nus_send_string()进行数据发送。(2)发送成功后,则清空占用的ACK数据,并判断下一类型发送数据是否为DATA,如果是,则接着发送DATA(在不退出函数的情况下,通过goto直接跳转),否则直接退出。(3)调用L0发送后,如果返回值为没有空闲的BUFFER(BLE_ERROR_NO_TX_BUFFERS),则设置发送完成中断的回调函数(set_complete_callback()),使蓝牙发送完成后再进入schedule_async_send()函数处理。(4)如果调用L0发送函数后,返回其它类型的错误值,则直接丢弃该次传输的数据,复位所有占用的资源后退出。这里有一个问题,就是if()的判断是多余的,因为在前面已经设置了next_task_type为TASK_NONE,因此永远不会进入if里面运行。



接下来,分别发送数据的流程,如下图所示。(1)先判断是否要发送首帧,如需要,则设置发送内容为首帧,并发送。(2)判断是否有数据未发送完成(因为BLE一次只能发20个字节,因此可能出现一条指令的L2层被分成多个帧发送,(通过content ->contentLeft不为0进行判断),如有,则设置发送的内容为剩下的数据。(3)如果content ->contentLeft为0,则说明没有数据需要发送了,设置sendContentType为CONTENT_NONE。(4)退出循环,并退出函数。如果有数据发送,则调用ble_nus_send_string()发送数据。



函数接着运行下去(L0发送调用后),判断ble_nus_send_string()的返回值error_code,并进行相应的处理。如返回值错误,则与上面发送ACK时的处理一样,(1)如果返回值为没有空闲的BUFFER(BLE_ERROR_NO_TX_BUFFERS),则设置发送完成中断的回调函数(set_complete_callback()),使蓝牙发送完成后再进入schedule_async_send()函数处理。如果调用L0发送函数后,(2)返回其它类型的错误值,则直接丢弃该次传输的数据,复位所有占用的资源后退出。唯一的不同,发送数据时还会判断next_task_type是否为ACK,如果为ACK,则还要goto到ACK发送的LABEL上。如下图所示。



ble_nus_send_string()返回值正确的话则根据sendContentType的值进行相应处理,(1)如果该值为CONTENT_NONE,则不做任何处理;(2)如果该值为CONTENT_HEADER时,则清空L1_header_need_schedule的数据(根据代码的结构,此处也是永远不会进入的)。(3)如果该值为CONTENT_DATA,则判断是否还有数据需要发送(通过content ->contentLeft不为0进行判断),(4)没有数据需要进行发送,则清空所占用的资源(清空发送完成回调函数,设置current_task_type为TASK_NONE),并通过register_wait_response()设置等待的应答包信息(主要是协议包序列号),在接收处理中,调用response_package_handle()会处理返回的ACK。(5)接着启动重发定时器(在没收到ACK回应时,定时重发)。(6)最后判断是否有next_task_type是否为TASK_ACK,如果是并且g_ack_package_buffer中有数据,则goto到ACK发送的LABEL上。(7)如果sendContentType的值为CONTENT_ACK并且g_ack_package_buffer有数据,则清空ACK发送占用的资源。



e) 重发定时器服务函数-delay_send_func()

(1)程序先判断发送的内容是否存在,如不存在,则直接return。(2)如有数据存在 ,判断协议包是否完成发送(通过判断content->contentLeft是否为0),(3)先判断重试的次数是否超出范围,(4)没有超出,则调用L1_resend_package()重发。(5)如重试次数超出范围,则清空占用资源,并设置NULL的发送完成回调。(6)如果设置了发送回调函数,则还需要调用回调函数,告诉发送者发送失败。



f) 协议包重发-L1_resend_package()

由于协议包重新发送时,其协议序列号会改变,因此这里重新填充一次首帧,并调用schedule_async_send()发送数据。



内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: