什么是中断呢?可以举一个日常生活中的例子来说明,假如你正在给看书,电话铃响了。这时,你放下手中的书,记录下你看书的页数,去接电话。通话完毕,再继续从原来的位置继续看书。这个例子就表现了中断及其处理过程:电话铃声使你暂时中止当前的看书的动作,而去处理更为急需处理的事情(接电话),把急需处理的事情处理完毕之后,再回头来继续原来的事情。

在这个例子中,电话铃声称为"中断请求",你暂停看书去接电话叫作"中断响应",接电话的过程就是"中断处理",相应地,在STM32执行程序的过程中,由于出现某个特殊情况(中断异常),使得CPU中止正在执行程序,而转去执行处理中断服务程序,待中断服务程序执行完毕,再返回断点继续执行原来的程序,这个过程称为中断。

1、中断源

中断源也就是导致产生中断的原因,在STM32中打断正常工作(程序)的异常,我们就叫它为中断源。

2、中断执行的过程

  • 主程序正常运行
  • 产生中断异常,CPU保存当前程序执行的位置
  • 进入中断服务函数
  • 处理中断服务函数
  • 中断服务函数处理完成后,返回断点处
  • 程序继续运行

上图中,步骤:3~5,只有在中断异常产生是才会执行,中断异常产生则步骤3~5立即执行。

3、中断的意义

生活中的情景:假设你有一个朋友来拜访你,但是由于不知道何时到达,你只能在大门等待,于是什么事情也干不了。如果在门口装一个门铃,你就不必在门口等待而去干其它的工作,朋友来了按门铃通知你,你这时才中断你的手头上的工作去开门,这样就避免等待和浪费时间。

STM32:串口阻塞接收机制原理是只有接收到一个字节的数据,才继续往下执行。这种机制的弊端:CPU无法及时处理其他的代码。

例如:

STM32中断机制:如果用了串口中断,则不需要阻塞等待接收数据。

4、中断优先级和中断嵌套

4.1、中断优先级

生活中的情景:你正看书,如果在电话铃响的同时,门铃也响了,那么你将在"接电话"和"开门"这两个中断请求中选择,先响应哪一个请求。考虑优先级的问题。如果"开门"比"接电话"重要(或者说"开门"比"接电话"的优先级高),那么就应该先开门,然后再接电话,接完电话后再回头来继续看书。这就是说,当同时有多个中断请求时,应该先响应优先级较高的中断请求。

STM32:如果有多个中断,则每个中断都应给分配一个优先级。举例:串口1中断需要有自己的优先级,串口2的中断也要有自己优先级。

​​​​​​​4.2、中断嵌套

高优先级的中断可以 打断 低优先级的中断的执行。处理完高优先级的服务程序后,再处理低优先级服务程序的未完成的代码。中断服务程序全部执行完成后,回到主函数,继续执行主函数的代码。

5、​​​​​​​​​​​​​​Cortex-M4中断控制系统

​​​​​​​5.1、NVIC控制器介绍

嵌套向量中断控制器(Nested Vectored Interrupt Controller),是属于内核控制器,该控制器是有ARM公司设计,Coretex-M4的芯片的NVIC控制都是一样。该控制器的作用:用于控制中断的使能禁止、设置中断的优先级。

Core-M4内核最多支持256个中断通道,但是芯片设计公司,例如ST,考虑到实际成本和功耗的情况会裁剪调一部分的中断通道。

STM32F4007ZGT6具有 82 个可屏蔽中断通道(可屏蔽:可以使能也可以禁止), 不包括 的 16 个Cortex™-M4F 内核中断)。

​​​​​​​5.2、异常向量表

5.3、​​​​​​​NVIC控制器中断优先级

5.3.1、优先级分类

  1. 抢占优先级:抢占优先级高的中断可以打断抢占优先级低的中断的执行。抢占优先级是用数字来表示:数字越小,优先级越高。(这个和生活中的“酒店”级别刚好相反)
  2. 响应优先级:如果多个中断的抢占级优先级相同,响应优先级高的中断先响应。响应优先级也是用数字来表示:数字越小,优先级越高。
  3. 自然优先级:如果多个抢占优先级和响应优先级相同的中断同时触发,自然优先级高的中断先执行。自然优先级是用数字来表示:数字越小,优先级越高。自然优先级是固定,具体请查看STM32F4xx中文参考手册的P234的异常向量表。

Core-M4内核最多支持256级的可编程优先级。用8位来表示优先级级别,,优先级级别分为8组,分别是组0~组7,如下图所示:

​​​​​​​5.3.2、STM32F407ZGT6中断优先级

Coretex-M4内核支持256个可编程优先级,ST公司设计STM32F407ZGT6时,为了精简设计,只用了16 个可编程优先级(使用了 高四位4 位来设置中断优先级,设置低四位的值是无效的)。 

