对于蓝牙协议栈的理解,最好的办法是找一个最简单的开源协议栈进行学习,BTStack整个协议栈都是C语言编写,非常适合刚入门的同学来学习借鉴。这篇文章为框架代码阅读第二部分,韦东山学习蓝牙的笔记。
可以从5个方面来理解BTStack的框架:
1.硬件操作:hci_transport_t
BTStack支持多种接口的蓝牙模块,比如USB口、3线串口、5线串口。
对于这些接口,会抽象出对应的hci_transport_t结构体。
该结构体成员如下:
里面有init、open、send_packet等重要成员。
对于3线串口、5线串口,它们在init、open设备时,不需要写两套代码。因为它们都是串口设备,只不过前者不使用硬件流控、后者使用硬件流控。
对串口硬件的操作,再抽象出一个btstack_uart_block_t结构体:
2. 操作系统相关代码:btstack_run_loop
BTStack支持多种操作系统,比如Windows、Linux、IOS。
不同的操作系统,操作设备的函数不一样,比如:
① Windows使用WaitForMultipleObjects函数来等待数据
② Linux使用select函数来等待数据
BTStack针对不同的运行环境,抽象出了对应的btstack_run_loop结构体,共同成员为:
比如其中的execute成员很重要,它是一个循环,在循环中等待、读取、处理函数。
3. 循环体中,怎么读取、处理数据
BTStack可以同时支持多个蓝牙设备,每一个蓝牙设备被open时都会返回一个文件句柄,Windows/Linux就是通过监视这些文件句柄来等待数据的。
显然,循环体从句柄A得到了数据,应该调用句标A对应的处理函数:句柄和处理函数是绑定的。
所以,BTStack里抽象出了btstack_data_source_t结构体,里面含有句柄、处理函数:
打开硬件设备时,
1)要设置对应的btstack_data_source_t结构体:句柄、处理函数
2) 并调用btstack_run_loop_add_data_source把这个结构体告诉btstack_run_loop,
3) btstack_run_loop的execute循环中,就监视该句标,获得数据后调用对应的处理函数
对于各种btstack_data_source_t结构体,都有自己的处理函数:
1)对于UART
UART有两种:
3线串口(其数据传输协议被称为H5),
5线串口(其数据传输协议被称为H4)。
使用H4、H5协议时它的处理函数都是btstack_uart_windows_process_read:
这个函数会调用block_received()来处理,block_received是函数指针。
对于H4协议,它指向:hci_transport_h4_block_read;
对于H5协议,它指向:hci_transport_h5_block_received:
所以:
对于H4协议,uart data source的process函数最终是hci_transport_h4_block_read;
对于H5协议,uart data source的process函数最终是hci_transport_h5_block_read。
2)对于USB
每一个endpoint都有对应的btstack_data_source_t结构体,也都有对应的process函数,比如:
可以看到,对于不同的接口,即对于H2、H4、H5,都分别有自己的处理函数。
各个句柄对应的处理函数,是该句柄数据的处理起点,它将会调用上面各层提供的处理函数。这些处理函数,最终都会通过以下调用,把数据上报给各层:
packet_handler(packet_type, packet, size); // h2,h4,h5中的packet_handler指针
这个packet_handler是hci.c提供的。hci.c文件中hci_init函数它通过以下调用,把hci.c的packet_handler函数传给hci_transport_t结构体,用来设置H2、H4或H5文件中的packet_handler指针:
4. 上面各层如何处理数据
hci.c提供的packet_handler函数,根据packet_type的不同,分别处理。
有3类packet_type:event、acl data、soc data。
btstack_data_source_t结构体中的process函数是数据处理的起点,该“起点”会调用hci.c中的packet_handler函数。
那么hci.c中的packet_handler可以认为是数据处理的分发站。
HCI层可以处理这些数据时,就由HCI层来处理,处理不了就上报。
L2CAP、SM、GATT、GAP、APP等等,都可以提供处理函数。
1)对于EVENT数据:
hci.c中的event_handler函数用来处理这些数据,
在HCI层能处理的数据将被用来设置hci_stack结构体;
有必要上报的数据,通过hci_emit_event函数上报。
hci_emit_event函数会调用hci_stack->event_handlers链表中的各个callback函数。该链表中的函数,来自上面各层,它们通过hci_add_event_handler注册这些callback函数。
调用hci_add_event_handler的文件有:
le_data_channel_client.c属于APP,即APP可以处理感兴趣的数据;
main.c中注册的函数没什么用处,就是在蓝牙模块初始化完毕后,打印一个提示信息:“BTstack up and running at …”;
gatt_client.c这个文件,在le_data_channel_client.c这个APP中没被使用;
att_Server.c这个文件是用于ATT Server的,在le_data_channel_client.c这个APP中没被使用;
btstack_crypto.c、sm.c都是用于安全管理;
l2cap.c,这很重要,ATT、GATT、GAP、SM各层都要使用L2CAP来收、发数据。
2)对于ACL数据:
hci.c中的acl_handler函数用来处理这些数据,
在HCI能处理的数据将被用来设置对应的hci_connection_t结构体,
有必要上报的数据,通过hci_emit_acl_packet函数上报。
hci_emit_acl_packet函数会调用hci_stack->acl_packet_handler来处理。
在l2cap.c中,通过如下调用设置hci_stack->acl_packet_handler:
hci_register_acl_packet_handler(&l2cap_acl_handler);
即:上报的ACL数据,将由L2CAP的l2cap_acl_handler函数来处理。
3)对于SCO数据:
hci.c中的sco_handler函数用来处理这些数据,
在HCI能处理的数据将被用来设置对应的hci_connection_t结构体,
有必要上报的数据,通过hci_stack->sco_packet_handler函数上报。
APP如果要处理SCO数据,可以通过hci_register_sco_packet_handler提供自己的处理函数。hci_register_sco_packet_handler就是真接设置hci_stack->sco_packet_handler。
5. 谁触发了数据的传输?
之前我们说过主循环里在等待数据,只有我们给蓝牙模块发送命令之后,它才会回送数据。
那么,在哪里给蓝牙模块发送了第1个命令?在APP代码中,必有如下语句:
// turn on!
hci_power_control(HCI_POWER_ON);
这会导致给蓝牙模块发送一系列的初始化命令,
初始化成功后,APP通过hci_add_event_handler注册的函数就会被调用,在这个函数里就可以执行我们自己的代码,比如发起scan、发起connection等等。
附:3线串口上H5协议
SLIP协议最好文章:
SLIP—串行线路上传输数据报的非标准协议