本来想直接添加文件,移植HAL层就可以了,结果官方代码难以解耦,没办法,文档又缺乏,没办法,先从代码分析开始.
先分析和HAL层最有关的spi_drv.c文件.
在头文件定义了收发QUEUE的大小,这样数据可以排队发送,主要程序逻辑是把"发送任务"和"推送内容到发送任务"分成两个任务实现,我不知道用意是什么,不过我觉得可能为了使得需要发送的内容能尽快推队列,异步处理吧.
#define TO_SLAVE_QUEUE_SIZE 20
#define FROM_SLAVE_QUEUE_SIZE 20
先贴一下每个传输都有的12字节的头.
struct esp_payload_header {
uint8_t if_type:4; // 接口类型,占用4位
uint8_t if_num:4; // 接口编号,占用4位
uint8_t flags; // 标志位,用于存储额外的控制信息
uint16_t len; // 数据长度,表示有效载荷的长度
uint16_t offset; // 数据偏移量,用于指示数据的起始位置
uint16_t checksum; // 校验和,用于数据完整性验证
uint16_t seq_num; // 序列号,用于数据包的顺序控制
uint8_t throttle_cmd:2; // 节流命令,占用2位,用于控制数据流
uint8_t reserved2:6; // 保留位,占用6位,未来可能用于扩展
/* 联合体字段必须始终位于最后,
* 这是hci_pkt_type的要求 */
union {
uint8_t reserved3; // 保留位,未来可能用于扩展
uint8_t hci_pkt_type; // HCI接口的包类型
uint8_t priv_pkt_type; // 私有接口的包类型
};
} __attribute__((packed)); // 使用packed属性确保结构体紧凑,避免编译器填充字节
在ESP-Hosted的通信中,用到了SPI引脚,DR和HS引脚,当遇到DR信号的时候,主机要尽快读取数据,当遇到HS信号时候,也说明数据已经被从机接受,整个传输过程是在spi_transaction_task中发生的,
// 快速RAM中断服务例程,用于处理握手信号
static void FAST_RAM_ATTR gpio_hs_isr_handler(void* arg)
{
g_h.funcs->_h_post_semaphore_from_isr(spi_trans_ready_sem); // 在中断中发送信号量
ESP_EARLY_LOGV(TAG, "%s", __func__); // 记录日志
}
// 快速RAM中断服务例程,用于处理数据准备信号
static void FAST_RAM_ATTR gpio_dr_isr_handler(void* arg)
{
g_h.funcs->_h_post_semaphore_from_isr(spi_trans_ready_sem); // 在中断中发送信号量
ESP_EARLY_LOGV(TAG, "%s", __func__); // 记录日志
}
d 这里是释放信号量,这个信号量在初始化时候创建,在spi_transaction_task中等待,一旦等到,就开始传输.
/**
* @brief SPI传输任务
* @param argument: 未使用
* @retval None
*/
static void spi_transaction_task(void const* pvParameters)
{
ESP_LOGD(TAG, "Staring SPI task"); // 记录SPI任务启动
#if DEBUG_HOST_TX_SEMAPHORE
if (H_DEBUG_GPIO_PIN_Host_Tx_Pin != -1)
g_h.funcs->_h_config_gpio(H_DEBUG_GPIO_PIN_Host_Tx_Port, H_DEBUG_GPIO_PIN_Host_Tx_Pin, H_GPIO_MODE_DEF_OUTPUT);
#endif
/* 配置握手信号线为中断模式 */
g_h.funcs->_h_config_gpio_as_interrupt(H_GPIO_HANDSHAKE_Port, H_GPIO_HANDSHAKE_Pin,
H_HS_INTR_EDGE, gpio_hs_isr_handler);
/* 配置数据准备信号线为中断模式 */
g_h.funcs->_h_config_gpio_as_interrupt(H_GPIO_DATA_READY_Port, H_GPIO_DATA_READY_Pin,
H_DR_INTR_EDGE, gpio_dr_isr_handler);
#if !H_HANDSHAKE_ACTIVE_HIGH
ESP_LOGI(TAG, "Handshake: Active Low"); // 记录握手信号为低电平有效
#endif
#if !H_DATAREADY_ACTIVE_HIGH
ESP_LOGI(TAG, "DataReady: Active Low"); // 记录数据准备信号为低电平有效
#endif
ESP_LOGD(TAG, "SPI GPIOs configured"); // 记录GPIO配置完成
create_debugging_tasks(); // 创建调试任务
for (;;) { // 无限循环
if ((!is_transport_rx_ready()) ||
(!spi_trans_ready_sem)) {
g_h.funcs->_h_msleep(300); // 如果传输未准备好,等待300ms
continue;
}
/* 执行SPI传输,直到接收到第一个事件。
* 之后仅在ESP发送中断时执行传输
*/
if (!g_h.funcs->_h_get_semaphore(spi_trans_ready_sem, HOSTED_BLOCK_MAX)) {
#if DEBUG_HOST_TX_SEMAPHORE
if (H_DEBUG_GPIO_PIN_Host_Tx_Pin != -1)
g_h.funcs->_h_write_gpio(H_DEBUG_GPIO_PIN_Host_Tx_Port, H_DEBUG_GPIO_PIN_Host_Tx_Pin, H_GPIO_LOW);
#endif
check_and_execute_spi_transaction(); // 执行SPI传输
}
}
}
既然要发送,那么就要准备数据,如果没有数据,那就相当于dummy发送.当发生dummy发送的时候,设置if_type为ESP_MAX_IF,这样代表无效数据,不需要处理.而且数据可能一次性是发不完的,要分多批发送,这时候还要置位schedule_dummy_tx,当然接受的schedule_dummy_rx同理,不过不在同一个函数里而已,然后就调用HAL相关函数_h_do_bus_transfer发送数据出去了.而_h_do_bus_transfer就是真正发送的SPI数据.发送完当然有数据返回,就进入process_spi_rx_buf处理.
static int check_and_execute_spi_transaction(void)
{
uint8_t *txbuff = NULL; // 发送缓冲区指针
uint8_t *rxbuff = NULL; // 接收缓冲区指针
uint8_t is_valid_tx_buf = 0; // 标记是否有有效的发送数据
void (*tx_buff_free_func)(void* ptr) = NULL; // 发送缓冲区释放函数指针
struct esp_payload_header *h = NULL; // ESP协议头指针
static uint8_t schedule_dummy_tx = 0; // 标记是否需要发送虚拟数据
uint32_t ret = 0; // 返回值
struct hosted_transport_context_t spi_trans = {0}; // SPI传输上下文
gpio_pin_state_t gpio_handshake = H_HS_VAL_INACTIVE; // 握手信号状态
gpio_pin_state_t gpio_rx_data_ready = H_DR_VAL_INACTIVE; // 数据准备信号状态
g_h.funcs->_h_lock_mutex(spi_bus_lock, HOSTED_BLOCK_MAX); // 锁定SPI总线
/* 读取握手信号线状态,判断从设备是否准备好进行下一次传输 */
gpio_handshake = g_h.funcs->_h_read_gpio(H_GPIO_HANDSHAKE_Port,
H_GPIO_HANDSHAKE_Pin);
/* 读取数据准备信号线状态,判断从设备是否有数据要发送 */
gpio_rx_data_ready = g_h.funcs->_h_read_gpio(H_GPIO_DATA_READY_Port,
H_GPIO_DATA_READY_Pin);
if (gpio_handshake == H_HS_VAL_ACTIVE) { // 如果握手信号有效
/* 获取下一个要发送的缓冲区 */
txbuff = get_next_tx_buffer(&is_valid_tx_buf, &tx_buff_free_func);
if ( (gpio_rx_data_ready == H_DR_VAL_ACTIVE) ||
(is_valid_tx_buf) || schedule_dummy_tx || schedule_dummy_rx ) {
if (!txbuff) { // 如果没有有效的发送数据
/* 即使没有数据要发送,SPI驱动也需要一个有效的缓冲区 */
txbuff = spi_buffer_alloc(MEMSET_REQUIRED);
assert(txbuff);
h = (struct esp_payload_header *) txbuff;
h->if_type = ESP_MAX_IF; // 设置协议头类型
#if H_MEM_STATS
h_stats_g.spi_mem_stats.tx_dummy_alloc++; // 统计虚拟缓冲区分配次数
#endif
tx_buff_free_func = spi_buffer_free; // 设置缓冲区释放函数
schedule_dummy_tx = 0; // 重置虚拟发送标记
} else {
schedule_dummy_tx = 1; // 设置虚拟发送标记
ESP_HEXLOGV("h_spi_tx", txbuff, 16); // 记录发送数据
}
ESP_LOGV(TAG, "dr %u tx_valid %u\n", gpio_rx_data_ready, is_valid_tx_buf);
/* 分配接收缓冲区 */
rxbuff = spi_buffer_alloc(MEMSET_REQUIRED);
assert(rxbuff);
#if H_MEM_STATS
h_stats_g.spi_mem_stats.rx_alloc++; // 统计接收缓冲区分配次数
#endif
spi_trans.tx_buf = txbuff; // 设置发送缓冲区
spi_trans.tx_buf_size = MAX_SPI_BUFFER_SIZE; // 设置发送缓冲区大小
spi_trans.rx_buf = rxbuff; // 设置接收缓冲区
#if ESP_PKT_STATS
struct esp_payload_header *payload_header =
(struct esp_payload_header *) txbuff;
if (payload_header->if_type == ESP_STA_IF)
pkt_stats.sta_tx_out++; // 统计STA接口发送数据包数量
#endif
/* 执行SPI传输,仅在以下条件之一成立时执行:
* a. 有有效的发送数据
* b. 从设备有数据要发送
*/
ret = g_h.funcs->_h_do_bus_transfer(&spi_trans);
if (!ret)
process_spi_rx_buf(spi_trans.rx_buf); // 处理接收到的数据
}
if (txbuff && tx_buff_free_func) {
tx_buff_free_func(txbuff); // 释放发送缓冲区
#if H_MEM_STATS
if (tx_buff_free_func == spi_buffer_free)
h_stats_g.spi_mem_stats.tx_freed++; // 统计发送缓冲区释放次数
else
h_stats_g.others.tx_others_freed++; // 统计其他缓冲区释放次数
#endif
}
}
if ((gpio_handshake != H_HS_VAL_ACTIVE) || schedule_dummy_tx || schedule_dummy_rx)
g_h.funcs->_h_post_semaphore(spi_trans_ready_sem); // 如果仍然需要发送,释放SPI传输信号量
g_h.funcs->_h_unlock_mutex(spi_bus_lock); // 解锁SPI总线
return ret; // 返回传输结果
}
取出接受的数据,检查头信息对不对,校验和对不对,如果都对,填充到buf_handle里,这个buf_handle是硬件无关的内部定义,然后以队列方式推送过去.需要处理这个buf的就直接去这个队列数据就可以了.
static int process_spi_rx_buf(uint8_t * rxbuff)
{
struct esp_payload_header *payload_header;
uint16_t rx_checksum = 0, checksum = 0;
interface_buffer_handle_t buf_handle = {0};
uint16_t len, offset;
int ret = 0;
uint8_t pkt_prio = PRIO_Q_OTHERS;
// 检查接收缓冲区是否有效
if (!rxbuff)
return -1;
ESP_HEXLOGV("h_spi_rx", rxbuff, 16);
// 解析接收到的数据包头部
payload_header = (struct esp_payload_header *) rxbuff;
// 从数据包头部获取长度和偏移量
len = le16toh(payload_header->len);
offset = le16toh(payload_header->offset);
// 如果数据包类型为ESP_MAX_IF,则取消调度虚拟接收
if (ESP_MAX_IF == payload_header->if_type)
schedule_dummy_rx = 0;
// 如果数据包长度为0,则处理节流命令
if (!len) {
wifi_tx_throttling = payload_header->throttle_cmd;
ret = -5;
goto done;
}
// 检查数据包长度和偏移量是否有效
if ((len > MAX_PAYLOAD_SIZE) ||
(offset != sizeof(struct esp_payload_header))) {
ESP_LOGI(TAG, "rx packet ignored: len [%u], rcvd_offset[%u], exp_offset[%u]\n",
len, offset, sizeof(struct esp_payload_header));
// 数据包无效,返回错误
ret = -2;
goto done;
} else {
// 计算并验证校验和
rx_checksum = le16toh(payload_header->checksum);
payload_header->checksum = 0;
checksum = compute_checksum(rxbuff, len+offset);
//TODO: checksum needs to be configurable from menuconfig
if (checksum == rx_checksum) {
// 初始化缓冲区句柄,用于后续处理
buf_handle.priv_buffer_handle = rxbuff;
buf_handle.free_buf_handle = spi_buffer_free;
buf_handle.payload_len = len;
buf_handle.if_type = payload_header->if_type;
buf_handle.if_num = payload_header->if_num;
buf_handle.payload = rxbuff + offset;
buf_handle.seq_num = le16toh(payload_header->seq_num);
buf_handle.flag = payload_header->flags;
wifi_tx_throttling = payload_header->throttle_cmd;
// 根据数据包类型确定优先级
if (buf_handle.if_type == ESP_SERIAL_IF)
pkt_prio = PRIO_Q_SERIAL;
else if (buf_handle.if_type == ESP_HCI_IF)
pkt_prio = PRIO_Q_BT;
/* else OTHERS by default */
// 将数据包放入相应的优先级队列,并通知接收任务
g_h.funcs->_h_queue_item(from_slave_queue[pkt_prio], &buf_handle, HOSTED_BLOCK_MAX);
g_h.funcs->_h_post_semaphore(sem_from_slave_queue);
} else {
// 校验和错误,丢弃数据包
ESP_LOGI(TAG, "rcvd_crc[%u] != exp_crc[%u], drop pkt\n",checksum, rx_checksum);
ret = -4;
goto done;
}
}
return ret;
done:
// 错误处理,释放接收缓冲区
if (rxbuff) {
spi_buffer_free(rxbuff);
#if H_MEM_STATS
h_stats_g.spi_mem_stats.rx_freed++;
#endif
rxbuff = NULL;
}
return ret;
}
另一个任务spi_process_rx_task,他负责挨个检查Queue,看看从那个Queue能取出数据,调用对应Queue的rx方法.这就不多说了.
static void spi_process_rx_task(void const* pvParameters)
{
interface_buffer_handle_t buf_handle_l = {0}; // 定义本地缓冲区句柄,初始化为0
interface_buffer_handle_t *buf_handle = NULL; // 定义缓冲区句柄指针,初始化为NULL
int ret = 0; // 定义返回值,初始化为0
while (1) { // 无限循环,持续处理接收数据
g_h.funcs->_h_get_semaphore(sem_from_slave_queue, HOSTED_BLOCK_MAX); // 获取信号量,确保队列访问的同步
// 从不同优先级的队列中尝试获取数据
if (g_h.funcs->_h_dequeue_item(from_slave_queue[PRIO_Q_SERIAL], &buf_handle_l, 0))
if (g_h.funcs->_h_dequeue_item(from_slave_queue[PRIO_Q_BT], &buf_handle_l, 0))
if (g_h.funcs->_h_dequeue_item(from_slave_queue[PRIO_Q_OTHERS], &buf_handle_l, 0)) {
ESP_LOGI(TAG, "No element in any queue found"); // 如果所有队列都为空,记录日志
continue; // 继续下一次循环
}
buf_handle = &buf_handle_l; // 将本地句柄赋值给指针,方便后续操作
struct esp_priv_event *event = NULL; // 定义私有事件指针,初始化为NULL
/* 根据接口类型处理接收到的缓冲区 */
if (buf_handle->if_type == ESP_SERIAL_IF) { // 如果是串行接口
/* 串行接口处理路径 */
serial_rx_handler(buf_handle); // 调用串行接口的接收处理函数
} else if((buf_handle->if_type == ESP_STA_IF) ||
(buf_handle->if_type == ESP_AP_IF)) { // 如果是STA或AP接口
schedule_dummy_rx = 1; // 设置标志位,表示需要调度虚拟接收
#if 1
if (chan_arr[buf_handle->if_type] && chan_arr[buf_handle->if_type]->rx) { // 如果通道存在且接收函数可用
/* TODO : 需要抽象化堆分配函数 */
uint8_t * copy_payload = (uint8_t *)g_h.funcs->_h_malloc(buf_handle->payload_len); // 分配内存用于拷贝有效载荷
assert(copy_payload); // 断言分配成功
memcpy(copy_payload, buf_handle->payload, buf_handle->payload_len); // 拷贝有效载荷
H_FREE_PTR_WITH_FUNC(buf_handle->free_buf_handle, buf_handle->priv_buffer_handle); // 释放原始缓冲区
ret = chan_arr[buf_handle->if_type]->rx(chan_arr[buf_handle->if_type]->api_chan,
copy_payload, copy_payload, buf_handle->payload_len); // 调用接收处理函数
if (unlikely(ret)) // 如果处理失败
HOSTED_FREE(copy_payload); // 释放拷贝的有效载荷
}
#else
// 另一种处理方式,直接传递原始缓冲区
if (chan_arr[buf_handle->if_type] && chan_arr[buf_handle->if_type]->rx) {
chan_arr[buf_handle->if_type]->rx(chan_arr[buf_handle->if_type]->api_chan,
buf_handle->payload, NULL, buf_handle->payload_len);
}
#endif
} else if (buf_handle->if_type == ESP_PRIV_IF) { // 如果是私有接口
process_priv_communication(buf_handle); // 处理私有通信
hci_drv_show_configuration(); // 显示HCI驱动配置
/* 接收到私有事务 */
ESP_LOGI(TAG, "Received INIT event"); // 记录日志,表示接收到初始化事件
event = (struct esp_priv_event *) (buf_handle->payload); // 将有效载荷转换为私有事件结构
if (event->event_type != ESP_PRIV_EVENT_INIT) { // 如果事件类型不是初始化事件
/* 用户可以重新使用此类事务 */
}
} else if (buf_handle->if_type == ESP_HCI_IF) { // 如果是HCI接口
hci_rx_handler(buf_handle); // 调用HCI接收处理函数
} else if (buf_handle->if_type == ESP_TEST_IF) { // 如果是测试接口
#if TEST_RAW_TP
update_test_raw_tp_rx_len(buf_handle->payload_len+H_ESP_PAYLOAD_HEADER_OFFSET); // 更新测试原始吞吐量接收长度
#endif
} else {
ESP_LOGW(TAG, "unknown type %d ", buf_handle->if_type); // 记录警告日志,表示未知的接口类型
}
/* 释放缓冲区句柄 */
/* 当缓冲区被卸载到其他模块时,该模块负责释放缓冲区。
* 如果未卸载或卸载失败,应在此处释放缓冲区。
*/
if (!buf_handle->payload_zcopy) { // 如果不需要零拷贝
H_FREE_PTR_WITH_FUNC(buf_handle->free_buf_handle,buf_handle->priv_buffer_handle); // 释放缓冲区
#if H_MEM_STATS
if (buf_handle->free_buf_handle && buf_handle->priv_buffer_handle) { // 如果释放函数和缓冲区句柄存在
if (spi_buffer_free == buf_handle->free_buf_handle) // 如果是SPI缓冲区释放函数
h_stats_g.spi_mem_stats.rx_freed++; // 更新SPI内存统计
else
h_stats_g.others.tx_others_freed++; // 更新其他内存统计
}
#endif
}
}
}
这个文件剩余的就是transport_init_internal方法,这个也不用介绍了,硬件HAL层除了这个文件,还有一个transport_drv.c文件,主要是设置IO等相关的,这里不啰嗦了.
移植相关要做的事情.
- 实现自己的OS层替换
- 实现自己的硬件层替换
- ESP_LOG 重新实现
- 内存管理 - 也可以依赖FreeRTOS直接用,不过我决定引入lwmem.
因为难以解耦,我不如先一个文件一个文件移植,可以直接复制的再复制.