STM32笔记:学习EXTI外部中断功能
一、简介
中断:在主程序运行过程中,出现了特定的中断触发条件(中断源),使得CPU暂停当前正在运行的程序,转而去处理中断程序,处理完成后又返回原来被暂停的位置继续运行;
当你正在写作业时,做到一半又去吃饭,吃完饭后又回来接着原来的作业继续完成。
中断,在单片机中占有非常重要的地位。代码默认地从上向下执行,遇到条件或者其他语句,会按照指定的地方跳转。而在单片机执行代码的过程中,难免会有一些突发的情况需要处理,这样就会打断当前的代码,待处理完突发情况之后,程序会回到被打断的地方继续执行。
外部信号进入经过1的边沿检测电路,检测是否符合(有2和3的上升沿和下降沿选择寄存器决定),产生信号,然后和4软件中断事件寄存器或值,(在这里也就说可以写入软件中断事件寄存器模拟中断和事件),之后产生信号一分为二,看5中断屏蔽寄存器和7事件屏蔽寄存器,如果中断和事件都没有屏蔽,首先会产生事件,进入脉冲发生器。其次,会进入6挂起寄存器,然后进入NVIC。
STM32 GPIO外部中断简图:
GPIO到EXTI的映射:(要产生中断,必须先配置好并使能中断线。)
中断优先级:当有多个中断源同时申请中断时,CPU会根据中断源的轻重缓急进行裁决,优先响应更加紧急的中断源;
中断嵌套:当一个中断程序正在运行时,又有新的更高优先级的中断源申请中断,CPU再次暂停当前中断程序,转而去处理新的中断程序,处理完成后依次进行返回。
(不用开启外设时钟:特殊)EXTI可以监测指定GPIO口的电平信号,当其指定的GPIO口产生电平变化时,EXTI将立即向NVIC发出中断申请,经过NVIC裁决后即可中断CPU主程序,使CPU执行EXTI对应的中断程序。
支持的触发方式:上升沿/下降沿/双边沿/软件触发
(EXTI 可以实现对每个中断/事件线进行单独配置,可以单独配置为中断或者事件,以及触发事件的属性。)
支持的GPIO口:所有GPIO口,但相同的Pin不能同时触发中断
通道数:16个GPIO_Pin,外加PVD输出、RTC闹钟、USB唤醒、以太网唤醒
触发响应方式:中断响应/事件响应
在中断的使用中还有一个极其重要的一部分为中断服务函数(触发中断后,系统执行的部分,例如上文的吃饭过程)中断服务函数是中断的入口。
中断向量表定义在启动文件中,发生中断时,CPU会自动执行对应的额中断服务函数;
STM32总结之开启外设时钟:
1.点亮LED灯实验时,用到了GPIOB,我们需要开启GPIOB的时钟:
2.使用按键的时候,不论是查询方式,还是中断方式,都用到了GPIOA,所以要开启GPIOA的时钟。
3.配置UASRT1时,用到了PA9和PA10,所有要开启GPIOA的时钟,另外还有开启USART1的时钟。
4.使用DMA时,要开启DAM时钟,DMA挂载在AHB总线上
5.使用基本定时器时要开启基本定时器的时钟。基本定时器挂载在APB1总线上。
6.使用通用定时器时要开启通用定时器的时钟。通用定时器挂载在APB1总线上。
需要注意的是:
1.配置按键中断时,只需要开启相应的GPIO的时钟。初始化EXTI结构体时,不需要开启EXTI时钟。
2.配置NVIC中断向量控制器时,不需要开启时钟。
3.使用SysTick系统定时器时,不需要开启时钟。
二、基本结构
硬件中断选择:通过下面的过程来配置20个线路做为中断源:
● 配置20个中断线的屏蔽位(EXTI_IMR)
中断屏蔽寄存器(EXTI_IMR)
允许至NVIC中断寄存器:
● 配置所选中断线的触发选择位(EXTI_RTSR和EXTI_FTSR);
边沿检测电路—配置触发信号
● 配置对应到外部中断控制器(EXTI)的NVIC中断通道的使能和屏蔽位,使得20个中断线中的请求可以被正确地响应。(最重要)逻辑即下图:
AFIO复用:(记得开启 外设时钟)通过AFIO_EXTICRx配置GPIO线上的外部中断/事件,必须先使能AFIO时钟。112通用I/O端口以下图的方式连接到16个外部中断/事件线上:
使用函数 GPIO_EXTILineConfig进行配置:
要产生中断,必须先配置好并使能中断线。根据需要的边沿检测设置2个触发寄存器(上升沿还是下降沿),同时在中断屏蔽寄存器的相应位写’1’允许中断请求(如下图利用与门的特点,0&x = 0,1&x = x)。当外部中断线上发生了期待的边沿时,将产生一个中断请求,对应的挂起位也随之被置’1’。在挂起寄存器的对应位写’1’,将清除该中断请求。
工作原理图:
(可以看到很多在信号线上打一个斜杠并标注“20”字样,这个表示在控制器内部类似的信号线路有 20 个,这与 EXTI 总共有 20 个中断/事件线是吻合的。)
PCLK2—-APB2的时钟
从上图还可以看出支持的触发方式:上升沿/下降沿/双边沿/软件触发,边沿检测电路进来后经过一个或门,或门是有1为1,故不管是软件中断寄存器进行触发还是边沿检测电路触发,都能触发中断,一路发生中断,一路是事件触发,同样都存在屏蔽寄存器。而请求挂起寄存器:
该寄存器的作用主要有两个:
1、检测外部中断线上是否发生了选择的边沿事件,如果发生了,该位置1,并将信号传递给 与门电路,进而进入NVIC中;
2、在该位手动(软件)写入1,可以清除之前中断信号的1,主要作用是进入中断后,清除中断位,防止多次进入中断;
此外,在配置nvic时还需要注意的是:EXTI9_5和EXTI15_10
EXPORT ,表示本程序里面用到的变量提供给其他模块调用的。
EXPORT EXTI0_IRQHandler
EXPORT EXTI1_IRQHandler
EXPORT EXTI2_IRQHandler
EXPORT EXTI3_IRQHandler
EXPORT EXTI4_IRQHandler
EXPORT EXTI9_5_IRQHandler
EXPORT EXTI15_10_IRQHandler
中断线 0-4 每个中断线对应一个中断函数,中断线 5-9 共用中断函数 EXTI9_5_IRQHandler,中断线 10-15 共用中断函数 EXTI15_10_IRQHandler。
NVIC配置代码:
NVIC_InitTypeDef NVIC_InitStructure; //定义结构体变量
NVIC_InitStructure.NVIC_IRQChannel = EXTI15_10_IRQn; //选择配置NVIC的EXTI15_10线
NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE; //指定NVIC线路使能
NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 1; //指定NVIC线路的抢占优先级为1
NVIC_InitStructure.NVIC_IRQChannelSubPriority = 1; //指定NVIC线路的响应优先级为1
NVIC_Init(&NVIC_InitStructure); //将结构体变量交给NVIC_Init,配置NVIC外设
STM32中断优先级基本概念:
1、抢占优先级(pre):高抢占优先级可以打断正在执行的低抢占优先级中断;
2、响应优先级(sub):当抢占优先级相同时,响应优先级高的先执行,但是不能相互打断;
3、抢占优先级和响应优先级都相同的情况下,自然优先级越高的先执行;
4、自然优先级:中断向量表中的优先级;
5、数值越小,表示优先级越高;
原文链接:https://blog.csdn.net/m0_56399733/article/details/134979299
三、外部中断代码实现
外部中断一般配置步骤:
1.初始换IO口为输入:GPIO_Init();
2.开启IO口复用时钟:RCC_APB2PeriphClockCmd();
简单来说:操作外设是通过外设总线来实现,只有外设总线有时钟了才能操作外设。
/*开启时钟*/
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOB, ENABLE); //开启GPIOB的时钟
RCC_APB2PeriphClockCmd(RCC_APB2Periph_AFIO, ENABLE); //开启AFIO的时钟,外部中断必须开启AFIO的时钟
/*GPIO初始化*/
GPIO_InitTypeDef GPIO_InitStructure;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IPU;
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_14;
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_Init(GPIOB, &GPIO_InitStructure); //将PB14引脚初始化为上拉输入
注意:可以看到, 只有当使用事件控制寄存器、复用重映射和调试寄存器以及外部中断寄存器的时候,才需要提前开启AFIO的时钟!并不是使用到引脚复用功能就必须开启AFIO时钟。
为什么配置成上拉?
3.设置IO口与中断线的映射关系: void GPIO_EXTILineConfig();
/*AFIO选择中断引脚*/
GPIO_EXTILineConfig(GPIO_PortSourceGPIOB, GPIO_PinSource14);//将外部中断的14号线映射到GPIOB,即选择PB14为外部中断引脚
4.初始化线上中断,设置触发条件等:EXTI_Init();
/*EXTI初始化*/
EXTI_InitTypeDef EXTI_InitStructure; //定义结构体变量
EXTI_InitStructure.EXTI_Line = EXTI_Line14; //选择配置外部中断的14号线
EXTI_InitStructure.EXTI_LineCmd = ENABLE; //指定外部中断线使能
EXTI_InitStructure.EXTI_Mode = EXTI_Mode_Interrupt; //指定外部中断线为中断模式
EXTI_InitStructure.EXTI_Trigger = EXTI_Trigger_Falling; //指定外部中断线为下降沿触发
EXTI_Init(&EXTI_InitStructure); //将结构体变量交给EXT
5.配置中断分组(NVIC),并且使能中断:NVIC_Init();
NVIC_PriorityGroupConfig(NVIC_PriorityGroup_2); //配置NVIC为分组2
//即抢占优先级范围:0~3,响应优先级范围:0~3
//此分组配置在整个工程中仅需调用一次
//若有多个中断,可以把此代码放在main函数内,while循环之前
//若调用多次配置分组的代码,则后执行的配置会覆盖先执行的配置
/*NVIC配置*/
NVIC_InitTypeDef NVIC_InitStructure; //定义结构体变量
NVIC_InitStructure.NVIC_IRQChannel = EXTI15_10_IRQn; //选择配置NVIC的EXTI15_10线
NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE; //指定NVIC线路使能
NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 1; //指定NVIC线路的抢占优先级为1
NVIC_InitStructure.NVIC_IRQChannelSubPriority = 1; //指定NVIC线路的响应优先级为1
NVIC_Init(&NVIC_InitStructure);
6.编写中断服务函数():EXTIx_IRQHandler();
PxN管脚共用外部中断线EXTIN和外部中断向量EXTIN_IRQn和中断服务程序入口EXTIN_IRQHandler(这些都不能写错)
7.清除中断标志位:EXTI_ClearITPendingBit();
/**
* 函 数:EXTI15_10外部中断函数
* 参 数:无
* 返 回 值:无
* 注意事项:此函数为中断函数,无需调用,中断触发后自动执行
* 函数名为预留的指定名称,可以从启动文件复制
* 请确保函数名正确,不能有任何差异,否则中断函数将不能进入
*/
void EXTI15_10_IRQHandler(void)
{
if (EXTI_GetITStatus(EXTI_Line14) == SET) //判断是否是外部中断14号线触发的中断
{
/*如果出现数据乱跳的现象,可再次判断引脚电平,以避免抖动*/
if (GPIO_ReadInputDataBit(GPIOB, GPIO_Pin_14) == 0)
{
CountSensor_Count ++; //计数值自增一次
}
EXTI_ClearITPendingBit(EXTI_Line14); //清除外部中断14号线的中断标志位
//中断标志位必须清除
//否则中断将连续不断地触发,导致主程序卡死
}
}
四、扩展内容
void TIMER6_IRQHandler(void) //0.5ms
{
uint32_t ulReturn;
/* 进入临界段,临界段可以嵌套 */
// ulReturn = taskENTER_CRITICAL_FROM_ISR();
if(SET == timer_interrupt_flag_get(TIMER6, TIMER_INT_FLAG_UP))
{
/******add opt********/
if(s_Fluctuate.shock_wave_ad > ADC_WATCHDOG1_LT)
{
if(s_SetParam.ControllerType == 1)
{
SeedSensorMonitor();
BaseFatSensorMonitor();
}
else if(s_SetParam.ControllerType == 0)
{
SeedSensorMonitor_TiaoBo();
}
}
else
{
debug_printf(INFO_ORDINARY,"low voltage not count ...\r\n");
}
timer_interrupt_flag_clear(TIMER6, TIMER_INT_FLAG_UP);
}
/* 退出临界段 */
// taskEXIT_CRITICAL_FROM_ISR( ulReturn );
}
static void Timer6_Config(uint16_t psc,uint16_t arr)
{
rcu_periph_clock_enable(RCU_TIMER6);
timer_deinit(TIMER6);
timer_parameter_struct timer_initparam;
timer_struct_para_init(&timer_initparam);
timer_initparam.alignedmode = TIMER_COUNTER_EDGE;
timer_initparam.clockdivision = TIMER_CKDIV_DIV1;
timer_initparam.counterdirection = TIMER_COUNTER_UP;
timer_initparam.period = arr - 1;
timer_initparam.prescaler = psc - 1;
timer_initparam.repetitioncounter = 0U;
timer_init(TIMER6,&timer_initparam);
timer_interrupt_enable(TIMER6,TIMER_INT_UP);
timer_enable(TIMER6);
nvic_irq_enable(TIMER6_IRQn, 4, 0);
}
void Bsp_TimerInit(void)
{
Timer5_Config( 100, 1000);
Timer6_Config( 100, 500); //0.5ms
}
作者:白少平