1. UART通信基础认知
第一次接触UART时,我盯着那两根交叉连接的TX/RX线看了半天——这玩意儿怎么就能传数据了?后来在嵌入式开发中踩过无数坑才明白,串口通信就像两个人在暗房里用闪光灯打摩斯密码。发送方(TX)按特定节奏闪烁,接收方(RX)必须用完全相同的节奏来解读,任何节奏错位都会导致乱码。
UART(Universal Asynchronous Receiver/Transmitter)采用异步传输机制,这意味着通信双方没有共用时钟信号。就像两个约好"每秒眨一次眼"的人,实际执行时可能你的1.01秒和我的0.99秒逐渐累积成时差。因此UART协议规定了这些关键参数:
- 波特率(Baud Rate):每秒传输的符号数,常见值有9600/115200等
- 数据位(Data Bits):通常7或8位表示一个字符
- 停止位(Stop Bits):1或2位标识帧结束
- 校验位(Parity):简单的错误检测机制
在Linux环境下,UART设备通常映射为/dev/ttyS*(原生串口)或/dev/ttyUSB*(USB转串口)。有一次我调试树莓派时,发现发送AT指令没反应,后来用ls -l /dev/serial/by-id才找到实际挂载点是/dev/ttyACM0——这就是新手容易踩的第一个坑。
2. Linux下的UART环境准备
2.1 硬件连接检查
用USB转TTL模块连接开发板时,切记TX-RX要交叉连接。我曾在凌晨三点调试时因为线序接反,怀疑人生半小时。推荐使用带LED指示的转换器,发送数据时能看到闪烁提示。接线后先用dmesg | grep tty查看系统是否识别设备:
code复制[ +0.000475] usb 1-1: FTDI USB Serial Device converter now attached to ttyUSB0
2.2 工具链安装
Minicom比screen更适合UART调试,支持显示控制字符:
bash复制sudo apt install minicom
sudo usermod -a -G dialout $USER # 避免每次sudo
配置minicom时有个隐藏技巧:按Ctrl+A → Z → O进入配置界面,需要关闭硬件流控(Hardware Flow Control设为No),否则某些设备会卡死。
2.3 权限与udev规则
反复插拔USB串口设备时,可能会遇到设备号跳变(ttyUSB0变成ttyUSB1)。通过创建udev规则固定设备路径:
bash复制# /etc/udev/rules.d/99-usb-serial.rules
SUBSYSTEM=="tty", ATTRS{idVendor}=="0403", ATTRS{idProduct}=="6001", SYMLINK+="ttyMyDevice"
用lsusb查看到厂商ID和产品ID后,重启udev服务即可通过/dev/ttyMyDevice稳定访问。
3. UART通信实战操作
3.1 基础收发测试
启动minicom进行基础测试:
bash复制minicom -D /dev/ttyUSB0 -b 115200
在终端里直接输入字符,如果线序正确且波特率匹配,接收端应能即时回显。遇到乱码时:
- 确认双方波特率是否完全一致(误差需<3%)
- 检查数据位/停止位设置(嵌入式设备常用8N1)
- 尝试降低波特率(高波特率对时钟精度要求更高)
3.2 结构化数据收发
传输二进制数据时需要特别处理。例如发送传感器数据包:
c复制// 发送端
struct sensor_data {
uint32_t timestamp;
float temperature;
uint8_t checksum;
} __attribute__((packed));
接收方Linux上用Python解析:
python复制import struct
with open('/dev/ttyUSB0','rb') as f:
while True:
data = f.read(9) # 结构体大小
ts, temp, chk = struct.unpack('<IfB', data)
关键技巧:在minicom中按Ctrl+A → Q切换显示模式,可查看原始16进制数据
3.3 流控制实战
当传输大量数据时(如固件升级),需要启用硬件流控(RTS/CTS)。在stty中配置:
bash复制stty -F /dev/ttyUSB0 115200 crtscts
测试时故意断开CTS线,发送端应自动暂停传输。注意:某些廉价USB转串口芯片的流控实现有缺陷,建议先用逻辑分析仪验证。
4. 常见问题诊断手册
4.1 症状:发送数据无响应
排查步骤:
- 用万用表测量TX/RX对地电压(应有3.3V或5V波动)
- 短接TX-RX测试自发自收
- 检查内核日志
dmesg | grep tty是否有错误 - 尝试Windows端用Putty交叉验证
4.2 症状:接收数据不完整
典型案例:每帧丢失最后1-2字节
- 解决方案:在接收代码中增加50ms延时
- 原理:USB转串口的缓冲区刷新需要时间
4.3 症状:高波特率下误码
树莓派上的特别注意事项:
bash复制# 提高UART时钟精度
sudo raspi-config → Advanced Options → UART Clock
将默认的3MHz时钟提升到16MHz以上可支持921600波特率
5. 进阶开发技巧
5.1 内核级调试
查看串口驱动详细状态:
bash复制sudo cat /proc/tty/driver/serial
输出示例:
code复制serinfo:1.0 driver revision:
0: uart:16550A port:000003F8 irq:4 tx:0 rx:0
1: uart:16550A port:000002F8 irq:3 tx:45 rx:0 RTS|DTR
5.2 多线程安全操作
Python中实现非阻塞读取的经典模式:
python复制import serial
from threading import Thread
ser = serial.Serial('/dev/ttyUSB0', 115200, timeout=0)
def read_thread():
while True:
data = ser.read(1024)
if data:
print(f"Received: {data}")
Thread(target=read_thread, daemon=True).start()
5.3 性能优化参数
调整内核缓冲区提升吞吐量:
bash复制sudo sysctl -w kernel.printk_ratelimit=0 # 禁用内核日志限速
sudo setserial /dev/ttyUSB0 low_latency
实测可将延迟从100ms级降到10ms级
最后分享一个血泪教训:曾用UART给设备刷固件时,没关闭终端模拟器的本地回显,导致设备把回显字符也当作指令执行。现在我的工作守则第一条就是——开始UART通信前,永远先执行stty -F /dev/ttyXXX -echo。
