0.briefly speaking
本篇文章对ESP-IDF中的通用异步收发器(Universal Asynchronous Receiver-Transmitter, UART)的协议细节和驱动代码实现进行梳理和总结,本文仍遵循前几篇文章的基本叙事风格,仍以使用示例+代码分析为行文主要脉络,并在其中对一些必要的话题进行扩展,本文仍以ESP32-C6作为主要的硬件平台,并参考如下参考材料和源代码:
- esp-idf/components/esp_driver_uart (UART驱动代码)
- esp-idf/examples/peripherals/uart (UART使用示例代码)
- ESP-IDF编程指南——通用异步接收器/发送器 (UART)
- ESP32-C6技术参考手册
0.引子——UART使用示例代码
这个例子展示了如何使用UART完成环回通信(echo),里面涵盖了UART的基本数据收发功能,以下是具体的示例代码:
static void echo_task(void *arg)
{
/* Configure parameters of an UART driver,
* communication pins and install the driver */
// UART驱动的配置参数
uart_config_t uart_config = {
.baud_rate = ECHO_UART_BAUD_RATE,
.data_bits = UART_DATA_8_BITS,
.parity = UART_PARITY_DISABLE,
.stop_bits = UART_STOP_BITS_1,
.flow_ctrl = UART_HW_FLOWCTRL_DISABLE,
.source_clk = UART_SCLK_DEFAULT,
};
int intr_alloc_flags = 0;
#if CONFIG_UART_ISR_IN_IRAM
intr_alloc_flags = ESP_INTR_FLAG_IRAM;
#endif
// 1.安装UART驱动程序
ESP_ERROR_CHECK(uart_driver_install(ECHO_UART_PORT_NUM, BUF_SIZE * 2, 0, 0, NULL, intr_alloc_flags));
// 2.对UART参数进行配置
ESP_ERROR_CHECK(uart_param_config(ECHO_UART_PORT_NUM, &uart_config));
// 3.设置UART通信引脚
ESP_ERROR_CHECK(uart_set_pin(ECHO_UART_PORT_NUM, ECHO_TEST_TXD, ECHO_TEST_RXD, ECHO_TEST_RTS, ECHO_TEST_CTS));
// 为到来的数据分配一块缓冲区
uint8_t *data = (uint8_t *) malloc(BUF_SIZE);
while (1) {
// 4.从串口读取数据
int len = uart_read_bytes( ECHO_UART_PORT_NUM,
data,
(BUF_SIZE - 1),
20 / portTICK_PERIOD_MS);
// 5.再将数据通过串口发送出去
uart_write_bytes(ECHO_UART_PORT_NUM, (const char *) data, len);
// 打印收到的串口字符串
if (len) {
data[len] = '\0';
ESP_LOGI(TAG, "Recv str: %s", (char *) data);
}
}
}
从上述示例代码可以发现,使用一个UART完成简单的通讯功能,只需要以下简单的几步,下面按照顺序对以下几个函数进行展开:
/ ************************************************************* /
// 1.安装UART驱动程序
uart_driver_install
// 2.对UART通信参数进行配置
uart_param_config
// 3.设置UART通信引脚
uart_set_pin
// 4.从串口接收数据
uart_read_bytes
// 5.从串口发送数据
uart_write_bytes
/ ************************************************************* /
1.安装UART驱动程序——uart_driver_install
首先给出uart_driver_install的函数调用关系图,同时也是本节目录:
/ ************************************************************ /
// 安装UART驱动程序
uart_driver_install
// 分配一个驱动对象,并为其分配必要的缓冲区和事件队列
uart_alloc_driver_obj
// 重置模式检测队列
uart_pattern_queue_reset
// 使能UART模块时钟
uart_module_enable
// 分配中断给UART,注册uart_rx_intr_handler_default为中断处理函数
uart_rx_intr_handler_default
// 配置UART中断参数
uart_intr_config
/ ************************************************************ /
esp_err_t uart_driver_install( uart_port_t uart_num,
int rx_buffer_size,
int tx_buffer_size,
int event_queue_size,
QueueHandle_t *uart_queue,
int intr_alloc_flags)
{
esp_err_t ret;
// 如果指定的UART口已经被GDB Stub占用,则报错
#ifdef CONFIG_ESP_SYSTEM_GDBSTUB_RUNTIME
ESP_RETURN_ON_FALSE((uart_num != CONFIG_ESP_CONSOLE_UART_NUM), ESP_FAIL, UART_TAG, "UART used by GDB-stubs! Please disable GDB in menuconfig.");
#endif // CONFIG_ESP_SYSTEM_GDBSTUB_RUNTIME
// 要使用的UART编号不应超过芯片拥有的UART总数
ESP_RETURN_ON_FALSE((uart_num < UART_NUM_MAX), ESP_FAIL, UART_TAG, "uart_num error");
// 如果指定的发送/接收缓冲区大小大于硬件预留的缓冲区大小,则报错
ESP_RETURN_ON_FALSE((rx_buffer_size > UART_HW_FIFO_LEN(uart_num)), ESP_FAIL, UART_TAG, "uart rx buffer length error");
ESP_RETURN_ON_FALSE((tx_buffer_size > UART_HW_FIFO_LEN(uart_num)) || (tx_buffer_size == 0), ESP_FAIL, UART_TAG, "uart tx buffer length error");
// 保证UART中断处理程序按照menuconfig的配置放置在了IRAM中
#if CONFIG_UART_ISR_IN_IRAM
if ((intr_alloc_flags & ESP_INTR_FLAG_IRAM) == 0) {
ESP_LOGI(UART_TAG, "ESP_INTR_FLAG_IRAM flag not set while CONFIG_UART_ISR_IN_IRAM is enabled, flag updated");
intr_alloc_flags |= ESP_INTR_FLAG_IRAM;
}
#else
if ((intr_alloc_flags & ESP_INTR_FLAG_IRAM) != 0) {
ESP_LOGW(UART_TAG, "ESP_INTR_FLAG_IRAM flag is set while CONFIG_UART_ISR_IN_IRAM is not enabled, flag updated");
intr_alloc_flags &= ~ESP_INTR_FLAG_IRAM;
}
#endif
/* 以上均是入口参数的检查和保证 */
// 如果还没有给对应的UART实例分配对应的驱动对象
if (p_uart_obj[uart_num] == NULL) {
// 分配驱动对象,并为其分配必要的收发缓冲区和事件队列
p_uart_obj[uart_num] = uart_alloc_driver_obj( uart_num,
event_queue_size,
tx_buffer_size,
rx_buffer_size);
if (p_uart_obj[uart_num] == NULL) {
ESP_LOGE(UART_TAG, "UART driver malloc error");
return ESP_FAIL;
}
// 填充UART驱动对象中的一些字段
p_uart_obj[uart_num]->uart_num = uart_num;
p_uart_obj[uart_num]->uart_mode = UART_MODE_UART;
p_uart_obj[uart_num]->coll_det_flg = false;
p_uart_obj[uart_num]->rx_always_timeout_flg = false;
p_uart_obj[uart_num]->event_queue_size = event_queue_size;
p_uart_obj[uart_num]->tx_ptr = NULL;
p_uart_obj[uart_num]->tx_head = NULL;
p_uart_obj[uart_num]->tx_len_tot = 0;
p_uart_obj[uart_num]->tx_brk_flg = 0;
p_uart_obj[uart_num]->tx_brk_len = 0;
p_uart_obj[uart_num]->tx_waiting_brk = 0;
p_uart_obj[uart_num]->rx_buffered_len = 0;
p_uart_obj[uart_num]->rx_buffer_full_flg = false;
p_uart_obj[uart_num]->tx_waiting_fifo = false;
p_uart_obj[uart_num]->rx_int_usr_mask = UART_INTR_RXFIFO_FULL | UART_INTR_RXFIFO_TOUT;
p_uart_obj[uart_num]->tx_buf_size = tx_buffer_size;
p_uart_obj[uart_num]->uart_select_notif_callback = NULL;
// 释放tx_fifo的信号量,重置模式检测队列
xSemaphoreGive(p_uart_obj[uart_num]->tx_fifo_sem);
uart_pattern_queue_reset(uart_num, UART_PATTERN_DET_QLEN_DEFAULT);
// 设置出口参数,将UART事件队列传送出去
// 并打印空闲空间大小
if (uart_queue) {
*uart_queue = p_uart_obj[uart_num]->event_queue;
ESP_LOGI(UART_TAG, "queue free spaces: %" PRIu32, (uint32_t)uxQueueSpacesAvailable(p_uart_obj[uart_num]->event_queue));
}
} else {
ESP_LOGE(UART_TAG, "UART driver already installed");
return ESP_FAIL;
}
// 配置UART中断参数
uart_intr_config_t uart_intr = {
// 默认使能的UART中断,定义如下:
// #define UART_INTR_CONFIG_FLAG ((UART_INTR_RXFIFO_FULL) \
// | (UART_INTR_RXFIFO_TOUT) \
// | (UART_INTR_RXFIFO_OVF) \
// | (UART_INTR_BRK_DET) \
// | (UART_INTR_PARITY_ERR))
// 关于这些中断的含义,请参考技术参考手册
.intr_enable_mask = UART_INTR_CONFIG_FLAG,
// UART RX通道满的阈值 = 120
.rxfifo_full_thresh = UART_THRESHOLD_NUM(uart_num, UART_FULL_THRESH_DEFAULT),
// UART RX通道超时阈值 = 10(字节发送时间)
.rx_timeout_thresh = UART_TOUT_THRESH_DEFAULT,
// UART TX通道空阈值 = 10
.txfifo_empty_intr_thresh = UART_THRESHOLD_NUM(uart_num, UART_EMPTY_THRESH_DEFAULT),
};
// 开启UART的总线时钟和Core时钟(UART_SCLK)
uart_module_enable(uart_num);
// 关中断和清除中断状态位
uart_hal_disable_intr_mask(&(uart_context[uart_num].hal), UART_LL_INTR_MASK);
uart_hal_clr_intsts_mask(&(uart_context[uart_num].hal), UART_LL_INTR_MASK);
// 为UART分配一个中断
// UART的信号、针脚定义都被预先定义在了uart_periph_signal数组中(components/soc/esp32c6/uart_periph.c)
// 注册的中断处理函数为uart_rx_intr_handler_default
// 关于esp_intr_alloc函数,请参考Peripheral Drivers in ESP-IDF(1)——GPIO
ret = esp_intr_alloc(uart_periph_signal[uart_num].irq, intr_alloc_flags,
uart_rx_intr_handler_default, p_uart_obj[uart_num],
&p_uart_obj[uart_num]->intr_handle);
ESP_GOTO_ON_ERROR(ret, err, UART_TAG, "Could not allocate an interrupt for UART");
// 配置UART的中断属性
ret = uart_intr_config(uart_num, &uart_intr);
ESP_GOTO_ON_ERROR(ret, err, UART_TAG, "Could not configure the interrupt for UART");
return ret;
// 错误处理函数
err:
uart_driver_delete(uart_num);
return ret;
}
1.1 分配UART驱动对象内存空间
1.1.1 UART驱动对象结构体定义——uart_obj_t
ESP-IDF使用uart_obj_t结构体来抽象一个UART设备中可能拥有的所有属性,以及数据缓冲区和一些读写数据时应该记录的信息,这个结构体拥有的域比较繁杂,具体的字段我们在驱动中遇到时再展开解释,整体定义暂记如下:
typedef struct {
uart_port_t uart_num; /*!< UART 端口号 (0, 1, 2) */
int event_queue_size; /*!< UART 事件队列大小 */
intr_handle_t intr_handle; /*!< UART 中断处理程序句柄 */
uart_mode_t uart_mode; /*!< UART 模式 (UART / RS485 / IRDA) */
bool coll_det_flg; /*!< RS485 碰撞检测标志 */
bool rx_always_timeout_flg; /*!< UART RX 超时检测标志 */
int rx_buffered_len; /*!< RX 缓冲区已缓存的数据长度 */
int rx_buf_size; /*!< RX 环形缓冲区大小 */
bool rx_buffer_full_flg; /*!< RX 缓冲区已满标志 */
uint8_t *rx_data_buf; /*!< 临时存储 FIFO 数据的缓冲区 */
uint8_t rx_stash_len; /*!< RX 缓冲区暂存的数据长度 */
uint32_t rx_int_usr_mask; /*!< RX 相关的中断状态 */
uart_pat_rb_t rx_pattern_pos; /*!< UART 接收数据模式匹配位置 */
int tx_buf_size; /*!< TX 环形缓冲区大小 */
bool tx_waiting_fifo; /*!< 是否有任务正在等待 TX FIFO 空闲的标志 */
uint8_t *tx_ptr; /*!< TX 数据指针 (用于 FIFO 发送) */
uart_tx_data_t *tx_head; /*!< TX 缓冲区的头部指针 */
uint32_t tx_len_tot; /*!< TX 当前发送数据的总长度 */
uint32_t tx_len_cur; /*!< TX 当前已发送的长度 */
uint8_t tx_brk_flg; /*!< TX 发送 BREAK 信号标志 */
uint8_t tx_brk_len; /*!< TX BREAK 信号的长度 */
uint8_t tx_waiting_brk; /*!< 是否等待发送 BREAK 信号完毕 */
uart_select_notif_callback_t uart_select_notif_callback; /*!< `select()` 事件通知回调 */
QueueHandle_t event_queue; /*!< UART 事件队列 */
RingbufHandle_t rx_ring_buf; /*!< RX 环形缓冲区句柄 */
RingbufHandle_t tx_ring_buf; /*!< TX 环形缓冲区句柄 */
SemaphoreHandle_t rx_mux; /*!< UART RX 互斥锁 */
SemaphoreHandle_t tx_mux; /*!< UART TX 互斥锁 */
SemaphoreHandle_t tx_fifo_sem; /*!< UART TX FIFO 信号量 */
SemaphoreHandle_t tx_done_sem; /*!< UART TX 发送完成信号量 */
SemaphoreHandle_t tx_brk_sem; /*!< UART TX BREAK 发送完成信号量 */
#if PROTECT_APB
esp_pm_lock_handle_t pm_lock; /*!< APB 总线保护 (低功耗模式) */
#endif
} uart_obj_t;
1.1.2 为UART驱动对象分配必要的内存——uart_alloc_driver_obj
此函数专门用来为UART驱动对象分配一些必要的内存空间和变量,例如环形缓冲区、事件队列等。
static uart_obj_t *uart_alloc_driver_obj( uart_port_t uart_num, // 使用的UART硬件实例
int event_queue_size,
int tx_buffer_size,
int rx_buffer_size)
{
// 从堆内存分配一个uart_obj_t对象的内存空间
uart_obj_t *uart_obj = heap_caps_calloc(1, sizeof(uart_obj_t), UART_MALLOC_CAPS);
if (!uart_obj) {
return NULL;
}
// 为RX FIFO缓冲区分配内存空间
uart_obj->rx_data_buf = heap_caps_calloc(UART_HW_FIFO_LEN(uart_num), sizeof(uint32_t), UART_MALLOC_CAPS);
if (!uart_obj->rx_data_buf) {
goto err;
}
// 为UART分配事件队列
if (event_queue_size > 0) {
uart_obj->event_queue = xQueueCreateWithCaps(event_queue_size, sizeof(uart_event_t), UART_MALLOC_CAPS);
if (!uart_obj->event_queue) {
goto err;
}
}
// 为UART分配一个发送环形缓冲区(RingBuffer)
if (tx_buffer_size > 0) {
uart_obj->tx_ring_buf = xRingbufferCreateWithCaps(tx_buffer_size, RINGBUF_TYPE_NOSPLIT, UART_MALLOC_CAPS);
if (!uart_obj->tx_ring_buf) {
goto err;
}
}
// 分配接收缓冲区
uart_obj->rx_ring_buf = xRingbufferCreateWithCaps(rx_buffer_size, RINGBUF_TYPE_BYTEBUF, UART_MALLOC_CAPS);
// 创建必要的互斥量和信号量
uart_obj->tx_mux = xSemaphoreCreateMutexWithCaps(UART_MALLOC_CAPS);
uart_obj->rx_mux = xSemaphoreCreateMutexWithCaps(UART_MALLOC_CAPS);
uart_obj->tx_brk_sem = xSemaphoreCreateBinaryWithCaps(UART_MALLOC_CAPS);
uart_obj->tx_done_sem = xSemaphoreCreateBinaryWithCaps(UART_MALLOC_CAPS);
uart_obj->tx_fifo_sem = xSemaphoreCreateBinaryWithCaps(UART_MALLOC_CAPS);
if (!uart_obj->rx_ring_buf || !uart_obj->rx_mux || !uart_obj->tx_mux || !uart_obj->tx_brk_sem ||
!uart_obj->tx_done_sem || !uart_obj->tx_fifo_sem) {
goto err;
}
#if PROTECT_APB
if (esp_pm_lock_create(ESP_PM_APB_FREQ_MAX, 0, "uart_driver",
&uart_obj->pm_lock) != ESP_OK) {
goto err;
}
#endif
return uart_obj;
// 错误处理
err:
uart_free_driver_obj(uart_obj);
return NULL;
}
1.2 重置模式检测队列
此函数专门用来重置UART RX通道上的模式检测队列,此队列专门用来存储符合模式的字符串起始位置。
esp_err_t uart_pattern_queue_reset(uart_port_t uart_num, int queue_length)
{
// 入口参数检查,UART编号合法且驱动对象已经分配
ESP_RETURN_ON_FALSE((uart_num < UART_NUM_MAX), ESP_FAIL, UART_TAG, "uart_num error");
ESP_RETURN_ON_FALSE((p_uart_obj[uart_num]), ESP_ERR_INVALID_STATE, UART_TAG, "uart driver error");
// 分配模式检测队列中的数组
// 此数组用来记录模式匹配中的下标位置,因此为整数类型
int *pdata = (int *)heap_caps_malloc(queue_length * sizeof(int), UART_MALLOC_CAPS);
if (pdata == NULL) {
return ESP_ERR_NO_MEM;
}
// rx_pattern_pos是用来检测RX通道特殊字符串模式的成员
// typedef struct {
// int wr;
// int rd;
// int len;
// int *data;
// } uart_pat_rb_t rx_pattern_pos;
UART_ENTER_CRITICAL(&(uart_context[uart_num].spinlock));
// 设置模式匹配队列中新的数组
int *ptmp = p_uart_obj[uart_num]->rx_pattern_pos.data;
p_uart_obj[uart_num]->rx_pattern_pos.data = pdata;
// 设置模式匹配队列的长度
p_uart_obj[uart_num]->rx_pattern_pos.len = queue_length;
// 复位读写指针
p_uart_obj[uart_num]->rx_pattern_pos.rd = 0;
p_uart_obj[uart_num]->rx_pattern_pos.wr = 0;
UART_EXIT_CRITICAL(&(uart_context[uart_num].spinlock));
free(ptmp);
return ESP_OK;
}
1.3 使能UART模块时钟
此函数的实现非常简单,就是在硬件上使能UART设备时钟,主要完成的动作分为三步,如下:
- uart_ll_enable_bus_clock:使能UART总线时钟(apb总线时钟)
- uart_ll_reset_register:复位UART寄存器
- uart_ll_sclk_enable:使能UART Core时钟
这三个步骤对于HP UART和LP UART是一致的,下面的代码使用UART编号来区分两者,大于SOC_UART_HP_NUM则为LP UART。
static void uart_module_enable(uart_port_t uart_num)
{
// 加锁保证下面这段代码的互斥性
_lock_acquire(&(uart_context[uart_num].mutex));
// 如果UART还未被初始化
if (uart_context[uart_num].hw_enabled != true) {
if (uart_num < SOC_UART_HP_NUM) {
// 开启UART总线时钟,保证访问的原子性
HP_UART_BUS_CLK_ATOMIC() {
uart_ll_enable_bus_clock(uart_num, true);
}
// 用于ESP_CONSOLE的UART不必复位寄存器和开启Core时钟
if (uart_num != CONFIG_ESP_CONSOLE_UART_NUM) {
// 复位UART所有寄存器
HP_UART_BUS_CLK_ATOMIC() {
uart_ll_reset_register(uart_num);
}
// 开启UART的Core时钟
HP_UART_SRC_CLK_ATOMIC() {
uart_ll_sclk_enable(uart_context[uart_num].hal.dev);
}
}
#if CONFIG_PM_POWER_DOWN_PERIPHERAL_IN_LIGHT_SLEEP
// 功耗控制相关的代码逻辑,这里暂时略去
#endif
}
// 如果UART编号大于SOC_UART_HP_NUM,那么说明要初始化LP_UART
// 步骤和上面是一样的:开启总线时钟->复位寄存器->开启Core时钟
#if (SOC_UART_LP_NUM >= 1)
else {
LP_UART_BUS_CLK_ATOMIC() {
lp_uart_ll_enable_bus_clock(TO_LP_UART_NUM(uart_num), true);
lp_uart_ll_reset_register(TO_LP_UART_NUM(uart_num));
}
lp_uart_ll_sclk_enable(TO_LP_UART_NUM(uart_num));
}
#endif
// 置位UART使能标志
uart_context[uart_num].hw_enabled = true;
}
_lock_release(&(uart_context[uart_num].mutex));
}
1.4 配置UART中断
1.3.1 UART中断的可配置项——uart_intr_config_t
ESP-IDF将UART中可能触发中断的事件中,可由用户配置的部分抽象出来形成了一个结构体uart_intr_config_t,它的定义如下所示,在上述的uart_driver_install函数中已经指定了安装UART驱动时的默认值。
/**
* @brief UART interrupt configuration parameters for uart_intr_config function
*/
typedef struct {
// UART中断使能掩码
uint32_t intr_enable_mask;
// UART超时中断阈值(单位: 发送一个比特位的时间)
// TODO: 这里原先的注释写的单位是一个byte的时间,但貌似源码实现不是...
uint8_t rx_timeout_thresh;
// TX空中断阈值
uint8_t txfifo_empty_intr_thresh;
// RX满中断阈值
uint8_t rxfifo_full_thresh;
} uart_intr_config_t;
1.3.2 配置UART中断——uart_intr_config
此函数接收一个用户指定的uart_intr_config_t配置结构体,根据其中指定的参数(TX空,RX满,RX超时)配置UART硬件,并在最终开启所有选中的UART中断信号:
esp_err_t uart_intr_config(uart_port_t uart_num, const uart_intr_config_t *intr_conf)
{
// 入口参数检查
ESP_RETURN_ON_FALSE((uart_num < UART_NUM_MAX), ESP_FAIL, UART_TAG, "uart_num error");
ESP_RETURN_ON_FALSE((intr_conf), ESP_FAIL, UART_TAG, "param null");
// 清空中断状态
uart_hal_clr_intsts_mask(&(uart_context[uart_num].hal), UART_LL_INTR_MASK);
UART_ENTER_CRITICAL(&(uart_context[uart_num].spinlock));
// 如果打开了RX超时中断,则设置对应的超时阈值
if (intr_conf->intr_enable_mask & UART_INTR_RXFIFO_TOUT) {
uart_hal_set_rx_timeout(&(uart_context[uart_num].hal), intr_conf->rx_timeout_thresh);
// 否则关闭RX超时中断
} else {
//Disable rx_tout intr
uart_hal_set_rx_timeout(&(uart_context[uart_num].hal), 0);
}
// 设置RX满中断阈值
if (intr_conf->intr_enable_mask & UART_INTR_RXFIFO_FULL) {
uart_hal_set_rxfifo_full_thr(&(uart_context[uart_num].hal), intr_conf->rxfifo_full_thresh);
}
// 设置TX空中断阈值
if (intr_conf->intr_enable_mask & UART_INTR_TXFIFO_EMPTY) {
uart_hal_set_txfifo_empty_thr(&(uart_context[uart_num].hal), intr_conf->txfifo_empty_intr_thresh);
}
// 开启选中的中断
uart_hal_ena_intr_mask(&(uart_context[uart_num].hal), intr_conf->intr_enable_mask);
UART_EXIT_CRITICAL(&(uart_context[uart_num].spinlock));
return ESP_OK;
}
1.5 UART中断处理函数(*)——uart_rx_intr_handler_default
这是UART驱动程序的默认驱动代码中的中断处理函数,因为UART支持以事件队列的方式来处理中断,所以下面的代码结构整体上是按照UART事件中断的类型来讨论处理的。出于代码的可读性,以下代码将按照UART中断类型分门别类地进行梳理:
- UART_INTR_TXFIFO_EMPTY中断处理逻辑(*)
- UART_INTR_RXFIFO_TOUT、UART_INTR_RXFIFO_FULL、UART_INTR_CMD_CHAR_DET中断处理逻辑(*)
- UART_INTR_RXFIFO_OVF中断处理逻辑
- UART_INTR_BRK_DET中断处理逻辑
- UART_INTR_FRAM_ERR中断处理逻辑
- UART_INTR_PARITY_ERR中断处理逻辑
- UART_INTR_TX_BRK_DONE中断处理逻辑
- UART_INTR_RS485_PARITY_ERR、UART_INTR_RS485_FRM_ERR、UART_INTR_RS485_CLASH中断处理逻辑
- 其他中断与收尾动作
首先梳理出中断处理函数的骨干架构,它按照UART中断的类型,使用了大量的if…else结构来对中断进行处理,所以请注意本质上排位靠前的中断优先级高于排位靠后的中断,因为前面的操作会
//internal isr handler for default driver code.
static void UART_ISR_ATTR uart_rx_intr_handler_default(void *param)
{
// 传入的参数是一个UART驱动对象
uart_obj_t *p_uart = (uart_obj_t *) param;
uint8_t uart_num = p_uart->uart_num;
int rx_fifo_len = 0;
uint32_t uart_intr_status = 0;
BaseType_t HPTaskAwoken = 0;
bool need_yield = false;
static uint8_t pat_flg = 0;
BaseType_t sent = pdFALSE;
// 中断处理程序采用while(1) break结构
while (1) {
// The `continue statement` may cause the interrupt to loop infinitely
// we exit the interrupt here
// 获取UART中断状态,若没有要处理的中断,则退出循环
uart_intr_status = uart_hal_get_intsts_mask(&(uart_context[uart_num].hal));
//Exit form while loop
if (uart_intr_status == 0) {
break;
}
// 声明一个UART事件,用于下述循环
uart_event_t uart_event = {
.type = UART_EVENT_MAX,
};
/*******************分类讨论不同中断的处理逻辑**********************/
// 如果发生了TX空事件
if (uart_intr_status & UART_INTR_TXFIFO_EMPTY) {
// 处理逻辑略,下同
// RX超时、RX满和检测到CMD_CHAR中断
} else if ((uart_intr_status & UART_INTR_RXFIFO_TOUT)
|| (uart_intr_status & UART_INTR_RXFIFO_FULL)
|| (uart_intr_status & UART_INTR_CMD_CHAR_DET)
) {
// RX FIFO溢出中断
} else if (uart_intr_status & UART_INTR_RXFIFO_OVF) {
// RX检测到NULL字符中断
} else if (uart_intr_status & UART_INTR_BRK_DET) {
// 帧错误中断
} else if (uart_intr_status & UART_INTR_FRAM_ERR) {
// 奇偶校验错误中断
} else if (uart_intr_status & UART_INTR_PARITY_ERR) {
// TX发送完所有数据后并发送完NULL字符后触发中断
} else if (uart_intr_status & UART_INTR_TX_BRK_DONE) {
// TX发送完所有数据后保持了最短的间隔时间时触发中断
} else if (uart_intr_status & UART_INTR_TX_BRK_IDLE) {
// 检测到AT_CMD字符时触发中断
} else if (uart_intr_status & UART_INTR_CMD_CHAR_DET) {
// RS485奇偶校验错误中断、帧错误中断、冲突中断
} else if ((uart_intr_status & UART_INTR_RS485_PARITY_ERR)
|| (uart_intr_status & UART_INTR_RS485_FRM_ERR)
|| (uart_intr_status & UART_INTR_RS485_CLASH)) {
// TX FIFO数据发送完毕的中断
} else if (uart_intr_status & UART_INTR_TX_DONE) {
}
// UART唤醒中断
#if SOC_UART_SUPPORT_WAKEUP_INT
else if (uart_intr_status & UART_INTR_WAKEUP) {
}
#endif
// 所有其他中断
else {
}
// 如果UART事件在上面已经被识别出来
if (uart_event.type != UART_EVENT_MAX && p_uart->event_queue) {
// 则将串口事件发送到UART事件队列
sent = xQueueSendFromISR(p_uart->event_queue, (void *)&uart_event, &HPTaskAwoken);
need_yield |= (HPTaskAwoken == pdTRUE);
if (sent == pdFALSE) {
#ifndef CONFIG_UART_ISR_IN_IRAM //Only log if ISR is not in IRAM
ESP_EARLY_LOGV(UART_TAG, "UART event queue full");
#endif
}
}
} // end of while(1)
// 如果有更高优先级的任务正在等待UART队列,则直接跳入高优先级任务
if (need_yield) {
portYIELD_FROM_ISR();
}
}
1.4.1 UART_INTR_TXFIFO_EMPTY中断处理逻辑
这是对UART_INTR_TXFIFO_EMPTY中断的处理逻辑,简单来说它执行的代码逻辑就是尝试,详细如下如下:
- 判断:是否有任务正在等待FIFO空中断,且是否RingBuffer已经清空
- 若是,可以用信号量通知正在等待的任务TX FIFO已空
- 判断:RingBuffer是否还有缓存的数据没有发送到TX FIFO
- 若是,尝试将RingBuffer中的数据填充到TX FIFO中来发送,直至将剩余空间tx_fifo_rem完全填满
- 否则,说明没有数据要发送,直接退出
其中第4步还涉及到解析UART数据包头、发送BREAK位等动作细节,详情见下:
if (uart_intr_status & UART_INTR_TXFIFO_EMPTY) {
// 关闭并清理UART_INTR_TXFIFO_EMPTY中断
UART_ENTER_CRITICAL_ISR(&(uart_context[uart_num].spinlock));
uart_hal_disable_intr_mask(&(uart_context[uart_num].hal), UART_INTR_TXFIFO_EMPTY);
UART_EXIT_CRITICAL_ISR(&(uart_context[uart_num].spinlock));
uart_hal_clr_intsts_mask(&(uart_context[uart_num].hal), UART_INTR_TXFIFO_EMPTY);
// 如果当前正在等待BREAK信号发送完毕,则直接进入下一次循环
if (p_uart->tx_waiting_brk) {
continue;
}
// 如有任务正在等待UART TX FIFO为空的中断
// 且TX Ring Buffer大小为0(没有软件缓冲区)
if (p_uart->tx_waiting_fifo == true && p_uart->tx_buf_size == 0) {
// 将标志置为假,表明现在TX FIFO已空,任务无需等待
// <!> 这里的逻辑和uart_write_bytes对应
p_uart->tx_waiting_fifo = false;
// 发送信号量,通知外界任务TX FIFO已空
// <!> 这里的逻辑和uart_write_bytes对应
xSemaphoreGiveFromISR(p_uart->tx_fifo_sem, &HPTaskAwoken);
need_yield |= (HPTaskAwoken == pdTRUE);
// 也即if(p_uart->tx_waiting_fifo == false || p_uart->tx_buf_size != 0)
// 如果没有任务在等待TX FIFO变空,或存在Tx Ringbuffer
} else {
// 如果没有Ringbuffer,直接进入下一轮循环
if (p_uart->tx_buf_size == 0) {
continue;
}
// 得到TX FIFO剩余的空间大小
bool en_tx_flg = false;
uint32_t tx_fifo_rem = uart_hal_get_txfifo_len(&(uart_context[uart_num].hal));
while (tx_fifo_rem) {
// 如果还并未尝试向外输出数据
if (p_uart->tx_len_tot == 0 || p_uart->tx_ptr == NULL || p_uart->tx_len_cur == 0) {
size_t size;
// 从Ringbuffer中获取一个数据项(item),其类型为uart_tx_data_t
// typedef struct {
// uart_event_type_t type; /*!< UART TX data type */
// struct {
// int brk_len;
// size_t size;
// uint8_t data[0];
// } tx_data;
// } uart_tx_data_t;
p_uart->tx_head = (uart_tx_data_t *) xRingbufferReceiveFromISR(p_uart->tx_ring_buf, &size);
// 如果读到了数据头,其中包含着数据信息,解析之
if (p_uart->tx_head) {
// 总大小没有设置,表明这是最开始的一个包
// 根据包头设置要发送的数据总大小
if (p_uart->tx_len_tot == 0) {
p_uart->tx_ptr = NULL;
p_uart->tx_len_tot = p_uart->tx_head->tx_data.size;
// 如果此数据包需要在传输完成后发送BREAK信号
// 则设置对应的标志位,且设置BREAK长度
if (p_uart->tx_head->type == UART_DATA_BREAK) {
p_uart->tx_brk_flg = 1;
p_uart->tx_brk_len = p_uart->tx_head->tx_data.brk_len;
}
//We have saved the data description from the 1st item, return buffer.
// 返回(Return)这个数据项到Ringbuffer,相当于从Ringbuffer中删去此数据项
// receive = 引用一个对象, return = 归还一个对象(释放内存),详细请见:
// https://esp32.com/viewtopic.php?t=30345
vRingbufferReturnItemFromISR(p_uart->tx_ring_buf, p_uart->tx_head, &HPTaskAwoken);
need_yield |= (HPTaskAwoken == pdTRUE);
// if (p_uart->tx_len_tot == 0)
// 如果数据总大小已确认,说明包头已被解析
// 当前解析的应该是数据包
} else if (p_uart->tx_ptr == NULL) {
//Update the TX item pointer, we will need this to return item to buffer.
// 记录读写指针为当前数据包头部
p_uart->tx_ptr = (uint8_t *)p_uart->tx_head;
en_tx_flg = true;
// 记录下当前数据包的长度
p_uart->tx_len_cur = size;
}
// if (p_uart->tx_head)失败
// 没有从TX Ringbuffer中提取有效数据,说明RingBuffer空了
} else {
//Can not get data from ring buffer, return;
break;
}
} // end of if (p_uart->tx_len_tot == 0 || p_uart->tx_ptr == NULL || p_uart->tx_len_cur == 0)
// 如果要发送的数据包总长、当前数据包总长均大于0,且tx_ptr不为空
// 表明当前有数据等待发送
if (p_uart->tx_len_tot > 0 && p_uart->tx_ptr && p_uart->tx_len_cur > 0) {
// 将Ringbuffer中的数据,填入UART TX FIFO,发送之
uint32_t send_len = uart_enable_tx_write_fifo(uart_num, (const uint8_t *) p_uart->tx_ptr,
MIN(p_uart->tx_len_cur, tx_fifo_rem));
// 更新读写指针和计数变量
p_uart->tx_ptr += send_len;
p_uart->tx_len_tot -= send_len;
p_uart->tx_len_cur -= send_len;
tx_fifo_rem -= send_len;
// 如果当前数据包已全部发送完毕
if (p_uart->tx_len_cur == 0) {
//Return item to ring buffer.
// 释放对应的Ringbuffer缓冲区
vRingbufferReturnItemFromISR(p_uart->tx_ring_buf, p_uart->tx_head, &HPTaskAwoken);
need_yield |= (HPTaskAwoken == pdTRUE);
// 复位头指针和发送指针
p_uart->tx_head = NULL;
p_uart->tx_ptr = NULL;
// 如果完整数据包也已经发送完毕,且需要发送BREAK符号
// 说明tx_fifo_rem >= tx_len_tot
if (p_uart->tx_len_tot == 0 && p_uart->tx_brk_flg == 1) {
// 清UART_INTR_TX_BRK_DONE标志
uart_hal_clr_intsts_mask(&(uart_context[uart_num].hal), UART_INTR_TX_BRK_DONE);
// 发送BREAK符号
UART_ENTER_CRITICAL_ISR(&(uart_context[uart_num].spinlock));
uart_hal_tx_break(&(uart_context[uart_num].hal), p_uart->tx_brk_len);
uart_hal_ena_intr_mask(&(uart_context[uart_num].hal), UART_INTR_TX_BRK_DONE);
UART_EXIT_CRITICAL_ISR(&(uart_context[uart_num].spinlock));
// 置位:正在发送BREAK信号,不要再向FIFO中填充数据
p_uart->tx_waiting_brk = 1;
// 此种情况下还不能打开UART_INTR_TXFIFO_EMPTY中断
// 因为BREAK符号还没有发送完毕,不能注入新的数据
en_tx_flg = false;
// end of if (p_uart->tx_len_tot == 0 && p_uart->tx_brk_flg == 1)
// 否则要打开UART_INTR_TXFIFO_EMPTY中断
} else {
//enable TX empty interrupt
en_tx_flg = true;
}
UART_ENTER_CRITICAL_ISR(&uart_selectlock);
// 调用select事件提醒回调函数,暂不深究
if (p_uart->uart_select_notif_callback) {
p_uart->uart_select_notif_callback(uart_num, UART_SELECT_WRITE_NOTIF, &HPTaskAwoken);
need_yield |= (HPTaskAwoken == pdTRUE);
}
UART_EXIT_CRITICAL_ISR(&uart_selectlock);
// end of if (p_uart->tx_len_cur == 0)
// 如果当前数据包还没有发送完毕
} else {
//enable TX empty interrupt
en_tx_flg = true;
}
} // end of if (p_uart->tx_len_tot > 0 && p_uart->tx_ptr && p_uart->tx_len_cur > 0)
} // end of while (tx_fifo_rem)
// 清UART_INTR_TXFIFO_EMPTY中断并再次使能
// 以上的情况中,只有发送完所有数据且正在发送BREAK信号时,不需要再次开UART_INTR_TXFIFO_EMPTY中断
// 其他情况均再次打开TX FIFO EMPTY中断
if (en_tx_flg) {
uart_hal_clr_intsts_mask(&(uart_context[uart_num].hal), UART_INTR_TXFIFO_EMPTY);
UART_ENTER_CRITICAL_ISR(&(uart_context[uart_num].spinlock));
uart_hal_ena_intr_mask(&(uart_context[uart_num].hal), UART_INTR_TXFIFO_EMPTY);
UART_EXIT_CRITICAL_ISR(&(uart_context[uart_num].spinlock));
}
}
}
1.4.2 UART_INTR_RXFIFO_TOUT、UART_INTR_RXFIFO_FULL、UART_INTR_CMD_CHAR_DET中断处理逻辑
这段代码处理了三种类型的的UART中断,包括RX超时中断、RX FIFO满事件以及检测到AT_CMD模式的中断,前两种中断比较好理解,最后一种中断是接收通道检测到了符合某种特殊模式的字符串时会产生的中断。以下是这三种中断的处理逻辑,主要来说它始终在尝试将RX FIFO中已有的数据向上呈递到RX端的RingBuffer中:
- 首先将FIFO中的数据读取到一个临时缓冲区(p_uart->rx_data_buf)中——[请注意p_uart->rx_data_buf是独立于RingBuffer的另一个软件缓冲区,它的存在使得对FIFO数据进行预处理更加方便]
- 在临时缓冲区中尝试进行模式匹配,检查是否有出现符合模式(pattern)的字符串出现
- 尝试将临时缓冲区的数据送入RX RingBuffer,如有必要,在rx_pattern_pos数组中记录匹配到的字符串的起始位置
- 如果发送失败,说明RX RingBuffer已满,关闭UART_INTR_RXFIFO_TOUT、UART_INTR_RXFIFO_FULL中断
- 反之发送成功,数据进入RingBuffer等待进一步处理
} else if ((uart_intr_status & UART_INTR_RXFIFO_TOUT)
|| (uart_intr_status & UART_INTR_RXFIFO_FULL)
|| (uart_intr_status & UART_INTR_CMD_CHAR_DET)
) {
// 如果pat_flg为1,表明检测到了AT CMD模式
if (pat_flg == 1) {
uart_intr_status |= UART_INTR_CMD_CHAR_DET;
pat_flg = 0;
}
// 如果RX RingBuffer没有满
if (p_uart->rx_buffer_full_flg == false) {
// 获取RX FIFO的数据字节数量
rx_fifo_len = uart_hal_get_rxfifo_len(&(uart_context[uart_num].hal));
// 如果当前UART经常检查到RX超时,但当前还没有触发超时中断
// 刻意留一个字节,故意触发一个超时中断
if ((p_uart_obj[uart_num]->rx_always_timeout_flg) && !(uart_intr_status & UART_INTR_RXFIFO_TOUT)) {
rx_fifo_len--; // leave one byte in the fifo in order to trigger uart_intr_rxfifo_tout
}
// 将RX FIFO中数据读取到p_uart->rx_data_buf
// [这是RX FIFO的一块额外的软件缓冲区]
uart_hal_read_rxfifo(&(uart_context[uart_num].hal), p_uart->rx_data_buf, &rx_fifo_len);
uint8_t pat_chr = 0;
uint8_t pat_num = 0;
int pat_idx = -1;
// 获取当前UART配置的AT CMD字符和约定的字符重复次数
uart_hal_get_at_cmd_char(&(uart_context[uart_num].hal), &pat_chr, &pat_num);
// Get the buffer from the FIFO
// 如果是AT CMD中断被触发
if (uart_intr_status & UART_INTR_CMD_CHAR_DET) {
// 清中断
uart_hal_clr_intsts_mask(&(uart_context[uart_num].hal), UART_INTR_CMD_CHAR_DET);
// 设置UART事件的类型和数据量大小
uart_event.type = UART_PATTERN_DET;
uart_event.size = rx_fifo_len;
// 倒序遍历,找到AT_CMD模式出现的第一个索引下标
pat_idx = uart_find_pattern_from_last(p_uart->rx_data_buf, rx_fifo_len - 1, pat_chr, pat_num);
// end of if (uart_intr_status & UART_INTR_CMD_CHAR_DET)
// 否则是RX FIFO超时或RX FIFO满中断
} else{
//After Copying the Data From FIFO ,Clear intr_status
// 清掉UART_INTR_RXFIFO_TOUT、UART_INTR_RXFIFO_FULL中断
uart_hal_clr_intsts_mask(&(uart_context[uart_num].hal),
UART_INTR_RXFIFO_TOUT | UART_INTR_RXFIFO_FULL);
// 设置UART事件类型为UART_DATA和数据量大小
uart_event.type = UART_DATA;
uart_event.size = rx_fifo_len;
uart_event.timeout_flag = (uart_intr_status & UART_INTR_RXFIFO_TOUT) ? true : false;
}
// 更新贮藏长度为从RX FIFO读出的数据量
// 此时数据全部被贮藏在p_uart->rx_data_buf,它是硬件FIFO的平替
p_uart->rx_stash_len = rx_fifo_len;
// If we fail to push data to ring buffer, we will have to stash the data, and send next time.
// Mainly for applications that uses flow control or small ring buffer.
// 尝试将贮藏的数据rx_data_buf插入到RX RingBuffer
sent = xRingbufferSendFromISR(p_uart->rx_ring_buf, p_uart->rx_data_buf, p_uart->rx_stash_len, &HPTaskAwoken);
need_yield |= (HPTaskAwoken == pdTRUE);
// 如果rx_data_buf数据插入RingBuffer失败,说明RX RingBuffer已满
if (sent == pdFALSE) {
// <!> 更新rx_buffer_full_flg为已满状态
// <!> 此标记会在读取数据的函数uart_read_bytes中被检查
p_uart->rx_buffer_full_flg = true;
UART_ENTER_CRITICAL_ISR(&(uart_context[uart_num].spinlock));
// 关闭中断UART_INTR_RXFIFO_TOUT和UART_INTR_RXFIFO_FULL中断
uart_hal_disable_intr_mask(&(uart_context[uart_num].hal),
UART_INTR_RXFIFO_TOUT | UART_INTR_RXFIFO_FULL);
UART_EXIT_CRITICAL_ISR(&(uart_context[uart_num].spinlock));
// 如果是检测到了AT CMD而产生的中断
// 将模式匹配开头的字符记录到队列(rx_pattern_pos)中
if (uart_event.type == UART_PATTERN_DET) {
UART_ENTER_CRITICAL_ISR(&(uart_context[uart_num].spinlock));
// 若贮藏的数据长度小于AT CMD指定的字符数
// 说明有些字符在上次中断中已经被读走了
if (rx_fifo_len < pat_num) {
// some of the characters are read out in last interrupt
// 若缓冲数据长度小于AT CMD指定的字符长度,则起始位置一定在之前读走的RingBuffer中
uart_pattern_enqueue(uart_num, p_uart->rx_buffered_len - (pat_num - rx_fifo_len));
} else {
// 反之,
// 此种情况下匹配到的字符串应该在完全临时缓冲区(p_uart->rx_data_buf)中
uart_pattern_enqueue(uart_num,
pat_idx <= -1 ?
//can not find the pattern in buffer,
// 如果没找到,则将指针指向数据结尾位置
p_uart->rx_buffered_len + p_uart->rx_stash_len :
// find the pattern in buffer
p_uart->rx_buffered_len + pat_idx);
}
UART_EXIT_CRITICAL_ISR(&(uart_context[uart_num].spinlock));
// 将生成的UART_PATTERN_DET事件发送到UART的事件队列
// 这里将发送动作提前了,因为这里其实还隐藏着一个UART_BUFFER_FULL事件
// 见下面的uart_event.type = UART_BUFFER_FULL;替换动作
sent = xQueueSendFromISR(p_uart->event_queue, (void *)&uart_event, &HPTaskAwoken);
need_yield |= (HPTaskAwoken == pdTRUE);
// 如果入队失败,则说明UART事件队列已满
if ((p_uart->event_queue != NULL) && (sent == pdFALSE)) {
#ifndef CONFIG_UART_ISR_IN_IRAM //Only log if ISR is not in IRAM
ESP_EARLY_LOGV(UART_TAG, "UART event queue full");
#endif
}
}
// UART事件类型替换为缓冲区已满
uart_event.type = UART_BUFFER_FULL;
// end of if (sent == pdFALSE)
// 说明RX RingBuffer还没有满
} else {
UART_ENTER_CRITICAL_ISR(&(uart_context[uart_num].spinlock));
// 如果是AT CMD中断,像上面一样找字符匹配到的初始位置
if (uart_intr_status & UART_INTR_CMD_CHAR_DET) {
// 若缓冲数据长度小于AT CMD指定的字符长度,则起始位置一定在RingBuffer中
if (rx_fifo_len < pat_num) {
//some of the characters are read out in last interrupt
uart_pattern_enqueue(uart_num, p_uart->rx_buffered_len - (pat_num - rx_fifo_len));
} else if (pat_idx >= 0) {
// find the pattern in stash buffer.
uart_pattern_enqueue(uart_num, p_uart->rx_buffered_len + pat_idx);
}
}
// 更新已缓冲在RingBuffer中的数据长度
p_uart->rx_buffered_len += p_uart->rx_stash_len;
UART_EXIT_CRITICAL_ISR(&(uart_context[uart_num].spinlock));
}
// 调用select事件提醒函数,这里暂不深究
if (uart_event.type == UART_DATA) {
UART_ENTER_CRITICAL_ISR(&uart_selectlock);
if (p_uart->uart_select_notif_callback) {
p_uart->uart_select_notif_callback(uart_num, UART_SELECT_READ_NOTIF, &HPTaskAwoken);
need_yield |= (HPTaskAwoken == pdTRUE);
}
UART_EXIT_CRITICAL_ISR(&uart_selectlock);
}
// end of if (p_uart->rx_buffer_full_flg == false)
// 如果RX RingBuffer已满
} else {
// 关闭UART_INTR_RXFIFO_FULL、UART_INTR_RXFIFO_TOUT中断
UART_ENTER_CRITICAL_ISR(&(uart_context[uart_num].spinlock));
uart_hal_disable_intr_mask(&(uart_context[uart_num].hal),
UART_INTR_RXFIFO_FULL | UART_INTR_RXFIFO_TOUT);
UART_EXIT_CRITICAL_ISR(&(uart_context[uart_num].spinlock));
// 清掉UART_INTR_RXFIFO_FULL、UART_INTR_RXFIFO_TOUT中断标记
uart_hal_clr_intsts_mask(&(uart_context[uart_num].hal), UART_INTR_RXFIFO_FULL | UART_INTR_RXFIFO_TOUT);
// 如果检测到了AT CMD,则触发一个UART_PATTERN_DET事件
if (uart_intr_status & UART_INTR_CMD_CHAR_DET) {
uart_hal_clr_intsts_mask(&(uart_context[uart_num].hal), UART_INTR_CMD_CHAR_DET);
uart_event.type = UART_PATTERN_DET;
uart_event.size = rx_fifo_len;
pat_flg = 1;
}
}
在上述的函数中,调用了uart_pattern_enqueue函数,此函数专门用来向UART的模式匹配队列中记录一个匹配到的字符串的开头位置,其代码实现如下,由于代码逻辑非常简单,这里只做一个简单的注解。
static esp_err_t UART_ISR_ATTR uart_pattern_enqueue(uart_port_t uart_num, int pos)
{
esp_err_t ret = ESP_OK;
// 取出UART驱动对象中的模式检测队列
uart_pat_rb_t *p_pos = &p_uart_obj[uart_num]->rx_pattern_pos;
// 得到下一个要写入的数组位置
int next = p_pos->wr + 1;
if (next >= p_pos->len) {
next = 0;
}
// 如果将要写入的下一个位置与读指针重合
// 说明此队列已满,输出一个INFO
if (next == p_pos->rd) {
#ifndef CONFIG_UART_ISR_IN_IRAM //Only log if ISR is not in IRAM
ESP_EARLY_LOGW(UART_TAG, "Fail to enqueue pattern position, pattern queue is full.");
#endif
ret = ESP_FAIL;
// 否则将当前位置记录在数组中,并更新写指针的位置
} else {
p_pos->data[p_pos->wr] = pos;
p_pos->wr = next;
ret = ESP_OK;
}
return ret;
}
1.4.3 UART_INTR_RXFIFO_OVF中断处理逻辑
当UART RX FIFO发生溢出错误时,UART_INTR_RXFIFO_OVF中断就会置位,处理UART_INTR_RXFIFO_OVF中断的逻辑非常简单,就是将RX FIFO直接重置,并清掉对应的中断。
} else if (uart_intr_status & UART_INTR_RXFIFO_OVF) {
// When fifo overflows, we reset the fifo.
// 重置RX FIFO
UART_ENTER_CRITICAL_ISR(&(uart_context[uart_num].spinlock));
uart_hal_rxfifo_rst(&(uart_context[uart_num].hal));
UART_EXIT_CRITICAL_ISR(&(uart_context[uart_num].spinlock));
// select事件提醒回调函数,暂时略过这段...
UART_ENTER_CRITICAL_ISR(&uart_selectlock);
if (p_uart->uart_select_notif_callback) {
p_uart->uart_select_notif_callback(uart_num, UART_SELECT_ERROR_NOTIF, &HPTaskAwoken);
need_yield |= (HPTaskAwoken == pdTRUE);
}
// 清掉UART_INTR_RXFIFO_OVF中断
UART_EXIT_CRITICAL_ISR(&uart_selectlock);
uart_hal_clr_intsts_mask(&(uart_context[uart_num].hal), UART_INTR_RXFIFO_OVF);
uart_event.type = UART_FIFO_OVF;
1.4.4 UART_INTR_BRK_DET中断处理逻辑
当UART检测到了BREAK符号时,UART_INTR_BRK_DET位就会被置位,处理UART_INTR_BRK_DET中断的逻辑也非常直接,就是清掉对应的中断位即可。
} else if (uart_intr_status & UART_INTR_BRK_DET) {
uart_hal_clr_intsts_mask(&(uart_context[uart_num].hal), UART_INTR_BRK_DET);
uart_event.type = UART_BREAK;
1.4.5 UART_INTR_FRAM_ERR中断处理逻辑
UART在检测到帧错误时会触发此中断,处理逻辑和之前的UART_INTR_BRK_DET中断是十分相似的,清理对应的中断位即可。
} else if (uart_intr_status & UART_INTR_FRAM_ERR) {
UART_ENTER_CRITICAL_ISR(&uart_selectlock);
if (p_uart->uart_select_notif_callback) {
p_uart->uart_select_notif_callback(uart_num, UART_SELECT_ERROR_NOTIF, &HPTaskAwoken);
need_yield |= (HPTaskAwoken == pdTRUE);
}
UART_EXIT_CRITICAL_ISR(&uart_selectlock);
// 清掉UART_INTR_FRAM_ERR中断位
uart_hal_clr_intsts_mask(&(uart_context[uart_num].hal), UART_INTR_FRAM_ERR);
uart_event.type = UART_FRAME_ERR;
1.4.6 UART_INTR_PARITY_ERR中断处理逻辑
UART在检测到奇偶校验位出错时会触发此中断,处理逻辑和之前一样,清理对应的中断位即可。
} else if (uart_intr_status & UART_INTR_PARITY_ERR) {
UART_ENTER_CRITICAL_ISR(&uart_selectlock);
if (p_uart->uart_select_notif_callback) {
p_uart->uart_select_notif_callback(uart_num, UART_SELECT_ERROR_NOTIF, &HPTaskAwoken);
need_yield |= (HPTaskAwoken == pdTRUE);
}
UART_EXIT_CRITICAL_ISR(&uart_selectlock);
uart_hal_clr_intsts_mask(&(uart_context[uart_num].hal), UART_INTR_PARITY_ERR);
uart_event.type = UART_PARITY_ERR;
1.4.7 UART_INTR_TX_BRK_DONE中断处理逻辑
此中断当TX FIFO所有数据发送完毕,且BREAK信号也发送完毕之后就会触发此中断,UART_INTR_TX_BRK_DONE中断对应的处理逻辑是:
- 停止BREAK信号的传输(uart_hal_tx_break),重新打开UART_INTR_TXFIFO_EMPTY中断
- 清掉UART_INTR_TX_BRK_DONE中断位,复位相关标志
- 发送信号量提醒外部正在等待的任务
} else if (uart_intr_status & UART_INTR_TX_BRK_DONE) {
// 让UART不再发送BREAK信号
UART_ENTER_CRITICAL_ISR(&(uart_context[uart_num].spinlock));
uart_hal_tx_break(&(uart_context[uart_num].hal), 0);
uart_hal_disable_intr_mask(&(uart_context[uart_num].hal), UART_INTR_TX_BRK_DONE);
// 如果TX需要在数据发送完毕后发送NULL字符
// 则打开UART_INTR_TXFIFO_EMPTY中断,这和上面处理UART_INTR_TXFIFO_EMPTY中断时逻辑是对应的
if (p_uart->tx_brk_flg == 1) {
uart_hal_ena_intr_mask(&(uart_context[uart_num].hal), UART_INTR_TXFIFO_EMPTY);
}
UART_EXIT_CRITICAL_ISR(&(uart_context[uart_num].spinlock));
// 清掉UART_INTR_TX_BRK_DONE中断位
uart_hal_clr_intsts_mask(&(uart_context[uart_num].hal), UART_INTR_TX_BRK_DONE);
// 如果BRK字符是通过RingBuffer缓冲后输出的
// 回忆:
// tx_brk_flg表示TX在数据发送完毕后应该发送BREAK信号
// tx_waiting_brk表示当前正在等待BREAK信号发送完毕
if (p_uart->tx_brk_flg == 1) {
p_uart->tx_brk_flg = 0;
p_uart->tx_waiting_brk = 0;
// 如果未经TX RingBuffer缓冲,而是通过TX FIFO直接发送的BRK信号
// 则发送一个信号量,提醒BREAK已经发送完毕
// TODO: 按照这个代码逻辑,第一次进来的时候并不会发送信号量?
} else {
xSemaphoreGiveFromISR(p_uart->tx_brk_sem, &HPTaskAwoken);
need_yield |= (HPTaskAwoken == pdTRUE);
}
1.4.8 UART_INTR_TX_BRK_IDLE中断处理逻辑
UART_INTR_TX_BRK_IDLE中断会在发送完最后一个数据之后且等待最短间隔时间时触发,它的中断处理逻辑也非常简单,就是将UART_INTR_TX_BRK_IDLE中断关闭,且清掉其中断位。
} else if (uart_intr_status & UART_INTR_TX_BRK_IDLE) {
UART_ENTER_CRITICAL_ISR(&(uart_context[uart_num].spinlock));
uart_hal_disable_intr_mask(&(uart_context[uart_num].hal), UART_INTR_TX_BRK_IDLE);
UART_EXIT_CRITICAL_ISR(&(uart_context[uart_num].spinlock));
uart_hal_clr_intsts_mask(&(uart_context[uart_num].hal), UART_INTR_TX_BRK_IDLE);
1.4.9 UART_INTR_CMD_CHAR_DET中断处理逻辑
这个中断我没啥好说的,上面已经处理过了,不知道为什么要在这里再处理一遍…可能是程序员起猛了 😃
} else if (uart_intr_status & UART_INTR_CMD_CHAR_DET) {
uart_hal_clr_intsts_mask(&(uart_context[uart_num].hal), UART_INTR_CMD_CHAR_DET);
uart_event.type = UART_PATTERN_DET;
1.4.10 UART_INTR_RS485_PARITY_ERR、UART_INTR_RS485_FRM_ERR、UART_INTR_RS485_CLASH中断处理逻辑
这是RS485模式下可能出现的三种错误,出现相关错误时应当清空对应的中断,并将RX FIFO复位,coll_det_flg标志置位表示检测到了RS485冲突。
} else if ((uart_intr_status & UART_INTR_RS485_PARITY_ERR)
|| (uart_intr_status & UART_INTR_RS485_FRM_ERR)
|| (uart_intr_status & UART_INTR_RS485_CLASH)) {
// RS485 collision or frame error interrupt triggered
UART_ENTER_CRITICAL_ISR(&(uart_context[uart_num].spinlock));
// 复位RX FIFO
uart_hal_rxfifo_rst(&(uart_context[uart_num].hal));
// Set collision detection flag
// 置位冲突检测标志
p_uart_obj[uart_num]->coll_det_flg = true;
UART_EXIT_CRITICAL_ISR(&(uart_context[uart_num].spinlock));
// 清中断
uart_hal_clr_intsts_mask(&(uart_context[uart_num].hal), UART_INTR_RS485_CLASH | UART_INTR_RS485_FRM_ERR | UART_INTR_RS485_PARITY_ERR);
1.4.11 UART_INTR_TX_DONE中断处理逻辑
UART_INTR_TX_DONE中断在UART发送完所有TX FIFO中的数据之后就会触发,在下面的处理逻辑中主要完成的事情就是清空并关闭UART_INTR_TX_DONE中断、使用信号量提醒外部正在等待TX传输任务完成的任务。
当传输模式为RS485半双工时需要做一些额外的操作:复位RX FIFO并组织对端进一步发送数据,这可能是因为
} else if (uart_intr_status & UART_INTR_TX_DONE) {
// 如果当前UART处于RS485半双工工作模式,且TX发送通道并没有空闲
// 则什么也不做,等待下一轮处理中断
if (UART_IS_MODE_SET(uart_num, UART_MODE_RS485_HALF_DUPLEX) && uart_hal_is_tx_idle(&(uart_context[uart_num].hal)) != true) {
// The TX_DONE interrupt is triggered but transmit is active
// then postpone interrupt processing for next interrupt
uart_event.type = UART_EVENT_MAX;
} else {
// Workaround for RS485: If the RS485 half duplex mode is active
// and transmitter is in idle state then reset received buffer and reset RTS pin
// skip this behavior for other UART modes
// 清掉UART_INTR_TX_DONE中断
uart_hal_clr_intsts_mask(&(uart_context[uart_num].hal), UART_INTR_TX_DONE);
// 关闭UART_INTR_TX_DONE中断
UART_ENTER_CRITICAL_ISR(&(uart_context[uart_num].spinlock));
uart_hal_disable_intr_mask(&(uart_context[uart_num].hal), UART_INTR_TX_DONE);
// 当传输模式为RS485半双工时,复位RX通道
// 同时设置rts流控信号为高,表示禁止对端继续发送数据
if (UART_IS_MODE_SET(uart_num, UART_MODE_RS485_HALF_DUPLEX)) {
uart_hal_rxfifo_rst(&(uart_context[uart_num].hal));
uart_hal_set_rts(&(uart_context[uart_num].hal), 1);
}
// 通知正在等待信号量的任务
UART_EXIT_CRITICAL_ISR(&(uart_context[uart_num].spinlock));
xSemaphoreGiveFromISR(p_uart_obj[uart_num]->tx_done_sem, &HPTaskAwoken);
need_yield |= (HPTaskAwoken == pdTRUE);
}
}
1.4.12 其他中断与收尾动作
对于上述代码中的其他中断,这里仅仅是清理了其中断标志位。最后,在整个中断处理程序的结尾处,将识别到的中断事件类型送入队列,供外部任务进一步处理。
// 对于其他中断,仅仅需要清空中断标志
else {
/*simply clear all other intr status*/
uart_hal_clr_intsts_mask(&(uart_context[uart_num].hal), uart_intr_status);
uart_event.type = UART_EVENT_MAX;
}
// 最后,如果事件类型在上面已经被成功识别出来,则送入事件队列中
if (uart_event.type != UART_EVENT_MAX && p_uart->event_queue) {
sent = xQueueSendFromISR(p_uart->event_queue, (void *)&uart_event, &HPTaskAwoken);
need_yield |= (HPTaskAwoken == pdTRUE);
if (sent == pdFALSE) {
#ifndef CONFIG_UART_ISR_IN_IRAM //Only log if ISR is not in IRAM
ESP_EARLY_LOGV(UART_TAG, "UART event queue full");
#endif
}
}
}
if (need_yield) {
portYIELD_FROM_ISR();
}
}
2.对UART通信参数进行配置——uart_param_config
uart_param_config函数用来供用户对UART串口通信过程的参数进行配置,首先对配置参数结构体uart_config_t进行解读。
2.1 可供用户配置的UART参数——uart_config_t
IDF中将UART可配置的一些参数抽象成了一个结构体uart_config_t,它的各个字段及其含义展示如下:
typedef struct {
int baud_rate; /*!< UART 波特率 */
uart_word_length_t data_bits; /*!< UART 数据位长度 */
uart_parity_t parity; /*!< UART 校验模式 */
uart_stop_bits_t stop_bits; /*!< UART 停止位 */
uart_hw_flowcontrol_t flow_ctrl; /*!< UART 硬件流控模式 (CTS/RTS) */
uint8_t rx_flow_ctrl_thresh; /*!< UART 硬件 RTS 触发阈值 */
union {
uart_sclk_t source_clk; /*!< UART 时钟源选择 */
#if (SOC_UART_LP_NUM >= 1)
lp_uart_sclk_t lp_source_clk; /*!< 低功耗 UART (LP_UART) 时钟源选择 */
#endif
};
struct {
uint32_t allow_pd: 1; /*!< 若设置此位,驱动程序允许在系统进入睡眠模式时关闭 UART 的电源域。
这样可以节省功耗,但会消耗更多的 RAM 以保存寄存器上下文。 */
uint32_t backup_before_sleep: 1; /*!< @deprecated(已弃用),与 allow_pd 含义相同 */
} flags; /*!< 配置标志位 */
} uart_config_t;
2.2 对UART通信参数进行配置——uart_param_config
和之前一样,首先给出函数调用流程图,对于其中一些比较简单的配置寄存器动作,这里不再展开。
/ ************************************************************ /
// 配置UART通信参数
uart_param_config
// 使能UART模块时钟(貌似操作重复了)
uart_module_enable
// 初始化UART硬件模块
uart_hal_init
// 设置UART CORE时钟
uart_hal_set_sclk
// 设置UART 波特率
uart_hal_set_baudrate
// 设置奇偶校验模式
uart_hal_set_parity
// 设置数据位的位数
uart_hal_set_data_bit_num
// 设置停止位的位数
uart_hal_set_stop_bits
// 设置空闲位的位数
uart_hal_set_tx_idle_num
// 设置硬件流控及其阈值
uart_hal_set_hw_flow_ctrl
// 复位TX/RX FIFO
uart_hal_rxfifo_rst
uart_hal_txfifo_rst
/ ************************************************************ /
完整的源代码及其注解如下:
esp_err_t uart_param_config(uart_port_t uart_num, const uart_config_t *uart_config)
{
// 入口参数检查
ESP_RETURN_ON_FALSE((uart_num < UART_NUM_MAX), ESP_FAIL, UART_TAG, "uart_num error");
ESP_RETURN_ON_FALSE((uart_config), ESP_FAIL, UART_TAG, "param null");
ESP_RETURN_ON_FALSE((uart_config->rx_flow_ctrl_thresh < UART_HW_FIFO_LEN(uart_num)), ESP_FAIL, UART_TAG, "rx flow thresh error");
ESP_RETURN_ON_FALSE((uart_config->flow_ctrl < UART_HW_FLOWCTRL_MAX), ESP_FAIL, UART_TAG, "hw_flowctrl mode error");
ESP_RETURN_ON_FALSE((uart_config->data_bits < UART_DATA_BITS_MAX), ESP_FAIL, UART_TAG, "data bit error");
bool allow_pd __attribute__((unused)) = (uart_config->flags.allow_pd || uart_config->flags.backup_before_sleep);
#if !SOC_UART_SUPPORT_SLEEP_RETENTION
ESP_RETURN_ON_FALSE(allow_pd == 0, ESP_ERR_NOT_SUPPORTED, UART_TAG, "not able to power down in light sleep");
#endif
// 使能UART模块时钟(总线时钟和SCLK)
// (这个操作貌似在分配UART驱动对象时已经做过了?)
uart_module_enable(uart_num);
#if SOC_UART_SUPPORT_SLEEP_RETENTION && CONFIG_PM_POWER_DOWN_PERIPHERAL_IN_LIGHT_SLEEP
// 低功耗处理的相关代码,从略
#endif
// HP UART时钟源选择,优先选择配置中的时钟源,否则选择默认时钟源(SOC_MOD_CLK_PLL_F80M)
soc_module_clk_t uart_sclk_sel = 0; // initialize to an invalid module clock ID
if (uart_num < SOC_UART_HP_NUM) {
// if no specifying the clock source (soc_module_clk_t starts from 1), then just use the default clock
uart_sclk_sel = (soc_module_clk_t)((uart_config->source_clk) ?
uart_config->source_clk : UART_SCLK_DEFAULT);
}
// 确定LP UART时钟源
#if (SOC_UART_LP_NUM >= 1)
else {
uart_sclk_sel = (soc_module_clk_t)((uart_config->lp_source_clk) ? uart_config->lp_source_clk : LP_UART_SCLK_DEFAULT);
}
#endif
// 支持UART时钟源来自于RTC时钟时,需要做一些额外的初始化操作
#if SOC_UART_SUPPORT_RTC_CLK
if (uart_sclk_sel == (soc_module_clk_t)UART_SCLK_RTC) {
periph_rtc_dig_clk8m_enable();
}
#endif
// 获取时钟源频率
uint32_t sclk_freq;
ESP_RETURN_ON_ERROR(esp_clk_tree_src_get_freq_hz(uart_sclk_sel, ESP_CLK_TREE_SRC_FREQ_PRECISION_CACHED, &sclk_freq), UART_TAG, "Invalid src_clk");
bool success = false;
UART_ENTER_CRITICAL(&(uart_context[uart_num].spinlock));
// UART硬件初始化
uart_hal_init(&(uart_context[uart_num].hal), uart_num);
// 使能UART CORE时钟
if (uart_num < SOC_UART_HP_NUM) {
esp_clk_tree_enable_src((soc_module_clk_t)uart_sclk_sel, true);
// 设置UART CORE时钟,并设置UART传输波特率
HP_UART_SRC_CLK_ATOMIC() {
uart_hal_set_sclk(&(uart_context[uart_num].hal), uart_sclk_sel);
success = uart_hal_set_baudrate(&(uart_context[uart_num].hal), uart_config->baud_rate, sclk_freq);
}
}
// 设置LP UART的时钟和波特率
#if (SOC_UART_LP_NUM >= 1)
else {
LP_UART_SRC_CLK_ATOMIC() {
lp_uart_ll_set_source_clk(uart_context[uart_num].hal.dev, (soc_periph_lp_uart_clk_src_t)uart_sclk_sel);
}
success = lp_uart_ll_set_baudrate(uart_context[uart_num].hal.dev, uart_config->baud_rate, sclk_freq);
}
#endif
/* 设置传输数据帧格式 */
// 设置奇偶校验模式
uart_hal_set_parity(&(uart_context[uart_num].hal), uart_config->parity);
// 设置数据位的位数
uart_hal_set_data_bit_num(&(uart_context[uart_num].hal), uart_config->data_bits);
// 设置停止位的位数
uart_hal_set_stop_bits(&(uart_context[uart_num].hal), uart_config->stop_bits);
// 设置空闲位的位数
uart_hal_set_tx_idle_num(&(uart_context[uart_num].hal), UART_TX_IDLE_NUM_DEFAULT);
// 设置硬件流控(flow_ctrl指定流控方式、rx_flow_ctrl_thresh触发硬件流控的阈值)
uart_hal_set_hw_flow_ctrl(&(uart_context[uart_num].hal), uart_config->flow_ctrl, uart_config->rx_flow_ctrl_thresh);
UART_EXIT_CRITICAL(&(uart_context[uart_num].spinlock));
// 复位RX和TX FIFO
uart_hal_rxfifo_rst(&(uart_context[uart_num].hal));
uart_hal_txfifo_rst(&(uart_context[uart_num].hal));
ESP_RETURN_ON_FALSE(success, ESP_FAIL, UART_TAG, "baud rate unachievable");
return ESP_OK;
}
2.3 初始化UART硬件模块——uart_hal_init
此函数对UART的传输过程参数进行初始化配置,可以看出这是UART的默认配置,而用户传入的配置会在之后覆盖这些初始化配置。因此这个函数的功能和uart_param_config函数结尾部分非常类似,只做简单注解:
void uart_hal_init(uart_hal_context_t *hal, uart_port_t uart_num)
{
// 设定UART工作模式为UART_MODE_UART
// typedef enum {
// UART_MODE_UART = 0x00,
// UART_MODE_RS485_HALF_DUPLEX = 0x01,
// UART_MODE_IRDA = 0x02,
// UART_MODE_RS485_COLLISION_DETECT = 0x03,
// UART_MODE_RS485_APP_CTRL = 0x04,
// } uart_mode_t;
// 设置UART工作模式
uart_ll_set_mode(hal->dev, UART_MODE_UART);
// 上同,这里不再做解释
uart_ll_set_parity(hal->dev, UART_PARITY_DISABLE);
uart_ll_set_data_bit_num(hal->dev, UART_DATA_8_BITS);
uart_ll_set_stop_bits(hal->dev, UART_STOP_BITS_1);
uart_ll_set_tx_idle_num(hal->dev, 0);
uart_ll_set_hw_flow_ctrl(hal->dev, UART_HW_FLOWCTRL_DISABLE, 100);
}
3.设置UART通信管脚——uart_set_pin
使用一个串口UART的第三步,就是设置UART要用来通信的GPIO管脚(RX/TX/RTS/CTS),这一步也是可以由用户配置的,对应的函数为uart_set_pin,其函数调用与流程图如下:
/ ************************************************************ /
// 设置UART通信引脚
uart_set_pin
// <! – 逐个设置UART TX、RX、RTS、CTS引脚>
// 尝试旁路GPIO交换矩阵,直接将UART信号输出
uart_try_set_iomux_pin
// 一旦设置不成功,或TX RX信号引脚重复,则转而尝试将UART信号通过交换矩阵路由出去
// <! 对于输入信号,例如RX和CTS>
gpio_input_enable(gpio_num); // 使能输入管脚
esp_rom_gpio_connect_in_signal(gpio_num, signal_idx, inv); // 将引脚通过矩阵接入特定外设输入信号
// <! 对于输出信号,例如TX和RTS>
gpio_func_sel(gpio_num, PIN_FUNC_GPIO); // 设置IOMUX管脚功能为GPIO
esp_rom_gpio_connect_out_signal(gpio_num, signal_idx, out_inv, oen_inv) // 将引脚通过矩阵接入特定外设输出信号
/ ************************************************************ /
代码的详细逻辑如下,简单来说这段代码的基本执行逻辑是:首先尝试将指定的GPIO引脚直接旁路GPIO交换矩阵而直接输出,当配置失败或者出现引脚复用的特殊情况时,转而将UART信号通过GPIO交换矩阵输出:
//internal signal can be output to multiple GPIO pads
//only one GPIO pad can connect with input signal
esp_err_t uart_set_pin(uart_port_t uart_num, int tx_io_num, int rx_io_num, int rts_io_num, int cts_io_num)
{
// 入口参数检查
ESP_RETURN_ON_FALSE((uart_num >= 0), ESP_FAIL, UART_TAG, "uart_num error");
ESP_RETURN_ON_FALSE((uart_num < UART_NUM_MAX), ESP_FAIL, UART_TAG, "uart_num error");
// 检查入口参数指定的引脚是否具备对应的输入输出属性
if (uart_num < SOC_UART_HP_NUM) {
ESP_RETURN_ON_FALSE((tx_io_num < 0 || (GPIO_IS_VALID_OUTPUT_GPIO(tx_io_num))), ESP_FAIL, UART_TAG, "tx_io_num error");
ESP_RETURN_ON_FALSE((rx_io_num < 0 || (GPIO_IS_VALID_GPIO(rx_io_num))), ESP_FAIL, UART_TAG, "rx_io_num error");
ESP_RETURN_ON_FALSE((rts_io_num < 0 || (GPIO_IS_VALID_OUTPUT_GPIO(rts_io_num))), ESP_FAIL, UART_TAG, "rts_io_num error");
ESP_RETURN_ON_FALSE((cts_io_num < 0 || (GPIO_IS_VALID_GPIO(cts_io_num))), ESP_FAIL, UART_TAG, "cts_io_num error");
}
// 对于LP UART,检查指定的IO管脚是否符合规定
#if (SOC_UART_LP_NUM >= 1)
else { // LP_UART IO check
// 如果LP核心不支持GPIO交换矩阵,那么UART的IO管脚是确认的
#if !SOC_LP_GPIO_MATRIX_SUPPORTED
const uart_periph_sig_t *pins = uart_periph_signal[uart_num].pins;
// LP_UART has its fixed IOs
// 对比指定的针脚与默认值是否保持一致
// 也可以不指定IO针脚编号,此时[tx_io_num < 0]
ESP_RETURN_ON_FALSE((tx_io_num < 0 || (tx_io_num == pins[SOC_UART_TX_PIN_IDX].default_gpio)), ESP_FAIL, UART_TAG, "tx_io_num error");
ESP_RETURN_ON_FALSE((rx_io_num < 0 || (rx_io_num == pins[SOC_UART_RX_PIN_IDX].default_gpio)), ESP_FAIL, UART_TAG, "rx_io_num error");
ESP_RETURN_ON_FALSE((rts_io_num < 0 || (rts_io_num == pins[SOC_UART_RTS_PIN_IDX].default_gpio)), ESP_FAIL, UART_TAG, "rts_io_num error");
ESP_RETURN_ON_FALSE((cts_io_num < 0 || (cts_io_num == pins[SOC_UART_CTS_PIN_IDX].default_gpio)), ESP_FAIL, UART_TAG, "cts_io_num error");
// 反之若LP核心支持交换矩阵,LP UART的管脚可以被路由到任何一个IO上
// 此时只需要判断指定的LP GPIO管脚是否是有效的
#else
// LP_UART signals can be routed to any LP_IOs
ESP_RETURN_ON_FALSE((tx_io_num < 0 || rtc_gpio_is_valid_gpio(tx_io_num)), ESP_FAIL, UART_TAG, "tx_io_num error");
ESP_RETURN_ON_FALSE((rx_io_num < 0 || rtc_gpio_is_valid_gpio(rx_io_num)), ESP_FAIL, UART_TAG, "rx_io_num error");
ESP_RETURN_ON_FALSE((rts_io_num < 0 || rtc_gpio_is_valid_gpio(rts_io_num)), ESP_FAIL, UART_TAG, "rts_io_num error");
ESP_RETURN_ON_FALSE((cts_io_num < 0 || rtc_gpio_is_valid_gpio(cts_io_num)), ESP_FAIL, UART_TAG, "cts_io_num error");
#endif // SOC_LP_GPIO_MATRIX_SUPPORTED
}
#endif // SOC_UART_LP_NUM >= 1
// Potential IO reserved mask
// 将指定的IO管脚编号编号记录下来,这些IO有可能是被保留的
uint64_t io_reserve_mask = 0;
io_reserve_mask |= (tx_io_num > 0 ? BIT64(tx_io_num) : 0);
io_reserve_mask |= (rx_io_num > 0 ? BIT64(rx_io_num) : 0);
io_reserve_mask |= (rts_io_num > 0 ? BIT64(rts_io_num) : 0);
io_reserve_mask |= (cts_io_num > 0 ? BIT64(cts_io_num) : 0);
// Since an IO cannot route peripheral signals via IOMUX and GPIO matrix at the same time,
// if tx and rx share the same IO, both signals need to be route to IOs through GPIO matrix
// 判断配置的TX和RX是否为同一个管脚
bool tx_rx_same_io = (tx_io_num == rx_io_num);
/* In the following statements, if the io_num is negative, no need to configure anything. */
// 设置TX管脚
// 默认情况下,使用IO_MUX直接旁路GPIO交换矩阵输出外设信号
// 失败后采取折中方案,使用交换矩阵输出信号
if (tx_io_num >= 0 && (tx_rx_same_io || !uart_try_set_iomux_pin(uart_num, tx_io_num, SOC_UART_TX_PIN_IDX))) {
// 如果指定了UART的管脚编号,且TX RX管脚相同
// 或指定了UART的管脚编号,但设置IOMUX不成功
if (uart_num < SOC_UART_HP_NUM) {
// 这里直接将IOMUX设置为原始的GPIO功能
// 这样才可以接收来自GPIO交换矩阵的外设信号输出
gpio_func_sel(tx_io_num, PIN_FUNC_GPIO);
// 将UART信号通过GPIO交换矩阵路由而出
esp_rom_gpio_connect_out_signal(tx_io_num, UART_PERIPH_SIGNAL(uart_num, SOC_UART_TX_PIN_IDX), 0, 0);
// output enable is set inside esp_rom_gpio_connect_out_signal func after the signal is connected
// (output enabled too early may cause unnecessary level change at the pad)
}
// 设置LP UART的输出管脚
#if SOC_LP_GPIO_MATRIX_SUPPORTED
else {
rtc_gpio_init(tx_io_num); // set as a LP_GPIO pin
lp_gpio_connect_out_signal(tx_io_num, UART_PERIPH_SIGNAL(uart_num, SOC_UART_TX_PIN_IDX), 0, 0);
// output enable is set inside lp_gpio_connect_out_signal func after the signal is connected
}
#endif
}
// 设置RX管脚,和上面类似
if (rx_io_num >= 0 && (tx_rx_same_io || !uart_try_set_iomux_pin(uart_num, rx_io_num, SOC_UART_RX_PIN_IDX))) {
// 否则才需要使用GPIO交换矩阵输入信号,此时IO无需被保留下来
io_reserve_mask &= ~BIT64(rx_io_num); // input IO via GPIO matrix does not need to be reserved
// 配置RX信号从GPIO交换矩阵输出,这里需要显式使能GPIO
// 因为在ROM代码里没有使能
if (uart_num < SOC_UART_HP_NUM) {
gpio_input_enable(rx_io_num);
esp_rom_gpio_connect_in_signal(rx_io_num, UART_PERIPH_SIGNAL(uart_num, SOC_UART_RX_PIN_IDX), 0);
}
#if SOC_LP_GPIO_MATRIX_SUPPORTED
else {
rtc_gpio_mode_t mode = (tx_rx_same_io ? RTC_GPIO_MODE_INPUT_OUTPUT : RTC_GPIO_MODE_INPUT_ONLY);
rtc_gpio_set_direction(rx_io_num, mode);
if (!tx_rx_same_io) { // set the same pin again as a LP_GPIO will overwrite connected out_signal, not desired, so skip
rtc_gpio_init(rx_io_num); // set as a LP_GPIO pin
}
lp_gpio_connect_in_signal(rx_io_num, UART_PERIPH_SIGNAL(uart_num, SOC_UART_RX_PIN_IDX), 0);
}
#endif
}
// 设置RTS引脚,逻辑和上面大致相同,不再赘述
if (rts_io_num >= 0 && !uart_try_set_iomux_pin(uart_num, rts_io_num, SOC_UART_RTS_PIN_IDX)) {
if (uart_num < SOC_UART_HP_NUM) {
gpio_func_sel(rts_io_num, PIN_FUNC_GPIO);
esp_rom_gpio_connect_out_signal(rts_io_num, UART_PERIPH_SIGNAL(uart_num, SOC_UART_RTS_PIN_IDX), 0, 0);
// output enable is set inside esp_rom_gpio_connect_out_signal func after the signal is connected
}
#if SOC_LP_GPIO_MATRIX_SUPPORTED
else {
rtc_gpio_init(rts_io_num); // set as a LP_GPIO pin
lp_gpio_connect_out_signal(rts_io_num, UART_PERIPH_SIGNAL(uart_num, SOC_UART_RTS_PIN_IDX), 0, 0);
// output enable is set inside lp_gpio_connect_out_signal func after the signal is connected
}
#endif
}
// 设置CTS管脚,与上面相同,不再赘述
if (cts_io_num >= 0 && !uart_try_set_iomux_pin(uart_num, cts_io_num, SOC_UART_CTS_PIN_IDX)) {
io_reserve_mask &= ~BIT64(cts_io_num); // input IO via GPIO matrix does not need to be reserved
if (uart_num < SOC_UART_HP_NUM) {
gpio_pullup_en(cts_io_num);
gpio_input_enable(cts_io_num);
esp_rom_gpio_connect_in_signal(cts_io_num, UART_PERIPH_SIGNAL(uart_num, SOC_UART_CTS_PIN_IDX), 0);
}
#if SOC_LP_GPIO_MATRIX_SUPPORTED
else {
rtc_gpio_set_direction(cts_io_num, RTC_GPIO_MODE_INPUT_ONLY);
rtc_gpio_init(cts_io_num); // set as a LP_GPIO pin
lp_gpio_connect_in_signal(cts_io_num, UART_PERIPH_SIGNAL(uart_num, SOC_UART_CTS_PIN_IDX), 0);
}
#endif
}
// 查看管脚是否已经被占用,如果被占用则报错
uint64_t old_busy_mask = esp_gpio_reserve(io_reserve_mask);
uint64_t conflict_mask = old_busy_mask & io_reserve_mask;
while (conflict_mask > 0) {
uint8_t pos = __builtin_ctzll(conflict_mask);
conflict_mask &= ~(1ULL << pos);
ESP_LOGW(UART_TAG, "GPIO %d is not usable, maybe used by others", pos);
}
return ESP_OK;
}
3.1 尝试设置UART的输入输出管脚以直接输出信号——uart_try_set_iomux_pin
此函数尝试将UART信号设置为从IOMUX直接引出,因此当发现指定的IO管脚号与IOMUX中的引脚功能定义不对应时,就会直接返回设置失败。除此之外的其他情况下,就会按部就班地设置IOMUX和GPIO交换矩阵。设置的具体细节如下:
static bool uart_try_set_iomux_pin(uart_port_t uart_num, int io_num, uint32_t idx)
{
/* Store a pointer to the default pin, to optimize access to its fields. */
const uart_periph_sig_t *upin = &uart_periph_signal[uart_num].pins[idx];
/* In theory, if default_gpio is -1, iomux_func should also be -1, but
* let's be safe and test both. */
// 指定索引的管脚定义不存在,或指定的管脚号并不等于默认的管脚号(defined in uart_periph.c:uart_periph_signal)
// 都会直接返回失败
if (upin->iomux_func == -1 || upin->default_gpio == -1 || upin->default_gpio != io_num) {
return false;
}
/* Assign the correct function to the GPIO. */
assert(upin->iomux_func != -1);
if (uart_num < SOC_UART_HP_NUM) {
// 如果当前针脚是输入信号,即RX方向
if (upin->input) {
// 1.设置IOMUX的功能(function)为FUNC_U0RXD_U0RXD(io_mux_reg.h)
// 2.使能输入管脚
// 3.设置信号U0RXD_IN_IDX旁路GPIO交换矩阵(gpio_sig_map.h)
gpio_iomux_input(io_num, upin->iomux_func, upin->signal);
} else {
// 1.否则设置输出管脚信号为U0TXD_OUT_IDX(gpio_sig_map.h)
// 2.使用外设enable信号来控制GPIOenable
gpio_iomux_output(io_num, upin->iomux_func);
}
}
// LP UART管脚设置
#if (SOC_UART_LP_NUM >= 1) && (SOC_RTCIO_PIN_COUNT >= 1)
else {
// 设置输出方向
if (upin->input) {
rtc_gpio_set_direction(io_num, RTC_GPIO_MODE_INPUT_ONLY);
} else {
rtc_gpio_set_direction(io_num, RTC_GPIO_MODE_OUTPUT_ONLY);
}
// 初始化LP GPIO并选择输出信号
rtc_gpio_init(io_num);
rtc_gpio_iomux_func_sel(io_num, upin->iomux_func);
}
#endif
return true;
}
4.从串口接收数据——uart_read_bytes
顾名思义,此函数就是完成从RX数据线上接收数据的函数,此函数的函数调用关系图如下:
/ ************************************************************ /
// TODO: 此函数中遇到的同步问题,值得认真思考
// 读取UART输入数据
uart_read_bytes
// 尝试将rx_data_buf中的数据填入RingBuffer,这是因为多核同步问题导致的
uart_check_buf_full
/ ************************************************************ /
具体实现和详细解析如下:
int uart_read_bytes(uart_port_t uart_num, void *buf, uint32_t length, TickType_t ticks_to_wait)
{
// 入口参数合法性检查
ESP_RETURN_ON_FALSE((uart_num < UART_NUM_MAX), (-1), UART_TAG, "uart_num error");
ESP_RETURN_ON_FALSE((buf), (-1), UART_TAG, "uart data null");
ESP_RETURN_ON_FALSE((p_uart_obj[uart_num]), (-1), UART_TAG, "uart driver error");
uint8_t *data = NULL;
size_t size = 0;
size_t copy_len = 0;
// 阻塞等待一段时间,如果还没有获得RX互斥量,直接退出,减少CPU资源浪费
if (xSemaphoreTake(p_uart_obj[uart_num]->rx_mux, (TickType_t)ticks_to_wait) != pdTRUE) {
return -1;
}
// 当还没有读够指定数量的字符时,持续读取
while (length) {
// 尝试从rx_ring_buf中读取数据,等待ticks_to_wait的时间
// length是期待读取的数据大小(入口参数),而size是真正读出的数据大小(出口参数)
data = (uint8_t *) xRingbufferReceiveUpTo(p_uart_obj[uart_num]->rx_ring_buf, &size, (TickType_t) ticks_to_wait, length);
// 如果没有读出数据,说明RingBuffer里可能真的没有数据
// 也有可能是rx_data_buf中的数据还没有放进来(?)
if (!data) {
// When using dual cores, `rx_buffer_full_flg` may read and write on different cores at same time,
// which may lose synchronization. So we also need to call `uart_check_buf_full` once when ringbuffer is empty
// to solve the possible asynchronous issues.
// 译:当使用双核时,rx_buffer_full_flg可能会被不同的核同时读写,从而失去同步
// 所以当RingBuffer为空时,我们还是需要调用一次uart_check_buf_full来解决可能存在的同步问题
// 上述注释提示了多核情况下的同步问题,以下是一个可能的场景:
// 核0上触发了RX FULL中断,且发现rx_data_buf无法填入RingBuffer,认为RingBuffer已满
// 则将会将uart_check_buf_full置为1,且rx_data_buf中残存有数据
// 与此同时,核1上的任务读取了RX RingBuffer,且读完所有数据还不足length.
// 此时可以再次执行uart_check_buf_full,将可能存在的更多数据填入RingBuffer
/* 补救措施 */
// uart_check_buf_full函数将会再次尝试将rx_data_buf里的数据放入RingBuffer
// 即便rx_buffer_full_flg为真
if (uart_check_buf_full(uart_num)) {
// This condition will never be true if `uart_read_bytes`
// and `uart_rx_intr_handler_default` are scheduled on the same core.
// 译: 此条件当uart_read_bytes函数与uart_rx_intr_handler_default在同一个核上调度时永不会成立
// 解释: uart_check_buf_full(uart_num)返回true,表示rx_buffer_full_flg为真
// 且RingBuffer还可以放下更多的数据,这是和uart_rx_intr_handler_default的处理逻辑是相悖的:
// 当uart_rx_intr_handler_default检测到rx_buffer_full_flg为假时,它就会自动填充,除非有多个核心
continue;
// 反之补救失败,直接跳出循环,结束读取动作
} else {
// Timeout while not fetched all requested length
break;
}
} // end of if(!data)
// 将读取到的数据data拷贝到buf中
memcpy((uint8_t *)buf + copy_len, data, size);
// 更新缓冲数据长度,和模式匹配队列
UART_ENTER_CRITICAL(&(uart_context[uart_num].spinlock));
p_uart_obj[uart_num]->rx_buffered_len -= size;
uart_pattern_queue_update(uart_num, size);
UART_EXIT_CRITICAL(&(uart_context[uart_num].spinlock));
// 更新迭代变量
copy_len += size;
length -= size;
// 删去RingBuffer中的数据项
vRingbufferReturnItem(p_uart_obj[uart_num]->rx_ring_buf, data);
// 再次尝试用rx_data_buf填充一下RingBuffer
uart_check_buf_full(uart_num);
} // end of while (length)
// 释放RX互斥量
xSemaphoreGive(p_uart_obj[uart_num]->rx_mux);
return copy_len;
}
4.1 检查缓冲区是否已满——uart_check_buf_full
此函数专门用来检查rx_buffer_full_flg标志所记录的缓冲区情况是否属实,也就是说可能存在RX RingBuffer缓冲区并不满,但是rx_buffer_full_flg标志位置位的情况,这也就是多核同步场景下可能存在的问题。
static bool uart_check_buf_full(uart_port_t uart_num)
{
// 如果驱动对象中的rx_buffer_full_flg已被置位
// 说明至少有一次插入RingBuffer缓冲区的尝试失败了,也有可能是同步问题导致读到了旧的rx_buffer_full_flg
if (p_uart_obj[uart_num]->rx_buffer_full_flg) {
// 再尝试一次将缓冲区rx_data_buf填入RingBuffer
BaseType_t res = xRingbufferSend(p_uart_obj[uart_num]->rx_ring_buf, p_uart_obj[uart_num]->rx_data_buf, p_uart_obj[uart_num]->rx_stash_len, 0);
// 如果插入成功,则更新缓冲数据长度和标志
if (res == pdTRUE) {
UART_ENTER_CRITICAL(&(uart_context[uart_num].spinlock));
p_uart_obj[uart_num]->rx_buffered_len += p_uart_obj[uart_num]->rx_stash_len;
p_uart_obj[uart_num]->rx_buffer_full_flg = false;
UART_EXIT_CRITICAL(&(uart_context[uart_num].spinlock));
/* Only re-activate UART_INTR_RXFIFO_TOUT or UART_INTR_RXFIFO_FULL
* interrupts if they were NOT explicitly disabled by the user. */
// 这个函数只是尝试再次打开UART_INTR_RXFIFO_TOUT | UART_INTR_RXFIFO_FULL中断
uart_reenable_intr_mask(p_uart_obj[uart_num]->uart_num,
UART_INTR_RXFIFO_TOUT | UART_INTR_RXFIFO_FULL);
// uart_reenable_intr_mask的函数,只能打开p_uart_obj[uart_num]->rx_int_usr_mask中记录的中断
// TODO: uart.c中有些代码非常费解,例如uart_enable_intr_mask中需要将使能的所有中断记录在rx_int_usr_mask中
// 且uart_enable_intr_mask函数只会被uart_enable_rx_intr调用,代码结构有些混乱
// 可以重新考虑rx_int_usr_mask是否可以改为新名称int_uar_mask
return true;
}
}
return false;
}
5.从串口发送数据——uart_write_bytes
/ ************************************************************ /
// 发送UART数据
uart_write_bytes
// UART发送数据的主函数
uart_tx_all
/ ************************************************************ /
uart_write_bytes仅仅做了简单的入口参数检查,随后调用uart_tx_all函数完成了主要的发送逻辑,因此我们的研究重点放在uart_tx_all函数上:
int uart_write_bytes(uart_port_t uart_num, const void *src, size_t size)
{
ESP_RETURN_ON_FALSE((uart_num < UART_NUM_MAX), (-1), UART_TAG, "uart_num error");
ESP_RETURN_ON_FALSE((p_uart_obj[uart_num] != NULL), (-1), UART_TAG, "uart driver error");
ESP_RETURN_ON_FALSE(src, (-1), UART_TAG, "buffer null");
return uart_tx_all(uart_num, src, size, 0, 0);
}
5.1 UART发送数据的主函数——uart_tx_all
UART发送数据时的基本逻辑分为两个部分:
- 如果存在TX RingBuffer,那么只需要将要发送的数据包及其头部(uart_tx_data_t)送入RingBuffer缓冲
- 如果不存在TX RingBuffer,则直接将要发送的数据写入TX FIFO,不再缓冲,并在最后直接发送BREAK字符
详细的代码逻辑如下:
static int uart_tx_all(uart_port_t uart_num, const char *src, size_t size, bool brk_en, int brk_len)
{
if (size == 0) {
return 0;
}
size_t original_size = size;
// lock for uart_tx
// 获取UART TX互斥量
xSemaphoreTake(p_uart_obj[uart_num]->tx_mux, (TickType_t)portMAX_DELAY);
#if PROTECT_APB
esp_pm_lock_acquire(p_uart_obj[uart_num]->pm_lock);
#endif
p_uart_obj[uart_num]->coll_det_flg = false;
// 如果当前存在TX RingBuffer
if (p_uart_obj[uart_num]->tx_buf_size > 0) {
// 获取可发送的最大数据量
size_t max_size = xRingbufferGetMaxItemSize(p_uart_obj[uart_num]->tx_ring_buf);
int offset = 0;
// 初始化要发送的数据项
uart_tx_data_t evt;
evt.tx_data.size = size;
evt.tx_data.brk_len = brk_len;
// 如果当前是要发送BREAK字符
// 则将数据包类型设置为UART_DATA_BREAK,反之为UART_DATA
if (brk_en) {
evt.type = UART_DATA_BREAK;
} else {
evt.type = UART_DATA;
}
// 将数据头填入到TX RingBuffer中
xRingbufferSend(p_uart_obj[uart_num]->tx_ring_buf, (void *) &evt, sizeof(uart_tx_data_t), portMAX_DELAY);
// 尝试填入数据,直到所有数据发送完毕
while (size > 0) {
// 发送长度最大不超过最大数据项的一半
size_t send_size = size > max_size / 2 ? max_size / 2 : size;
// 将数据写入TX RingBuffer
xRingbufferSend(p_uart_obj[uart_num]->tx_ring_buf, (void *)(src + offset), send_size, portMAX_DELAY);
size -= send_size;
offset += send_size;
// 设置TX空阈值,并使能TX EMPTY中断
uart_enable_tx_intr(uart_num, 1, UART_THRESHOLD_NUM(uart_num, UART_EMPTY_THRESH_DEFAULT));
}
// if (p_uart_obj[uart_num]->tx_buf_size > 0)
// 如果不存在TX RingBuffer,则直接将要发送的数据写入TX FIFO
} else {
// 尝试将所有size长度的大小,全部发送出去
while (size) {
//semaphore for tx_fifo available
if (pdTRUE == xSemaphoreTake(p_uart_obj[uart_num]->tx_fifo_sem, (TickType_t)portMAX_DELAY)) {
// 尝试将数据全部写入TX FIFO
uint32_t sent = uart_enable_tx_write_fifo(uart_num, (const uint8_t *) src, size);
// 如果只写了一部分
if (sent < size) {
// 那么置位tx_waiting_fifo,表示当前有任务正在等待TX FIFO空
p_uart_obj[uart_num]->tx_waiting_fifo = true;
// 设置TX EMPTY中断阈值,且使能对应的中断,等待提醒传输队列空
uart_enable_tx_intr(uart_num, 1, UART_THRESHOLD_NUM(uart_num, UART_EMPTY_THRESH_DEFAULT));
}
// 更新要发送的数据量和位置
size -= sent;
src += sent;
}
}
// 如果要发送BREAK,则在此完成发送
if (brk_en) {
uart_hal_clr_intsts_mask(&(uart_context[uart_num].hal), UART_INTR_TX_BRK_DONE);
// 发送BREAK字符,并打开UART_INTR_TX_BRK_DONE中断以捕获传输BREAK结束
UART_ENTER_CRITICAL(&(uart_context[uart_num].spinlock));
uart_hal_tx_break(&(uart_context[uart_num].hal), brk_len);
uart_hal_ena_intr_mask(&(uart_context[uart_num].hal), UART_INTR_TX_BRK_DONE);
UART_EXIT_CRITICAL(&(uart_context[uart_num].spinlock));
// 获取break传输信号量
xSemaphoreTake(p_uart_obj[uart_num]->tx_brk_sem, (TickType_t)portMAX_DELAY);
}
xSemaphoreGive(p_uart_obj[uart_num]->tx_fifo_sem);
}
#if PROTECT_APB
esp_pm_lock_release(p_uart_obj[uart_num]->pm_lock);
#endif
// 释放TX互斥量
xSemaphoreGive(p_uart_obj[uart_num]->tx_mux);
return original_size;
}
6.UART驱动中的同步与信息传递
在上述的驱动代码解读中,不难发现,UART数据的收发动作和其中断处理程序之间存在着信息交换,有些是通过标志位来完成的,有些则依靠信号量和互斥量来实现动作上的同步。本节对上述驱动代码中的标志位和同步过程进行梳理,如下图所示: