4. UART串口通信:从配置到实战

UART串口,搞嵌入式的人天天打交道。说它是调试的“生命线”一点不过分——板子能不能跑、数据对不对,很多时候全靠它传话。我刚开始用STM32那会儿,总觉得UART配置简单,结果一上电就是乱码,折腾了大半天才发现是时钟配错了。嗯,这章咱们就把UART的配置流程、三种收发模式,以及那些容易踩的坑,一次性说清楚。

4.1 UART配置流程:三步走

在CubeIDE里配UART,其实就三步。我习惯按这个顺序来,不容易漏东西。

  1. 引脚与时钟:在Pinout & Configuration里选好UART外设,对应的TX/RX引脚会自动分配。记得检查时钟树,确保USART的时钟源是开启的。
  2. 参数设置:波特率、数据位、停止位、校验位。大部分场景用115200-8-N-1就够了。
  3. 中断/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配合接收不定长数据的完整过程。

UART中断+DMA不定长接收流程图 初始化DMA接收 等待数据到达 DMA自动搬运数据到内存 触发空闲中断 计算长度,处理数据 重新开启DMA接收 说明: 1. DMA负责搬运,不占CPU 2. 空闲中断标记一帧结束 3. 处理完必须重新开启

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模式处理大数据。记住三点:时钟要准、中断要开、回调要续。把这三点记牢了,串口通信基本不会出大问题。