基于STM32F407的串口通信

串口通信

串口作为 MCU 的重要外部接口,同时也是软件开发重要的调试手段,其重要性不言而喻。现在基本上所有的 MCU 都会带有串口,STM32 自然也不例外。通过串口,我们可以实现多设备间的单双向通信。

本文主要讲解对正点原子的串口通信实验中串口中断的通信协议的理解与应用,关于串口的初始化配置等这里略过不讲。

在正式开始之前,先把涉及到的寄存器讲一下:

状态寄存器 (USART_SR)

状态寄存器的[9:0]为非保留位,这里我们主要了解其[7:5]位。
状态寄存器 (USART_SR)
当要发送数据时,TDR寄存器将存放着的待发送的数据传输到移位寄存器,当TDR的数据全部移位到移位寄存器,TXEIE置1,表明数据传输到移位寄存器。

当传送数据完成时,TC位置1。

当要接收数据时,移位寄存器把数据传输到DR寄存器中,当传输完成后,RXNE位置1,表明数据已完全传输到DR寄存器中,已准备好随时被读取。

[7:5]位

数据寄存器 (USART_DR)

串口通信中,STM32F4有一个专门的数据寄存器用于存放数据,该寄存器的[8:0]位(共9位)为有效数据位,用于存放数据值。( 数据寄存器包含两个寄存器,一个用于发送 (TDR),一个用于接收 (RDR) ) 而我们知道一个字节是一个8位长的数据单位,可用一个字节表示一个字符、数字或其他字符,或者系列二进制位。

于是我们得到这样一个结论,串口通信中寄存器每一次通过USART_SendData或USART_ReceiveData函数可以发送或接收一个字节的数据。
数据寄存器(USART_DR)

接收大量数据

由于上面提到的,串口通信的数据寄存器每次只能存放一个字节的数据,这对我们来说显然是不够用的。为了扩大接收数据的容量,首先自然而然想到的是新建一个数组变量,用于缓存数据。

正点原子定义了一个u8型的数组变量 USART_RX_BUF ,数组的大小为USART_REC_LEN,可根据用户需要自行定义,我们在usart.h里宏定义其值为200,即我们的数据缓存数组最多只能存放200个字节的数据。

根据前文提到的状态寄存器,我们知道每次接收完数据后,RXNE位会置1,表明随时可读取接收到的数据。(同时,如果 USART_CR1 寄存器中 RXNEIE = 1,则会生成中断。)

于是,为了一次获取足够多的数据,我们有这样一个思路:在配置USART时我们开启USART_IT_RXNE中断,当每次中断(表明一次数据接收完成,得到一个字节的数据)发生后,将DR寄存器中的数据读取出来,存放到USART_RX_BUF数组中。这样,当我们通过串口发送大量数据时,通过多次中断,我们就能把数据依次存放到数组中,最后,我们再把数组中的数据遍历输出,即为我们要接收的数据。

那么紧接而来的有两个问题:一、我们需要一个指针不断进行移位,使得每次我们从DR寄存器get到的数据都能存放在USART_RX_BUF数组中的相应位置而不相互覆盖。二、我们需要一些‘标志’用来判断我们的数据是否接收失败、何时接收完毕。

正点原子是这样解决的:建立一个USART_RX_STA的u16类型的变量,用来作为接收状态的标记量。该变量的低14位(即[13:0]位)用来表示接收到有效数据的个数。每从DR寄存器接收到一个数据并存放进USART_RX_BUF数组后,USART_RX_STA的值加一。(注意,表示的是数据的个数,并不是数据本身)。同时,用USART_RX_STA变量的次高位和最高位用来表示接受状态,分别表示“即将接收完成”和“已经接收完成”。即,当USART_RX_STA的次高位置1时,表明即将接收完成。当USART_RX_STA的最高位置1时,表明数据已经传输完成,已经把所有数据都存放到USART_RX_BUF中。

那么,如何判断我们的数据“即将接收完成”和“已经接收完成”呢?我们可以通过自行定义这么一个简单的协议:发送的数据均以‘OK’为末尾作为结束。

这样,每次判断从DR获取的数据,如果接收到‘O’,则表明“即将接收完成”,次高位置1,在此基础上,如果再接收到‘K’,也就是说在次高位为1的情况下,接收到数据‘K’,则表明接收完成。如果接收到的既不是‘O’也不是‘K’,则表明介绍到的是除末尾‘OK’以外的正常数据,则把它存放进USART_RX_BUF数组中,并使USART_RX_STA的值加一。

通过这些操作,我们就能实现接收大量数据的功能了。

下面我们来具体看一下代码上如何实现:

	if(USART_GetITStatus(USART1, USART_IT_RXNE) != RESET)  
	{
		//读取DR接收到的数据
		Res = USART_ReceiveData(USART1);	

		/*0x8000----1000000000000000----*/
		if((USART_RX_STA&0x8000)==0)			//最高位不为1,则接收未完成
		{
			//次高位为1,表明上次中断已经接收到倒数第二个数据'O',
			//则接下来判断这次接收到的数据是不是'K',即是否符合最后一个数据的要求
			if(USART_RX_STA&0x4000)			/*0x4000----0100000000000000----*/			
			{
				//如果接收完'O'后接收到的不是'K',
				//则说明接收错误,重新开始
				if(Res!='K')USART_RX_STA=0;
				
				//如果接收完'O'后接收到的是'K',
				//则说明接收完成了,把相关标志位(最高位,即0x8000)置1
				else USART_RX_STA|=0x8000;		/*0x8000----1000000000000000----*/
			}
			else	//次高位不为1,表明上次中断还没接收到倒数第二个数据'O'
			{
				//那就要判断这次中断是不是收到倒数第二个数据'O',
				//如果是的话,则次高位置1
				if(Res=='O')USART_RX_STA|=0x4000;										
				else		//如果这次中断接收的不是'O',
				//则说明收到的数据是普通的数据内容,那就把它存进buffer里面
				{
					//存储本次接收到的数据内容,
					//USART_RX_STA相当于数组的一个索引值,
					//对于不同索引值,每次把Res的数据存放在不同位置
					USART_RX_BUF[USART_RX_STA&0X3FFF]=Res;
					/*0x3fff----0011111111111111----*/
					USART_RX_STA++;							//索引值加一
					//索引值越界,说明接收数据过长,接收数据错误,重新开始接收
					if(USART_RX_STA>(USART_REC_LEN-1))USART_RX_STA=0;		  
				}
			}
		}
	}

通过这个串口中断函数,我们可以得到大于一个字节的数据。要获取数据,只需要读取USART_RX_BUF里面的内容即可。于是我们可以在main函数里面通过while循环判断USART_RX_STA的最高位来判断是否可读取BUF里的内容,可以的话则对其迭代打出数据。

emmmmm大概就这样。

猜你喜欢

转载自blog.csdn.net/weixin_44692935/article/details/101104522