乐于分享
好东西不私藏

嵌入式软件面试必备|STM32单片机实现 printf 函数,搞定后会很爽 附代码

嵌入式软件面试必备|STM32单片机实现 printf 函数,搞定后会很爽 附代码

二十年前刚开始做80C51单片机的时候,我真的被串口调试搞懵了,现在想起来还觉得有点好笑。那时候只会用单片机的串口发送单个字符,每次调试程序,想看看变量的值、程序有没有走到指定步骤,都得一个个发送字符,拼来拼去才能知道结果,麻烦得要死,感觉特别不爽。反观上位机,动不动就可以调用printf()函数,想打印什么就打印什么,不管是变量、字符串,还是调试信息,一行代码就能直接输出到屏幕上,清晰又高效,那时候就特别羡慕,心里一直琢磨着,要是单片机也能用上printf(),调试起来肯定能省一半力气。后来接触STM32单片机,终于研究明白怎么实现printf函数,搞定的那一刻,真的爽翻了——调试再也不用拼字符,程序哪里出问题、变量值对不对,一行printf全搞定,效率直接翻倍,而且这也是嵌入式软件面试的高频考点,搞定它,面试时也能更有底气。

对于嵌入式软件开发者来说,printf函数看似简单,却是STM32调试中最实用、最高效的工具,也是面试中区分新手和入门开发者的关键细节。很多应届生面试时,被问到如何在STM32中实现printf函数,要么答不上来,要么只知道皮毛,说不出核心原理和实操步骤,直接错失offer

今天就用大白话+实战干货+面试考点,把STM32单片机实现printf函数的方法彻底讲透,从核心原理、两种实现方式(库函数+寄存器)、实操步骤,到面试高频问答、常见误区,全程无废话,新手能上手,老手能查漏补缺,收藏起来,面试+调试都能用,搞定后你就知道有多爽!

先明确:为什么STM32不能直接用printf函数?

很多新手刚接触STM32时,会下意识地直接调用printf函数,结果发现程序报错、无法编译,甚至死机。其实原因很简单:printf函数是C语言标准库函数,默认是面向PC端的,它的输出是通过PC端的控制台(如串口助手、CMD)实现的,而STM32本身没有控制台,也没有默认的输出接口。

通俗来说,printf函数就像一个信使,它本身不会输出内容,需要一个传递通道才能把信息送出去——PC端的通道是控制台,而STM32的通道,就是我们常用的串口(USART)。所以,STM32实现printf函数的核心,就是重定向printf的输出通道,让它不再往PC控制台输出,而是通过STM32的串口,把信息发送到上位机(串口助手),这样就能实现一行代码打印调试信息的效果。

重点提醒:这也是面试高频考点——面试官常会问“STM32为什么不能直接使用printf?如何解决?,记住核心答案:因为printf默认输出到PC控制台,STM32无默认输出通道,需重定向到串口即可。

二、STM32实现printf函数的两种核心方式(实操必备,面试重点)

STM32实现printf函数,主要有两种方式,一种是基于HAL库(新手首选,简单易上手),一种是基于寄存器(底层逻辑清晰,面试常考),两种方式都拆解清楚,按需选择,新手先掌握HAL库方式,再了解寄存器方式,应对面试足够。

方式1HAL库实现(新手首选,最快上手)

HAL库已经封装好了串口相关的函数,我们只需重定向printf的输出函数(fputc),将输出内容通过HAL库的串口发送函数,发送到上位机,无需复杂的底层配置,几步就能搞定。

① 核心原理:printf函数的底层,本质是调用了C标准库的fputc函数(单个字符输出函数),我们只需重写fputc函数,让它不再输出到控制台,而是通过STM32的串口发送单个字符,这样printf函数调用时,就会自动通过我们重写的fputc函数,将字符串逐字符发送到上位机。

② 实操步骤(以STM32F103+HAL库为例,直接套用):

1.配置串口:在STM32CubeMX中配置USART1(或其他串口),波特率设为115200(常用),数据位8位,停止位1位,无校验位,开启串口发送功能,生成工程代码;

2.重写fputc函数:在main.c文件中,添加重写函数(注意:需包含stdio.h头文件);

3.调用printf函数:在需要调试的地方,直接调用printf函数,打印调试信息(如printf("变量a的值:%d\r\n", a););

