提示:文章写完后,目录可以自动生成,如何生成可参考右边的帮助文档
理论部分:
一、EXTI简介
二、中断系统
三、执行流程
四、60个中断源
如图这些灰色的属于CM3内核的中断-内核中断,哪个芯片都有,剩下的属于外部中断,ADC、CAN总线、RCC、TIM定时器、I2C总线、UART串口等灯都也是支持外部中断功能;
五、NVIC基本原理
首先CPU不可能引出太多接线口,但是我们的外设很多足足有68个还多,所以这里就增加了NVIC模块; NVIC模块就能引出很多接线了,这里箭头上写了个n,意思是一个外设可能会同时占用多个中断通道,所哟这里实际是n条线的; 然后NVIC只有一个输出口,NVIC根据每个中断的优先级分配中断的先后顺序,之后通过右边这一个输出口告诉CPU,你该处理哪个中断。 NVIC起到中断通道分配的作用。 举个例子,CPU就是医生,NVIC是医院的叫号系统,哪些中断外设就是看病的病人,NVIC的作用就是给病人分配一个看病的优先等级,提高医生的看病效率。 优先叫号紧急的病人,最后叫号系统输出的就是一个一个排好队的病人给医生。
六、中断处理优先等级
七、EXTI内部执行结构
最左边是GPIO外设A~G,每个外设有16根线,所以进去是16根线的进去到AFIO; GPIOA~G就有16组,每组有有16个引脚1-6根线,那么接线口就不够用了,所以这里增添了AFIO中断引脚选择器(这个就类似数字电路中的数据选择器) AFIO是一个多路数据选择器,他可以将前面这3个GPIO外设的16个引脚里选择其中一路通道,连接到EXTI,给EXTI时候扔然是16根线,每16根线叫一个通道; GPIOA~G 16个+PVD输出+RTC闹钟+USB唤醒+ETH以太网唤醒共20路,就构成EXTI的20个外设输入信号通道,每次每个通道也只有一个的16根数据-一路加到EXTI的输入口; 接着在EXTI里要经历一个“边沿检测及控制”的处理:此时输出有两类 →①类是接到NVIC; 接到NVIC就是用来触发中断的,按理说20路输入对应20路输出,在这EXYI9_5合为一个通道,EXTI15_10合为一个通道,也就是使用EXYI9_5会触发一个中断函数,使用EXTI15_10会触发另一中断函数 →②类是接到其他外设; 这里是有20根数据线构成一路输出给其他外设,也就是刚才说的“事件响应”;
实操部分:
一-如何进入中断
以对射红外传感器试验为例
接线图连接
对射红外传感器的Vcc接面包板的Vcc,GND接面包板的GND,Do数据后随便找一个GPIO口接上; 如此接上,先测以下对射红外传感器好坏,当住传感器凹口,板子输出灯会熄灭,说明能正常输出高低电平;
试验结果
教程演示的应该是,每遮挡一次“对射红外传感器”,“OLED显示屏”显示的数值就会自加1; “对射式红外传感器”每被遮挡一次,他就会输出一次跳变的信号;
开始写代码-建立工程
继上次OLED显示屏的代码,复制出来继而改造; 还是把这个外传感器的功能封装起来,就继续在Hardware文件下建立CountSensor的.c和.h文件:
【重点】功能模块初始化
一般程序都是先写初始化部分
①步配置RCC,把涉及到的外设时钟都打开
涉及的外设包含RCC时钟、GPIOA~G这一列、AFIO、EXTI、NVIC RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOB,ENABLE);//开启GPIOB RCC_APB2PeriphClockCmd(RCC_APB2Periph_AFIO,ENABLE);//开启AFIO //EXTI和NVIC时钟本来就默认是打开的,故不用配置开启,NVIC属于内核模块 //RCC管不着他,EXTI兼顾唤醒功能也是打开的
②步配置GPIO,设置端口为输入模式
GPIO_InitTypeDef GPIO_InitStruct; GPIO_InitStruct.GPIO_Mode=GPIO_Mode_IN_FLOATING; //对于外设模式选择浮空输入、上拉输入、下拉输入其中的一个,在这给了一个浮空输入 GPIO_InitStruct.GPIO_Pin=GPIO_Pin_11 ;//我们用的是B11引脚 GPIO_InitStruct.GPIO_Speed=GPIO_Speed_50MHz;//频率只有输出模式起作用 GPIO_Init(GPIOA, &GPIO_InitStruct); 这是GPIO的配置结果:
③步配置AFIO,选择我们用的GPIO-连接到EXTI
这个AFIO外设,ST公司并没有给他分配专门的库函数文件,他的库函数是和GPIO在一个文件里,打开stm3210fx_gpio.h拉倒最后,其中有一些就是和AFIO相关的: 350行 void GPIO_AFIODeInit(void);//这个函数是用来复位AFIO的,调用以下这个函数,AFIO外设的配置就会全部清楚; 361行 void GPIO_PinLockConfig(GPIO_TypeDef* GPIOx, uint16_t GPIO_Pin); //这个函数是用来锁定GPIO配置的,调用这个函数参数指定某个引脚,那这个引脚的配置就会被锁定,放置意外更改,这个属于GPIO外设的函数,用的不多; 362行 void GPIO_EventOutputConfig(uint8_t GPIO_PortSource, uint8_t GPIO_PinSource); 363行 void GPIO_EventOutputCmd(FunctionalState NewState); //这两个函数是用来配置AFIO的时间输出功能的,用的也不多; 364行 void GPIO_PinRemapConfig(uint32_t GPIO_Remap, FunctionalState NewState); 365行 void GPIO_EXTILineConfig(uint8_t GPIO_PortSource, uint8_t GPIO_PinSource); //这两个函数就比较重要了,GPIO_PinRemapConfig()可以用来进行引脚重映射,第一个参数可以选择你要重映射的方式,第二个参数是新的状态; void GPIO_EXTILineConfig()就是我们本节外部中断需要用到的函数,调用这个函数,就可以配置AFIO的数据选择器,来选择我们想要的中断引脚 366行 void GPIO_ETH_MediaInterfaceConfig(uint32_t GPIO_ETH_MediaInterface); //这个是和以太网有关的,我们103f这个芯片没有以太网外设,所以不考虑了
回到keil,AFIO配置 GPIO_EXTILineConfig(GPIO_PortSourceGPIOB,GPIO_PinSource14);//接通PB14引脚 当执行完这个函数后,AFIO的第14个数据选择器就拨好了,其中输入端被拨到GPIOB外设上,对应的就是PB14引脚,输出端固定连接的是EXTI的第14个中断线路,这样PB14号引脚的电平信号就可以顺利通过AFIO,进入后级的EXTI电路了
④步配置EXTI,选择触发方式和选择触发相应方式
我们找到exti.h文件,打开拖到最后,这些就就是EXTI的所有库函数 158行 void EXTI_DeInit(void);//调用他就可以把EXTI的配置都清除,恢复成上电默认的状态 159行 void EXTI_Init(EXTI_InitTypeDef* EXTI_InitStruct);//调用这个函数,就可以根据这个结构题里的参数配置EXTI外设,我们初始化EXTI主要也是用到的这个函数,使用方法也是和GPIO_Init也是一样的 160行 void EXTI_StructInit(EXTI_InitTypeDef* EXTI_InitStruct);//调用这个函数,可以把参数传递的结构体变量赋一个默认值,前面这三个函数(158/159/160),基本所有外设都有,就像库函数的模板函数一样,基本每个外设都需要这些类型的函数 161行 void EXTI_GenerateSWInterrupt(uint32_t EXTI_Line);//这个函数是用来软件触发外部中断的,调用这个函数,参数给一个指定的中断线,就能软件触发一次这个外部中断 162/163/164/165行 也是库函数的模板函数,一个套路格式的,在外设运行过程中,会产生一些状态标志位,比如外部中断来了,是不是会有一个挂起寄存器置位一个标志位?对于其他外设,比如串口收到数据,也会置位标志位,还有定时器时间到,也会置位标志位,这些标志位都是存放在状态寄存器的,当程序想要看这些标志位时,就用到了162/163/164/165行这四个函数了 162行 FlagStatus EXTI_GetFlagStatus(uint32_t EXTI_Line);//可以获取指定的标志为是否被置1了 163行 void EXTI_ClearFlag(uint32_t EXTI_Line);//可以对置1的标志位进行清除 有些标志位比较紧急,那么他是在中断程序中的,就用到164/165 164行 ITStatus EXTI_GetITStatus(uint32_t EXTI_Line);//获取中断标志位是否被置1了 165行 void EXTI_ClearITPendingBit(uint32_t EXTI_Line);//清除中断挂起标志位
所以总结下,若果你想在主程序例查看和清除标志位,就用162/163,若是想在中断函数中查看和清除标志位,就用164/165这两行
查看EXTI_Line能选什么
这是EXTI的配置结果:
⑤步配置NVIC,给我们中断选一个合适的优先级
找到NVIC库函数,因为NVIC是内核外设,所以他存在了文件的杂项里面了,找到misc.h,拉倒最后就能找到 196行 void NVIC_PriorityGroupConfig(uint32_t NVIC_PriorityGroup); //这个函数是用来中断分组的,参数是中断分组的方式 197行 void NVIC_Init(NVIC_InitTypeDef* NVIC_InitStruct); //根据结构体里面指定的参数初始化NVIC 198行 void NVIC_SetVectorTable(uint32_t NVIC_VectTab, uint32_t Offset); //设置中断相量表,用的不多可以先不看 199行 void NVIC_SystemLPConfig(uint8_t LowPowerMode, FunctionalState NewState); //系统低功耗配置,用的不多可以先不看 以下是整个EXTI外设的配置结果: 至此外部中断就配置完成,信号从GPIO——AFIO——EXTI——NVIC——CPU,这样才能让CPU由主程序跳转到中断程序执行; 那么执行中断程序,中断程序放到哪呢? 那就需要一个中断函数了,在stm32中,中断函数的名字都是固定的,每个中断通道都对应这一个中断函数; 中断函数的名字我们可以参考以下启动文件: 这里定义了中断相量表,里面以IRQHandler结尾的字符串,就是中断函数的名字,我们可以找到这个EXTI15_10_IRQHandler这一项,中就是EXTI15_10的中断函数,我们复制一下,开始写中断函数,格式: void EXTI15_10_IRQHandler(void) {
} 中断函数都是无参数无返回值的,中断函数的名字不要写错了,写错了就进不了中断了,最好是直接从启动文件复制过来,这样就不会有问题了 在写中断函数时,一般都是先来个进行中断标志位的判断,确保是我们想要的中断源触发的这个个中断函数,因为这个函数 EXTI15_10都能进来,具体是哪个呢??所以需要先判断以下是不是我们想要的EXTI11进来的,这时就需要到exti.h里看一下 至此中断程序就写完了,就是下面这个 void EXTI15_10_IRQHandler(void) { if(EXTI_GetFlagStatus(EXTI_Line11)==SET) { EXTI_ClearITPendingBit(EXTI_Line11); } }
接下来我们试一下他能不能进去中断函数??!!~ 剩下别忘了,初始化的函数要声明,中断函数不需要声明,因为他是硬件自动执行,不需要软件声明上的引导; 这是最后的.h声明文件
调试参考
在编译→下载之前,可以使用台式功能查看能不进如中断 验证: 这个是进入中断的程序调试,这时在面包板去触发PB11脚,看到结果,断点处多了一个黄色箭头,表示成功进入断点 重复验证的方法:再此点击“全速运行”→然后在触发PB11脚看结果
浮空输入触发中断结果
GPIO_InitStruct.GPIO_Mode=GPIO_Mode_IN_FLOATING;//浮空输入模式 浮空输入模式下只要用一个电阻或一根线,一头接触PB11脚,就能触发中断
按键上拉输入触发中断
紧接着浮空输入触发中断,我们换成按键能不能触发中断; 只修改如图,的GPIO输入模式,浮空模式换成上拉模式;
按键上拉输入结果
EXTI外部中断初始化配置总结
二、【重点】中断处理优先等级(详解)
理论概述
这里意思说的是: ①抢占优先级高的会打断抢占优先级低的,无论响应优先级是否高于低于等于; 例:中断2的抢占优先级高于中断1的抢占优先级,忽略响应优先级; 那么中断1正在进行中,中断2就应该能打断中断1; ②响应优先级必须在抢占优先级相同的情况下,才能发生,继而也有三种情形,高、低、相同,值得注意的是响应优先级不存在打断的事情,比较讲道理,论先来后到,谁先来就执行谁; 下面验证下这几种情形,我们预期的结果是: 验证①中断2抢占优先级高于中断1抢占优先级,无论它们的响应优先级高低等与否,中断2都能终止中断1,并执行中断2;分有三种情形(中断2高于中断1;中断2低于中断1;中断2等于中断1;) 验证②中断2和中断①抢占优先级相同的情况下,中断2分三种情形(中断2低于中断1;中断2等于中断1;)它们都不应该打断中断1,而是和中断1平级,谁先来就执行谁;
验证
建立3个中断的程序
方法是在原有1个中断初始化后面再排列2个初始化程序,和中断程序中要增添其他中断进入后做的事情 void CountSensor12_Init(void)//PB12脚中断初始化 { … } void CountSensor13_Init(void)//PB12脚中断初始化 { … } void CountSensor14_Init(void)//PB12脚中断初始化 { … }
void EXTI15_10_IRQHandler(void)//中断程序 { if(EXTI_GetFlagStatus(EXTI_Line12)==SET) { CountSensor_Count++;//PB12c触发数值自增1 EXTI_ClearITPendingBit(EXTI_Line12); } if(EXTI_GetFlagStatus(EXTI_Line13)==SET) { CountSensor_Count–;//PB13触发数值自减1 EXTI_ClearITPendingBit(EXTI_Line13); } if(EXTI_GetFlagStatus(EXTI_Line14)==SET) { CountSensor_Count++;//PB14触发数值自增1 EXTI_ClearITPendingBit(EXTI_Line14); } } ``
情形1-中断2高于中断1
验证中断2抢占优先级高于中断1抢占优先级,无论它们的响应优先级高低等与否,中断2都能终止中断1,并执行中断2;分有三种情形(验证①中断2高于中断1;验证②中断2低于中断1;验证③中断2等于中断1;) 验证①中断2高于中断1: 设置中断1抢占优先级居中,那就是NVIC_PriorityGroupConfig(NVIC_PriorityGroup_2);//中断分组,设为第2组; NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority=2;//抢占优先级设为2 NVIC_InitStructure.NVIC_IRQChannelSubPriority=2;//响应优先级设为2 设置中断1无论是抢占优先级还是响应优先级都是居中; 设置中断2抢占优先级比中断1的要高一级,那就是NVIC_PriorityGroupConfig(NVIC_PriorityGroup_1);//中断分组,设为第1组; 验证结果: 当镊子按住不放PB12(中断1)浮空触发时,数值增加,在这当中,螺丝刀碰触PB13(中断2),数值终止增加而减小,达到了预期的结果;
情形2-中断2低于中断1
验证中断2抢占优先级高于中断1抢占优先级,无论它们的响应优先级高低等与否,中断2都能终止中断1,并执行中断2;分有三种情形(验证①中断2高于中断1;验证②中断2低于中断1;验证③中断2等于中断1;) 验证②中断2低于中断1: 设置中断1抢占优先级居中,那就是NVIC_PriorityGroupConfig(NVIC_PriorityGroup_2);//中断分组,设为第2组; NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority=2;//抢占优先级设为2 NVIC_InitStructure.NVIC_IRQChannelSubPriority=2;//响应优先级设为2 设置中断1无论是抢占优先级还是响应优先级都是居中;
设置中断2抢占优先级比中断1的要低一级,那就是NVIC_PriorityGroupConfig(NVIC_PriorityGroup_3);//中断分组,设为第3组; 验证结果: 当镊子按住不放PB12(中断1)浮空触发时,数值增加,在这当中,螺丝刀碰触PB13(中断2),数值并没有终止增加而减小,达到了预期的结果;
情形3-中断2抢占优先级等于中断1抢占优先级
验证中断2抢占优先级等于中断1抢占优先级,这时候响应优先级发挥作用,但是不存在打断的情型,即使你响应优先级高,这里中断1和中断2的响应优先级高、中断2的响应优先级低、中断2的响应优先级等是平级关系,谁先触发就执行谁; 验证的前提:中断2抢占优先级等于中断1抢占优先级; 分有三种情形 (验证①中断2响应优先级高于中断1响应优先级; 验证②中断2响应优先级低于中断1响应优先级; 验证③中断2响应优先级等于中断1响应优先级; 验证①中断2响应优先级高于中断1响应优先级; 设置中断1抢占优先级居中,那就是NVIC_PriorityGroupConfig(NVIC_PriorityGroup_2);//中断1分组,设为第2组; NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority=2;//抢占优先级设为2 NVIC_InitStructure.NVIC_IRQChannelSubPriority=2;//响应优先级设为2 设置中断1无论是抢占优先级还是响应优先级都是居中; 设置中断2抢占优先级和中断1抢占优先级相同,那就是NVIC_PriorityGroupConfig(NVIC_PriorityGroup_2);//中断2分组,设为第2组; 设置中断2响应优先级高于中断1的响应优先级,那就是 NVIC_InitStructure.NVIC_IRQChannelSubPriority=1;//响应优先级设为1 验证结果: 当镊子按住不放PB12(中断1)浮空触发时,数值增加,在这当中,螺丝刀碰触PB13(中断2),数值并没有终止增加而减小,PB12和PB13会单独独立触发运行,谁先到就先执行谁,互不关联达到了预期的结果;
验证②中断2响应优先级低于中断1响应优先级; 设置中断1抢占优先级居中,那就是NVIC_PriorityGroupConfig(NVIC_PriorityGroup_2);//中断1分组,设为第2组; NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority=2;//抢占优先级设为2 NVIC_InitStructure.NVIC_IRQChannelSubPriority=2;//响应优先级设为2 设置中断1无论是抢占优先级还是响应优先级都是居中; 设置中断2抢占优先级和中断1抢占优先级相同,那就是NVIC_PriorityGroupConfig(NVIC_PriorityGroup_2);//中断2分组,设为第2组; 设置中断2响应优先级低于中断1的响应优先级,那就是 NVIC_InitStructure.NVIC_IRQChannelSubPriority=3;//响应优先级设为3 验证结果: 当镊子按住不放PB12(中断1)浮空触发时,数值增加,在这当中,螺丝刀碰触PB13(中断2),数值并没有终止增加而减小,PB12和PB13会单独独立触发运行,谁先到就先执行谁,互不关联达到了预期的结果;
验证③中断2响应优先级等于中断1响应优先级; 设置中断1抢占优先级居中,那就是NVIC_PriorityGroupConfig(NVIC_PriorityGroup_2);//中断1分组,设为第2组; NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority=2;//抢占优先级设为2 NVIC_InitStructure.NVIC_IRQChannelSubPriority=2;//响应优先级设为2 设置中断1无论是抢占优先级还是响应优先级都是居中; 设置中断2抢占优先级和中断1抢占优先级相同,那就是NVIC_PriorityGroupConfig(NVIC_PriorityGroup_2);//中断2分组,设为第2组; 设置中断2响应优先级低于中断1的响应优先级,那就是 NVIC_InitStructure.NVIC_IRQChannelSubPriority=2;//响应优先级设为2 验证结果: 当镊子按住不放PB12(中断1)浮空触发时,数值增加,在这当中,螺丝刀碰触PB13(中断2),数值并没有终止增加而减小,PB12和PB13会单独独立触发运行,谁先到就先执行谁,互不关联达到了预期的结果;
总结
提示:这里对文章进行总结: 例如:以上就是今天要讲的内容,本文仅仅简单介绍了pandas的使用,而pandas提供了大量能使我们快速便捷地处理数据的函数和方法。
|