想象一下你和朋友用对讲机聊天,每次只能一个人说,一个人听,而且必须约定好说话的节奏——这就是串口通信的基本场景。在嵌入式开发中,UART/USART就像这个对讲机,负责微控制器与传感器、显示屏等外设之间的"对话"。
我第一次接触串口是在大学电子设计竞赛,当时用STM32通过串口给电脑发数据,结果收到一堆乱码。调试半天才发现是波特率没匹配,这个教训让我深刻理解了串口配置的重要性。串口通信有两个关键角色:UART(通用异步收发器)和它的升级版USART(通用同步/异步收发器)。它们就像邮局的两种寄信方式:UART是平邮,不需要双方实时同步;USART则是挂号信,需要邮局(时钟信号)确认每一步。
UART的硬件连接简单到令人感动——只需要两根数据线(TXD发送、RXD接收)加一根地线。我在调试树莓派与Arduino通信时,曾尝试省去GND线,结果数据完全错乱。后来用示波器测量才发现,两地之间存在电压差,这让我明白:共地是串口通信的底线(字面意思)。
全双工模式是UART的默认工作方式,就像电话通话可以同时说和听。但实际项目中我经常用半双工节省IO口,比如控制RS485模块时,需要通过DE/RE引脚切换收发状态。这里有个坑:切换延时必须大于一个字符的传输时间,否则会丢失起始位。
UART的数据帧就像快递包裹:
我在智能家居项目中发现,当通信距离超过3米时,1位停止位经常出现帧错误。改用2位停止位后稳定性大幅提升,代价是传输效率降低约9%。具体配置要根据噪声环境权衡:
| 配置项 | 典型值 | 适用场景 |
|---|---|---|
| 数据位 | 8位 | 大多数ASCII字符传输 |
| 校验位 | 无校验 | 短距离可靠线路 |
| 停止位 | 1位 | 标准速率(≤115200bps) |
波特率就像两人约定的说话速度。曾有个项目需要STM32与蓝牙模块通信,模块默认波特率是38400,而我的程序设成9600,结果收到的数据全是0x00或0xFF。后来用这个公式计算定时器重载值:
c复制// 对于STM32F103,USART时钟为72MHz时
#define BAUD_RATE 115200
uint32_t usart_div = 72000000 / (16 * BAUD_RATE);
USART_BRR = (usart_div / 16) << 4 | (usart_div % 16);
常见波特率对应的位周期:
实测发现,当晶振误差超过2%时,115200bps以上通信就会不稳定。这时要么换精度更高的晶振,要么降低波特率。
USART的同步模式就像军训走正步,所有人必须跟着口令(时钟信号)齐步走。我在开发RFID读卡器时,发现同步模式能稳定实现1Mbps的通信速率,而异步模式到500Kbps就出现误码。关键配置点:
c复制// SPI模式0配置示例(CPOL=0, CPHA=0)
USART_CR2 |= USART_CR2_LINEN | USART_CR2_CLKEN;
USART_CR3 |= USART_CR3_HDSEL;
当传输大量数据时,硬件流控制就像交通信号灯。有次用USART传图像数据,没启用RTS/CTS导致缓冲区溢出。后来发现Linux端需要额外设置:
bash复制stty -F /dev/ttyS0 crtscts
流控制引脚对应关系:
在工业现场,我的PLC通信项目曾饱受电磁干扰之苦。通过以下措施将误码率从10⁻⁴降到10⁻⁷:
即使硬件完美,也要做好软件防护。我的串口协议栈总会包含这些要素:
c复制// 简易CRC16实现
uint16_t crc16(uint8_t *data, uint32_t len) {
uint16_t crc = 0xFFFF;
while(len--) {
crc ^= *data++;
for(uint8_t i=0; i<8; i++)
crc = (crc & 1) ? (crc >> 1) ^ 0xA001 : (crc >> 1);
}
return crc;
}
用USART做IAP升级时,我总结出可靠的三段式协议:
python复制# PC端升级工具示例
import serial
ser = serial.Serial('COM3', 115200, timeout=1)
ser.write(b'UPGRADE')
if ser.read(5) == b'READY':
with open('firmware.bin', 'rb') as f:
while chunk := f.read(512):
ser.write(chunk)
if ser.read(1) != b'ACK':
raise TimeoutError
通过串口构建主从网络时,地址标识很关键。我的温控系统采用Modbus RTU协议,每个从机有唯一ID。主机轮询格式:
| 字段 | 从机地址 | 功能码 | 数据地址 | 数据长度 | CRC16 |
|---|---|---|---|---|---|
| 字节数 | 1 | 1 | 2 | 2 | 2 |
从机响应超时设为3个字符时间,计算公式:
code复制Timeout = 3 * 11 * 1000 / baud_rate (ms)