STM32 HAL库:使用C标准库或MicroLIB实现printf | 多串口使用printf输出 | 结合DMA实现printf | 常见问题总结

综合多篇文章对实现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发送。

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

  • 31
    点赞
  • 44
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
### 回答1: 在使用STM32 HAL库时,可以通过重定向标准输出流的方式使用printf函数。具体步骤如下: 1. 在main函数中调用HAL库提供的函数HAL_InitTick(),以初始化系统时钟。 2. 在main函数中调用HAL库提供的函数HAL_UART_Init(),以初始化UART串口通信。 3. 在main函数中调用HAL库提供的函数HAL_UART_MspInit(),以配置UART串口通信的GPIO引脚。 4. 在main函数中调用HAL库提供的函数fopen(),以打开标准输出流。 5. 在main函数中调用HAL库提供的函数freopen(),以重定向标准输出流到UART串口通信。 6. 在需要输出信息的地方,使用printf函数进行输出。 需要注意的是,使用printf函数会增加程序的代码大小和运行时间,因此在实际应用中需要根据具体情况进行考虑。 ### 回答2: STM32是一种嵌入式系统微控制器,STMicroelectronics公司推出的STM32 HAL库提供了许多方便函数来方便地管理芯片硬件资源。然而,STM32 HAL库不支持标准的stdio.h中的printf函数,这对于开发者来说是一个非常恼人的问题。本文将解释如何在STM32 HAL库使用printf函数,并提供一些实际的代码示例。 使用printf函数 使用printf函数必须设置好串口或者USB虚拟串口。虚拟串口需要使用满足CDC或者VCP规范的USB转串口芯片硬件。这里介绍使用串口的方法。 步骤1:初始化USART2串口设备 void MX_USART2_UART_Init(void) { huart2.Instance = USART2; huart2.Init.BaudRate = 115200; huart2.Init.WordLength = UART_WORDLENGTH_8B; huart2.Init.StopBits = UART_STOPBITS_1; huart2.Init.Parity = UART_PARITY_NONE; huart2.Init.Mode = UART_MODE_TX_RX; huart2.Init.HwFlowCtl = UART_HWCONTROL_NONE; huart2.Init.OverSampling = UART_OVERSAMPLING_16; if (HAL_UART_Init(&huart2) != HAL_OK) { Error_Handler(); } } 步骤2:添加头文件 添加头文件#include "stdio.h" 步骤3:重定向_C使用的I/O到串口函数 int __io_putchar(int ch){ HAL_UART_Transmit(&huart2, (uint8_t*)&ch, 1, HAL_MAX_DELAY); return ch; } int __io_getchar(void){ uint8_t ch = 0; HAL_UART_Receive(&huart2, &ch, 1, HAL_MAX_DELAY); return ch; } 步骤4:PRINTF使用串口 下面的代码演示了如何在STM32 HAL库使用PRINTF函数: #include "main.h" int main(void){ HAL_Init(); /* Configure the system clock */ SystemClock_Config(); /* Initialize all configured peripherals */ MX_USART2_UART_Init(); printf("Hello World!\n\r"); while (1){} } 总结 通过重定向printf流到UART串口,可以方便地在开发STM32使用printf输出调试信息。但使用printf有一定的缺点,它是在实时系统中使用的较为耗时函数。这是因为printf函数必须解析格式字符串,并调用适当的转换函数。为了避免这个问题,最好使用半主动的方法使用日志输出,例如printf之类的函数,通过缓冲区进行输出,或将输出设置为仅在发现问题时才进行输出。这样可以提高代码执行效率,同时也可以提供相应的实时能力。 ### 回答3: STM32 HAL库是一套由STMicroelectronics公司推出的一种开发工具,能够简化STM32系列单片机的应用程序开发过程。在HAL库中,使用printf函数能够方便的打印各种数据信息,但是在使用printf函数之前,需要进行一些配置。 首先,在使用printf函数之前,需要配置USART串口通信的参数,例如波特率、数据位、停止位、奇偶校验位等,这些参数需要根据具体的串口硬件进行设置。我们可以在MX_GPIO_Init函数中配置串口管脚的引脚和模式,在MX_USART2_UART_Init函数中配置USART串口通信的参数。 其次,需要开启printf函数的内存输出模式。在代码中,我们需要加入一行语句“setvbuf(stdout, NULL, _IONBF, 0);”,这样可以将输出重定向到内存中。这是因为,printf函数默认是将数据输出到stdout流中,而stdout流默认是以缓存方式进行输出,需要使用setvbuf函数进行缓存设置,这里我们将缓存区设置为NULL,表示不进行缓存,直接输出到内存中。 最后,还需要编写一个函数putchar(或者重定向printf函数),将输出的数据读取出来,发送到串口中进行实际输出。我们可以在主程序中编写一个简单的死循环,循环中调用putchar函数,将内存中的数据读取出来,发送到串口实现实际输出。 综上所述,使用printf函数需要进行串口通信的参数配置、内存输出模式设置和实际输出函数编写。这些步骤都比较繁琐,但是通过HAL库的封装,大大简化了代码编写的复杂性,提高了开发效率。除了printf函数,HAL库还包含其他丰富的函数库,可以方便的进行GPIO控制、定时器设置、中断处理等功能,适用于各种STM32系列单片机应用程序开发。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值