资讯详情

ESP32 VHCI架构实现BLE扫描设备

零. 声明

我们将以连载的方式不断更新本专栏的文章。本专栏计划更新如下:

ESP-IDF基本介绍,主要涉及模块、芯片开发板、环境建设、程序编译下载、启动过程等基本操作,让您ESP-IDF对发展有一个全面的了解,比我们后续的学习打下基础!

ESP32-IDF外设驱动介绍主要基于esp-idf现有的driver,提供各种外设的驱动,如LED,OLED,SPI LCD,TOUCH,红外,Codec ic等等,在这篇文章中,我们不仅要做外设驱动,还要介绍常用的外设总线,让大家知道为什么!

目前比较热GUI LVGL介绍,主要设计LVGL7.1,LVGL8移植介绍,也将介绍每个组件,了解原理,最后,我们将推出一个组态软件来构建我们GUI,提高我们的效率!

ESP32-蓝牙,熟悉我,应该知道,即使我从事蓝牙协议堆栈的开发,所以这是我们独特的优势,在本章中,我们将提供蓝牙应用知识,也将应用蓝牙底协议堆栈理论,让您从上到下完全通过蓝牙监督!

Wi-Fi介绍,熟悉我应该知道,我们也做过一个sdio wifi驱动教程板,所以在wifi我们在这方面也有独特的优势,在这一章中,我们不仅提供Wi-Fi应用知识也会结合底层理论,让你对Wi-Fi有清晰的认知!

FreeRTOS介绍,主要介绍FreeRTOS使用和操作各种功能(任务管理/消息队列/信号量/相互排斥/事件/软件定时器/任务通知/内存管理/中断管理等)。

Arduino介绍,主要介绍ESP32 Arduino的基本操作(环境搭建,烧录,下载等开发流程),以及介绍下基于Arduino蓝牙,wifi的使用。

Demo,本章整合上述章节,做一些全面的工作demo,在巩固上述章节的同时,也可以学习实际项目!

此外,我们的教程包括但不限于上述章节,,请详细查看!

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

↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓

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

一. 整体介绍

本文主要以介绍为基础ESP32 VHCI的架构实现BLE扫描,即不使用默认Host API,编写一部分代码来实现功能ESP如下图所示:

我们做了什么?我们相当于默认Host取下,如图所示bluedroid部分,写Host实现部分广播

这篇文章很熟悉VHCI无论是默认的还是默认的Host(Bluedroid/nimble)甚至自己移植进来一个Host都有很大的帮助,即使我起到了抛砖引玉的作用!

二. menuconfig实现

虽然不需要ESP32的默认Host,但是我们要用他默认的Controller,所以还是要配置的,一共三个地方要特别注意,其他地方要保持默认,如下图所示:

1.Bluetooth controller mode

2.HCI mode

3.bluetooth Host

三. 程序实现

1.初始化NVS

/* Initialize NVS — it is used to store PHY calibration data */ esp_err_t ret = nvs_flash_init(); 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 );

必须添加此段,主要用于存储PHY信息,否则不能正常使用controller

2.初始化controller,注册Controller的callback函数实现

esp_bt_controller_config_t bt_cfg = BT_CONTROLLER_INIT_CONFIG_DEFAULT();  ret = esp_bt_controller_mem_release(ESP_BT_MODE_CLASSIC_BT); if (ret) {     ESP_LOGI(TAG, "Bluetooth controller release classic bt memory failed: %s", esp_err_to_name(ret));     return; }  if ((ret = esp_bt_controller_init(&bt_cfg)) != ESP_OK) {     ESP_LOGI(TAG, "Bluetooth controller initialize failed: %s", esp_err_to_name(ret));     return; }  if ((ret = esp_bt_controller_enable(ESP_BT_MODE_BLE)) != ESP_OK) {     ESP_LOGI(TAG, "Bluetooth controller enable failed: %s", esp_err_to_name(ret));     return; }  ...      esp_vhci_host_register_callback(&vhci_host_cb);

我们正在介绍这一段esp32 controller api已经详细介绍了,看API名字也知道主要做什么,没有困难,让我们看看注册controller的callback的实现

/*  * @brief: BT controller callback function, used to notify the upper layer that  *         controller is ready to receive command  */ static void controller_rcv_pkt_ready(void) {     ESP_LOGI(TAG, "controller rcv pkt ready")
}

/*
 * @brief: BT controller callback function to transfer data packet to
 *         the host
 */
