STM32 HAL库:使用C标准库或MicroLIB实现printf函数输出

综合多篇文章对实现printf进行总结,本篇博客包含内容如下:

  • 使用MicroLIB实现printf(Windows环境下和Linux环境下)
  • 使用C标准库实现printf(也就是不需要配置工程打开MicroLIB)
  • 结合DMA实现printf(针对大量数据、OS任务间使用printf冲突)
  • 多串口通信(当有多个串口通信设备时)
  • printf使用常见问题
  • 如果本篇博客对你有帮助的话,记得点个赞哦!谢谢大家!😀😀😀😀😀😀

    文章目录

  • 一、需打开MicroLIB版
  • 打开MicroLIB,后面的步骤基于此(重点!!!!!!!)
  • 1.1 方案一
  • 1.1.1 修改usart.c
  • Ⅰ添加头文件stdio.h
  • Ⅱ 定义结构体FILE
  • Ⅲ 重写fputc() & fgetc函数
  • Ⅳ 修改后usart.c
  • 1.1.2 使用演示
  • 1.2 方案二(Linux环境下有用)
  • 1.2.1 添加头文件并修改usart.c
  • 1.2.2 使用演示
  • 二、标准库(不需要打开MicroLIB)
  • 2.1 修改usart.c文件
  • 2.2 使用演示
  • MicroLIB库和C标准库有什么区别?
  • 三、大量数据传输(DMA)
  • 3.1 什么时候需要
  • 3.2 Cube配置DMA
  • 3.3 代码移植
  • 3.3.1 修改usart.c
  • 3.3.2 修改usart.h
  • 四、多串口通信(满足与ESP8266、ESP32等进行通信)
  • 4.1写法一
  • 4.1.1 修改usart.c
  • 4.1.2修改usart.h
  • 4.1.3 使用演示
  • 4.2 写法二
  • 4.2.1 修改usart.c
  • 4.2.2 修改usart.h
  • 4.2.3 使用演示
  • 五、关于printf的问题总结
  • 5.1 不加\r\n串口助手不显示(两种情况)
  • 5.1.1 串口不发送
  • 5.1.2 串口助手程序的问题
  • 5.2 debug时卡死在LDR R0, =SystemInit
  • 5.3 FreeRTOS堆栈不足,导致printf函数无法使用
  • 5.4 不修改文件实现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发送。

    具体可以参考:在HAL库中的使用printf()函数和sprintf()函数

    物联沃分享整理
    物联沃-IOTWORD物联网 » STM32 HAL库:使用C标准库或MicroLIB实现printf函数输出

    发表评论