基于STM32标准库的USART串口通信

一、USART串口通信简介

  • 串口是一种应用十分广泛的通讯接口,串口成本低、容易使用、通信线路简单,可实现两个设备的互相通信。单片机的串口可以使单片机与单片机、单片机与电脑、单片机与各式各样的模块互相通信,极大地扩展了单片机的应用范围,增强了单片机系统的硬件实力

  • USART(Universal Synchronous/Asynchronous Receiver/Transmitter)是STM32内部集成的硬件外设,可根据数据寄存器的一个字节数据自动生成数据帧时序,从TX引脚发送出去,也可自动接收RX引脚的数据帧时序,拼接为一个字节数据,存放在数据寄存器里。USART具有以下的参数:

    1. 自带波特率发生器,最高达4.5Mbits/s

    2. 可配置数据位长度(8/9)、停止位长度(0.5/1/1.5/2)

    3. 可选校验位(无校验/奇校验/偶校验)

    4. 支持同步模式、硬件流控制、DMA、智能卡、IrDA、LIN

    以下是USART串口通信在单片机内部的流程图

    在这里插入图片描述
    USART主要通过GPIO的TX和RX引脚收发数据。当单片机作为发送端时,系统通过波特率发生器规定发送速度,发送控制器根据波特率将要发送的数据放入发送数据寄存器TDR,TDR再将数据压入发送移位寄存器中,通过TX引脚从低位到高位逐一发送。每次移位发送的数据流有以下特征:

    在这里插入图片描述

    空闲位一直置1,当有数据进入后,起始位置0,随后开始读取数据,数据位共8位或9位,含奇偶校验位或不校验,最后停止位置1,结束该数据流,等待下一个数据流

当单片机作为接收端时也是具有相似的原理,系统将收到的数据先放在接收移位寄存器,再逐一放入接收数据寄存器RDR,接收的速率也是由接受控制器进行配置的,所以串口通信双方必须要保持严格的同步性,才能保证数据无误。

二、标准库封装配置USART1相关函数

我们首先进行标准库中USART1的函数配置

在以前的博客中,我曾详细介绍了新建模板工程的步骤,请不会的友友们去看这篇博客,这次我就不再做过多介绍了,直接开始使用工程开始配置。

链接:利用STM32实现流水灯程序_stm32流水灯程序代码_Constellation_zZ的博客-CSDN博客

我们打开工程模板,在Hardware组里新建“Serial.c"和”Serial.h"两个文件

在这里插入图片描述

注意,新建时请在路径后加上“\Hardware",方便封装与调用

在这里插入图片描述

随后我们按照之前的方式一样,在头文件上给出以下格式

#ifndef __SERIAL_H
#define __SERIAL_H
#include "stm32f10x.h"                 // Device header



#endif

在”Serial.c"函数中写入以下

#include "stm32f10x.h"                  // Device header
#include "Delay.h"
#include "Stdio.h"


写入一个“Serial_Init”函数,用于配置基本的USART和GPIO

首先是打开APB使能时钟,

//打开USART1时钟
	RCC_APB2PeriphClockCmd(RCC_APB2Periph_USART1, ENABLE);
	//打开GPIOA时钟
	RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA, ENABLE);

接下来配置GPIO口,打开GPIOA的PA9引脚,作为数据发送。模式设置为复用推挽输出模式

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);

接下来是配置USART串口通信,我们选择USART1作为串口,同样建立结构体,设置波特率为9600,模式为仅发送,0校验位,1停止位,8数据位。编写如下:

USART_InitTypeDef USART_InitStruture;
	USART_InitStruture.USART_BaudRate=9600;//波特率设置为9600
	USART_InitStruture.USART_HardwareFlowControl=USART_HardwareFlowControl_None ;//设置无硬件流
	USART_InitStruture.USART_Mode=USART_Mode_Tx;//模式设置为仅发送
	USART_InitStruture.USART_Parity=USART_Parity_No ;///不需要校验
	USART_InitStruture.USART_StopBits=USART_StopBits_1 ;//停止位选择1位
	USART_InitStruture.USART_WordLength=USART_WordLength_8b;//数据位选择8位
	USART_Init(USART1,&USART_InitStruture);
	
	USART_Cmd(USART1,ENABLE);//启用USART外设

