UART(Universal Asynchronous Receiver/Transmitter)作为嵌入式系统中最基础的串行通信接口,几乎存在于所有MCU的硬件外设中。它的异步传输特性使得仅需两根信号线(TX/RX)就能实现全双工通信,典型应用场景包括:
在STM32的HAL库中,UART驱动抽象出了三层结构:
这种设计使得移植代码时只需关注硬件差异,而业务逻辑代码可以跨平台复用。以STM32F4系列为例,其UART外设支持:
注意:实际波特率误差应控制在2%以内,否则可能出现通信失败。计算公式为:
USARTDIV = fCK / (16 * BaudRate)
其中fCK为UART时钟频率(如APB1 45MHz)
初始化是UART使用的第一步,这个函数会配置所有硬件参数并建立通信链路。其函数原型为:
c复制HAL_StatusTypeDef HAL_UART_Init(UART_HandleTypeDef *huart);
关键配置参数通过huart->Init结构体传递:
c复制typedef struct {
uint32_t BaudRate; // 波特率(如115200)
uint32_t WordLength; // 数据位(UART_WORDLENGTH_8B/9B)
uint32_t StopBits; // 停止位(UART_STOPBITS_1/1.5/2)
uint32_t Parity; // 校验位(UART_PARITY_NONE/EVEN/ODD)
uint32_t Mode; // 收发模式(UART_MODE_TX_RX)
uint32_t HwFlowCtl; // 硬件流控(UART_HWCONTROL_NONE/RTS/CTS)
uint32_t OverSampling; // 过采样(UART_OVERSAMPLING_16/8)
} UART_InitTypeDef;
典型初始化代码示例:
c复制UART_HandleTypeDef huart2;
void UART_Init(void) {
huart2.Instance = USART2;
huart2.Init.BaudRate = 115200;
huart2.Init.WordLength = UART_WORDLENGTH_8B;
huart2.Init.StopBits = UART_STOPBITS_1;
huart2.Init.Parity = UART_PARITY_NONE;
huart2.Init.Mode = UART_MODE_TX_RX;
huart2.Init.HwFlowCtl = UART_HWCONTROL_NONE;
huart2.Init.OverSampling = UART_OVERSAMPLING_16;
if (HAL_UART_Init(&huart2) != HAL_OK) {
Error_Handler();
}
}
避坑指南:
- 确保时钟已使能(__HAL_RCC_USART2_CLK_ENABLE())
- GPIO需配置为复用功能(GPIO_MODE_AF_PP)
- 过采样选择16倍时兼容性更好,但8倍可降低功耗
最基本的同步传输方式,函数会阻塞直到完成操作:
c复制// 发送数据(阻塞)
HAL_StatusTypeDef HAL_UART_Transmit(
UART_HandleTypeDef *huart,
uint8_t *pData,
uint16_t Size,
uint32_t Timeout);
// 接收数据(阻塞)
HAL_StatusTypeDef HAL_UART_Receive(
UART_HandleTypeDef *huart,
uint8_t *pData,
uint16_t Size,
uint32_t Timeout);
使用示例:
c复制uint8_t tx_data[] = "Hello World!";
uint8_t rx_data[20];
// 发送示例
HAL_UART_Transmit(&huart2, tx_data, strlen((char*)tx_data), 100);
// 接收示例
HAL_UART_Receive(&huart2, rx_data, 10, 1000); // 等待接收10字节,超时1秒
非阻塞方式,通过中断回调处理:
c复制// 启动中断发送
HAL_StatusTypeDef HAL_UART_Transmit_IT(
UART_HandleTypeDef *huart,
uint8_t *pData,
uint16_t Size);
// 启动中断接收
HAL_StatusTypeDef HAL_UART_Receive_IT(
UART_HandleTypeDef *huart,
uint8_t *pData,
uint16_t Size);
需要实现中断回调函数:
c复制// 发送完成回调
void HAL_UART_TxCpltCallback(UART_HandleTypeDef *huart) {
if(huart->Instance == USART2) {
// 发送完成处理
}
}
// 接收完成回调
void HAL_UART_RxCpltCallback(UART_HandleTypeDef *huart) {
if(huart->Instance == USART2) {
// 处理接收到的数据
// 如需持续接收,需再次调用HAL_UART_Receive_IT
}
}
高效的大数据量传输方式:
c复制// DMA发送
HAL_StatusTypeDef HAL_UART_Transmit_DMA(
UART_HandleTypeDef *huart,
uint8_t *pData,
uint16_t Size);
// DMA接收
HAL_StatusTypeDef HAL_UART_Receive_DMA(
UART_HandleTypeDef *huart,
uint8_t *pData,
uint16_t Size);
配套回调函数:
c复制void HAL_UART_TxHalfCpltCallback(UART_HandleTypeDef *huart); // 发送过半
void HAL_UART_TxCpltCallback(UART_HandleTypeDef *huart); // 发送完成
void HAL_UART_RxHalfCpltCallback(UART_HandleTypeDef *huart); // 接收过半
void HAL_UART_RxCpltCallback(UART_HandleTypeDef *huart); // 接收完成
void HAL_UART_ErrorCallback(UART_HandleTypeDef *huart); // 错误处理
性能对比(基于STM32F407@168MHz):
传输方式 1KB数据耗时 CPU占用率 阻塞式 8.7ms 100% 中断式 8.7ms 30%~50% DMA 8.7ms <5%
当需要非标准波特率时,可通过重写HAL_UART_MspInit函数实现:
c复制void HAL_UART_MspInit(UART_HandleTypeDef* huart) {
if(huart->Instance == USART2) {
// 启用时钟
__HAL_RCC_USART2_CLK_ENABLE();
// 计算并设置BRR寄存器
uint32_t clock = 45000000; // APB1时钟
uint32_t baud = 250000; // 目标波特率
huart->Instance->BRR = (clock + baud/2) / baud;
}
}
HAL库提供了接收超时中断(RTO),适合处理不定长数据:
c复制// 启用接收超时(单位:波特周期)
#define RX_TIMEOUT 50 // 约等于3.5个字符间隔
SET_BIT(huart2.Instance->CR2, USART_CR2_RTOEN);
MODIFY_REG(huart2.Instance->RTOR, USART_RTOR_RTO, RX_TIMEOUT);
// 处理超时中断
void HAL_UART_RxTimeoutCallback(UART_HandleTypeDef *huart) {
uint16_t len = huart->RxXferSize - __HAL_DMA_GET_COUNTER(huart->hdmarx);
// 处理已接收的数据
}
常见错误类型及处理方法:
c复制void HAL_UART_ErrorCallback(UART_HandleTypeDef *huart) {
uint32_t errors = huart->ErrorCode;
if(errors & HAL_UART_ERROR_PE) {
// 奇偶校验错误
}
if(errors & HAL_UART_ERROR_NE) {
// 噪声错误
}
if(errors & HAL_UART_ERROR_FE) {
// 帧错误(检查波特率是否匹配)
}
if(errors & HAL_UART_ERROR_ORE) {
// 溢出错误(处理速度跟不上接收速度)
__HAL_UART_CLEAR_OREFLAG(huart);
}
if(errors & HAL_UART_ERROR_DMA) {
// DMA传输错误
}
// 错误处理后需重新初始化接收
HAL_UART_Receive_IT(huart, rx_buf, BUF_SIZE);
}
解决数据接收与处理的速率不匹配问题:
c复制#define BUF_SIZE 256
typedef struct {
uint8_t buffer[BUF_SIZE];
volatile uint16_t head;
volatile uint16_t tail;
} ring_buffer_t;
ring_buffer_t rx_ring;
void UART_RxCallback(UART_HandleTypeDef *huart) {
while(huart->Instance->SR & USART_SR_RXNE) {
uint8_t data = (uint8_t)(huart->Instance->DR & 0xFF);
uint16_t next = (rx_ring.head + 1) % BUF_SIZE;
if(next != rx_ring.tail) {
rx_ring.buffer[rx_ring.head] = data;
rx_ring.head = next;
}
}
}
实现简单的AT指令处理:
c复制typedef struct {
const char *cmd;
void (*handler)(const char *args);
} uart_cmd_t;
uart_cmd_t cmd_table[] = {
{"LED_ON", led_on_handler},
{"LED_OFF", led_off_handler},
{"GET_TEMP", get_temp_handler},
{NULL, NULL}
};
void process_command(char *line) {
for(int i=0; cmd_table[i].cmd; i++) {
if(strncmp(line, cmd_table[i].cmd, strlen(cmd_table[i].cmd)) == 0) {
char *args = line + strlen(cmd_table[i].cmd);
while(*args == ' ') args++;
cmd_table[i].handler(args);
return;
}
}
printf("Unknown command: %s\r\n", line);
}
c复制uint8_t dma_buf1[128], dma_buf2[128];
void start_double_buffer(void) {
HAL_UART_Receive_DMA(&huart2, dma_buf1, 128);
HAL_UARTEx_ReceiveToIdle_DMA(&huart2, dma_buf2, 128);
}
void HAL_UARTEx_RxEventCallback(UART_HandleTypeDef *huart, uint16_t Size) {
if(huart->hdmarx->Instance->CR & DMA_SxCR_CT) {
// 当前使用buf1,处理buf2数据
process_data(dma_buf2, Size);
} else {
// 当前使用buf2,处理buf1数据
process_data(dma_buf1, Size);
}
}
c复制void enter_low_power(void) {
HAL_UART_Abort(&huart2); // 终止当前传输
__HAL_UART_DISABLE(&huart2);
__HAL_RCC_USART2_CLK_DISABLE();
}
void wakeup_uart(void) {
__HAL_RCC_USART2_CLK_ENABLE();
__HAL_UART_ENABLE(&huart2);
HAL_UART_Receive_IT(&huart2, rx_buf, BUF_SIZE);
}
经过多年实际项目验证,UART通信的稳定性往往取决于以下细节: