How to Redirect printf to UART on STM32
·276 字
目录
在嵌入式开发与调试过程中,printf 是最直观、最常用的日志输出工具。许多新手在刚接触 STM32 时,往往被繁琐的标准库/底层系统调用重写搞得一头雾水。
本文介绍一种极简、直接且跨平台兼容的 printf 重定向方法,涵盖 Keil MDK 与 STM32CubeIDE (GCC) 两大主流开发环境,3 分钟即可配置完毕。
核心原理 #
C 语言标准库中的 printf 底层最终通过字符输出函数将字符逐个写入底层硬件:
- Keil MDK (ARM Compiler):依赖标准 I/O 的
fputc(int ch, FILE *f)。 - GCC / STM32CubeIDE:底层通常走
__io_putchar(int ch)或_write()系统调用。
我们只需要重写对应的底层单字符发送函数,将其映射到 STM32 的串口发送函数(如 HAL_UART_Transmit)即可。
一、代码实现(以 HAL 库为例) #
打开工程的 main.c(或单独的 retarget.c 文件),完成以下两步:
1. 引入标准输入输出头文件 #
#include <stdio.h>
2. 添加重定向实现代码 #
将以下代码段粘贴到 /* USER CODE BEGIN 0 */ 或全局代码区域:
/* ==================== printf 串口重定向 ==================== */
#if defined(__CC_ARM) || defined(__ARMCC_VERSION) // Keil MDK (AC5 / AC6)
int fputc(int ch, FILE *f) {
// 阻塞模式发送 1 字节字符
HAL_UART_Transmit(&huart1, (uint8_t *)&ch, 1, 0xFFFF);
return ch;
}
#elif defined(__GNUC__) // STM32CubeIDE / GCC
int __io_putchar(int ch) {
HAL_UART_Transmit(&huart1, (uint8_t *)&ch, 1, 0xFFFF);
return ch;
}
// 兼容部分 GCC 版本的 _write 弱函数覆盖
__attribute__((weak)) int _write(int file, char *ptr, int len) {
HAL_UART_Transmit(&huart1, (uint8_t *)ptr, len, 0xFFFF);
return len;
}
#endif
/* ========================================================== */
提示:如果使用的是 标准外设库(Standard Peripheral Library),将发送逻辑替换为寄存器轮询即可:
USART_SendData(USART1, (uint8_t)ch); while (USART_GetFlagStatus(USART1, USART_FLAG_TXE) == RESET);
二、IDE 配置(关键避坑点) #
不同开发环境需要开启对应的编译选项,否则程序可能直接卡死在初始化阶段。
1. Keil MDK 用户(必须配置) #
- 点击工具栏的 魔术棒图标 (Options for Target)。
- 切换到 Target 选项卡。
- 勾选 Use MicroLIB(使用微库)。
- 点击 OK 保存并重新全编译(Rebuild)。

为什么必须勾选 MicroLIB?
Keil 默认的标准 C 库包含半主机模式(Semihosting),如果未勾选 MicroLIB 且未禁用半主机模式,程序调用printf时会等待调试器响应,从而导致单片机脱机运行时直接卡死。
2. STM32CubeIDE / GCC 用户 #
- 无需勾选 MicroLIB。
- 若需要在
printf中打印浮点数(如%f),需在工程属性中开启浮点输出支持:- 右键工程 -> Properties -> C/C++ Build -> Settings -> Tool Settings -> MCU Settings。
- 勾选 Use float with printf from newlib-nano (-u _printf_float)。
三、测试验证 #
在 main() 函数的主循环之前调用测试输出:
int main(void) {
HAL_Init();
SystemClock_Config();
MX_GPIO_Init();
MX_USART1_UART_Init();
/* 串口打印测试 */
printf("\r\n============================\r\n");
printf(" STM32 UART1 Printf Ready! \r\n");
printf(" System Clock: %lu Hz \r\n", SystemCoreClock);
printf("============================\r\n");
while (1) {
// ...
}
}
打开电脑端串口助手,设置与 CubeMX 一致的波特率(通常为 115200 8-N-1),复位单片机即可看到格式化输出。
四、常见问题排查 #
- 串口输出乱码
- 检查串口助手的波特率是否与代码配置一致。
- 检查外部晶振频率(HSE_VALUE)配置是否与硬件板载晶振一致。
- 只输出一次就卡死
- 绝大多数情况是 Keil 未勾选 Use MicroLIB 导致的半主机中断挂起。
- 内容没有及时显示
- C 库标准输出存在行缓冲机制,请确保输出字符串结尾带有
\r\n。
- C 库标准输出存在行缓冲机制,请确保输出字符串结尾带有