↓跳到主要内容

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 用户(必须配置) #

  1. 点击工具栏的 魔术棒图标 (Options for Target)。
  2. 切换到 Target 选项卡。
  3. 勾选 Use MicroLIB(使用微库)。
  4. 点击 OK 保存并重新全编译(Rebuild)。

alt text

为什么必须勾选 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),复位单片机即可看到格式化输出。


四、常见问题排查 #

  1. 串口输出乱码
    • 检查串口助手的波特率是否与代码配置一致。
    • 检查外部晶振频率(HSE_VALUE)配置是否与硬件板载晶振一致。
  2. 只输出一次就卡死
    • 绝大多数情况是 Keil 未勾选 Use MicroLIB 导致的半主机中断挂起。
  3. 内容没有及时显示
    • C 库标准输出存在行缓冲机制,请确保输出字符串结尾带有 \r\n。