You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

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版本问题

  1. 执行流程阻塞:app_main中的死循环先调用rx_polling,而rx_polling本身是死循环,导致tx_polling永远无法执行。
  2. 错误使用FreeRTOS API:不使用FreeRTOS的情况下,调用xQueueCreate、xQueueSendFromISR等FreeRTOS接口无效,这些API依赖FreeRTOS内核运行。
  3. 错误的中断方式:用GPIO边缘中断监听UART RX引脚,UART数据传输时每个比特位都会触发边缘中断,会产生大量无效中断,且无法正确识别完整的UART数据帧。

FreeRTOS版本问题

  1. 错误的中断方式:误用GPIO中断代替UART自身的接收中断,无法正确响应UART数据接收事件,GPIO的ANYEDGE中断会在每个比特位变化时触发,而非完整数据帧接收完成时触发。
  2. 未启用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);
}

修复要点说明

  1. 使用UART原生中断:配置UART的接收FIFO满中断(也可使用接收超时中断),当UART接收缓冲区有数据时触发中断。
  2. 正确的中断处理:在中断函数中清除中断标志,读取数据并通过队列传递给任务处理,避免在中断中执行耗时操作。
  3. 合理的任务调度:创建独立的RX和TX任务,确保任务不会阻塞主线程。

内容的提问来源于stack exchange,提问作者Yash

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.03 06:19:54