📋 文章目录

本文记录了嵌入式开发中 USART 串口通信的学习过程,包含理论学习、环境搭建、代码实现与问题排查等内容。以下是文章结构概览:

  1. 📅 学习进度概览
  2. 📝 学习记录与问题追踪
    1. 1. 理论学习与概念梳理
    2. 2. 环境搭建与第一个工程
    3. 3. 轮询模式工程创建:代码实现与调试过程
    4. 4. 中断模式工程创建:代码实现与调试过程
    5. 5. DMA模式工程创建:代码实现与调试过程
    6. 6. printf重定向到usart串口上,实现printf打印信息到串口助手上

📅 学习进度概览

当前学习阶段:嵌入式开发 - USART 串口通信

当前使用的开发板:STM32F407VGT6-Disk

开始时间:2026 年 8 月 22 日

学习目标:

  • 理解 USART(通用同步异步收发器)的基本工作原理
  • 掌握 STM32(或其他 MCU)的 USART 外设配置流程
  • 能够实现基本的串口收发功能(查询/中断/DMA 方式)
  • 记录并解决学习过程中遇到的实际问题

注意:本文中每个实验均另开了一个工作空间并重新创建项目,并非在原先工程上做出改变


📝 学习记录与问题追踪

1. 理论学习与概念梳理

时间:2026/08/16

内容:

  • 复习了串行通信与并行通信的区别。
  • 理解了 USART 与 UART 的关键差异:USART 支持同步模式(有时钟线),UART 仅支持异步。
  • 学习了波特率、数据位、停止位、奇偶校验等关键参数的含义。

2. 环境搭建与第一个工程

时间:2026/08/20

内容:

  • 使用 STM32CubeMX 初始化 USART 外设,生成代码。
  • 使用 STM32CubeIDE 进行开发。
  • 使用 USB 转 TTL 模块连接电脑配合串口助手工具进行调试。

遇到的问题:

  • 串口助手打印结果乱码
  • 串口助手无打印结果
  • 配合 printf 函数重定向但 printf 函数没能打印出结果

3. 轮询模式工程创建:代码实现与调试过程

时间:2026/08/20

使用 STM32CubeMX 创建新工程,并开启外部高速晶振(HSE)作为系统时钟源。

创建工程,开启外部晶振:

创建工程,开启外部晶振

配置时钟:

配置时钟

修改 debug 模式:

修改debug模式

开启串口为异步模式:

开启串口为异步模式

勾选为每个外设单独生成 .c/.h 文件

勾选为每个外设单独生成.c/.h文件

生成代码,使用 VS Code 打开项目,借助扩展包采用提示词工程给出的代码写好测试发送的代码

打开 CubeIDE,代码自动更新,点击小锤子编译,无报错无警告

按照设置的串口连接好硬件及开发板,为 USB 转 TTL 模块下载好驱动,本文采用波特律动串口助手中提供的驱动下载链接下载好 CH340 型号驱动,并按如图方式连接好硬件部分,TXD 接 PA10,RXD 接 PA9,并将 USB 口接在电脑上

烧录代码

打开波特律动串口助手,选择好串口并开启,查看串口信息打印

遇到的实际问题与解决方案:

  • 发现串口有东西打印出来,且打印间隔为原 while 函数中设置的 1 秒,但是打印出来的数据是乱码,本应该是 "Hello World"
  • 时间间隔没问题,则程序确实进入了 while 循环,且循环没问题,但数据乱码,问题大概率出在波特率不匹配,硬件接线问题
  • [最终解决方案]:修改连接的串口,我最终改成使用usart2串口,使用PA2,PA3两个引脚,--取消时钟配置--并重新生成代码,代码最终逻辑如图,波特率,校验位等无任何修改,最终得到串口输出结果

改写代码为接收电脑发送的数据,并用发送函数检验是否收到

最终也能成功返回发送的数据

  • 最后总结原因:成功与失败的最大区别是取消了外部晶振提供时钟,采用的内部HSI,大概率就是这个原因影响了串口

4.中断模式工程创建:代码实现与调试过程

时间:2026/08/21

MX工程创建与轮询方式类似,但本次不使用外部时钟,且勾选usart2的全局中断,点击usart2的NVIC标签,勾选全局中断,保存并生成代码,代码仅需将发送,接收函数加上_IT后缀,将传递的超时时间字段去掉即可。