5.4、NVIC中断中断控制器相关函数

这些函数由内核设置控制中断的函数,已经封装好的。具体代码在:core_cm4.h

​​​​​​​​​​​​​​​​​​​​​​​​​​​​5.4.1、NVIC分组设置

void NVIC_SetPriorityGrouping(uint32_t PriorityGroup)

位置:core_cm4.h的1453行

作用:设置优先级分组。

参数:PriorityGroup优先级分组组号

举例:STM32的优先级分组设置为组5,则对应的代码如下:NVIC_SetPriorityGrouping(5);

​​​​​​​5.4.2、NVIC具体中断优先级编码

uint32_t NVIC_EncodePriority (uint32_t PriorityGroup, uint32_t PreemptPriority, uint32_t SubPriority)

作用:设置抢占优先级和响应优先级的级别

位置:core_cm4.h的1610行

参数:PriorityGroup优先级分组组号;PreemptPriority:抢占优先级;SubPriority:响应优先级

返回值:32位的编码值,编码值用于中断优先级设置

举例:优先级分组选择为组5,抢占优先级为2,响应优先级为2,代码如下:

      u32 prio;   prio = NVIC_EncodePriority(5,2,2);

​​​​​​​5.4.3、NVIC中断优先级设置

void NVIC_SetPriority(IRQn_Type IRQn, uint32_t priority)

作用:将优先级分组情况以及抢占优先级和响应优先级设置到响应的中断。

参数:

IRQn :中断通道编号。

priority:是NVIC_EncodePriority函数的返回值

举例:设置串口1全局中断的优先级分组选择为组5,抢占优先级为2,响应优先级为2

对应的代码:

     u32 prio;

prio = NVIC_EncodePriority(5,2,2);

NVIC_SetPriority(37,prio);  

也可以写成NVIC_SetPriority(USART1_IRQn,prio);

​​​​​​​5.4.4、NVIC中断使能

void NVIC_EnableIRQ(IRQn_Type IRQn)

作用:使能中断通道

参数:IRQn :中断通道编号。

举例:使能串口1全局中断,NVIC_EnableIRQ(37);  可以写成NVIC_EnableIRQ(USART1_IRQn);

​​​​​​​5.4.5、NVIC中断禁能

void NVIC_DisableIRQ(IRQn_Type IRQn)

作用:禁止中断通道。

参数:IRQn :中断通道编号。

举例:禁止串口1全局中断,NVIC_DisableIRQ(37);

​​​​​​​5.4.6、中断通道编号

    中断通断编号在“stm32f4xx.h”中有定义。

​​​​​​​5.4.7、中断服务函数编写原则

  1. 设置STM32的优先级分组。注意:一个工程只能有一个分组。
  2. 设置相应通道的抢占优先级和响应优先级
  3. 使能片上外设中断的使能位,需要操作片上外设的寄存器。(片上外设  peripheral on chip)
  4. 查找中断函数名,在startup_stm32f40_41xxx.s查找
  5. 定义一个无返回值,无形参的中断服务函数,举例:串口1中断服务函数

void USART1_IRQHandler(void)

{

//中断服务程序代码

}

注意:不可以主动调用中断函数;中断函数的代码不能太多。中断函数的执行时间不能过长。

​​​​​​​5.4.7、中断程序设计

“读取数据寄存器不为空”则表明成功接收到数据,这是串口中断的一个“中断源”,如果成功接收一个字节的数据且使能了串口中断,则会执行中断服务函数。

 

6、软件设计

以串口中断为例

寄存器版

