【windows下基于Eclipse和GCC搭建stm32开发环境(6)】通过串口USRAT1实现Printf()打印

串口调试是开发STM32必不可多少的工具,printf()直接打印到串口,配上超级终端监控软件运行,简单又实用,那么下面就开始搞!!!:

注意: 本教程基于Eclipse+GCC+Eclipse Embedded C/C++插件搭建的STM32工程 ,STM32在Eclipse下的开发环境搭建请移步: 【windows下基于Eclipse和GCC搭建stm32开发环境(1)】

一、串口初始化配置:

要想通过串口USART打印信息,首先得把串口初始化和驱动搞起来:

1、 串口简介:

USART:通用同步/异步串行接收发送器,即串口。一般使用串口都使用异步收发模式。

以USART1为例,需要用到的HAL库函数有:

UART初始化:HAL_UART_Init ( )

异步串口初始化函数,在源文件:stm32f1xx_hal_uart.c

UART对应的GPIO口初始化:HAL_UART_MspInit ( )

串口MSP初始化,其实就是GPIO口模式、速度的初始化,该函数由HAL_UART_Init()函数自动调用,但是原HAL库中是(_weak)声明的空函数,需要我们重写!

串口发送函数

HAL_StatusTypeDef HAL_UART_Transmit_IT(UART_HandleTypeDef *huart, uint8_t *pData, uint16_t Size)

串口接收函数

HAL_StatusTypeDef HAL_UART_Receive_IT(UART_HandleTypeDef *huart, uint8_t *pData, uint16_t Size)

2、启用HAL库UART代码块:

在建立一个新的工程时,HAL库的很多模块代码是没有启动的,包括UART和DMA模块:
在这里插入图片描述

所以我们要启用UAST库文件,具体操作如下图:
在这里插入图片描述
在这里插入图片描述

去掉Exclude resource from build前面的勾子!
stm32f1xx_hal_dma.c的操作和以上类似,不在过多赘述,那为啥要放开dma模块的代码,我们又没用到,主要是因为USART模块可以使用DMA通道传输数据,在HAL库中相互引用,放开DMA模块代码是为了去除编译错误

3、 串口初始化(基于HAL库编程):

基于HAL_UART_Init(),我们创建自己的UART初始化函数:void Usart1Init(int baudRate)
我们先在app路径下新建两个文件:
在这里插入图片描述

后续我们所有模块的驱动代码都放到app路径下,然后我们在usart.c中写如下初始化代码:

#include "stm32f1xx.h"
UART_HandleTypeDef 	UART1_Handle; // UART1串口的初始化句柄,整个工程堆UART1的操作都要用到这个句柄
void Usart1Init(int baudRate) // baudRate 波特率,初始化时指明
{
/*初始化UART1的句柄(参数配置结构体)*/
	UART1_Handle.Instance = USART1;			//串口句柄1与串口1对应
	UART1_Handle.Init.BaudRate = baudRate;				//初始化波特率为输入波特率
	UART1_Handle.Init.HwFlowCtl = UART_HWCONTROL_NONE;		//无硬件控制流
	UART1_Handle.Init.Mode = UART_MODE_TX_RX;				//串口异步收发模式
	UART1_Handle.Init.OverSampling = UART_OVERSAMPLING_16;	//默认过采样16倍
	UART1_Handle.Init.Parity = UART_PARITY_NONE;			//默认无校验位
	UART1_Handle.Init.StopBits = UART_STOPBITS_1;			//默认1位停止位
	UART1_Handle.Init.WordLength = UART_WORDLENGTH_8B;		//8bit数据位
	if (HAL_UART_Init(&UART1_Handle) != HAL_OK)
  {
     //Error_Handler();自己定义一个报错,可以是某个异常中断,便于调试
  }
}

/重定义HAL_UART_MspInit函数/

void HAL_UART_MspInit(UART_HandleTypeDef* huart)							
{
	GPIO_InitTypeDef UART1_GPIO = {0};	
	if(huart->Instance == USART1)
	{
		__HAL_RCC_USART1_CLK_ENABLE();							//使能串口1时钟
    	__HAL_RCC_GPIOA_CLK_ENABLE();							//GPIOA时钟使能
		
		UART1_GPIO.Pin = GPIO_PIN_9 | GPIO_PIN_10;				//初始化A9 A10
		UART1_GPIO.Alternate = GPIO_AF7_USART1;			    //串口1复用模式
		UART1_GPIO.Mode = GPIO_MODE_AF_PP;				    //推挽复用模式
		UART1_GPIO.Pull = GPIO_PULLUP;					    //上拉
		UART1_GPIO.Speed = GPIO_SPEED_FREQ_VERY_HIGH;			//高速
		
		HAL_GPIO_Init(GPIOA,&UART1_GPIO);						//GPIO初始化函数
		
		HAL_NVIC_SetPriority(USART1_IRQn, 1, 0);			//串口中断初始化及使能
    	HAL_NVIC_EnableIRQ(USART1_IRQn);
		
	}	
}

usart.h文件:

