目录
ESP32蓝⽛架构
BLUEDROID主机架构
BLUEDROID协议栈下HCI线程的数据处理流程。
ESP32蓝⽛架构
在 ESP32 的系统上,选择 BLUEDROID 为蓝⽛牙主机,并通过 VHCI(软件实现的虚拟 HCI 接⼝口)接⼝口,访问控制器器。此场景下,BLUEDROID 和控制器器都运⾏在同⼀宿主上(即 ESP32 芯⽚)
ESP-IDF 的默认运⾏行行环境为双核 FreeRTOS,ESP32 的蓝⽛牙可按照功能分为多个任务(task) 运⾏,不同任务的优先级也有不同,其中优先级最⾼高的为运⾏控制器的任务。控制任务对实时性的要求较⾼,在 FreeRTOS 系统中的优先级仅次于 IPC 任务(IPC 任务⽤于双核 CPU 的进程间通信)。BLUEDROID(ESP-IDF 默认蓝⽛牙主机)共包含 4 个任务,
分别运行 BTC、BTU、HCI UPWARD,及 HCI DOWNWARD。
BLUEDROID主机架构
从上图可以看到,BLUEDROID 内部⼤致分为 2 层:BTU 层和 BTC 层(除去 HCI ),每个层都有对应的任务来处理。
BTU 层主要负责蓝牙主机底层协议栈的处理,包括L2CAP、GATT/ATT、SMP、GAP 以及部分规范等,并向上提供以“bta”为前缀的接口;
BTC 层主要负责向应⽤用层提供接⼝支持、处理理基于 GATT 的规范、处理杂项等,并向应⽤层提供以“esp”为前缀的接⼝。所有的 API 都在 ESP_API 层,开发者应当使⽤“esp”为前缀的蓝⽛牙 API(特殊的除外)。
上图并未详细描述 HCI 部分,⽽事实上,HCI 具有 2 个任务(⾄少在 ESP-IDF V2.1 以前),分别处理 Downward 和 Upward 的数据。
此框架的其中一条设计思路路是尽量量将蓝⽛牙相关的任务交给 BTC 来处理,从而避免和降低用户任务 (User Task) 的负载,也使结构上更加简洁。
经典蓝⽛牙的部分规范,如 RFCOMM、A2DP 等,中层次偏协议偏底层的部分运行在 BTU 层,偏控制流程的以及需要提供 ESP-API 的运行在 BTC 层。
蓝⽛牙低功耗的部分规范或偏底层的功能,如 6LowPan 或 Dynamic L2CAP Channel 的功能,将运行在 BTU 层,再通过 BTC 向应⽤层提供 ESP-API。
BLUEDROID协议栈下HCI线程的数据处理流程。
跟HCI相关的接口文件
##### 初始化流程--- hci_start_up()
static void bte_main_enable(void)
{
APPL_TRACE_DEBUG("Enable HCI\n");
if (hci_start_up()) {
APPL_TRACE_ERROR("Start HCI Host Layer Failure\n");
return;
}
//Now Test Case Not Supported BTU
BTU_StartUp();
}
int hci_start_up(void)
{
packet_fragmenter->init(&packet_fragmenter_callbacks); //分包与重组相关,发送相关
hal->open(&hal_callbacks, hci_host_thread);//hal_callbacks()接收数据处理
}
hal_callbacks()
-------->>>>hal_says_packet_ready()
--------------------btu_task_post(SIG_BTU_HCI_MSG, packet) //发送BTU层处理
关于hal 和packet_fragmenter结构体初始化
const hci_t *hci_layer_get_interface(void)
{
hal = hci_hal_h4_get_interface();
packet_fragmenter = packet_fragmenter_get_interface();
init_layer_interface();
return &interface;
}
static bool hal_open(const hci_hal_callbacks_t *upper_callbacks, void *task_thread)
{
assert(upper_callbacks != NULL);
assert(task_thread != NULL);
##HAL-open---真实引用处理(callbacks 实际就是上层处理函数 hal_callbacks)
callbacks = upper_callbacks;
#if (BLE_ADV_REPORT_FLOW_CONTROL == TRUE)
hci_hal_env_init(HCI_HAL_SERIAL_BUFFER_SIZE, BLE_ADV_REPORT_FLOW_CONTROL_NUM + L2CAP_HOST_FC_ACL_BUFS + QUEUE_SIZE_MAX); // adv flow control num + ACL flow control num + hci cmd numeber
#else
hci_hal_env_init(HCI_HAL_SERIAL_BUFFER_SIZE, QUEUE_SIZE_MAX);
#endif
hci_h4_thread = (osi_thread_t *)task_thread;
//register vhci host cb
//HCI交互消息接收的处理函数vhci_host_cb注册回调
if (esp_vhci_host_register_callback(&vhci_host_cb) != ESP_OK) {
return false;
}
return true;
}
static const esp_vhci_host_callback_t vhci_host_cb = {
.notify_host_send_available = host_send_pkt_available_cb,
.notify_host_recv = host_recv_pkt_cb,
};
HCI接口:RX接收处理
static void hci_hal_env_init(
size_t buffer_size,
size_t max_buffer_count)
{
assert(buffer_size > 0);
assert(max_buffer_count > 0);
hci_hal_env.buffer_size = buffer_size;
hci_hal_env.adv_free_num = 0;
hci_hal_env.rx_q = fixed_queue_new(max_buffer_count);
if (hci_hal_env.rx_q) {
fixed_queue_register_dequeue(hci_hal_env.rx_q, event_uart_has_bytes);//注册接收处理
} else {
HCI_TRACE_ERROR("%s unable to create rx queue.\n", __func__);
}
return;
}
再结合上面流程,所有HCI接收的消息都会回调event_uart_has_bytes()进行处理
static void event_uart_has_bytes(fixed_queue_t *queue)
{
BT_HDR *packet;
while (!fixed_queue_is_empty(queue)) {
packet = fixed_queue_dequeue(queue, FIXED_QUEUE_MAX_TIMEOUT);
hci_hal_h4_hdl_rx_packet(packet);
}
}
//流程
hci_hal_h4_hdl_rx_packet()
-------->>>> callbacks->packet_ready(packet);此接口就是上层流程初始化引入Callback,将数据传给BTU
HCI接口:TX接收处理
看代码可以发现,event_command_ready和event_packet_ready 他们都会调用同一个接口来发送数据,packet_fragmenter模块里面的:
static int hci_layer_init_env(void)
{
fixed_queue_register_dequeue(hci_host_env.command_queue, event_command_ready);
fixed_queue_register_dequeue(hci_host_env.packet_queue, event_packet_ready);
}
//都会调用同一个接口如下:
//packet_fragmenter->fragment_and_dispatch(wait_entry->command);
也就是说,所有的数据都会先进行fragment以及dispatch的过程,我们这里主要关注数据的流向,那么也就是dispatch的流程:
static void fragment_and_dispatch(BT_HDR *packet)
{
callbacks->fragmented(packet, true);
}
发现最后是通过回调函数来发送,追到真实的发送函数如下,hal的相关的接口:
// Callback for the fragmenter to send a fragment
static void transmit_fragment(BT_HDR *packet, bool send_transmit_finished)
{
uint16_t event = packet->event & MSG_EVT_MASK;
serial_data_type_t type = event_to_data_type(event);
hal->transmit_data(type, packet->data + packet->offset, packet->len);
if (event != MSG_STACK_TO_HC_HCI_CMD && send_transmit_finished) {
osi_free(packet);
}
}
static uint16_t transmit_data(serial_data_type_t type,
uint8_t *data, uint16_t length)
{
uint8_t previous_byte;
assert(data != NULL);
assert(length > 0);
if (type < DATA_TYPE_COMMAND || type > DATA_TYPE_SCO) {
HCI_TRACE_ERROR("%s invalid data type: %d", __func__, type);
return 0;
}
// Write the signal byte right before the data
--data;
previous_byte = *data;
*(data) = type;
++length;
BTTRC_DUMP_BUFFER("Transmit Pkt", data, length);
// TX Data to target
esp_vhci_host_send_packet(data, length);
// Be nice and restore the old value of that byte
*(data) = previous_byte;
return length - 1;
}
最后总结一下hci_thread处理的数据流程:
-
当hci向下发送数据的时候,会将数据放置到packet_queue,然后调用到fragment_and_dispatch,然后经过esp_vhci_host_send_packet(data, length);,最后抵达controller
-
当controller有数据上传的时候,hci_thread会一直poll这个节点,调用 hal_says_data_ready读出数据,数据经过 event_uart_has_bytes 将数据送到btu_hci_msg_queue,然后由btu 线程继续处理。
本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)