1. 为什么你的printf()在Keil MDK里“哑巴”了?
刚接触STM32这类单片机开发的朋友,估计都踩过这个坑:在电脑上写C语言,printf("Hello World") 能轻松在控制台打印出来,怎么一到Keil MDK里,对着单片机代码如法炮制,串口助手却一片死寂,啥也看不到?你反复检查串口初始化、波特率设置,甚至怀疑硬件是不是坏了,结果折腾半天,问题可能出在一个最基础的概念上——标准库的输入输出(stdio)在嵌入式环境里默认是“断线”的。
简单来说,在PC上,操作系统已经为你准备好了“控制台”这个默认的输出设备,printf 函数知道该把字符送到哪里去。但在单片机这片“荒原”上,没有现成的操作系统,printf 函数根本不知道你要把字符通过哪个串口、用哪种方式发送出去。它需要一个“向导”,这个向导就是 fputc 函数。printf 函数内部在需要输出一个字符时,最终会调用 fputc。我们的任务,就是自己动手实现这个 fputc,告诉它:“嘿,请把这个字符通过我的USART1发送出去。” 这个过程,就叫做 printf函数的重定向。
听起来有点抽象?我给你打个比方。printf 就像一位才华横溢的作家,他擅长创作(格式化字符串),但他需要一个邮差(fputc)把手稿(字符)送到出版社(串口)才能印刷成书(在电脑上显示)。在PC环境里,邮差是现成的。在单片机世界,邮差得你自己来当。Keil MDK为了适应资源紧张的嵌入式环境,提供了一个轻量级的C库——MicroLIB。启用它,是让我们的重定向工作得以顺利进行的第一步,因为它比标准C库更精简,对重定向的支持也更直接。接下来,我就手把手带你,从最简单的单串口开始,一步步让 printf 在你的板子上“开口说话”。
2. 基础准备:启用MicroLIB与理解重定向核心
在开始写代码之前,有两项准备工作必须到位,这能避免很多后续的奇怪问题。
### 2.1 工程配置:勾选Use MicroLIB
这是最关键的一步,很多新手都会忽略。打开你的Keil MDK工程,找到那个像魔法棒一样的按钮,点击它打开“Options for Target”对话框。
- 在弹出的窗口中,切换到 Target 标签页。
- 在代码生成(Code Generation)区域,找到 Use MicroLIB 这个复选框,务必勾选上。
MicroLIB是ARM公司提供的、专为嵌入式深度优化过的C库,它体积小、效率高,并且明确支持对 stdio 函数(如 printf, scanf)进行重定向。如果不勾选,Keil会使用标准C库,其重定向机制可能更复杂,甚至在某些配置下无法正常工作。勾选MicroLIB后,我们重写 fputc 函数的行为才会被正确识别和链接。
### 2.2 重定向的核心:fputc函数
我们来深入看看 fputc 这个函数。它的标准原型是:int fputc(int ch, FILE *f)。它接收两个参数:一个是要输出的字符 ch(以int类型传递),另一个是文件流指针 f(在我们重定向到串口的场景下,这个参数通常用不到,但必须声明)。它的返回值是成功输出的字符,如果出错则返回 EOF。
我们的实现逻辑极其简单:
- 将接收到的字符
ch,通过你已初始化好的串口发送函数(比如HAL_UART_Transmit或你自己写的USART_SendData)发送出去。 - 完成后,将
ch作为返回值返回。
为什么这样就行了?因为 printf 在向“标准输出”打印时,底层会循环调用 fputc,将格式化好的字符串逐个字符传递过来。我们劫持了这个调用过程,把字符引向了串口。这里有一个非常重要的细节:这个函数必须在你工程中的某个源文件里实现,并且要包含 stdio.h 头文件,以确保函数原型一致。 通常,我们会把它放在串口相关的源文件里,因为逻辑上它属于串口输出功能的一部分。
3. 实战场景一:单串口输出(最常用)
这是最常见、最基础的需求:我把所有 printf 的输出,都固定通过一个串口(比如USART1)发送出去,方便调试。


被折叠的 条评论
为什么被折叠?



