3、UART异步串口:起始位、数据位、校验位、停止位,波特率如何匹配
UART,说白了就是串口通信的物理层协议。很多刚入行的朋友觉得它简单,不就是两根线一接就能收发数据吗?嗯,表面上看确实如此,但实际项目中因为UART配置不对导致通信失败的案例,我见过太多了。
我个人习惯把UART比作两个人打电话。你得先约定好:什么时候开始说话(起始位),一句话说几个字(数据位),要不要加个校验防止听错(校验位),说完怎么表示结束(停止位),以及说话的速度多快(波特率)。任何一个对不上,这通电话就打不通。
3.1 起始位:通信开始的信号
UART是异步通信,没有单独的时钟线。那接收方怎么知道对方要发数据了?靠的就是起始位。
空闲时,TX线保持高电平。当发送方要发数据时,先把TX线拉低一个比特的时间。这个从高到低的跳变,就是起始位。接收方检测到这个跳变后,就开始按约定的波特率采样后续数据。
关键点:起始位一定是低电平,长度固定为1个比特时间。这是UART协议的硬性规定,不能改。
我在项目中遇到过一个问题:某次调试时发现数据偶尔会漏掉第一个字节。查了半天,发现是接收端的起始位检测阈值设置得太严格了。稍微放宽一点检测窗口,问题就解决了。嗯,这里要注意,起始位的检测容差很重要。
3.2 数据位:真正要传的信息
起始位之后,就是数据位了。常见的有5、6、7、8位,最常用的是8位。为什么?因为一个字节正好8位,方便和内存数据对齐。
数据位的传输顺序是LSB(最低有效位)在前,MSB(最高有效位)在后。举个例子,你要发送0x55(二进制01010101),先发最低位1,再发0,再发1……最后发最高位0。
小技巧:如果你用逻辑分析仪抓UART波形,记得把数据解析设为LSB first,否则读出来的数据全是反的。我曾经因为这个低级错误浪费了半天时间。
数据位长度怎么选?我的建议是:
- 传ASCII字符:7位就够了(标准ASCII是7位编码)
- 传二进制数据:用8位,最通用
- 特殊场景(如某些工业协议):可能用5位或6位,但很少见
3.3 校验位:数据对不对?
校验位是可选的。它用来检测数据传输过程中有没有发生错误。常见的有三种:
| 校验方式 | 说明 | 适用场景 |
|---|---|---|
| 无校验(None) | 不添加校验位 | 通信环境好,或上层有更可靠的校验 |
| 奇校验(Odd) | 数据位+校验位中1的个数为奇数 | 检测单比特错误 |
| 偶校验(Even) | 数据位+校验位中1的个数为偶数 | 检测单比特错误 |
举个例子:数据位是0x55(二进制01010101),有4个1。如果用偶校验,校验位就得是0,让总1的个数保持偶数(4+0=4)。如果用奇校验,校验位就得是1,让总1的个数变成奇数(4+1=5)。
注意:校验位只能检测奇数个比特错误。如果两个比特同时翻转,校验位是检测不出来的。所以关键数据还是得上层加CRC校验。
我个人习惯在气象站项目里用无校验。为什么?因为传感器数据量大,而且偶尔一个比特错误对温度读数影响不大。但如果是控制指令,我建议用偶校验,至少能筛掉大部分随机错误。
3.4 停止位:给接收方一点缓冲
数据位和校验位发完后,发送方要把TX线拉高至少1个比特时间。这就是停止位。常见的停止位长度有1位、1.5位、2位。
停止位的作用有两个:
- 告诉接收方:这一帧数据结束了
- 给接收方留出处理时间,准备接收下一帧
你想想看,如果接收方处理速度慢,停止位太短,下一帧数据就来了,那肯定要丢数据。所以:
- 大多数场景用1位停止位就够了
- 如果接收端是低速MCU,或者中断响应慢,用2位停止位更稳妥
- 1.5位很少用,一般出现在某些老式终端设备上
3.5 波特率:说话的速度
波特率就是每秒传输的比特数。单位是bps(bits per second)。常见的波特率有:9600、19200、38400、115200等。
为什么波特率必须匹配?说白了,发送方和接收方得用同一个节奏来采样。发送方按9600bps发,接收方按115200bps收,那读出来的数据全是乱的。
核心公式:传输一个字节(8位数据,无校验,1位停止位)需要的时间 = (1 + 8 + 1) / 波特率 = 10 / 波特率
例如9600bps下,传一个字节约1.04ms。115200bps下,约0.087ms。
波特率匹配的坑在哪里?我遇到过最典型的一个:两个设备的晶振精度不同。比如一个用16MHz晶振,另一个用12MHz晶振,即使都配置成9600bps,实际波特率也会有偏差。偏差超过3%就可能丢数据。
避坑指南:我曾经在一个项目里,两块板子用UART通信,波特率都设成115200,但就是时不时丢数据。后来用示波器一量,发现实际波特率差了4.5%。换成9600bps后,问题消失了。所以,高速率下对时钟精度要求更高。如果通信距离远或环境干扰大,降速是王道。
3.6 实际配置示例
以STM32为例,配置UART的代码片段:
// USART2配置:115200bps, 8数据位, 无校验, 1停止位
USART_InitTypeDef USART_InitStructure;
USART_InitStructure.USART_BaudRate = 115200;
USART_InitStructure.USART_WordLength = USART_WordLength_8b;
USART_InitStructure.USART_StopBits = USART_StopBits_1;
USART_InitStructure.USART_Parity = USART_Parity_No;
USART_InitStructure.USART_HardwareFlowControl = USART_HardwareFlowControl_None;
USART_InitStructure.USART_Mode = USART_Mode_Tx | USART_Mode_Rx;
USART_Init(USART2, &USART_InitStructure);
USART_Cmd(USART2, ENABLE);
这个配置在气象站项目里很常用。115200的速率足够传输温度、湿度、气压等传感器数据,而且8位数据位能直接传二进制,省去转换的麻烦。
3.7 总结一下
UART通信的参数配置,说白了就是双方对好暗号:
- 起始位:固定1位低电平,不能改
- 数据位:推荐8位,兼容性最好
- 校验位:环境好可以不用,关键数据建议用偶校验
- 停止位:默认1位,低速或干扰大用2位
- 波特率:双方必须一致,注意时钟精度带来的偏差
嗯,这些参数看着简单,但每个都有讲究。我刚开始做嵌入式那会儿,总觉得UART太基础了不值得花时间。直到有一次在野外调试气象站,因为波特率偏差导致数据全乱码,大热天的蹲在田里调了一下午……从那以后,我每次配置UART都会用示波器确认一下实际波形。
下一章我们聊聊I2C总线,那个比UART稍微复杂一点,但用好了也很顺手。