STM32学习笔记---中断篇

目录

一、什么是中断

1、中断机制执行过程

2、中断的控制系统 NVIC

2.1 NVIC是什么

2.2 NVIC内部结构

2.3 中断协作模型

3、中断服务函数跟平时调用的函数的区别

4、什么时候用中断

二、如何配置中断

1、中断优先级

1.1 优先级设置

1.2 优先级分组

2、中断配置函数

3、中断服务函数

三、具体使用中断

需求1:串口接收字符控制蜂鸣器

需求2: 串口接收字符串控制流水灯

需求3:PC通过串口发送命令控制流水灯的开关和调速

需求4:开发板1控制开发板2的流水灯

需求5:PC通过串口发送命令控制两个板子的流水灯


本篇主要以串口中断来展开学习 

一、什么是中断

1、中断机制执行过程

        当CPU执行主程序时,接收到处理紧急事件的请求,CPU中断当前操作,去执行紧急事件(就是中断服务函数内的程序),执行完紧急事件后再返回到主程序继续执行。

2、中断的控制系统 NVIC

2.1 NVIC是什么

        首先NVIC的全称是中断嵌套向量控制器,位于M4内核中,属于核心外设。NVIC的作用:监控紧急事件是否发生、判断紧急事件的优先级、调度内核执行紧急事件

2.2 NVIC内部结构

嵌套向量中断控制器 NVIC具有82个中断源

中断仲裁主要解决的问题是多个中断同时发生时,确定哪个中断应优先处理。中断仲裁的核心在于中断的优先级设置,高优先级的中断能够在低优先级的中断之前被处理,从而确保了系统对紧急情况的快速响应和处理。

STM32的中断仲裁机制包括以下几点:

‌中断优先级‌:STM32支持设置不同的中断优先级,允许用户根据需要调整各个中断的优先级。
‌中断嵌套‌:如果一个高优先级的中断发生,它会立即打断当前正在处理的中断(如果其优先级较低),并首先处理这个高优先级的中断,这就是所谓的中断嵌套。
‌中断执行流程‌:当中断发生时,STM32的执行流程包括外设发出中断请求、处理器暂停当前执行的任务、跳转到对应的中断服务程序(ISR)并执行、恢复现场后返回到被中断的位置继续执行等步骤。

2.3 中断协作模型

片上外设产生中断源,除了GPIO

3、中断服务函数跟平时调用的函数的区别

        首先,中断服务函数不需要调用;其次,中断服务函数跟普通函数执行的程序点不同,普通函数需要CPU执行到此函数调用的位置才能执行相应的功能,执行程序的地点是固定的;而中断服务函数,无论CPU执行到哪里,只要有紧急事件发生就会中断当前操作去执行中断服务函数,执行程序的地点是随时,不固定的。

4、什么时候用中断

        首先,需要有特定的条件触发紧急事件,并且紧急事件不需要一直运行能瞬间完成此事件;最后,还需要实时的响应,需要的时候触发,不需要的时候不触发

二、如何配置中断

1、中断优先级

优先级分类:

抢占优先级:   通过设置优先级寄存器设置对应的值 //程序设置

响应优先级:   通过设置优先级寄存器设置对应的值 //程序设置

自然优先级:   是固定,不能通过软件进行配置;   

说明:优先级的级别值越小,优先级越高。

1.1 优先级设置

在CM4中的NVIC控制器,每一个中断源都分配了一个8位的寄存器来放置该中断的优先级(抢占、响应)

注意:在STM32中只用到其中的高四位,低四位为固定值。

高四位分为两部分,一部分用来设置中断源的抢占优先级,另一部分用来设置中断源的响应优先级

具体抢占优先级和响应优先级各占用多少位--->需要根据设置的优先级分组决定

1.2 优先级分组

在地址为0xE000ED0C这个寄存器的 10~8 位设置优先级分组(应用程序中断及复位控制 寄存器(AIRCR))

通过设置这个值来确定-->设置优先级寄存器的高四位中抢占优先级占几位,响应优先级占几位

精简ST芯片分组表:

    写入值/分组号  占先位数  次级位数   占先取值范围   次级取值范围

                  3                4            0            0~15                   0

                  4                3             1            0~7                    0~1

                  5                2             2            0~3                    0~3         

                  6                1              3            0~1                    0~7

                  7                0              4             0                       0~15

串口中断过程:

2、中断配置函数

        由于NVIC属于CM4内核级的外设,所有芯片厂家在寄存器上是完全相同的(配置方式,作用),所以ARM公司提供了一份通用的NVIC配置函数。

配置NVIC所需要的函数

NVIC_SetPriorityGrouping(uint32_t PriorityGroup);//通过对应的寄存器的 8~10 位写值(7 6 5 4 3---分组号)--->设置优先级分组

