零. 声明
本专栏文章我们会以连载的方式持续更新,本专栏计划更新内容如下:
第一篇: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有一个清晰的认知!
------------------------------------------------------------------------------------------------------------------------------------------
一. 整体介绍
此文章主要介绍基于ESP32 VHCI的架构实现传统蓝牙的可被搜索功能,也就是不使用默认的Host API,自己编写一部分代码来实现功能,具体ESP32的架构如下图所示:
那我们做的事情是什么呢?我们相当于把默认的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_BLE);
if (ret) {
ESP_LOGI(TAG, "Bluetooth controller release ble 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_CLASSIC_BT)) != 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的实现
static void controller_rcv_pkt_ready(void)
{
printf("controller rcv pkt ready\n");
}
static int host_rcv_pkt(uint8_t *data, uint16_t len)
{
printf("host rcv pkt: ");
for (uint16_t i = 0; i < len; i++) {
printf("0x%02x ", data[i]);
}
printf("\n");
return 0;
}
static esp_vhci_host_callback_t vhci_host_cb = {
controller_rcv_pkt_ready,
host_rcv_pkt
};
以上代码,我们接受到数据,并没有做什么事情,只是打印下数据的hex而已!
3.实现HCI 初始化,达到能被扫描到的效果
while (1) {
bool send_avail = false;
vTaskDelay(1000 / portTICK_PERIOD_MS);
send_avail = esp_vhci_host_check_send_available();
if (send_avail) {
switch (cmd_cnt) {
case 0: hci_cmd_send_reset(); ++cmd_cnt; break;
case 1: hci_cmd_send_change_local_name(); ++cmd_cnt; break;
case 2: hci_cmd_send_set_scan_mode(); ++cmd_cnt; break;
}
}
printf("BT SCAN MODE, flag_send_avail: %d, cmd_sent: %d\n", send_avail, cmd_cnt);
}
3.1 hci广播的流程
如果不考虑整个蓝牙Host的健壮性以及可用性,只考虑用ESP32 VHCI架构实现BLE广播功能,那么其实4个步骤就能实现,分别如下:
1) 发送HCI reset command
2) 发送HCI change local name,设置本地蓝牙名称
3) 发送HCI set scan mode,设置为inquiry scan enable & page scan enable,达到可被搜索的目的
在后面的小节我们再一一介绍下每个command的格式以及作用!
3.2 hci command的格式
我们知道了步骤,并且知道了发送哪些command,但是command的格式是什么呢?那么这个算是一个比较大的话题,牵扯到蓝牙core spec hci章节的内容,我们本章本着应用文章,此部门我们简单的一笔带过,如果想彻底了解内部原理,那么我建议你看下这两篇文章:
以上两篇文章,分别介绍H4以及HCI的格式,好了,我们回归主题来介绍下HCI command的格式。
Opcode:每个命令被分配一个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定义
Parameter Total Length:后续参数的长度
Parameter:每个command的para不同
3.3 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.4 hci change local name命令
hci command的格式如下:
OGF=0x03,OCF=0x013
我们来看下这个command的格式:
local_name:本地蓝牙名称,需要注意的是必须要248个字节,不需要的部分填0
整个实现的代码如下:
#define TAG "Wireless Link "
uint16_t make_cmd_chanage_local_name(uint8_t *buf,uint8_t *name)
{
UINT8_TO_STREAM (buf, H4_TYPE_COMMAND);
UINT16_TO_STREAM (buf, HCI_CHANGE_LOCAL_NAME);
UINT8_TO_STREAM (buf, 248);
ARRAY_TO_STREAM(buf,name,strlen((const char *)name));
return HCI_H4_CMD_PREAMBLE_SIZE + HCIC_PARAM_SIZE_CHNAGE_LOCAL_NAME;
}
static void hci_cmd_send_change_local_name(void)
{
memset(hci_cmd_buf,0,sizeof(hci_cmd_buf));
uint16_t sz = make_cmd_chanage_local_name (hci_cmd_buf,(uint8_t *)TAG);
esp_vhci_host_send_packet(hci_cmd_buf, sz);
}
3.5 hci set scan mode命令
hci command的格式如下:
OGF=0x08,OCF=0x1a
我们来看下这个command的格式:
scan_enable:设置scan mode enable的类型
0x00代表不可搜索,不可被连接
0x01代表可被搜索,不可被连接
0x02代表不可被搜索,可被连接
0x03代表可被搜索,可被连接
uint16_t make_cmd_set_scan_mode(uint8_t *buf,uint8_t scan_mode)
{
UINT8_TO_STREAM (buf, H4_TYPE_COMMAND);
UINT16_TO_STREAM (buf, HCI_SET_SCAN_MODE);
UINT8_TO_STREAM (buf, HCIC_PARAM_SIZE_SET_SCAN_MODE);
UINT8_TO_STREAM (buf, scan_mode);
return HCI_H4_CMD_PREAMBLE_SIZE + HCIC_PARAM_SIZE_SET_SCAN_MODE;
}
static void hci_cmd_send_set_scan_mode(void)
{
uint8_t scan_mode = 3; // inquiry scan & page scan enable
uint16_t sz = make_cmd_set_scan_mode (hci_cmd_buf,scan_mode);
esp_vhci_host_send_packet(hci_cmd_buf, sz);
}
四.程序效果
可以看到能搜索到我们的设备,但是不能连接哈,因为host完整,没有处理连接事件以及后续的profile的连接动作