学习笔记——stm32串口中断函数的逻辑理解

这片文章主要是讲解原子给的代码里面的串口中断的中断函数,前面是我个人的学习路径。

关于stm32串口的学习,主要分为以下几个点
1.USART的功能和内部结构。
功能包括同步、异步模式、双工通信、半工通信等
内部结构,参考这张图片,去把各个部分了解清楚就差不多了。
在这里插入图片描述

2.USART的相关寄存器,看一看有个映像就可以了,主要了解一下这些寄存器对应上面的图,设置哪一部分的就ok了。
在这里插入图片描述
3.USART的收发格式,波特率的设置,了解硬件流控制。
4.USART的中断请求与模式配置,这个比较重要,要认真看一下,因为你要根据这个来配置串口发生什么中断。
下面附一篇文章,供大家参考学习,文章的博主还是讲得挺详细的。
添加链接描述
剩下的,再翻一下参考手册,应该没什么问题。
再附上一张USART的引脚图在这里插入图片描述

这是我学习的路径。

基本上stm32的串口配置都差不多,基本上都是直接参考的原子或者野火的代码,但是关于原子给的串口中断函数的理解,我找了找,基本没找到什么比较详细的文章。
我也是花了一点点时间才搞清楚这段代码的逻辑。

串口配置的代码直接用原子的,如果你已经很熟悉配置,可以直接跳过看后面串口中断函数解释。

头文件

#define USART1_REC_LEN		200  	//定义最大接收字节数 200
extern u8  USART1_RX_BUF[USART1_REC_LEN]; //接收缓冲,最大USART_REC_LEN个字节.末字节为换行符 
extern u16 USART1_RX_STA;         		//接收状态标记
void USART1_Init(u32 bound);

配置代码

/*******************************************************************************
* 函 数 名         : USART1_Init
* 函数功能		   : USART1初始化函数
* 输    入         : bound:波特率
* 输    出         : 无
*******************************************************************************/ 
void USART1_Init(u32 bound)
{
   //GPIO端口设置
	GPIO_InitTypeDef GPIO_InitStructure;
	USART_InitTypeDef USART_InitStructure;
	NVIC_InitTypeDef NVIC_InitStructure;
	
	RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA,ENABLE);
	RCC_APB2PeriphClockCmd(RCC_APB2Periph_USART1,ENABLE);
 
	
	/*  配置GPIO的模式和IO口 */
	GPIO_InitStructure.GPIO_Pin=GPIO_Pin_9;//TX			   //串口输出PA9
	GPIO_InitStructure.GPIO_Speed=GPIO_Speed_50MHz;
	GPIO_InitStructure.GPIO_Mode=GPIO_Mode_AF_PP;	    //复用推挽输出
	GPIO_Init(GPIOA,&GPIO_InitStructure);  /* 初始化串口输入IO */
	GPIO_InitStructure.GPIO_Pin=GPIO_Pin_10;//RX			 //串口输入PA10
	GPIO_InitStructure.GPIO_Mode=GPIO_Mode_IN_FLOATING;		  //浮空输入
	GPIO_Init(GPIOA,&GPIO_InitStructure); /* 初始化GPIO */
	

	//USART1 初始化设置
	USART_InitStructure.USART_BaudRate = bound;//波特率设置
	USART_InitStructure.USART_WordLength = USART_WordLength_8b;//字长为8位数据格式
	USART_InitStructure.USART_StopBits = USART_StopBits_1;//一个停止位
	USART_InitStructure.USART_Parity = USART_Parity_No;//无奇偶校验位
	USART_InitStructure.USART_HardwareFlowControl = USART_HardwareFlowControl_None;//无硬件数据流控制
	USART_InitStructure.USART_Mode = USART_Mode_Rx | USART_Mode_Tx;	//收发模式
	USART_Init(USART1, &USART_InitStructure); //初始化串口1
	
	USART_Cmd(USART1, ENABLE);  //使能串口1 
	
	USART_ClearFlag(USART1, USART_FLAG_TC);
		
	USART_ITConfig(USART1, USART_IT_RXNE, ENABLE);//开启接收中断

	//Usart1 NVIC 配置
	NVIC_InitStructure.NVIC_IRQChannel = USART1_IRQn;//串口1中断通道
	NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority=3;//抢占优先级3
	NVIC_InitStructure.NVIC_IRQChannelSubPriority =3;		//子优先级3
	NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE;			//IRQ通道使能
	NVIC_Init(&NVIC_InitStructure);	//根据指定的参数初始化VIC寄存器、	
}

