STM32为啥要用重定向printf来打印串口数据?直接用串口发送函数不行吗

Posted

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了STM32为啥要用重定向printf来打印串口数据?直接用串口发送函数不行吗相关的知识,希望对你有一定的参考价值。

printf是标准输出接口嘛,可能用这个感觉正式一点吧,哈哈!
其实我一般都不用这个,搞什么重定向什么的挺麻烦。直接用sprintf格式化到字符串,然后用串口发送函数发送字符串就行了,这个方法不管什么单片机都好使。追问

正式倒没觉得,麻烦倒是有,一眼都看不出用的是哪个串口

参考技术A STM32直接用串口发送函数是可以的,为什么要用重定向printf来打印串口数据?printf函数格式灵活,使用方便,何必要你重编一个个子函数。追问

那你知道printf使用还有其他什么好处吗

追答

对单片机不是十分了解的人,也可以写程序,操作上脱离了底层函数,上升了一个层次。

STM32串口的介绍与使用(原理结构体发送字符串printf函数重定向)

摘自:串口的结构体 重定向printf串口发送stm32等博文
作者:点灯小哥
发布时间: 2021-03-06 21:46:33
网址:https://blog.csdn.net/weixin_46016743/article/details/114458698

串口相关知识

1.定义

串口通讯(Serial Communication)是一种设备间非常常用的串行通讯方式,因为它简单便捷,因此大部分电子设备都支持该通讯方式,其通讯协议可分层为协议层和物理层。物理层规定通信协议中具有机械、电子功能的特性,从而确保原始数据在物理媒体的传播;协议层主要规定通讯逻辑,统一双方的数据打包、解包标准。通俗的讲物理层规定我们用嘴巴还是肢体交流,协议层规定我们用中文还是英文交流。

2.通信概念

1.通讯结构

串口通讯的物理层的主要标准是RS-232标准,其规定了信号的用途、通讯接口及信号的电平标准,其通讯结构如下:

在这里插入图片描述
在设备内部信号是以TTL电平标准传输的,设备之间是通过RS-232电平标准传输的,而且TTL电平需要经过电平转换芯片才能转化为RS-232电平,RS-232电平转TTL电平也是如此。

2.电平标准

根据使用的电平标准不同,串口通讯可分为 RS-232标准 及TTL标准,具体标准如下:
在这里插入图片描述

在电子电路中常使用TTL的电平标准,但其抗干扰能力较弱,为了增加串口的通讯距离及抗干扰能力,使用RS-232电平标准在设备之间传输信息,经常使用MA3232芯片对TTL电平及RS-232电平进行相互转换。

3.数据传输方式:

  • A同步:
    传输以数据块为核心,在一个数据块内,字符间无间隔,接受发送同步,有sclk时钟,双方sclk连在一起,提供同步
    特点:效率高,无间隔
  • B异步:
    以字符为传输单位,每发一个字符,都得发送一个起始位,(告诉对方我开始发了)结束发送停止位。(我发完了)
    特点:效率低,间隔任意

4.串口数据包组成

起始位、数据位(8位或者9位)、奇偶校验位(第9位)、起始停止位(1,15,2位)、波特率设置

校验方式 :
奇偶校验需要一位校验位,即使用串口通信的方式2或方式3(8位数据位+1位校验位)。
奇校验(odd parity):让传输的数据(包含校验位)中1的个数为奇数。
即:如果传输字节中1的个数是偶数(不包含校验位),则校验位为“1”,奇数相反。

5.速率类型:

比特(bit):每秒传输的二进制位
波特(byte):每秒传输的码源个数(串口常用)

注:这俩本质上其实是差不多的

6.通信类型

  • 串行:一个一个传输 如:fsmc
  • 特点:占用资源多,速度慢,看干扰强
  • 并行:多个一起传输 如:spi usart
  • 特点:占用资源少,速度快。抗干扰能力弱,距离近

7.通信方式

  • 单工:数据传输只支持数据在一个方向上传输;如:打印机

  • 半双工:允许数据在两个方向上传输。但是,在某一时刻,只允许数据在一个方向上传输,它实际上是一种切换方向的单工通信;它不需要独立的接收端和发送端,两者可以合并一起使用一个端口。如:对讲机,spi

  • 全双工:允许数据同时在两个方向上传输。因此,全双工通信是两个单工通信方式的结合,需要独立的接收端和发送端。如:spi,usart
    在这里插入图片描述

