一、编写说明

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()发送数据。

【NRF51822】百度手环开源源码分析--底层通讯部分相关推荐

  1. 【NRF51822】百度手环开源源码分析--存储部分

    一.编写说明 对duBand源码的存储部分进行分析,本文档用于记录分析过程. 二.源码分析 1. 存储区域划分 在使用nRF Studio进入程序下载时,可以看出,存储区域分为三个Region,如下图 ...

  2. 【NRF51822】百度手环开源源码分析--框架部分

    1.Main函数(main.c) a) gpio_init(); //初始化全部IO口为初始状态(输入.关上拉.关复用) b) pre_init(); //初始化时钟与RTC0并进行待机,待RTC0中 ...

  3. 【NRF51822】百度手环开源源码分析--数据自动同步部分

    1.  void send_all_data(boolis_from_cb) 发送数据都是通过send_all_data()去上传的. 1)  static boolneed_send_sync_pr ...

  4. Android开源源码推荐(一)

    qianqianlianmeng Android开源源码推荐(一) 1.Android-ViewPagerIndicator http://www.akaifa.com/code/86/android ...

  5. 2022全球20多款知名的Android刷机ROM镜像和Android系统开源源码(覆盖全球机型)

    推荐阅读 ​Android10系统定制|frida逆向分析实战课程 2022全球20多款知名的Android刷机ROM镜像和Android系统开源源码(覆盖全球机型) 因此,您拥有一台Android设 ...

  6. 很火的仿soul交友盲盒1.0全开源源码

    简介: 目前很火的仿soul交友盲盒1.0全开源源码,2000块钱购买的一套仿soul盲盒交友开源源码 网盘下载地址: http://kekewl.org/UUqVYnPqS0t0 图片:

  7. 云蹦迪云广场舞软件开源源码

    云蹦迪广场舞 意:第2,3步只在第一次运行时需要配置 一定要按照顺序执行,执行顺序有错会报错 开播 在抖音找到自己的直播间,复制直播间号(绿色区域是直播间号)(只在第一次运行时需要) 在解压后的deb ...

  8. PHP抽奖小程序/微信红包封面抽奖小程序/抽奖小程序开源源码

    PHP抽奖小程序源码,微信红包封面抽奖小程序,全开源.带流量主(需要自行替换代码),独立后台,亲测完美可用. 下载 PHP抽奖小程序/微信红包封面抽奖小程序/抽奖小程序开源源码 亲测截图:

  9. Telegram Android开源源码运行

    TG-Android开源源码运行 源码地址 https://github.com/DrKLO/Telegram 环境介绍 Android Studio版本至少3.4,或者直接去官网下载最新的 手机最低 ...

最新文章

  1. java 注册回调_java 实现回调代码实例
  2. e.getMessage() e.printStackTrace() 和e.printStackTrace() 小结
  3. ABAP面试题系列:写一组会出现死锁(Deadlock)的ABAP程序
  4. 葡萄城报表V11 SP2新版本震撼发布!
  5. 对图片进行压缩,水印,伸缩变换,透明处理,格式转换操作
  6. 提升网页加载速度—预加载VS预读取
  7. php选择版本,怎样选择PHP的版本
  8. 对话框的数据交换--MFC深入浅出
  9. torch中permute()函数用法
  10. ZT: 排名前50个常用软件下载(带序列号)
  11. 知识付费的多重属性与本质特征
  12. 基于ueditor 扩展的电子病历编辑器
  13. 前端学习笔记之品优购项目(一) 3.15
  14. 使用plf更新Mandriva系统
  15. 纯JS写一个用苹果序列号查询生产信息的小工具
  16. Cesium加载影像图
  17. 搭建阿里云物联网平台实现MQTT通信
  18. Spring源码研读
  19. 我的世界android制作教程,我的世界手机版红石教程 十进二编码器制作方法攻略...
  20. 阿里短信服务 JAVA

热门文章

  1. 全国3000多名医护人员感染新冠,医疗机器人与智能技术提供解决方案
  2. 数据结构之手斯红黑树
  3. PlusFo小道消息独家报道,与黑子的争霸谁能更胜一筹
  4. 机器人3D模型可视化建模线上三维虚拟现实展示
  5. 哪个邮箱群发效果好?邮件可以群发吗?群发邮件技巧教程来了
  6. 由己方的a网站跳转第三方的b网站
  7. 德语键盘在标准ASCII键盘上使用的小窍门/ 德语键盘英语输入法输出波浪线~
  8. 第一节课的Python基础知识
  9. Python办公自动化学习笔记--Word操作
  10. vertical-align 各个属性值的含义