如果其中的一些函数不懂,查固件库手册就好了,固件库中有函数详细的说明。
上面的配置代码中强调一点

USART_ITConfig(USART1, USART_IT_RXNE, ENABLE);//开启接收中断

这行代码,这个串口中断函数第二个参数,要仔细去查一下固件库手册,它还有其他的参数,可以不配置成接收中断。

下面是中断函数
首先强调一点0x0d和0x0a在ASCII码表里面表示\r和\n,分别表示回车和换行


/*******************************************************************************
* 函 数 名         : USART1_IRQHandler
* 函数功能		   : USART1中断函数
* 输    入         : 无
* 输    出         : 无
*******************************************************************************/ 
//0x0d和0x0a在ASCII码表里面表示\r和\n,分别表示回车和换行
void USART1_IRQHandler(void)                	//串口1中断服务程序
{
1	u8 r;
2	if(USART_GetITStatus(USART1, USART_IT_RXNE) != RESET)  //接收中断
3	{
4		r =USART_ReceiveData(USART1);//(USART1->DR);	//读取接收到的数据
5		if((USART1_RX_STA&0x8000)==0)//接收未完成
6		{
7			if(USART1_RX_STA&0x4000)//接收到了0x0d
8			{
9				if(r!=0x0a)USART1_RX_STA=0;//接收错误,重新开始
10				else USART1_RX_STA|=0x8000;	//接收完成了 
11			}
12			else //接收到0X0D之前或0x0D
13			{	
14				if(r==0x0d)USART1_RX_STA|=0x4000;
15				else
16				{
17					USART1_RX_BUF[USART1_RX_STA&0X3FFF]=r;
18					USART1_RX_STA++;
19					if(USART1_RX_STA>(USART1_REC_LEN-1))USART1_RX_STA=0;//接收数据错误,重新开始接收	  
				}		 
			}
		}   		 
	} 
} 	

为了方便讲解,我在每行代码前加上了序号,移植的时候去掉就可以了。
其中
USART1_RX_BUF[],USART1_RX_STA在头文件中定义过了

#define USART1_REC_LEN		200  	//定义最大接收字节数 200
extern u8  USART1_RX_BUF[USART1_REC_LEN]; //接收缓冲,最大USART_REC_LEN个字节.末字节为换行符 
extern u16 USART1_RX_STA;         		//接收状态标记

先说说这段代码的整体设计思路,这段代码是定义了一个协议,你所向单片机发送的字符必须以“/r/n”结尾(关于/r和/n,文章最后有补充说明),然后在中断函数里面通过判断\r和\n的ascll值使定义的 USART1_RX_STA 这个“状态寄存器”的相应位置1,然后在主函数里面判断 USART1_RX_STA这个“状态寄存器”的标志位的值,来发送数据。

看完之后是不是觉得很神奇,USART1_RX_STA明明是定义的一个变量,怎么就变成“寄存器了”,这就是设计师奇妙的设计,定义的USART1_RX_STA是两个字节的变量,有16个位可以用,但是我们的最大接收字节只要200个,就算有1000个字节我们也用不到2的15次方的大小,字节再多stm32就没这么大的存储空间了,所以200个字节已经足够我们一次接收的数据了。显然16个位我们是用不完的,所以我们可以把USART1_RX_STA的高位设置成状态标志位,低位用来计数。如图所示
在这里插入图片描述
图中可以看到,0-13位用来记录字符的长度,高两位作为状态标志位。
下面开始正式讲解代码。

1.当接收到数据时,会产生接收中断,进入中断函数,将接收到的数据存入变量r中,注意,r只占一个字节。

