STM32调试技巧:重定向printf串口

一、引言

通过重定向printf 串口,我们可以把调试信息发送在串口上,方便观察参数信息,对程序进行调试

二、原理

printf函数在C语言标准库中是基于fputc函数实现的。fputc函数用于将一个字符输出到指定的文件流中。在嵌入式系统中,我们可以通过重写fputc函数,将字符输出到串口,从而实现printf函数的重定向。

三、实现

1. 标准库实现(Keil5)

这里我们以stm32f103c8t6 实现printf 向串口发送一个字节为例:

这里不实现通过中断达到连续发送字节的效果,只实现通过重定向printf主动发送一个字节的效果,如果需要前者,可以私信我获取

首先进行USART1和GPIO的初始化:

c 复制代码
  /*开启时钟*/
	RCC_APB2PeriphClockCmd(RCC_APB2Periph_USART1, ENABLE);	//开启USART1的时钟
	RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA, ENABLE);	//开启GPIOA的时钟
	
	/*GPIO初始化*/
	GPIO_InitTypeDef GPIO_InitStructure;
	GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP;
	GPIO_InitStructure.GPIO_Pin = GPIO_Pin_9;
	GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
	GPIO_Init(GPIOA, &GPIO_InitStructure);			//将PA9引脚初始化为复用推挽输出
	
	GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IPU;
	GPIO_InitStructure.GPIO_Pin = GPIO_Pin_10;
	GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
	GPIO_Init(GPIOA, &GPIO_InitStructure);			//将PA10引脚初始化为上拉输入
        
  /*USART初始化*/
	USART_InitTypeDef USART_InitStructure;					//定义结构体变量
	USART_InitStructure.USART_BaudRate = 9600;				//波特率
	USART_InitStructure.USART_HardwareFlowControl = USART_HardwareFlowControl_None;	//硬件流控制,不需要
	USART_InitStructure.USART_Mode = USART_Mode_Tx | USART_Mode_Rx;	//模式,发送模式和接收模式均选择
	USART_InitStructure.USART_Parity = USART_Parity_No;		//奇偶校验,不需要
	USART_InitStructure.USART_StopBits = USART_StopBits_1;	//停止位,选择1位
	USART_InitStructure.USART_WordLength = USART_WordLength_8b;		//字长,选择8位
	USART_Init(USART1, &USART_InitStructure);				//将结构体变量交给USART_Init,配置USART1

定义一个通过串口发送一个字节的函数:

c 复制代码
/**
  * 函    数:串口发送一个字节
  * 参    数:Byte 要发送的一个字节
  * 返 回 值:无
  */
void Serial_SendByte(uint8_t Byte)
{
	USART_SendData(USART1, Byte);		//将字节数据写入数据寄存器,写入后USART自动生成时序波形
	while (USART_GetFlagStatus(USART1, USART_FLAG_TXE) == RESET);	//等待发送完成
	/*下次写入数据寄存器会自动清除发送完成标志位,故此循环后,无需清除标志位*/
}

然后将printf重定向到该函数:

c 复制代码
/**
  * 函    数:使用printf需要重定向的底层函数
  * 参    数:保持原始格式即可,无需变动
  * 返 回 值:保持原始格式即可,无需变动
  */
int fputc(int ch, FILE *f)
{
	Serial_SendByte(ch);			//将printf的底层重定向到自己的发送字节函数
	return ch;
}

之后调用printf 以把想要显示的内容发送到串口

2. hal库实现(STM32CubeIDE)

这里实现通过中断向串口发送字符串的效果

  • 通过芯片图对引脚进行配置:
  • USART1的中断:
  • 要修改RCC的时钟来源为HSE让系统时钟频率为72MHZ
  • SYS的Debug,要不然下载不进去代码:
  • Connectivity的USART1中设置Mode为:Asynchronous
  • 然后ctrl+S生成代码,进入代码编辑页面
  • 打开USART.c文件,在Includes处添加stdio.h头文件,在/* USER CODE BEGIN 0 /和/ USER CODE END 0 */添加重定向代码:
c 复制代码
/* Includes ------------------------------------------------------------------*/
#include "usart.h"
#include "stdio.h"

/* USER CODE BEGIN 0 */

#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,HAL_MAX_DELAY);
    return ch;
}

/* USER CODE END 0 */

这里解释条件编译的作用:stm32cubuIDE使用的编译器为GCC,GCC的printf的底层是__io_putchar函数,其他编译器的printf的底层是fputc函数,这样做能保证PUTCHAR_PROTOTYPE能始终指向当前编译器printf的底层,并重定向它。

  • 通过以上操作,printf函数就能正确向串口发送字符和字符串。

四、总结

本文讲解了如何在标准库中实现printf向串口发送字节以及如何在hal库中通过中断实现printf向串口丝滑发送字符串两种操作,第一次发文章,目的是记录自己在学习过程中解决问题的过程,如果能帮助到一些人,当然更好,如果有读者认为该文章有错误的地方或者需要改善的地方,欢迎指正,感谢大家!

相关推荐
日更嵌入式的打工仔5 天前
C 语言 restrict 关键字
c
REDcker8 天前
OpenSSL 完整文档
c++·安全·github·c·openssl·后端开发
橘颂TA9 天前
【剑斩OFFER】算法的暴力美学——力扣 675 题:为高尔夫比赛砍树
数据结构·算法·c·结构与算法
程芯带你刷C语言简单算法题12 天前
Day48~对于高度为 n 的台阶,从下往上走,每一步的阶数为 1,2,3 中的一个。问要走到顶部一共有多少种走法
c语言·开发语言·学习·算法·c
余衫马15 天前
为什么在 Windows 上用 Clang/LLVM?
c++·windows·c
REDcker17 天前
AIGCJson 库介绍与使用指南
c++·json·aigc·c
消失的旧时光-194318 天前
函数指针 + 结构体 = C 语言的“对象模型”
c·对象模型
REDcker19 天前
RTCP 刀尖点跟随技术详解
c++·机器人·操作系统·嵌入式·c·数控·机床
消失的旧时光-194320 天前
函数指针 + 结构体 = C 语言的“对象模型”?——从 C 到 C++ / Java 的本质统一
linux·c语言·开发语言·c++·c
埃伊蟹黄面21 天前
ELF深入解剖:从文件头到动态段,图解库的二进制构成
linux·c·