4、UART驱动调试:串口设备树节点配置、波特率与数据格式设置、DMA与中断模式切换、流控(RTS/CTS)调试、实际案例:控制台输出乱码排查
UART调试,说白了就是让机顶盒的串口能正常说话。我做了这么多年驱动,遇到最多的坑就是串口。你想想看,串口是嵌入式调试的「生命线」,它要是挂了,你连内核启动日志都看不到,那还玩什么?
这一章,咱们就把它彻底搞明白。
4.1 串口设备树节点配置
设备树是硬件描述文件,UART的节点配置是第一步。我个人习惯先把芯片手册翻出来,确认UART挂在哪条总线上,中断号是多少,时钟源是什么。
一个典型的UART设备树节点长这样:
&uart2 {
status = "okay";
pinctrl-names = "default";
pinctrl-0 = <&uart2_pins_a>;
clocks = <&clk_uart2>;
clock-names = "uart";
interrupts = <GIC_SPI 36 IRQ_TYPE_LEVEL_HIGH>;
interrupt-names = "uart";
dmas = <&dma 2 0>, <&dma 2 1>;
dma-names = "tx", "rx";
cts-gpios = <&gpio 3 18 GPIO_ACTIVE_LOW>;
rts-gpios = <&gpio 3 19 GPIO_ACTIVE_LOW>;
};
这里有几个关键点:
- pinctrl:管脚复用配置,错了串口根本不通。我在项目中遇到过管脚配成GPIO的情况,查了半天才发现。
- clocks:时钟必须使能,否则UART模块不工作。
- interrupts:中断号要跟芯片手册一致,不然中断处理函数不会被调用。
- dmas:DMA通道,后面会细说。
- cts-gpios / rts-gpios:硬件流控引脚,不是所有板子都接。
4.2 波特率与数据格式设置
波特率设置是UART调试的基础。说白了,就是收发双方约定好「每秒传多少位」。常见的波特率有115200、9600、57600等。
在Linux内核中,波特率设置通常在驱动初始化时完成。代码里一般这么写:
struct uart_port *port;
port->uartclk = 24000000; // 时钟频率24MHz
port->baud = 115200; // 目标波特率
uart_update_timeout(port, CS8, port->baud);
数据格式包括数据位、停止位、校验位。最常见的配置是8N1:8个数据位,无校验,1个停止位。
| 参数 | 常见值 | 说明 |
|---|---|---|
| 数据位 | 5, 6, 7, 8 | 大部分场景用8位 |
| 停止位 | 1, 1.5, 2 | 1位最常用 |
| 校验位 | 无, 奇校验, 偶校验 | 无校验最普遍 |
嗯,这里要注意:波特率误差不能超过3%。如果时钟源不准,算出来的波特率会有偏差。我建议用示波器量一下TX引脚的波形,数一下位宽,确认实际波特率。
4.3 DMA与中断模式切换
UART有两种工作模式:中断模式和DMA模式。怎么选?看场景。
- 中断模式:每收发一个字节就触发一次中断。CPU频繁进出中断,适合低速率、小数据量场景。
- DMA模式:DMA控制器负责搬运数据,CPU只在开始和结束时参与。适合高速率、大数据量场景。
我在项目中遇到过一个问题:机顶盒播放4K视频时,串口日志打印导致画面卡顿。后来切换到DMA模式,CPU负载从30%降到了5%。
切换方式通常通过设备树或驱动参数控制:
// 设备树中使能DMA
dmas = <&dma 2 0>, <&dma 2 1>;
dma-names = "tx", "rx";
// 驱动中动态切换
if (use_dma) {
uart_port->flags |= UPF_HARD_FLOW;
uart_add_one_port(drv, uart_port);
} else {
uart_port->flags &= ~UPF_HARD_FLOW;
}
4.4 流控(RTS/CTS)调试
流控分两种:硬件流控和软件流控。机顶盒场景下,硬件流控更常见。
硬件流控的原理很简单:发送前先问对方「你准备好了吗?」(RTS信号),对方回复「准备好了」(CTS信号),然后才开始发数据。
调试流控时,我一般先确认几个点:
- 硬件上RTS/CTS引脚是否连接?很多开发板根本没接。
- 设备树中是否配置了流控引脚?看前面的cts-gpios和rts-gpios。
- 驱动中是否使能了流控?
驱动中使能流控的代码示例:
// 使能硬件流控
termios->c_cflag |= CRTSCTS;
// 关闭硬件流控
termios->c_cflag &= ~CRTSCTS;
我曾经遇到一个案例:机顶盒连接蓝牙模块,串口数据经常丢包。查了半天,发现蓝牙模块要求硬件流控,但板子上RTS/CTS引脚没接。最后飞了两根线才解决。
4.5 实际案例:控制台输出乱码排查
这个案例我印象特别深。有一次客户反馈机顶盒启动时控制台输出乱码,像这样:
��[ 0.000000] Booting Linux on physical CPU 0x0
��[ 0.000000] Linux version 4.9.84
��[ 0.000000] CPU: ARMv7 Processor [410fc075] revision 5
乱码的排查思路,我总结了一个流程:
- 先看波特率:终端软件设置的波特率跟UART实际输出的是否一致?这是最常见的原因。
- 再看时钟:UART时钟源频率对不对?如果时钟频率算错了,波特率误差会很大。
- 然后看电平:TTL电平还是RS232电平?电平不匹配也会导致乱码。
- 最后看接地:GND有没有共地?不共地信号会飘。
那次排查的结果是什么?你猜猜看。其实是bootloader阶段波特率是115200,但内核启动后驱动重新初始化了UART,波特率变成了9600。终端软件一直用115200收,自然乱码。
解决方案很简单:在设备树中固定波特率,或者让bootloader和内核用同一个配置。
// 在设备树中固定波特率
chosen {
bootargs = "console=ttyS0,115200n8";
};
核心要点:
- 设备树节点配置要核对管脚、时钟、中断、DMA
- 波特率误差不能超过3%,用示波器验证
- 中断模式适合调试,DMA模式适合高性能场景
- 硬件流控必须确认物理连接,否则别使能
- 乱码排查:波特率 → 时钟 → 电平 → 接地
UART调试说难不难,说简单也不简单。关键是要有系统性的排查思路,别一上来就瞎改代码。嗯,这一章就到这里,下一章咱们聊SPI总线调试,那个坑更多,到时候再细说。