2.代码第5行,判断最高位的状态,USART1_RX_STA最高位是接收到\n(0x0a),才会置1的状态标志位,当它置1时,标志着整个数据已经接收完毕,main中就是判断这个位是否为1来进行发送数据的。很显然,能够进入中断,那说明接收还未完成,所以接下来就是执行第5行下面的代码。

3.第7行是判断USART1_RX_STA次高位,USART1_RX_STA次高位是否置1,这要取决于是否接收到了倒数第2个字符0x0d。

  • 如果上一次接收中断接收到了0x0d,就会把次高位置1,代码第7行检测到了USART1_RX_STA次高位置1后,就会进入if里面(执行代码第9或者第10行),判断数据最后一位是否是0x0a(\n),如果最后一位不是0x0a,那说明这串字符不是按照程序所定义的协议发送给单片机的,单片机自然也就识别不了,这个数据就会被丢掉(代码第9行),USART1_RX_STA清零(包括里面的记录的数据长度和状态标志位),重新接收新的发送过来的数据;如果最后一位是0x0a,那这个字符串就是按照协议发送给单片机的,程序执行第10行,将USART1_RX_STA的最高位置1,标志这串字符串接收完毕。USART1_RX_STA最高位置1后,main函数中就可以检测它进行操作了。
  • 如果上一次接收中断未接收到了0x0d,那么USART1_RX_STA次高位就不会被置1,在程序走到第7行时,检测到USART1_RX_STA次高位为0,程序就会执行第12行的else。

4.程序第12行的else中,要判断接收到的数据是0x0d还是0x0d之前的数据。

  • 如果这个数据是0x0d,那么就将USART1_RX_STA次高位置1,在下一次进入接收中断时,就会执行上述3中的步骤了,程序第7行就能够检测到USART1_RX_STA次高位的状态了。
  • 如果这个数据是0x0d之前的数据,就执行17-19行代码,这三行代码的作用是将这个字节存入USART1_RX_BUF[USART1_REC_LEN]数组(头文件中定义过了)中,记录字符的长度,判断字符长度是否发生溢出(最大200字节)。
  • USART1_RX_BUF[USART1_RX_STA&0X3FFF]=r;说明一下这行代码中的USART1_RX_STA&0X3FFF,USART1_RX_STA的0-13位时用来记录数据长度的,这句话的意思就是去掉了USART1_RX_STA中的高两位状态位,剩下的数据与数组的值相对应。

5.看了上面4点解释脑子里是不是还是很乱,这也就是这段代码难的地方,不能够顺序的去看这个代码,不然还是理不清逻辑,这个串口中断,就相当于一个循环,要判断到接收数据完毕了才会结束这个循环,怎么判断结束是否完毕呢?就看USART1_RX_STA的高两位的状态。下面再来屡屡逻辑。

  • [0] 来了一串数据,假设有50个字符,占50个字节,再加上协议定义的结尾\r\n,一共是52个字节。

  • [1] 当接收到0x0d(\r,也是第51个字符)之前的字符,串口中断函数执行的过程是前4行执行完,第5行条件成立进入if,执行到第7行时,条件不成立,转至12行,此时14行进行判断,这个字符不是0x0d,条件不成立,执行17-19行,将数据存入数组中。

  • [2] 当0x0d前面的数据接收完时,下一个到来的字符是0x0d(\r)时,此时USART1_RX_STA的最高位和次高位都还是0,程序执行到第7行时,条件依然不会成立,程序会跳转置12行的else,此时程序14行的判断成立,会将USART1_RX_STA的次高位置1,然后结束这次中断。

  • [3] 接着上面的步骤,0x0d过了是最后一个字符0x0a(\n),当接收到这个字符时,第5行条件成立,进入if,执行到程序第7行时,由于上一步接收到了0x0d,已经把USART1_RX_STA的次高位置1了,程序就进入if,执行8-11行的代码,判断是否是0x0a,如果不是,执行程序第9行,如果是,执行第10行,将USART1_RX_STA最高位置1,此时整个数据接收就完成了,剩下的交给主函数处理了。

接下来是主函数的处理程序了,把这个串口中断函数理解清楚了,主函数也就好理解了。

