蓝牙协议栈记录—BTStack

本文详细介绍BTStack蓝牙协议栈的架构与使用方法,包括单线程应用架构、内存配置、runloop处理流程及如何初始化BTStack。文章还介绍了如何使用BTStack进行设备发现、配对、创建服务和连接远程设备。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

再次强调:转载的。!!!

蓝牙协议栈记录—BTStack

TSTack User Guid 翻译过来的

1.简介

2.BTStack 架构

BTStack在所实现的协议和服务之间采用很多状态机实现相互作用,特点:

<1>单线程.BTStack只有一个单独的循环。

<2>没有阻塞,采用event事件方式。

<3>No artficially limited buffers/pools-Incoming and out going data packets are not queued

<4>Statically bouneded memory--最大连接\通道\服务数都可以配置

image

上图单线程应用架构。

3.怎样使用BTStack

3.1 协议和服务

BTStack已经实现了HCI、L2CAP、L2CAP-LE、RFCOMM、SDP和ATT。如下图所示.BTStack为HCI,L2CAP,EFCOMM和SDP提供了各种API接口,可直接使用.

image

BTStack有个重要的结构,service.一个service代表表示一个处理输入连接的服务器端。目前BTStack实现了两个服务,RFCOMM和L2CAP service.LECAP服务处理连接到L2CAP通道的连接,并用协议服务复用器(PSM)注册。RFCOMM服务则处理连接到RFCOMM连接的输入,并用RFCOMM通道ID注册。对于输出则不需要特别的注册,在应用需要的时候会创建.

3.2 内存配置

service 结构体,活跃的连接和远端设备可分为两种不同的方式:

<1>静态的个体的内存池,最大元素在config配置文件中定义。调用btstack_memory_init函数初始化静态内存池。

<2>使用malloc/free 函数动态分配,要在config文件中定义HAVE_MALLOC.

如果同时定义了上述两种方法,则静态分配优先,反之,两者都没定义,则会报错.

函数btstack_memory_init完成内存的设置.

3.3 Run loop

        BTStack运行runloop来处理输入数据、安排工作.runloop处理两个来自完全不同类型源的事件:数据源和定时器.数据源表示通讯接口比如UART和USB驱动。定时器用于实现各种蓝牙相关的超时.也可用于处理周期性的事件.

数据源和定时器分别通过结构体data_source_t和timer_source_t表示.每个结构体都包含一个链接列表节点和一个指向回调函数的指针。所有活跃的定时器和数据源都保存在链接列表中。不同的是列表中的数据源没有分类,而定时器是通过失效分类的从而高效处理.完整的run loop循环运行步骤:

<1>轮训运行所有已注册的数据源的回调函数。

<2>执行已就绪的定时器的回调函数.

<3>是否有中断处理请求,没有的话则进入sleep模式.

通过UART、USB或者定时器tick的输入数据会产生中断并叫醒处理器。为了避免在run loop刚好进入睡眠模式的时候发生数据源中断,中断驱动的数据源必须调用embeded_trigger函数.该函数设置一个内部标识在刚进入睡眠模式的临界点中进行判断.

定时器都是单发的:在定时器事件处理函数被执行之前是从定时器列表中移除的.如果定时器是周期的,就在回调函数中再重新注册同样的定时器源.注意,要使用定时器,必须在config文件中定义HAVE_TICK。在代码中,run loop通过调用run_loop_init函数实现:

run_loop_init(RUN_LOOP_EMBEDED);

3.4 BTStack 初始化

        初始化BTStack,要先初始化内存和run loop,然后设置HCI和其他所需要的高层协议.

HCI初始化要调整BTStack以适应所采用的平台,并需要四个参数:

<1>Bluetooth hardware control: 蓝牙硬件控制API用一个定制的初始化脚本提供HCI层、制造商特殊的波特率改变命令,以及系统电量通知。也可用于控制蓝牙模块的电源模式。此外还提供一个错误处理函数hw_error,当蓝牙模块报告硬件错误的时候,会调用该函数.bt_control_t结构体封装了常用的功能,比如,bt_control_cc256x_in-stance函数返回一个指针到适合CC256X芯片的控制结构体.