static int host_rcv_pkt(uint8_t *data, uint16_t len)
{
    host_rcv_data_t send_data;
    uint8_t *data_pkt;
    /* Check second byte for HCI event. If event opcode is 0x0e, the event is
     * HCI Command Complete event. Sice we have recieved "0x0e" event, we can
     * check for byte 4 for command opcode and byte 6 for it's return status. */
    if (data[1] == 0x0e) {
        if (data[6] == 0) {
            ESP_LOGI(TAG, "Event opcode 0x%02x success.", data[4]);
        } else {
            ESP_LOGE(TAG, "Event opcode 0x%02x fail with reason: 0x%02x.", data[4], data[6]);
            return ESP_FAIL;
        }
    }

    data_pkt = (uint8_t *)malloc(sizeof(uint8_t) * len);
    if (data_pkt == NULL) {
        ESP_LOGE(TAG, "Malloc data_pkt failed!");
        return ESP_FAIL;
    }
    memcpy(data_pkt, data, len);
    send_data.q_data = data_pkt;
    send_data.q_data_len = len;
    if (xQueueSend(adv_queue, (void *)&send_data, ( TickType_t ) 0) != pdTRUE) {
        ESP_LOGD(TAG, "Failed to enqueue advertising report. Queue full.");
        /* If data sent successfully, then free the pointer in `xQueueReceive'
         * after processing it. Or else if enqueue in not successful, free it
         * here. */
        free(data_pkt);
    }
    return ESP_OK;
}

static esp_vhci_host_callback_t vhci_host_cb = {
    controller_rcv_pkt_ready,
    host_rcv_pkt
};

此时收到数据,会通过消息队列把收到的数据发送出去,然后会有一个task专门接收消息队列的消息

void hci_evt_process(void *pvParameters)
{
    .....
    while (1) {
        xQueueReceive(adv_queue, rcv_data, portMAX_DELAY);
        ....
           
    }
}

我们只是列出来一个雏形,后续看懂了广播消息的格式,我们再来介绍下这个task解析广播包的具体代码

3.实现HCI 的扫描

while (continue_commands) {
        if (continue_commands && esp_vhci_host_check_send_available()) {
            switch (cmd_cnt) {
            case 0: hci_cmd_send_reset(); ++cmd_cnt; break;
            case 1: hci_cmd_send_set_evt_mask(); ++cmd_cnt; break;

            /* Send scan commands. */
            case 2: hci_cmd_send_ble_scan_params(); ++cmd_cnt; break;
            case 3: hci_cmd_send_ble_scan_start(); ++cmd_cnt; break;
            default: continue_commands = 0; break;
            }
            ESP_LOGI(TAG, "BLE Advertise, cmd_sent: %d", cmd_cnt);
        }
        vTaskDelay(1000 / portTICK_RATE_MS);
    }

3.1 hci广播的流程

如果不考虑整个蓝牙Host的健壮性以及可用性,只考虑用ESP32 VHCI架构实现BLE广播功能,那么其实4个步骤就能实现,分别如下:

1) 发送HCI reset command

2) 发送set evt mask,如果这个不开启,那么收不到le meta event

3) 发送ble set scan param,也就是设置扫描的参数

4) 发送ble set scan start,也就是开启扫描

在后面的小节我们再一一介绍下每个command的格式以及作用!

3.2 hci command的格式

我们知道了步骤,并且知道了发送哪些command,但是command的格式是什么呢?那么这个算是一个比较大的话题,牵扯到蓝牙core spec hci章节的内容,我们本章本着应用文章,此部门我们简单的一笔带过,如果想彻底了解内部原理,那么我建议你看下这两篇文章:

蓝牙HCI command/event/acl/sco格式介绍_Wireless_Link的博客-CSDN博客

蓝牙传输介质Transport UART H4(RS232)介绍_Wireless_Link的博客-CSDN博客_蓝牙通过什么介质传输

以上两篇文章,分别介绍H4以及HCI的格式,好了,我们回归主题来介绍下HCI command的格式。

每个命令被分配一个2字节的操作码(opcode),用来唯一地识别不同类型的命令,操作码(opcode)参数分为两个字段,称为操作码组字段(Opcode Group Field, OGF)和操作码命令字段(Opcode Command Field, OCF)。其中OGF占用高6bit字节,OCF占用低10bit字节。

一共有以下几组OGF:

1)Link Control commands, the OGF is defined as 0x01.链路控制OGF,也就是控制蓝牙芯片跟remote沟通的命令

