STM32使用printf函数的步骤和注意事项

第一种方法: 使用微库:

1,在KEIL中勾选 Use MicroLIB . 即使用微库.
在这里插入图片描述
2, 在代码中添加 如下代码 (目的是为了调用stdio库中的print 函数)

 #include "stdio.h"  //支持print 一般添加到main.h中

3,在uart.c 填加如下代码 (目的是为了重定向 stdio库中的print 函数 )

//重定向print
int fputc(int ch, FILE *f)//printf
{
	HAL_GPIO_WritePin(GPIOA,GPIO_PIN_0,GPIO_PIN_SET); //485发送使能端口 没有可去掉
	HAL_UART_Transmit(&huart1, (uint8_t *)&ch, 1,0xffff);  //发送一个字节的数据到你希望的串口
	HAL_GPIO_WritePin(GPIOA,GPIO_PIN_0,GPIO_PIN_RESET); //485发送使能端口 没有可去掉
	return (ch);
}

第二种方法: 不使用微库,使用标准C库,声明不使用半主机模式

1, 在代码中添加 如下代码 (目的是为了调用stdio库中的print 函数)

 #include "stdio.h"  //支持print 一般添加到main.h中

2, 关闭半主机模式,并重定向printf

#pragma import(__use_no_semihosting)//不使用半主机模式

//避免使用半主机模式
void _sys_exit(int x)
{
	x = x;
}
//标准库需要支持的函数
struct __FILE
{
	int handle;
};
FILE __stdout;

//重定向print
int fputc(int ch, FILE *f)//printf
{
	HAL_GPIO_WritePin(GPIOA,GPIO_PIN_0,GPIO_PIN_SET); //485发送使能端口 没有可去掉
	HAL_UART_Transmit(&huart1, (uint8_t *)&ch, 1,0xffff);  //发送一个字节的数据到你希望的串口
	HAL_GPIO_WritePin(GPIOA,GPIO_PIN_0,GPIO_PIN_RESET); //485发送使能端口 没有可去掉
	return (ch);
}

解释:

想要明白为什么会有使用微库和不使用微库这两种方案,需要先搞明白一个概念 半主机模式

半主机模式概念:
半主机是用于 ARM 目标的一种机制,可将来自应用程序代码的输入/输出请求传送至运行调试器的主机。 例如,使用此机制可以启用 C 库中的函数,如 printf() 和 scanf(),来使用主机的屏幕和键盘,而不是在目标系统上配备屏幕和键盘。
这种机制很有用,因为开发时使用的硬件通常没有最终系统的所有输入和输出设备。 半主机可让主机来提供这些设备。
简单来说: 半主机模式就是 比如我们的开发板没有 键盘和屏幕 ,但是,使用半主机模式后,我们就可以利用仿真器或其他连接到电脑(主机),使用电脑(即主机)的屏幕和键盘通过printf() 和 scanf() 来与开发板交互。
这种方式常用于调试。

单片机想要使用printf() 和 scanf() 函数遇到的问题

单片机使用 printf() 和 scanf() 函数时 ,只是希望通过自身硬件带有的串口,打印或接收数据。所以此时的单片机并不是工作在半主机模式的。
而我们通常使用的C库 中 printf() 和 scanf() 函数 是需要工作在半主机模式,通过主机的屏幕和键盘才可以使用的。

我们是如何解决的
可以看到 想要解决单片机使用 C库 中 printf() 和 scanf() 函数,首先要解决的一个问题就是要让单片机在使用printf() 和 scanf() 函数 不要工作在半主机模式。

方案1在KEIL中勾选 Use MicroLIB . 即使用微库. (因为微库是一个压缩库,而微库中的printf() 和 scanf() 函数 就不是工作在半主机模式下的)想要了解微库和标准C库区别的朋友可自行百度,这里不在叙述。

方案2继续使用标准的C库,在代码中声明不使用半主机模式.

解决完半主机模式的问题之后,我们还要对printf() 和 scanf() 函数进行重写 (重定向),因为不管是标准的C库还是微库中printf() 和 scanf() 函数,都是不能直接通过串口进行输入输出的。(况且开发库的人也不知道你的单片机有没有串口),所以需要我们进行重写底层库函数,当我们使用 printf() 和 scanf() 函数时 直接调用我们单片机中的串口发送或接收函数即可。

说明 : 因为单片机一般都是为了打印输出调试数据,很少使用接收数据,所以这里并没有 在代码中给出 scanf() 函数重写的方法, 但原理都是一样的。

最后思考:

其实我们使用printf() 函数目的就是为了通过串口数据打印输出我们想要的调试信息,而这些调试信息大多是汉字或各种字符串, 而通常我们的串口函数 都是只支持 int 型数据。 如果我们的串口函数也能支持 汉字,即支持字符串的编解码, 那同样我们也可以直接通过 串口函数进行 调试信息的收发了。 也就没有必要去使用printf() 函数了。

但是,轮子已经有了,我们只要稍加改造即可使用。 就没有必要再去造轮子了