精华内容
下载资源
问答
  • ESP32 蓝牙开发资料,用于了解ESP32内部的蓝牙实现。 本⼿册为 ESP32 的蓝⽛架构简介,主要分个章节介绍了蓝⽛、经典蓝⽛和蓝⽛低功耗 ⽅⾯的整体架构。注意,本⼿册仅针对 ESP-IDF V2.1 及以下版本。 ⽬录 1. 蓝...
  • ESP32 蓝牙配网用户指南,ESP32 蓝牙配网用户指南,ESP32 蓝牙配网用户指南
  • 稍微说一下程序原理:电脑串口传输字符串给ESP32ESP32将字符串通过蓝牙天线发送给智能小车,智能小车的串口蓝牙接收到信号之后触发串口中断,在中断服务程序执行控制小车行动的逻辑,然后再返回一个相同的字符串给...
  • 基于ESP32芯片的蓝牙可视扬声器。 主要特点 A2DP音频流 I2S和PDM输入/ I2S输出 VFX输出(GIF /音频FFT / Rainbow / Starsky / ...) BLE控制界面(OTA固件更新/ VFX远程控制) 音频提示(已连接/已断开/睡眠/唤醒...
  • 关于esp32蓝牙模块的使用——esp32学习笔记 零、前言 esp32自带蓝牙模块可以让我们进行蓝牙连接并进行数据交换 一、经典蓝牙BT 首先来编写代码,经典蓝牙的使用非常简单,首先声明使用了BluetoothSerial这个库,然后...

    关于esp32蓝牙模块的使用——esp32学习笔记

    零、前言

    esp32自带蓝牙模块可以让我们进行蓝牙连接并进行数据交换

    一、经典蓝牙BT

    首先来编写代码,经典蓝牙的使用非常简单,首先声明使用了BluetoothSerial这个库,然后使用SerialBT.begin函数设置蓝牙的名字,再然后就是设置配对码,由于这里没有用到配对码,所以我将注释掉。当配对成功我们就使用printf函数打印出蓝牙配对成功。

    #include <Arduino.h>
    #include <BluetoothSerial.h>
    
    BluetoothSerial SerialBT;
    
    void setup()
    {
        Serial.begin(115200);
        SerialBT.begin("BAKUMAN"); // 如果没有参数传入则默认是蓝牙名称是: "ESP32"
        // SerialBT.setPin("1234");   // 蓝牙连接的配对码
        Serial.printf("BT initial ok and ready to pair. \r\n");
    }
    
    void loop()
    {
        if (Serial.available())
        {
            SerialBT.write(Serial.read());
        }
        if (SerialBT.available())
        {
            Serial.write(SerialBT.read());
        }
        delay(1);
    }
    

    再然后就是loop函数中:

    Serial.available()是串口中(演示中为电脑端)收到的信息,如果串口中有数据,就使用蓝牙发送出去。

    SerialBT.available()是蓝牙中(演示中为手机端)收到的信息,如果蓝牙中有数据,就发送给串口,串口就会打印出来。

    接下来就是演示的截图:

    串口发送信息:

    串口发送数据image-20211009160839765

    蓝牙发送数据:

    image-20211009160118260Screenshot_20211009_155717

    二、低功耗蓝牙BLE()

    0.前言

    UUID:ble的服务和characteristic是通过UUID来进行识别的。创建uuid可以使用这么一个网站:Online UUID Generator Tool

    notify:如果这个主机的一个特征值characteristic发生改变,就可以通过notify来告诉我们

    pCharacteristic = pService->createCharacteristic(CHARACTERISTIC_UUID_TX, BLECharacteristic::PROPERTY_NOTIFY); 
    //创建一个(读)特征值, 它是通知下发类型的特征值
    

    write:我们可以把特征值定为写入类型, 这样客户端可以给我们写入, 触发写入回调函数

    BLECharacteristic *pCharacteristic = pService->createCharacteristic(CHARACTERISTIC_UUID_RX, BLECharacteristic::PROPERTY_WRITE);
    //创建一个(写)特征, 它是写入类型的特征值
      pCharacteristic->setCallbacks(new MyCallbacks());  
    //为特征添加一个回调
    

    使用这个低功耗蓝牙有这么几个步骤:

    \1. 创建一个 BLE Server

    \2. 创建一个 BLE Service

    \3. 创建一个 BLE Characteristic

    \4. 创建一个 BLE Descriptor

    \5. 开始服务

    \6. 开始广播

    并且我们还需要使用BLEDevice,BLEServer,BLEUtils,BLE2902,common这几个库

    1.首先我们来做一些准备工作

    /*
        Video: https://www.youtube.com/watch?v=oCMOYS71NIU
        Based on Neil Kolban example for IDF: https://github.com/nkolban/esp32-snippets/blob/master/cpp_utils/tests/BLE%20Tests/SampleNotify.cpp
        Ported to Arduino ESP32 by Evandro Copercini
    
       Create a BLE server that, once we receive a connection, will send periodic notifications.
       创建一个BLE服务器,一旦我们收到连接,将会周期性发送通知
    
       T使用步骤:
       1. 创建一个 BLE Server
       2. 创建一个 BLE Service
       3. 创建一个 BLE Characteristic
       4. 创建一个 BLE Descriptor
       5. 开始服务
       6. 开始广播
    
    
    */
    #include <Arduino.h>
    #include <BLEDevice.h>
    #include <BLEServer.h>
    #include <BLEUtils.h>
    #include <BLE2902.h>
    #include "common.h"
    
    uint8_t txValue = 0;                         //后面需要发送的值
    BLEServer *pServer = NULL;                   //BLEServer指针 pServer
    BLECharacteristic *pTxCharacteristic = NULL; //BLECharacteristic指针 pTxCharacteristic
    bool deviceConnected = false;                //本次连接状态
    bool oldDeviceConnected = false;             //上次连接状态
    
    // See the following for generating UUIDs: https://www.uuidgenerator.net/
    #define SERVICE_UUID "12a59900-17cc-11ec-9621-0242ac130002" // UART service UUID
    #define CHARACTERISTIC_UUID_RX "12a59e0a-17cc-11ec-9621-0242ac130002"
    #define CHARACTERISTIC_UUID_TX "12a5a148-17cc-11ec-9621-0242ac130002"//注意这里面是用
    
    class MyServerCallbacks : public BLEServerCallbacks
    {
        void onConnect(BLEServer *pServer)
        {
            deviceConnected = true;
        };
    
        void onDisconnect(BLEServer *pServer)
        {
            deviceConnected = false;
        }
    };
    
    class MyCallbacks : public BLECharacteristicCallbacks
    {
        void onWrite(BLECharacteristic *pCharacteristic)
        {
            std::string rxValue = pCharacteristic->getValue(); //接收信息
    
            if (rxValue.length() > 0)
            { //向串口输出收到的值
                Serial.print("RX: ");
                for (int i = 0; i < rxValue.length(); i++)
                    Serial.print(rxValue[i]);
                Serial.println();
            }
        }
    };
    
    

    2.创建一个BLE设备

    	BLEDevice::init(ble_name);
    

    3.创建一个BLE服务

        pServer = BLEDevice::createServer();
        pServer->setCallbacks(new MyServerCallbacks()); //设置回调
        BLEService *pService = pServer->createService(SERVICE_UUID);
    

    在这里面的回调函数就是上面代码串中的MyServerCallbacks

    4.创建一个BLE特征

    pTxCharacteristic = pService->createCharacteristic(CHARACTERISTIC_UUID_TX, BLECharacteristic::PROPERTY_NOTIFY);
    pTxCharacteristic->addDescriptor(new BLE2902());
    BLECharacteristic *pRxCharacteristic = pService->createCharacteristic(CHARACTERISTIC_UUID_RX, BLECharacteristic::PROPERTY_WRITE);
    pRxCharacteristic->setCallbacks(new MyCallbacks()); //设置回调
    

    这里创建了一个特征值,类型是通知。

    在后面使用createCharacteristic(CHARACTERISTIC_UUID_RX, BLECharacteristic::PROPERTY_WRITE)将rx收集到的信息写入,通过MyCallbacks再打印出来

    为了实现"串口",我们在这个服务下添加了两个特征值, 一个是TX. 一个是RX.另外还需注意三个uuid因该是相对应的值。

    5.开始服务和广播

        pService->start();                  // 开始服务
        pServer->getAdvertising()->start(); // 开始广播
        Serial.println(" 等待一个客户端连接,且发送通知... ");
    

    6.接下来呢在loop函数中我们的蓝牙连接会对应三种状态:

    (1)设备已经连接:

    if (deviceConnected){
            pTxCharacteristic->setValue(&txValue, 1); // 设置要发送的值为1
            pTxCharacteristic->notify();              // 广播
            txValue++;                                // 指针数值自加1
            delay(2000);                              // 如果有太多包要发送,蓝牙会堵塞
        }
    

    这里面测试的是不停发送数据,每发一次数值加一。

    此时

    class MyServerCallbacks : public BLEServerCallbacks
    {
        void onConnect(BLEServer *pServer)
        {
            deviceConnected = true;
        };
    
        void onDisconnect(BLEServer *pServer)
        {
            deviceConnected = false;
        }
    };
    

    通过这个回调可以说清楚这个蓝牙到底有没有连接,如果连接了通过

    pServer->setCallbacks(new MyServerCallbacks());
    

    这个MyServerCallbacks回调就可以返回连接状态。

    调式结果如图:

    ble串口:

    ble串口接收image-20211009210430190

    ble开始广播

    ble开始广播

    最后附上整段代码:

    /*
        Video: https://www.youtube.com/watch?v=oCMOYS71NIU
        Based on Neil Kolban example for IDF: https://github.com/nkolban/esp32-snippets/blob/master/cpp_utils/tests/BLE%20Tests/SampleNotify.cpp
        Ported to Arduino ESP32 by Evandro Copercini
    
       Create a BLE server that, once we receive a connection, will send periodic notifications.
       创建一个BLE服务器,一旦我们收到连接,将会周期性发送通知
    
       T使用步骤:
       1. 创建一个 BLE Server
       2. 创建一个 BLE Service
       3. 创建一个 BLE Characteristic
       4. 创建一个 BLE Descriptor
       5. 开始服务
       6. 开始广播
    
    
    */
    #include <Arduino.h>
    #include <BLEDevice.h>
    #include <BLEServer.h>
    #include <BLEUtils.h>
    #include <BLE2902.h>
    #include "common.h"
    
    uint8_t txValue = 0;                         //后面需要发送的值
    BLEServer *pServer = NULL;                   //BLEServer指针 pServer
    BLECharacteristic *pTxCharacteristic = NULL; //BLECharacteristic指针 pTxCharacteristic
    bool deviceConnected = false;                //本次连接状态
    bool oldDeviceConnected = false;             //上次连接状态d
    // See the following for generating UUIDs: https://www.uuidgenerator.net/
    #define SERVICE_UUID "12a59900-17cc-11ec-9621-0242ac130002" // UART service UUID
    #define CHARACTERISTIC_UUID_RX "12a59e0a-17cc-11ec-9621-0242ac130002"
    #define CHARACTERISTIC_UUID_TX "12a5a148-17cc-11ec-9621-0242ac130002"
    
    class MyServerCallbacks : public BLEServerCallbacks
    {
        void onConnect(BLEServer *pServer)
        {
            deviceConnected = true;
        };
    
        void onDisconnect(BLEServer *pServer)
        {
            deviceConnected = false;
        }
    };
    
    class MyCallbacks : public BLECharacteristicCallbacks
    {
        void onWrite(BLECharacteristic *pCharacteristic)
        {
            std::string rxValue = pCharacteristic->getValue(); //接收信息
    
            if (rxValue.length() > 0)
            { //向串口输出收到的值
                Serial.print("RX: ");
                for (int i = 0; i < rxValue.length(); i++)
                    Serial.print(rxValue[i]);
                Serial.println();
            }
        }
    };
    
    void setup()
    {
        Serial.begin(115200);
    
        // 创建一个 BLE 设备
        BLEDevice::init("BAKUMAN");//在这里面是ble的名称
    
        // 创建一个 BLE 服务
        pServer = BLEDevice::createServer();
        pServer->setCallbacks(new MyServerCallbacks()); //设置回调
        BLEService *pService = pServer->createService(SERVICE_UUID);
    
        // 创建一个 BLE 特征
        pTxCharacteristic = pService->createCharacteristic(CHARACTERISTIC_UUID_TX, BLECharacteristic::PROPERTY_NOTIFY);
        pTxCharacteristic->addDescriptor(new BLE2902());
        BLECharacteristic *pRxCharacteristic = pService->createCharacteristic(CHARACTERISTIC_UUID_RX, BLECharacteristic::PROPERTY_WRITE);
        pRxCharacteristic->setCallbacks(new MyCallbacks()); //设置回调
    
        pService->start();                  // 开始服务
        pServer->getAdvertising()->start(); // 开始广播
        Serial.println(" 等待一个客户端连接,且发送通知... ");
    }
    
    void loop()
    {
        // deviceConnected 已连接
        if (deviceConnected)
        {
            pTxCharacteristic->setValue(&txValue, 1); // 设置要发送的值为1
            pTxCharacteristic->notify();              // 广播
            txValue++;                                // 指针数值自加1
            delay(2000);                              // 如果有太多包要发送,蓝牙会堵塞
        }
    
        // disconnecting  断开连接
        if (!deviceConnected && oldDeviceConnected)
        {
            delay(500);                  // 留时间给蓝牙缓冲
            pServer->startAdvertising(); // 重新广播
            Serial.println(" 开始广播 ");
            oldDeviceConnected = deviceConnected;
        }
    
        // connecting  正在连接
        if (deviceConnected && !oldDeviceConnected)
        {
            // do stuff here on connecting
            oldDeviceConnected = deviceConnected;
        }
    }
    
    展开全文
  • ESP32-BLE-Mouse:用于ESP32蓝牙LE鼠标库(兼容Arduino IDE)
  • ESP32 蓝牙开发

    2021-08-02 11:03:41
    1. 低功耗蓝牙(BLE)协议栈 低功耗蓝牙协议是蓝牙通信协议的一种,BLE协议栈就是实现低功耗蓝牙协议的代码 1.1 层次协议 蓝牙协议规定了两个层次的协议,分别为蓝牙核心协议(Bluetooth Core)和蓝牙应用层...

    1. 低功耗蓝牙(BLE)协议栈

    低功耗蓝牙协议是蓝牙通信协议的一种,BLE协议栈就是实现低功耗蓝牙协议的代码

    1.1 层次协议

    蓝牙协议规定了两个层次的协议,分别为蓝牙核心协议(Bluetooth Core)蓝牙应用层协议(Bluetooth Application)

    蓝牙核心协议就是对蓝牙技术本身的规范,不涉及其应用方式

    蓝牙应用层协议是在蓝牙核心协议的基础上,根据具体的应用需求定义出的特定策略

    蓝牙协议栈框图如下所示:

    1.2 蓝牙核心协议(Bluetooth Core)


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

    Controller:负责定义RF、Baseband等偏硬件的规范

    Host:负责在逻辑链路的基础上,进行更为友好的封装,这样就可以屏蔽掉蓝牙技术的细节,让Bluetooth Application更为方便的使用

    Controller是工作在物理层、数据链路层、网络层、传输层的协议,Host则是工作在传输层 、会话层、表示层、应用层的协议,Host将Controller封装成可被配置为函数的形式供程序使用

    包含的层次简介

    • 物理层(PHY)

            用于指定BLE所用的无线频段、调制解调方式和方法等

            BLE工作在1Mbps自适应跳频的GFSK射频,免于许可证的2.4GHz ISM(工业、课学、医疗)频段

            可以直观理解为规定了BLE的天线部分

    •  链路层(LL—Link Layer)

            BLE协议栈的核心

            相当于TCP/IP协议中的数据链路层(负责选择哪个射频通道,管理当前链路)+网络层(负责识别和发送空中数据包)+传输层(负责保证数据包安全、完整的发送、接收、重传等)

    • 主机控制接口层(HCI—Host Controller Interface)

            这一层是可选的,HCI主要用于2颗IC实现BLE协议栈的场合,用于贵方两者的通信协议和通信命令等

    1.3 蓝牙应用协议(Bluetooth Application)

    包含的层次简介

    • 通用访问配置文件层(GAP——Generic access profile)

            实际配置中常接触到的一层

            GAP是对LL层有效数据包(payload)进行解析的两种方式中最简单的一种,主要用于广播、扫描、发起连接这些具体行为

    • 逻辑链路控制及自适应协议层(L2CAP——Logical Link Control and Adaptation Protocol)

            这一层是对LL的简单封装,在L2CAP中区分出是使用加密通道还是普通通道,同时负责连接间隔的管理

    • 安全管理层(SM——Security Manager)

            负责管理BLE连接的加密、安全,需要兼顾安全性和用户使用的便利性

    • 属性协议层(ATT——Attribute protocol)

            实际配置中最常接触到的一层

            负责定义用户命令和命令操作的数据(如读写数据等)

            详细内容见后文【BLE的两种模式】和【ATT简述】

    • 通用属性配置文件层(GATT——Generic Attribute profile)

            实际配置中常接触到的一层

            用于规范attribute中的数据内容(attribute见后文【BLE的两种模式】),并使用分组(group)来对attribute进行分类管理

            一般地,BLE在没有GATT的情况下也能跑,只不过互联互通会出现问题

            BLE需要在GATT和各种应用profile的支持下才能实现最便捷高效稳定的通信

    2. BT与BLE的区别


    当前的蓝牙协议分为基础率/增强数据率(BR/EDR)和低耗能(LE)两种技术类型

    经典蓝牙统称BT,低功耗蓝牙称为BLE

    2.1 经典蓝牙模块(BT)

    泛指支持蓝牙协议在4.0以下的模块,一般用于数据量比较大的传输。

    经典蓝牙模块可再细分为:传统蓝牙模块和高速蓝牙模块。

    传统蓝牙模块在2004年推出,主要代表是支持蓝牙2.1协议的模块,在智能手机爆发的

    时期得到广泛支持。

    高速蓝牙模块在2009年推出,速率提高到约24Mbps,是传统蓝牙模块的八倍。

    2.2 低功耗蓝牙模块(BLE)

    指支持蓝牙协议4.0或更高的模块,也称为BLE模块(Bluetooth Low Energy Module),最大的特点是成功和功耗的降低。

    蓝牙低功耗技术采用可变连接时间间隔,这个间隔根据具体应用可以设置为几毫秒到几秒不等。

    另外,因为BLE技术采用非常快速的连接方式,因此可以处于“非连接”状态(节省能源),此时链路两端相互间仅能知晓对方,必要时可以才开启链路,然后在尽可能短的时间内关闭链路。

    其他分类
    按用途来分:蓝牙模块有数据蓝牙模块,语音蓝牙模块,串口蓝牙模块和车载蓝牙模块

    按芯片设计来分:蓝牙模块有flash版本和ROM版本。前者一般是BGA封装(球栅阵列封装),外置flash;后者一般是LCC封装(表面贴装型封装),外接EEPROM。

    3. BLE的两种模式


    3.1 客户端 Client

    1. 请求数据服务
    2. 客户端可以主动搜索并连接附近的服务端
    3. 客户端类似蹭网的

    3.2 服务端Server

    1. 提供数据服务
    2. 服务端不需要进行主动设置,只要开启广播就可以让附近的客户端搜索到,并提供连接
    3. 服务端类似被蹭网的wifi

    如果想要让ESP处于别人随时可以搜索连接的情况要配置为服务端;如果想让ESP通过扫描连接周围可连接的蓝牙设备,需要把它设置成客户端,正好和WiFi模式的设定相反

    Server通过characteristic对数据进行封装,多个characteristic组成一个Service——Server是一个基本的BLE应用,如果某个Service是一个蓝牙联盟定义的标准服务,也可以称其为profile

    要具体了解这些内容需要先了解属性协议层ATT

    4. ATT简述


    属性协议层ATT(Attribute Protocol)是GATT和GAP的基础,它定义了BLE协议栈上层的数据结构和组织方式;在层内,它定义了属性(Attribute)的内容,规定了访问属性的方法和权限

    属性是一个数据结构,它包括了数据类型和数据值,可以像C语言的结构体那样构造

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

    4.1 属性的种类和分组

    属性大致可以分为三种类型:服务项Profile、特征值Characteristic和描述符Descriptor

    最顶级为Profile, 下面是多个服务项(Service), 服务项下面是多个特征值(Characteristic), 特征值下面是多个描述符(Descriptor)

    每个设备都包含以下必要的特征值和服务项:

    PROFILE

    Generic Access Service(Primary Service)
    Device Name(Characteristic)
    Appearance(Characteristic)
    Generic Attribute Service(Primary Service)
    Service Changed(Characteristic)
    CCCD(Descriptor)

    4.2 服务项Service

     服务项这种类型本身并不包含数据,仅仅相当于是一个容器,用来容纳特征值

    特征值characteristic

    特征值用于保存用户数据,但它也有自己的UUID——可以把它看作一个变量,变量里存着数据(用户数据),也有自己的地址信息(UUID)

    使用特征值时,也要遵循“先声明再赋值”的步骤——先声明特征值自身,再声明它的项

    一个characteristic包含三种条目:

    1. characteristic声明:每个characteristic的分界符,解析时一旦遇到一个声明,就可以认为接下来又是一个新的characteristic;声明还包含了接下来characteristic值的读写属性等
    2. characteristic值:数据的值
    3. characteristic描述符:数据的额外信息

    一般BLE的属性体系在系统中以GattDB表示,即属性数据库,gattDB是BLE协议栈在内存中开辟的一段专有区域,会在特定的时候写入Flash进行保存,并在启动时读取出来回写到内存中去,但并非所有的BLE数据通信是操作gattDB

    characteristic用Attribute数据结构来实现

    4.3 属性Attribute的数据结构

    Attribute由四部分组成:

    属性句柄Attribute handle:可以视为指向属性实体的指针,对端设备通过属性句柄来访问某个属性,大小2字节,起始于0x0001,系统初始化时,各属性的句柄逐步+1,但最大不超过0xFFFF

    属性类型Attribute type:用以区分当前属性是服务项或是特征值等,用通用唯一识别码(UUID)标识的16字节十六进制字符串(形如f6257d37-34e5-41dd-8f40-e308210498b4,从网上抄来的示例,如有雷同那就是雷同)表示。一个合法的UUID,一定是随机的、全球唯一的,不应该出现两个相同的UUID。属性类型分类如下:

    1. 首要服务项Primary Service
    2. 次要服务项Secondary Service
    3. 包含服务项Include
    4. 特征值Characteristic

    他们与UUID的映射关系如下:

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

    为了减少传输的数据量,BLE协议做了一个转换约定,给定一个固定的16字节模板,只设置2个字节为变化量,其他为常量,2字节的UUID在系统内部会被替换,进而转换成标准的16字节UUID;反之,如果一个特征值的UUID是16字节的,在系统内部它的属性类型也可能写成第3、4字节组成的双字节

    示例如下:

    UUID模板为

    0000XXXX-0000-1000-8000-00805F9B34FB1

    其中从左数第3、4个字节“XXXX”就是变化位,其他为固定位。如:UUID=0x2A00在系统内部会转换成00002A00-0000-1000-8000-00805F9B34FB。

    属性值Attribute value:真正的数据值,大小为0-512字节。如果该属性是服务项类型或者是特征值声明类型,那么它的属性值就是UUID等信息;如果是普通的特征值,则属性值是用户的数据,属性值需要预留空间以保存用户数据,可以将属性值的预留空间看做I2C的数据空间,操作特征值里的用户数据,就是对那块内存空间进行读写,所以启用蓝牙后会占用额外的内存

    属性权限Attribute permissions:Attribute的权限属性,主要有四种:

    1. 访问权限(Access Permission):只读或只写或读写
    2. 加密权限(Encryption Permission):加密或不加密
    3. 认证权限(Authentication Permission):需要认证或无需认证。指相互确认对方身份,BLE中所说的“认证”过程就是设备配对
    4. 授权权限(Authorization Permission):需要授权或无需授权。指对授信设备开放权利

    授权的管控等级要高于认证,认证的设备未必被授权,授权的设备一定是认证的——认证是授权的充分不必要条件。认证是设备配对,两边都符合协议规定就行,但是授权取决于Server设备对Client设备的主动许可。

    一个没有经过认证的设备,被称为未知设备(Unknown Device);经过了认证,该设备会在绑定信息中被标记为Untrusted,被称为不可信设备(Untrusted Device);经过了认证,并且在绑定信息中被标记为Trusted的设备被称为可信设备(Trusted Device)

    具体的权限示例如下所示:

    1. Open(随意读写)
    2. No Access(禁止读写)
    3. Authentication(需要配对才能读写,分成很多子类型用于适配配对的类型)
    4. Authorization(允许应用在回调函数中读写)
    5. Signed(签名后才能随意读写)


    4.4 属性协议ATT PDU

    拥有一组属性的设备称为服务端(Server);读写该属性值的设备称为客户端(Client)

    Client和Server之间通过ATT PDU通信

    属性协议ATT PDU共有6种,如下表所示: 

    ATT PDU种类发送方向触发响应说明
    CommandClient -> Server客户端发送Command,服务器无需任何返回
    RequestClient -> ServerResponse客户端发送Request,服务器需要返回一个Response,表明服务器收到
    ResponseServer -> Client
    NotificationServer -> Client服务器发送Notification,户端无需任何返回
    IndicationServer -> ClientConfirmation服务器发送Indication,客户端需要返回一个Confirmation,表明客户端收到
    ConfirmationClient -> Server

    BLE下,所有命令都是“必达”的,每个命令发送完毕后,发送者会等待ACK信息(类似I2C),如果收到了ACK包,发起方认为命令完成;否则发起方会一直重传该命令直到超时导致BLE连接断开(类似CAN的出错重发机制),可以说只要数据包放到了协议栈射频FIFO中,蓝牙协议栈就能保证该数据包“必达”对方,但是没有回复相对有回复就是“不太可靠”,这时候就需要特殊的“有回复属性”

    4.5 Request后缀

    特别地,如果一个命令需要response,那么可以在相应命令后面加上request后缀,这个response包在应用层有回调事件,可以用于触发特殊的功能,这是默认的协议ACK恢复不具有的,采用request/response方式,应用层可以按顺序地发送一些数据包;如果一个命令只需要ACK而不需要response,那么它的后面就不会带request

    然而Request/response会大大降低通信的吞吐率,因为request/response必须在不同的连接间隔中出现,这就导致两个连接间隔最多只能发一个数据包,而不带request后缀的ATT命令就没有这个问题——一般情况下,在同一个连接间隔中可以同时发多个数据包,这样将大大提高数据的吞吐率

    常用的带request命令:所有read命令,writerequest,indication等

    常用的不带request命令:write command,notification等

    5. 通用属性协议GATT简述

    GATT(Generic Attribute Profile),描述了一种使用ATT的服务框架。该框架定义了服务(Server)和服务属性(characteristic)的过程(Procedure)及格式,负责处理具体数据段通过蓝牙连接的发送和接收

    现在的BLE大多建立在GATT协议之上,GATT建立在ATT和L2CAP之上,GATT需要使用通用访问协议GAP来确定设备的连接

    5.1 通用访问协议GAP

    GAP 使设备被其他设备可见,并决定了当前设备是否可以或者怎样与合同设备进行交互

    GAP中,设备被分为外围设备Peripheral和中心设备Central

    外围设备:性能相对较弱、功耗相对低的设备,他们通常被连接到更加强大的中心设备

    中心设备:性能相对较强、功耗较高的设备

    GAP广播
    GAP 中外围设备不停向外广播以让中心设备知道它的存在。通过两种方式向外广播数据:

    广播数据(Advertising Data Payload):必须的,外设需要以此来和中心设备取得连接

    扫描回复(Scan Response Data Payload):可选的,中心设备可以向外围设备请求扫描回复,向其提供一些设备的额外信息

    外围设备会设定一个广播间隔,每个间隔中,它会重新发送自己的广播数据,广播间隔越长约省电,但同时更不容易被扫描到

    基于GATT广播的BLE连接只能是一个外围设备连接一个中心设备,可以理解成一个蓝牙耳机只能连接一台手机,不能同时连接两台手机

    5.2 GATT协议

    GATT协议建立在ATT协议的基础上。将ATT协议中的Service、Characteristic 及对应数据都保存在一个查找表中,查找表使用16位的ID作为索引。建立GATT连接前必须先经过GAP协议

    GATT连接是独占的,也就是说同一个BLE外设(外部设备)同时只能被一个中心设备连接,一旦外设被连接,它就会停止GAP广播,对其它设备不可见;当设备断开时它又开始广播。

    如果中心设备和外设需要双向通信,唯一的方式就是建立GATT连接,GAP通信是单向的,只能让中心设备向外设发送信息

    GATT通信双方是C/S关系:

    外设作为GATT的Server,维持ATT查找表、Service、Characteristic定义;

    中心设备作为GATT的Client,向Server发起请求,所有通信事件都由中心设备Client发起,从Server接收响应。一旦连接建立,外设将会给中心设备建议一个连接间隔,中心设备可以选择在每个连接间隔尝试重新连接,检查是否有新数据,不过这个间隔只是建议,中心设备可以不严格按照这个间隔执行请求。

    5.3 GATT结构

    GATT结构建立在ATT的属性Attribute数据结构之上(其实和ATT的那些东西一模一样

    Attribute结构体组成种类不同的Characteristic,多个Characteristic被封装在Servce容器中,Characteristic和Service容器都有着自己的UUID(有官方认证的16位UUID和自定义的128位UUID),各种常用的Service集合成Profile

    BLE外设的通信主要通过Characteristic,通过在Characteristic中读写数据就实现了双向通信,也可以通过实现类似串口的Service来配置TxCharacteristic和RxCharacteristic,这些都是具体项目的选择了

    6. BLE从初始化到建立连接的过程简述

    1. 外围设备开始广播,发送完一个广播包后T_IFS,开启射频Rx窗口接收来自中心设备的数据包
    2. 中心设备扫描到广播,在收取此广播T_IFS后如果开启了中心设备的扫描回复,中心设备将向外设发送回复
    3. 外设收取到中心设备的回复,做好接收准备,并返回ACK包
    4. 如果ACK包未被中心设备接收到,中心设备将一直发送回复直到超时,此期间内只要外设返回过一次ACK包就算连接成功
    5. 开始建立通信,后续中心设备将以收取到外设广播的时间为原点,以Connection Interval为周期向外设发送数据包,数据包将具有两个作用:同步两设备时钟和建立主从模式通信

    外设每收到中心设备的一个包,就会把自己的时序原点重新设置,以和中心设备同步(Service向Client同步)

    BLE通信在建立成功后变为主从模式,中心设备Central变为Master,外设Peripheral变为Slave,Slave只能在Master向它发送了一个包以后才能在规定的时间内把自己的数据回传给Master

    6. 连接建立成功
    7. 外设自动停止广播,其他设备无法再查找到该外设
    8. 按照以下时序进行通信,在中心设备发送包的间隔内,外设可以发送多个包


    9. 需要连接断开时只需要中心设备停止连接(停止发送包)即可
    10. 中心设备可以将外设的addr写入Flash或SRAM等存储器件,保持监听此addr,当再次收到外设广播时就可以建立通信。BLE Server设备为了省电,当一段时间内没有数据要发送时,可以不再发送包,双方就会因为连接超时(connection timeout)断开,这时需要中心设备启动监听,这样,当BLE Server设备需要发送数据时,就可以再次连接 

    7. ESP的蓝牙外设配置

    蓝牙配置相关库函数
     

    7.1 相关头文件及其作用

    #include "bt.h"//蓝牙控制器和VHCI设置头文件
    #include "esp_gap_ble_api.h"//GAP设置头文件,广播和连接相关参数配置
    #include "esp_gatts_api.h"//GATT配置头文件,创建Service和Characteristic
    #include "esp_bt_main.h"//蓝牙栈空间的初始化头文件


    7.2 蓝牙控制器

    使用esp_bt_controller_init()

    esp_bt_controller_init(esp_bt_controller_config_t *cfg);//esp_bt_controller_config_t是蓝牙控制器配置结构体
    
    struct esp_bt_controller_config_t
    {
        uint16_t controller_task_stack_size;//蓝牙控制器栈大小
        uint8_t controller_task_prio;//蓝牙控制器任务优先级
        uint8_t hci_uart_no;//使用哪个UART作为HCI的IO,仅能选择UART1或UART2串口
        uint32_t hci_uart_baudrate;//HCI串口波特率
        uint8_t scan_duplicate_mode;//重复扫描模式
        uint8_t scan_duplicate_type;//重复扫描类型
        uint16_t normal_adv_size;//普通广播报文大小
        uint16_t mesh_adv_size;//mesh广播报文大小
        uint16_t send_adv_reserved_size;//蓝牙控制器最小的内存大小(保留出发送报文所需的内存大小)
        uint32_t controller_debug_flag;//蓝牙控制器debug log的属性
        uint8_t mode;//BR/EDR/BLE/Dual模式选择
        uint8_t ble_max_conn;//BLE模式最多连接个数
        uint8_t bt_max_acl_conn;//BR或EDR最大的ACL连接个数
        uint8_t bt_sco_datapath;//SCO数据路径 用于HCI或PCM模块
        bool auto_latency;//BLE自动延迟,用于降低传统蓝牙的功耗
        bool bt_legacy_auth_vs_evt;//BR/EDR传统的授权完毕事件,用于防止BIAS攻击
        uint8_t bt_max_sync_conn;//BR/EDR最多的ACL连接数目,也可以在menuconfig中配配置
        uint8_t ble_sca;//BLE晶振准确度指数
        uint8_t pcm_role;//PCM角色,选择master或slave
        uint8_t pcm_polar;//PCM触发极性,选择下降沿或上升沿
        uint32_t magic;//神奇数字
    }

    初始化蓝牙控制器,此函数只能被调用一次,且必须在其他蓝牙功能被调用之前调用

    使用esp_bt_controller_deinit()来取消初始化,用于关闭蓝牙并清除其占用的内存,还会将蓝牙任务删除

    下面是蓝牙控制器的常用API

    esp_bt_controller_enable(esp_bt_mode_t mode);//使能蓝牙控制器,mode是蓝牙模式,如果想要动态改变蓝牙模式不能直接调用该函数,应该先用下面的disable关闭蓝牙再使用该API来改变蓝牙模式
    esp_bt_controller_disable(void);//关闭蓝牙控制器
    sp_bt_controller_get_status(void);//获取蓝牙控制器状态
    esp_bt_get_mac(void);//获取蓝牙MAC地址
    
    esp_bt_controller_mem_release(esp_bt_mode_t mode);//释放蓝牙控制器的所有内存,包括BSS、数据和其他蓝牙使用的堆栈空间
    //这个API仅仅应该再esp_bt_controller_init()或after esp_bt_controller_deinit()之前被调用
    esp_bt_mem_release(esp_bt_mode_t mode);
    //释放蓝牙控制器和蓝牙数据的所有内存,比esp_bt_controller_mem_release()更彻底
    esp_bt_sleep_enable(void);//让蓝牙进入睡眠模式,这个函数应该在esp_bt_controller_enable()被调用之后再调用

    特别地,官方文档中给出了一套在线升级蓝牙设备软件时的关闭流程

    esp_bluedroid_disable();
    esp_bluedroid_deinit();
    esp_bt_controller_disable();
    esp_bt_controller_deinit();
    esp_bt_mem_release(ESP_BT_MODE_BTDM);


    7.3 经典蓝牙

    用于蓝牙运行的API如下所示

    esp_bluedroid_get_status(void);//获取蓝牙当前状态
    //可能的状态如下所示
    ESP_BLUEDROID_STATUS_UNINITIALIZED==0//未初始化
    ESP_BLUEDROID_STATUS_INITIALIZED//已被初始化但是未开启
    ESP_BLUEDROID_STATUS_ENABLED//初始化并开启
        
    esp_bluedroid_enable(void);//使能蓝牙
    esp_bluedroid_disable(void);//关闭蓝牙
    esp_bluedroid_init(void);//初始化蓝牙并分配系统资源,它应该被第一个调用
    esp_bluedroid_deinit(void);//取消初始化蓝牙并将系统资源释放,用于蓝牙结束工作后的收尾


    用于设备蓝牙配置的API如下所示

    esp_bt_dev_get_address(void);//获取当前设备蓝牙地址
    esp_bt_dev_set_device_name(const char *name);//设置设备名

    这些函数都应该在蓝牙启用后被调用

    7.4 BLE-GAP相关库函数

    外围设备库函数

    esp_ble_gap_start_advertising(esp_ble_adv_params_t *adv_params);//开始广播
    esp_ble_gap_stop_advertising(void);//停止广播
    
    esp_ble_gap_config_adv_data(esp_ble_adv_data_t *adv_data);//广播数据参数设置
    //adv_data数据结构如下
    bool set_scan_rsp//设置是否需要扫描response
    bool include_name//广播内容是否包括设备名
    bool include_txpower//广播数据是否包括发射功率
    int min_interval//最小广播时间间隔
    //计算公式:connIntervalmin = Conn_Interval_Min * 1.25 ms
    //Conn_Interval_Min在0x0006到0x0C80之间,0xFFFF就是没有特定的最小值
    int max_interval//最大广播间隔
    //计算公式:connIntervalmax = Conn_Interval_Max * 1.25 ms
    //Conn_Interval_Max在0x0006到0x0C80之间,Conn_Interval_Max应大于等于Conn_Interval_Min
    //0xFFFF代表没有特定的最大值
    int appearance//设备外形(External appearance)
    uint16_t manufacturer_len//生产商数据长度
    uint8_t *p_manufacturer_data//生产商数据指针
    uint16_t service_data_len//Service数据长度
    uint8_t *p_service_data//Service数据指针
    uint16_t service_uuid_len//Service的UUID长度
    uint8_t *p_service_uuid//Service的UUID数组指针
    uint8_t flag//广播属性(flag)
        
    esp_ble_gap_config_adv_data_raw(uint8_t *raw_data, uint32_t raw_data_len);//设置空的广播数据包,用户需要自行设置包的内容


    中心设备库函数

    esp_ble_gap_start_scanning(uint32_t duration);//使用该函数让设备扫描附近正在广播的外设,duration为扫描间隔
    esp_ble_gap_stop_scanning(void);//停止扫描
    
    esp_ble_gap_set_scan_params(esp_ble_scan_params_t *scan_params);//设置扫描参数
    esp_ble_gap_register_callback(esp_gap_ble_cb_t callback)//间隔回调函数
        
    esp_ble_gap_set_pkt_data_len(esp_bd_addr_t remote_device, uint16_t tx_data_length);//设置最大数据包大小
    
    esp_ble_gap_set_prefer_conn_params(esp_bd_addr_t bd_addr, uint16_t min_conn_int, uint16_t max_conn_int, uint16_t slave_latency, uint16_t supervision_tout);//设置当默认连接参数无法使用时的优先连接参数,这个库函数只能用在中心设备master上
    
    esp_ble_gap_config_scan_rsp_data_raw(uint8_t *raw_data, uint32_t raw_data_len);//设置空的response数据包,用户需要自行设置数据
    
    esp_ble_gap_read_rssi(esp_bd_addr_t remote_addr);//读取远程设备的RSSI,结果会在间隔回调函数中随ESP_GAP_BLE_READ_RSSI_COMPLETE_EVT事件返回

    连接配置库函数

    esp_ble_gap_update_conn_params(esp_ble_conn_update_params_t *params);//在连接建立后更新连接参数
    esp_ble_gap_clear_rand_addr(void);//清空应用的随机地址
    
    esp_ble_gap_update_whitelist(bool add_remove, esp_bd_addr_t remote_bda, esp_ble_wl_addr_type_t wl_addr_type);//新建或移除白名单中的设备
    esp_ble_gap_get_whitelist_size(uint16_t *length);//获取白名单的大小
    
    esp_ble_gap_set_device_name(const char *name);//设置本机设备名
    esp_ble_gap_get_local_used_addr(esp_bd_addr_t local_used_addr, uint8_t *addr_type);//获取本机设备地址

    7.5 GATT Server的配置

    Server-Master
    基本设置

     

    esp_err_t ret;//用于debug
    esp_bt_controller_config_t bt_cfg = BT_CONTROLLER_INIT_CONFIG_DEFAULT();//设置蓝牙为默认参数
    
    ret = nvs_flash_init();//初始化NVS
    if (ret == ESP_ERR_NVS_NO_FREE_PAGES || ret == ESP_ERR_NVS_NEW_VERSION_FOUND)
    {
        ESP_ERROR_CHECK(nvs_flash_erase());
        ret = nvs_flash_init();
    }
    ESP_ERROR_CHECK(ret);
    ESP_LOGI(TAG, "%s init NVS finished\n", __func__);
    
    ESP_ERROR_CHECK(esp_bt_controller_mem_release(ESP_BT_MODE_BLE));//释放蓝牙所需空间
    
    ret = esp_bt_controller_init(&bt_cfg);//初始化蓝牙控制器
    if (ret)
    {
        ESP_LOGE(TAG, "%s enable controller failed: %s\n", __func__, esp_err_to_name(ret));
        return;
    }
    ret = esp_bt_controller_enable(ESP_BT_MODE_BLE);//使能蓝牙控制器
    if (ret)
    {
        ESP_LOGE(TAG, "%s enable controller failed: %s\n", __func__, esp_err_to_name(ret));
        return;
    }
    ret = esp_bluedroid_init();//初始化蓝牙栈bluedroid stack
    /*
    蓝牙栈bluedroid stack包括了BT和BLE使用的基本的define和API
    初始化蓝牙栈以后并不能直接使用蓝牙功能,
    还需要用FSM管理蓝牙连接情况
    */
    if (ret)
    {
        ESP_LOGE(TAG, "%s init bluetooth failed: %s\n", __func__, esp_err_to_name(ret));
        return;
    }
    ret = esp_bluedroid_enable();//使能蓝牙栈
    if (ret) 
    {
        ESP_LOGE(TAG, "%s enable bluetooth failed: %s\n", __func__, esp_err_to_name(ret));
        return;
    }
    ESP_LOGI(TAG, "%s init bluetooth finished\n", __func__);
    //建立蓝牙的FSM
    //这里使用回调函数来控制每个状态下的响应,需要将其在GATT和GAP层的回调函数注册
    ret = esp_ble_gatts_register_callback(BLE_gatts_event_handler);
    if (ret)
    {
        ESP_LOGE(TAG, "gatts register error, error code = %x", ret);
        return;
    }
    ret = esp_ble_gap_register_callback(BLE_gap_event_handler);
    if (ret)
    {
        ESP_LOGE(TAG, "gap register error, error code = %x", ret);
        return;
    }
    /*BLE_gatts_event_handler和BLE_gap_event_handler处理蓝牙栈可能发生的所有情况,达到FSM的效果*/
    
    //下面创建了两个BLE GATT profile,相当于两个独立的应用程序
    ret = esp_ble_gatts_app_register(GATT_APP_A_ID);
    if (ret)
    {
        ESP_LOGE(TAG, "gatts app register error, error code = %x", ret);
        return;
    }
    ret = esp_ble_gatts_app_register(GATT_APP_B_ID);
    if (ret)
    {
        ESP_LOGE(TAG, "gatts app register error, error code = %x", ret);
        return;
    }


    有限状态机FSM(finite state machine),或者说状态机SM(state machine)是一种特殊的控制算法,能够根据控制信号按照预先设定的状态进行状态转移

    若输出只和状态有关而与输入无关,则称为Moore状态机;若输出不仅和状态有关而且和输入有关系,则称为Mealy状态机

    控制蓝牙的状态机一般为Moore状态机,随蓝牙所处的状态进行不同的操作(代码中通过switch语句进行控制)

    Server的profile利用一个结构体来定义,结构体成员取决于在这个profile中执行的service和characteristic,如下所示

    struct gatts_profile_inst {
        esp_gatts_cb_t gatts_cb;//GATT回调函数
        uint16_t gatts_if;//GATT接口
        uint16_t app_id;//应用的ID
        uint16_t conn_id;//连接的ID
        uint16_t service_handle;//Service句柄
        esp_gatt_srvc_id_t service_id;//Service ID
        uint16_t char_handle;//Characteristic句柄
        esp_bt_uuid_t char_uuid;//Characteristic的UUID
        esp_gatt_perm_t perm;//属性Attribute 授权
        esp_gatt_char_prop_t property;//Characteristic的优先级
        uint16_t descr_handle;//Client的Characteristic配置句柄
        esp_bt_uuid_t descr_uuid;//Client的Characteristic UUID
    };


    可以将这个结构体进一步组合为结构体数组

    static struct gatts_profile_inst gl_profile_tab[PROFILE_NUM] = {
        [PROFILE_A_APP_ID] = {
            .gatts_cb = gatts_profile_a_event_handler,
            .gatts_if = ESP_GATT_IF_NONE,
        [PROFILE_B_APP_ID] = {
            .gatts_cb = gatts_profile_b_event_handler,
            .gatts_if = ESP_GATT_IF_NONE,
        },
    };


    这样使用类似gl_profile_tab[i].gatts_if的语句就可以访问结构体的成员,i用于指示第(i+1)个profile

    使用上面的结构体数组来定义每个profile对应的GATT回调函数(gatts_profile_a_event_handler()、gatts_profile_b_event_handler()),就使得每个不同的profile使用不同的接口;初始化时,将gatts_if = ESP_GATT_IF_NONE,在之后通过各自的处理函数将profile连接到接口

    最后使用esp_ble_gatts_app_register()这个API将应用的ID注册到GATT

    ret = esp_ble_gatts_app_register(GATT_APP_A_ID);//run GATT app A register
    if (ret)
    {
        ESP_LOGE(TAG, "gatts app register error, error code = %x", ret);
        return;
    }
    ret = esp_ble_gatts_app_register(GATT_APP_B_ID);//run GATT app B register
    if (ret)
    {
        ESP_LOGE(TAG, "gatts app register error, error code = %x", ret);
        return;
    }


    7.6 GAP设置

    使用esp_ble_adv_data_t结构体来配置GAP广播情况,并使用esp_ble_gap_config_adv_data()函数进行广播

    typedef struct {
        bool set_scan_rsp;//是否作为扫描的回应信号广播
        bool include_name;//是否包括设备名
        bool include_txpower;//是否包括信号的发射功率
        int min_interval;//广播数据显示slave设备的连接最小时间间隔
        int max_interval;//广播数据显示slave设备的连接最大时间间隔
        int appearance;//设备外观(?)
        uint16_t manufacturer_len;//附加数据长度
        uint8_t *p_manufacturer_data;//附加数据指针
        uint16_t service_data_len;//Service数据长度
        uint8_t *p_service_data;//Service数据指针
        uint16_t service_uuid_len;//Servic UUID长度
        uint8_t *p_service_uuid;//Servic UUID指针
        uint8_t flag;//广播的发现模式,可选BLE_ADV_DATA_FLAG枚举值
    } esp_ble_adv_data_t;
    
    //设置示例
    static esp_ble_adv_data_t adv_data = {
        .set_scan_rsp = false,
        .include_name = true,
        .include_txpower = false,
        .min_interval = 0x0006, //slave connection min interval, Time = min_interval * 1.25 msec=7.5ms
        .max_interval = 0x0010, //slave connection max interval, Time = max_interval * 1.25 msec=20ms
        .appearance = 0x00,
        .manufacturer_len = 0, //TEST_MANUFACTURER_DATA_LEN
        .p_manufacturer_data = NULL, //&test_manufacturer[0]
        .service_data_len = 0,
        .p_service_data = NULL,
        .service_uuid_len = sizeof(adv_service_uuid128),
        .p_service_uuid = adv_service_uuid128,
        .flag = (ESP_BLE_ADV_FLAG_GEN_DISC | ESP_BLE_ADV_FLAG_BREDR_NOT_SPT),
    };

    一个广播的有效数据是31字节,如果超过会导致超出部分被截掉

    使用esp_ble_gap_config_adv_data_raw()esp_ble_gap_config_scan_rsp_data_raw()函数可以广播自定义的空数据

    广播数据设置完毕后,会自动进入ESP_GAP_BLE_ADV_DATA_SET_COMPLETE_EVT()或ESP_GAP_BLE_ADV_DATA_RAW_SET_COMPLETE_EVT状态,此时可以在

    gap_event_handler()中设置FSM控制程序

    static void BLE_gap_event_handler(esp_gap_ble_cb_event_t event, esp_ble_gap_cb_param_t* param)
    {
        switch (event)
        {
    #ifdef CONFIG_SET_RAW_ADV_DATA
        case ESP_GAP_BLE_ADV_DATA_RAW_SET_COMPLETE_EVT:
            adv_config_done &= (~adv_config_flag);
            if (adv_config_done == 0)
            {
                esp_ble_gap_start_advertising(&adv_params);
            }
            break;
        case ESP_GAP_BLE_SCAN_RSP_DATA_RAW_SET_COMPLETE_EVT:
            adv_config_done &= (~scan_rsp_config_flag);
            if (adv_config_done == 0)
            {
                esp_ble_gap_start_advertising(&adv_params);
            }
            break;
    #else
        case ESP_GAP_BLE_ADV_DATA_SET_COMPLETE_EVT:
            adv_config_done &= (~adv_config_flag);
            if (adv_config_done == 0)
            {
                esp_ble_gap_start_advertising(&adv_params);
            }
            break;
        case ESP_GAP_BLE_SCAN_RSP_DATA_SET_COMPLETE_EVT:
            adv_config_done &= (~scan_rsp_config_flag);
            if (adv_config_done == 0)
            {
                esp_ble_gap_start_advertising(&adv_params);
            }
            break;
    #endif
        case ESP_GAP_BLE_ADV_START_COMPLETE_EVT:
            //advertising start complete event to indicate advertising start successfully or failed
            if (param->adv_start_cmpl.status != ESP_BT_STATUS_SUCCESS)
            {
                ESP_LOGE(TAG, "Advertising start failed\n");
            }
            break;
        case ESP_GAP_BLE_ADV_STOP_COMPLETE_EVT:
            if (param->adv_stop_cmpl.status != ESP_BT_STATUS_SUCCESS)
            {
                ESP_LOGE(TAG, "Advertising stop failed\n");
            }
            else
            {
                ESP_LOGI(TAG, "Stop adv successfully\n");
            }
            break;
        case ESP_GAP_BLE_UPDATE_CONN_PARAMS_EVT:
            ESP_LOGI(TAG, "update connection params status = %d, min_int = %d, max_int = %d,conn_int = %d,latency = %d, timeout = %d",
                param->update_conn_params.status,
                param->update_conn_params.min_int,
                param->update_conn_params.max_int,
                param->update_conn_params.conn_int,
                param->update_conn_params.latency,
                param->update_conn_params.timeout);
            break;
        default:
            break;
        }
    }


    只要使用了esp_ble_gap_start_advertising()函数,GATT Server就会开始广播,在此之前还需要用esp_ble_adv_params_t结构体配置相关的参数

    //广播参数
    typedef struct {
        uint16_t adv_int_min;
        //非定向和循环定向广播的最小时间间隔
        //间隔设置在0x0020到0x4000,默认0x0800(1.28s),实际时间=N * 0.625 ms,时间范围在20ms到10.24s
        
        uint16_t adv_int_max;
        //非定向和循环定向广播的最大时间间隔
        //间隔设置在0x0020到0x4000,默认0x0800(1.28s),实际时间=N * 0.625 ms,时间范围在20ms到10.24s
        
        esp_ble_adv_type_t adv_type;//广播类型
        esp_ble_addr_type_t own_addr_type;//拥有者的蓝牙设备地址类型
        esp_bd_addr_t peer_addr;//附近的蓝牙设备地址
        esp_ble_addr_type_t peer_addr_type;//附近的蓝牙设备地址类型
        esp_ble_adv_channel_t channel_map;//广播通道映射
        esp_ble_adv_filter_t adv_filter_policy;//广播过滤器设置
    }
    esp_ble_adv_params_t;
    
    //设置示例
    static esp_ble_adv_params_t adv_params = {
        .adv_int_min        = 0x20,//最小时间间隔
        .adv_int_max        = 0x40,//最大时间间隔
        .adv_type           = ADV_TYPE_IND,
        .own_addr_type      = BLE_ADDR_TYPE_PUBLIC,//公共地址
        //.peer_addr            =默认
        //.peer_addr_type       =默认
        .channel_map        = ADV_CHNL_ALL,//全通道
        .adv_filter_policy = ADV_FILTER_ALLOW_SCAN_ANY_CON_ANY,//扫描所有连接
    };

    设置完毕后,可以使用esp_ble_gap_start_advertising()进行广播

    注意:esp_ble_gap_config_adv_data()使用esp_ble_adv_data_t结构体进行设置,配置的是广播出去的数据;而esp_ble_gap_start_advertising()使用esp_ble_adv_params_t结构体进行设置,配置的是该怎样广播

    8. 经典蓝牙的子集SPP

    蓝牙串口协议Serial Port Profile简写为SPP,SPP就是一种能在蓝牙设备之间创建串口进行数据传输的协议,最终目的是在两个不同设备(通信的两端)上的应用之间保证一条完整的通信路径

    SPP的协议栈示意图如下

    连接流程

    1. 创建虚拟连接
    2. 接受虚拟串口连接
    3. 在本地SDP数据上注册服务

    SPP协议与GATT协议的对比:

    经典蓝牙BT-SPP低功耗蓝牙BLE-GATT

    速率高

    灵活多变、集成很多profile
    兼容性好高速率传输时兼容性难以保障
    对IOS不友好对IOS很友好
    APP编程不方便开发资源丰富、接口多


     

    展开全文
  • esp32蓝牙通信

    千次阅读 2021-03-04 00:24:17
    虽然选择了蓝牙连接,但为了以后扩展wifi方便,所以硬件选用了esp32,同时有wifi和蓝牙连接的功能,代码又兼容arduino,使用非常方便。 蓝牙连接方式 初步设想是把硬件的mac地址生成二维码,手机软件扫描二维码获取...

    最近想做一个发热垫,可以用手机控制。

    一开始思考过用wifi接入米家进行控制,这样还能使用语音助手。但后来仔细思索一番,发现使用场景不对。如果使用wifi连接,那意味着只能在室内使用了。

    所以,最后还是决定直接使用蓝牙连接。

    硬件选型

    虽然选择了蓝牙连接,但为了以后扩展wifi方便,所以硬件选用了esp32,同时有wifi和蓝牙连接的功能,代码又兼容arduino,使用非常方便。

    蓝牙连接方式

    1. 初步设想是把硬件的mac地址生成二维码,手机软件扫描二维码获取mac地址,进行连接及发送温度设置等指令。
    2. 后来发现,貌似可以直接用设备名进行蓝牙连接,如此一来便可以把所有的硬件设备都设置为相同的设备名,又可以省去二维码,着实不错。
    3. 最后是在查资料时看到一种蓝牙广播的方式,不过尚未来及做实验,日后有机会倒可以试试。

    温控方式

    使用温敏电阻即可读取温度。

    1. 最简单的温控可以是直接用继电器开关进行控制。设置温度的上下区间,加热到上区间停止,低于下区间则重启加热。
    2. 高阶一点的是用pwm的方式调整发热电阻的功率,离目标温度越接近则功率越小,如此即可实现平滑温度曲线。甚至于再不行,还可上pid闭环控制算法,叠加上之前的误差,实时调整。

    手机软件

    由于我不会做安卓软件,现在只是使用一款“蓝牙串口”的app直接发送指令,控制硬件。

    以后还是要学一下安卓,做一套架子出来。

    esp32程序

    //This example code is in the Public Domain (or CC0 licensed, at your option.)
    //By Evandro Copercini - 2018
    //
    //This example creates a bridge between Serial and Classical Bluetooth (SPP)
    //and also demonstrate that SerialBT have the same functionalities of a normal Serial
    
    #include "BluetoothSerial.h"
    
    #if !defined(CONFIG_BT_ENABLED) || !defined(CONFIG_BLUEDROID_ENABLED)
    #error Bluetooth is not enabled! Please run `make menuconfig` to and enable it
    #endif
    
    BluetoothSerial SerialBT;
    char START_FLAG = '$';
    char END_FLAG = '#';
    int TEMPERATURE_MIN = 0;
    int TEMPERATURE_MAX = 50;
    
    void setup() {
      Serial.begin(115200);
      SerialBT.begin("ESP32test"); //Bluetooth device name
      Serial.println("The device started, now you can pair it with bluetooth!");
    }
    
    void SerialBT_sendMsg(String msg) {
      int i = 0;
      for (i = 0; i < msg.length(); i++) {
        SerialBT.write(msg[i]);
      }
    }
    
    int NONE = 0;
    int START = 1;
    int pre_status = NONE;
    
    int num = 0;
    void loop() {
      if (SerialBT.available()) {
        char msg_char = SerialBT.read();
        if (msg_char == START_FLAG) {
          num = 0;
          pre_status = START;
        } else if (msg_char == END_FLAG && pre_status == START) {
          if (num >= TEMPERATURE_MIN && num <= TEMPERATURE_MAX) {
            String msg = String("set temperature to " + String(num) + "\n");
            SerialBT_sendMsg(msg);
          }
          num = 0;
          pre_status = NONE;
        } else if (isDigit(msg_char) && pre_status == START) {
          num = num * 10 + (msg_char - '0');
        } else {
          num = 0;
          pre_status = NONE;
        }
    
        // SerialBT_sendMsg(String(String(msg_char) + "\n"));
      }
    
    
    
    
      delay(20);
    }

    file

    展开全文
  • ESP32蓝牙连接测试

    2021-05-09 20:47:52
    ESP32的两大集成网络,WIFI+蓝牙。 使用蓝牙可以在ESP_BLE中找到示例,使用了下BLE_UART功能,效果不错。 /* Video: https://www.youtube.com/watch?v=oCMOYS71NIU Based on Neil Kolban example for IDF: ...

    ESP32的两大集成网络,WIFI+蓝牙。

    使用蓝牙可以在ESP_BLE中找到示例,使用了下BLE_UART功能,效果不错。

    /*
        Video: https://www.youtube.com/watch?v=oCMOYS71NIU
        Based on Neil Kolban example for IDF: https://github.com/nkolban/esp32-snippets/blob/master/cpp_utils/tests/BLE%20Tests/SampleNotify.cpp
        Ported to Arduino ESP32 by Evandro Copercini
    
       Create a BLE server that, once we receive a connection, will send periodic notifications.
       The service advertises itself as: 6E400001-B5A3-F393-E0A9-E50E24DCCA9E
       Has a characteristic of: 6E400002-B5A3-F393-E0A9-E50E24DCCA9E - used for receiving data with "WRITE" 
       Has a characteristic of: 6E400003-B5A3-F393-E0A9-E50E24DCCA9E - used to send data with  "NOTIFY"
    
       The design of creating the BLE server is:
       1. Create a BLE Server
       2. Create a BLE Service
       3. Create a BLE Characteristic on the Service
       4. Create a BLE Descriptor on the characteristic
       5. Start the service.
       6. Start advertising.
    
       In this example rxValue is the data received (only accessible inside that function).
       And txValue is the data to be sent, in this example just a byte incremented every second. 
    */
    #include <BLEDevice.h>
    #include <BLEServer.h>
    #include <BLEUtils.h>
    #include <BLE2902.h>
    
    BLEServer *pServer = NULL;
    BLECharacteristic * pTxCharacteristic;
    bool deviceConnected = false;
    bool oldDeviceConnected = false;
    uint8_t txValue = 0;
    
    // See the following for generating UUIDs:
    // https://www.uuidgenerator.net/
    
    #define SERVICE_UUID           "6E400001-B5A3-F393-E0A9-E50E24DCCA9E" // UART service UUID
    #define CHARACTERISTIC_UUID_RX "6E400002-B5A3-F393-E0A9-E50E24DCCA9E"
    #define CHARACTERISTIC_UUID_TX "6E400003-B5A3-F393-E0A9-E50E24DCCA9E"
    
    
    class MyServerCallbacks: public BLEServerCallbacks {
        void onConnect(BLEServer* pServer) {
          deviceConnected = true;
        };
    
        void onDisconnect(BLEServer* pServer) {
          deviceConnected = false;
        }
    };
    
    class MyCallbacks: public BLECharacteristicCallbacks {
        void onWrite(BLECharacteristic *pCharacteristic) {
          std::string rxValue = pCharacteristic->getValue();
    
          if (rxValue.length() > 0) {
            Serial.println("*********");
            Serial.print("Received Value: ");
            for (int i = 0; i < rxValue.length(); i++)
              Serial.print(rxValue[i]);
    
            Serial.println();
            Serial.println("*********");
          }
        }
    };
    
    
    void setup() {
      Serial.begin(115200);
    
      // Create the BLE Device
      BLEDevice::init("UART Service");
    
      // Create the BLE Server
      pServer = BLEDevice::createServer();
      pServer->setCallbacks(new MyServerCallbacks());
    
      // Create the BLE Service
      BLEService *pService = pServer->createService(SERVICE_UUID);
    
      // Create a BLE Characteristic
      pTxCharacteristic = pService->createCharacteristic(
    										CHARACTERISTIC_UUID_TX,
    										BLECharacteristic::PROPERTY_NOTIFY
    									);
                          
      pTxCharacteristic->addDescriptor(new BLE2902());
    
      BLECharacteristic * pRxCharacteristic = pService->createCharacteristic(
    											 CHARACTERISTIC_UUID_RX,
    											BLECharacteristic::PROPERTY_WRITE
    										);
    
      pRxCharacteristic->setCallbacks(new MyCallbacks());
    
      // Start the service
      pService->start();
    
      // Start advertising
      pServer->getAdvertising()->start();
      Serial.println("Waiting a client connection to notify...");
    }
    
    void loop() {
    
        if (deviceConnected) {
            pTxCharacteristic->setValue(&txValue, 1);
            pTxCharacteristic->notify();
            txValue++;
    		delay(100); // bluetooth stack will go into congestion, if too many packets are sent
    	}
    
        // disconnecting
        if (!deviceConnected && oldDeviceConnected) {
            delay(500); // give the bluetooth stack the chance to get things ready
            pServer->startAdvertising(); // restart advertising
            Serial.println("start advertising");
            oldDeviceConnected = deviceConnected;
        }
        // connecting
        if (deviceConnected && !oldDeviceConnected) {
    		// do stuff here on connecting
            oldDeviceConnected = deviceConnected;
        }
    }

    烧写到ESP32中,就可以通过手机找到BLE_UART的蓝牙,连接后串口将打印start advertising。

    下载一个BLE Utility,可以通过终端向ESP32发送数据,32将同时通过串口将数据打印出来。

    比如我发送Hdjdj,那么串口助手上将出现该字符。

    因为ESP32一直在向连接的设备发送1到255的ASCII码,所以在手机端进行16进制显示,将出现下图一堆从00到FF循环的16进制码。因为设置为终端模式有乱码,正常应该为ASCII字符。

    功能确实强大。

    展开全文
  • ESP32已经支持了蓝牙Mesh和WIFI协议栈,但是其硬件基础上仅单天线,并且受限其有限的资源;而令人叫喜的是,IDF4.2版本已经很好的支持共存,虽然目前仅只有Wi-Fi STA模式下支持共存,但是通过增加PSRAM可有效提高了...
  • ESP32网络广播 这是一个简单的网络广播流媒体。 它通过wifi连接到Web广播电台,解码流并通过I2S编解码器播放声音,或者直接连接到DAC引脚的扬声器播放声音。 特征: 解码MP3和AAC(测试版) 蓝牙扬声器模式 起源 ...
  • 用于ESP32的简单Arduino蓝牙音乐接收器和发送器 ESP32提供了一个蓝牙A2DP API,可从您的手机接收声音数据,并通过回调方法使之可用。 输出是从SBC格式解码的PCM数据流。 该文档可在找到。 I2S是用于将数字音频设备...
  • APP Invertor蓝牙小车制作1、插件下载1.1、导入插件2、APP界面展示2.1 、可视化编程3、esp32 蓝牙代码4、实测效果4.1 APP控制端4.2 蓝牙接收端   经过几天的学习,终于成功制作了一个属于自己的蓝牙APP,APP可以...
  • 这是本人第一次开发的原创透传固件,适配ESP8266与ESP32,本固件是一款基于ESP Arduino core 的ESP8266/ESP32增强型透传固件(目前还没有蓝牙功能),相比现在网络流传的一些透传固件,多了很多功能。 优点:支持在WEB...
  • ble_esp32蓝牙_esp32_源码

    2021-09-30 16:22:22
    ESP32 低功耗蓝牙的双向传送,跑系统,比较稳定。远距离
  • 在长达两年的售后中,收到华为手机连接ESP32蓝牙是最容易断开连接的,一开始小米手机、苹果手机都没问题,就只有华为,一连上就断开,所以我们认为是APP问题,于是我们去找APP的蓝牙底层,每一次的断开连接都是...
  • ESP32 蓝牙模块 初探

    千次阅读 2018-11-23 14:52:06
     开发蓝牙首要条件,是对蓝牙的概率、协议和流程要有基本的掌握。开发内容,大同小异。  推荐文章:  a、蓝牙概念介绍、开发的基本流程  https://blog.csdn.net/changyourmind/article/details/60137061  b...
  • 1、 爬坑学习新旅程,虚拟机搭建esp32开发环境,打印 “Hellow World”。 2、 巧用eclipes编辑器,官方教程在在Windows下搭建esp32开发环境,打印 “Hellow World”。 3、 认识基本esp32的GPIO接口,开...
  • 玩转ESP32蓝牙(一)——ESP32经典蓝牙与HC02蓝牙通信 本文介绍 ESP32 经典蓝牙与汇承 HC02 蓝牙模块通信,ESP32 经典蓝牙作主机,HC02 蓝牙模块是从机, 出厂默认的波特率是 9600,配对密码是 1234。 一、实验器材...
  • ESP32 蓝牙BLE SCAN demo

    千次阅读 2020-01-15 16:42:36
    uint32_t taget_sum = 0; static esp_gattc_char_elem_t *char_elem_result = NULL; static esp_gattc_descr_elem_t *descr_elem_result = NULL; /* Declare static functions */ static void esp_gap_cb(esp_gap_...
  • 使用Arduino IDE的ESP32蓝牙经典入门

    千次阅读 2020-04-08 19:20:38
    ESP32随附Wi-Fi,低功耗蓝牙和经典蓝牙。在本教程中,您将学习如何将ESP32蓝牙经典版与Arduino IDE结合使用,以在ESP32和Android智能手机之间交换数据。 我们将控制ESP32输出,并使用Bluetooth Classic将传感器读数...
  • 想用手机连接ESP32蓝牙,目前安卓手机能搜索到设备并能成功链接,但是苹果手机却搜索不到设备,是什么原因呢?
  • ESP32蓝牙接收并截取指定格式的字符串 /* 字符串格式: [wifi_zhanghao,wifi_mima] */ typedef unsigned char u8; #include "BluetoothSerial.h" #include <string.h> #if !defined(CONFIG_BT_ENABLED) |...
  • ESP32 蓝牙 开发 资料 收集

    千次阅读 2018-08-05 16:40:04
    1 arduino ESP32 AndroidStudio BLE低功耗蓝牙 物联网 https://blog.csdn.net/qq_35174914/article/details/79328125 进入这个仓库 https://github.com/nkolban/ESP32_BLE_Arduino 打包下载所有文件 2 Bluetooth...
  • esp32蓝牙例程的学习记录

    千次阅读 2019-09-17 21:16:03
    4、交换密匙(exchanging keys) 在gatt_security_client的日志里: 在gatt_security_server的日志里: ...在esp32例程的讲解中是这么解释的: https://github.com/espressif/esp-idf/bl...
  • 该库可让您使ESP32充当蓝牙键盘并控制其功能。 你也可能对此有兴趣: 特征 发送按键 发短讯 按下/释放单个键 支持媒体密钥 读取Numlock / Capslock / Scrolllock状态 设置电池电量(基本上可以使用,但不会...
  • ESP32 蓝牙耳机

    2017-12-05 15:57:00
    第11章 ESP32 蓝牙耳机 概述 本文为ESP32学习,蓝牙耳机,更多学习目录请查阅ESP32学习目录。 11.1 基本概念 需要知道几个概念,A2DP(Advance Audio Distribution Profile)蓝牙立体声音频传输规范,AVRCP...
  • 玩转ESP32蓝牙)—— ESP32经典蓝牙与HC05蓝牙通信 本文介绍ESP32经典蓝牙与汇承HC05蓝牙模块通信,ESP32经典蓝牙作从机,HC05蓝牙模块作主机,HC05蓝牙模块出厂默认是从机,波特率是9600,配对密码是1234。 一...
  • ESP32蓝牙的Gatt Client的例子演练

    千次阅读 2020-01-10 20:07:20
    https://github.com/espressif/esp-idf/blob/dd8db6621/examples/bluetooth/bluedroid/ble/gatt_client/tutorial/Gatt_Client_Example_Walkthrough.md #include <stdint.h> #include...
  • ESP32蓝牙组网之节点配网

    千次阅读 2020-01-14 13:39:54
    在进行这篇文章之前,请确保你已经知道了一个蓝牙节点的组成。 APP链接(这个APP是安卓的):https://github.com/EspressifApp/EspBLEMeshForAndroid/releases/tag/v1.0.0 源码连接:...

空空如也

空空如也

1 2 3 4 5 ... 20
收藏数 3,547
精华内容 1,418
关键字:

esp32蓝牙