3. nRF UARTE外设驱动与板级适配

UARTE 是 nRF 系列 SoC 上带 EasyDMA 的增强型 UART 外设,它在传统 UART 的收发逻辑之上,把数据搬运工作交给 DMA 完成,从而让 CPU 从逐字节读写 TXD/RXD 寄存器中解放出来。理解 UARTE 的关键,不是把它当成“更快的串口”,而是把它看成一条由 EasyDMA 通道 + 硬件 FIFO + 波特率发生器 + 引脚映射 组成的自动收发链路。Zephyr 的 UART 驱动模型则在这条链路之上提供统一的设备接口,设备树负责把引脚、波特率、硬件流控等板级信息翻译成驱动可用的配置。

UARTE 的 EasyDMA 收发机制

UARTE 内部有独立的 TXD 和 RXD EasyDMA 通道。发送时,CPU 只需把待发数据缓冲区的首地址写入 TXD.PTR,把长度写入 TXD.MAXCNT,再触发 TASKS_STARTTX,硬件就会自动从 RAM 读取数据并串行移位输出。每搬完一个字节,TXD.AMOUNT 递增;当 AMOUNT == MAXCNT 时产生 EVENTS_ENDTX。接收方向同理:预先给出接收缓冲区地址和最大长度,启动 TASKS_STARTRX 后,硬件把 RXD 引脚上收到的字节自动写入 RAM,达到 MAXCNT 时产生 EVENTS_ENDRX。

这里有一个容易踩坑的细节:EasyDMA 只能访问 Data RAM 区域,不能直接指向 Flash 中的常量字符串。若要把字面量通过 UARTE 发出,必须先把数据拷贝到 RAM 缓冲区,或者使用驱动提供的发送接口由驱动内部处理。另一个细节是 MAXCNT 为 0 时表示“无限长度”,此时 DMA 不会自动停止,需要软件在合适时机触发 TASKS_STOPTX,否则会持续读取后续内存,属于危险用法。

UARTE 的波特率由 BAUDRATE 寄存器决定,常用值 115200、921600、1000000。实际波特率与目标值的误差应控制在 ±2% 以内,否则长时间传输会累积采样偏移导致误码。nRF52840 在 115200 下的误差通常远小于 1%。

Zephyr UART 驱动模型

Zephyr 把串口抽象为 uart_driver_api,对上提供 uart_poll_out()、uart_fifo_fill()、uart_fifo_read()、uart_irq_tx_enable()、uart_irq_rx_enable() 等接口,对下由 uart_nrfx_uarte.c 把请求翻译成 UARTE 寄存器操作和 EasyDMA 配置。应用层通过 DEVICE_DT_GET(DT_NODELABEL(uart0)) 拿到设备指针,无需关心底层是 UARTE 还是普通 UART。

三种典型收发模式需要区分清楚:轮询模式用 uart_poll_out() 逐字节发送,简单但阻塞 CPU;中断模式通过 uart_irq_* 接口在回调中搬运数据,适合中小数据量;DMA/异步模式使用 uart_tx()、uart_rx_enable() 配合 uart_callback_set(),由驱动直接配置 EasyDMA,CPU 只在完成回调中被唤醒,适合高速或大数据量场景。选择哪种模式,取决于数据量、实时性要求和功耗预算。

设备树节点配置

UARTE 的板级适配首先体现在设备树。以 nRF52840 DK 的 uart0 为例,典型节点如下:

&uart0 {
    status = "okay";
    current-speed = <115200>;
    hw-flow-control;
    pinctrl-0 = <&uart0_default>;
    pinctrl-1 = <&uart0_sleep>;
    pinctrl-names = "default", "sleep";
};

&pinctrl {
    uart0_default: uart0_default {
        group1 {
            psels = <NRF_PSEL(UART_TX, 0, 6)>,
                    <NRF_PSEL(UART_RX, 0, 8)>;
        };
        group2 {
            psels = <NRF_PSEL(UART_RTS, 0, 5)>,
                    <NRF_PSEL(UART_CTS, 0, 7)>;
            bias-pull-up;
        };
    };
};

其中 current-speed 决定 BAUDRATE,hw-flow-control 启用 RTS/CTS,pinctrl-0 把 TXD、RXD、RTS、CTS 映射到具体 P0 引脚。注意 NRF_PSEL(UART_TX, 0, 6) 表示端口 0 的第 6 脚,若硬件把 TXD 接到 P1.12,则写成 NRF_PSEL(UART_TX, 1, 12)。引脚映射错误是串口“无输出”最常见的原因之一。

