USART串口(发送和接收)

目录

[一. USART串口协议](#一. USART串口协议)

[二. USART串口外设](#二. USART串口外设)

[三. 串口发送+接收](#三. 串口发送+接收)

[四. 效果展示](#四. 效果展示)


一. USART串口协议

USART(Universal Synchronous/Asynchronous Receiver/Transmitter)通用同步/异步收发器。

通信的目的:将一个设备的数据传送到另一个设备,扩展硬件系统。

通信的协议:制定通信的规则,通信双方按照协议规则进行数据收发。

单片机有了通信功能,就能与众多别的模块互联。总的来说,通信的目的就是进行信息传递,双方约定的规则就是通信协议。通信协议不止一种,在STM32中,就有诸如USART,I2C,SPI,CAN,USB等通信。今天我们主要介绍USART串口通信。

单片机的串口可以使单片机与单片机,单片机与电脑,单片机与各种各样的模块互相通信,极大地扩展了单片机的应用范围,增强了单片机系统的硬件实力。

其中,单片机和电脑通信,是串口的一大优势,可以接电脑屏幕,非常适合调试程序,打印信息。

串口的硬件电路,需要其中一个设备的发送端TX连接另外一个设备的接收端RX,接收端RX连接另一个设备的发送端TX。当只需单向的数据传输时,可以只接一根通信线。

TX和RX都是单端信号,它们的高低电平都是相对于GND的。所以串口通信的TX,RX,GND都是必须要连接的。

串口的参数及时序:

**1)波特率:**串口通信的速率。由于串口一般使用异步通信,所以需要双方约定一个通信速率。它决定了每隔多久发送一位。

**2)起始位:**它是标志一个数据帧的开始,固定为低电平。首先串口的空闲状态是高电平,也就是没有数据传输的时候,引脚必须要置高电平,作为空闲状态。然后需要传输的时候,就必须要先发送一个起始位,这个起始位必须是低电平,来打破空闲状态的高电平,产生一个下降沿。这个下降沿就告诉接收设备,这一帧数据要开始了。

**3)停止位:**在一个字节数据发送完成后,必须要有一个停止位。这个停止位的作用就是用于数据帧间隔,固定为高电平。同时,这个停止位,也是为下一个起始位做准备的。

**4)数据位:**表示数据帧的有效载荷,1为高电平,0为低电平,低位先行。低位先行的意思就是数据先从低位发送。

**5)校验位:**用于数据验证,根据数据位计算得来。校验可以选择3种方式。无校验,奇校验和偶校验。

二. USART串口外设

一般我们串口很少使用同步功能,所以USART和UART使用起来,并没有太大的区别。所以我们学习串口,主要还是异步通信。

USART是STM32内部集成的硬件外设,可根据数据寄存器的一个字节数据自动生成数据帧时序,从TX引脚发送出去,也可自动接收RX引脚的数据帧时序,拼接为一个字节数据,存放在数据寄存器里。

USART外设就是串口通信的硬件支持电路。当我们配置好了USART电路,直接读写数据寄存器,就能自动发送和接收数据了。

举个案例,我们STM32F103C8T6的USART资源就有如下:USART1,USART2,USART3.总共3个独立的USART外设,可以挂载很多串口设备。其中,USART1是APB2总线上的设备,剩下的两个都是APB1总线的设备。

下面我们来了解一下整个USART的框图和结构:

如上所示, 串口的数据寄存器就包括上面的右边部分,发送或接收的字节数据就存在这里。一个是发送数据寄存器TDR(Transmit DR)和接收数据寄存器RDR(Receive DR)。

但其实,这两个数据寄存器都占用一个地址,在程序上只表现为一个寄存器,那就是数据寄存器DR(Data Register)。

当数据从TDR移动到移位寄存器时,会置一个标志位,**叫TXE(TX Empty),发送寄存器空。**我们检查这个标志位,如果置1了,我们就可以在TDR写入下一个数据了。然后发送移位器就会在下面的发送器控制的驱动下,向右移位,然后一位一位地,把数据输出到TX引脚。向右移位,正好和串口协议规定的低位先行,是一致的。

同样的,接收数据的时候,也会置一个标志位,**叫RXNE(RX Not Empty),接收数据寄存器非空。**当我们检测到RXNE置1之后,就可以把数据读走了。当数据从移位寄存器转移到RDR时,就可以直接移位接收下一帧数据了。

如上所示的中间位置,有一个唤醒单元,而这个唤醒单元的作用就是用来实现多设备的功能。

串口一般是点对点模式。

上面的时钟输入一般是fPCLKx(x=1或2)。

USART1挂载在APB2,所以就是PCLK2的时钟,一般是72M.其他的USART都挂载在APB1,所以是PCLK1的时钟,一般是32M.

之后这个时钟进行一个分频,除以一个USARTDIV的分频系数。之后分频完后,还要再除个16,得到发送器时钟和接收器时钟,通向控制部分。

然后右边这里,如果TE(TX Enable)为1,就是发送器使能了,发送部分的波特率就有效。

如果RE(RX Enable)为1,就是接收器使能了,接收部分的波特率就有效。

到这里USART的框图就看完了,剩下的就是一些寄存器的指示,比如各个CR控制寄存器的哪一位控制那一部分电路。这些都可以自己看看手册里的寄存器描述。

最后我们看下USART基本结构:

上图中的最左边这里就是波特率发生器,用于产生约定的通信速率。

时钟来源是PCLK2或1.