2)Link Policy commands, the OGF is defined as 0x02,链路策略OGF,也就是写一些Policy,比如转换角色等

3)HCI Control and Baseband commands, the OGF is defined as 0x03,控制本地芯片跟基带的OGF。比如reset本地芯片等。

4)Informational Parameters commands, the OGF is defined as 0x04。读取信息的OGF,比如读取本地芯片的LMP版本呢,支持的command,蓝牙地址等,

5)status parameters commands, the OGF is defined as 0x05,状态参数OGF,比如读取RSSI等。

6)Testing commands, the OGF is defined as 0x06,测试命令的OGF,比如让芯片进入测试模式(DUT,device under test)

7)LE Controller commands, the OGF code is defined as 0x08,BLE 的comand

8)vendor-specific debug commands,the OGF code is defined as 0x3F,此部分是vendor定义的,也就是芯片厂商为了扩展core文档的HCI command定义

OCF众多,在每个OGF下都有一堆的OCF定义

:后续参数的长度

r:每个command的para不同

3.3 hci event的格式

HCI event是蓝牙芯片发送给协议栈的事件。HCI事件包的格式如下图所示:

唯一event编码,在后续的小节会介绍(是固定的)

后续参数的长度

event参数。

3.4 hci reset命令

hci command的命令如下:

OGF是0x03,OCF是0x03

hci reset作用如下:

The HCI_Reset command willreset the Controller and the Link Manager on the BR/EDR Controller or the Link Layer on an LE Controller. If the Controller supports both BR/EDR and LEthen the HCI_Reset command shall reset the Link Manager, Baseband and Link Layer. The HCI_Reset command shall not affect the used HCI transport layer since the HCI transport layers may have reset mechanisms of their own. After the reset is completed, the current operational state will be lost, the Controller will enter standby mode and the Controller will automatically revert to the default values for the parameters for which default values are defined in the specification.

Note: The HCI_Reset command will not necessarily perform a hardware reset. This is implementation defined.

The Host shall not send additional HCI commands before the HCI_Command_Complete event related to the HCI_Reset command has been received。

函数实现如下:

/*  HCI Command Opcode group Field (OGF) */
#define HCI_GRP_HOST_CONT_BASEBAND_CMDS    (0x03 << 10)            /* 0x0C00 */
#define HCI_GRP_BLE_CMDS                   (0x08 << 10)

/*  HCI Command Opcode Command Field (OCF) */
#define HCI_RESET                       (0x0003 | HCI_GRP_HOST_CONT_BASEBAND_CMDS)

uint16_t make_cmd_reset(uint8_t *buf)
{
    UINT8_TO_STREAM (buf, H4_TYPE_COMMAND);
    UINT16_TO_STREAM (buf, HCI_RESET);
    UINT8_TO_STREAM (buf, 0);
    return HCI_H4_CMD_PREAMBLE_SIZE;
}
static void hci_cmd_send_reset(void)
{
    uint16_t sz = make_cmd_reset (hci_cmd_buf);
    esp_vhci_host_send_packet(hci_cmd_buf, sz);
}

组成的数据格式是:0x01 ,0x03 0x0c 0x00

3.5 hci set event mask命令

hci command的格式如下:

OGF=0x03,OCF=0x01

我们来看下这个command的格式:

Event_Mask:事件掩码,也就是决定哪些消息可以从controller上报到host,有以下消息

 

整个实现的代码如下:

uint16_t make_cmd_set_evt_mask (uint8_t *buf, uint8_t *evt_mask)
{
    UINT8_TO_STREAM (buf, H4_TYPE_COMMAND);
    UINT16_TO_STREAM (buf, HCI_SET_EVT_MASK);
    UINT8_TO_STREAM  (buf, HCIC_PARAM_SIZE_SET_EVENT_MASK);
    ARRAY_TO_STREAM(buf, evt_mask, HCIC_PARAM_SIZE_SET_EVENT_MASK);
    return HCI_H4_CMD_PREAMBLE_SIZE + HCIC_PARAM_SIZE_SET_EVENT_MASK;
}
static void hci_cmd_send_set_evt_mask(void)
{
    /* Set bit 61 in event mask to enable LE Meta events. */
    uint8_t evt_mask[8] = {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x20};
    uint16_t sz = make_cmd_set_evt_mask(hci_cmd_buf, evt_mask);
    esp_vhci_host_send_packet(hci_cmd_buf, sz);
}

从代码中可以得知开启了le meta的event

3.6 ble set scan param命令

hci command的格式如下:

OGF=0x08,OCF=0x0B

我们来看下这个command的格式:

分主动扫描跟被动扫描

区别主要有几个:

被动扫描仅仅接受广播包,不会发起扫描请求

主动扫描接受广播包后悔发送扫描请求给处于广播态的设备,来获取额外的广播数据

一般被动扫描用于确定从机不会发送扫描响应,只会发送31byte的广播数据,而主动扫描用于不确定从机是否有额外的数据,所以要额外发起扫描请求来接受更多的广播的数据

注意:主动扫描的扫描请求以及扫描响应也是广播封包

扫描窗口跟扫描间隔

 

效果如图:这个就决定了扫描的占空比~

uint16_t make_cmd_ble_set_scan_params (uint8_t *buf, uint8_t scan_type,
                                       uint16_t scan_interval, uint16_t scan_window,
                                       uint8_t own_addr_type, uint8_t filter_policy)
{
    UINT8_TO_STREAM (buf, H4_TYPE_COMMAND);
    UINT16_TO_STREAM (buf, HCI_BLE_WRITE_SCAN_PARAM);
    UINT8_TO_STREAM  (buf, HCIC_PARAM_SIZE_BLE_WRITE_SCAN_PARAM);
    UINT8_TO_STREAM (buf, scan_type);
    UINT16_TO_STREAM (buf, scan_interval);
    UINT16_TO_STREAM (buf, scan_window);
    UINT8_TO_STREAM (buf, own_addr_type);
    UINT8_TO_STREAM (buf, filter_policy);
    return HCI_H4_CMD_PREAMBLE_SIZE + HCIC_PARAM_SIZE_BLE_WRITE_SCAN_PARAM;
}
static void hci_cmd_send_ble_scan_params(void)
{
    /* Set scan type to 0x01 for active scanning and 0x00 for passive scanning. */
    uint8_t scan_type = 0x01;

    /* Scan window and Scan interval are set in terms of number of slots. Each slot is of 625 microseconds. */
    uint16_t scan_interval = 0x50; /* 50 ms */
    uint16_t scan_window = 0x30; /* 30 ms */

    uint8_t own_addr_type = 0x00; /* Public Device Address (default). */
    uint8_t filter_policy = 0x00; /* Accept all packets excpet directed advertising packets (default). */
    uint16_t sz = make_cmd_ble_set_scan_params(hci_cmd_buf, scan_type, scan_interval, scan_window, own_addr_type, filter_policy);
    esp_vhci_host_send_packet(hci_cmd_buf, sz);
}

3.7 hci send ble scan start命令

hci command的格式如下:

OGF=0x08,OCF=0x0c

我们来看下这个command的格式:

开始或者停止扫描

是否过滤重复的内容

 

代码如下:

static void hci_cmd_send_ble_scan_start(void)
{
    uint8_t scan_enable = 0x01; /* Scanning enabled. */
    uint8_t filter_duplicates = 0x00; /* Duplicate filtering disabled. */
    uint16_t sz = make_cmd_ble_set_scan_enable(hci_cmd_buf, scan_enable, filter_duplicates);
    esp_vhci_host_send_packet(hci_cmd_buf, sz);
    ESP_LOGI(TAG, "BLE Scanning started..");
}
uint16_t make_cmd_ble_set_scan_enable (uint8_t *buf, uint8_t scan_enable,
                                       uint8_t filter_duplicates)
{
    UINT8_TO_STREAM (buf, H4_TYPE_COMMAND);
    UINT16_TO_STREAM (buf, HCI_BLE_WRITE_SCAN_ENABLE);
    UINT8_TO_STREAM  (buf, HCIC_PARAM_SIZE_BLE_WRITE_SCAN_ENABLE);
    UINT8_TO_STREAM (buf, scan_enable);
    UINT8_TO_STREAM (buf, filter_duplicates);
    return HCI_H4_CMD_PREAMBLE_SIZE + HCIC_PARAM_SIZE_BLE_WRITE_SCAN_ENABLE;
}

3.8 解析扫描到的设备信息

收到的是这个event,具体原理可以看下我这篇文章

低功耗蓝牙搜索广播的实现流流程介绍 /BLE scan flow ----- 蓝牙低功耗协议栈_Wireless_Link的博客-CSDN博客

四.程序效果

 

标签: lmp331液压变送器

锐单商城拥有海量元器件数据手册IC替代型号,打造 电子元器件IC百科大全!

锐单商城 - 一站式电子元器件采购平台