b t_ c o n t r o l _t  * c o n t r o l = b t_ c o n t r o l_ c c 2 5 6 x_ i n s t a n c e ( ) ;

<2>HCI 传输层实现:嵌入式系统中,蓝牙模块可通过UART,或USB端口连接。BTStack实现了两种基于UART的协议:HCI UART 传输层(H4)和带有eHCILL H4(TI的一种轻量级低功耗的变体).这可以通过连接不同的合适的文件而实现,然后获取一个指向HCI传输层实现的指针,比如:

image

<3>HCI传输层配置:

        因为H4传输接口所使用的UART配置不是标准的,因此BTStack在Main应用中提供该配置.比如:

  h c i _u a r t_ c o n f i g_ t  * c o n f i g = h c i _u a r t_ c o n f i g_ c c 2 5 6 x_ i n s t a n c e ( ) ;

<4>永久存储—指定永久数据的存放比如密码,远程设备名字等.一般需要根据平台指定代码来访问MCU的EEPROM等.比如:

remote_device_db_t * remote_db=&remote_device_dv_memory;

然后即可进行HCI初始化:

hci_init(transport,config,control,remote_db);

高层只依赖于BTStack,并通过各自的*_init函数进行初始化.这些初始化函数用下面的层注册自己.

3.5 获取数据-packet handlers

        硬件和BTStack配置完成之后就进入了run loop循环.从此开始一切都是事件驱动的.应用层调用BTStack函数,这些函数可能会轮流发送命令到蓝牙模块.所导致的事件会传回到应用层.

image

BTStack没有为每一个可能发生的事件都实现一个单独的回调句柄,而是为事件逻辑分组并提供通用的接口。

image 

HCI和一般的BTStack事件传递到l2cap_register_packet_handler函数所指定的数据包句柄,或hci_register_packet_handler(如果L2CAP没有使用的话).在L2CAP中,BTStack区分输入和输出连接.比如事件和数据包传递到不同的数据包句柄.输出连接用于访问远程服务,输入连接用于提供服务.对于输入连接,使用l2cap_register_service指定的句柄处理,对于输出连接,l2cap_create_channel_internal指定的句柄可处理.对于RFCOMM连接,目前采用rfcomm_register_packet_handler指定的句柄来处理所有的连接.应用层可以注册一个单独的共享的数据包句柄处理所有的协议和服务,或者对每个协议层和服务都采用各自的数据包句柄.共享的数据包句柄常用语堆栈初始化和连接管路.独自的数据包句柄可用于每个L2CAP服务和输出连接.

3.6 RFCOMM流控制

        RFCOMM有强制的基于积分的流控制,这就意味着确立了RFCOMM连接的两个设备采用积分来追踪还有多少RFCOMM数据包可以发送到彼此.如果一个设备没有剩余积分了,它就不能再发送RFCOMM数据包,传送必须暂停.在连接建立的阶段,提供初始的积分.BTStack在两个方向追踪积分数据.如果没有输出积分,RFCOMM发送函数返回一个错误,晚会儿再试。对于接收数据,BTStack提供通道和服务用和不用自动积分管理通过不同的函数来创建或注册它们自己.

4.快速入门

4.1 周期性的定时器处理

因为BTStack中的定时器都是单发(Single shot)的,对于周期性的定时器通过在timer_handler回调函数中重新注册timer_source来实现.如下所示:

4.2 定义定制的HCI命令模板.

    每个HCI命令都赋有一个2字节的操作码(OpCode)用于唯一识别不同的命令类型.操作码分为两部分,OGF(OpCode Group Field)和OCF(OpCode Command Field).其后跟着参数的全部长度和实际参数.BTStack提供hci_cmd_t结构体作为HCI命令数据包佛如压缩格式.命令的OpCode可通过OPCODE宏来计算.如下所示:

 