串口回环与中断/DMA 收发验证

回环测试是验证 UARTE 通路最直接的方法:把 TXD 与 RXD 短接,发送的数据应原样收回。在 Zephyr 中可用异步 API 实现:

static void uart_cb(const struct device *dev,
                    struct uart_event *evt, void *user_data)
{
    if (evt->type == UART_RX_RDY) {
        /* evt->data.rx.buf 中已有收到数据 */
    }
}

uart_callback_set(uart0, uart_cb, NULL);
uart_rx_enable(uart0, rx_buf, sizeof(rx_buf), 100);
uart_tx(uart0, tx_buf, sizeof(tx_buf), 100);

若回环正常,UART_RX_RDY 回调中会看到与发送内容一致的字节;若收不到,依次检查:TXD/RXD 是否真的短接、引脚映射是否与硬件一致、status 是否为 okay、波特率是否匹配。中断模式下则用 uart_irq_rx_ready() 判断是否有数据,再 uart_fifo_read() 取出。DMA 模式的优势在高速率下更明显:115200 波特率下每字节约 87 µs,若用轮询会频繁占用 CPU;EasyDMA 则让 CPU 在整块数据搬完后才介入。

nRF UARTE 收发链路与板级适配 CPU / RAM tx_buf / rx_buf Data RAM 区域 EasyDMA 通道 TXD.PTR / MAXCNT RXD.PTR / MAXCNT UARTE 核心 波特率发生器 TXD/RXD 移位 地址/长度 字节流 GPIO 引脚映射 TXD / RXD / RTS / CTS 电平转换 3.3V ↔ RS232/RS485 DB9 / 端子 外部设备 回环测试:TXD 短接 RXD,验证收发通路 Zephyr UART 驱动接口层 uart_poll_out() uart_fifo_fill/read() uart_irq_tx/rx_enable() uart_tx() / uart_rx_enable() 轮询 · 中断 · 异步 DMA 三种模式 设备树:current-speed / pinctrl / hw-flow-control

RS232/RS485 板级电平转换适配

nRF 的 UARTE 引脚是 3.3V CMOS 电平,不能直接接 RS232 的 ±12V 或 RS485 的差分总线,必须经过电平转换。RS232 方向常用 MAX3232 一类收发器:nRF 的 TXD 接 MAX3232 的 T1IN,RXD 接 R1OUT,MAX3232 的 T1OUT/R1IN 接 DB9 的 2/3 脚,外围只需 4 个 0.1 µF 电荷泵电容。RS485 方向常用 MAX3485 或 SP3485:nRF 的 TXD 接 DI,RXD 接 RO,再用一个 GPIO 控制 DE/RE 收发方向切换。

RS485 半双工的关键是方向控制时序。发送前把 DE 拉高,等待最后一个字节完全移出移位寄存器后再拉低,否则会截断末字节。判断“完全移出”的可靠依据是 EVENTS_TXDRDY 或 Zephyr 的 UART_TX_DONE 回调,而不是简单延时。若总线上有多个节点,还需在 A/B 线上加 120 Ω 终端电阻,并确认共地,否则差分信号会因反射和共模漂移而误码。

RS485 方向切换若用普通 GPIO 且未做时序保护,在 921600 波特率下末字节丢失概率明显上升。建议在 UART_TX_DONE 回调中拉低 DE,或使用带自动方向控制的收发器(如 MAX13487)。

板级适配检查要点

  • 设备树 status 为 okay,current-speed 与对端一致。
  • pinctrl 中 TXD/RXD 引脚编号与原理图逐一核对,注意 P0/P1 端口区分。
  • 启用硬件流控时,RTS/CTS 必须同时映射,且对端也支持流控。
  • RS232 收发器电荷泵电容不可省略;RS485 终端电阻与共地不可忽略。
  • 回环测试通过后再接外部设备,可快速定位是 MCU 侧还是板级转换侧问题。

把 UARTE 的 EasyDMA 机制、Zephyr 驱动接口、设备树配置和板级电平转换串起来看,整条链路就清晰了:设备树告诉驱动“用哪些引脚、多快速度”,驱动把应用请求翻译成 EasyDMA 的地址与长度,UARTE 硬件自动完成字节搬运,电平转换芯片把 3.3V 信号适配到外部总线。任何一环配置错误,都会表现为“发不出”或“收不到”,而回环测试与逐段排查正是定位问题的有效手段。