void usart1_Init()
{
	u16 integer ;
	u16 fraction;
	float USARTDIV;
	u32 baudRate = 115200;
	RCC->AHB1ENR |= 1<<0;//使能GPIOA的时钟  RCC_AHB1ENR
	GPIOA->MODER &= ~(3<<18);//清零
	GPIOA->MODER |= 2<<18;//设置PA9为复用功能模式MODER
	GPIOA->MODER &= ~(3<<20);//清零
	GPIOA->MODER |= 2<<20;//设置PA10为复用功能模式MODER
	GPIOA->AFR[1]&= ~(0XF<<4);//清零
	GPIOA->AFR[1]|= (7<<4);//设置PA9的复用功能为第7复用功能 RXD, AFRH
	GPIOA->AFR[1]&= ~(0XF<<8);//清零
	GPIOA->AFR[1]|= (7<<8);//设置PA10的复用功能为第7复用功能 TXD, AFRH
	
	RCC->APB2ENR |= 1<<4;//使能串口1模块时钟 RCC_APB2ENR
	USART1->CR1  |= 1<<15;//设置串口OVER8为1
	USART1->CR1  &= ~(1<<12);//设置串口数据位长度 :1 起始位, 8 数据位
	USART1->CR2 &=~(3<<12);//设置串口停止位长度 :1 个停止位
	USART1->CR1 &= ~(1<<10);//无校验
	USART1->CR1 |= 1<<3;//发送使能
	USART1->CR1 |= 1<<2;//接收使能
	USARTDIV = 84000000/8/baudRate;//串口波特率设置:USARTDIV = fCK/(8*(2-OVER8) /波特率
	integer = (u16)USARTDIV;
	fraction = ((u16)(USARTDIV-integer))<<4;
	USART1->BRR |= integer<<4 | fraction;
	/* 使能中断源中断条件 */
	USART1->CR1 |= 1 << 5;       //使能接收中断
	NVIC_EnableIRQ(USART1_IRQn);       //使能USART_1NVIC中断
	USART1->CR1 |= 1<<13;//使能串口
}
void USART1_IRQHandler(void)
{
	u8 res;	
	if(USART1->SR&(1<<5))//接收到数据
	{
		res=USART1->DR; 
		if(res == 0x0D || res == 0x0A)
			return;
		USART1->DR = res+1;
		while(!(USART1->SR&(1<<6)));  //SR寄存器的位6为1则表明成功发送一个字节数据,跳出循环
		USART1->SR &=~(1<<6);  //清零			
	}
}
int main(void)
{
	usart1_Init();
	while(1)
	{
		
	}
}

库函数版

void usart1_Init()
{
	u32 baudRate = 115200;
	GPIO_InitTypeDef GPIO_InitStructe;
	USART_InitTypeDef USART_InitStructe;
	NVIC_InitTypeDef   NVIC_InitStructe;
	RCC_AHB1PeriphClockCmd(RCC_AHB1Periph_GPIOA,ENABLE); //使能 A 端口
	RCC_APB2PeriphClockCmd(RCC_APB2Periph_USART1,ENABLE); //使能串口端口
	GPIO_PinAFConfig(GPIOA,GPIO_PinSource9,GPIO_AF_USART1); //GPIO 端口映射到 USART
	GPIO_PinAFConfig(GPIOA,GPIO_PinSource10,GPIO_AF_USART1); //GPIO 端口映射到 USART
	// USART_DeInit(USART1); //复位
	GPIO_InitStructe.GPIO_Mode =GPIO_Mode_AF; //配置为输出模式
	GPIO_InitStructe.GPIO_Pin =GPIO_Pin_9|GPIO_Pin_10; //初始化 GPIOA9/10;
	GPIO_Init(GPIOA,&GPIO_InitStructe);
	USART_InitStructe.USART_BaudRate =baudRate; //波特率
	USART_InitStructe.USART_HardwareFlowControl =USART_HardwareFlowControl_None;//无硬件流控制
	USART_InitStructe.USART_Mode =USART_Mode_Tx|USART_Mode_Rx; //接收模式,发送模式使能
	USART_InitStructe.USART_Parity =USART_Parity_No; //无奇偶校验
	USART_InitStructe.USART_StopBits =USART_StopBits_1; //一位停止位
	USART_InitStructe.USART_WordLength =USART_WordLength_8b;//8 位数据位
	USART_Init(USART1,&USART_InitStructe);
	
	USART_ITConfig(USART1, USART_IT_RXNE, ENABLE);//开启相关中断
	NVIC_InitStructe.NVIC_IRQChannel= USART1_IRQn; //串口 1 中断
	NVIC_InitStructe.NVIC_IRQChannelCmd =ENABLE; //串口 1 使能
	NVIC_InitStructe.NVIC_IRQChannelPreemptionPriority =0; //抢占优先级设为 0
	NVIC_InitStructe.NVIC_IRQChannelSubPriority =0; //子优先级设为 0
	NVIC_Init(&NVIC_InitStructe);
	NVIC_PriorityGroupConfig(NVIC_PriorityGroup_2);
	USART_Cmd(USART1,ENABLE); //使能串口
	
}
void USART1_IRQHandler(void)
{
	u8 res;	
	while(USART_GetFlagStatus(USART1,USART_FLAG_RXNE)) //等待接收完成
	{
		USART_ClearITPendingBit(USART1,USART_FLAG_RXNE); //清楚标志位
		res=USART_ReceiveData(USART1); //接收串口的数据
		if(res == 0x0D || res == 0x0A)
			continue;
		USART_SendData(USART1,res+1); //输出+1
	}
}
int main(void)
{
	usart1_Init();
	while(1)
	{
		
	}
}

 

 

Logo

北京人形旗下天工造物具身智能开源社区,聚焦具身天工与慧思开物两大平台

更多推荐