4、UART接口基础:UART协议、波特率与帧格式、Linux串口驱动框架、硬件流控
好,咱们进入第四章。这一章聊UART,串口通信。
说实话,做嵌入式开发,UART可能是你打交道最多的接口。调试打印、蓝牙AT指令、WiFi模块配置,哪哪都离不开它。我刚开始做驱动那会儿,觉得UART太简单了,不就是两根线收发数据嘛。结果呢?被波特率不匹配、丢数据、流控配置这些坑,一个个教做人。
今天咱们就把UART彻底讲透。从协议原理,到Linux驱动框架,再到硬件流控,一步到位。
4.1 UART协议:本质就是两根线
UART,全称Universal Asynchronous Receiver/Transmitter,通用异步收发器。
异步,意味着没有时钟线。收发双方靠约定好的波特率来同步。你想想看,没有时钟线,怎么保证两边节奏一致?全靠事先说好:我每秒发9600个bit,你也按这个速度收。
UART的物理层很简单:
- TXD:发送数据线
- RXD:接收数据线
- GND:地线,共地参考
就这三根线,就能干活。当然,如果需要流控,还会加上RTS和CTS。
数据在空闲时,总线保持高电平。开始传输时,先拉低一个bit时间,这叫起始位。然后从低位到高位,依次发送数据位。最后是校验位(可选)和停止位(1位或2位)。
我在项目中遇到过一个问题:WiFi模块和MCU通信,偶尔出现乱码。查了半天,发现是地线没接好,共地电压有偏差。嗯,这种低级错误,犯过一次就记住了。
4.2 波特率与帧格式:两边必须一模一样
波特率,就是每秒传输的符号数。对于UART来说,一个符号就是一个bit。所以波特率等于比特率。
常见的波特率有:9600、19200、38400、115200、921600。
我个人习惯,调试用115200,够快。WiFi模块配置一般用115200或921600。蓝牙模块很多默认是9600,但我会改成115200,不然传数据太慢。
| 波特率 | 1字节传输时间(8N1) | 适用场景 |
|---|---|---|
| 9600 | 约1.04ms | 调试、低速传感器 |
| 115200 | 约86.8μs | 通用通信、AT指令 |
| 921600 | 约10.9μs | 高速数据传输 |
帧格式,就是数据包的组成。标准写法是:数据位+校验位+停止位。比如8N1,就是8位数据、无校验、1位停止位。
常见的帧格式:
- 8N1:8数据位,无校验,1停止位。最常用。
- 8E1:8数据位,偶校验,1停止位。
- 8O1:8数据位,奇校验,1停止位。
- 7E1:7数据位,偶校验,1停止位。老设备常见。
为什么8N1最流行?因为8位刚好一个字节,无校验省开销,1位停止位效率高。说白了,就是简单高效。
我曾经踩过一个坑:一个蓝牙模块,手册写的是8N1,但实际要配成8E1才能正常工作。这种坑,只能靠逻辑分析仪抓波形来确认。所以,我建议你手边常备一个逻辑分析仪,几十块钱那种就行,关键时刻能救命。
我曾经遇到过波特率偏差导致通信间歇性失败的问题。两边都配115200,但MCU的时钟精度不够,实际波特率是114800,偏差0.35%。短时间没问题,但连续发大量数据时,误差累积,就会丢字节。
解决办法:用示波器或逻辑分析仪测量实际波特率,或者选用高精度晶振。
4.3 Linux串口驱动框架:从tty到硬件
Linux里,串口驱动属于tty子系统。tty是Teletype的缩写,历史遗留叫法,现在泛指终端设备。
整个框架分三层:
- tty核心层:提供统一的文件操作接口,比如open、read、write、ioctl。用户空间看到的/dev/ttyS0、/dev/ttyUSB0,就是这层提供的。
- 线路规程(line discipline):处理数据格式转换。比如规范模式下的行编辑、回显。默认的线路规程是N_TTY。
- 串口驱动层:直接操作硬件寄存器。负责收发数据、配置波特率、控制流控引脚。
数据流是这样的:
用户程序 → tty核心 → 线路规程 → 串口驱动 → 硬件
反过来,硬件收到数据,触发中断,串口驱动把数据往上送,经过线路规程,最终到用户程序。
我刚开始学的时候,觉得这层叠太多了。但后来发现,这种分层设计很巧妙。你想换硬件?只改最底层的串口驱动就行。想加新的数据处理方式?写一个新的线路规程就行。各司其职,互不干扰。
4.4 硬件流控:RTS/CTS到底要不要用
硬件流控,就是通过RTS(请求发送)和CTS(允许发送)两根线,来控制数据流。
工作原理很简单:
- 当接收端准备好接收数据时,拉高RTS,告诉对方可以发。
- 当接收端缓冲区快满了,拉低RTS,让对方暂停发送。
- 发送端在发送前,先检查CTS引脚。如果CTS是高电平,就发;如果是低电平,就等。
说白了,就是硬件层面的“你等一下,我还没消化完”。
什么时候必须用硬件流控?
- 高速通信,比如921600以上。软件流控(XON/XOFF)在这种速度下根本来不及处理。
- 长距离传输,信号容易受干扰,流控可以防止数据溢出。
- WiFi模块或蓝牙模块,它们内部有缓冲区,满了会丢数据。用流控可以避免。
什么时候可以不用?
- 低速通信,比如9600。数据量小,缓冲区够用。
- 短距离、稳定的环境。
- 只是调试打印,丢几行日志无所谓。
在Linux里配置硬件流控,用termios结构体:
#include <termios.h>
struct termios options;
tcgetattr(fd, &options);
// 使能硬件流控
options.c_cflag |= CRTSCTS;
// 禁用硬件流控
options.c_cflag &= ~CRTSCTS;
tcsetattr(fd, TCSANOW, &options);
注意,CRTSCTS这个宏,不同平台可能不一样。有些平台用CNEW_RTSCTS。我建议你编译前查一下头文件定义。
我建议你在设计电路时,就把RTS/CTS的引脚预留出来。哪怕当前不用,也留个焊盘。万一后面需要流控,不用重新改板。我就吃过这个亏,一个项目量产了才发现WiFi模块在高负载下会丢数据,加流控又得改PCB,折腾死。
4.5 实战要点:串口配置的完整流程
写一个串口驱动,配置流程基本固定。我总结一下:
- 打开设备:open("/dev/ttyS0", O_RDWR | O_NOCTTY)。O_NOCTTY防止串口成为控制终端。
- 获取当前配置:tcgetattr(fd, &options)。
- 设置波特率:cfsetispeed(&options, B115200); cfsetospeed(&options, B115200)。
- 设置帧格式:数据位、校验位、停止位。比如8N1:options.c_cflag &= ~CSIZE; options.c_cflag |= CS8; options.c_cflag &= ~PARENB; options.c_cflag &= ~CSTOPB。
- 设置流控:按需使能或禁用CRTSCTS。
- 设置本地模式:options.c_cflag |= (CLOCAL | CREAD)。CLOCAL忽略调制解调器控制,CREAD使能接收。
- 设置输入输出模式:一般设为原始模式,不做行处理。options.c_lflag &= ~(ICANON | ECHO | ECHOE | ISIG)。
- 设置超时和最小字节数:options.c_cc[VMIN] = 1; options.c_cc[VTIME] = 0。表示至少读1个字节才返回,不超时。
- 写入配置:tcsetattr(fd, TCSANOW, &options)。
- 清空缓冲区:tcflush(fd, TCIOFLUSH)。
这个流程,我建议你背下来。因为所有串口设备,WiFi模块、蓝牙模块、GPS模块、4G模块,配置方式都大同小异。
UART通信,本质就是两边约定好:波特率、帧格式、流控方式。任何一边配错了,通信就失败。调试时,先用逻辑分析仪抓波形,确认物理层没问题,再查软件配置。
好了,这一章就到这里。UART看似基础,但它是所有无线模块通信的基石。下一章,咱们会基于这个基础,开始实战WiFi模块的驱动开发。