一、前言:为什么你的 printf 经常报错?
新手最常见问题:
- 重定向代码不全,必须勾选 MicroLIB 才能编译
- 打印偶尔卡死、串口乱码、重启
- 标准库代码放到 HAL 工程报错、函数不兼容
- 只能打印数字,无法打印字符串、中文
核心原因:缺少半主机模式关闭代码、底层输出函数不匹配、超时参数错误。
本文提供 纯原创、最精简、可量产 的两套重定向模板:
✅ 标准库版(STM32F1/F4 标准库工程通用)
✅ HAL库版(CubeMX 生成工程通用)
无需任何库配置、无需勾选 MicroLIB、零冗余、直接复制即用。
二、原理一句话看懂
C 库的 printf 底层依赖 fputc 输出单个字符。
我们重写 fputc 函数,把原本输出到电脑屏幕的数据,转发到串口 USART。
同时关闭 ARM 半主机调试模式,避免工程必须依赖 MicroLIB。
三、STM32 标准库|极简 printf 重定向(推荐量产)
适用:所有标准库工程、Keil 环境、无需 MicroLIB
使用串口:USART1,可自行修改
#include "stdio.h"
#include "stm32f10x_usart.h"
//关闭半主机模式,彻底摆脱MicroLIB
#pragma import(__use_no_semihosting)
//兼容库函数,防止编译报错
void _sys_exit(int x)
{
x = x;
}
//printf底层重定向函数
int fputc(int ch, FILE *f)
{
USART_SendData(USART1, (uint8_t)ch);
while(USART_GetFlagStatus(USART1, USART_FLAG_TXE) == RESET);
return ch;
}
优势:
- 代码最短、执行最快、无阻塞延时
- 支持字符串、数字、小数、中文打印
- 工程稳定,量产项目首选
四、STM32 HAL库|极简 printf 重定向(CubeMX 专用)
适用:CubeMX 生成的所有 HAL 工程、F1/F4/H7 全系列
无需修改任何配置,直接放在 usart.c 文件末尾
#include "stdio.h"
extern UART_HandleTypeDef huart1;
#pragma import(__use_no_semihosting)
void _sys_exit(int x)
{
x = x;
}
//HAL库 printf 重定向
int fputc(int ch, FILE *f)
{
HAL_UART_Transmit(&huart1, (uint8_t *)&ch, 1, HAL_MAX_DELAY);
return ch;
}
如果你的串口是 USART2,只需修改 &huart2 即可。
五、最简测试代码(直接验证)
int main(void)
{
USART_Init(); //标准库初始化
//MX_USART1_UART_Init(); //HAL库初始化
while(1)
{
printf("Volt:%.2f\r\n",3.14f);
printf("Status:OK\r\n");
HAL_Delay(500);
}
}
六、工程必须注意的 5 个关键点(避坑)
1、彻底不用 MicroLIB
很多旧教程必须勾选 MicroLIB,本文代码完全不需要,避免库冲突、内存占用变大。
2、中文乱码解决
工程编码改为 UTF-8,串口助手编码同样选择 UTF-8,中文完美打印。
3、不要在中断里疯狂 printf
串口发送是轮询阻塞模式,中断高频打印会导致系统卡死、堆栈溢出。
4、必须先初始化串口,再调用 printf
顺序错误会直接串口失效、程序卡死。
5、多串口打印修改句柄即可
需要 USART2 打印,仅需把 huart1 改为 huart2,无需改其他逻辑。
七、常见报错快速解决
报错1:undefined reference to `_sys_exit'
缺少 _sys_exit 函数,复制本文完整代码即可解决。
报错2:printf 无输出、黑屏
串口未初始化、波特率不匹配、重定向句柄和实际串口不一致。
报错3:偶尔卡死
中断内调用 printf、高频率打印阻塞主线程。
八、总结(工程规范)
- printf 重定向核心就是重写 fputc + 关闭半主机模式;
- 标准库、HAL库两套代码完全独立,按需复制,零修改即可用;
- 不开启 MicroLIB 是现代嵌入式工程最佳规范;
- 串口打印仅用于调试,禁止业务逻辑高频依赖 printf。