



好的,编写STM32的串口程序是入门和项目开发中最常见的任务之一。下面我将为您提供一个从零开始、非常详细的教程,包含两种最主流的方法:使用HAL库 和 使用LL库,并以轮询和中断两种方式为例。
我们以最常见的场景 UART1 为例,实现发送和接收功能。
准备工作
- 硬件: 一块STM32开发板(如STM32F103C8T6最小系统板),USB转TTL串口模块(如CH340、CP2102)。
- 软件: STM32CubeIDE(它集成了STM32CubeMX和代码编辑、编译功能,是ST官方推荐的工具)。
- 连接:
- 将开发板的 UART1_TX (通常是PA9) 连接到USB转TTL模块的 RX。
- 将开发板的 UART1_RX (通常是PA10) 连接到USB转TTL模块的 TX。
- 共地! 将开发板的 GND 与USB转TTL模块的 GND 连接。
方法一:使用STM32CubeIDE和HAL库(最推荐,最快)
HAL库抽象程度高,代码可移植性好,适合快速开发。
步骤1:创建工程并配置时钟
- 打开STM32CubeIDE,新建一个工程,选择你的MCU型号(例如STM32F103C8Tx)。
- 在图形化配置界面(Pinout & Configuration)中:
- 在左侧System Core -> SYS中,将Debug选为Serial Wire(如果要用ST-Link调试)。
- 在RCC中,将High Speed Clock (HSE) 选为Crystal/Ceramic Resonator(使用外部高速晶振)。
步骤2:配置UART引脚和参数
- 在左侧Connectivity -> USART1中:
- 将Mode设置为 Asynchronous(异步模式)。
- 此时,右边的芯片图上PA9和PA10会自动被设置为USART1_TX和USART1_RX。
- 在下方Parameter Settings中配置串口参数:
- Baud Rate:
115200(常用波特率) - Word Length:
8 Bits(数据位) - Parity:
None(校验位) - Stop Bits:
1(停止位) - Over Sampling:
16 Samples(默认) - 其他保持默认。
- Baud Rate:
步骤3:生成代码
- 点击右上角的"GENERATE CODE"按钮。
- STM32CubeIDE会自动生成一个完整的MDK/IDE/IAR等工具链的项目代码。
步骤4:编写用户代码
生成的代码中,你的用户代码应该写在/* USER CODE BEGIN ... */ 和 /* USER CODE END ... */ 之间,以防止下次重新生成代码时被覆盖。
示例1:轮询方式发送 + 中断方式接收(最常用)
我们实现一个回声(Echo) 功能:单片机收到一个字节,就原样发回给电脑。
-
首先开启接收中断:
- 在
main.c的/* USER CODE BEGIN 2 */部分,启动UART的接收中断。
/* USER CODE BEGIN 2 */ // 启动UART1的接收中断 // 参数1: UART句柄, 参数2: 存放接收数据的缓冲区, 参数3: 接收的数据量(1字节) HAL_UART_Receive_IT(&huart1, &rx_data, 1); /* USER CODE END 2 */ - 在
-
定义全局变量:
- 在
/* USER CODE BEGIN PV */部分,定义接收缓冲区。
/* USER CODE BEGIN PV */ uint8_t rx_data; // 用于存放接收到的1个字节 /* USER CODE END PV */ - 在
-
重写接收中断回调函数:
- 在
main.c文件的末尾,/* USER CODE BEGIN 4 */部分,重写HAL库的UART接收完成中断回调函数。当收到一个字节后,这个函数会被自动调用。
/* USER CODE BEGIN 4 */ // UART接收完成中断回调函数 void HAL_UART_RxCpltCallback(UART_HandleTypeDef *huart) { // 判断是否是UART1触发的中断 if (huart->Instance == USART1) { // 1. 将收到的字节原样发送回去(轮询方式发送) HAL_UART_Transmit(&huart1, &rx_data, 1, 1000); // 超时时间1000ms // 2. !!!重要:重新启动接收中断,以等待下一个字节 !!! HAL_UART_Receive_IT(&huart1, &rx_data, 1); } } /* USER CODE END 4 */ - 在
代码解释:
HAL_UART_Receive_IT(...): 启动一次中断接收,设置好接收缓冲区和长度后,UART硬件会在收到数据时自动产生中断,并将数据存入缓冲区,然后调用回调函数。HAL_UART_RxCpltCallback: 这是一个弱定义函数,我们重写它来加入自己的逻辑。在这里,我们简单地回传收到的数据。- 必须重新启动接收中断:因为HAL库的中断接收是一次性的,每次完成后都需要重新启动才能进行下一次接收。
示例2:轮询方式发送和接收(简单,但会阻塞)
/* USER CODE BEGIN 2 */
uint8_t tx_buffer[] = "Hello World!\r\n"; // 要发送的数据
uint8_t rx_buffer[10]; // 接收缓冲区
// 轮询发送字符串
HAL_UART_Transmit(&huart1, tx_buffer, sizeof(tx_buffer) - 1, 1000);
// 轮询接收10个字节(程序会停在这里,直到收到10个字节或超时)
HAL_UART_Receive(&huart1, rx_buffer, 10, 1000);
// 将接收到的10个字节再发送回去
HAL_UART_Transmit(&huart1, rx_buffer, 10, 1000);
/* USER CODE END 2 */
方法二:使用LL库(更接近底层,效率更高)
LL库提供了对寄存器的直接操作,代码量小,运行效率高,但需要开发者对寄存器有更多了解。
步骤1&2:工程配置
与HAL库步骤相同,但在Project Manager -> Advanced Settings中,将USART1的API全部改为LL。
步骤3:编写LL库代码
同样实现Echo功能。
-
开启中断并编写中断服务函数:
- 在
main.c的/* USER CODE BEGIN 2 */部分,使能UART接收中断。
/* USER CODE BEGIN 2 */ // LL库:使能UART1的接收寄存器非空中断 LL_USART_EnableIT_RXNE(USART1); /* USER CODE END 2 */ - 在
-
编写中断服务函数:
- 在
stm32f1xx_it.c文件中,找到USART1_IRQHandler函数。
// 在 stm32f1xx_it.c 文件中 void USART1_IRQHandler(void) { /* USER CODE BEGIN USART1_IRQn 0 */ // 检查是否是"接收寄存器非空"中断标志 if(LL_USART_IsActiveFlag_RXNE(USART1)) { // 读取接收到的数据(这个操作会自动清除RXNE标志) uint8_t rx_data = LL_USART_ReceiveData8(USART1); // 等待发送寄存器为空,确保可以发送新数据 while(!LL_USART_IsActiveFlag_TXE(USART1)); // 将收到的数据发送回去 LL_USART_TransmitData8(USART1, rx_data); } /* USER CODE END USART1_IRQn 0 */ // ... 其他代码 } - 在
LL库代码解释:
LL_USART_EnableIT_RXNE: 使能"接收寄存器非空"中断。当UART收到数据后,状态寄存器的RXNE位会置1,并产生中断。LL_USART_IsActiveFlag_RXNE: 在中断服务函数中检查是否是接收中断。LL_USART_ReceiveData8: 读取接收到的数据,读取后硬件会自动清除RXNE标志。LL_USART_IsActiveFlag_TXE和LL_USART_TransmitData8: 检查发送寄存器是否为空,然后写入要发送的数据。
测试与调试
- 编译并下载程序到STM32开发板。
- 连接串口模块到电脑。
- 在电脑上打开串口调试助手(如Putty、SecureCRT、或者STM32CubeIDE自带的串口监视器)。
- 选择正确的COM口,设置波特率为
115200,8N1。 - 在串口调试助手的发送框中输入任意字符,点击发送,你应该能看到对方立即回复同样的字符。这就是Echo功能。
总结与关键点
| 特性 | HAL库 | LL库 |
|---|---|---|
| 易用性 | 高,函数抽象好,逻辑清晰 | 中,需要了解寄存器 |
| 效率/代码大小 | 较低,代码较大 | 高,代码精简 |
| 可移植性 | 好,在不同STM32系列间移植容易 | 一般,与系列相关性高 |
| 推荐场景 | 快速原型开发、初学者、复杂应用 | 对性能和资源有严格要求的项目 |
关键注意事项:
- 中断 vs 轮询:中断方式不阻塞主程序,是实际项目中的首选。轮询方式简单,但会浪费CPU时间。
- 重入中断:使用HAL库中断接收时,务必在回调函数中重新启动接收。
- 缓冲区管理:上面的例子是单字节收发。如果处理不定长数据,需要设计环形缓冲区,并结合空闲中断(IDLE) 来判定一帧数据接收完成。HAL库提供了
HAL_UARTEx_ReceiveToIdle_IT函数来处理这种情况。 - 引脚复用:确保你的UART引脚没有和其他功能(如USB)冲突。
通过以上步骤,你应该可以成功编写并运行你的第一个STM32串口程序。从简单的Echo开始,逐步尝试更复杂的功能,如解析指令、传输数据包等。

被折叠的 条评论
为什么被折叠?



