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的缩写,历史遗留叫法,现在泛指终端设备。

整个框架分三层:

  1. tty核心层:提供统一的文件操作接口,比如open、read、write、ioctl。用户空间看到的/dev/ttyS0、/dev/ttyUSB0,就是这层提供的。
  2. 线路规程(line discipline):处理数据格式转换。比如规范模式下的行编辑、回显。默认的线路规程是N_TTY。
  3. 串口驱动层:直接操作硬件寄存器。负责收发数据、配置波特率、控制流控引脚。

数据流是这样的:

用户程序 → 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 实战要点:串口配置的完整流程

写一个串口驱动,配置流程基本固定。我总结一下:

  1. 打开设备:open("/dev/ttyS0", O_RDWR | O_NOCTTY)。O_NOCTTY防止串口成为控制终端。
  2. 获取当前配置:tcgetattr(fd, &options)。
  3. 设置波特率:cfsetispeed(&options, B115200); cfsetospeed(&options, B115200)。
  4. 设置帧格式:数据位、校验位、停止位。比如8N1:options.c_cflag &= ~CSIZE; options.c_cflag |= CS8; options.c_cflag &= ~PARENB; options.c_cflag &= ~CSTOPB。
  5. 设置流控:按需使能或禁用CRTSCTS。
  6. 设置本地模式:options.c_cflag |= (CLOCAL | CREAD)。CLOCAL忽略调制解调器控制,CREAD使能接收。
  7. 设置输入输出模式:一般设为原始模式,不做行处理。options.c_lflag &= ~(ICANON | ECHO | ECHOE | ISIG)。
  8. 设置超时和最小字节数:options.c_cc[VMIN] = 1; options.c_cc[VTIME] = 0。表示至少读1个字节才返回,不超时。
  9. 写入配置:tcsetattr(fd, TCSANOW, &options)。
  10. 清空缓冲区:tcflush(fd, TCIOFLUSH)。

这个流程,我建议你背下来。因为所有串口设备,WiFi模块、蓝牙模块、GPS模块、4G模块,配置方式都大同小异。

🔑 核心要点:

UART通信,本质就是两边约定好:波特率、帧格式、流控方式。任何一边配错了,通信就失败。调试时,先用逻辑分析仪抓波形,确认物理层没问题,再查软件配置。

好了,这一章就到这里。UART看似基础,但它是所有无线模块通信的基石。下一章,咱们会基于这个基础,开始实战WiFi模块的驱动开发。