8.概念补充

  • 1.数据包
    串口通讯的数据包由发送设备通过自身的TXD接口传输到接收设备得RXD接口,在协议层中规定了数据包的内容,具体包括起始位、主体数据(8位或9位)、校验位以及停止位,通讯的双方必须将数据包的格式约定一致才能正常收发数据。

  • 2.波特率
    由于异步通信中没有时钟信号,所以接收双方要约定好波特率,即每秒传输的码元个数,以便对信号进行解码,常见的波特率有4800、9600、115200等。STM32中波特率的设置通过串口初始化结构体来实现。

  • 3.起始和停止信号
    数据包的首尾分别是起始位和停止位,数据包的起始信号由一个逻辑0的数据位表示,停止位信号可由0.5、1、1.5、2个逻辑1的数据位表示,双方需约定一致。STM32中起始和停止信号的设置也是通过串口初始化结构体来实现。

  • 4.有效数据
    有效数据规定了主题数据的长度,一般为8或9位,其在STM32中也是通过串口初始化结构体来实现的。

  • 5.数据校验
    在有效数据之后,有一个可选的数据校验位。由于数据通信相对更容易受到外部干扰导致传输数据出现偏差,可以在传输过程加上校验位来解决这个问题。校验方法有奇校验(odd)、偶校验(even)、 0 校验(space)、 1 校验(mark)以及无(noparity)。这些也都可以在串口初始化结构体中实现的。
    在这里插入图片描述

3.USART简介

在这里插入图片描述

USART(通用同步异步收发器)是一个串行通信设备,可以灵活地与外部设备进行全双工数据交换。

有别于 USART 还有一个UART,它是在 USART 基础上裁剪掉了同步通信功能,只有异步通信。简单区分同步和异步就是看通信时需不需要对外提供时钟输出,我们平时用的串口通信基本都是 UART。USART 在 STM32 应用最多莫过于“打印”程序信息,一般在硬件设计时都会预留一USART 通信接口连接电脑,用于在调试程序是可以把一些调试信息“打印”在电脑端的串口调试助手工具上,从而了解程序运行是否正确、如果出错哪具体哪里出错等等。

串口的结构体

在这里插入图片描述

在这里插入图片描述
串口的结构体在USART.h里面

在这里插入图片描述

在这里插入图片描述

在这里插入图片描述
串口标志位
在这里插入图片描述

在这里插入图片描述

串口发送字符(STM32发送到上位机)

新建一个usart文件夹,然后添加到工程里面。

usart.c

#include "stm32f10x.h"                  // Device header
#include "usart.h"
 
void Usart_Init(void)
{
	//2. 配置GPIO的结构体
	GPIO_InitTypeDef GpioInitStructure; //初始化GPIO结构体命名
	USART_InitTypeDef UsartInitStructure;//初始化USART结构体命名
	
	//1. 配置时钟使能: GPIO口的时钟,引脚复用的时钟,串口的时钟
		RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA, ENABLE);
		RCC_APB2PeriphClockCmd(RCC_APB2Periph_AFIO, ENABLE);
		RCC_APB2PeriphClockCmd(RCC_APB2Periph_USART1, ENABLE);
 
	//2.1 配置PA9 TX(输出)
	GpioInitStructure.GPIO_Mode  = GPIO_Mode_AF_PP;//复用推挽输出
	GpioInitStructure.GPIO_Pin   = GPIO_Pin_9;
	GpioInitStructure.GPIO_Speed = GPIO_Speed_50MHz;//任意选择,影响不大
	GPIO_Init(GPIOA,&GpioInitStructure);
	
	//2.2 配置PA10 RX(接收)
	GpioInitStructure.GPIO_Mode  = GPIO_Mode_IN_FLOATING;//浮空输入
	GpioInitStructure.GPIO_Pin   = GPIO_Pin_10;
	GPIO_Init(GPIOA,&GpioInitStructure);
	
	//3.配置串口结构体   这是不带时钟的结构体  还有一个是带时钟的结构图,用于同步
	UsartInitStructure.USART_BaudRate =   115200;    //波特率
	UsartInitStructure.USART_HardwareFlowControl = USART_HardwareFlowControl_None;   //硬件流(没有)
	UsartInitStructure.USART_Mode = USART_Mode_Rx| USART_Mode_Tx;//模式(输入输出都可以)
	UsartInitStructure.USART_Parity = USART_Parity_No;			//校验位(不用)
	UsartInitStructure.USART_StopBits =	USART_StopBits_1;		//停止位(1位)
	UsartInitStructure.USART_WordLength =	USART_WordLength_8b;//有效字节长度(8位)
	
	USART_Init(USART1, &UsartInitStructure);
	USART_Cmd(USART1, ENABLE);//打开串口 比配置GPIO多这一步
 
 
	
}

usart.h

#include "stm32f10x.h" 
void Usart_Init(void);
 

main.c

#include "stm32f10x.h" 
#include "usart.h"//头文件是单独创建的文件 main函数找不到 要去手工添加路径(点击魔术棒...)

void delay(uint16_t time)
{
		uint16_t i = 0;
		while(time--)
		{
			i=12000;
			while(i--);
		}
}
int main(void)
{
		Usart_Init();
 
		while(1)
		{
			
			
			USART_SendData(USART1, 'O');     //数据寄存器空标志位   RESET状态说明上面字符发送成功了
			while (USART_GetFlagStatus(USART1, USART_FLAG_TXE) == RESET); //USART_GetFlagStatus是判断标志位 
						USART_SendData(USART1, 'K');                     //USART_FLAG_TXE 去usart.h(固件库里的头文件)  FLAG里找
			while (USART_GetFlagStatus(USART1, USART_FLAG_TXE) == RESET);   
						USART_SendData(USART1, '\\n');
			while (USART_GetFlagStatus(USART1, USART_FLAG_TXE) == RESET);  //右键USART_GetFlagStatus去goto找到RESET
						delay(1000);
		}
}