这些函数都是配置函数,需要搭建起这样的基本环境才能进行下一步主程序编程,建议在使用这些函数的时候右键看一下定义,自己理解一下。

最后一步,把void Serial_Init(void);放到头文件里声明一下。

至此配置就完成了

三、自动发送字符串

3.1 发送函数编写

我们需要编写一个发送函数,用于单片机对电脑进行数据传输,在标准库中,给出了发送函数为

void USART_SendData(USART_TypeDef* USARTx, uint16_t Data)

我们通过两种方式实现字符串的发送

3.1.1 利用数组发送

我们将字符串的每个字符放在一个数组的每个元素中,发送时发送数组即可

首先我们先封装发送一个字符的函数,如下

//发送数据
void Serial_SendByte(uint8_t Byte)//发送一个字符
{
	USART_SendData(USART1,Byte);//调用库函数将数据送至发送数据寄存器
	while(USART_GetFlagStatus(USART1,USART_FLAG_TXE)==RESET);//等待标志位置一,结束循环

}

之后的所有发送方式都是基于发送一个字符来实现的。然后我们封装一个发送数组的函数

void Serial_SendArray(uint8_t *Array,uint16_t Length)//发送数组
{
	uint16_t i;
	for(i=0;i<Length;i++)
	{
		Serial_SendByte(Array[i]);
		Delay_ms(10);
	}
}

至此,函数就写完了。接下来只需要在主函数中定义一个数组,把字符串中的每个字符填到数组里面,再调用上面写的发送数组函数即可。这个主函数就交给读者自己完成了。本次主要利用下面的方法实现发送。

3.1.2 直接发送字符串

我们也可以直接发送一个字符串,原理其实跟发送数组差不多,只不过在发送函数中把字符串逐个拆开放在数组里在发送出来,这样就不需要在主函数里再定义数组了,发送函数如下:

void Serial_SendString(char *String)//发送字符串
{
	uint16_t i;
	for(i=0;i<String[i]!=0;i++)
	{
		Serial_SendByte(String[i]);
		Delay_ms(10);
	}
}

int fputc(int ch, FILE *f)//printf重定向到串口,这是后面使用printf的必要函数,把字符打印到串口
{
	Serial_SendByte(ch);
	return ch;
}
	

与上一种方式相比,这样显得更简洁一些,主函数也写的更少了

一定记住,要把上述所编写的函数放到头文件里进行声明!!头函数最终为

#ifndef __SERIAL_H
#define __SERIAL_H
#include "stm32f10x.h"                  // Device header

void Serial_Init(void);
void Serial_SendByte(uint8_t Byte);
void Serial_SendArray(uint8_t *Array,uint16_t Length);
void Serial_SendString(char *String);
#endif

3.2 主函数编写

基于利用发送字符串的方式发送数据,我们将主函数编写如下:

#include "stm32f10x.h"                  // Device header
#include "Delay.h"
#include "Serial.h"
#include "Stdio.h"
int main(void)
{
	Serial_Init();
//	uint8_t Array[]={'H','e','l','l','o',',','W','i','n','d','o','w','s','!','!'};//感兴趣可自行完成数组方式

	while (1)
	{
		Serial_SendString("Hello,Windows!!");
		Serial_SendString("\r\n");
		Delay_ms(1000);
		printf("你好,世界!\r\n");
		Delay_ms(1000);
	}
}

3.3 烧录测试结果

我们将程序烧录到开发板上后,打开我们手上的串口调试助手(野火,ISP那些都可以,这里我用的是自己的串口助手)。

但不管是什么软件,打开助手后,一定要注意调好参数,如下所示

在这里插入图片描述

设置好后,打开串口,结果如下:

在这里插入图片描述

3.4 波形测试观察

keil MDK自带的调试模式可以通过输出波形来观察串口的电平变化情况,关于调试方面的配置之前已经讲过了,这里就把图列出来:

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

在设置观察端口时,我们观察USART1的电平变化情况。

在这里插入图片描述

波形测试结果如下:
在这里插入图片描述
颜色较深宽度较宽的部分是英文数据,另一个是中文数据。
在这里插入图片描述
放大来看,波形为0时是正在发送的一个码元。从图中可以看出传输一个码元需要耗费0.001s

