3. Qt内存管理机制

串口通信,说白了就是让两个设备通过一根线,按位(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是半双工的,同一时间只能一个设备发,其他设备收。

我的经验: 做Qt串口程序时,物理层选型直接影响上层代码。232用全双工,收发可以同时写。485必须加流控或协议切换收发状态,否则数据会撞车。

3.2 通信参数详解:波特率、数据位、停止位、校验位

这四个参数必须两端一致,否则收到的全是乱码。我见过太多新手在这上面栽跟头。

3.2.1 波特率

波特率就是每秒传多少个符号。对串口来说,一个符号就是一个bit。所以9600波特率,就是每秒传9600个bit。换算成字节:9600 / 10(起始位1+数据8+停止位1)= 960字节/秒。

常用值:9600、19200、38400、115200。我个人习惯,调试口用115200,速度快。工业现场用9600或19200,抗干扰好。

注意: 波特率误差不能超过±2%。晶振不准或分频系数算错,会导致通信时好时坏。我曾经被一个劣质USB转串口线坑过,标称115200,实际跑出来只有114800,偶尔丢字节。

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可能出现在正常数据里,导致误触发。所以软件流控只适合传文本。我个人很少用,除非硬件不支持流控线。

避坑指南: 我曾经在一个项目里用了软件流控传二进制固件包,结果固件里恰好有0x13,发送方直接卡死。从那以后,传二进制数据我坚决不用软件流控。

3.4 串口数据帧结构分析

一个完整的串口数据帧长什么样?咱们从bit层面拆开看。

起始位(1bit) + 数据位(5~8bit) + 校验位(0/1bit) + 停止位(1/1.5/2bit)

以最常见的8N1(8数据位、无校验、1停止位)为例,传一个字节0x55(二进制01010101):

  1. 空闲状态:线路上是高电平(逻辑1)
  2. 起始位:拉低到0,持续1个bit时间。告诉接收方“准备收数据”
  3. 数据位:从最低位(LSB)开始传。0x55的bit顺序是:1,0,1,0,1,0,1,0
  4. 停止位:拉高到1,持续1个bit时间。表示字节结束

你想想看,如果波特率是9600,每个bit约104微秒。传一个字节大概1毫秒。这个时间窗口里,接收方要精确采样。所以时钟精度很重要。

下面这张图展示了数据帧的完整结构:

串口数据帧结构(8N1格式) 空闲 高电平 起始 1 bit D0 LSB D1 D2 D3 D4 D5 D6 D7 MSB 停止 1 bit 说明: • 空闲时线路保持高电平(逻辑1),起始位拉低到0表示开始传输 • 数据位从最低位(LSB)开始传,到最高位(MSB)结束。停止位恢复高电平

接收方怎么知道什么时候开始采样?靠起始位的下降沿。检测到电平从1变0,就知道要来了。然后按波特率算好的时间间隔,在每个bit的中间位置采样。这样最稳。

调试技巧: 用逻辑分析仪抓串口波形,比用示波器方便。设置好波特率,它能自动解码出每个字节。我排查乱码问题时,第一件事就是上逻辑分析仪,看波形对不对。

3.5 实际项目中的参数配置建议

说了这么多理论,最后给点实战建议。你在Qt里配置串口时,可以参考这个表:

场景 波特率 数据位 停止位 校验位 流控制
调试输出 115200 8 1 无 无
工业传感器 9600 8 1 偶校验 无
长距离传输 19200 8 2 奇校验 硬件
高速数据采集 460800 8 1 无 硬件