怎样免费建立个人网站,自己做网站怎么样,如何网上查个人房产信息,中国城乡住房和城乡建设部网站首页中断系统原理
中断
中断系统是管理和执行中断的逻辑结构#xff0c;外部中断是众多能产生中断的外设之一#xff0c;所以本节我们就借助外部中断来学习一下中断系统。在以后学习其它外设的时候#xff0c;也是会经常和中断打交道的。
中断#xff1a;在主程序运行过程中…中断系统原理
中断
中断系统是管理和执行中断的逻辑结构外部中断是众多能产生中断的外设之一所以本节我们就借助外部中断来学习一下中断系统。在以后学习其它外设的时候也是会经常和中断打交道的。
中断在主程序运行过程中出现了特定的中断触发条件中断源。比如对于外部中断来说可以是引脚发生了电平跳变对于定时器来说可以是定时的时间到了对于串口通信来说可以是接收到了数据使得CPU暂停当前正在运行的程序转而去处理中断程序处理完成后又返回原来被暂停的位置继续运行。就好比晚上睡觉前定了个闹钟时间到了提醒你不管时间到不到你可以安心睡觉。
中断优先级当有多个中断源同时申请中断时CPU会根据中断源的轻重缓急进行裁决优先响应更加紧急的中断源。这个中断优先级是我们根据程序设计的需求自己设置的。
中断嵌套中断程序再次中断二次中断现象当一个中断程序正在运行时又有新的更高优先级的中断源申请中断CPU再次暂停当前中断程序转而去处理新的中断程序处理完成后依次进行返回。也是为了照顾非常紧急的中断。 中断执行流程
中断程序的执行流程如下当它执行到某个地方时外设的中断条件满足了那这时无论主程序是在干什么事情比如OLED显示程序才执行一半Delay函数还在等待等中断来了主程序都得立即暂停程序由硬件电路自动跳转到中断程序中当中断程序执行完之后程序再返回被暂停的地方继续运行这个暂停的地方叫做断点。为了程序能在中断返回后继续原来的工作在中断执行前会对程序的现场进行保护中断执行后会再返回现场这样保证主程序被中断了回来之后也能继续执行。 中断嵌套的执行流程如下。当一个中断正在执行时又有新的优先级更高的中断来那个旧中断会被打断执行新的中断新的中断结束再继续执行原来的中断原来的中断结束再继续主程序这就是中断嵌套的执行流程。 STM32中断
多个可屏蔽中断通道中断源包含EXTI外部中断、TIM、ADC模数转换器、USART串口、SPI、I2C、RTC实时时钟等多个外设。几乎所有模块都能申请中断 使用NVIC统一管理中断每个中断通道都拥有16个可编程的优先等级可对优先级进行分组进一步设置抢占优先级和响应优先级。
NVIC就是STM32中用来管理中断、分配优先级的NVIC的中断优先级共有16个等级。
EXTIx是外部中断对应的中断资源。
EXTI简介
EXTIExtern Interrupt外部中断
EXTI可以监测指定GPIO口的电平信号当其指定的GPIO口产生电平变化时EXTI将立即向NVIC发出中断申请经过NVIC裁决后即可中断CPU主程序使CPU执行EXTI对应的中断程序。简单说引脚电平变化申请中断
支持的触发方式引脚电平的变化类型上升沿电平从低电平变到高电平的瞬间触发中断/下降沿电平从高电平变到低电平的瞬间触发中断/双边沿上升沿和下降沿都可以触发中断/软件触发程序执行代码就能触发中断
支持的GPIO口外部中断引脚所有GPIO口都能触发中断但相同的Pin不能同时触发中断比如PA0和PB0不能同时使用智能选一个作为中断引脚所以如果有多个中断引脚要选择不同的pin引脚比如PA0和PA1、PB3就可以 通道数总共有20个中断线路。16个GPIO_Pin对应GPIO_pin0到15是外部中断的主要功能外加PVD输出、RTC闹钟、USB唤醒、以太网唤醒这4个中断线路是因为外部中断有个功能是从低功耗模式的停止模式下唤醒STM32那对于PVD电源电压检测当从电源从电池过压恢复时就需要PVD借助一下外部中断的退出停止模式对于RTC闹钟来说有时候为了省电RTC定一个闹钟之后STM32回进入停止模式等到闹钟响的时候再唤醒这叶需要借助外部中断剩余USB唤醒、以太网唤醒也是类似的作用
触发响应方式中断响应引脚电平触发中断申请中断让CPU执行中断函数/事件响应不会触发中断而是触发别的外设操作属于外设之间的联合工作。外部中断的信号不会通向CPU而是通向其它外设用来触发其它外设的操作比如触发ADC转换、触发DMA等 EXTI基本结构 外部中断的整体结构图如下
首先最左边是GPIO口的外设每个GPIO外设有16个引脚所以进来16根线如果每个引脚占用一个通道那EXTI的16个通道是不够用的所以在这里会有一个AFIO中断引脚选择的电路模块这个AFIO就是一个数据选择器可以将图中前面的3个GPIO外设的16个引脚中的其中一个连接到后面的EXTI通道16个GPIO通道所以对于PA0\PB0\PC0这些通过AFIO选择之后只有其中一个能接到EXTI的通道0上然后通过AFIO选择后的16个通道就能接到了EXTI边沿检测及控制电路上同时下面这4个蹭网的外设PVD\PTC\USB\ETH也是并列接进来的这些加起来就组成了EXTI的20个输入信号然后经过EXTI电路之后分为了两种输出也就是中断响应和事件响应上面接到了NVIC用来触发中断下面有20条输出线路到了其它外设也就是事件响应 注EXTI9_5是外部中断的5,6,7,8,9分到了一个通道里EXTI15_10也是一样也就是说外部中断的9到5会触发同一个中断函数15到10也会触发同一个中断函数在编程的时候我们在这两个中断函数里需要再根据标志位区分到底是哪个中断进来的。本来20路输入应该有20路中断的输出可能20个输出太多了比较占用NVIC的通道资源所以就把其中的外部中断9~515~10给分到了一个通道 AFIO复用IO口内部电路
内部电路就是一系列的数据选择器如下图的最上面输入是PA0\PB0\PC0等尾号都是0然后通过数据选择器最终选择一个连接到EXTI0上上面写的文字是说配置这个寄存器的哪一个位就可以决定选择哪一个输入图中后面部分内容都雷同。
AFIO主要用于引脚复用功能的选择和重定义也就是数据选择器的作用。
在STM32中AFIO主要完成两个任务1.复用功能引脚重映射就是最开始提到的引脚定义表当想把默认复用功能换到重定义功能时就是用AFIO来完成的这也是AFIO的一大主要功能、2.中断引脚选择。 EXTI内部电路框图
EXTI的右边就是20根输入线然后输入线首先进入边沿检测电路在上面的上升沿寄存器和下降沿寄存器可以选择是上升沿触发还是下降沿触发或者两个都触发接着硬件触发信号和软件中断寄存器的值就进入到这个或门的输入端也就是任意一个为1或门就可以输出1然后触发信号通过这个或门后就兵分两路上一路是触发中断的下一路是触发事件的触发中断首先会置一个挂起寄存器挂起寄存器相当于一个中断标志位可以读取这个寄存器判断是哪个通道触发的中断如果挂起寄存器置1它就会继续向左走和中断屏蔽寄存器共同进入一个与门与门实际上就是开关控制作用中断屏蔽寄存器给1那另一个输入就是输出也就是允许中断中断屏蔽寄存器给0那另一个输入无论是什么输出都是0相当于屏蔽了这个中断然后是NVIC中断控制器。接着就是下一路的选择是触发事件首先也是一个事件屏蔽寄存器进行开关控制最后通过一个吗脉冲发生器到其它外设脉冲发生器就是给一个电平脉冲用来触发其它外设的动作
补充框图最上面两个就是外设接口和APB总线我们可以通过总线访问这些寄存器。 EXTI外部中断的特性和使用场景
1.什么样的设备需要用到外部中断使用外部中断有什么好处呢大概总结了使用外部中断模块的特性就是对于stm32来说想要获取的信号是外部驱动的很快的突发信号。 比如旋转编码器的输出信号不拧它这时不需要stm32做任何事情但是一拧它就会有很多脉冲波形需要stm32接收这个信号是突发的stm32不知道什么时候会来同时它是外部驱动的stm32只能被动读取最后这个信号非常快stm32稍微晚一点来读取就会错过很多波形所以对于这种情况来说就可以考虑使用stm32的外部中断有脉冲过来stm32立即进入中断函数处理没有脉冲的时候stm32就专心做其他事情
另外还有比如红外遥控接收头的输出接收到要遥控数据之后它会输出一端波形这个波形转瞬即逝并且不会等你所以就需要使用外部中断来读取
最后还有按键虽然它的动作是外部驱动的突发事件但我并不推荐使用外部中断来读取按键因为外部中断不好处理按键抖动和松手检测对于按键来说它的输出波形也不是转瞬即逝的所以要求不高的话可以在主程序中循环读取也可以考虑一下定时器中断读取的方式 5-1 对射式红外传感器原理代码
接线图 当我们的挡光片或者编码盘在这个对射式红外传感器中间经过时这个DO就会输出电平跳变的信号然后这个电平跳变的信号触发STM32 PB14号口的中断我们在中断函数里执行变量的程序然后主循环里用OLED显示这个变量这样第一个程序就完成了。 首先在之前OLED的代码基础上进行修改
继续将传感器文件封装在一个模块中 CountSensor.c
第一个都为初始化函数但这时需要进行外部中断的配置函数
CountSensor.c
CountSensor_Init GPIO初始化
(1) 使用RCC使能GPIO时钟
(2) 初始化GPIO 结构体 GPIO_Mode GPIO_Pin GPIO_Speed GPIO口的模式配置在数据手册中可查到有一个外设的GPIO配置表
(3) 使用输出或输入函数控制GPIO口设置端口默认的高低电平
AFIO配置
(1) 开启AFIO时钟EXTI和NVIC两个外设的时钟是一直开的 NVIC内核外设都是不需要开启时钟-(通过AFIO_EXTICRx配置GPIO线上的外部中断/事件必须先使能AFIO时钟)
(2) AFIO选择中断引脚
/*** 函 数计数传感器初始化* 参 数无* 返 回 值无*/
void CountSensor_Init(void)
{/*开启时钟*/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选择中断引脚*/GPIO_EXTILineConfig(GPIO_PortSourceGPIOB, GPIO_PinSource14);//将外部中断的14号线映射到GPIOB即选择PB14为外部中断引脚/*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); //将结构体变量交给EXTI_Init配置EXTI外设/*NVIC中断分组*/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线路的抢占优先级为1NVIC_InitStructure.NVIC_IRQChannelSubPriority 1; //指定NVIC线路的响应优先级为1NVIC_Init(NVIC_InitStructure); //将结构体变量交给NVIC_Init配置NVIC外设
}
EXTI初始化
NVIC配置
EXTI15_10_IRQHandler
/*** 函 数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号线的中断标志位//中断标志位必须清除//否则中断将连续不断地触发导致主程序卡死}
}CountSensor_Get
/*** 函 数获取计数传感器的计数值* 参 数无* 返 回 值计数值范围0~65535*/
uint16_t CountSensor_Get(void)
{return CountSensor_Count;
} main.c
#include stm32f10x.h // Device header
#include Delay.h
#include OLED.h
#include CountSensor.hint main(void)
{/*模块初始化*/OLED_Init(); //OLED初始化CountSensor_Init(); //计数传感器初始化/*显示静态字符串*/OLED_ShowString(1, 1, Count:); //1行1列显示字符串Count:while (1){OLED_ShowNum(1, 7, CountSensor_Get(), 5); //OLED不断刷新显示CountSensor_Get的返回值}
}5-2 旋转编码器计次
与对射式红外传感器原理类似都是通过外部中断 检测转瞬即逝 的信号
同理将代码模块化在OLED的基础上进行修改分为三部分OLED.c Encoder.c main.c Encoder.c
代码逻辑与对射式红外传感器 外部中断检测突变信号原理相似
#include stm32f10x.h // Device headerint16_t Encoder_Count; //全局变量用于计数旋转编码器的增量值/*** 函 数旋转编码器初始化* 参 数无* 返 回 值无*/
void Encoder_Init(void)
{/*开启时钟*/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_0 | GPIO_Pin_1;GPIO_InitStructure.GPIO_Speed GPIO_Speed_50MHz;GPIO_Init(GPIOB, GPIO_InitStructure); //将PB0和PB1引脚初始化为上拉输入/*AFIO选择中断引脚*/GPIO_EXTILineConfig(GPIO_PortSourceGPIOB, GPIO_PinSource0);//将外部中断的0号线映射到GPIOB即选择PB0为外部中断引脚GPIO_EXTILineConfig(GPIO_PortSourceGPIOB, GPIO_PinSource1);//将外部中断的1号线映射到GPIOB即选择PB1为外部中断引脚/*EXTI初始化*/EXTI_InitTypeDef EXTI_InitStructure; //定义结构体变量EXTI_InitStructure.EXTI_Line EXTI_Line0 | EXTI_Line1; //选择配置外部中断的0号线和1号线EXTI_InitStructure.EXTI_LineCmd ENABLE; //指定外部中断线使能EXTI_InitStructure.EXTI_Mode EXTI_Mode_Interrupt; //指定外部中断线为中断模式EXTI_InitStructure.EXTI_Trigger EXTI_Trigger_Falling; //指定外部中断线为下降沿触发EXTI_Init(EXTI_InitStructure); //将结构体变量交给EXTI_Init配置EXTI外设/*NVIC中断分组*/NVIC_PriorityGroupConfig(NVIC_PriorityGroup_2); //配置NVIC为分组2//即抢占优先级范围0~3响应优先级范围0~3//此分组配置在整个工程中仅需调用一次//若有多个中断可以把此代码放在main函数内while循环之前//若调用多次配置分组的代码则后执行的配置会覆盖先执行的配置/*NVIC配置*/NVIC_InitTypeDef NVIC_InitStructure; //定义结构体变量NVIC_InitStructure.NVIC_IRQChannel EXTI0_IRQn; //选择配置NVIC的EXTI0线NVIC_InitStructure.NVIC_IRQChannelCmd ENABLE; //指定NVIC线路使能NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority 1; //指定NVIC线路的抢占优先级为1NVIC_InitStructure.NVIC_IRQChannelSubPriority 1; //指定NVIC线路的响应优先级为1NVIC_Init(NVIC_InitStructure); //将结构体变量交给NVIC_Init配置NVIC外设NVIC_InitStructure.NVIC_IRQChannel EXTI1_IRQn; //选择配置NVIC的EXTI1线NVIC_InitStructure.NVIC_IRQChannelCmd ENABLE; //指定NVIC线路使能NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority 1; //指定NVIC线路的抢占优先级为1NVIC_InitStructure.NVIC_IRQChannelSubPriority 2; //指定NVIC线路的响应优先级为2NVIC_Init(NVIC_InitStructure); //将结构体变量交给NVIC_Init配置NVIC外设
}/*** 函 数旋转编码器获取增量值* 参 数无* 返 回 值自上此调用此函数后旋转编码器的增量值*/
int16_t Encoder_Get(void)
{/*使用Temp变量作为中继目的是返回Encoder_Count后将其清零*//*在这里也可以直接返回Encoder_Count但这样就不是获取增量值的操作方法了也可以实现功能只是思路不一样*/int16_t Temp;Temp Encoder_Count;Encoder_Count 0;return Temp;
}/*** 函 数EXTI0外部中断函数* 参 数无* 返 回 值无* 注意事项此函数为中断函数无需调用中断触发后自动执行* 函数名为预留的指定名称可以从启动文件复制* 请确保函数名正确不能有任何差异否则中断函数将不能进入*/
void EXTI0_IRQHandler(void)
{if (EXTI_GetITStatus(EXTI_Line0) SET) //判断是否是外部中断0号线触发的中断{/*如果出现数据乱跳的现象可再次判断引脚电平以避免抖动*/if (GPIO_ReadInputDataBit(GPIOB, GPIO_Pin_0) 0){if (GPIO_ReadInputDataBit(GPIOB, GPIO_Pin_1) 0) //PB0的下降沿触发中断此时检测另一相PB1的电平目的是判断旋转方向{Encoder_Count --; //此方向定义为反转计数变量自减}}EXTI_ClearITPendingBit(EXTI_Line0); //清除外部中断0号线的中断标志位//中断标志位必须清除//否则中断将连续不断地触发导致主程序卡死}
}/*** 函 数EXTI1外部中断函数* 参 数无* 返 回 值无* 注意事项此函数为中断函数无需调用中断触发后自动执行* 函数名为预留的指定名称可以从启动文件复制* 请确保函数名正确不能有任何差异否则中断函数将不能进入*/
void EXTI1_IRQHandler(void)
{if (EXTI_GetITStatus(EXTI_Line1) SET) //判断是否是外部中断1号线触发的中断{/*如果出现数据乱跳的现象可再次判断引脚电平以避免抖动*/if (GPIO_ReadInputDataBit(GPIOB, GPIO_Pin_1) 0){if (GPIO_ReadInputDataBit(GPIOB, GPIO_Pin_0) 0) //PB1的下降沿触发中断此时检测另一相PB0的电平目的是判断旋转方向{Encoder_Count ; //此方向定义为正转计数变量自增}}EXTI_ClearITPendingBit(EXTI_Line1); //清除外部中断1号线的中断标志位//中断标志位必须清除//否则中断将连续不断地触发导致主程序卡死}
}main.c
#include stm32f10x.h // Device header
#include Delay.h
#include OLED.h
#include Encoder.hint16_t Num; //定义待被旋转编码器调节的变量int main(void)
{/*模块初始化*/OLED_Init(); //OLED初始化Encoder_Init(); //旋转编码器初始化/*显示静态字符串*/OLED_ShowString(1, 1, Num:); //1行1列显示字符串Num:while (1){Num Encoder_Get(); //获取自上此调用此函数后旋转编码器的增量值并将增量值加到Num上OLED_ShowSignedNum(1, 5, Num, 5); //显示Num}
}