作为一名嵌入式开发者,我经常需要处理设备间的数据通信问题。USART(Universal Synchronous/Asynchronous Receiver/Transmitter)作为最基础的通信接口之一,几乎出现在所有STM32项目中。今天我就结合自己踩过的坑,详细拆解USART的工作原理和实战应用。
初学者常犯的错误是直接套用库函数而不理解底层机制,导致出现通信异常时无从排查。本文将带你从电气特性到协议层,再到代码实现,建立完整的知识框架。我们会重点分析波特率计算、数据帧结构、状态机设计等核心内容,最后给出经过量产验证的代码模板。
全双工就像双向四车道的高速公路,数据可以同时双向传输。USART的TX和RX就是独立通道,发送数据时不影响接收。实际布线时要注意:
半双工类似对讲机,需要协议层控制收发切换。典型应用是RS-485总线,硬件上只需单根数据线,但需要额外控制DE/RE引脚方向。我曾在一个工业传感器项目中,因为切换时序不当导致数据冲突,最后通过示波器抓包才发现问题。
单工模式现在较少见,早期用于广播系统。现代嵌入式设计中,即使像红外发射这类看似单向的应用,也会预留反馈通道用于状态监测。
异步通信的难点在于时钟同步。以115200波特率为例,每个bit仅约8.7μs。我推荐的做法:
同步通信的SPI接口在高速传输(>10Mbps)时优势明显。但要注意:
以STM32F103为例,其USART1挂在APB2总线(最高72MHz),USART2/3在APB1(36MHz)。这个差异直接影响最大波特率:
code复制USART1最大波特率 = PCLK2/16 = 4.5Mbps
USART2最大波特率 = PCLK1/16 = 2.25Mbps
实际项目中,我遇到过一个坑:当APB1分频设置改变但忘记调整波特率时,导致通信失败。建议在初始化代码中添加波特率校验:
c复制assert_param(IS_USART_BAUDRATE(huart->Init.BaudRate));
发送流程:
接收流程则相反,RXNE标志指示数据就绪。这里有个重要细节:读取RDR会自动清除RXNE,但如果使用中断方式,必须手动清除中断标志,否则会持续触发。
一个典型的数据帧(8N1格式)包含:
示波器实测0x55的波形如下:
code复制______| |_| |_| |_| |______
Start 01010101 Stop
测量高低电平时间可验证波特率是否准确。我曾用这个方法发现某USB转串口工具的时钟偏差达3%,导致长数据包出错。
BRR寄存器的计算公式:
code复制DIV = f_PCLK / (16 * BaudRate)
例如PCLK2=72MHz,要求9600波特率:
code复制DIV = 72,000,000 / (16 * 9600) = 468.75
BRR = (468<<4) | 12 // 整数部分占高12位,小数部分低4位
实际调试建议:
c复制void USART1_Init(uint32_t baudrate) {
// 1. 时钟使能
RCC_APB2PeriphClockCmd(RCC_APB2Periph_USART1 | RCC_APB2Periph_GPIOA, ENABLE);
// 2. GPIO配置
GPIO_InitTypeDef GPIO_InitStruct = {
.GPIO_Pin = GPIO_Pin_9, // TX
.GPIO_Mode = GPIO_Mode_AF_PP,
.GPIO_Speed = GPIO_Speed_50MHz
};
GPIO_Init(GPIOA, &GPIO_InitStruct);
GPIO_InitStruct.GPIO_Pin = GPIO_Pin_10; // RX
GPIO_InitStruct.GPIO_Mode = GPIO_Mode_IN_FLOATING;
GPIO_Init(GPIOA, &GPIO_InitStruct);
// 3. USART参数配置
USART_InitTypeDef USART_InitStruct = {
.USART_BaudRate = baudrate,
.USART_WordLength = USART_WordLength_8b,
.USART_StopBits = USART_StopBits_1,
.USART_Parity = USART_Parity_No,
.USART_Mode = USART_Mode_Tx | USART_Mode_Rx,
.USART_HardwareFlowControl = USART_HardwareFlowControl_None
};
USART_Init(USART1, &USART_InitStruct);
// 4. 中断配置(可选)
USART_ITConfig(USART1, USART_IT_RXNE, ENABLE);
NVIC_EnableIRQ(USART1_IRQn);
// 5. 使能USART
USART_Cmd(USART1, ENABLE);
}
工业级通信通常需要协议封装。以下是我总结的增强型数据包格式:
| 字段 | 长度 | 说明 |
|---|---|---|
| SOF | 1字节 | 起始符0xFF |
| LEN | 1字节 | 数据长度 |
| CMD | 1字节 | 命令字 |
| DATA | N字节 | 有效载荷 |
| CRC | 2字节 | CRC-16校验 |
| EOF | 1字节 | 结束符0xFE |
对应的状态机实现:
c复制typedef enum {
STATE_WAIT_SOF,
STATE_READ_LEN,
STATE_READ_DATA,
STATE_READ_CRC,
STATE_COMPLETE
} PacketState;
void USART1_IRQHandler(void) {
static PacketState state = STATE_WAIT_SOF;
static uint8_t buffer[256], *ptr;
static uint16_t crc, expected_len;
if(USART_GetITStatus(USART1, USART_IT_RXNE)) {
uint8_t data = USART_ReceiveData(USART1);
switch(state) {
case STATE_WAIT_SOF:
if(data == 0xFF) {
state = STATE_READ_LEN;
crc = 0xFFFF;
}
break;
case STATE_READ_LEN:
expected_len = data;
ptr = buffer;
state = (expected_len > 0) ? STATE_READ_DATA : STATE_READ_CRC;
break;
case STATE_READ_DATA:
*ptr++ = data;
if(--expected_len == 0)
state = STATE_READ_CRC;
break;
case STATE_READ_CRC:
// CRC校验处理...
if(crc_match)
state = STATE_COMPLETE;
else
state = STATE_WAIT_SOF; // 校验失败
break;
}
USART_ClearITPendingBit(USART1, USART_IT_RXNE);
}
}
问题:接收数据错位
问题:偶发丢包
c复制DMA_InitStructure.DMA_PeripheralBaseAddr = (uint32_t)&USART1->DR;
DMA_InitStructure.DMA_MemoryBaseAddr = (uint32_t)tx_buffer;
DMA_InitStructure.DMA_BufferSize = data_len;
DMA_Init(DMA1_Channel4, &DMA_InitStructure);
USART_DMACmd(USART1, USART_DMAReq_Tx, ENABLE);
c复制USART_ITConfig(USART1, USART_IT_IDLE, ENABLE);
经过多个项目的验证,这套方案在工业环境(-40℃~85℃)下能稳定运行。最后提醒:通信协议设计要预留扩展字段,我曾在产品升级时因为字段不够被迫修改协议,导致兼容性问题。