三. 串口发送+接收

通过观察引脚定义表,我们可以发现USART1的TX是PA9,RX是PA10.

如下所示:

所以根据产品手册和上文中所介绍的USART串口结构,我们可以总结除如下串口的相关代码配置流程:

1)开启时钟,把需要用到的USART和GPIO的时钟打开

    RCC_APB2PeriphClockCmd(RCC_APB2Periph_USART1, ENABLE);	//开启USART1的时钟
	RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA, ENABLE);	//开启对应的GPIO口

**2)GPIO初始化,**把TX配置成复用功能(因为只有复用功能才能让片上外设模块的复用功能生效),RX配置成输入模式。

    /*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;           //将PA10引脚初始化为上拉输入模式
	GPIO_InitStructure.GPIO_Pin = GPIO_Pin_10;
	GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
	GPIO_Init(GPIOA, &GPIO_InitStructure);					

**3)配置USART,**可以直接使用一个结构体,就可以把相关的所有参数配置好了。

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

4)如果你只需要发送的功能,就直接开启USART,初始化就完成了。如下所示:

USART_Cmd(USART1, ENABLE);

如果你需要接收的功能,可能还需要配置中断,那就在开启USART之前,再加上ITConfig和NVIC的代码就行了。如下所示:

    /*NVIC中断分组*/
	NVIC_PriorityGroupConfig(NVIC_PriorityGroup_2);			//配置NVIC为分组2
	
	/*NVIC配置*/
	NVIC_InitTypeDef NVIC_InitStructure;					//定义结构体变量
	NVIC_InitStructure.NVIC_IRQChannel = USART1_IRQn;		//选择配置NVIC的USART1线
	NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE;			//指定NVIC线路使能
	NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 1;		//指定NVIC线路的抢占优先级为1
	NVIC_InitStructure.NVIC_IRQChannelSubPriority = 1;		//指定NVIC线路的响应优先级为1
	NVIC_Init(&NVIC_InitStructure);						

在上面进行好了USART相关初始化后,就需要编写串口函数了,在启动文件startup_stm32f10x_md.s中找到串口的名称,如下所示:

void USART1_IRQHandler(void)
{
	if (USART_GetITStatus(USART1, USART_IT_RXNE) == SET)		//判断是否是USART1的接收事件触发的中断
	{
		Serial_RxData = USART_ReceiveData(USART1);				//读取数据寄存器,存放在接收的数据变量
		Serial_RxFlag = 1;										//置接收标志位变量为1
		USART_ClearITPendingBit(USART1, USART_IT_RXNE);			//清除USART1的RXNE标志位
																//读取数据寄存器会自动清除此标志位
																//如果已经读取了数据寄存器,也可以不执行此代码
	}
}

我们就可以编写程序来测试下了。这里例举两个测试案例:

#include "Serial.h"

uint8_t RxData;			//定义用于接收串口数据的变量

int main(void)
{
	/*模块初始化*/
	OLED_Init();		//OLED初始化
	
	/*显示静态字符串*/
	OLED_ShowString(1, 1, "RxData:");
	
	/*串口初始化*/
	Serial_Init();		//串口初始化
	
	while (1)
	{
		if (Serial_GetRxFlag() == 1)			//检查串口接收数据的标志位
		{
			RxData = Serial_GetRxData();		//获取串口接收的数据
			Serial_SendByte(RxData);			//串口将收到的数据回传回去,用于测试
			OLED_ShowHexNum(1, 8, RxData, 2);	//显示串口接收的数据
		}
	}
}

四. 效果展示

在这里我们使用某一位b站博主自己做的串口助手,并对它设置好波特率为9600,数据位为8,停止位为1等参数,然后我们发送一个数23,如下所示:

然后我们再来看下我们的OLED调试屏幕:

如上所示,屏幕上也显示出了所接收到数据23.

好了,到这里所有的内容差不多已经全部讲完,如果有任何疑问或者问题都可以在下面进行留言或者私信。

相关推荐
LightningJie8 分钟前
STM32的hal库在实现延时函数(例如:Delay_ms 等)为什么用滴答定时(Systick)而不是定时器定时中断,也不是RTC?
stm32·单片机·实时音视频
光明中黑暗19 分钟前
Python 学习笔记
笔记·python·学习
神一样的老师20 分钟前
物联网中物理与逻辑设计的综述:设备、协议、通信模型和API的比较分析
物联网
聪明的墨菲特i24 分钟前
VUE组件学习 | 六、v-if, v-else-if, v-else组件
前端·vue.js·学习·前端框架·vue
ChoSeitaku39 分钟前
链表|反转链表|移除链表元素|链表的中间节点|返回倒数第k个节点|合并两个有序链表(C)
c语言·数据结构·链表
czme1 小时前
C语言数组
c语言
金子总会发光的1231 小时前
HTTP相关返回值异常原因分析,第二部分
嵌入式硬件·物联网·http·硬件工程
月巴月巴白勺合鸟月半2 小时前
使用C#学习Office文件的处理(pptx docx xlsx)
学习·microsoft·c#·excel·office
YuCaiH2 小时前
【STM32】LED闪烁 & LED流水灯 & 蜂鸣器
笔记·stm32·单片机·嵌入式硬件
路过羊圈的狼2 小时前
STM32CUBEMX安富莱STM32-V6开发板使用FMC驱动SDRAM芯片MT48LC4M32B2TG
stm32·单片机·嵌入式硬件