ESP32 UART接收中断代码失效问题排查求助
问题描述
我希望实现当Hercules工具向ESP32的UART RX引脚发送数据时,触发接收中断并打印“Interrupt Occur, Data Received”日志,但现有代码无法正常工作。目前带FreeRTOS的版本仅每2秒输出Hello World,无FreeRTOS版本无任何输出。
现有代码
1. 不使用FreeRTOS版本
#include <stdio.h> #include <string.h> #include "driver/uart.h" #include "esp_log.h" #include "esp_system.h" #include "driver/gpio.h" #include "soc/uart_reg.h" #define RX_BUF_SIZE 1024 #define TXD_PIN GPIO_NUM_17 #define RXD_PIN GPIO_NUM_16 SemaphoreHandle_t uart_mutex; QueueHandle_t uart_queue; static void IRAM_ATTR uart_rx_intr_handler(void *arg); static void rx_polling(void); static void tx_polling(void); void init(void) { const uart_config_t uart_config = { .baud_rate = 115200, .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, }; uart_driver_install(UART_NUM_2, RX_BUF_SIZE * 2, 0, 0, NULL, 0); uart_param_config(UART_NUM_2, &uart_config); uart_set_pin(UART_NUM_2, TXD_PIN, RXD_PIN, UART_PIN_NO_CHANGE, UART_PIN_NO_CHANGE); uart_queue = xQueueCreate(10, sizeof(char)); gpio_config_t io_conf = { .pin_bit_mask = (1ULL << RXD_PIN), .mode = GPIO_MODE_INPUT, .intr_type = GPIO_INTR_ANYEDGE, .pull_up_en = GPIO_PULLUP_ENABLE, }; gpio_config(&io_conf); gpio_install_isr_service(0); gpio_isr_handler_add(RXD_PIN, uart_rx_intr_handler, (void *)RXD_PIN); } int sendData(const char* logName, const char* data) { const int len = strlen(data); const int txBytes = uart_write_bytes(UART_NUM_2, data, len); ESP_LOGI(logName, "Wrote %d bytes", txBytes); return txBytes; } static void IRAM_ATTR uart_rx_intr_handler(void *arg) { ESP_LOGI("ISR", "Interrupt called"); char message = 'I'; if (xQueueSendFromISR(uart_queue, &message, NULL) != pdPASS) { ESP_LOGI("ISR", "Queue send failed"); } } static void rx_polling(void) { static const char *RX_TASK_TAG = "RX_TASK"; esp_log_level_set(RX_TASK_TAG, ESP_LOG_INFO); char received_msg; while (1) { if (xQueueReceive(uart_queue, &received_msg, portMAX_DELAY)) { ESP_LOGI(RX_TASK_TAG, "Received: %c", received_msg); } else { ESP_LOGI(RX_TASK_TAG, "Queue receive failed"); } } } static void tx_polling(void) { static const char *TX_TASK_TAG = "TX_TASK"; esp_log_level_set(TX_TASK_TAG, ESP_LOG_INFO); while (1) { sendData(TX_TASK_TAG, "Hello world\n"); vTaskDelay(2000 / portTICK_PERIOD_MS); } } void app_main(void) { uart_mutex = xSemaphoreCreateMutex(); init(); while (1) { rx_polling(); tx_polling(); } }
2. 使用FreeRTOS版本
#include <stdio.h> #include <string.h> #include "freertos/FreeRTOS.h" #include "freertos/task.h" #include "freertos/queue.h" #include "driver/uart.h" #include "esp_log.h" #include "esp_system.h" #include "driver/gpio.h" #include "soc/uart_reg.h" static const int RX_BUF_SIZE = 1024; #define TXD_PIN GPIO_NUM_17 #define RXD_PIN GPIO_NUM_16 SemaphoreHandle_t uart_mutex; QueueHandle_t uart_queue; static void IRAM_ATTR uart_rx_intr_handler(void *arg); static void rx_task(void *arg); static void tx_task(void *arg); void init(void) { const uart_config_t uart_config = { .baud_rate = 115200, .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, }; uart_driver_install(UART_NUM_2, RX_BUF_SIZE * 2, 0, 0, NULL, 0); uart_param_config(UART_NUM_2, &uart_config); uart_set_pin(UART_NUM_2, TXD_PIN, RXD_PIN, UART_PIN_NO_CHANGE, UART_PIN_NO_CHANGE); uart_queue = xQueueCreate(10, sizeof(char)); gpio_config_t io_conf = { .pin_bit_mask = (1ULL << RXD_PIN), .mode = GPIO_MODE_INPUT, .intr_type = GPIO_INTR_ANYEDGE, .pull_up_en = GPIO_PULLUP_ENABLE, }; gpio_config(&io_conf); gpio_install_isr_service(0); gpio_isr_handler_add(RXD_PIN, uart_rx_intr_handler, (void *)RXD_PIN); } int sendData(const char* logName, const char* data) { const int len = strlen(data); const int txBytes = uart_write_bytes(UART_NUM_2, data, len); ESP_LOGI(logName, "Wrote %d bytes", txBytes); return txBytes; } static void IRAM_ATTR uart_rx_intr_handler(void *arg) { ESP_LOGI("ISR", "Interrupt called"); BaseType_t xHigherPriorityTaskWoken = pdFALSE; char message = 'I'; if (xQueueSendFromISR(uart_queue, &message, &xHigherPriorityTaskWoken) != pdPASS) { ESP_LOGI("ISR", "Queue send failed"); } if (xHigherPriorityTaskWoken == pdTRUE) { portYIELD_FROM_ISR(); } } static void rx_task(void *arg) { static const char *RX_TASK_TAG = "RX_TASK"; esp_log_level_set(RX_TASK_TAG, ESP_LOG_INFO); char received_msg; while (1) { if (xQueueReceive(uart_queue, &received_msg, portMAX_DELAY)) { ESP_LOGI(RX_TASK_TAG, "Received: %c", received_msg); } else { ESP_LOGI(RX_TASK_TAG, "Queue receive failed"); } } } static void tx_task(void *arg) { static const char *TX_TASK_TAG = "TX_TASK"; esp_log_level_set(TX_TASK_TAG, ESP_LOG_INFO); while (1) { sendData(TX_TASK_TAG, "Hello world\n"); vTaskDelay(2000 / portTICK_PERIOD_MS); } } void app_main(void) { uart_mutex = xSemaphoreCreateMutex(); init(); xTaskCreate(rx_task, "uart_rx_task", 1024*2, NULL, configMAX_PRIORITIES, NULL); xTaskCreate(tx_task, "uart_tx_task", 1024*2, NULL, configMAX_PRIORITIES-1, NULL); }
问题原因分析
无FreeRTOS版本问题
- 执行流程阻塞:
app_main中的死循环先调用rx_polling,而rx_polling本身是死循环,导致tx_polling永远无法执行。 - 错误使用FreeRTOS API:不使用FreeRTOS的情况下,调用
xQueueCreate、xQueueSendFromISR等FreeRTOS接口无效,这些API依赖FreeRTOS内核运行。 - 错误的中断方式:用GPIO边缘中断监听UART RX引脚,UART数据传输时每个比特位都会触发边缘中断,会产生大量无效中断,且无法正确识别完整的UART数据帧。
FreeRTOS版本问题
- 错误的中断方式:误用GPIO中断代替UART自身的接收中断,无法正确响应UART数据接收事件,GPIO的ANYEDGE中断会在每个比特位变化时触发,而非完整数据帧接收完成时触发。
- 未启用UART接收中断:没有配置UART的接收中断,绕开了UART外设的原生中断机制,导致无法正确检测UART数据接收。
修复方案(FreeRTOS版本为例)
正确做法是使用UART外设自带的接收中断,而非GPIO中断。以下是修复后的代码:
#include <stdio.h> #include <string.h> #include "freertos/FreeRTOS.h" #include "freertos/task.h" #include "freertos/queue.h" #include "driver/uart.h" #include "esp_log.h" #include "driver/gpio.h" #define RX_BUF_SIZE 1024 #define TXD_PIN GPIO_NUM_17 #define RXD_PIN GPIO_NUM_16 #define UART_PORT UART_NUM_2 QueueHandle_t uart_queue; static void IRAM_ATTR uart_rx_intr_handler(void *arg) { uint16_t intr_status = uart_get_intr_status(UART_PORT); BaseType_t xHigherPriorityTaskWoken = pdFALSE; if (intr_status & UART_RXFIFO_FULL_INT_ST_M) { // 清除中断标志 uart_clear_intr_status(UART_PORT, UART_RXFIFO_FULL_INT_CLR_M); ESP_LOGI("ISR", "Interrupt Occur, Data Received"); // 读取数据并传递给任务处理 uint8_t data[32]; int len = uart_read_bytes(UART_PORT, data, sizeof(data), 0); if (len > 0) { xQueueSendFromISR(uart_queue, data, &xHigherPriorityTaskWoken); } } if (xHigherPriorityTaskWoken == pdTRUE) { portYIELD_FROM_ISR(); } } static void rx_task(void *arg) { static const char *TAG = "RX_TASK"; uint8_t received_data[32]; while (1) { if (xQueueReceive(uart_queue, received_data, portMAX_DELAY)) { ESP_LOGI(TAG, "Received data: %s", received_data); } } } static void tx_task(void *arg) { static const char *TAG = "TX_TASK"; while (1) { const char *msg = "Hello world\n"; int tx_bytes = uart_write_bytes(UART_PORT, msg, strlen(msg)); ESP_LOGI(TAG, "Wrote %d bytes", tx_bytes); vTaskDelay(2000 / portTICK_PERIOD_MS); } } void app_main(void) { const uart_config_t uart_config = { .baud_rate = 115200, .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, }; // 安装UART驱动 uart_driver_install(UART_PORT, RX_BUF_SIZE * 2, 0, 0, NULL, 0); uart_param_config(UART_PORT, &uart_config); uart_set_pin(UART_PORT, TXD_PIN, RXD_PIN, UART_PIN_NO_CHANGE, UART_PIN_NO_CHANGE); // 创建队列用于传递接收的数据 uart_queue = xQueueCreate(10, sizeof(uint8_t)*32); // 配置UART中断:禁用所有中断后,启用接收FIFO满中断 uart_disable_intr_mask(UART_PORT, UART_INTR_MASK_ALL); uart_enable_intr_mask(UART_PORT, UART_RXFIFO_FULL_INT_ENA_M); // 注册UART中断处理函数 uart_isr_register(UART_PORT, uart_rx_intr_handler, NULL, ESP_INTR_FLAG_IRAM, NULL); // 创建任务 xTaskCreate(rx_task, "uart_rx_task", 2048, NULL, configMAX_PRIORITIES-1, NULL); xTaskCreate(tx_task, "uart_tx_task", 2048, NULL, configMAX_PRIORITIES-2, NULL); }
修复要点说明
- 使用UART原生中断:配置UART的接收FIFO满中断(也可使用接收超时中断),当UART接收缓冲区有数据时触发中断。
- 正确的中断处理:在中断函数中清除中断标志,读取数据并通过队列传递给任务处理,避免在中断中执行耗时操作。
- 合理的任务调度:创建独立的RX和TX任务,确保任务不会阻塞主线程。
内容的提问来源于stack exchange,提问作者Yash
相关产品推荐
相关产品推荐

