精华内容
下载资源
问答
  • 【蓝牙】什么?还不知道蓝牙协议栈开源了?
    千次阅读
    2022-03-21 15:41:53

    一,前述

    这年头协议栈开源的太多了,掌握基础蓝牙协议栈作为嵌入式软件工程师的进阶技能。如果有了解并应用的市面上大部分蓝牙芯片,不妨看看如下内容,对于理解并提升蓝牙协议了解有一定帮助。

    本次文章主要说明如何去学习蓝牙Host协议栈,controller协议栈仅做参考。(conrtoller涉及PHY层,除非原厂深入,一般我们仅作了解)

    二,关于Host协议栈

    1, 源码链接

    上官方链接:https://mynewt.apache.org/
    上Github链接:https://github.com/apache/mynewt-nimble
    

    Apache Mynewt是一个开源项目,其中Mynewt Nimble是开源5.1蓝牙协议栈,包含host&controller协议栈,可完全替代nordic softdevice(跟zephyr一样的)。
    网上开源协议栈很多,诸如:btstack,bluez,zephyr等等,但是Nimble属轻量级LE Host协议栈,对于初次了解协议栈的朋友非常友好。

    官方介绍:

    NimBLE complies with Bluetooth Core Specification 5.0 which makes it an ideal wireless technology for the Internet of Things (IoT).
    
    LE Advertising Extensions
    
    2Msym/s PHY for higher throughput
    
    Coded PHY for LE Long Range
    
    High Duty Cycle Non-Connectable Advertising
    
    Channel Selection Algorithm #2 to utilize channels in more efficient way.
    
    LE Privacy 1.2 for frequent changes to the device address to make it difficult to track for outsiders
    
    LE Secure Connections featuring FIPS-compliant algorithms.
    
    LE Data Length Extension for higher throughput
    
    Coming Soon: Assigning an Internet Protocol (IP) address (compliant with the IPv6 or 6LoWPAN standard) to a Bluetooth device through Internet Protocol Support Profile (IPSP)
    
    The Bluetooth 5 is backward compatible with previous Bluetooth version 4.2 which is also supported by Apache Mynewt.
    

    2,如何跑通Host协议栈

    本次主要验证如何跑通Host协议栈以及对应源码的简单介绍。
    关于Host的支持项如下:

    Host
    
    Logical Link Control and Adaptation Protocol (L2CAP): provides logical channels, named L2CAP channels, which are multiplexed over one or more logical links to provide packet segmentation and reassembly, flow control, error control, streaming, QoS etc.
    
    Security Manager (SM): uses Security Manager Protocol (SMP) for pairing and transport specific key distribution for securing radio communication
    
    Attribute protocol (ATT): allows a device (Server) to expose certain pieces of data, known as Attributes, to another device (Client)
    
    Generic Attribute Profile (GATT): a framework for using the ATT protocol to exchange attributes encapsulated as Characteristics or Services
    
    Generic Access Profile (GAP): a base profile which all Bluetooth devices implement, which in the case of LE, defines the Physical Layer, Link Layer, L2CAP, Security Manager, Attribute Protocol and Generic Attribute Profile.
    
    Host Controller Interface (HCI): the interface between the host and controller either through software API or by a hardware interface such as SPI, UART or USB.
    

    对于一个Linux子设备,如IPC或者网关蓝牙配网提供wifi ssid/token等等,一份简单的host协议栈即可满足需求。(且可配置无需依赖linux kernel)

    具体流程如下:

    (1)下载源码:

    git clone https://github.com/apache/mynewt-nimble
    

    (2)编译源码:

    root@ubuntu:/home/timcheng/project/mynewt-nimble-latest# cd porting/examples/linux/
    
    // 默认工具链为GCC,本次编译环境为Ubuntu 16.04
    root@ubuntu:/home/timcheng/project/mynewt-nimble-latest/porting/examples/linux# make 
    
    // 生成一个nimble-linux的可执行文件
    root@ubuntu:/home/timcheng/project/mynewt-nimble-latest/porting/examples/linux# ls
    ble.c  ble.o  include  main.c  main.o  Makefile  nimble-linux  README.md
    
    // 直接执行即可。
    // 需说明:由于采用的是HCI SOCKET形式与设备交互,所以如果没有安装驱动需单独安装驱动。
    //如果采用市面上的realtk dongle直插,需重新加载驱动,具体如何安装驱动以及驱动程序在我的附件中。
    

    (3)关于蓝牙服务说明:
    默认服务还是蛮多的,如果需要添加自己的vendor service,可按照任意一个的服务初始化添加即可,比较简单。同时需注释掉如下服务。
    注释掉如下服务

    (4)关于HCI Socket说明:

    索引地址:nimble\transport\socket\src\ble_hci_socket.c
    

    由于直接在ubuntu上编译且不依赖Apache Mynewt框架,所以可以选择性浏览一些代码。hci实现形式非常多,对于前期理解可先易后难。

    三,关于Host源码说明

    1,Host分层

    协议栈分层已经是基础了,我们剖析中间每一层去理解更为深刻。Apache Mynewt Nimble完全按照L2CAP->ATT/SM->GATT Server/Client以及GAP分层。注重说明GATT与GAP源码。

    2,Host协议栈之GAP

    索引地址:nimble\Host\src\ble_gap.c
    
    // 着重注意GAT Event,每一个GAP Event将会通过回调机制返回状态。
    static int gap_event_cb(struct ble_gap_event *event, void *arg)
    
    // 具体包含如下GAP Event事件
    #define BLE_GAP_EVENT_CONNECT               0
    #define BLE_GAP_EVENT_DISCONNECT            1
    /* Reserved                                 2 */
    #define BLE_GAP_EVENT_CONN_UPDATE           3
    ...
    #define BLE_GAP_EVENT_PERIODIC_SYNC_LOST    22
    #define BLE_GAP_EVENT_SCAN_REQ_RCVD         23
    #define BLE_GAP_EVENT_PERIODIC_TRANSFER     24
    
    // 关于GAP接口可参考文件内信息,着重说明如下几个常用接口
    // 开启广播
    int ble_gap_adv_start(uint8_t own_addr_type, const ble_addr_t *direct_addr,
                          int32_t duration_ms,
                          const struct ble_gap_adv_params *adv_params,
                          ble_gap_event_fn *cb, void *cb_arg);
    
    // 关闭广播
    int ble_gap_adv_stop(void);
    
    // 设置广播信息
    int ble_gap_adv_set_data(const uint8_t *data, int data_len);
    int ble_gap_adv_rsp_set_data(const uint8_t *data, int data_len);
    
    // 连接子设备
    int ble_gap_connect(uint8_t own_addr_type, const ble_addr_t *peer_addr,
                        int32_t duration_ms,
                        const struct ble_gap_conn_params *params,
                        ble_gap_event_fn *cb, void *cb_arg);
    
    // 开启扫描
    int ble_gap_disc(uint8_t own_addr_type, int32_t duration_ms,
                     const struct ble_gap_disc_params *disc_params,
                     ble_gap_event_fn *cb, void *cb_arg);
    
    // 关闭扫描
    int ble_gap_disc_cancel(void);
    

    3,Host协议栈之GATT

    Server索引地址:nimble\Host\src\ble_gatts.c
    Client索引地址:nimble\Host\src\ble_gatts.c
    
    // 常用读写notify接口
    int ble_gattc_write_no_rsp(uint16_t conn_handle, uint16_t attr_handle,
                               struct os_mbuf *om);
                               
    int ble_gattc_write(uint16_t conn_handle, uint16_t attr_handle,
                        struct os_mbuf *om,
                        ble_gatt_attr_fn *cb, void *cb_arg);
    
    int ble_gattc_notify_custom(uint16_t conn_handle, uint16_t att_handle,
                                struct os_mbuf *om);
    
    int ble_gattc_indicate_custom(uint16_t conn_handle, uint16_t chr_val_handle, struct os_mbuf *txom);
    
    int ble_gattc_read_by_uuid(uint16_t conn_handle, uint16_t start_handle,
                               uint16_t end_handle, const ble_uuid_t *uuid,
                               ble_gatt_attr_fn *cb, void *cb_arg);
    
    // 常用Client端发现服务等接口
    int ble_gattc_disc_all_svcs(uint16_t conn_handle,
                                ble_gatt_disc_svc_fn *cb, void *cb_arg);
    
    int ble_gattc_disc_svc_by_uuid(uint16_t conn_handle, const ble_uuid_t *uuid, ble_gatt_disc_svc_fn *cb, void *cb_arg);
    
    int ble_gattc_find_inc_svcs(uint16_t conn_handle, uint16_t start_handle,
    uint16_t end_handle, ble_gatt_disc_svc_fn *cb, void *cb_arg);
                                
    int ble_gattc_disc_all_chrs(uint16_t conn_handle, uint16_t start_handle,
    uint16_t end_handle, ble_gatt_chr_fn *cb, void *cb_arg);
    
    int ble_gattc_disc_chrs_by_uuid(uint16_t conn_handle, uint16_t start_handle, uint16_t end_handle, const ble_uuid_t *uuid,
    ble_gatt_chr_fn *cb, void *cb_arg);
    
    int ble_gattc_disc_all_dscs(uint16_t conn_handle, uint16_t start_handle,
    uint16_t end_handle, ble_gatt_dsc_fn *cb, void *cb_arg);
    
    

    4,关于Host的一些宏定义

    // 决定了你发送数据/广播/新增服务等的buffer大小
    MYNEWT_VAL_MSYS_1_BLOCK_COUNT
    
    // 决定了你接收数据的buffer大小
    MYNEWT_VAL_BLE_ACL_BUF_COUNT
    
    // 决定了你接收的hci事件的buffer大小,如果包含扫描,务必设定较大。
    MYNEWT_VAL_BLE_HCI_EVT_HI_BUF_COUNT
    MYNEWT_VAL_BLE_HCI_EVT_LO_BUF_COUNT
    
    

    四,最后说明

    开源协议栈框架仅仅是作为一个基础模型,真正做产品还是需要多加打磨的。无论是协议交互还是内存管理,都需要深度理解,才能打造好的产品。

    关于如何制作Controller与驱动以及协议栈内存管理分配,可参考另外篇幅。

    如果有相关需求,可以联系本人(vx:timcheng93)。可有偿提供整套协议栈服务。

    更多相关内容
  • nimble 开源蓝牙协议栈

    2020-02-20 15:47:03
    NimBLE 软件包是 RT-Thread 基于 Apache NimBLE 开源蓝牙 5.0 协议栈的移植实现,该协议栈提供完整的 Host 层和 Controller 层支持,目前支持 Nordic nRF51 和 nRF52 系列芯片。
  • 蓝牙协议栈详解

    2018-05-07 16:13:40
    蓝牙协议栈的详细文档,硬件工程师和嵌入式软件工程师必读的文档
  • nordic的蓝牙协议栈

    2018-03-27 15:31:38
    nordic官方的蓝牙协议栈,(至2018.3.27最新),s132,nrf52系列的.
  • 蓝牙协议栈

    2018-07-12 20:10:25
    蓝牙协议栈原理和结构,蓝牙协议栈原理和结构,蓝牙协议栈原理和结构,蓝牙协议栈原理和结构。
  • nordic s132蓝牙协议栈

    2019-01-17 00:53:18
    nordic s132蓝牙协议栈
  • 青风资料,NRF52832的SDK15.0学习资料,满满的干货,值得下载下来学习。希望可以帮助到有需要的人。
  • 蓝牙协议栈移植(arm)

    2018-08-14 15:30:34
    蓝牙协议栈移植,arm板子上面移植过程,非常宝贵的资料。
  • 对应蓝牙协议栈的初始化一直是大家关注的问题, Nordic 的协议栈的初始化及其调 度机制将是本节的详细探讨内容。 并且通过分析基本原理, 在匹配的 SDK10.0 的蓝牙样例的例子基础上就行分析与 讲解, 使用的协议栈为...
  • 运动手环常用的主控IC的nrf52832蓝牙协议栈S132_SDS_v6.0,
  • TI 蓝牙芯片(CC254x,CC2540、CC2501)蓝牙协议栈安装包BLE-Stack Setup,这个是官方英文版本,暂时无中文版。TI的蓝牙4.0开发包堆栈开发过程中需要用到的程序。虽然官网也能下,但是需要申请后才可以。
  • TI 低功耗蓝牙协议栈 SDK 开发资料,支持蓝牙芯片CC2540和CC2541,下载资料包含安装文件“BLE-CC254x-1.3.1.exe”,安装后提供相关文档资料和例程代码资料。
  • CC2650蓝牙协议栈及官方开发者指导手册,从零开始指导学习CC2650蓝牙芯片入门开发
  • BLE-STACK V2.2.5 (Support for CC2640/CC2650),官网最新蓝牙协议栈
  • 本课程定位是:引领想学习蓝牙协议栈的学生或者从事蓝牙,但是对蓝牙没有一个系统概念的工程师快速入门 课程是多年从事蓝牙经验总结出来的,希望能让你看完有一种醍醐灌顶的感觉。 不要在摸着石头过河了·学习完这些...
  • 智能传感器的蓝牙协议栈与SoC结构设计,通过对蓝牙协议栈和智能传感器功能要求的分析,讨论用于智能传感器设计的嵌入式蓝牙协议栈SoC的基本结构,以及功能组成要求。
  • 简介2.BTStack 架构BTStack在所实现的协议和服务之间采用很多状态机实现相互作用,特点:<1>单线程.BTStack只有一个单独的循环。<2>没有阻塞,采用event事件方式。<3>No artficially limited ...

    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--最大连接\通道\服务数都可以配置

    73f3e48ab8837dae94de686adb541b07.png

    上图单线程应用架构。

    3.怎样使用BTStack

    3.1 协议和服务

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

    6916aaa21e08253c9218a95b8ff8095f.png

    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传输层实现的指针,比如:

    6fc5d799f28ca23efa3f5edd363bade6.png

    <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函数,这些函数可能会轮流发送命令到蓝牙模块.所导致的事件会传回到应用层.

    510e041f2a9293aba45c7acbb3fbccb8.png

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

    511bb0668a7b3988883e90b4337ea8d0.png

    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来实现.如下所示:

    c8b1a045fee927399b377b865eca1010.png

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

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

    01888302ceb10fa5af40295f6d6b81b3.png

    BTStack定义的可能的OGF:

    937d2614c3d04834e8a53c7b7a8f5e72.png

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

    f027239120c697d0a1f244ae6f7c09d6.png

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

    a660773e0e7d24b0d627e5e815668cc9.png

    4.3 基于模板发送HCI命令

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

    b3d92edd207e614b566e38e4515e02d9.png

    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).

    bb465ecf62150aa24f9fac06d02537c9.png

    默认情况下,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。

    497a421d9bbc2b1c3a5e62eb11a72a30.png

    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数据包.

    95bf1ed8d2765b58b4041aeaff20d867.png

    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 缓冲空闲)事件重新发送.

    940a21cd976e6eff045d0836704f4ddd.png

    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数据包.

    b25e94e0b557079798104097788f198f.png

    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事件来重新发送.

    977be359e7013823abd1845766ca814d.png

    4.12 放缓RFCOMM数据接收

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

    aabafce0d6acd1d963016e3a53972435.png

    814038b553ae0147864e2ad72b23bde8.png

    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.

    1f13110987a98eab80f0866cf2eb3fba.png

    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,

    9ca73dd5253aaee70c15e85cacd7cb1f.png

    4124af598d79137f853c7ab6e210d314.png

    蓝牙协议栈中的 OSAL

    蓝牙协议栈里的操作系统叫做 OSAL(操作系统抽象层).它并非一个真正意义上的操作系统,它只是实现了操作系统的一些功能,如任务切换.内存管理. OSAL 产生的根源:基于蓝牙协议栈开发的产品,实际上是 ...

    Bluedroid&colon; 蓝牙协议栈源码剖析

    一. 基础知识介绍 1.缩略语 BTIF: Bluetooth Interface BTU : Bluetooth Upper Layer BTM: Bluetooth Manager BTE: Bl ...

    蓝牙 BLE 协议学习: 3种蓝牙架构实现方案(蓝牙协议栈方案)

    导言 不同的蓝牙架构可以用在不同的场景中.从而协议帧的架构方案也会不同. 转载自: 蓝牙架构实现方案有哪几种?我们一般把整个蓝牙实现方案叫做蓝牙协 ...

    第08节-开源蓝牙协议栈BTStack数据处理

    本篇博客根据韦东山的视频整理所得. 在上篇博客,通过阅读BTStack的源码,大体了解了其框架,对于任何一个BTStack的应用程序都有一个main函数,这个main函数是统一的.这个main函数做了 ...

    BLE 蓝牙协议栈开发

    1.由浅入深,蓝牙4.0/BLE协议栈开发攻略大全(1) 2.由浅入深,蓝牙4.0/BLE协议栈开发攻略大全(2) 3.由浅入深,蓝牙4.0/BLE协议栈开发攻略大全(3)

    BLE协议栈及传统蓝牙协议栈对比图

    1. BLE协议栈的层次图如下: 主机控制接口层: 为主机和控制器之间提供标准通信接口 逻辑链路控制及自适应协议层: 为上层提供数据封装服务 安全管理层: 定义配对和密钥分配方式,为协议栈其他层与另一 ...

    微信蓝牙ble记录

    参加了一个简单的微信蓝牙ble项目,做一些记录 首先按网站上面的各种配置 简单的说就是,软件上面,生成deviceid->绑定设备和deviceid. 几点注意: 1>deviceid是唯 ...

    Android BlueDroid(蓝牙协议栈)

    Android BlueDroid(一):BlueDroid概述 Android BlueDroid(二):BlueDroid蓝牙开启过程init Android BlueDroid(三):BlueD ...

    第07节-开源蓝牙协议BTStack框架代码阅读(上)

    首先来看一下,对于硬件操作,它是如何来进行处理的.在上篇文章中曾说过,在main函数里面它会调用硬件相关的代码,调用操作系统相关的代码.在BTStack中,可以搜索一下main.c,将会发现有很多ma ...

    随机推荐

    夺命雷公狗---DEDECMS----13dedecms首页的完成

    我们的dedecms搭建起来后直接复制templets的目录复制一份,如下所示: 然后进入templets目录里面,然后再将default里面的东西都给干掉,然后将我们预先准备好的首页放进来,代码如下 ...

    repeater控件实现分页

    repeater控件实现排序的方法,今天我再向大家介绍repeater控件如何实现分页的效果. 分页分为真分页和假分页. 真分页:控件上一页需要显示多少数据,就从数据库取出并绑定多少数据,每次换页时都 ...

    win7下使用apache ab 比较测试node与 tomcat

    最近在研究node,都说node单线程.事件环机制,高并发效率高,亲测一下,一探究竟 apache ab 安装 进入:http://httpd.apache.org/download.cgi#apac ...

    HTML5中自定义属性(data-&ast;)

    在HTML元素上直接添加以‘data-’开头的属性,例如

    jabRef里引用的相邻同名作者变横线

    用jabRef引用同名作者的文章时,出现了第二个文章的作者变成了横线,在搜了相关资料后,发现作如下修改可避免: 1.在.bib文件中加入开关,并修改默认配置: @IEEEtranBSTCTL{IEEE ...

    转&colon; FFmpeg功能命令汇总

    原文: FFmpeg功能命令汇总 前言 如此强大的FFmpeg,能够实现视频采集.视频格式转化.视频截图.视频添加水印.视频切片.视频录制.视频推流.更改音视频参数功能等.通过终端命令如何实现这些功能 ...

    mysql-connector-odbc-8&period;0&period;11-winx64&period;msi安装失败

    mysql-connector-odbc-8.0.11-winx64.msi安装失败 提示需要Redistributable for Visual Studio 2015 去下载 vc_redist. ...

    通过Intel XDK编写跨平台app&lpar;二&rpar;

    通过Intel XDK编写跨平台app(一) 通过Intel XDK编写跨平台app(二) 在这个系列的上一篇文章中,我们大致了解了Interl XDK的概况.在这一部分中,我们会详细地介绍如何通过这 ...

    ServletContext域对象

    场景:假设某个web服务,有两个servlet分别是servlet1和servlet2,servlet1要传参数name=zhangsan传送给servlet2,传统方法如下: servlet1端:用 ...

    展开全文
  • 主要介绍下蓝牙协议栈开发板跑传统蓝牙搜索AT指令以及上位机操作步骤,以及原理 一. 声明 本专栏文章我们会以连载的方式持续更新,本专栏计划更新内容如下: 第一篇:蓝牙综合介绍 ,主要介绍蓝牙的一些概念,...

    零. 概述

    主要介绍下蓝牙协议栈开发板跑传统蓝牙搜索AT指令以及上位机操作步骤,以及原理

    一. 声明

    本专栏文章我们会以连载的方式持续更新,本专栏计划更新内容如下:

    第一篇:蓝牙综合介绍 ,主要介绍蓝牙的一些概念,产生背景,发展轨迹,市面蓝牙介绍,以及蓝牙开发板介绍。

    第二篇:Transport层介绍,主要介绍蓝牙协议栈跟蓝牙芯片之前的硬件传输协议,比如基于UART的H4,H5,BCSP,基于USB的H2等

    第三篇:传统蓝牙controller介绍,主要介绍传统蓝牙芯片的介绍,包括射频层(RF),基带层(baseband),链路管理层(LMP)等

    第四篇:传统蓝牙host介绍,主要介绍传统蓝牙的协议栈,比如HCI,L2CAP,SDP,RFCOMM,HFP,SPP,HID,AVDTP,AVCTP,A2DP,AVRCP,OBEX,PBAP,MAP等等一系列的协议吧。

    第五篇:低功耗蓝牙controller介绍,主要介绍低功耗蓝牙芯片,包括物理层(PHY),链路层(LL)

    第六篇:低功耗蓝牙host介绍,低功耗蓝牙协议栈的介绍,包括HCI,L2CAP,ATT,GATT,SM等

    第七篇:蓝牙芯片介绍,主要介绍一些蓝牙芯片的初始化流程,基于HCI vendor command的扩展

    第八篇:附录,主要介绍以上常用名词的介绍以及一些特殊流程的介绍等。

    另外,开发板如下所示,对于想学习蓝牙协议栈的最好人手一套。以便更好的学习蓝牙协议栈,相信我,学完这一套视频你将拥有修改任何协议栈的能力(比如Linux下的bluez,Android下的bluedroid)。

    -------------------------------------------------------------------------------------------------------------------------

    CSDN学院链接(进入选择你想要学习的课程):https://edu.csdn.net/lecturer/5352?spm=1002.2001.3001.4144

    蓝牙交流扣扣群:970324688

    Github代码:https://github.com/sj15712795029/bluetooth_stack

    入手开发板:https://item.taobao.com/item.htm?spm=a1z10.1-c-s.w4004-22329603896.18.5aeb41f973iStr&id=622836061708

    蓝牙学习目录https://blog.csdn.net/XiaoXiaoPengBo/article/details/107727900

    --------------------------------------------------------------------------------------------------------------------------

    演示视频点击我(!!!!!!!!!!!!!!!!)

    二. STM32蓝牙协议栈封装使用AT command实现搜索

    使用步骤操作如下:

    步骤 1)准备好代码,从github下载下来最新的代码(在上面有介绍Github连接)

    步骤 2)连接好硬件(把模组插好,ST-LINK接上,TYPE-C debug先接上,按下按钮可以看到蓝色电源等亮起)

    步骤 3)打开Keil工程文件夹下的project\stm32f10x_bb_csr8x11_bt\stm32f10x_bb_csr8x11.uvprojx,然后编译下载

    此部分注意几点:

    • 下载需要ST-LINK驱动,我已经放在下载资料中的软件工具文件夹中
    • STM32 F1的pack要有,我已经放在软件工具文件夹中的MDK下,没有没安装过要安装下,名字如下:

       

    • 下载的debug要选ST-LINK

         

    • 下载的时候要勾选Use micro lib

         

    步骤4)打开串口工具(我用的是XCOM),然后做初始化动作,在发送串口敲BT_START,点击发送,出来以下log就证明初始化通过了,我们就可以来进行搜索动作了注意一点:不能勾选发送新行,否则会解析错误

    步骤5)然后敲BT_INQUIRY就能搜索到设备了

    三.使用我们自己写的上位机来实现搜索

    步骤跟AT的1)2)3)一样,我们从第四步开始讲解

    打开我们工程源码1-BLUETOOTH\mcu_bt_tool\mcu_bt_tool\mcu_bt_tool\bin\Debug中的mcu_bt_tool.exe,当然你也可以直接用VS2010打开工程

    步骤 1)打开串口

    步骤 2)点击蓝牙开启按钮(此步骤跟AT 命令BT_START一样的效果,就是实现蓝牙初始化)

    步骤 3) 等待初始化完成点击搜索按钮,你就发现可以搜索到蓝牙了

    另外:使用上位机的时候注意几点:

    ① mcu_bt_tool.exe你如果想把可执行文件拿到别的路径单独执行,那么必须要把Newtonsoft.Json.dll跟exe放在同一个路径下,因为上位机是跟STM32用json沟通的

    ② 因为目前搜索是开启的EIR,带RSSI的,所以他会重复性上来同一个设备,我没做根据同一个蓝牙地址做显示过滤,如果有兴趣的人可以加上这一块

    四. 串口工具AT command以及上位机实现搜索的原理

    步骤 1)Type C uart debug口的tx,rx初始化

    /******************************************************************************
     * func name   : hw_uart_debug_init
     * para        : baud_rate(IN)  --> Baud rate of uart1
     * return      : hw_uart_debug_init result
     * description : Initialization of USART1.PA9->TX PA10->RX
    ******************************************************************************/
    uint8_t hw_uart_debug_init(uint32_t baud_rate)
    {
        GPIO_InitTypeDef GPIO_InitStructure;
        USART_InitTypeDef USART_InitStructure;
        NVIC_InitTypeDef NVIC_InitStructure;
        DMA_InitTypeDef DMA_InitStructure;
    
        /* Enable RCC clock for USART1,GPIOA,DMA1 */
        RCC_APB2PeriphClockCmd(RCC_APB2Periph_USART1|RCC_APB2Periph_GPIOA, ENABLE);
        RCC_AHBPeriphClockCmd(RCC_AHBPeriph_DMA1, ENABLE);
    
        /* Initialization GPIOA9 GPIOA10 */
        GPIO_InitStructure.GPIO_Pin = GPIO_Pin_9;
        GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
        GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP;
        GPIO_Init(GPIOA, &GPIO_InitStructure);
    
        GPIO_InitStructure.GPIO_Pin = GPIO_Pin_10;
        GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IN_FLOATING;
        GPIO_Init(GPIOA, &GPIO_InitStructure);
    
        /* Data format :1:8:1, no parity check, no hardware flow control */
        USART_InitStructure.USART_BaudRate = baud_rate;
        USART_InitStructure.USART_WordLength = USART_WordLength_8b;
        USART_InitStructure.USART_StopBits = USART_StopBits_1;
        USART_InitStructure.USART_Parity = USART_Parity_No;
        USART_InitStructure.USART_HardwareFlowControl = USART_HardwareFlowControl_None;
        USART_InitStructure.USART_Mode = USART_Mode_Tx | USART_Mode_Rx;
    
        /* Enable USART interrupts, mainly for idle interrupts */
        NVIC_InitStructure.NVIC_IRQChannel = USART1_IRQn;
        NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority=DEBUG_PREE_PRIO;
        NVIC_InitStructure.NVIC_IRQChannelSubPriority = DEBUG_SUB_PRIO;
        NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE;
        NVIC_Init(&NVIC_InitStructure);
    
        /* Initializes USART1 to enable USART, USART idle interrupts and USART RX DMA */
        USART_Init(USART1, &USART_InitStructure);
        USART_ITConfig(USART1, USART_IT_IDLE, ENABLE);
        USART_DMACmd(USART1,USART_DMAReq_Rx,ENABLE);
        USART_Cmd(USART1, ENABLE);
    
        /* Initializes DMA and enables it */
        DMA_DeInit(DMA1_Channel5);
        DMA_InitStructure.DMA_PeripheralBaseAddr = (uint32_t)&USART1->DR;
        DMA_InitStructure.DMA_MemoryBaseAddr = (uint32_t)uart1_rev_buffer;
        DMA_InitStructure.DMA_DIR = DMA_DIR_PeripheralSRC;
        DMA_InitStructure.DMA_BufferSize = UART1_MAX_REV;
        DMA_InitStructure.DMA_PeripheralInc = DMA_PeripheralInc_Disable;
        DMA_InitStructure.DMA_MemoryInc = DMA_MemoryInc_Enable;
        DMA_InitStructure.DMA_PeripheralDataSize = DMA_PeripheralDataSize_Byte;
        DMA_InitStructure.DMA_MemoryDataSize = DMA_MemoryDataSize_Byte;
        DMA_InitStructure.DMA_Mode = DMA_Mode_Normal;
        DMA_InitStructure.DMA_Priority = DMA_Priority_Medium;
        DMA_InitStructure.DMA_M2M = DMA_M2M_Disable;
        DMA_Init(DMA1_Channel5, &DMA_InitStructure);
    
        DMA_Cmd(DMA1_Channel5, ENABLE);
    
        return HW_ERR_OK;
    
    }
    

    可以看到TX我们就是普通的实现发送,RX我们用串口空闲中断+DMA的方式来实现接受串口工具以及上位机的发送指令,然后串口中断的实现原理是这样:

    /******************************************************************************
     * func name   : USART1_IRQHandler
     * para        : NULL
     * return      : NULL
     * description : Interrupt handler for usart1
    ******************************************************************************/
    void USART1_IRQHandler(void)
    {
    
        if(USART_GetITStatus(USART1, USART_IT_IDLE) != RESET)
        {
            /* Without this, the interrupt cannot be cleared and continues into the interrupt */
            USART_ReceiveData(USART1);
            uart1_rev_len =UART1_MAX_REV-DMA_GetCurrDataCounter(DMA1_Channel5);
    
            if(uart1_rev_len != 0)
            {
                /* Call the parse function */
    		shell_parse(uart1_rev_buffer);
                hw_memset(uart1_rev_buffer,0,sizeof(uart1_rev_buffer));
            }
            /* Clear the interrupt and reset DMA */
            USART_ClearITPendingBit(USART1,USART_IT_IDLE);
            uart1_dma_enable(DMA1_Channel5);
        }
    }
    

    步骤2)收到串口工具或者上位机的解析函数如下:

    uint8_t shell_parse(uint8_t *shell_string)
    {
        uint8_t result = HW_ERR_OK;
    
    
        cJSON* parse_json = cJSON_Parse((const char *)shell_string);
        uint8_t* func_value = (uint8_t*)((cJSON *)cJSON_GetObjectItem(parse_json,"FUNC"))->valuestring;
        uint8_t* operate_value = (uint8_t*)((cJSON *)cJSON_GetObjectItem(parse_json,"OPERATE"))->valuestring;
        uint8_t* para1 = (uint8_t*)((cJSON *)cJSON_GetObjectItem(parse_json,"PARAM1"))->valuestring;
        uint8_t* para2 = (uint8_t*)((cJSON *)cJSON_GetObjectItem(parse_json,"PARAM2"))->valuestring;
        uint8_t* para3 = (uint8_t*)((cJSON *)cJSON_GetObjectItem(parse_json,"PARAM3"))->valuestring;
        uint8_t* para4 = (uint8_t*)((cJSON *)cJSON_GetObjectItem(parse_json,"PARAM4"))->valuestring;
        uint8_t* para5 = (uint8_t*)((cJSON *)cJSON_GetObjectItem(parse_json,"PARAM5"))->valuestring;
        uint8_t* para6 = (uint8_t*)((cJSON *)cJSON_GetObjectItem(parse_json,"PARAM6"))->valuestring;
    
        if(hw_strcmp((const char *)func_value,"BT") == 0)
        {
            result = shell_json_parse(operate_value,para1,para2,para3,para4,para5,para6);
        }
        else
        {
            result = shell_at_cmd_parse(shell_string);
        }
    
        cJSON_Delete(parse_json);
        return result;
    
    }

    在这里我们分两种方式来解析:①AT command(串口工具采用这种方式) ②Json(上位机采用这种方式),如果解析不是我们定义的json格式,那么就自动转变为AT指令的解析

    步骤3)AT command解析执行BT_START,以及BT_INQUIRY

    uint8_t shell_at_cmd_parse(uint8_t *shell_string)
    {
    
        if(hw_strcmp("BT_START",(const char*)shell_string) == 0)
        {
            HW_DEBUG("SHELL:operate bt start\n");
            bt_start(&bt_app_cb);
            return HW_ERR_OK;
        }
    
        ........
    
    
        if(hw_strcmp("BT_INQUIRY",(const char*)shell_string) == 0)
        {
            HW_DEBUG("SHELL:operate bt inquiry\n");
            bt_start_inquiry(0x30,HCI_INQUIRY_MAX_DEV);
            return HW_ERR_OK;
        }
    }

    步骤4)接受上位机json指令解析

    uint8_t shell_json_parse(uint8_t *operate_value,
                             uint8_t *para1,uint8_t *para2,uint8_t *para3,
                             uint8_t *para4,uint8_t *para5,uint8_t *para6)
    {
        if(hw_strcmp((const char *)operate_value,"BT_START") == 0)
        {
            HW_DEBUG("UART PARSE DEBUG:operate BT_START\n");
            bt_start(&bt_app_cb);
            operate_stauts_oled_show("BT",operate_value,"SUCCESS",0,0,0,0,0,0);
            return HW_ERR_OK;
        }
    
        .....
    
    
        if(hw_strcmp((const char *)operate_value,"BT_START_INQUIRY") == 0)
        {
            HW_DEBUG("UART PARSE DEBUG:operate BT_INQUIRY\n");
            bt_start_inquiry(0x30,HCI_INQUIRY_MAX_DEV);
            return HW_ERR_OK;
        }
    }

    以上步骤3)4)其中bt_start以及bt_start_inquiry就是协议栈函数了,这样就实现了AT command或者json上位机跟蓝牙协议栈的对接。

    展开全文
  • BLE低功耗蓝牙协议栈

    2022-03-14 17:52:42
    BLE低功耗蓝牙协议栈 (1)蓝牙核心协议(Bluetooth Core) (2)蓝牙应用层协议(Bluetooth Application) (3)BLE低功耗蓝牙核心协议层详解(Bluetooth Core) ① 物理层(PHY) ② 链路层(LL) ③ 主机...

    目录

    一. BLE低功耗蓝牙协议栈

    (1)蓝牙核心协议(Bluetooth Core)

    (2)蓝牙应用层协议(Bluetooth Application)

    (3)BLE低功耗蓝牙核心协议层详解(Bluetooth Core)

    ① 物理层(PHY)

    ② 链路层(LL)

    ③ 主机控制接口层(HCI)

    ④ 通用访问配置文件层(GAP)

    ⑤ 逻辑链路控制及自适应协议层(L2CAP)

    ⑥ 安全管理层(SM)

    ⑦ 属性协议层(ATT)

    ⑧ 通用属性配置文件层(GATT)

    (4)ATT协议层中的属性(attribute)

    (4.1)属性的组成(数据结构)

    二. BLE中的GAP和GATT:

    1. 引言:

    2. GAP:

    2.1 设备角色

    2.2 广播数据

    3. GATT

    3.1 GATT通信事务

    3.2 GATT结构

                            Profile

                            Service

                            Characteristic

                            UUID

    三. 一些常见术语概念

    1. ATT PDU(属性协议)

    2. GAP Bond管理

    使用GAPBondMgr

    四. 如何通过无线发送一个数据包

    广播方式

    连接方式


    经典蓝牙(BT):一般用于数据量比较大的传输,如:语音、音乐等较高数据量的传输。

    低功耗蓝牙模块(BLE):最大的特点就是成本和功耗的降低,可应用于实时性要求较高的产品当中,比如:智能家居类(蓝牙锁、蓝牙灯)、传感设备的数据发送(血压计、温度传感器)、消费类电子(电子烟、遥控玩具)等。

    BTBLE两者物理层调制解调方式是不一样的,所以BLE设备和BT设备两者之间是不能相互通信的,选型的时候千万不要搞混,如果主设备是BLE设备,从设备也必须是BLE设备;同样,BT的从设备也只能和BT的主设备进行通信。

    不过市场上还有一种双模蓝牙设备,即同时支持BLE和BT,比如我们天天用到的手机,手机可以和BT设备通信,也可以和BLE设备通信,但这不代表BLE设备可以和BT设备通信。其实是手机使用了分时机制来达到同时和BLE设备以及BT设备通信的目的,即手机让双模蓝牙芯片不断地在BLE模式和BT模式之间进行切换,以同时支持BLE设备和BT设备。

    一. BLE低功耗蓝牙协议栈

    深入浅出讲解低功耗蓝牙(BLE)协议栈 - 知乎

    BLE协议栈主要用来对你的应用数据进行层层封包,以生成一个满足BLE协议的空中数据包。也就是说,把应用数据包裹在一系列的帧头(header)和帧尾(tail)中。

    蓝牙协议规定了两个层次的协议:

    (1)蓝牙核心协议(Bluetooth Core)

    蓝牙核心协议关注对蓝牙核心技术的描述和规范,它只提供基础的机制,并不关心如何使用这些机制。

    蓝牙核心协议又包含BLE Controller和BLE Host两部分。

    • Controller:负责定义RF、Baseband等偏硬件的规范,并在这之上抽象出用于通信的逻辑链路(Logical Link);
    • Host:负责在逻辑链路的基础上,进行更为友好的封装,这样就可以屏蔽掉蓝牙技术的细节,让Bluetooth Application更为方便的使用。

    (2)蓝牙应用层协议(Bluetooth Application)

    蓝牙应用层协议,是在蓝牙核心协议的基础上,根据具体的应用需求,百花齐放,定义出各种各样的策略,如FTP、文件传输、局域网等等。

    (3)BLE低功耗蓝牙核心协议层详解(Bluetooth Core)

    ① 物理层(PHY)

    PHY层用来指定BLE所用的无线频段,调制解调方式和方法等。PHY层做得好不好,直接决定整个BLE芯片的功耗,灵敏度以及selectivity等射频指标。

    ② 链路层(LL)

    LL层是整个BLE协议栈的核心。

    LL层要做的事情非常多,比如具体选择哪程度 个射频通道进行通信,怎么识别空中数据包,具体在哪个时间点把数据包发送出去,怎么保证数据的完整性,ACK如何接收,如何进行重传,以及如何对链路进行管理和控制等等。

    LL层只负责把数据发出去或者收回来,对数据进行怎样的解析则交给上面的GAP或者ATT。

    ③ 主机控制接口层(HCI)

    HCL是可选的,主要用于两个芯片实现BLE协议栈的场合,用来规范两者之间的通信协议、通信命令等。

    ④ 通用访问配置文件层(GAP)

    主要用来进行广播、扫描和发起连接等。

    ⑤ 逻辑链路控制及自适应协议层(L2CAP)

    L2CAP对LL进行了一次简单封装。LL层只关心传输的数据本身,L2CAP就要区分加密通道还是普通通道,同时还要对连接间隔进行管理。

    ⑥ 安全管理层(SM)

    用来管理BLE连接的加密和安全的。

    ⑦ 属性协议层(ATT)

    简单来说,ATT层用来定义用户命令及命令操作的数据,比如读/写某个数据。

    开发者接触最多的就是ATT。BLE引入了attribute(属性)概念,用来描述一条条数据,attribute除了定义数据,还定义该数据可以使用的ATT命令,因此这一层被称为ATT层。

    ⑧ 通用属性配置文件层(GATT)

    用来规范attribute中的数据内容,并用group(分组)的概念进行分类管理。

    (4)ATT协议层中的属性(attribute)

    ATT(Attribute Protocol)属性层是GATT和GAP的基础,它定义了BLE协议栈上层的数据结构和组织方式。

    属性(Attribute)概念是ATT层的核心,ATT层定义了属性的内容,规定了访问属性的方法和权限。以编程的眼光来看,属性是一个数据结构,它包括了数据类型和数据值,就如同C语言结构体的概念,开发者可以设计独特的结构,来描述外部世界实体。

    属性包括三种类型:服务项(service)、特征值(characteristic)和描述符。三者之间存在树状包含关系:服务项包含一个或多个特征值,特征值包含一个或多个描述符,多个服务项组织在一起,构成属性规范(Attribute Profile)。

    对于常用的属性规范,比如体重计、心率计,SIG(蓝牙技术联盟)做了具体定义,这样的话,只要BLE主从设备均遵守某个Profile来进行设计,那么二者就能够优雅的通信。

    (4.1)属性的组成(数据结构)

    属性主要由以下四部分组成:

     属性句柄(Attribute Handler):

    犹如指向属性实体的指针,可通过属性句柄来访问该属性。它是一个2字节长度的十六进制码,起始于0x0001,系统初始化时各个属性的句柄逐步加1,最大不超过0xFFFF。

    属性类型(Attribute Type):

    用以区分当前属性是服务项或是特征值等,用UUID来表示。

    UUID(universally unique identifier,通用唯一识别码)是一个软件构建标准,并非BLE独有的概念,一个合法的UUID,一定是随机的、全球唯一的,不应该出现两个相同的UUID。

    BLE的属性类型是有限的,有四个大类:

    • Primary Service(首要服务项)
    • Secondary Service(次要服务项)
    • Include(包含服务项)
    • Characteristic(特征值)

    这些属性类型分别对应了指定的UUID,BLE对这些UUID与属性类型的映射关系做了规定:

    • 0x1800 – 0x26FF :服务项类型
    • 0x2700 – 0x27FF :单位
    • 0x2800 – 0x28FF :属性类型
    • 0x2900 – 0x29FF :描述符类型
    • 0x2A00 – 0x7FFF :特征值类型

    假如UUID=0x1800,就表示它是一个首要服务项。

    标准的UUID是一串16字节十六进制字符串,但对于一些常用的UUID,为了减少传输的数据量,BLE协议做了一个转换约定,给定一个固定的16字节模板,只设置2个字节为变化量,其他为常量,2字节的UUID在系统内部会被替换,进而转换成标准的16字节UUID。

    UUID模板为:

    0000XXXX-0000-1000-8000-00805F9B34FB

    其中 xxxx 就是变化位,其他为固定位。

    如:UUID=0x2A00在系统内部会转换成00002A00-0000-1000-8000-00805F9B34FB。

    属性值(Attribute Value):

    用于存放数据。

    如果该属性是服务项类型或者是特征值声明类型,那么它的属性值就是UUID等信息。如果是普通的特征值,则属性值是用户的数据。

    操作特征值里的用户数据,就是对那块内存空间进行读写。

    属性权限(Attribute Permissions):

    主要有以下四种:

    • 访问权限(Access Permission)- 只读、只写、读写
    • 加密权限(Encryption Permission) – 加密、不加密
    • 认证权限(Authentication Permission) – 需要认证、无需认证
    • 授权权限(Authorization Permission) – 需要授权、无需授权

    注:

    加密:就是对数据进行加密;

    认证:是指相互确认对方身份,BLE中,认证过程就是配对;

    授权:授权要求设备为Trusted Device(可信任设备)。在实际使用中,经过配对以后设备即为Untrusted Device——认证,在代码中调用API可以设置设备为Trusted Device——授权。

    二. BLE中的GAP和GATT:

    蓝牙BLE: GATT Profile 简介(GATT 与 GAP) - 夜行过客 - 博客园

    1. 引言:

    低功耗蓝牙(BLE)连接都是建立在GATT(Generic Attribute Profile)协议之上。

    GATT是一个蓝牙连接之上的发送和接收很短的数据段的通用规范,这些很短的数据段被称为属性(Attribute)。

    2. GAP:

    介绍GATT之前,需要了解GAP(Generic Access Profile),它用来控制设备连接和广播。GAP使你的设备被其他设备可见,并决定了你的设备是否可以或者怎样与其他设备进行交互。

    2.1 设备角色

    GAP给设备定义了若干角色,主要的两个是:

    ① 外围设备(Peripheral):比如手环。一般是小、简单、低功耗设备,用来提供数据,并连接到一个强大的中心设备上。

    ② 中心设备(Central):比如手机。用来连接其他外围设备。

    2.2 广播数据

    在 GAP 中外围设备通过两种方式向外广播数据: Advertising Data Payload(广播数据)和 Scan Response Data Payload(扫描回复),每种数据最长可以包含 31 byte。这里广播数据是必需的,因为外设必需不停的向外广播,让中心设备知道它的存在。扫描回复是可选的,中心设备可以向外设请求扫描回复,这里包含一些设备额外的信息,例如设备的名字。

     从图中我们可以清晰看出广播数据和扫描回复数据是怎么工作的。外围设备会设定一个广播间隔,每个广播间隔中,它会重新发送自己的广播数据。广播间隔越长,越省电,同时也不太容易扫描到。

    大部分情况下,外设通过广播自己来让中心设备发现自己,并建立GATT连接,从而进行更多的数据交换。

    也有些情况是不需要连接的,只要外设广播自己的数据即可。

    用这种方式主要目的是让外围设备把自己的信息发送给多个中心设备。因为基于GATT连接方式的,只能是一个外设连接一个中心设备。

    3. GATT

    GATT 连接,必须先经过GAP协议。

    一旦两个设备建立起了连接,GATT 就开始起作用了。

    中心设备和外设需要双向通信的话,唯一的方式就是建立GATT 连接。

    GATT 定义两个BLE设备通过Service和Characteristic进行通信。

    GATT使用ATT协议,把Service、Characteristic以及对应的数据保存在一个查找表中,次查找表使用16bit ID作为每一项的索引。

    GATT 连接需要注意的是:GATT 连接是独占的,即一个BLE外设同时只能被一个中心设备连接,一旦外设被连接,它就会立马停止广播,这样它就对其他设备不可见了。

    一旦建立了连接,通信就是双向的了。而前边的GAP广播通信则是单向的(外设进行广播)。

    如果要让两个外设进行通信,就只能通过中心设备中转。

    3.1 GATT通信事务

    GATT通信的双方是C/S关系。

    外设作为GATT服务端(Server),维持了ATT的查找表以及service和characteristic的定义。

    中心设备是GATT客户端(Client),它向Server发起请求。

    需要注意,所有的通信事件,都是由客户端(也叫主设备,Master)发起,并且接收服务端(也叫从设备,Slave)的响应。

    一旦建立连接,外设会给中心设备建议一个连接间隔(Connection Interval),这样中心设备就会在每个间隔尝试去重新连接,检查是否有新数据。这个连接间隔只是个建议。

    下图展示一个外设(GATT服务端)和中心设备(GATT客户端)之间的数据交流流程,可以看到的是,每次都是主设备发起请求:

    3.2 GATT结构

    GATT事务是建立在嵌套的Profiles,Services和Characteristics之上的,如下所示:

    Profile

    一种规范,一种标准的通信协议。每个profile中会包含多个service服务,每个service代表该从机的一种能力。

    Service

    一种服务,也就是从机的能力。例如,蓝牙从机的电量信息服务、系统信息服务等。每个service中又包含多个characteristic特征值,每个具体的characteristic才是BLE通信的主题。

    Characteristic

    特征值,BLE主从机的通信均是通过characteristic来实现,可以理解为一个标签,通过这个标签可以获取或者写入想要的内容。

    举个例子,Heart Rate Measurement Characteristic。

    这个 Characteristic 是Heart Rate Service必须实现的,它的UUID为0x2A37。它的数据结构是,开始8bit定义心率数据格式,后边的是对应格式的实际心率数据。

    UUID

    统一标识码,service 和 characteristic 都需要一个唯一的UUID来标识。

    三. 一些常见术语概念

    1. ATT PDU(属性协议)

    认识BLE 5协议栈 —— 属性协议层 – SYQ

    在ATT层协议框架内,拥有一组属性的设备称为服务端(Server),读写该属性值的设备称为客户端(Client),Server和Client通过ATT PDU进行交互。属性协议共有6种:

    属性PDU方向触发响应
    CommandClient -> Server
    RequestClient -> ServerResponse
    ResponseServer -> Client
    NotificationServer -> Client
    IndicationServer -> ClientConfirmation
    ConfirmationClient -> Server

    解释:

    • 客户端发送Request,服务器需要返回一个Response,表明服务器收到了。
    • 服务器发送Indication,客户端需要返回一个Confirmation,表明客户端收到了。
    • 以上两种方式,均是单线程操作,即下一个Request/Indication操作需要在上一个操作收到Response/Confirmation之后才能开始。
    • 客户端发送Command,服务器无需任何返回。
    • 服务器发送Notification,客户端无需任何返回。

    2. GAP Bond管理

    gap_bond_manager_and_le_secure_connections

    GAP Bond Manager 是一个可配置模块,使用Bond Manager后应用程序可以减少大部分安全机制。

    术语描述
    配对(Pairing)交换密钥的过程
    加密(Encryption)0x02
    认证(Authentication)使用中间人(MITM)保护完成的配对
    Bonding将密钥存储在非易失性存储器中
    授权(Authorization)除了认证之外,还需要额外的应用级密钥交换
    OOB(Out of band)密钥不是通过空中交换,而是通过串行端口或NFC等其他来源进行交换。这也提供了MITM保护。
    MITM(Man in the Middle protection)这可以防止攻击者收听通过空中传输的密钥来破坏加密。
    只是工作(Just work)配对方法,其中密钥在没有MITM的情况下通过空中传送

    在程序中使用GAPBondMgr实现,过程如下:

    • 配对过程通过选择配对模式中描述的方法交换密钥。
    • 使用步骤1的密钥加密连接。
    • 绑定过程将密钥存储在安全闪存(SNV)中。
    • 重新连接时,使用存储在SNV中的密钥来加密连接。

    使用GAPBondMgr

    使用GAPBondMgr模块的一般步骤:

    1. 配置堆栈以包括GAPBondMgr功能,如果需要安全连接。

    2. 堆栈还必须配置为使用1或2个SNV界面。

    3. 如果使用安全连接,则PDU大小必须大于等于69,可用于安全连接的最小堆大小为3690。

    4. 根据需要初始化其参数来配置GAPBondMgr。

    5. 使用GAPBondMgr注册应用程序回调,以便应用程序可以与GAPBondMgr通信并通知事件。

    //Register with bond manager after starting device
    GAPBondMgr_Register(&bondmanager_callbacks);

    一旦GAPBondMgr被配置,它主要从应用程序的角度自主运行。当建立连接时,根据初始化期间设置的配置参数启动配对和绑定,并根据需要通过定义的回调与应用程序通信。

    GAPBondMgr与应用程序之间的大多数通信都是通过在步骤5中注册的回调发生的。

    GAPBondMgr与此应用程序之间的大多数通信都是通过在步骤5中注册的回调发生的。 下图是GAPBondMgr的流程图示例,通知应用程序配对已经完成。对于各种其他事件的发生也是用相同的方法,并将在后面部分进行扩展。

    四. 如何通过无线发送一个数据包

    深入浅出低功耗蓝牙(BLE)协议栈_iini的博客-CSDN博客

    下面以如何发送一个数据包为例来讲解BLE协议栈各层是如何紧密配合,以完成发送任务的。

    假设有设备A、B,A要把自己当前的电量值83%(十六进制表示为0x53)发给设备B,该怎么做呢?对开发者而言,他希望调用一个简单的API就能完成这件事,比如 send(0x53),实际上BLE协议栈就是这样设计的。开发者只需调用API,其余事情BLE协议栈帮你搞定。

    很多人会想,BLE协议栈是不是直接在物理层就把0x53发出去了,就像下图所示:

    实际不是这样的。

    首先他没有考虑用哪一个射频信道来进行传输。在不更改API的情况下,我们只能对协议栈进行分层,因此引入 LL层,开发者还是调用 send(0x53),send(0x53) 里再调用send_LL(0x53, 2402M)(注:2402M为信道频率)。

    这里还有一个问题,设备B怎么知道这个数据包是发给自己的还是其他人的,为此BLE引入access address 概念,用来指明接受者身份。(其中,0x8E89BED6这个access address比较特殊,它表示要发给周边所有设备,即广播)。如果要一对一进行通信(BLE称其为连接),即设备A的数据包只能设备B接收,同样设备B的数据包只能设备A接收,那么就必须生成一个独特的随机access address 以标识设备A和设备B两者之间的连接。

    广播方式

    我们先来看一下简单的广播情况,这种情况下,我们把设备A叫advertiser(广播者),设备B叫scanner或者observer(扫描者)。广播状态下设备A的LL层API将变成 send_LL(0x53,2402M, 0x8E89BED6)。由于设备B可以同时接收到很多设备的广播,因此数据包还必须包含设备A的device address(0xE1022AAB753B)以确认该广播包来自设备A,为此send_LL参数需要变成(0x53,2402M, 0x8E89BED6, 0xE1022AAB753B)。LL层还要检查数据的完整性,为此引入CRC24对数据包进行校验(假设为0xB2C78E)。同时为了调制解调电路工作更高效,每一个数据包的最前面会加上1个字节的帧头(一般为0x55或0xAA)。

    这样,整个空中包就变成:(空中包用小端模式表示)

    上面这个数据包还有如下问题:

    • (1)没有对数据包进行分类组织,设备B无法找到自己想要的数据0x53。为此需要在access address之后加入两个字段:LL header和长度字节。LL header用来表示数据包的LL类型,长度字节用来指明payload的长度。
    • (2)设备B什么时候开启射频窗口以接收空中数据包呢?如上图所示,只有case3的情况,通信才能成功,即设备A的数据包在空中传输时,设备B正好打开射频接收窗口,此时通信才能成功,换句话说,LL层还必须定义通信时序
    • (3)当设备B拿到数据0x53后,该如何解析这个数据呢?这就是GAP层要做的工作,GAP层引入了LTV(Length-Type-Value)结构来定义数据。比如020105, 02-长度,01-类型,05-值。由于广播包最大只能为31个字节,它能定义数据类型极其有限,像上边说的电量,GAP就没有定义,因此要通过广播方式把电量数据发出去,只能使用供应商自定义数据类型0xFF,即04FF590053(小端表示),其中04-长度,FF-自定义数据类型,0x0059-供应商ID(自定义数据中的强制字段,),0x53-数据(设备双方约定0x53就是表示电量)。

    最终,空中传输的数据包变为:(小端表示)

    AA D6BE898E 60 0E 3B75AB2A02E1 02010504FF5900 53 8EC7B2

    • AA – 前导帧(preamble)
    • D6BE898E – 访问地址(access address)
    • 60 – LL帧头字段(LL header)
    • 0E – 有效数据包长度(payload length)
    • 3B75AB2A02E1 – 广播者设备地址(advertiser address)
    • 02010504FF590053 – 广播数据
    • 8EC7B2 – CRC24值

    经过上边过程的封装(PHY、LL和GAP),就可以发送广播包了,但广播包携带的信息极其有限,而且还有如下几大限制:

    (1)无法进行一对一通信;

    (2)由于不支持组包和拆包,因此无法传输大数据;

    (3)通信不可靠。广播信道不能太多,否则将导致扫描效率低下。为此,BLE只使用37(2402MHz)、38(2426MHz)、39(2480MHz)三个信道进行广播和扫描,因此广播不支持跳频。由于广播是一对多,所以广播也无法支持ACK,这些都使得广播通信变得不可靠。

    跳频:收发双方按照同一时序不同变换频率,这个频率的变化规律一般由一个伪随机的序列控制,通信前要先同步,使得双方的序列同步。

    (4)扫描端功耗高。由于扫描端不知道设备端何时广播,也不知道设备端选用哪个频道进行广播,扫描端只能拉长扫描窗口时间,并同时对37/38/39三个通道进行扫描,这样功耗就会比较高。

    而连接则可以很好解决上述问题。

    连接方式

    所谓设备A和设备B建立蓝牙连接,就是指A、B两者“同步”成功,具体包含以下几方面:

    • A、B对接下来要使用的物理通道达成一致;
    • A、B双方建立一个共同的时间锚点,也就是说把双方的时间原点变为同一个点;
    • A、B两者时钟同步成功,即双方都知道什么时候发送/接收数据;

    连接成功后,A、B通信流程如下所示:

    如上图所示,一旦设备A和设备B连接成功(此种情况下,我们把设备A称为Master或者Central,把设备B称为Slave或者Peripheral),设备A将周期性以CI(connection interval)为间隔向设备B发送数据包,而设备B也周期性地以CI为间隔打开射频接收窗口以接收设备A的数据包。同时按照蓝牙spec要求,设备B收到设备A数据包150us后,设备B切换到发送状态,把自己的数据发给设备A;设备A则切换到接收状态,接收设备B发过来的数据。由此可见,连接状态下,设备A和设备B的射频发送和接收窗口都是周期性地有计划地开和关,而且开的时间非常短,从而大大减低系统功耗并大大提高系统效率。

    现在我们看看连接状态下是如何把数据0x53发送出去的,从中大家可以体会到蓝牙协议栈分层的妙处。

    (1)对开发者,很简单,他只需调用 send(0x53);

    (2)GATT层定义数据的类型和分组,我们假设用 0x0013 来表示电量这种数据类型,这样GATT层就把数据打包成 :130053(小端);

    (3)ATT层用来选择具体的通信命令,比如read/write/notify/indicate等,这里选择notify命令0x1B,这样数据包就变成了:1B130053;

    (4)L2CAP用来指定CI(connection interval,连接间隔),比如每10ms同步一次(CI不体现在数据包中),同时指定逻辑通道编号0004(表示ATT命令),最后把ATT数据长度0x0004加在包头,这样数据就变成:040004001B130053;

    (5)LL层要做很多事。

    ① 首先LL层需要指定用哪个物理信道进行传输(物理信道不体现在数据包中),

    ② 然后再给此连接分配一个Access address(0x50655DAB)以标识此连接只为设备A和设备B直连服务,

    ③ 然后加上LL header和payload length字段,LL header标识此packet为数据packet,而不是control packet等,payload length为整个L2CAP字段的长度,最后加上CRC24字段,以保证整个packet的数据完整性,

    所以数据包最后变成:

    AAAB5D65501E08040004001B130053D550F6

    • AA – 前导帧(preamble)
    • 0x50655DAB – 访问地址(access address)
    • 1E – LL帧头字段(LL header)
    • 08 – 有效数据包长度(payload length)
    • 04000400 – ATT数据长度,以及L2CAP通道编号
    • 1B – notify command
    • 0x0013 – 电量数据handle
    • 0x53 – 真正要发送的电量数据
    • 0xF650D5 – CRC24值

    虽然开发者只调用了 send(0x53),但由于低功耗蓝牙协议栈层层打包,最后空中实际传输的数据将变成下图所示的模样,这就既满足了低功耗蓝牙通信的需求,又让用户API变得简单,可谓一箭双雕!

    上面只是对BLE协议栈实现原理做了一个简单概述。对很多开发者来说,他们也不关心BLE协议栈是如何实现的,他们更关心的是BLE协议栈的使用,即怎么开发一个BLE应用。

    展开全文
  • Linux蓝牙协议栈OpenBT及其应用程序开发.pdf
  • 蓝牙( Bluetooth® ):是一种无线技术标准,可实现固定设备、移动设备和楼宇个人域网之间的短距离数据交换(使用2.4—2.485GHz的ISM波段的UHF无线电波)。蓝牙技术最初由电信巨头爱立信公司于1994年创制,当时是...
  • 主要介绍下蓝牙协议栈开发板跑传统蓝牙串口协议SPP AT指令以及上位机操作步骤,以及原理 一. 声明 本专栏文章我们会以连载的方式持续更新,本专栏计划更新内容如下: 第一篇:蓝牙综合介绍 ,主要介绍蓝牙的一些...
  • 不同的应用场景有不同的需求,因此不同的应用场景对蓝牙实现方案的要求也不一样,从而催生不同的蓝牙架构实现方案,或者说蓝牙协议栈方案。 架构1:host+controller双芯片标准架构 蓝牙是跟随手机而诞生的,如何在...
  • 安卓系统蓝牙协议栈 bluedroid 使能流程分析本文承接上篇文章《安卓中蓝牙系统服务层的使能流程分析》,接续分析协议栈层相关的使能流程,所以蓝牙协议栈bluedroid的使能始于JNI层enableNative()中调用协议栈接口...
  • 吐血推荐历史最全的蓝牙协议栈介绍

    万次阅读 多人点赞 2020-07-21 18:29:00
    第二篇:Transport层介绍,主要介绍蓝牙协议栈跟蓝牙芯片之前的硬件传输协议,比如基于UART的H4,H5,BCSP,基于USB的H2等 第三篇:传统蓝牙controller介绍,主要介绍传统蓝牙芯片的介绍,包括射频层(RF),基带层...
  • lwlibs 是一个小巧的蓝牙、TCPIP协议栈,本资源中包含有其源代码。另外还尝试在MTK平台上做了一次简单移植。 仅供学习。
  • 蓝牙协议栈概述

    2021-01-05 19:37:08
    蓝牙协议栈概述 蓝牙协议栈的信息源头是https://www.bluetooth.com 蓝牙协议栈的所有者是蓝牙技术联盟。它有七个公司为它的核心成员 这些公司包括爱立信,英特尔,微软,联想,东芝,三星、朗讯 蓝牙的版本发展 从...

空空如也

空空如也

1 2 3 4 5 ... 20
收藏数 11,698
精华内容 4,679
关键字:

蓝牙协议栈

友情链接: Name_ExcelToSimulink.zip