当前位置: 首页 > news >正文

STM32 零基础可移植教程 07:USART 串口打印,从 CubeMX 配置到 printf 输出

STM32 零基础可移植教程 07:USART 串口打印,从 CubeMX 配置到 printf 输出

前面几篇我们已经做了 LED、蜂鸣器、按键轮询、按键消抖和外部中断。

到这里,板子已经能“看得见、听得到、按得动”。

但嵌入式调试里还有一个非常重要的能力:让板子把信息说出来。

比如你想知道:

  • 程序有没有跑到某个位置;

  • 按键事件到底有没有触发;

  • ADC 采样值是多少;

  • I2C 设备地址有没有应答;

  • 某个变量为什么变成了奇怪的值。

这时候串口打印就很有用了。

这一篇只做一个明确目标:

STM32 通过 USART 每秒打印一次 Hello 嵌入式小站

并且把printf()重定向到串口。后面再写 ADC、I2C、SPI、CAN 时,我们就可以直接打印调试信息。

本篇目标

最终现象:

串口助手每1秒显示一行: Hello 嵌入式小站, count=1Hello 嵌入式小站, count=2Hello 嵌入式小站, count=3...

本篇用到的外设:

USART GPIO Alternate Function

本篇跑通标准:

  • Keil 编译通过;

  • 程序能下载到开发板;

  • 串口助手能收到 STM32 打印的文本;

  • printf()能正常输出;

  • 能说清楚 TX、RX、GND 怎么接;

  • 能说清楚波特率、数据位、停止位、校验位是什么意思。

本篇只讲串口发送和printf()打印,不讲串口接收、中断、DMA、不定长帧。那些后面单独讲。

准备工作

你需要准备:

|
项目
|
说明
|
| — | — |
|
STM32 开发板
|
任意 STM32 开发板
|
|
下载器
|
ST-LINK/V2 或板载 ST-LINK
|
|
USB 转 TTL 模块
|
如果开发板没有板载 USB 串口,需要外接
|
|
串口助手
|
任意常见串口工具都可以
|
|
原理图
|
确认 USART TX/RX 接到哪个引脚
|

如果你用的是 Nucleo、Discovery 或一些带 USB 串口芯片的开发板,可能板子已经把某个 USART 接到了板载 USB 串口。

如果你用的是 F103 最小系统板,通常需要外接 USB 转 TTL 模块。

硬件连接

串口最容易接错的是 TX 和 RX。

STM32 的TX是发送,USB 转 TTL 的RX是接收,所以要交叉连接:

|
STM32
|
USB 转 TTL
|
| — | — |
|
USART_TX
|
RXD
|
|
USART_RX
|
TXD
|
|
GND
|
GND
|

如果本篇只做 STM32 打印,理论上只接:

STM32 TX ->USB 转 TTL RX GND ->GND

也能看到输出。

但建议一开始就把 RX 也接好,后面讲串口接收时可以继续用。

注意电平:

STM32 GPIO 通常是3.3V TTL 电平 不要直接接 RS232 电平

普通 USB 转 TTL 模块一般支持 3.3V 或 5V,有些模块有跳帽或开关。给 STM32 接信号时,优先选择 3.3V 电平。

串口参数先看懂

我们先用最常见配置:

115200, 8N1

它的意思是:

|
参数
|

|
说明
|
| — | — | — |
|
Baud Rate
|
115200
|
每秒传输的符号数
|
|
Word Length
|
8 Bits
|
每个数据字节 8 位
|
|
Parity
|
None
|
不使用校验位
|
|
Stop Bits
|
1
|
1 个停止位
|
|
Flow Control
|
None
|
不使用硬件流控
|

串口两端参数必须一致。

STM32 配了 115200,串口助手也要选 115200。STM32 配 8N1,串口助手也要选 8N1。

如果两边不一致,常见现象就是乱码。

CubeMX 配置步骤

1. 复制上一篇工程

建议从上一篇06_key_exti或一个干净基础工程复制一份,改名为:

07_usart_printf

这一篇不依赖按键,可以只保留 LED,也可以从基础工程开始。

2. 选择 USART 实例

常见 STM32F103 工程里,经常使用:

USART1

默认引脚通常是:

PA9 ->USART1_TX PA10 ->USART1_RX

但不同芯片、不同开发板不一定一样。

在 CubeMX 左侧找到:

Connectivity ->USART1

把 Mode 设置为:

Asynchronous

3. 确认 TX/RX 引脚

CubeMX 会自动把对应引脚设置成 USART 复用功能。

比如:

PA9 ->USART1_TX PA10 ->USART1_RX

你要做的是确认这两个引脚和你的开发板接线一致。

如果开发板板载 USB 串口接的是 USART2,那就不要硬用 USART1,要改成 USART2。

原则还是那句话:

原理图接到哪个 USART,就配置哪个 USART

4. 配置 USART 参数

进入 USART 参数配置页面,设置:

|
配置项
|
推荐值
|
| — | — |
|
Baud Rate
|
115200
|
|
Word Length
|
8 Bits
|
|
Parity
|
None
|
|
Stop Bits
|
1
|
|
Data Direction
|
Receive and Transmit
|
|
Hardware Flow Control
|
None
|
|
Over Sampling
|
16 Samples
|

本篇只做发送,但建议保留 Receive and Transmit,后面讲接收可以继续用。

5. 本篇暂时不打开 USART 中断

这一篇只做阻塞发送:

HAL_UART_Transmit()

所以暂时不需要打开 USART NVIC 中断。

后面讲串口接收、中断接收、DMA 接收时,我们再单独打开。

6. 生成 Keil 工程

配置完成后点击:

GENERATE CODE

然后打开 Keil 工程,先编译一次。

Keil 工程生成和编译

打开 Keil 后,先编译:

Build / F7

确认输出里没有错误:

0Error(s)

然后看一下 CubeMX 是否生成了串口句柄。

在此之前我们先解释一下串口句柄的概念,你可以把串口想象成一扇门(比如 COM1 是房门,COM2 是窗户)。

句柄就是这扇门的把手,或者更准确地说,是操作系统发给你的一把“数字钥匙”。

你拿到这把“钥匙”(句柄),就能对门做事情:开门(初始化串口)、送东西出去(发送数据)、收东西进来(接收数据)、关门(关闭串口)。

如果你没有这把钥匙,操作系统就不允许你碰这扇门。

在很多 CubeMX 工程里,你会看到类似:

UART_HandleTypeDef huart1;

如果你用的是 USART2,可能是:

UART_HandleTypeDef huart2;

在单片机中,没有操作系统管理“句柄”这个概念,但是CubeMX生成的结构体指针,起到了完全相同的作用 他也是一个“钥匙”,包含了串口的所有状态和配置信息。

后面的代码默认用huart1。如果你的工程是huart2,需要改一个宏。

这个 huart1 变量(准确的说是它的指针 &huart1)就扮演了“串口句柄”的角色。 后续所有串口操作都要带上它:

发送:HAL_UART_Transmit(&huart1, data, len,timeout);接收:HAL_UART_Receive(&huart1, buffer, len,timeout);

完整代码

这一篇新增两个文件:

Core/Inc/app_uart.h Core/Src/app_uart.c

它们负责两件事:

  1. 封装字符串发送函数;

  2. 把 Keil 下常用的printf()输出重定向到 USART。

1. 新建Core/Inc/app_uart.h

Core/Inc目录下新建:

app_uart.h

写入下面代码:

#ifndef APP_UART_H#define APP_UART_H#include "main.h"#include <stdint.h>void App_UART_Init(void);void App_UART_Print(const char *text);void App_UART_PrintBytes(const uint8_t *data, uint16_t len);#endif

2. 新建Core/Src/app_uart.c

Core/Src目录下新建:

app_uart.c

写入下面代码:

#include "app_uart.h"#include <stdio.h>#include <string.h>/* * Default USART handle is huart1. * If your project uses USART2, define APP_UART_HANDLE as huart2. */#ifndef APP_UART_HANDLE#define APP_UART_HANDLE huart1#endifextern UART_HandleTypeDef APP_UART_HANDLE;void App_UART_Init(void){}void App_UART_Print(const char *text){if(text==NULL){return;}HAL_UART_Transmit(&APP_UART_HANDLE,(uint8_t *)text,(uint16_t)strlen(text), HAL_MAX_DELAY);}void App_UART_PrintBytes(const uint8_t *data, uint16_t len){if((data==NULL)||(len==0u)){return;}HAL_UART_Transmit(&APP_UART_HANDLE,(uint8_t *)data, len, HAL_MAX_DELAY);}int fputc(int ch, FILE *f){uint8_t data=(uint8_t)ch;HAL_UART_Transmit(&APP_UART_HANDLE,&data, 1u, HAL_MAX_DELAY);returnch;}

这里有一个可移植点:

#define APP_UART_HANDLE huart1

如果你用的是 USART2,就在app_uart.c里改成:

#define APP_UART_HANDLE huart2

如果你用的是 USART3,就改成:

#define APP_UART_HANDLE huart3

fputc()是关键。Keil 工程里printf()最终会一个字符一个字符输出,我们在fputc()里调用HAL_UART_Transmit(),这样printf()的内容就会从 USART 发出去。

3. 把app_uart.c加入 Keil 工程

在 Keil 工程树里右键:

Application/User/Core

选择:

Add Existing Files to Group'Application/User/Core'

然后添加:

Core/Src/app_uart.c

如果忘了这一步,可能会报:

undefined symbol App_UART_Init undefined symbol App_UART_Print

或者printf()没有按预期走你的重定向。

main.c 调用方式

1. Includes 区域

找到:

/*USERCODE BEGIN Includes */ /*USERCODE END Includes */

改成:

/*USERCODE BEGIN Includes */#include "app_uart.h"#include <stdio.h>/*USERCODE END Includes */

注意:printf()需要包含:

#include <stdio.h>

2. 初始化区域

确保 CubeMX 生成的串口初始化函数已经被调用。

如果用 USART1,通常是:

MX_USART1_UART_Init();

如果用 USART2,可能是:

MX_USART2_UART_Init();

然后在USER CODE BEGIN 2里添加:

/*USERCODE BEGIN2*/ App_UART_Init();printf("USART printf test start\r\n");/*USERCODE END2*/

App_UART_Init()现在是空函数,保留它是为了以后扩展,比如初始化环形缓冲区、日志等级、串口接收状态机。

3. while 循环区域

找到:

while(1){/*USERCODE END WHILE */ /*USERCODE BEGIN3*/ /*USERCODE END3*/}

改成:

while(1){/*USERCODE END WHILE */ /*USERCODE BEGIN3*/ static uint32_t count=0;count++;printf("Hello STM32, count = %lu\r\n", count);HAL_Delay(1000);/*USERCODE END3*/}

这里的\r\n是换行。

很多串口助手在 Windows 下更习惯:

\r\n

如果只写\n,有些工具可能只换行不回到行首,看起来排版有点怪。

Keil 里建议打开 MicroLIB

在 Keil 里使用printf()时,建议打开 MicroLIB。

进入:

OptionsforTarget ->Target

勾选:

Use MicroLIB

如果你不勾 MicroLIB,有些工程也能工作,但新手阶段容易遇到半主机、库函数重定向、链接配置这些额外问题。

本系列先采用最容易跑通的方式。

串口助手设置

打开串口助手,选择 USB 转 TTL 对应的 COM 口。

参数设置为:

Baud Rate:115200Data Bits:8Stop Bits:1Parity: None Flow Control: None

如果你不知道是哪个 COM 口,可以打开 Windows 设备管理器,看“端口 COM 和 LPT”。

编译、下载和验证

代码加完后,先编译:

Build / F7

没有错误后下载:

Download

打开串口助手,正常情况下你应该能看到:

USARTprintfteststart Hello STM32, count=1Hello STM32, count=2Hello STM32, count=3

如果 Keil 下载后程序没有自动运行,但你按复位键后能看到串口输出,那说明程序本身没问题,还是之前说过的下载后自动复位运行问题。

移植到其他板子的修改点

这篇的移植点主要有 7 个。

|
要改的地方
|
为什么要改
|
在哪里改
|
| — | — | — |
|
USART 实例
|
不同板子板载串口可能接 USART1/2/3
|
CubeMX Connectivity
|
|
TX/RX 引脚
|
不同芯片复用引脚不同
|
CubeMX Pinout 和原理图
|
|
串口句柄
|huart1
huart2huart3不同
|APP_UART_HANDLE|
|
波特率
|
两端必须一致
|
CubeMX USART 参数和串口助手
|
|
时钟
|
波特率依赖串口时钟
|
CubeMX Clock Configuration
|
|
电平
|
STM32 是 TTL 电平,不是 RS232 电平
|
硬件连接
|
|
GND
|
串口通信必须共地
|
开发板和 USB 转 TTL
|

换板子的推荐顺序:

  1. 看原理图,确认板载 USB 串口接到哪个 USART;

  2. 如果外接 USB 转 TTL,确认你接的是哪个 TX/RX 引脚;

  3. CubeMX 配置对应 USART 为 Asynchronous;

  4. 设置 115200、8N1;

  5. 根据实际句柄修改APP_UART_HANDLE

  6. 串口助手选择相同参数;

  7. 编译下载,看是否能收到Hello STM32

常见问题排查

1. 串口助手完全没输出

优先检查:

|
优先检查
|
具体方法
|
| — | — |
|
程序是否运行
|
下载后按复位键,看是否开始输出
|
|
TX/RX 是否接反
|
STM32 TX 接 USB-TTL RX
|
|
GND 是否共地
|
STM32 GND 和 USB-TTL GND 必须相连
|
|
COM 口是否选对
|
设备管理器查看端口号
|
|
波特率是否一致
|
STM32 和串口助手都设为 115200
|
|app_uart.c
是否加入工程
|
Keil 工程树确认
|

2. 串口乱码

常见原因:

  • 波特率不一致;

  • 串口助手数据位/停止位/校验位不一致;

  • 系统时钟配置不对,导致串口实际波特率偏差;

  • USB 转 TTL 电平不匹配;

  • GND 没接好或接触不良。

先把两边都设成:

115200, 8N1

如果还是乱码,再回 CubeMX 看 Clock Configuration 有没有红色错误。

3. 编译报huart1未定义

说明你的工程里没有huart1

可能原因:

  • 你配置的是 USART2,所以句柄是huart2

  • 你配置的是 USART3,所以句柄是huart3

  • CubeMX 没有生成 USART 初始化代码;

  • app_uart.cAPP_UART_HANDLE没改。

解决方法:

打开 CubeMX 生成的串口初始化文件或main.c,找到实际句柄,比如:

UART_HandleTypeDef huart2;

然后把app_uart.c里的宏改成:

#define APP_UART_HANDLE huart2

4.printf()没输出,但App_UART_Print()能输出

说明串口发送本身是通的,问题在printf()重定向。

优先检查:

  • app_uart.c里是否有fputc()

  • app_uart.c是否加入 Keil 工程;

  • main.c是否包含<stdio.h>

  • Keil 是否勾选Use MicroLIB

  • 工程里是否有另一个fputc()或重定向函数冲突。

5. 输出不换行

建议使用:

printf("Hello STM32\r\n");

不要只写:

printf("Hello STM32\n");

有些串口助手对\n的显示不够友好,\r\n更稳。

6. 中文输出乱码

新手阶段建议先用英文和数字输出。

中文涉及源码编码、串口助手编码显示、终端字体等问题,容易把简单问题复杂化。

先确认英文:

Hello STM32

能稳定输出,再考虑中文。

本篇小结

这一篇我们完成了 STM32 串口打印的第一步。

你现在至少应该知道:

  • 串口 TX/RX 要交叉连接;

  • STM32 和串口助手参数必须一致;

  • 常用参数是115200, 8N1

  • CubeMX 里 USART 要选择 Asynchronous;

  • HAL_UART_Transmit()可以阻塞发送数据;

  • fputc()可以把 Keil 下的printf()重定向到串口;

  • 换 USART1/2/3 时要改APP_UART_HANDLE

  • 串口没输出时,先查运行、接线、COM 口、波特率和 GND。

下一篇我们继续沿着串口往下走:

STM32 串口接收一个字节:先把 RX 中断跑通。

有了接收能力以后,板子就不只是能“说话”,还能听电脑发来的命令。

http://www.jsqmd.com/news/881682/

相关文章:

  • PanelAI 测试版即将上线!一键部署Ollama+OpenWebUI等多款AI项目,本地私有化管理面板彻底跑通
  • 内存对比工具V2.6版:解决规律性噪音地址问题
  • 中介核对对账
  • DMA优化与MIMO系统性能分析:6G通信关键技术
  • 量子随机数生成器(QRNG)技术原理与应用解析
  • Unity Remote原理与实战:真机输入调试避坑指南
  • 别再折腾Barrier了!Ubuntu 20.04下用Synergy 1.8.8实现Win/Linux键鼠共享的保姆级避坑指南
  • PagedAttention 源码解析:KV Cache 怎么管理
  • 可观测性最佳实践:构建全面的系统监控体系
  • 融合UFF与机器学习势:高通量筛选MOF吸附剂的高效精准方案
  • JMeter接口测试与压力测试实战:从协议仿真到性能瓶颈定位
  • 2026-05-24 GitHub 热点项目精选
  • Keil C251中RTX251配置错误解决方案
  • 机器学习预测高温合金氧化行为:从合金特性到反应产物的范式转变
  • C# WinForms七巧板图形编程实战:坐标系、变换与交互
  • 天辛大师浅谈湖湘文化传承,如何使用AI整理湖南文学序列(二)
  • web学习-rce远程命令执行以及http协议和简单php安全
  • 深度学习结合CT图像预测岩石渗透率:从孔隙网络到升尺度计算
  • 人工智能(AI)
  • 告别apt默认版本!Ubuntu 20.04手动编译安装snaphu 2.0.5完整指南(含gcc/make依赖解决)
  • 鲁棒非参数回归理论:重尾噪声下Huber损失与预测误差分析
  • 量子随机数生成器技术演进与多分布实时生成方案
  • 力学引导机器学习:构建土壤液化地理空间预测新范式
  • 机器学习降维与聚类在光学像差分析中的应用:PCA、FA与HC实战
  • 极验4滑块验证码W参数逆向与Python本地生成
  • VirtualBox虚拟机装完Win10后必做的5件事:共享文件夹、双向粘贴、USB连接全搞定
  • 扩散模型量化技术:挑战、突破与实战指南
  • 传奇 3 光通版手游官网下载:传奇 3 光通版最新官方下载渠道
  • 遥感新手避坑指南:在Windows 10/11上一步步搞定Py6s和6S模型(含MinGW、Fortran配置)
  • 天辛大师谈山东爱济南文化,AI赋能后的泉城文学序列