四、基于中断的可控发送

现在基于原有的串口功能进行深度优化。要求打开串口后,输入一个“*”开始发送字符串,当输入一个“#”,串口停止发送,输入其他符号时,显示命令异常。

我们使用中断函数来实现上述功能,利用所要求的字符触发中断函数,调用中断函数执行发送和停止命令。

4.1 配置NVIC控制USART1触发中断

在原有“Serial.c"中Serial_Init()代码的基础上,我们需要进行以下修改

首先开启GPIOA的PA10端口。

//配置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);
	
	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);

然后加上打开USART中断,配置NVIC

//开启USART中断
	USART_ITConfig(USART1,USART_IT_RXNE,ENABLE);
	
	//配置NVIC
  NVIC_PriorityGroupConfig(NVIC_PriorityGroup_2);//选择分组2
	NVIC_InitTypeDef NVIC_InitStructure;
	NVIC_InitStructure.NVIC_IRQChannel= USART1_IRQn;//USART1在NVIC中的通道
	NVIC_InitStructure.NVIC_IRQChannelCmd=ENABLE ;
	NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority=1;
	NVIC_InitStructure.NVIC_IRQChannelSubPriority=1;
	NVIC_Init(&NVIC_InitStructure);
	
	USART_Cmd(USART1,ENABLE);//启用USART外设

4.2 中断函数编写

接下来是编写中断函数,标准库给出了控制USART1中断的函数USART1_IRQHandler()

//接下来是中断控制

void USART1_IRQHandler(void)//中断函数
{
	if(USART_GetFlagStatus(USART1,USART_IT_RXNE)==SET)//判断标志位
	{
		Serial_RxData=USART_ReceiveData(USART1);//读取PC发来的数据
		Serial_RxFlag=1;//标志位置1
		
		USART_ClearITPendingBit(USART1,USART_IT_RXNE);//清除中断函数标志位
	}
}
uint8_t Serial_GetRxFlag(void)//读后自动清除
{
	if(Serial_RxFlag==1)
	{
		Serial_RxFlag=0;
		return 1;
	}
	else return 0;
	
}
uint8_t Serial_GetRxData(void)//将中断读取的值返回到主函数中
{
	return Serial_RxData;
	
}

中断部分分为三个函数,中断函数,清除标志位函数以及读取值返回函数。当单片机接收到电脑发送的数据后,触发中断函数,利用标准库所提供的 USART_ReceiveData(USART1)接收数据,并将中断标志位置1,读后调用清除函数清除标志位,并将数据返回到主函数中。

4.3 主函数代码

我们在头文件中加入我们新编写的函数

#ifndef __SERIAL_H
#define __SERIAL_H
#include "stm32f10x.h"                  // Device header

void Serial_Init(void);
void Serial_SendByte(uint8_t Byte);
void Serial_SendArray(uint8_t *Array,uint16_t Length);
void Serial_SendString(char *String);
uint8_t Serial_GetRxFlag(void);//读后自动清除
uint8_t Serial_GetRxData(void);//将中断读取的值返回到主函数中
#endif

随后在主函数中编写以下代码,附带每行代码的解释

#include "stm32f10x.h"                  // Device header
#include "Delay.h"
//#include "OLED.h"
#include "Serial.h"

int main(void)
{
//	OLED_Init();
	Serial_Init();
  uint8_t RxData1;//定义接收变量,用于接收从PC端发来的数据
  uint16_t flag=0;//定义标志位,用于打断或开启发送
		while (1)
	{
		if (Serial_GetRxFlag() == 1)
		{
			RxData1 = Serial_GetRxData();//接收数据
		if(RxData1=='*')
		{
				flag=1;//标志位置1
			while(flag==1)//开始发送
			{
				Serial_SendString("Hello,Windows!");
				Serial_SendString("\r\n");
//				OLED_ShowString(1, 1, "Hello,Windows!");
				Delay_ms(1000);
				
				RxData1 = Serial_GetRxData();//再读取一次接收端数据
				if(RxData1=='#')//若为’#‘,跳出循环
				{
					flag=0;//标志位变为0
				}
				
			}	
			
		}
		else if(RxData1=='#')//输入#,单片机停止发送
		{
			Serial_SendString("Stop!!! Press '*' to continue!");
//			OLED_ShowString(1, 1, "                  ");
//			OLED_ShowString(1, 1, "Stop!!!");
			Serial_SendString("\r\n");
		}
		else //输入其他字符,显示错误
		{
			Serial_SendString("Wrong!!!");
//			OLED_ShowString(1, 1, "                  ");
//			OLED_ShowString(1, 1, "Wrong!!!");
			Serial_SendString("\r\n");
		}
		}
	}
}