BTStack定义的可能的OGF:

 

HCI命令参数所支持的格式:

 

BTStack命令模块例子:其中OGF_CONTROLLER_BASEBAND是OGF,0x13是OCF,参数格式’N’表示一个空的UTF-8字符串.

 

4.3 基于模板发送HCI命令

函数hci_send_command用于发送基于模板的HCI命令和一列参数,在发送之前需检查输出缓冲是否为空以及蓝牙模块是否准备好接收下一条命令--因为大多数蓝牙模块只允许发送单条HCI命令。可通过调用hci_can_send_packet_packet_now(HCI_COMMAND_DATA_PACKET)来完成,如果返回true则可以发送.

4.4 Living with a single output buffer

输出数据包,无论数据还是命令,在BTStack中都不需要排队。独立于输出缓冲的数字,数据包的产生必须与远端接收器或/和最大连接速度相适应.因此数据包只有在可以发送的时候才会产生.BTStack返回BTSTACK_ACL_BUFFERS_FULL,如果输出缓冲是满的。如果没有输出积分,会返回RFCOMM_NO_OUTGOING_CREDITS。

4.5 Become discoverable

蓝牙设备必须设置为 discoverable 才能被其它执行查询扫描的设备发现.要设置为discoverable,应用直接调用hci_discoverable_control 带参数 1.如果希望设备有个名字,可通过发送hci_write_local_name 命令来设置.若要节能,则可以设置设备为 undiscoverable.

4.6 Discover remote devices.

要扫描远端设备,采用命令 hci_inquiry.然后,蓝牙模块会主动扫描其他设备并将报告作为HCI_EVENT_INQUIRY_RESULT,HCI_EVENT_INQUIRY_RESULT_WITH_RSSI,或HCI_EVENT_EXTENDED_INQUIRY_RESPONSE事件,每个响应包括至少蓝牙地址、设备class,寻呼扫描重复模式,时钟偏移量等.后面的事件添加关于所接收的信号的信息或者提供扩展查询结果(EIR,Extended Inquiry Result).

 

默认情况下,RSSI或EIR都不会报告.如果蓝牙设备的蓝牙协议版本在2.1之后,那么hci_write_inquiry_mode命令可以打开这些高级功能(0,标准结果;1,RSSI;2,RSSI和EIR).

4.7 设备配对

        默认的蓝牙通讯是不需要授权的,任何蓝牙设备都可以和其它蓝牙设备对话.蓝牙设备可以选择需要授权以提供特殊的服务。蓝牙授权一般通过PIN码完成.PIN码是一个ASCII字符串,最多16个字符。用户需要在两个设备上输入同样的PIN码,这个过程就是配对.一旦用户输入了PIN码,两个设备会产生一个链接key.链接key可以存在蓝牙模块上,也可以放在永久存储中.下次两个设备会使用先前产生的链接key。

image

4.8 获取远程设备L2CAP服务

        L2CAP是基于通道的概念.通道是基带上面的逻辑连接,每个通道会绑定到一个单独的协议(多对一的方式,即同一协议可以和很多通道绑定,但是一个通道只能绑定一种协议,多个通道可以共享同样的基带连接).

        要与远端设备的L2CAP通讯,本地的蓝牙设备应用要初始化L2CAP层,使用l2cap_init()函数。然后用函数l2cap_create_channel_internal()创建到远程设备的PSM的输出L2CAP通道.如果基带连接不存在,该函数会初始化一个新的基带连接.作为L2CAP创建通道函数的输入参数的数据包句柄会分配给新的输出L2CAP通道.该句柄会接收L2CAP_EVENT_CHANNEL_OPENED和L2CAP_EVENT_CHANNEL_CLOSED事件以及L2CAP数据包.

image

4.9 创建L2CAP服务

