Bootstrap

STM32调试手段:重定向printf串口

引言

       C语言中经常使用printf来输出调试信息,打印到屏幕。由于在单片机中没有屏幕,但是我们可以重定向printf,把数据打印到串口,从而在电脑端接收调试信息。这是除了debug外,另外一个非常有效的调试手段。


一、什么是printf的重定向

       我们知道 C 语言中printf 函数默认输出设备是控制台,如果要实现在串口或者 LCD 上显示,必须重定义C语言标准库函数里调用的与输出设备相关的函数。比如使用 printf 输出到串口,则需要将 fputc 里面的输出指向串口,这一过程就叫 重定向

二、怎么样重定向printf

       那么如何让 STM32 使用 printf 函数呢?很简单,只需要将 fputc 里面的输出指向 STM32 串口即可,fputc 函数有固定的格式,我们只需要在函数内操作STM32串口即可。

2.1 配置microLIB

       由于我们使用的开发环境或者说工具链是keil-MDK,而在KEIL-MDK开发环境中,本身是没有包含C语言的一些标准库函数,如stdio.h这种文件等。       

       在KEIL-MDK开发环境中,可以选择使用MicroLIB库。MicroLIB是一个高度优化的C库,适用于嵌入式应用程序。它的特点是代码体积小,但功能较少,不支持某些ISO C特性。当然这里重定向printf要使用的库函数已经够用了。

我们可以直接在【魔法棒】中勾选上即可完成它的配置


2.2 重定向fputc函数(寄存器实现)

在使用MicroLIB库时,需要重定向fputc函数。fputc函数的原型如下:

int fputc(int ch, FILE* stream)
{
USART_SendChar(USART1, (uint8_t)ch);
return ch;
}

        那么,本次我们将printf重定向到串口上,这里使用的串口是USART1,只需要编写一个发送单字符数据的函数,接着重新定义一下fputc函数即可实现printf的重定向。

       也就是说,只需要在usart.c中编写三个函数,分别是串口的初始化发送一个字符函数fputc函数重写即可。

参考代码如下:

1、usart.h

#ifndef __USART_H
#define __USART_H

#include "stm32f10x.h"
#include <stdio.h>

// 初始化
void USART_Init(void);

// 发送一个字符
void USART_SendChar(uint8_t ch);

#endif

2、usart.c

#include "usart.h"

// 初始化
void USART_Init(void)
{
    // 1. 开启GPIO时钟 PA9 PA10
    RCC->APB2ENR |= RCC_APB2ENR_IOPAEN;
    RCC->APB2ENR |= RCC_APB2ENR_USART1EN;

    // 2. 设置GPIO工作模式
    // PA9 TX 输出,复用推挽输出 MODE-11 CNF-10
    // PA10 RX 输入,浮空输入 MODE-00 CNF-01
    GPIOA->CRH |= GPIO_CRH_MODE9;
    GPIOA->CRH |= GPIO_CRH_CNF9_1;
    GPIOA->CRH &= ~GPIO_CRH_CNF9_0;

    GPIOA->CRH &= ~GPIO_CRH_MODE10;
    GPIOA->CRH &= ~GPIO_CRH_CNF10_1;
    GPIOA->CRH |= GPIO_CRH_CNF10_0;

    // 3. 串口配置
    // 3.1 设置波特率
    USART1->BRR = 0x271;         // 115.2 Kpbs

    // 3.2 开启模块及收发使能
    USART1->CR1 |= USART_CR1_UE;
    USART1->CR1 |= USART_CR1_TE;
    USART1->CR1 |= USART_CR1_RE;

    // 3.3 其他配置(字长、奇偶校验、停止位)
    USART1->CR1 &= ~USART_CR1_M;
    USART1->CR1 &= ~USART_CR1_PCE;
    USART1->CR2 &= ~USART_CR2_STOP;
}

// 发送一个字符
void USART_SendChar(uint8_t ch)
{
    // 当发送的数据不为空时等待,TXE为1则可以继续写入数据
    while ((USART1->SR & USART_SR_TXE) == 0)
    {}

    // 发送一个字符
    USART1->DR = ch;    
}

// 重定向fputc函数
int fputc(int ch, FILE * file)
{
    USART_SendChar((uint8_t)ch);
    return (int)ch;
}

3、main.c

#include "usart.h"

int main(void)
{
	// 初始化
	USART_Init();

	// printf
	int a = 100;
	printf("a = %d", a);

	// 死循环保持状态
	while(1)
	{		
		
	}
}

2.3 重定向fputc函数(HAL库实现)

        然后我们在借助HAL库实现。其实也非常简单,其在STM32CubeMX中需要进行的配置和前面串口通讯轮询案例的HAL库实现需要的配置是一样的,因为本次作重定向主要就只是多重写一个fputc函数,即STM32CubeMX软件自动生成代码后我们再在usart.c中添加重写fputc函数的代码即可,非常简单。

所以这里附上前面HAL库实现的串口通讯轮询案例的文章链接,可以直接去看看相关配置步骤USART_串口通讯轮询案例(HAL库实现)-CSDN博客https://blog.csdn.net/2301_79475128/article/details/145263748?spm=1001.2014.3001.5502

然后,根据上面所述原理,这里直接给上添加到usart.c中的参考代码如下:

/* USER CODE BEGIN 1 */

int fputc(int ch, FILE * file)
{
  HAL_UART_Transmit(&huart1, (uint8_t *)&ch, 1, 1000);
  return ch;
}

/* USER CODE END 1 */

      值得注意的是,keil中要勾选上【microLIB】代码中引入#include<stdio.h> ,否则会提示找不到printf函数。


三、测试

编译以后,我们烧录然后在串口助手中看效果

1、寄存器实现的测试

2、HAL库实现的测试

       显然,通过重定向printf,我们可以通过printf将一些信息打印发送到电脑上,使用串口助手进行查看。


以上便是本次文章的所有内容,欢迎各位朋友在评论区讨论,本人也是一名初学小白,愿大家共同努力,一起进步吧!

鉴于笔者能力有限,难免出现一些纰漏和不足,望大家在评论区批评指正,谢谢!

;