4.4 烧录调试结果

经过烧录,串口输出情况如下:

img

五、总结

在STM32的串口通信中,USART是使用最普遍的外部串口通信模式,通过CH340上的TXD与RXD与单片机上的PA10与PA9两个引脚相连接,实现单片机与电脑端的实时通信。USART串口通信可以实现延时自动发送,同时也可以利用中断函数对通信进行控制。通过本次程序的编写,让我对串口通信有了基本的了解。对USART配置,标准库相关函数有了很深的认识,拓展了很多很多的方法不断对代码进行优化,让我对嵌入式编程又增加了兴趣

六、参考

[1]. [9-3] 串口发送&串口发送+接收_哔哩哔哩_bilibili

  • 1
    点赞
  • 17
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
当然可以!下面是一个基于STM32标准库的示例代码,演示如何使用串口通信与RFID模块进行交互。 ```c #include "stm32f4xx.h" #include "stdio.h" USART_InitTypeDef USART_InitStruct; void GPIO_Config(void); void USART_Config(void); void USART_SendString(char* str); int main(void) { GPIO_Config(); USART_Config(); while (1) { if(USART_GetFlagStatus(USART2, USART_FLAG_RXNE) == SET) { char data = USART_ReceiveData(USART2); // 读取RFID模块发送的数据 printf("Received RFID data: %c\r\n", data); // 在这里可以根据接收到的数据进行相应的处理逻辑 } } } void GPIO_Config(void) { RCC_AHB1PeriphClockCmd(RCC_AHB1Periph_GPIOA, ENABLE); GPIO_InitTypeDef GPIO_InitStruct; GPIO_InitStruct.GPIO_Pin = GPIO_Pin_2 | GPIO_Pin_3; GPIO_InitStruct.GPIO_Mode = GPIO_Mode_AF; GPIO_InitStruct.GPIO_OType = GPIO_OType_PP; GPIO_InitStruct.GPIO_Speed = GPIO_Speed_50MHz; GPIO_InitStruct.GPIO_PuPd = GPIO_PuPd_UP; GPIO_Init(GPIOA, &GPIO_InitStruct); GPIO_PinAFConfig(GPIOA, GPIO_PinSource2, GPIO_AF_USART2); GPIO_PinAFConfig(GPIOA, GPIO_PinSource3, GPIO_AF_USART2); } void USART_Config(void) { RCC_APB1PeriphClockCmd(RCC_APB1Periph_USART2, ENABLE); USART_StructInit(&USART_InitStruct); USART_InitStruct.USART_BaudRate = 9600; USART_InitStruct.USART_Mode = USART_Mode_Rx; USART_Init(USART2, &USART_InitStruct); USART_Cmd(USART2, ENABLE); } void USART_SendString(char* str) { while (*str) { while (USART_GetFlagStatus(USART2, USART_FLAG_TXE) == RESET); USART_SendData(USART2, *str++); } } ``` 上述代码使用了STM32标准库函数来进行串口通信,并通过USART2与RFID模块进行数据交互。在 `main()` 函数中,我们首先调用 `GPIO_Config()` 函数来配置GPIO引脚,然后调用 `USART_Config()` 函数来配置USART2。在主循环中,我们使用 `USART_GetFlagStatus()` 来检查是否有数据可读取,如果有,则使用 `USART_ReceiveData()` 函数读取数据,并通过串口打印到终端。你可以根据实际需求,在读取到数据后添加相应的处理逻辑。 请注意,具体的引脚和波特率等参数可能需要根据你所使用的STM32型号和RFID模块的规格进行调整。此外,还需要根据你的工程配置适配并初始化其他相关的外设和中断处理函数。 希望这段代码能对你有所帮助!如果有任何问题,请随时提问。

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值