第4章 UART串口适配:从控制器到调试
UART串口,说白了就是嵌入式系统里最基础的通信手段。你想想看,从系统启动打印到控制台交互,从蓝牙模块到GPS模组,哪样离得开它?我在Tina Linux上做过不下二十种外设适配,UART这块坑最多,也最值得花功夫讲透。
本章核心脉络:UART控制器驱动 → 设备树节点配置 → 波特率与流控 → 调试三板斧
4.1 UART控制器驱动框架
Tina Linux基于Linux 4.9/5.4内核,UART驱动走的是serial core + 8250框架。说白了,内核已经把通用逻辑封装好了,我们只需要把硬件相关的部分填进去。
驱动加载流程大致是这样:
- 内核启动时,serial core注册uart_driver
- platform驱动匹配设备树节点,触发probe
- probe里做三件事:映射寄存器地址、申请中断、注册端口
- 用户空间通过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 串口调试三板斧
串口出问题了,别急着翻代码。我总结了一套调试流程,屡试不爽:
- 第一板斧:查设备节点
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:0tx/rx计数为0,说明驱动加载了但没数据收发。
- 第二板斧:查引脚电平
用示波器或逻辑分析仪量TX引脚。空闲时应该是高电平(3.3V或1.8V)。如果一直是低电平,可能是引脚被其他外设占用了。
- 第三板斧:查波特率匹配
用逻辑分析仪抓一段数据,看实际波特率。我习惯抓一个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调试其实没那么玄乎。只要把设备树配对、波特率算准、流控选对,大部分问题都能解决。真遇到疑难杂症,逻辑分析仪一抓,波形一看,问题就现原形了。