要提供L2CAP服务,本地的蓝牙设备应用必须初始化L2CAP并用l2cap_register_service_internal函数注册该服务.然后就可以等待输入L2CAP的连接.应用也可以根据具体情况使用l2cap_accept_connection_internal 来接受输入连接或l2cap_deny_connection_internal来拒绝连接.如果连接被接受且输入L2CAP通道成功地打开,那么L2CAP服务就可以用l2cap_send_internal函数发送L2CAP数据包到所连接的设备.

此外,L2CAP数据包的发送可能会失败,这时候应用层要根据DAEMON_EVENT_HCI_PACKET_SENT(BTStack输出缓冲是空闲的)或L2CAP_EVENT_CREDITS(ACL 缓冲空闲)事件重新发送.

4.10  获取远端设备的RFCOMM服务

        为了同远端设备的RFCOMM服务通信,本地蓝牙设备的应用层使用rfcomm_init函数初始化RFCOMM层,然后使用rfcomm_create_channel_internal()函数创建一个RFCOMM输出通道到给定的服务器通道.函数rfcomm_create_channel_internal-al为RFCOMM多路器初始化一个新的L2CAP通道(如果不存在的话).该通道自动为另一端提供充足的积分.如果手动提供积分,要通过调用rfcomm_create_channel_with_initial_credits_internal函数创建RFCOMM连接.数据包句柄作为RFCOMM创建通道函数的输入参数被分配给新的输出RFCOMM通道,该句柄会接收RFCOMM_EVENT_OPEN_CHAN-NEL_COMPLETE和RFCOMM_EVENT_CHANNEL_CLOSED事件和RFCOMM数据包.

image

4.11 提供RFCOMM服务

为了提供RFCOMM服务,本地蓝牙设备的应用需先初始化L2CAP和RFCOMM层然后用rfcomm_register_service_internal函数注册.然后即可以等待输入RFCOMM连接.应用层可根据情况接受(rfcomm_accept_connection_internal)或拒绝(rfcomm_deny_connection_internal)。如果连接请求被接受,且输入RFCOMM通道成功打开RFCOMM服务即可发送EFCOMM数据包到所连接的设备使用(rfconmm_send_internal)并通过rfcomm_register_service_internal调用所提供的数据包句柄来接收数据.

发送RFCOMM数据包可能会失败,这时候,应用层可依赖DAEMON_EVENT_HCI_PACKET_SENT或RFCOMM_EVENT_CREDITS事件来重新发送.

4.12 放缓RFCOMM数据接收

RFCOMM有个强制的基于积分的流控制可用于调整数据速率.(手动和自动两种方式)

 

 

4.13 创建SDP记录

        BTStack含有一个完整的SDP服务器,允许注册SDP记录.SDP记录是一列SDP属性{ID,Value}存储在Data Element Sequence(DES)中,其中,ID是一个16位的数字,value可以是整形数字或者字符串甚至可以包含其他的DES。

        要为SPP服务创建一个SDP记录,可以调用sdp_create_spp_service(src/sdp_util.c)用一个指针指向缓冲区以存储记录、RFCOMM服务器通道数字和记录名字.对于其他类型的记录,可以使用srx/sdp_util.c中的数据单元de_*。

First, a DES is created and then the Service Record Handle and Service Class ID List attributes are added to it. The Service Record Handle attribute is added by calling the de add number
function twice: the rst time to add 0x0000 as attribute ID, and the second time to add the actual record handle (here 0x1000) as attribute value. The Service Class ID List attribute has ID 0x0001, and it requires a list of UUIDs as attribute value. To create the list, de push sequence is called, which "opens" a sub-DES.The returned pointer is used to add elements to this sub-DES. After adding all UUIDs, the sub-DES is "closed" with de pop sequence.

image

 

 

 

 

 

 

 

 

 

 

 

 

 

4.14 查询远端SDP