编译并烧录,查看串口助手打印结果

结果在未发送任何数据时界面不断发送空,发送了R0后界面不断发送R0回来

查看代码发现将两个中断模式的发送接收函数都写到while循环里了,中断模式不像轮询模式一样,是非阻塞的,会一直循环执行,导致循环过快而显示如图样式,只要将中断接收函数放到while循环外即可,但是一般不会将中断发送放在while循环里,不然也会导致上述结果,一般是中断接收后调用中断处理函数HAL_UART_RxCpltCallback,此函数可自己重新定义一下,并将逻辑写入,而后中断接收完成后将执行函数中的逻辑

那有要求中断接收不能放在while循环里,要保证能不断接收则可将中断接收函数放在中断处理函数的末尾,这样中断处理函数逻辑完成前就会进入下一次中断接收。

5.DMA模式工程创建:代码实现与调试过程

时间:2026/08/21

MX项目创建只要基于中断模式下再添加DMA通道即可,如图分别添加Rx和Tx的DMA通道保持默认设置即可

保存并生成代码,发送、接收函数仅需在中断发送函数的基础上将_IT后缀改为_DMA即可,我在中断回调处理函数中加上了阻塞式发送并在末尾补上DMA接收,将接收数组放在PV注释对中以防报错

编译,烧录,查看串口助手,发现能正常接收并回复

接收不定长数据

主要靠的是串口空闲中断,使用HAL_UARTEx_ReceiveToIdle,这个函数也有阻塞,中断,DMA三种方式,也是通过修改后缀即可使用,另外,使用此函数对应的回调函数也有所不同,这里应使用HAL_UARTEx_RxEventCallback回调函数,这里我直接使用DMA方式在本工程里进行测试结果如图,由于我定义的数组长度是50,所以发送过长时触发了传输过半中断,故而后面的数据没能返回,可以使用_HAL_DMA_DISABLE_IT函数取消传输过半中断。

6.printf重定向到usart串口上,实现printf打印信息到串口助手上

时间:2026/08/21

目前尝试了各种方法,如重写fputc函数,重写_write函数,关闭半主机模式(代码实现,写在fputc函数后)均未能实现,须待解决

解决方法如下:

经过排查,最终通过以下方案成功实现了 printf 重定向。核心思路是:在 STM32CubeIDE 环境中(GCC编译环境),重写 __io_putchar 函数,将 printf 的输出字符逐个通过 HAL 库的串口发送函数送出,同时关闭半主机模式以避免链接错误。

第一步(最重要的步骤):将usb转ttl接口的3v3的引脚接到板子的3v3接线柱上,其他接口照旧(Rx接Tx,Tx接Rx,共GND)

第二步:包含头文件

在 usart.h/* USER CODE BEGIN Includes */ 区域添加标准输入输出头文件:

/* USER CODE BEGIN Includes */
#include <stdio.h>
/* USER CODE END Includes */

第三步:重写 __io_putchar 函数

在 usart.c中, /* USER CODE BEGIN 1 */ 之间添加如下代码。这里使用阻塞式发送 HAL_UART_Transmit,将 printf 输出的每个字符通过 USART2 发送出去:

int __io_putchar(int ch)
{
  HAL_UART_Transmit(&huart2, (uint8_t *)&ch, 1, HAL_MAX_DELAY);
  return ch;
}

第四步:修改主函数测试

while (1)
  {
    /* USER CODE END WHILE */
    printf("Hello World!\n\r");
    HAL_Delay(1000);
    /* USER CODE BEGIN 3 */
  }

第五步:测试结果

关键注意事项:

  • 串口句柄匹配__io.putchar 中使用的 huart2 必须与 CubeMX 中实际配置的串口句柄一致。如果使用 USART1,则改为 &huart1
  • 波特率匹配:确保 CubeMX 中配置的波特率与串口助手一致(如 115200),否则会出现乱码。
  • 换行符:建议在 printf 字符串末尾添加 \r\n,否则串口助手可能不会正确换行。
  • 接线:一定要接3v3

完成以上步骤后,重新编译烧录,打开串口助手即可看到 printf 打印的信息。

Logo

智能硬件社区聚焦AI智能硬件技术生态,汇聚嵌入式AI、物联网硬件开发者,打造交流分享平台,同步全国赛事资讯、开展 OPC 核心人才招募,助力技术落地与开发者成长。

更多推荐