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

ESP32自定义UART驱动中断异常:写入使能寄存器后ISR未触发

UART TX_FIFO_EMPTY 中断无法触发的问题

我编写了一个UART发送函数,原本应触发TX_FIFO_EMPTY中断对应的ISR,但功能完全失效。调试发现:无论FIFO为空还是有数据,ISR都不会触发;直接写入FIFO寄存器可在串口输出字符,但无法通过ISR完成发送操作。


相关代码

静态变量与发送函数

static UART_INPUT uart_input_ = { .UART_NUM = 0, .data = NULL, .length = 0 };

void uart_send(uint8_t UART_NUM, char* data) {
  // 修改要发送的数据参数
  uart_input_.UART_NUM = UART_NUM;
  uart_input_.length = strlen(data);
  uart_input_.data = data;
  
  // 清空寄存器并触发TX FIFO写入中断
  WRITE_REG(UARTS_ENB_INT[UART_NUM], 0);
  WRITE_REG(UARTS_ENB_INT[UART_NUM], (1 << 1));
}

寄存器定义数组

#define UART0_INTERRUPT_ENABLE (UART0_BASE + 0x0C)
#define UART1_INTERRUPT_ENABLE (UART1_BASE + 0x0C)
#define UART2_INTERRUPT_ENABLE (UART2_BASE + 0x0C)

#define UART0_INTERRUPT_DISABLE (UART0_BASE + 0x10)
#define UART1_INTERRUPT_DISABLE (UART1_BASE + 0x10)
#define UART2_INTERRUPT_DISABLE (UART2_BASE + 0x10)

const uint32_t UARTS_FIFO[] = {  UART0_FIFO, UART1_FIFO, UART2_FIFO  };
const uint32_t UARTS_DIS_INT[] = { UART0_INTERRUPT_DISABLE, UART1_INTERRUPT_DISABLE, UART2_INTERRUPT_DISABLE  };
const uint32_t ETS_UARTS[] = {  ETS_UART0_INTR_SOURCE, ETS_UART1_INTR_SOURCE, ETS_UART2_INTR_SOURCE  };
const uint32_t UARTS_ENB_INT[] = {  UART0_INTERRUPT_ENABLE, UART1_INTERRUPT_ENABLE, UART2_INTERRUPT_ENABLE  };
const uint32_t UARTS_INT_ST[] = {  UART0_INT_ST, UART1_INT_ST, UART2_INT_ST  };

static RingBuffer *ring_buffer;
static uint8_t *buffer = NULL;

ISR实现

void IRAM_ATTR uart_isr(void* arg) {
  // ESP32每个UART只有一个中断,这里同时处理读写中断
  UART_INPUT* uart_input = (UART_INPUT*)arg;
  // 从参数获取UART编号
  uint8_t UART_NUM = uart_input->UART_NUM;
  // 读取中断状态,判断是读还是写中断
  uint32_t status = READ_REG(UARTS_INT_ST[UART_NUM]);

  // 如果是RX FIFO满中断
  if (status & (1 << 0)) {

    while (is_data_available_uart(UART_NUM)) {
      // 读取寄存器数据并清除保留位
      uint8_t data = READ_REG(UARTS_FIFO[UART_NUM]) & 0xFF;
      // 将数据写入环形缓冲区供后续读取
      write_ring_buffer(ring_buffer, data);
    }
  }

  // 如果是TX FIFO空中断
  if (status & (1 << 1)) {
    // 获取要发送的数据
    char* data = uart_input_.data;

    // 通过UART发送用户数据
    // 检查中断是否仍处于使能状态
    while (uart_input_.length > 0 && (READ_REG(UARTS_ENB_INT[UART_NUM]) & (1 << 1))) {
      // 遍历数据并写入FIFO寄存器
      WRITE_REG(UARTS_FIFO[UART_NUM], *data); // 发送前必须解引用数据
      uart_input_.length--;
      data++;
    }

    if (uart_input_.length == 0) {
      WRITE_REG(UARTS_DIS_INT[UART_NUM], (1 << 1));
    }
  }
  // 清除中断,避免重复触发
  WRITE_REG(UARTS_DIS_INT[UART_NUM], 1 >> 0);
  WRITE_REG(UARTS_DIS_INT[UART_NUM], (1 << 1));
}

中断注册代码

esp_err_t err = esp_intr_alloc(ETS_UARTS[UART_NUM], ESP_INTR_FLAG_IRAM, uart_isr, (void*) &uart_input_, NULL);

问题分析与修复方案

1. 中断使能寄存器操作错误

ESP32的UART中断使能寄存器是位掩码寄存器,直接写入0会关闭所有中断,且直接赋值(1 << 1)会覆盖原有配置,误关其他已使能的中断。正确操作应该是对目标位进行置位/清除,而非直接覆盖寄存器值:

// 先清除TX中断位,再置位(保留其他中断配置)
WRITE_REG(UARTS_ENB_INT[UART_NUM], READ_REG(UARTS_ENB_INT[UART_NUM]) & ~(1 << 1));
WRITE_REG(UARTS_ENB_INT[UART_NUM], READ_REG(UARTS_ENB_INT[UART_NUM]) | (1 << 1));

2. TX FIFO中断触发阈值未配置

ESP32的TX FIFO空中断默认不是"FIFO为空时触发",而是需要手动配置触发阈值。在UART初始化时添加以下代码,将阈值设为0(FIFO为空时触发):

// 配置TX FIFO空中断触发阈值为0
WRITE_REG(UART_CONF0[UART_NUM], 
          (READ_REG(UART_CONF0[UART_NUM]) & ~UART_TX_FIFO_THRHD_M) 
          | (0 << UART_TX_FIFO_THRHD_S));

3. ISR全局变量访问隐患

ISR中直接访问全局变量uart_input_,而非使用传入的uart_input指针,若后续扩展多UART场景会导致逻辑混乱。修改为使用参数指针:

// 替换原有全局变量访问逻辑
char* data = uart_input->data;
while (uart_input->length > 0 && (READ_REG(UARTS_ENB_INT[UART_NUM]) & (1 << 1))) {
  WRITE_REG(UARTS_FIFO[UART_NUM], *data);
  uart_input->length--;
  data++;
}

if (uart_input->length == 0) {
  WRITE_REG(UARTS_DIS_INT[UART_NUM], (1 << 1));
}

4. 初始触发缺失

首次使能TX中断时,若FIFO为空,需要手动写入一个字节启动发送流程,否则UART不会主动触发空中断。在uart_send函数末尾添加:

// 手动写入第一个字节,启动发送流程
if (uart_input_.length > 0) {
  WRITE_REG(UARTS_FIFO[UART_NUM], *uart_input_.data);
  uart_input_.length--;
  uart_input_.data++;
}

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.14 22:49:57