esp_hosted for MCU 源码分析

/ 0评 / 0

本来想直接添加文件,移植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等相关的,这里不啰嗦了.

移植相关要做的事情.

因为难以解耦,我不如先一个文件一个文件移植,可以直接复制的再复制.

发表回复

您的邮箱地址不会被公开。 必填项已用 * 标注