3. 串口通信基础理论:RS-232/422/485标准、波特率/数据位/停止位/校验位详解、流控制(硬件/软件)、串口数据帧结构分析
串口通信,说白了就是让两个设备通过一根线,按位(bit)逐个传输数据。听起来简单,但里面的门道不少。我刚开始做嵌入式时,觉得串口就是接两根线的事,结果被RS-232的电压问题坑过好几次。今天咱们把这块彻底捋清楚。
3.1 三大物理层标准:RS-232、RS-422、RS-485
这三个标准定义了信号怎么在物理线上传输。选错了,设备之间根本聊不起来。
| 特性 | RS-232 | RS-422 | RS-485 |
|---|---|---|---|
| 传输方式 | 单端(不平衡) | 差分(平衡) | 差分(平衡) |
| 最大距离 | 约15米 | 约1200米 | 约1200米 |
| 最大节点数 | 1发1收 | 1发10收 | 32发32收(典型) |
| 电压范围 | ±3V ~ ±15V | ±2V ~ ±6V | ±1.5V ~ ±6V |
| 典型应用 | 调试口、短距设备 | 工业长距传输 | 多设备总线 |
RS-232 是最常见的。电脑上的DB9串口就是它。信号用一根线对地测量,电压范围大,但抗干扰差。我记得有一次调试一个工控机,232线拉了10米,数据就开始乱码了。后来换成422,问题立马解决。
RS-422 用了差分信号,两根线拧在一起传,噪声互相抵消。距离能到上千米。我建议做工业项目时,只要距离超过5米,优先考虑422或485。
RS-485 是422的升级版。它支持多节点挂在同一条总线上。你想想看,一个车间里几十个传感器,用485一根线就能串起来。不过要注意,485是半双工的,同一时间只能一个设备发,其他设备收。
3.2 通信参数详解:波特率、数据位、停止位、校验位
这四个参数必须两端一致,否则收到的全是乱码。我见过太多新手在这上面栽跟头。
3.2.1 波特率
波特率就是每秒传多少个符号。对串口来说,一个符号就是一个bit。所以9600波特率,就是每秒传9600个bit。换算成字节:9600 / 10(起始位1+数据8+停止位1)= 960字节/秒。
常用值:9600、19200、38400、115200。我个人习惯,调试口用115200,速度快。工业现场用9600或19200,抗干扰好。
3.2.2 数据位
数据位是每个字节实际传的bit数。常见的是8位,也有7位、5位。8位能传0-255,7位只能传0-127(ASCII字符够用)。
现在绝大多数设备都用8位。除非你在跟老式电传机或某些专用设备通信,才可能遇到7位或5位。
3.2.3 停止位
停止位是每个字节结束后的空闲时间。可以是1位、1.5位或2位。停止位越长,容错越好,但传输效率越低。
我一般用1位停止位。如果通信环境噪声大,比如电机旁边,我会改成2位。多花点时间,但数据稳。
3.2.4 校验位
校验位用来检测传输错误。有三种模式:
- 无校验(None):不校验,最快。调试时常用。
- 奇校验(Odd):数据位+校验位里1的个数为奇数。
- 偶校验(Even):数据位+校验位里1的个数为偶数。
举个例子:数据位是0b01010101(四个1)。奇校验的话,校验位要补1,让总1数变成5(奇数)。偶校验就补0,保持4个1(偶数)。
校验位只能检测奇数个bit的错误。如果两个bit同时翻转,校验位看不出来。所以关键数据还得靠上层CRC或校验和。
3.3 流控制:硬件流控与软件流控
流控制解决什么问题?发送方太快,接收方来不及处理。数据就丢了。
3.3.1 硬件流控(RTS/CTS)
用两根额外的线:RTS(请求发送)和CTS(允许发送)。接收方缓冲区快满时,拉低CTS,告诉发送方“停一下”。等缓冲区空了,再拉高CTS。
我建议在高速通信(115200以上)或大数据量传输时,一定要用硬件流控。Qt里设置很简单:
serialPort->setFlowControl(QSerialPort::HardwareControl);
但要注意,你的串口线和设备必须支持RTS/CTS。很多USB转串口线只接了TX/RX/GND三根线,硬件流控用不了。
3.3.2 软件流控(XON/XOFF)
不用额外线,靠数据流里插特殊字符。接收方发0x13(XOFF)让发送方暂停,发0x11(XON)让发送方继续。
问题在于,如果传输的是二进制数据,0x13和0x11可能出现在正常数据里,导致误触发。所以软件流控只适合传文本。我个人很少用,除非硬件不支持流控线。
3.4 串口数据帧结构分析
一个完整的串口数据帧长什么样?咱们从bit层面拆开看。
起始位(1bit) + 数据位(5~8bit) + 校验位(0/1bit) + 停止位(1/1.5/2bit)
以最常见的8N1(8数据位、无校验、1停止位)为例,传一个字节0x55(二进制01010101):
- 空闲状态:线路上是高电平(逻辑1)
- 起始位:拉低到0,持续1个bit时间。告诉接收方“准备收数据”
- 数据位:从最低位(LSB)开始传。0x55的bit顺序是:1,0,1,0,1,0,1,0
- 停止位:拉高到1,持续1个bit时间。表示字节结束
你想想看,如果波特率是9600,每个bit约104微秒。传一个字节大概1毫秒。这个时间窗口里,接收方要精确采样。所以时钟精度很重要。
下面这张图展示了数据帧的完整结构:
接收方怎么知道什么时候开始采样?靠起始位的下降沿。检测到电平从1变0,就知道要来了。然后按波特率算好的时间间隔,在每个bit的中间位置采样。这样最稳。
3.5 实际项目中的参数配置建议
说了这么多理论,最后给点实战建议。你在Qt里配置串口时,可以参考这个表:
| 场景 | 波特率 | 数据位 | 停止位 | 校验位 | 流控制 |
|---|---|---|---|---|---|
| 调试输出 | 115200 | 8 | 1 | 无 | 无 |
| 工业传感器 | 9600 | 8 | 1 | 偶校验 | 无 |
| 长距离传输 | 19200 | 8 | 2 | 奇校验 | 硬件 |
| 高速数据采集 | 460800 | 8 | 1 | 无 | 硬件 |