在这里插入图片描述

串口发送字符串

上一节串口只能一个一个发送字符,要想发送字符串怎么实现?

可以自己写一个串口发送字符串函数,然后调用即可。

//发送字符函数(自己定义的 注意形参要和固件库里面串口发送的形参要一样)
void USARTSendByte(USART_TypeDef* USARTx, uint16_t Data)//main函数调用这个函数的方法:USARTSendByte(USART1, 'A');
{
		USART_SendData( USARTx,  Data);//固件库本身的串口发送函数
		//判断数据寄存器是否为空
		while( USART_GetFlagStatus( USARTx,  USART_FLAG_TXE) == RESET);//USART_GetFlagStatus是判断标志位 USART_FLAG_TXE 去usart.h  FLAG找
}

//发送字符串函数(自己定义的)
void USARTSendString( USART_TypeDef* USARTx, char *str)
{
		uint16_t i = 0;
		do{
			USARTSendByte(USART1,*(str+i));
			i++;
		}while(*(str+i) != '\\0');//字符串结束标识
		
		while( USART_GetFlagStatus( USARTx,  USART_FLAG_TC) == RESET);//USART_GetFlagStatus是判断标志位 USART_FLAG_TC(这是判断字符串) 去usart.h  FLAG找

      //main函数里这样调用
      USARTSendString( USART1, "你好STM32")
      

}

重定向printf

在C语言标准库中,printf()拥有十分强大的输出能力,可以输出各种类型的数据,整型、浮点型、8进制、16进制、换行符,缩进符等等。

printf()是把数据输出到屏幕,但是ARM芯片中没有屏幕,我们设想将printf()打印到串口,这样我们就可以通过printf()和串口实时的观察ARM芯片内部的工作情况,运行结果。

先来认识一个关键字:__weak

weak的字面意思就是“微弱”的意思,其主要作用就是可以重新定义重名函数或变量而编译时不报错。笔者最开始注意到这个关键字是在使用 STM32 HAL 库的时候注意到的,比如这张图片所示:
在这里插入图片描述

在上图我们可以看到左边的 HAL_MspInit 函数前面用 __weak 进行修饰,而图片右边又定义了 HAL_MspInit
函数,这时整个工程就定义了两个 HAL_MspInit 函数,声明可以有多个,但是定义只能存在一个,因为 __weak
的存在,所以不会报错,并且真正起作用的函数是没有用 __weak 修饰的函数。

printf函数其实就是调用了fputc,我们来重写fputc达到重定向printf的目的。

实现过程:在usart.c 的Usart_Init()后面增加两个函数

usart.c

int fputc(int ch, FILE *f)//输
{
                            	//int型强制转换成无符号整型 防止出错
		USARTSendByte( USART1,  (uint8_t)ch);
		while( USART_GetFlagStatus( USART1,  USART_FLAG_TXE) == RESET);
		return (ch);
}

int fgetc(FILE *f)//接收
{
		                                     //标志位选择接收
		while( USART_GetFlagStatus( USART1,  USART_FLAG_RXNE) == RESET);
		
		return (int) USART_ReceiveData(USART1);
 
}

在这里插入图片描述

注意:在使用printf等C语言标准库函数要包含头文件stdio.h,并且勾选Target中的use MicroLIB使用标准库。

在这里插入图片描述

usart.h

#include "stm32f10x.h" 
#include "stdio.h"

void Usart_Init(void);
void USARTSendByte(USART_TypeDef* USARTx, uint16_t Data);
void USARTSendString( USART_TypeDef* USARTx, char *str);
int fputc(int ch, FILE *f);

main.c

#include "stm32f10x.h"                  // Device header
#include "usart.h"
 
void delay(uint16_t time)
{
		uint16_t i = 0;
		while(time--)
		{
			i=12000;
			while(i--);
		}
}
 
int main(void)
{
 
		Usart_Init();
 
		printf("你好STM32");//printf调用重写的fputc  这样单片机就可以通过串口发送字符串"你好STM32"
		
		putchar('X');  //putchar调用重写的fputc   这样单片机就可以通过串口发送字符'X'   
	
		while(1)
		{
			
			
			
		}
}

以上是关于STM32为啥要用重定向printf来打印串口数据?直接用串口发送函数不行吗的主要内容,如果未能解决你的问题,请参考以下文章

STM32串口打印的那些知识

STM32串口printf()重定向问题

stm32-串口实验遇到的问题

STM32CubeMX开发04——串口打印

STM32CubeMX开发04——串口打印

STM32G0学习手册——FreeRTOS中使用printf-stdarg.c进行串口打印