BTStack 提供SDP客户端可以查询远端SDP服务.sdp_client_query函数启动一个L2CAP连接到远端SDP服务器,一旦建立连接,Service Search Attribute请求用Service Search Pattern和Attribute ID List 就会发送出去.该查询的结果包括一列Service Records,每一个都包含有所请求的属性.这些记录通过SDP parser处理.parser会传递SDP_PARSER_ATTRIBUTE_VALUE和SDP_PARSER_COMPLETE事件,SDP_PARSER_ATTRIBUTE_VALUE事件会一个字节一个字节的传递属性值。

此外,也可以实现特殊的SDP查询.

For example, BTstack provides a query for RFCOMM service name and channel number. This information is needed, e.g., if you want to connect to a remote SPP service.The query delivers all matching RFCOMM services, including its name and the channel number, as well as a query complete event via a registered callback,

### 回答1: 蓝牙协议栈BTstack是一种为蓝牙设备开发的基础框架。它旨在为使用低功耗蓝牙协议(BLE)和传统蓝牙协议(BR / EDR)的设备提供通用接口。 BTstack提供了一组API,使开发人员可以使用不同平台上的相同代码来处理蓝牙样式的应用程序。 BTstack提供的API包括协议栈初始化,L2CAP,RFCOMM,SDP,ATT / GATT等。 BTstack的另一个优点是它是一个开源项目,可以在许多平台上使用。它已被移植到多种硬件平台和操作系统中,例如ARM Cortex-M3 / M4(例如STM32),ATmega,Arduino,Raspberry Pi,Windows,Linux,macOS和iOS。 BTstack的应用广泛,包括智能手机,平板电脑,手表,耳机,音响,传感器和医疗设备。随着IoT设备的普及,BTstack将越来越受到重视。 总之,BTstack是一个功能强大、开源、跨平台的蓝牙协议栈,为开发人员提供了通用API,使他们可以在不同的平台上开发相同的应用程序。它是蓝牙设备开发者的重要工具之一。 ### 回答2: 蓝牙协议栈是指一套硬件和软件技术,用于使不同设备间的数据传输更加方便快捷。蓝牙协议栈的运行方式是通过多层协议交互实现的,向上层应用提供数据传输的支持,向下层硬件提供调用接口。 BTstack是一种开源的蓝牙协议栈,提供了高效、可靠和易于使用的终端设备的蓝牙连接。BTstack支持各种蓝牙协议,包括BLE、HFP、A2DP、AVRCP和SPP等,可以在不同的操作系统和硬件平台上使用。BTstack的优点在于它是可移植的,可以快速适应各种不同的蓝牙设备。 BTstack还支持多个蓝牙连接,并提供了稳定的设备连接,具备智能重连和设备发现功能,同时还支持多个蓝牙配置文件。BTstack还支持单片机,特别是ARM Cortex-M处理器,以及一些嵌入式系统平台。 总之,BTstack是一种强大的、稳定的、高度可移植的蓝牙协议栈,可以帮助开发者快速开发出高效、可靠的蓝牙连接应用。 ### 回答3: 蓝牙协议栈是用来实现蓝牙通信协议的软件模块,因此需要进行一系列的层次化操作,这些层次可以分为物理层,链路层,传输层,应用层。而btstack则是其中一个基于C语言的开源协议栈。 btstack采用了模块化的设计,每个模块之间相对独立,且易于扩展。可以在移植到不同平台上时,很容易添加某些特定的硬件驱动和操作系统。同时,btstack也提供了丰富的API接口,便于快速实现各种蓝牙应用,例如数据传输、音频传输、打印机、键盘/鼠标等等。 除此之外,btstack还支持免费开源的蓝牙协议栈,可供个人开源项目和商业开发者使用。同步提供了符合不同使用场景的两种许可证:BSD和GPLv2。且支持大部分操作系统,包括Windows、MacOS、Linux、Android、IOS 和Windows Phone等,在各个平台上表现出色。 总而言之,btstack是个高度模块化、可移植性高、及具有完整的特性和相同的性能的优秀蓝牙堆栈。它极大地推进了蓝牙技术的发展,且为蓝牙应用开发者提供了更多的灵活自由,是目前蓝牙通信领域应用很广泛的协议栈。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值