printf函数重定向为往串口打印信息
在UART.h、UART1.h和UART.c文件的内容编写好后,在main函数中实现通过串口发送一串字符串和一个不断加1的变量
方法一:
使用sprintf函数,组装字符串,然后通过串口发送数组的函数进行串口发送
/* Includes ------------------------------------------------------------------*/
#include <main.h>
/* Private define-------------------------------------------------------------*/
/* Private variables----------------------------------------------------------*/
uint16_t Cnt = 0; //初始化自动加1的变量
/* Public variables-----------------------------------------------------------*/
/* Private function prototypes------------------------------------------------*/
/*
* @name main
* @brief 主函数
* @param void
* @retval int
*/
int main(void)
{
//系统初始化
Hradware.Sys_Init();
//串口1发送初始化信息
UART1.UART_SendString("Initialization completed,system startup!\r\n");
//系统主循环
while(1)
{
//按键检测
//KEY1.KEY_Detect();
//KEY2.KEY_Detect();
//PWM.PWM_LED_Adjust_Brightness();
//通过串口发送字符串和自动加1的变量
//方法一
/*sprintf函数将Cnt按照格式组装成一个字符串,然后放入到UART1.pucSend_Buffer指向的发送数组中*/
sprintf(UART1.pucSend_Buffer,"hello:%u\r\n",Cnt++);
Public.Delay_ms(300);
UART1.UART_SendArray(UART1.pucSend_Buffer,20);
Run_LED.Run_LED_Flip();
}
}
/********************************************************
End Of File
********************************************************/
注意Cnt变量的类型以及组装字符串时的格式化%方式不对的话,都会造成不同的效果
注意一:
/* Private variables----------------------------------------------------------*/
uint8_t Cnt = 0; //初始化自动加1的变量
如果Cnt的类型是uint8_t的话,下面的格式化不变,还是%u,则串口输出的不是加1,而是加256了
如果Cnt类型非得写成 uint8_t 的话,那么格式化前要强制类型转换才会正常加1,并从0加到255,然后又从0开始
sprintf(UART1.pucSend_Buffer,"hello:%u\r\n",(uint16_t)Cnt++);
注意二:
sprintf(UART1.pucSend_Buffer,"hello:%c\r\n",Cnt++);
如果格式化时,写成了%c,Cnt的类型还是uint16_t,则串口打印不了Cnt变量
如果把Cnt类型改为uint8_t,则Cnt被打印成了一个圈,而且还不连续,明显是错误的
方法二:
使用printf重定向来做,本次实验对putchar函数进行修改,需要在main.h头文件中引入< stdio.h >头文件
UART1.c:
/*
* @name putchar
* @brief 字符发送函数重定向
* @param c:发送的字符
* @retval char
*/
extern char putchar(char ch)
{
UART1.UART_SendData((uint8_t)ch); //在putchar函数内直接调用串口发送字符函数
return ch;
}
main.c:
/* Includes ------------------------------------------------------------------*/
#include <main.h>
/* Private define-------------------------------------------------------------*/
/* Private variables----------------------------------------------------------*/
uint16_t Cnt = 0; //初始化自动加1的变量
/* Public variables-----------------------------------------------------------*/
/* Private function prototypes------------------------------------------------*/
/*
* @name main
* @brief 主函数
* @param void
* @retval int
*/
int main(void)
{
//系统初始化
Hradware.Sys_Init();
//串口1发送初始化信息
UART1.UART_SendString("Initialization completed,system startup!\r\n");
//系统主循环
while(1)
{
//按键检测
//KEY1.KEY_Detect();
//KEY2.KEY_Detect();
//PWM.PWM_LED_Adjust_Brightness();
//通过串口发送字符串和自动加1的变量
//方法一
/*sprintf函数将Cnt按照格式组装成一个字符串,然后放入到UART1.pucSend_Buffer指向的发送数组中*/
// sprintf(UART1.pucSend_Buffer,"hello:%u\r\n",Cnt++);
// Public.Delay_ms(100);
// UART1.UART_SendArray(UART1.pucSend_Buffer,20);
// Run_LED.Run_LED_Flip();
//方法二
//printf函数重定向
printf("hello:%u\r\n",Cnt++);
Public.Delay_ms(100);
Run_LED.Run_LED_Flip();
}
}
重定向后,也可以使用printf函数打印浮点数
float f = 3.14;
。。。
printf("hello:%u%f\r\n",Cnt++,f);
注意:
网上文章许多都是说改写fputc函数,但我在这个程序中试了是不行的,会编译出错,定位到函数的FILE类型上,因为C51是没有FILE类型的,所以这里是使用不了fputc函数的
int fputc(int ch,FILE*stream) //编译时在FILE处报错
{
UART1.UART_SendData((uint8_t)ch);
return ch;
}
那为什么网上还有那么多说改fputc函数呢?因为那是在编写STM32单片机程序的时候,可以在keil软件的设置里,勾选Use MicroLIB选项,就可以使用fputc函数来实现重定向了
打开STM32工程文件时keil设置界面:
为什么使用microlib库
microlib 是缺省 C 库的备选库,它指在需要装入到极少量内存中的深层嵌入式应用程序配合使用,这些应用程序不在操作系统中运行。
microlib 进行了高度优化以使代码变得很小,它的功能比缺省 C 库少,并且根本不具备某些 ISO C 特性。某些库函数的运行速度也比较慢,例如,memcpy()。
microlib 与缺省 C 库之间的主要差异:
(1)、microlib 不符合 ISO C 库标准。不支持某些 ISO 特性,并且其他特性具有的功能也较少。
(2)、microlib 不符合 IEEE 754 二进制浮点算法标准。
(3)、microlib 进行了高度优化以使代码变得很小。
(4)、无法对区域设置进行配置。缺省 C 区域设置是唯一可用的区域设置。
(5)、不能将 main() 声明为使用参数,并且不能返回内容。
(6)、不支持 stdio,但未缓冲的 stdin、stdout 和 stderr 除外。
(7)、microlib 对 C99 函数提供有限的支持。
(8)、microlib 不支持操作系统函数。
(9)、microlib 不支持与位置无关的代码。
(10)、microlib 不提供互斥锁来防止非线程安全的代码。
(11)、microlib 不支持宽字符或多字节字符串。
(12)、与 stdlib 不同,microlib 不支持可选择的单或双区内存模型。microlib 只提供双区内存模型,即单独的堆栈和堆区。
(13)、可以合理地将 microlib 与 --fpmode=std 或 --fpmode=fast 配合使用。
勾选微库之后,重写fputc函数和fgetc函数就可以支持printf函数和scanf函数。
但打开编写51单片机的程序时,keil软件设置里是没有这个选项的,所以51的程序没法使用fputc函数
打开51工程文件时keil设置界面:
而从keil的帮助文档里可以知道, printf 也是基于putchar实现的,所以本次重新实现putchar,就可以实现printf的重定向了
至于重定向的方法网上有很多,但为什么可以重定向,就比较少文章介绍,这里看到有一篇文章写的不错:https://zhuanlan.zhihu.com/p/133460085
文章中介绍的weak的属性,相信以后在STM32中会经常遇到
fputc
语法:
#include <stdio.h> int fputc( int ch, FILE *stream );
函数fputc()把给出的字符ch写到给出的输出流. 返回值是字符, 发生错误时返回值是EOF.
putchar
语法:
#include <stdio.h> int putchar( int ch );
putchar()函数把 ch 写到STDOUT(标准输出). 代码
putchar( ch );
和
putc( ch, STDOUT );
一样.
putchar()的返回值是被写的字符, 发生错误时返回EOF