#ifndef USART_USART_H_  
#define USART_USART_H_
#include "stm32f1xx.h"  //因为用到HAL库,所以.h文件也要引用头文件

extern UART_HandleTypeDef 	UART1_Handle; //.c文件中定义的全局变量要在.h中进行extern声明,以便于在其他文件中调用

void Usart1Init(int baudRate); //函数声明
void HAL_UART_MspInit(UART_HandleTypeDef* huart);

#endif /* USART_USART_H_ */ 

二、USART1中断函数重定向

虽然进行了UART模块的初始化,但是中断函数也需要我们配置一下,那中断在哪里引用的呢? 答案是:向量表!
首先找到中断向量表定义的位置:
在这里插入图片描述

可以看到向量表中USART1串口对应的中断函数是名为USART1_IRQHandler
在这里插入图片描述

找一下这个USART1_IRQHandler函数:
鼠标放在这个函数名上面,然后按住Ctrl再按鼠标左键:
找到定义的位置:
在这里插入图片描述

可以看到这个中断函数是一个若定义,并且指定了一个别名!也就是说只要执行USART1中断,就运行名为Default_Handler的函数,这是在工程建立时默认的,所有中断都这样,这是需要我们后面重定义的
看一下默认中断中干了啥:
在这里插入图片描述

可以看到默中断中啥也没干,就是个死循环,也就是说我们如果不重新指明USART1中断的话,只要触发串口中断,就会进入死循环
那我们怎么重新指明USART1中断呢?
首先改别名:
在这里插入图片描述

然后新建这个别名函数:
在这里插入图片描述

这样的话只要有中USART1断函数就会进入我们自己的中断函数:
那我们自己的中断函数干嘛呢? 这时候就用到HAL库了,调用HAL库USART1中断函数就好了:
在这里插入图片描述

同时我们要把app.h头文件包含进来:
在这里插入图片描述

引入app.h主要是为了引入usart.h进而引用extern UART_HandleTypeDef UART1_Handle; 句柄
在这里插入图片描述

好了,到这里未知,USART的配置就完成了!
接下来在main函数中应用:
在这里插入图片描述

三、串口收发消息调试

1、超级终端连接串口:

超级终端软件推荐:MobaXterm(非常好,开源免费)、Xshell(收费,需破解)
本文以MobaXterm软件为例:

在这里插入图片描述

在这里插入图片描述
在这里插入图片描述

打开串口后,可以看到串口不停的打印出字符串“测试!”,但是好像有一个问题,为啥不是从头开的?
看一下代码:
在这里插入图片描述

加上“\r”之后,烧录程序,再次测试:
在这里插入图片描述

三、newlib库printf函数重定向

串口已经配置好了,并且能够在超级终端打印信息,那么用printf()打印会怎么样呢?
答案是:看不到打印!

1、newlib库:

这时候我们了解一下newlib库是个啥东西:
由于我们创建工程的时候,选择了使用newlib库,所以我们代码中已经包含了newlib库的代码:

在这里插入图片描述
在这里插入图片描述
newlib库介绍:

Newlib是一个面向嵌入式系统的与GNU兼容的嵌入式C运行库的其中一种。最初是由Cygnus Solutions收集组装的一个源代码集合,取名为newlib

但是从成熟度来讲,newlib是最优秀的。newlib具有独特的体系结构,使得它能够非常好地满足深度嵌入式系统的要求。newlib可移植性强,具有可重入特性、功能完备等特点,已广泛应用于各种嵌入式系统中。

Newlib的所有库函数都建立在20个桩函数的基础上,这20个桩函数完成一些newlib无法实现的功能:

  1. 级I/O和文件系统访问(open、close、read、write、lseek、stat、fstat、fcntl、link、unlink、rename);

  2. 扩大内存堆的需求(sbrk);

  3. 获得当前系统的日期和时间(gettimeofday、times);

  4. 各种类型的任务管理函数(execve、fork、getpid、kill、wait、_exit);

那么newlib库和printf()函数啥关系呢?

printf()是C标准库中的函数,在prinf函数运行时会调用C运行库中的桩函数_write()

这个函数是写的意思,往哪儿写呢?先看一下该函数:
在这里插入图片描述

可以看到,原工程是_write函数是调用了DMA串口发送函数,关于DMA的应用,后续文章讲解!

那么我们如果想让printf打印的东西显示在串口连接的超级终端中,我们是不是只需要修改_write函数就行了!
怎么修改呢?接着往下看:

2、write函数重定向:

在这里插入图片描述

我们在_write函数中调用串口发送函数,ptr是要发送的字符串指针,len是长度,这个不用管,因为这是printf传给_write函数的,改变_write函数中字符串的写通道,就是所谓的重定向

当然头文件还是要引用进来:
在这里插入图片描述

好了,测试一下:

在这里插入图片描述

在这里插入图片描述

窗口中打印出来printf的内容了!完美!这样我们printf()函数就实现在超级终端的打印了!

结语:

版权声明:本文为博主原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。
本文链接:https://blog.csdn.net/a2529280665/article/details/121663539

  • 1
    点赞
  • 7
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

Flash张

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值