上一篇的复杂按键没看懂也没关系,涉及的东西太多,应用也不频繁,STM32就算分模块的,即使你复杂按键不会,依旧也能学STM32。
今天学习中断系统和NVIC,STM32的内核是CM3的内核,有256个中断,其中包含16个内核中断和240个外部中断。STM32部分使用了他的中断(16个内核中断和68个可屏蔽中断->STM32F103系列只有60个可屏蔽中断,STM32F107系列有68个可屏蔽中断)。我使用的是一款STM32F103系列的,有0-59这60个可屏蔽的中断供操作,这么多中断我们便需要一个方式去管理,所以引入了中断管理(NVIC)去给分组,设置中断优先级,我们用四位去分配优先级,即2的4次方,是16个优先级别,然后这每个优先级别又分2位去设置抢占优先级,2位去设置响应优先级,分组配置是在寄存器SCB->AIRCR中配置。抢占优先级是比如a事件设置的抢占优先级是2,b设置的抢占优先级是3,那无论他们响应优先级如何,都是抢占优先级高的(a)先执行,如果ab的抢占优先级相同,响应优先级a比b高,那ab无论谁先发生,都无法打断执行,但如果是ab同时发生,就会先执行响应优先级高的。即:抢占优先级可以抢着执行,响应优先级只能“同时执行时”去抢着执行。
接下来介绍一下“中断优先级”的设置步骤
1.系统运行后先设置中断优先级分组。调用函数设置中断优先级分组:(切记整个系统执行过程中,只设置一次中断分组,随意改变分组会导致中断系统混乱,程序出现意想不到的执行结果):
void NVIC_PriorityGroupConfig(uint32_t NVIC_PriorityGroup); //关于NVIC的函数在misc.c和misc.h库函数里找。后面还会介绍。
2.针对每个中断,设置对应的抢占优先级和响应优先级:
void NVIC_Init(NVIC_InitTypeDef* NVIC_InitStruct);
3.如果需要挂起/解挂,查看中断当前激活状态,分别调用相关函数即可。
首先讲解一下中断优先级分组函数
void NVIC_PriorityGroupConfig(uint32_t NVIC_PriorityGroup);
在我们的bsp.c里有关于bsp_Init的初始化,里面就有一句设置中断优先级
NVIC_PriorityGroupConfig(NVIC_PriorityGroup_2); 将优先级分组设置成了2。(变量值在misc.h的最下面找到该声明函数右键GO TO一下,就可以找到该函数,再assert后面分组处右击GOTO就可以查看到取值范围
一共是五个中断优先级分组。
再介绍一下各个中断相关的寄存器,
中断激活标志位寄存器组:IABR[8] :
作用:只读,通过它可以知道当前在执行的中断是哪一个中断
如果对应位为1,说明该中断正在执行。
中断使能寄存器组 :ISER[8]:
作用:用来使能中断。
32位寄存器,每个位控制一个中断的使能
中断失能寄存器组:ICER[8]
作用:用来失能中断
32位寄存器,每个位控制一个中断失能。
中断挂起控制寄存器组:ISPR[8] 作用:用来挂起中断
中断解挂控制寄存器组:ICPR[8] 作用:用来解挂中断
这些寄存器了解一下就可以,实际应用比较少。
接下来介绍一下第二部的NVIC初始化NVIC_Init(NVIC_InitTypeDef* NVIC_InitStruct);
关于中断初始化,和以前学的GPIO和按键的初始化是一样的道理,首先都是创建结构体然后再赋值,关于这个结构体我们进行一下介绍。点开misc.h,按住ctrl+F跳出搜索框直接搜NVIC_InitTypeDef这个结构体,我们看一下都有什么成员。
NVIC_IRQChannel代表的是中断的IRQ通道
NVIC_IRQChannelPreemptionPriority代表设置响应优先级
NVIC_IRQChannelSubPriority代表设置抢占优先级
NVIC_IRQChannelCmd代表失能
STM32的每个IO口都可以作为外部中断输入, 在STM32里,IO口外部中断在中断向量表中只分配了7个中断向量,也就是只能使用7个中断服务函数。(外部中断线5~9分配一个中断向量,共用一个服务函数,外部中断线10~15分配个中断向量,共用一个服务函数)
关于中断服务函数的列表(0-15对应GPIOX.1~15)
EXTI0_IRQHandler
EXTI1_IRQHandler
EXTI2_IRQHandler
EXTI3_IRQHandler
EXTI4_IRQHandler
EXTI9_5_IRQHandler
EXTI15_10_IRQHandler
还有关于外部中断常用库函数的介绍(外部中断库文件在stm32f10x_exti.h)
初始化中断线的初始化参数(触发方式等)
void EXTI_Init(EXTI_InitTypeDef* EXTI_InitStruct);
判断中断线中断状态,是否发生
ITStatus EXTI_GetITStatus(uint32_t EXTI_Line);
清除中断线上的中断标志位
void EXTI_ClearFlag(uint32_t EXTI_Line);
现在我们开始书写代码,首先创建bsp_exti.c和bsp_exti.h,
在.c文件里写入函数,.h文件里写入函数的声明
我们之前在key.h里写入了关于按键的宏定义,所以这里可以不用在bsp_exti.h里写了
在.c里首先先写下IO引脚的初始化方便操作,这个我们在简单按键也有写过
void KEY_IO_Init(void)
{
GPIO_InitTypeDef GPIO_InitStructure;
/* 第1步:打开GPIO时钟 */
RCC_APB2PeriphClockCmd(RCC_ALL_KEY, ENABLE);
GPIO_PinRemapConfig(GPIO_Remap_SWJ_JTAGDisable, ENABLE); /* 关闭jtag,使能SWD,可以用SWD模式调试 */
/* 第2步:配置所有的按键GPIO */
GPIO_InitStructure.GPIO_Pin = WKUP_GPIO_PIN; /* WKUP端口 */
GPIO_InitStructure.GPIO_Mode = WKUP_GPIO_MODE; /* WKUP端口模式 */
GPIO_Init(WKUP_GPIO_PORT, &GPIO_InitStructure); /* 初始化WKUP */
GPIO_InitStructure.GPIO_Pin = KEY0_GPIO_PIN; /* KEY0端口 */
GPIO_InitStructure.GPIO_Mode = KEY0_GPIO_MODE; /* KEY0端口模式 */
GPIO_Init(KEY0_GPIO_PORT, &GPIO_InitStructure); /* 初始化KEY0 */
GPIO_InitStructure.GPIO_Pin = KEY1_GPIO_PIN; /* KEY1端口 */
GPIO_InitStructure.GPIO_Mode = KEY1_GPIO_MODE; /* KEY1端口模式 */
GPIO_Init(KEY1_GPIO_PORT, &GPIO_InitStructure); /* 初始化KEY1 */
GPIO_InitStructure.GPIO_Pin = KEY2_GPIO_PIN; /* KEY2端口 */
GPIO_InitStructure.GPIO_Mode = KEY2_GPIO_MODE; /* KEY1端口模式 */
GPIO_Init(KEY2_GPIO_PORT, &GPIO_InitStructure); /* 初始化KEY2 */
}
然后是中断系统的初始化,根据最上面的介绍,我们需要进行的包括NVIC优先级的初始化和EXTI外部中断的初始化
void EXTI_KEY_Init(void)
{
//定义结构体EXTI,NVIC
EXTI_InitTypeDef EXTI_InitStructure;
NVIC_InitTypeDef NVIC_InitStructure;
//打开AFIO时钟,复用时钟——外部中断务必打开此时钟
RCC_APB2PeriphClockCmd(RCC_APB2Periph_AFIO, ENABLE);
//中断线映射配置
//WKUP--PA0
GPIO_EXTILineConfig(GPIO_PortSourceGPIOA,GPIO_PinSource0 );
//KEY0--PE4
GPIO_EXTILineConfig( GPIO_PortSourceGPIOE,GPIO_PinSource4);
//KEY1--pe3
GPIO_EXTILineConfig( GPIO_PortSourceGPIOE,GPIO_PinSource3);
//key2--pe2
GPIO_EXTILineConfig( GPIO_PortSourceGPIOE,GPIO_PinSource2);
//配置EXTI的结构体
//WKUP--PA0
EXTI_InitStructure.EXTI_Line=EXTI_Line0;//外部中断线0
EXTI_InitStructure.EXTI_LineCmd=ENABLE;//使能外部中断
EXTI_InitStructure.EXTI_Mode=EXTI_Mode_Interrupt;//中断方式而不是事件方式
EXTI_InitStructure.EXTI_Trigger=EXTI_Trigger_Rising;//上升沿触发
EXTI_Init( &EXTI_InitStructure);
//KEY0--PE4
EXTI_InitStructure.EXTI_Line=EXTI_Line4;//外部中断线4
EXTI_InitStructure.EXTI_LineCmd=ENABLE;//使能外部中断
EXTI_InitStructure.EXTI_Mode=EXTI_Mode_Interrupt;//中断方式而不是事件方式
EXTI_InitStructure.EXTI_Trigger=EXTI_Trigger_Falling;//下降沿触发
EXTI_Init( &EXTI_InitStructure);
//KEY1--PE3
EXTI_InitStructure.EXTI_Line=EXTI_Line3;//外部中断线3
EXTI_InitStructure.EXTI_LineCmd=ENABLE;//使能外部中断
EXTI_InitStructure.EXTI_Mode=EXTI_Mode_Interrupt;//中断方式而不是事件方式
EXTI_InitStructure.EXTI_Trigger=EXTI_Trigger_Falling;//下降沿触发
EXTI_Init( &EXTI_InitStructure);
//KEY2--PE2
EXTI_InitStructure.EXTI_Line=EXTI_Line2;//外部中断线2
EXTI_InitStructure.EXTI_LineCmd=ENABLE;//使能外部中断
EXTI_InitStructure.EXTI_Mode=EXTI_Mode_Interrupt;//中断方式而不是事件方式
EXTI_InitStructure.EXTI_Trigger=EXTI_Trigger_Falling;//下降沿触发
EXTI_Init( &EXTI_InitStructure);
//配置NVIC的 结构体
//WKUP--PA0
NVIC_InitStructure.NVIC_IRQChannel=EXTI0_IRQn;//中断通道号
NVIC_InitStructure.NVIC_IRQChannelCmd=ENABLE;//使能WKUP的NVIC
NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority=0x03;//抢占优先级3
NVIC_InitStructure.NVIC_IRQChannelSubPriority=0x03;//响应优先级3
NVIC_Init( &NVIC_InitStructure);
//KEY0--PE4
NVIC_InitStructure.NVIC_IRQChannel=EXTI4_IRQn;//中断通道号
NVIC_InitStructure.NVIC_IRQChannelCmd=ENABLE;//使能KEY0的NVIC
NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority=0x03;//抢占优先级3
NVIC_InitStructure.NVIC_IRQChannelSubPriority=0x03;//响应优先级3
NVIC_Init( &NVIC_InitStructure);
//KEY1--PE3
NVIC_InitStructure.NVIC_IRQChannel=EXTI3_IRQn;//中断通道号
NVIC_InitStructure.NVIC_IRQChannelCmd=ENABLE;//使能KEY1的NVIC
NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority=0x03;//抢占优先级3
NVIC_InitStructure.NVIC_IRQChannelSubPriority=0x03;//响应优先级3
NVIC_Init( &NVIC_InitStructure);
//KEY2--PE2
NVIC_InitStructure.NVIC_IRQChannel=EXTI2_IRQn;//中断通道号
NVIC_InitStructure.NVIC_IRQChannelCmd=ENABLE;//使能KEY2的NVIC
NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority=0x03;//抢占优先级3
NVIC_InitStructure.NVIC_IRQChannelSubPriority=0x03;//响应优先级3
NVIC_Init( &NVIC_InitStructure);
}
关于NVIC的成员的变量填什么和EXTI的成员填什么,我在代码里都有注释,具体怎么找的都是在对应的.h里GOTO一下,找到取值范围,这里我对NVIC的抢占优先级举例,NVIC在misc库文件里,所以打开NVIC.h,往下找到NVIC的初始化函数声明并点击GOTO,
这里再次点击GOTO即可查看到取值范围。
然后是关于中断的通道号,其实在misc里是没有的,通道号统一都放在了stm32f10x.h里,我们只要一找就能找到
然后我们再写一下外部中断函数的服务函数来选择发生对应中断执行什么操作,外部中断的服务函数在stm32f10x_hd.s里
往下翻便可以看到对应的函数名
这里我们写一下对应的外部中断0的中断服务函数,剩下的同理自行补齐即可
void EXTI0_IRQHandler(void)
{
//判断中断是否发生
if(EXTI_GetITStatus(EXTI_Line0)!=RESET)
{
//用户操作的任务
if(WKUP==WKUP_ACTIVE_LEVEL)
{
bsp_BEEPToggle();
}
//清除中断标志
EXTI_ClearITPendingBit(EXTI_Line0); //清除标志位
}
}
按照对应的函数名,中断发生标志函数我们在上面都有介绍,这里先写检测是否标准了中断发生,如果发生,用户按下了WKUP的情况下执行蜂鸣器翻转的操作,然后执行完还需要清除中断的标志。
剩下三个皆是同理。
写完之后把初始化函数写入bsp.c里让启动时就应用,并且记得在bsp_exti.h里声明函数。本次工程便完成了。