u32 pri = NVIC_EncodePriority (uint32_t PriorityGroup, uint32_t PreemptPriority, uint32_t SubPriority);//计算优先级编码值,设置抢占和响应的级别值

NVIC_SetPriority(IRQn_Type IRQn, uint32_t priority);//设置具体某个中断源的优先级

NVIC_EnableIRQ(IRQn_Type IRQn);//NVIC模块响应片上外设中断源的开关--->使能NVIC响应通道

3、中断服务函数

进入中断服务函数的条件:

  1. 状态寄存器SR的第5位/第4位置1(接收中断与空闲中断)
  2. 中断使能打开

分析:

void 中断服务函数(void)

{

//紧急事件

}

由于状态寄存器SR的第5位/第4位一直置1,所以一直执行中断服务函数,故需要清除中断标志位(状态寄存器SR的第5位/第4位清0)

所以得到以下框架:

void 中断服务函数(void)

{

//清除中断标志位

//紧急事件

}

又由于每个中断源下面有多个中断信号可以触发,触发了都会进入同一个中断服务函数

但是每个中断信号触发执行紧急事件是不一样的,所以要进入中断服务函数后要判断到底是哪个中断信号触发

所以最终得到以下框架:

void 中断服务函数(void)

{

        //判断某个中断信号触发

        {

        //清除中断标志位

        //紧急事件

        }

}

三、具体使用中断

需求1:串口接收字符控制蜂鸣器

流水灯运行,随时可以通过串口接收字符,控制蜂鸣器。O 开   N 关闭

分析:

打开接收中断,关闭空闲中断

/*
函数名: USART1_IRQHandler
函数功能:中断服务函数
返回值:void
形参:void
函数说明:

*/
u8 data;
void USART1_IRQHandler(void)
{
	
	//是否产生接收中断
	if(USART1->SR & (1 << 5))
	{
		//清除接收中断的标志位
		USART1->SR &= ~(1 << 5);
		//紧急事件
		data = USART1->DR;
		if(data == 'O')
		{
			LED_ON;
			
		}
		else if(data == 'N')
		{
			LED_OFF;
		}
	}
	
}

需求2: 串口接收字符串控制流水灯

流水灯运行,随时可以通过串口接收字符串,控制蜂鸣器。open 开   close 关

分析:

打开接收中断和空闲中断

中断服务函数:

NVIC.h

#ifndef _NVIC_H
#define _NVIC_H

#include "stm32f4xx.h"

typedef struct usart1_rec
{
	u8 usart1_buff[50];//接收字符串
	u8 usart1_idle_flag;//空闲中断接收字符串完毕标志位
	u8 len;//数组索引值
	
}USART1_REC;

extern USART1_REC usart1_val;

#endif 

NVIC.c

/*
函数名: USART1_IRQHandler
函数功能:中断服务函数
返回值:void
形参:void
函数说明:

*/
USART1_REC usart1_val;//接收数据结构体变量
void USART1_IRQHandler(void)
{
	//是否产生接收中断
	if(USART1->SR & (1 << 5))
	{
		//清除接收中断的标志位
		USART1->SR &= ~(1 << 5);
		//紧急事件     以一字节的方式接受,接收完后就到下一次中断
		usart1_val.usart1_buff[usart1_val.len] = USART1->DR;
		usart1_val.len++;
		
	}
	//是否产生空闲中断
	if(USART1->SR & (1 << 4))
	{
		
		//清除空闲标志位
		USART1->SR;
		USART1->DR;
		//紧急事件
		usart1_val.usart1_buff[usart1_val.len] = '\0';
		usart1_val.len = 0;
		//字符串接收完毕
		 usart1_val.usart1_idle_flag = 1;
	}
}

主函数:

u8 led_flag = 0;
u8 speed = 5;
while(1)
	{
		//字符串接收完毕
		if(usart1_val.usart1_idle_flag  == 1)
		{
			usart1_val.usart1_idle_flag = 0;//为下次再次接收字符串
			if(strcmp((const char*)usart1_val.usart1_buff,"Open") == 0)   //存在BUG Open123也会亮   解决方案:空闲中断内设置标志位
			{
				led_flag = 1;
			}
			else if(strcmp((const char*)usart1_val.usart1_buff,"Close") == 0)
			{
				led_flag = 0;
				LED_OFF;
			}
		 }
			if(led_flag == 1)
			{
				LED_flash(speed);
			}
		
	 }

为什么要在空闲中断中设置接收字符串完成标志位

为了防止接收到其他字符也会打开或关闭流水灯。比如:发送open123流水灯一定概率能运行,为什么?因为每进一次中断接收一个字符,直到接收到open后,又因为全局变量在open后面添加‘\0’从而形成了一个完整的字符串,所以还没接收到后面的“123”,流水灯就能运行了。这是个小BUG,只要加在空闲中断中设置接收字符串完成标志位就能解决了