int main()
{
	u16 t=0;
	u16 len=0;
	
	NVIC_PriorityGroupConfig(NVIC_PriorityGroup_2);  //中断优先级分组 分2组
	USART1_Init(115200);
	
	while(1)
	{
		if(USART1_RX_STA&0x8000)
		{					   
			len=USART1_RX_STA&0x3fff;//得到此次接收到的数据长度
			for(t=0;t<len;t++)
			{
				USART_SendData(USART1, USART1_RX_BUF[t]);         //向串口1发送数据
				while(USART_GetFlagStatus(USART1,USART_FLAG_TC)!=SET);//等待发送结束
			}
			USART1_RX_STA=0;
		}
		
}

1.主函数while里面,首先判断USART1_RX_STA的最高位,看数据是否接收完毕,再进行操作。
2.len是记下的数据长度,用于for循环中数据发送的次数(因为每次只能发送一个字节)。
3.for循环中的while是用的串口发送完成的标志位来判断的是否发送完毕。
4.最后一定要把USART1_RX_STA置0,方便下次的数据接收,这很重要!!!

补充,关于\r和\n

  • 回车 \r 本义是光标重新回到本行开头,r的英文return,控制字符可以写成CR,即Carriage Return。ASCII码13(0x0d)。
  • 换行 \n 本义是光标往下一行(不一定到下一行行首),n的英文newline,控制字符可以写成LF,即Line Feed。ASCII码10(0x0a)。

Windows系统中,我们键盘上的enter键,按下就是\r\n。所以也是为什么要在串口中定义这个协议。当输入一串字符之后按下回车键,点击发送,可以在右边的框中看到最后两个字符的十六进制码是0x0d和0x0a。
在这里插入图片描述

我问了老师,也可以定义\n\r的协议,但是在Windows中应该不行,键盘上每个字符都有个ascll码,\就有一个ascll码占一个字节,当你输入\n\r时,实际上不是两个字节,是4个字节。
在这里插入图片描述
当然,输入\r\n也不行,只要摁enter键。

最后,总结一下吧,这里面用得最好的就是定义了一个“状态寄存器USART1_RX_STA”,我也是第一次见到,原来变量还可以这么用,在其他的开发板,或者实现其他的功能中都可以用这种方式来优化程序。

  • 48
    点赞
  • 180
    收藏
    觉得还不错? 一键收藏
  • 18
    评论
STM32F4中,串口中断服务函数的命名规则是根据不同的串口编号来确定的。根据引用\[1\]和引用\[2\]的描述,不同的串口有不同的中断服务函数命名。具体命名规则如下: - USART1的中断服务函数命名为USART1_IRQHandler() - USART2的中断服务函数命名为USART2_IRQHandler() - USART3的中断服务函数命名为USART3_IRQHandler() - UART4的中断服务函数命名为UART4_IRQHandler() - UART5的中断服务函数命名为UART5_IRQHandler() - USART6的中断服务函数命名为USART6_IRQHandler() - UART7的中断服务函数命名为UART7_IRQHandler() - UART8的中断服务函数命名为UART8_IRQHandler() 在这些中断服务函数中,可以根据需要编写相应的代码来处理接收到的数据。例如,根据引用\[3\]中的代码示例,可以在中断服务函数中使用USART_GetITStatus()函数来检查接收数据寄存器非空的状态,并使用USART_ReceiveData()函数来读取接收到的数据。然后,可以使用USART_SendData()函数将接收到的数据发送回去。 需要注意的是,以上命名规则是根据STM32F4的标准库来确定的,如果使用其他库或者自定义的命名规则,可能会有所不同。因此,在具体的项目中,需要根据所使用的库和配置来确定正确的中断服务函数命名。 #### 引用[.reference_title] - *1* *3* [串口接收中断配置过程---STM32F4--HAL](https://blog.csdn.net/qq_41683305/article/details/121380867)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v91^control,239^v3^insert_chatgpt"}} ] [.reference_item] - *2* [STM32 串口中断服务函数学习笔记:2)](https://blog.csdn.net/Zao5544/article/details/119456680)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v91^control,239^v3^insert_chatgpt"}} ] [.reference_item] [ .reference_list ]

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值