C语言中,printf函数默认的输出位置是stdout,即调试终端。但是对于单片机来说,没有stdout,所以一般将其重定向至串口来作输出。
然而很多使用过printf的STM32开发伙伴仅仅会使用而已,并不会自己实现这个函数。当没有这个函数的时候,就只能使用常见的sprintf函数和USART_SendData函数来实现数据的发送。显然这种方法比较笨,当printf函数可用的时候,大家更愿意使用printf。下面就帮助大家分析这个函数的实现方法。
printf函数属于系统调用,在不同编译环境下的实现方法略有不同,但其本质上会调用一个向stdout输出字符的系统调用函数。常用的STM32开发环境有两大类,keil MDK和GCC。在keil MDK环境下,这个系统函数是fputc,在GCC环境下这个系统函数是write。因此重写fputc或者write就可以实现STM32上的printf函数。
在MDK环境下重定义的方法如下。正点原子或野火的例程里就有。
#pragma import(__use_no_semihosting)
//标准库需要的支持函数
struct __FILE
{
int handle;
};
FILE __stdout;
//定义_sys_exit()以避免使用半主机模式
_sys_exit(int x)
{
x = x;
}
//重定义fputc函数
int fputc(int ch, FILE *f)
{ // 将这里的 USART1修改为USART2或USART3,即可实现printf函数输出到指定串口
while((USART1->SR&0X40)==0);//循环发送,直到发送完毕
USART1->DR = (uint8_t) ch;
return ch;
}
而由于缺少GCC环境下的例程,很多人照搬MDK的代码,发现printf函数根本无法使用,下面帮助大家重点分析GCC的代码。
#include <stdio.h>
// 减少编译器的警告
#pragma GCC diagnostic ignored "-Wunknown-pragmas"
//确保程序中不包含任何半主机的函数
#pragma import(__use_no_semihosting)
int _write (int fd, char *pBuffer, int size)
{
int i = 0;
for (i = 0; i < size; i++)
{ // 将这里的 USART1修改为USART2或USART3,即可实现printf函数输出到指定串口
while((USART1->SR&0X40)==0);//循环发送,直到发送完毕
USART1->DR = (uint8_t) *pBuffer++;
}
return size;
}
从上面的代码可以看出,GCC环境下printf函数的重定向方法更简单,只需要重写write函数就行了。
大家可能觉得掌握MDK的方法就可以了,但是随着开发的深入,我们才会发现GCC环境才是主流,ST官方的STM32CubeIDE,RT-Thread Studio等工具使用的都是GCC环境。我们有必要知道这一点,避免照搬keil MDK的代码。
另外,printf函数有个特点,在某些环境下遇见换行符才会更新输出。因此在STM32上使用printf函数的时候,必须在数据的结尾加上换行符 '\n'。有些地方喜欢 '\r\n',这是由于windows平台的换行就是'\r\n'。在GCC环境下,一个\n就可以了。
如果大家想要练手,可以使用这个嵌入式仿真平台,它很像Proteus,但是比Proteus人性化很多,还提供了许多学习例程,编译的hex文件还可以下载出来,非常方便。
平台的地址是 https://app.puliedu.com/#/,现在平台正在推广,可以免费注册体验