需求3:PC通过串口发送命令控制流水灯的开关和调速

open  开

close 关

sp1~9

按键1控制蜂鸣器开关

分析:

打开接收中断和空闲中断

//字符串接收完毕
		if(usart1_val.usart1_idle_flag  == 1)
		{
			usart1_val.usart1_idle_flag = 0;//为下次再次接收字符串
			if(strcmp((const char*)usart1_val.usart1_buff,"Open") == 0) 
			{
				led_flag = 1;
			}
			else if(strcmp((const char*)usart1_val.usart1_buff,"Close") == 0)
			{
				led_flag = 0;
				LED_OFF;
			}
			//方法1:strcmp函数先把SP这两个字符限制住,再比较字符1~9,最后再限制住字符串的            
                     长度从而达到除了SP1~9以外的也能调速的Bug
			else if(strcmp((char *)usart1_val.usart1_buff,"SP1") >= 0 && strcmp((char *)usart1_val.usart1_buff,"SP9") <= 0  && strlen((char *)usart1_val.usart1_buff) == 3 )
			{
					speed = usart1_val.usart1_buff[2] - 48;
			}
			//方法2:strncmp函数
//			else if(strlen((char *)usart1_val.usart1_buff) == 3 && strncmp((char *)usart1_val.usart1_buff,"SP",2) == 0)//限制长度是为了防止除了SP1~9以外的也能调速
//			{
//				if(usart1_val.usart1_buff[2] >= '1' && usart1_val.usart1_buff[2] <= '9')
//				{
//					speed = usart1_val.usart1_buff[2] - 48;
//				}
//	   	}
		
		 }
			if(led_flag == 1)
			{
				LED_flash(speed);
			}
		
	 }

需求4:开发板1控制开发板2的流水灯

按键1       板2的流水灯开

按键2       板2的流水灯关

按键3       板2的流水灯加速

按键4       板2的流水灯减速

分析:

打开接收中断,关闭空闲中断

用USART2通信

PA2-------Tx

PA3-------Rx

#ifdef Send  //发送板
		Keynum = Key_scan();
		if(Keynum != 0xff)
		{
			Usart2_sendbyte(Keynum);
		}
		
		#else  //接收板
		switch(usart2_recdata)
		{
				case 1:flag = 1;break;//按键1打开流水灯
				case 2:flag = 0;LED_OFF;break;//按键2关闭流水灯
				case 3:if(flag){usart2_recdata = 0;speed -= 1;if(speed < 1)  speed = 5;}break;//按键3加速  usart2_recdata = 0为了只执行一次这个选择
				case 4:if(flag){usart2_recdata = 0;speed += 1;if(speed > 10) speed = 5;}break;//按键4减速
		}
		if(flag == 1)
		{
				LED_flash(speed);
		}
		
		#endif
		

注意:需要设定接收字符完成标志位,要不然速度一减到底或者一加到顶

需求5:PC通过串口发送命令控制两个板子的流水灯

PC发送open_1   板1的流水灯运行

PC发送open_2   板2的流水灯运行

PC发送close_1   板1的流水灯关闭

PC发送close_2   板2的流水灯关闭

分析:

打开接收中断和空闲中断

        PC通过串口1发送给板1,如果是所需的字符串则实现相应的功能,如果不是则板1通过串口2发给板2;板2用串口2接收到字符串,如果是所需的字符串则实现相应的功能,如果不是则不实现相应的功能

while(1)
	{
		#ifdef send
		//发送板
		if(usart1_val.usart_idle_flag == 1)
		{
			usart1_val.usart_idle_flag = 0;
			if(strcmp((const char *)usart1_val.usart_buff ,"open_1") == 0)
			{
				led_flag = 1;
			}
			else if(strcmp((const char *)usart1_val.usart_buff ,"close_1") == 0)
			{
				led_flag = 0;
				LED_OFF;
			}
			else
			{
				Usart2_sendstr(usart1_val.usart_buff);
			}
				
		}
		
		
		#else
		if(usart2_val.usart_idle_flag == 1)
		{
			usart2_val.usart_idle_flag = 0;
			if(strcmp((const char *)usart2_val.usart_buff ,"open_2") == 0)
			{
				led_flag = 1;
			}
			else if(strcmp((const char *)usart2_val.usart_buff ,"close_2") == 0)
			{
				led_flag = 0;
				LED_OFF;
			}
		
		
		}
		#endif
		
		if(led_flag)
		{
			LED_flash(1);
			
		}
		
	}

总结:字符只需要用到接收中断,别打开空闲中断

          字符串需要用到接收中断和空闲中断(添加个’\0’为了形成一个完整的字符串)

          在使用之前想清楚需要打开哪个串口的接收和空闲中断

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值