综合多篇文章对实现printf进行总结,本篇博客包含内容如下:
- 使用MicroLIB实现printf(Windows环境下和Linux环境下)
- 使用C标准库实现printf(也就是不需要配置工程打开MicroLIB)
- 结合DMA实现printf(针对大量数据、OS任务间使用printf冲突)
- 多串口通信(当有多个串口通信设备时)
- printf使用常见问题
如果本篇博客对你有帮助的话,记得点个赞哦!谢谢大家!😀😀😀😀😀😀
文章目录
一、需打开MicroLIB版
打开MicroLIB,后面的步骤基于此(重点!!!!!!!)
鉴于很多作者忘记在文章中提醒读者打开MicroLIB,所以我在最开始就把这个步骤列出来。
如果不打开MicroLIB,可能会在stm32进行调试时,进入 LDR R0, =SystemInit卡死。参考文章
1.1 方案一
1.1.1 修改usart.c
Ⅰ添加头文件stdio.h
在usart.c头文件定义段加入stdio.h
#include <stdio.h>
Ⅱ 定义结构体FILE
这步不要也行,我把它注释掉后,照样能够正常输出汉字、整数、浮点数、字符、字符串。
现在自己知识不够,解释不了这个结构体的意义,后面知道了回来把这个坑补上。
struct FILE {
int handle;
};
Ⅲ 重写fputc() & fgetc函数
int fputc(int ch, FILE * f){
HAL_UART_Transmit(&huart1, (uint8_t *)&ch, 1, 0xffff);///<普通串口发送数据
while(__HAL_UART_GET_FLAG(&huart1, UART_FLAG_TC) == RESET){}///<等待发送完成
return ch;
}
int fgetc(FILE * F) {
uint8_t ch = 0;
HAL_UART_Receive(&huart1,&ch, 1, 0xffff);///<普通串口接收数据
while(__HAL_UART_GET_FLAG(&huart1, UART_FLAG_TC) == RESET){}///<等待发送完成
return ch;
}
Ⅳ 修改后usart.c
切记将这些内容写在沙盒段(USER BEGIN 和 USER END)之间
1.1.2 使用演示
在需要printf的c文件#include <stdio.h>,然后就可以使用printf了。
测试输出整数、浮点数、字符、字符串
1.2 方案二(Linux环境下有用)
1.2.1 添加头文件并修改usart.c
#include <stdio.h> /* 包含头文件!!!!! */
#ifdef __GNUC__
#define PUTCHAR_PROTOTYPE int __io_putchar(int ch)
#else
#define PUTCHAR_PROTOTYPE int fputc(int ch, FILE *f)
#endif
PUTCHAR_PROTOTYPE
{
HAL_UART_Transmit(&huart1,(uint8_t *)&ch,1,0xFFFF);//阻塞方式打印
return ch;
}
有的同志可能发现了方案二其实和方案一一样,确实,如果你在Windows上使用keil5方案一和方案二没什么区别,因为keil5默认使用ARM Compiler 5编译器,最终都是重定向了fputs函数。但如果你在Linux上使用GNUC编译器进行编译,那么这两种方案就有区别了。
1.2.2 使用演示
经过验证,可用
二、标准库(不需要打开MicroLIB)
2.1 修改usart.c文件
#include <stdio.h>
/* 告知连接器不从C库链接使用半主机的函数 */
#pragma import(__use_no_semihosting)
/* 定义 _sys_exit() 以避免使用半主机模式 */
void _sys_exit(int x)
{
x = x;
}
/* 标准库需要的支持类型 */
struct __FILE
{
int handle;
};
FILE __stdout;
/* */
int fputc(int ch, FILE *stream)
{
/* 不同芯片的串口标志位不一定相同! */
while((USART1->SR & 0X40) == 0);
/* 串口发送完成,将该字符发送 */
USART1->DR = (uint8_t) ch;
return ch;
}
2.2 使用演示
与前面使用方法相同,不再赘述。
MicroLIB库和C标准库有什么区别?
三、大量数据传输(DMA)
3.1 什么时候需要
- 需要传输大量数据时,为了减缓CPU的压力
- FreeRTOS多任务之间使用printf冲突
3.2 Cube配置DMA
3.3 代码移植
3.3.1 修改usart.c
添加以下代码
#include <stdarg.h>
void printf_DMA(UART_HandleTypeDef *husart, char *fmt,...)
{
uint8_t n=0;
for(int i=0;i<sizeof(Sendbuf);i++) //清空发送缓存
{
Sendbuf[i]=0;
}
va_list arg;
va_start(arg,fmt);//将...中的输入与fmt初始化到arg
vsprintf((char*)Sendbuf,fmt,arg);//将输出存到发送缓存中
va_end(arg);
for(int i=0;i<sizeof(Sendbuf);i++) //计算发送缓存中的非零字符数
{
if(Sendbuf[i]!=0) n++;
}
HAL_UART_Transmit_DMA(husart,(uint8_t*)&Sendbuf,n); //通过DMA发送字符串
}
3.3.2 修改usart.h
void printf_DMA(UART_HandleTypeDef *husart, char *fmt,...);
四、多串口通信(满足与ESP8266、ESP32等进行通信)
4.1写法一
4.1.1 修改usart.c
添加以下代码到usart.c中,注意将代码放到
USER段
#include <stdarg.h>
#include <string.h>
#include <stdio.h>
void usartPrintf(UART_HandleTypeDef USARTx, char *fmt,...)
{
unsigned char UsartPrintfBuf[296];
va_list ap;
unsigned char *pStr = UsartPrintfBuf;
va_start(ap, fmt);
vsnprintf((char *)UsartPrintfBuf, sizeof(UsartPrintfBuf), fmt, ap); //格式化
va_end(ap);
while(*pStr != NULL)
{
HAL_UART_Transmit (&USARTx ,(uint8_t *)pStr++,1,HAL_MAX_DELAY );
}
}
4.1.2修改usart.h
添加以下代码到usart.h中
void usartPrintf(UART_HandleTypeDef USARTx, char *fmt,...);
4.1.3 使用演示
与前面不同的是,如果这里要使用usartPrintf()包含的头文件是usart.h,不是stdio.h,usartPrintf的用法和printf一样。
4.2 写法二
4.2.1 修改usart.c
#include <stdarg.h>
uint8_t XL_Printf(UART_HandleTypeDef *huart,const char *format, ...)
{
char buf[512]; //定义临时数组,根据实际发送大小微调
va_list args;
va_start(args, format);
uint16_t len = vsnprintf((char *)buf, sizeof(buf), (char *)format, args);
va_end(args);
return HAL_UART_Transmit(huart,buf,len,1000); //串口打印函数,可以更换为中断发送或者DMA发送
}
4.2.2 修改usart.h
在usart.h中添加以下代码
uint8_t XL_Printf(UART_HandleTypeDef *huart,const char *format, ...);
4.2.3 使用演示
包含头文件#include <usart.h>
五、关于printf的问题总结
5.1 不加\r\n串口助手不显示(两种情况)
5.1.1 串口不发送
这个我没遇到过,是在浏览相关文章的时候看到的,有点没看明白,他说的解决方法我没看到,感觉没啥变化。
5.1.2 串口助手程序的问题
有的串口助手在接收字符串时必须以\r\n结尾才会显示,否者不会在屏幕上显示。例如我的就是在vofa+上无法显示,在串口调试助手上就能正常显示。
5.2 debug时卡死在LDR R0, =SystemInit
这个是因为没有打开微库MicroLIB的原因造成的。
5.3 FreeRTOS堆栈不足,导致printf函数无法使用
这个是我在使用FreeRTOS时遇到的问题,如果任务中出现加上printf就卡死,那么就很有可能时任务堆栈不足。
5.4 不修改文件实现printf的效果
利用sprintf完成字符格式化,在通过HAL_UART_Transmit发送。