4.测试:将STM32PC通过串口连接,打开串口助手(波特率115200),下载程序,即可看到printf打印的信息。

③ 关键代码片段(面试可直接说):

c#include   "stdio.h"   // 重写fputc函数,重定向到串口1   int fputc(int ch, FILE *f)   {// HAL_UART_TransmitHAL库串口发送函数,USART1为串口句柄,&ch为发送数据,1为发送长度,100为超时时间HAL_UART_Transmit(&huart1,   (uint8_t*)&ch, 1, 100);return ch;   }   // 主函数中调用printf   int main(void)   {HAL_Init();SystemClock_Config();MX_GPIO_Init();MX_USART1_UART_Init();int a = 100;while (1){printf("STM32 printf测试!变量a的值:%d\r\n", a);HAL_Delay(1000); // 每隔1秒打印一次}   }

④ 实操注意点:必须包含stdio.h头文件,否则printf函数会报错;串口配置必须正确(波特率、数据位等与串口助手一致);若打印乱码,检查串口配置或晶振配置。

方式2:寄存器实现(底层逻辑,面试高频)

寄存器方式无需依赖HAL库,直接操作串口相关寄存器,更能理解底层逻辑,也是面试官重点考查的内容——很多面试官会要求不用HAL库,如何实现STM32printf函数,掌握这种方式,面试加分明显。

① 核心原理:与HAL库方式一致,都是重写fputc函数,区别在于:串口发送单个字符时,不调用HAL库函数,而是直接操作USART_DR寄存器(串口数据寄存器),将字符写入该寄存器,实现串口发送。

② 实操步骤(以STM32F103为例):

1.初始化串口:配置GPIOTX引脚设为复用推挽输出,RX引脚设为浮空输入),配置USART_CR1寄存器(开启串口、开启发送功能),配置波特率(通过USART_BRR寄存器配置);

2.编写串口发送单个字符函数:通过判断USART_SR寄存器的TXE位(发送数据寄存器空),判断是否可以发送,若空,则将字符写入USART_DR寄存器;

3.重写fputc函数:调用自己编写的串口发送函数,实现单个字符发送;

4.调用printf函数,测试打印效果。

③ 关键代码片段(面试重点):

c#include   "stdio.h"   #include "stm32f10x.h"   // 串口初始化(USART1,波特率115200   void USART1_Init(void)   {// 1. 开启GPIOUSART1时钟RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA | RCC_APB2Periph_USART1,   ENABLE);// 2. 配置GPIOPA9TX,复用推挽输出)GPIO_InitTypeDef GPIO_InitStruct;GPIO_InitStruct.GPIO_Pin =   GPIO_Pin_9;GPIO_InitStruct.GPIO_Mode =   GPIO_Mode_AF_PP; // 复用推挽输出GPIO_InitStruct.GPIO_Speed =   GPIO_Speed_50MHz;GPIO_Init(GPIOA,   &GPIO_InitStruct);// 3. 配置USART1USART_InitTypeDef USART_InitStruct;USART_InitStruct.USART_BaudRate =   115200; // 波特率115200USART_InitStruct.USART_WordLength =   USART_WordLength_8b; // 8位数据位USART_InitStruct.USART_StopBits =   USART_StopBits_1; // 1位停止位USART_InitStruct.USART_Parity =   USART_Parity_No; // 无校验位USART_InitStruct.USART_HardwareFlowControl =   USART_HardwareFlowControl_None; // 无硬件流控USART_InitStruct.USART_Mode =   USART_Mode_Tx; // 仅开启发送模式USART_Init(USART1,   &USART_InitStruct);// 4. 开启USART1USART_Cmd(USART1, ENABLE);   }   // 串口发送单个字符   void USART1_SendChar(uint8_t ch)   {// 判断发送数据寄存器是否为空(TXE位为1表示空)while(USART_GetFlagStatus(USART1,   USART_FLAG_TXE) == RESET);// 将字符写入数据寄存器,发送数据USART_SendData(USART1, ch);   }   // 重写fputc函数,重定向到串口1   int fputc(int ch, FILE *f)   {USART1_SendChar((uint8_t)ch);return ch;   }   // 主函数测试   int main(void)   {USART1_Init();int b = 200;while(1){printf("寄存器方式实现printf!变量b的值:%d\r\n",   b);// 延时函数(简单延时,实际项目可使用定时器)for(int i=0; i<500000; i++);}   }

