4. UART串口通信:从配置到实战
UART串口,搞嵌入式的人天天打交道。说它是调试的“生命线”一点不过分——板子能不能跑、数据对不对,很多时候全靠它传话。我刚开始用STM32那会儿,总觉得UART配置简单,结果一上电就是乱码,折腾了大半天才发现是时钟配错了。嗯,这章咱们就把UART的配置流程、三种收发模式,以及那些容易踩的坑,一次性说清楚。
4.1 UART配置流程:三步走
在CubeIDE里配UART,其实就三步。我习惯按这个顺序来,不容易漏东西。
- 引脚与时钟:在Pinout & Configuration里选好UART外设,对应的TX/RX引脚会自动分配。记得检查时钟树,确保USART的时钟源是开启的。
- 参数设置:波特率、数据位、停止位、校验位。大部分场景用115200-8-N-1就够了。
- 中断/NVIC:如果用中断模式,记得在NVIC Settings里勾上对应的中断使能。
我的习惯:配完参数后,我会先看一眼CubeMX生成的时钟树截图,确认USART的时钟频率对不对。有一次我配了UART4,结果时钟树里忘了开PCLK1,串口死活不工作。这种低级错误,看一眼就能避免。
4.2 三种收发模式:轮询、中断、DMA
HAL库提供了三套收发API,分别对应不同的应用场景。我整理了一张表,方便你对比。
| 模式 | API函数 | 特点 | 适用场景 |
|---|---|---|---|
| 轮询 | HAL_UART_Transmit / HAL_UART_Receive |
阻塞等待,CPU占用高 | 调试打印、低速数据 |
| 中断 | HAL_UART_Transmit_IT / HAL_UART_Receive_IT |
非阻塞,CPU可干别的 | 不定长数据、交互式通信 |
| DMA | HAL_UART_Transmit_DMA / HAL_UART_Receive_DMA |
后台搬运,几乎不占CPU | 大数据量、高速传输 |
4.2.1 轮询模式:简单但别乱用
轮询模式说白了就是CPU死等。发一个字节,CPU就在那盯着,直到发完才回来。我一般只在调试时用,或者发一些不频繁的控制指令。
// 发送一个字符串
uint8_t msg[] = "Hello, UART!\r\n";
HAL_UART_Transmit(&huart1, msg, sizeof(msg), HAL_MAX_DELAY);
// 接收固定长度数据
uint8_t rx_buf[10];
HAL_UART_Receive(&huart1, rx_buf, 10, 1000); // 超时1秒
注意:轮询接收时,如果对方发来的数据少于你指定的长度,程序会一直卡在HAL_UART_Receive里,直到超时。我曾经在一个项目里用轮询收GPS数据,结果GPS模块偶尔丢包,程序就卡死了。后来改成中断模式才解决。
4.2.2 中断模式:最常用的方式
中断模式是我用得最多的。它不阻塞CPU,数据来了自动进中断处理。HAL库的中断回调函数是HAL_UART_RxCpltCallback,你只需要在里面处理收到的数据就行。
// 启动中断接收(只收1个字节)
uint8_t rx_byte;
HAL_UART_Receive_IT(&huart1, &rx_byte, 1);
// 中断回调
void HAL_UART_RxCpltCallback(UART_HandleTypeDef *huart)
{
if (huart->Instance == USART1)
{
// 处理收到的字节
process_byte(rx_byte);
// 重新开启下一次接收
HAL_UART_Receive_IT(&huart1, &rx_byte, 1);
}
}
这里有个关键点:每次中断接收完成后,必须重新调用HAL_UART_Receive_IT,否则只能收到一次数据。我见过不少新手在这栽跟头。
4.2.3 DMA模式:解放CPU
DMA模式适合大批量数据传输。比如从传感器连续读取数据流,或者通过串口更新固件。DMA会在后台自动把数据从外设搬到内存,CPU完全不用管。
// DMA发送
uint8_t large_data[1024];
HAL_UART_Transmit_DMA(&huart1, large_data, 1024);
// DMA接收(需要配合空闲中断实现不定长接收)
HAL_UART_Receive_DMA(&huart1, dma_rx_buf, DMA_BUF_SIZE);
__HAL_UART_ENABLE_IT(&huart1, UART_IT_IDLE); // 开启空闲中断
实战经验:DMA接收不定长数据时,我习惯配合空闲中断(IDLE Line Interrupt)。当总线空闲时,触发中断,在中断里计算实际收到的字节数。这样既不用固定长度,也不会丢数据。
4.3 中断与DMA的协同:一个典型流程
为了让你更直观地理解,我画了一张流程图,展示中断+DMA配合接收不定长数据的完整过程。
4.4 避坑指南:我踩过的那些雷
UART调试中,有些问题反复出现。我把自己踩过的坑整理了一下,希望能帮你少走弯路。
- 乱码:八成是波特率不对。检查时钟树,确认USART时钟频率和CubeMX里配的一致。我遇到过PLL配置改了,但UART时钟没跟着变,结果波特率偏了。
- 只能收一次数据:中断模式下,每次回调完必须重新调用
HAL_UART_Receive_IT。DMA模式下,如果不用循环模式,传输完成标志位会卡住,需要手动清除。 - 数据错位:检查数据位和停止位设置。有些模块要求9位数据或2位停止位,配错了就会丢字节。
- 中断不触发:NVIC里没使能中断,或者中断优先级被其他外设抢了。我习惯把UART中断优先级设高一点,尤其是做数据流的时候。
曾经有一次,我在一个项目里用UART和蓝牙模块通信。蓝牙模块要求波特率是115200,但我忘了改CubeMX里的时钟树,实际波特率跑成了9600。结果蓝牙死活连不上,我查了三天硬件,最后发现是软件配置的问题。从那以后,我每次配完UART都会用示波器量一下TX脚的波形,确认波特率对不对。
4.5 小结
UART串口通信,配置不难,但细节多。轮询模式适合调试,中断模式是主力,DMA模式处理大数据。记住三点:时钟要准、中断要开、回调要续。把这三点记牢了,串口通信基本不会出大问题。