第4章 UART串口适配:从控制器到调试

UART串口,说白了就是嵌入式系统里最基础的通信手段。你想想看,从系统启动打印到控制台交互,从蓝牙模块到GPS模组,哪样离得开它?我在Tina Linux上做过不下二十种外设适配,UART这块坑最多,也最值得花功夫讲透。

本章核心脉络:UART控制器驱动 → 设备树节点配置 → 波特率与流控 → 调试三板斧

4.1 UART控制器驱动框架

Tina Linux基于Linux 4.9/5.4内核,UART驱动走的是serial core + 8250框架。说白了,内核已经把通用逻辑封装好了,我们只需要把硬件相关的部分填进去。

驱动加载流程大致是这样:

  1. 内核启动时,serial core注册uart_driver
  2. platform驱动匹配设备树节点,触发probe
  3. probe里做三件事:映射寄存器地址、申请中断、注册端口
  4. 用户空间通过ttySx设备节点访问

我个人习惯先确认内核是否打开了相关配置。在Tina的kernel_menuconfig里,这几个选项必须勾上:

Device Drivers → Character devices → Serial drivers
  → [*] 8250/16550 and compatible serial support
  → [*]   Console on 8250/16550 and compatible serial port
  → [*]   Support for Allwinner SUNXI UART

注意那个SUNXI UART选项,全志平台专用。如果你用的是其他SoC,找对应的vendor选项就行。

4.2 设备树串口节点配置

设备树配置是UART适配的重头戏。我在项目中遇到过好几次,驱动明明加载了,串口就是不通,最后发现是设备树里一个属性写错了。

先看一个标准的UART节点长什么样:

&uart1 {
    pinctrl-names = "default";
    pinctrl-0 = <&uart1_pins>;
    status = "okay";
};

嗯,就这么简单?别急,这只是最简配置。实际项目中,你至少还要考虑这几项:

属性 说明 常见取值
pinctrl-0 引脚复用配置 TX/RX/CTS/RTS引脚
clock-frequency UART模块时钟频率 24MHz / 48MHz
dmas DMA传输通道 可选,高波特率时推荐
cts-gpios / rts-gpios 硬件流控GPIO 使用GPIO模拟流控时

引脚复用这块最容易出错。我曾经在A133平台上调UART3,设备树配了半天没反应,最后发现pinctrl里把TX和RX写反了。你想想看,信号都接反了,能通才怪。

我的调试习惯:先在pinctrl里把TX/RX单独配成GPIO模式,用示波器量一下电平,确认硬件连接没问题,再切回UART功能。这一步能省下大量排错时间。

4.3 波特率与流控设置

波特率设置,说白了就是双方约定好每秒传多少位。标准波特率有115200、9600、57600这些。但你知道吗?实际能跑多快,取决于UART模块的时钟频率和分频系数。

内核里波特率计算逻辑在8250_port.c里:

static unsigned int serial8250_get_divisor(struct uart_port *port,
    unsigned int baud)
{
    unsigned int quot;
    quot = port->uartclk / (16 * baud);
    return quot;
}

公式很简单:分频系数 = 时钟频率 / (16 × 目标波特率)。如果时钟是24MHz,想跑115200,算出来分频系数是13.02,取整后实际波特率会有偏差。偏差超过3%就可能丢数据。

流控分两种:

  • 硬件流控(RTS/CTS):靠两根专用引脚握手,可靠但多占两个IO
  • 软件流控(XON/XOFF):靠特殊字符控制,省引脚但效率低

我个人建议,只要硬件支持,优先用硬件流控。特别是接蓝牙模块或4G模组时,数据量大,软件流控根本扛不住。

在设备树里开启硬件流控:

&uart1 {
    pinctrl-0 = <&uart1_pins>;
    uart-has-rtscts;    /* 告诉内核这个UART支持硬件流控 */
    status = "okay";
};

然后在应用层用tcsetattr设置:

struct termios options;
tcgetattr(fd, &options);
cfsetispeed(&options, B115200);
cfsetospeed(&options, B115200);
options.c_cflag |= CRTSCTS;  /* 使能硬件流控 */
tcsetattr(fd, TCSANOW, &options);

注意:硬件流控需要收发双方都支持。我曾经调试一个项目,设备端开了RTS/CTS,但对端模块只接了TX/RX两根线,结果数据发不出去,卡了我整整一天。

4.4 串口调试三板斧

串口出问题了,别急着翻代码。我总结了一套调试流程,屡试不爽:

  1. 第一板斧:查设备节点

    ls /dev/ttyS* 看看设备有没有生成。没有?检查驱动是否probe成功:

    cat /proc/tty/driver/serial
    serinfo:1.0 driver revision:
    0: uart:16550A port:00000000 irq:38 tx:0 rx:0
    1: uart:16550A port:00000000 irq:39 tx:0 rx:0

    tx/rx计数为0,说明驱动加载了但没数据收发。

  2. 第二板斧:查引脚电平

    用示波器或逻辑分析仪量TX引脚。空闲时应该是高电平(3.3V或1.8V)。如果一直是低电平,可能是引脚被其他外设占用了。

  3. 第三板斧:查波特率匹配

    用逻辑分析仪抓一段数据,看实际波特率。我习惯抓一个0x55(二进制01010101),每个bit宽度应该一致。量一下宽度,1/宽度就是实际波特率。

实用工具推荐:在Tina Linux里,用busybox自带的microcom或stty就能快速测试串口。stty -F /dev/ttyS1 115200设置参数,microcom -s 115200 /dev/ttyS1直接收发数据,比写测试程序快多了。

还有一个常见问题:串口打印乱码。这通常是波特率不匹配或电平不匹配造成的。我遇到过最奇葩的一次,是板子上UART芯片的参考电压是1.8V,但对端模块是3.3V电平,结果数据全错位。加个电平转换芯片就解决了。

UART串口适配知识体系 UART控制器驱动 设备树节点配置 波特率与流控 串口调试技巧 pinctrl配置 时钟频率 分频系数计算 硬件/软件流控 设备节点检查 逻辑分析仪 核心原则:先确认硬件连接 → 再检查驱动加载 → 最后调参数 调试工具:示波器 / 逻辑分析仪 / microcom / stty

最后说一句,UART调试其实没那么玄乎。只要把设备树配对、波特率算准、流控选对,大部分问题都能解决。真遇到疑难杂症,逻辑分析仪一抓,波形一看,问题就现原形了。