④ 面试考点:寄存器方式的核心是操作哪些寄存器?答题要点:主要操作3类寄存器——GPIO寄存器(配置TX/RX引脚)、USART_CR1寄存器(开启串口、发送功能)、USART_DR寄存器(发送数据)、USART_BRR寄存器(配置波特率)、USART_SR寄存器(判断发送状态)。

三、两种实现方式对比(面试不丢分)

实现方式

核心特点

优势

劣势

适用场景

HAL库方式

重写fputc,调用HAL串口发送函数

简单易上手,无需懂底层寄存器,效率高

依赖HAL库,底层逻辑不清晰

新手开发、快速调试项目

寄存器方式

重写fputc,直接操作串口寄存器

底层逻辑清晰,不依赖库,面试加分

需熟悉串口寄存器,配置稍复杂

面试考查、底层开发、无库环境

四、面试高频问答(覆盖90%考点)

STM32实现printf函数,是嵌入式软件面试(尤其是应届生、初级工程师)的高频考点,重点考查重定向原理、实现方式、问题排查,记住以下答题要点,轻松应对:

1.核心问题1STM32为什么不能直接使用printf函数?如何解决?答题要点:原因:printfC标准库函数,默认输出到PC控制台,STM32无默认输出通道;解决方法:重定向printf的输出通道,将其重定向到STM32的串口,通过串口发送到上位机。

2.核心问题2STM32实现printf函数的核心原理是什么?答题要点:核心是重写C标准库的fputc函数(printf底层调用fputc发送单个字符);重写后,fputc函数不再输出到控制台,而是通过STM32串口发送单个字符,printf调用时,会逐字符发送字符串到上位机。

3.核心问题3:请说说两种实现printf函数的方式,各自的优缺点?答题要点:① HAL库方式:优点是简单易上手,无需懂寄存器,效率高;缺点是依赖HAL库,底层逻辑不清晰;② 寄存器方式:优点是底层逻辑清晰,不依赖库,面试加分;缺点是需熟悉串口寄存器,配置稍复杂。

4.核心问题4:使用printf函数时,出现打印乱码,可能的原因有哪些?如何排查?答题要点:可能原因:① 串口配置不一致(波特率、数据位、停止位等与串口助手不匹配);② 未包含stdio.h头文件;③ 晶振配置错误,导致串口波特率偏差;④ 重写的fputc函数有错误。排查方法:先核对串口配置,再检查头文件和fputc函数,最后检查晶振配置。

5.核心问题5:不用HAL库,如何实现STM32printf函数?(重点)答题要点:① 初始化串口:配置GPIO引脚(TX为复用推挽)、开启串口时钟、配置波特率和串口模式;② 编写串口发送单个字符函数(判断USART_SR寄存器TXE位,写入USART_DR寄存器);③ 重写fputc函数,调用编写的串口发送函数;④ 调用printf函数测试。


最后总结

STM32实现printf函数,看似是一个简单的调试技巧,实则是嵌入式软件开发的基础,也是面试中的高频考点。搞定它,不仅能让调试效率翻倍——不用再一个个发送字符拼调试信息,一行代码就能打印变量、程序状态,省时又省心,那种一键打印的爽感,只有试过才知道;更能在面试中体现你的专业功底,拉开与其他应届生的差距。

新手建议先掌握HAL库方式,快速上手,满足日常调试需求;再深入学习寄存器方式,理解底层逻辑,应对面试考查。其实两种方式的核心都是重定向fputc函数,记住这个核心,不管面试官怎么问、怎么考,都能对答如流。


更多往期精彩内容》》》

嵌入式面试必备|MCU正常运行三要素,应届生面试答不上来基本就被pass

嵌入式面试必备 | STM32 GPIO 的八种模式详解,了如指掌才能驾轻就熟

知识分享|STM32 低功耗模式——专为省电“续命”而设计

硬件面试必备 | 为什么很多高手都喜欢在信号线上串接一颗33欧姆的电阻

收藏这篇干货,面试前快速过一遍核心原理和代码片段,调试时直接套用步骤,避开常见误区,轻松搞定STM32printf函数,面试+调试双丰收!

互动话题:你第一次在STM32中实现printf函数时,遇到过什么问题?比如打印乱码、无法打印,欢迎在评论区分享你的排查过程,一起交流学习~