什么是中断?中断有什么用?
中断的作用?
用于响应和处理突发事件或紧急事件,提高CPU运行效率。
什么是中断?
中断定义:在主程序运行过程中,出现了特定的中断触发条件(中断源),使得CPU暂停当前正在运行的程序,转而去处理中断程序,处理完成后又返回原来被暂停的位置继续运行。
中断优先级:当有多个中断源同时申请中断时,CPU会根据中断源的轻重缓急进行裁决,优先响应更加紧急的中断源。
中断嵌套:当一个中断程序正在运行时,又有新的更高优先级的中断源申请中断,CPU再次暂停当前中断程序,转而去处理新的中断程序,处理完成后依次进行返回。
STM32中的中断
68个可屏蔽中断通道(中断源),包含EXTI(外部中断)、TIM(定时器)、ADC(模数转换器)、 USART串口、SPI通信、I2C通信、RTC实时时钟等多个外设。
STM32中的内核中断
这些中断比较高深,一般用不到,了解下即可。
STM32中的外设中断
WWDG窗口看门狗,用来监测程序运行状态的中断。
比如:你的程序卡死,没有及时喂狗,窗口看门狗就会申请中断,让你的程序调到窗口看门狗的中断程序里,在中断程序里就可以进行错误检查,看看出了什么问题。
PVD电源电压监测,如果你的供电电压不足,PVD电路就会申请中断,你在中断里就知道,现在供电不足,是不是电池没电了,赶紧保存一下重要数据。
外设电路检测到异常或事件,需要提醒一下CPU的时候,就可以申请中断,让程序调到对应的中断函数里运行一次,用来处理这个异常或事件。
中断地址的作用
因为我们程序中的中断函数的地址是由编译器来分配的,是不固定的,但是我们的中断跳转由于硬件的限制,只能跳到固定的地址执行程序,为了让硬件跳转到一个不固定的中断函数里,需要在内存中定义一个地址列表,这个地址列表是固定的,中断发生后,就跳到这个固定位置,然后在这个固定位置,由编译器,再加上一条跳转到中断函数的代码,这样中断跳转就可以跳转到任意位置。中断地址的列表就叫中断向量表,相当于中断跳转的一个跳板,用C语言编程时,不需要管这个中断向量表,因为编译器已经帮我们做好了。
NVIC(Nested Vectored Interrupt Controller 嵌套中断向量控制器)
NVIC作用:分配中断优先级和管理中断,每个中断通道都拥有16个可编程的优先等级,可对优先级进行分组,进一步设置抢占优先级和响应优先级。是一个内核外设,是CPU的小助手
NVIC的基本结构
/n:表示一个外设可能同时占用n个中断通道,所以这里是n条线
NVIC优先级分组
NVIC的中断优先级由优先级寄存器的4位(0~15对应每个中断通道有16个可编程的优先等级)决定,这4位可以进行切分,分为高n位的抢占优先级和低4-n位的响应优先级。
- 抢占优先级高的可以中断嵌套
- 响应优先级高的可以优先排队
- 抢占优先级和响应优先级均相同的按中断号排队(中断号为表55中的优先级)
EXTI(Extern Interrupt)外部中断
STM32中中断的一种
EXTI功能:监测指定GPIO口的电平信号,当其指定的GPIO口产生电平变化时,EXTI将立即向NVIC发出中断申请,经过NVIC裁决后即可中断CPU主程序,使CPU执行EXTI对应的中断程序。
支持的触发方式:上升沿(低电平变高电平)/下降沿(高电平变低电平)/双边沿/软件触发(代码编写)
支持的GPIO口:所有GPIO口,相同的Pin不能同时触发中断。即PA0和PB0不能同时用;PA1、PB1、PB1,pin一样,只能选其中一个作为中断引脚
通道数:16个GPIO_Pin,外加PVD输出、RTC闹钟、USB唤醒、以太网唤醒
16个GPIO_Pin是主要的;外加PVD输出、RTC闹钟、USB唤醒、以太网唤醒是来蹭网的
触发响应方式:中断响应/事件响应
- 中断响应是正常的流程,引脚电平变化触发中断,信号流向CPU,让CPU执行中断函数
- 事件响应信号流向其他外设,不会触发中断,信号不会流向CPU,而是触发别的外设操作,属于外设之间的联合工作。
EXIT基本结构
AFIO中断引脚选择:相当于数据选择器,可以在前面3个GPIO外设的16个引脚里选择其中的一个连接到后面EXIT的通道里
注意:外部中断的9~5给分配到了一个EXIT9_5通道里,触发同一个中断函数。15~10给分配到了一个EXIT9_5EXIT15_10通道里,也触发同一个中断函数。因此在编程时,我们在这两个中断函数里,需要再根据标志位来区分到底是哪个中断位进来的
右下角的20条输出线路:用来触发其他外设操作的,就是相应事件
EXTI内部框图
EXIT框图解释
20条输入线(GPIO口里选择16个通道加上PVD、RTC、USB、ETH外设)先进入边沿检测电路,边沿检测电路上面的上升沿寄存器和下降沿寄存器,可以选择是上升沿触发还是下降沿触发,或者两个都触发,接着触发信号就进入到或门输入端,硬件触发和软件中断寄存器的值连到了这个或门上,任意一个为1则输出1,所以,支持的触发方式是上升沿、下降沿、双边沿(上升沿和下降沿都触发)、软件触发。触发信号通过或门后的上面一路是触发中断的、下面一路是触发事件的。
上面一路是触发中断:触发中断首先挂起寄存器,相当于一个中断标志位,我们可读取该寄存器判断是哪个通道触发的中断,如果中断寄存器置1,那它会继续向左走,和中断屏蔽寄存器共同进入一个与门,再至NVIC中断控制器,这里的与门实际上是开关的作用。对与门来说,1与上任意数X,等于任意数X。相当于中断屏蔽寄存器给1,那另一个输入就是直接输入,也就是允许中断;中断屏蔽寄存器给0,那另一个输入无论是什么,输出都是0,相当于屏蔽这个中断。
下面一路是触发事件:首先是事件屏蔽寄存器进行开关控制,同上述原理,最后通过一个脉冲发生器,到其他外设,这个脉冲发生器就是给一个电平脉冲,用来触发其他外设动作。
/20:表示20根线,代表20个通道。
顶端是外设接口和APB总线,我们可以通过总线访问这些寄存器。
AFIO(Alternate Function I/O)复用IO
功能:用于引脚复用功能(即一个IO用在多个外设上)的选择和重定义
在STM32中,AFIO主要任务:
- 复用功能引脚重映射
- 中断引脚的选择
从这个图也可以反映出为什么相同的Pin不能同时触发中断
从前面的GPIO口里选择16个通道加上PVD、RTC、USB、ETH外设组成了EXTI20个输入信号
总结外部中断代码书写流程
根据外部中断整体结构图进行配置
配置RCC
什么是RCC(Reset and Clock Control 复位和时钟控制)
- 负责管理微控制器的时钟树,还有外设的晶振、内部振荡器以及各种时钟源的分频和倍频。
- 可以控制各个外设的时钟使能,确保只有当前使用设备才会消耗能量。
开启要使用的接口及AFIO的外设的时钟。不开启时钟,外设无法工作。
打开所涉及的外设的时钟。
注意:EXTI和NVIC两个外设的时钟是一直开的,不需要开启时钟
EXTI是一个独立外设,按理来说是要开启的,但是寄存器中没有EXIT时钟的控制位。原因不知道,猜想可能是和EXIT唤醒有关或者一些电路设计上的考虑。
NVIC是内核的外设,内核的外设都是不需要开启时钟的,NVIC和CPU一起都是住在“皇宫”里的,在内核里,而RCC管的都是内核的外设,所以RCC管不着NVIC。
开启时钟示例
/*开启时钟*/
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOB, ENABLE); //开启GPIOB的时钟
RCC_APB2PeriphClockCmd(RCC_APB2Periph_AFIO, ENABLE); //开启AFIO的时钟,外部中断必须开启AFIO的时钟
配置GPIO
- 定义结构体
- 引出结构体成员
- 选择端口输入模式(浮空、上拉、下拉)。不知道选择什么模式可以看参考手册,在外设的GPIO配置一章
- 选择Pin口
- GPIO_Speed不是很重要,默认设为GPIO_Speed_50MHz
- 最后初始化GPIO外设
配置GPIO示例
/*GPIO初始化*/
GPIO_InitTypeDef GPIO_InitStructure; //定义结构体
//引出结构体各个成员
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IPU; //对于外部中断来说,要选择浮空输入、上拉输入、下拉输入模式,不知道可以查看参考手册,在GPIO一章
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_14;
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz; //不算重要
//初始化GPIOB外设
GPIO_Init(GPIOB, &GPIO_InitStructure); //将PB14引脚初始化为上拉输入
配置AFIO
库函数文件和GPIO在一个文件
//用来复位AFIO外设,调用该函数清空AFIO外设的配置
void GPIO_AFIODeInit(void);
//用来锁定GPIO配置,调用该函数,参数指定某个引脚,那该引脚的配置会被锁定,防止意外更改
void GPIO_PinLockConfig(GPIO_TypeDef* GPIOx, uint16_t GPIO_Pin);
//用来配置AFIO的事件输出功能,用的不多,了解即可
void GPIO_EventOutputConfig(uint8_t GPIO_PortSource, uint8_t GPIO_PinSource);
void GPIO_EventOutputCmd(FunctionalState NewState);
//重要
//用来进行引脚重映射。第一个参数:选择重映射的方式;第二个参数:新的状态
void GPIO_PinRemapConfig(uint32_t GPIO_Remap, FunctionalState NewState);
//重要
//配置AFIO的数据选择器,选择我们想要的中断引脚。第一个参数:选择某个GPIO外设作为外部中断源,可以是GPIO_PortSourcex,x可以是A到G;第二个参数:指定要配置的外部中断线,GPIO_PinSourcex,x可以是0到15
void GPIO_EXTILineConfig(uint8_t GPIO_PortSource, uint8_t GPIO_PinSource);
//和以太网有关的外设
void GPIO_ETH_MediaInterfaceConfig(uint32_t GPIO_ETH_MediaInterface);
使用函数void GPIO_EXTILineConfig(uint8_t GPIO_PortSource, uint8_t GPIO_PinSource);选择我们用的这一路GPIO,连接到后面的EXTI。
配置AFIO示例
/*AFIO选择中断引脚*/
//AFIO外设的库函数与GPIO.h在一个文件里,函数是GPIO开头,但实际上操作的是AFIO
//第一个参数:选择某个GPIO外设作为外部中断源
//第二个参数:指定要配置的外部中断线
GPIO_EXTILineConfig(GPIO_PortSourceGPIOB, GPIO_PinSource14);//将外部中断的14号线映射到GPIOB,即选择PB14为外部中断引脚
/*
执行完这个函数后,AFIO的第14个数据选择器就拨好了
其中输入端被拨到了GPIO外设上,对应的是PB14号引脚
输出端固定连接的是EXTI的第14个中断线路
这样PB14号引脚的电平信号就可以顺利通过AFIO,进入到后级的EXTI电路了*/
/*
配置EXTI
在exti.h文件中,可查看库函数
//清除EXTI配置,恢复成上电状态
void EXTI_DeInit(void);
//可以根据EXTI_InitStruct结构体里的参数配置EXTI外设,初始化EXTI就是用的这个函数,使用方法和GPIO_Init是一样的
void EXTI_Init(EXTI_InitTypeDef* EXTI_InitStruct);
//可以把参数传递的结构体变量赋一个默认值
void EXTI_StructInit(EXTI_InitTypeDef* EXTI_InitStruct);
//前3个函数,就像是库函数的模板函数一样,基本每个外设都需要这类型的函数
//用来软件触发外部中断,调用该函数,参数给定一个中断线,就能软件触发一次这个外部中断
void EXTI_GenerateSWInterrupt(uint32_t EXTI_Line);
//在外设运行过程中,会产生一些状态标志位
//比如:外部中断来了,就会有一个挂起寄存器置了一个标志位;对于其他外设,比如串口收到数据会置标志位,定时器时间到会置标志位
//这些标志位都是放在状态寄存器中,当程序想要看这些标志位就可以用到这四个函数
FlagStatus EXTI_GetFlagStatus(uint32_t EXTI_Line); //获取指定标志位是否被置1了
void EXTI_ClearFlag(uint32_t EXTI_Line); //对被置1的标志位进行清楚
ITStatus EXTI_GetITStatus(uint32_t EXTI_Line); //获取中断标志位是否被置1了
void EXTI_ClearITPendingBit(uint32_t EXTI_Line); //清除中断挂起的标志位
//总结就是:如果想在主程序中查看和清除标志位,就用上面两个函数;如果想在中断函数中查看和清除标志位,就用下面两个函数。本质上,这四个函数都是对状态寄存器的读写
- 定义EXTI结构体变量
- 将所有结构体成员引出
- 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的参数里,前面加上取地址符号
初始化EXIT示例
/*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外设
/*
目前的配置为:将EXTI的第14个线路配置为中断模式,下降沿触发,开启中断
这样PB14的电平信号就能通过EXTI通向下一级NVIC
*/
配置NVIC
相关库函数在misc.h中
//用来中断分组,参数是中断分组方式
void NVIC_PriorityGroupConfig(uint32_t NVIC_PriorityGroup);
//根据结构体里面指定的参数初始化NVIC
void NVIC_Init(NVIC_InitTypeDef* NVIC_InitStruct);
//设置中断向量表
void NVIC_SetVectorTable(uint32_t NVIC_VectTab, uint32_t Offset);
//系统低功耗配置
void NVIC_SystemLPConfig(uint8_t LowPowerMode, FunctionalState NewState);
- 配置优先级分组:先占优先级(抢占优先级)和从占优先级(响应优先级)
- 参数取值,几位抢占、几位响应,具体选择要根据实际需求,一般中断不多,难导致中断冲突,对优先级分组来说比较随意。
- 注意 : 这个分组方式整个芯片只能用一种,按理说这个分组代码整个工程只需要执行一次就行了,如果把它放在模块里进行分组,那你就要确保每个模块都选的是同一个分组,要不就把这个代码放在主函数最开始,这样模块里就不用在进行分组
- 定义NVIC结构体变量
- 引出结构体成员变量
- 给各个成员变量赋值
- 最后调用NVIC初始化函数,参数设置为NVIC结构体的地址
最后通过NVIC,外部中断信号就能进入CPU
这样CPU才能收到中断信号,才能跳转到中断函数执行中断程序
配置NVIC示例
/*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线路的抢占优先级为1
NVIC_InitStructure.NVIC_IRQChannelSubPriority = 1; //指定NVIC线路的响应优先级为1
NVIC_Init(&NVIC_InitStructure); //将结构体变量交给NVIC_Init,配置NVIC外设
编写中断函数
在STM32中中断函数的名字都是固定的,每个中断通道都对应一个中断函数,中断函数的名字可以参考启动文件(startup_stm32f10x_md.s),中断向量表。
以IRQHandler结尾的字符串就是中断函数的名字。
//中断函数名称
__Vectors DCD __initial_sp ; Top of Stack
DCD Reset_Handler ; Reset Handler
DCD NMI_Handler ; NMI Handler
DCD HardFault_Handler ; Hard Fault Handler
DCD MemManage_Handler ; MPU Fault Handler
DCD BusFault_Handler ; Bus Fault Handler
DCD UsageFault_Handler ; Usage Fault Handler
DCD 0 ; Reserved
DCD 0 ; Reserved
DCD 0 ; Reserved
DCD 0 ; Reserved
DCD SVC_Handler ; SVCall Handler
DCD DebugMon_Handler ; Debug Monitor Handler
DCD 0 ; Reserved
DCD PendSV_Handler ; PendSV Handler
DCD SysTick_Handler ; SysTick Handler
; External Interrupts
DCD WWDG_IRQHandler ; Window Watchdog
DCD PVD_IRQHandler ; PVD through EXTI Line detect
DCD TAMPER_IRQHandler ; Tamper
DCD RTC_IRQHandler ; RTC
DCD FLASH_IRQHandler ; Flash
DCD RCC_IRQHandler ; RCC
DCD EXTI0_IRQHandler ; EXTI Line 0
DCD EXTI1_IRQHandler ; EXTI Line 1
DCD EXTI2_IRQHandler ; EXTI Line 2
DCD EXTI3_IRQHandler ; EXTI Line 3
DCD EXTI4_IRQHandler ; EXTI Line 4
DCD DMA1_Channel1_IRQHandler ; DMA1 Channel 1
DCD DMA1_Channel2_IRQHandler ; DMA1 Channel 2
DCD DMA1_Channel3_IRQHandler ; DMA1 Channel 3
DCD DMA1_Channel4_IRQHandler ; DMA1 Channel 4
DCD DMA1_Channel5_IRQHandler ; DMA1 Channel 5
DCD DMA1_Channel6_IRQHandler ; DMA1 Channel 6
DCD DMA1_Channel7_IRQHandler ; DMA1 Channel 7
DCD ADC1_2_IRQHandler ; ADC1_2
DCD USB_HP_CAN1_TX_IRQHandler ; USB High Priority or CAN1 TX
DCD USB_LP_CAN1_RX0_IRQHandler ; USB Low Priority or CAN1 RX0
DCD CAN1_RX1_IRQHandler ; CAN1 RX1
DCD CAN1_SCE_IRQHandler ; CAN1 SCE
DCD EXTI9_5_IRQHandler ; EXTI Line 9..5
DCD TIM1_BRK_IRQHandler ; TIM1 Break
DCD TIM1_UP_IRQHandler ; TIM1 Update
DCD TIM1_TRG_COM_IRQHandler ; TIM1 Trigger and Commutation
DCD TIM1_CC_IRQHandler ; TIM1 Capture Compare
DCD TIM2_IRQHandler ; TIM2
DCD TIM3_IRQHandler ; TIM3
DCD TIM4_IRQHandler ; TIM4
DCD I2C1_EV_IRQHandler ; I2C1 Event
DCD I2C1_ER_IRQHandler ; I2C1 Error
DCD I2C2_EV_IRQHandler ; I2C2 Event
DCD I2C2_ER_IRQHandler ; I2C2 Error
DCD SPI1_IRQHandler ; SPI1
DCD SPI2_IRQHandler ; SPI2
DCD USART1_IRQHandler ; USART1
DCD USART2_IRQHandler ; USART2
DCD USART3_IRQHandler ; USART3
DCD EXTI15_10_IRQHandler ; EXTI Line 15..10
DCD RTCAlarm_IRQHandler ; RTC Alarm through EXTI Line
DCD USBWakeUp_IRQHandler ; USB Wakeup from suspend
__Vectors_End
其中EXTI15_10IRQHandler就是EXTI15_10的中断函数。
中断函数都是无参,无返回值的,中断函数名字不能写错,写错了就进不了中断,最好是从启动文件复制过来。
- 先进行中断标志位的判断,确保是我们想要的中断源触发的这个函数,因为这个函数EXTI10到EXTI15都能进来,所以要判断是否是外部中断14号线触发的中断
- 再执行中断程序
- 最后一定要清除中断标志位
void EXTI15_10_IRQHandler(void)
{ //1,中断标志位的判断,确保是我们想要的中断源触发的这个函数,因为这个函数EXTI10-15都能进来
if (EXTI_GetITStatus(EXTI_Line14) == SET) //所以要判断是否是外部中断14号线触发的中断
{
/*如果出现数据乱跳的现象,可再次判断引脚电平,以避免抖动*/
if (GPIO_ReadInputDataBit(GPIOB, GPIO_Pin_14) == 0)
{
CountSensor_Count ++; //计数值自增一次
}
//2,每次中断程序结束后都应该清楚中断标志位,如果不清除中断标志位,那它会一直申请中断导致卡死
EXTI_ClearITPendingBit(EXTI_Line14); //清除外部中断14号线的中断标志位
//中断标志位必须清除
//否则中断将连续不断地触发,导致主程序卡死
}
}
对射式红外传感器计次实例
CountSensor.c
#include "stm32f10x.h" // Device header
uint16_t CountSensor_Count; //全局变量,用于计数,统计中断次数
/**
* 函 数:计数传感器初始化
* 参 数:无
* 返 回 值:无
*/
void CountSensor_Init(void)//一般模块写初始化函数
//配置外部中断,根据PPt中外部中断的整体结构图来配置东西
//简单来说就是将GPIC到NVIC这一路中出现的外设模块配置好,打通信号电路
{
/*开启时钟*/
//注意GPIOB是APB2的外设,注意函数和参数的对应
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOB, ENABLE); //开启GPIOB的时钟
RCC_APB2PeriphClockCmd(RCC_APB2Periph_AFIO, ENABLE); //开启AFIO的时钟,外部中断必须开启AFIO的时钟
//EXTI和NVIC两个外设的时钟是一直开的,不需要我们开启
//EXTI是一个独立外设,按理来说是要开启的,但是寄存器中没有EXIT时钟的控制位
//原因不知道,猜想可能是和EXIT唤醒有关或者一些电路设计上的考虑
//NVIC是内核的外设,内核的外设都是不需要开启时钟的,NVIC和Cpu一起都是住在“皇宫”里的
//RCC管的都是内核的外设,所以RCC管不着NVIC
/*GPIO初始化*/
GPIO_InitTypeDef GPIO_InitStructure; //定义结构体
//引出结构体各个成员
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IPU; //对于外部中断来说,要选择浮空输入、上拉输入、下拉输入模式,不知道可以查看参考手册,在GPIO一章
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_14;
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz; //不算重要
//初始化GPIOB外设
GPIO_Init(GPIOB, &GPIO_InitStructure); //将PB14引脚初始化为上拉输入
/*AFIO选择中断引脚*/
//AFIO外设的库函数与GPIO.h在一个文件里,函数是GPIO开头,但实际上操作的是AFIO
//第一个参数:选择某个GPIO外设作为外部中断源
//第二个参数:指定要配置的外部中断线
GPIO_EXTILineConfig(GPIO_PortSourceGPIOB, GPIO_PinSource14);//将外部中断的14号线映射到GPIOB,即选择PB14为外部中断引脚
/*执行完这个函数后,AFIO的第14个数据选择器就拨好了
其中输入端被拨到了GPIO外设上,对应的是PB14号引脚
输出端固定连接的是EXTI的第14个中断线路
这样PB14号引脚的电平信号就可以顺利通过AFIO,进入到后级的EXTI电路了*/
/*
void EXTI_DeInit(void);
void EXTI_Init(EXTI_InitTypeDef* EXTI_InitStruct);
void EXTI_StructInit(EXTI_InitTypeDef* EXTI_InitStruct);
这三个函数基本上所有外设都有,每个外设都需要设置这些类型的函数
*/
/*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外设
/*
目前的配置为:将EXTI的第14个线路配置为中断模式,下降沿触发,开启中断
这样PB14的电平信号就能通过EXTI通向下一级NVIC
*/
/*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线路的抢占优先级为1
NVIC_InitStructure.NVIC_IRQChannelSubPriority = 1; //指定NVIC线路的响应优先级为1
NVIC_Init(&NVIC_InitStructure); //将结构体变量交给NVIC_Init,配置NVIC外设
}
/**
* 函 数:获取计数传感器的计数值
* 参 数:无
* 返 回 值:计数值,范围:0~65535
*/
uint16_t CountSensor_Get(void)
{
return CountSensor_Count;
}
/**
* 函 数:EXTI15_10外部中断函数
* 参 数:无
* 返 回 值:无
* 注意事项:此函数为中断函数,无需调用,中断触发后自动执行
* 函数名为预留的指定名称,可以从启动文件复制
* 请确保函数名正确,不能有任何差异,否则中断函数将不能进入
在STm32中中断函数的名字都是固定的,每个中断通道都对应一个中断函数,中断函数的名字可以参考启动文件,中断向量表
以IRQHandler结尾的字符串就是中断函数的名字
中断函数都是无参,无返回值的,中断函数名字不能写错,写错了就进不了中断,最好是从启动文件复制过来
*/
void EXTI15_10_IRQHandler(void)
{ //1,中断标志位的判断,确保是我们想要的中断源触发的这个函数,因为这个函数EXTI10-15都能进来
if (EXTI_GetITStatus(EXTI_Line14) == SET) //所以要判断是否是外部中断14号线触发的中断
{
/*如果出现数据乱跳的现象,可再次判断引脚电平,以避免抖动*/
if (GPIO_ReadInputDataBit(GPIOB, GPIO_Pin_14) == 0)
{
CountSensor_Count ++; //计数值自增一次
}
//2,每次中断程序结束后都应该清楚中断标志位,如果不清除中断标志位,那它会一直申请中断导致卡死
EXTI_ClearITPendingBit(EXTI_Line14); //清除外部中断14号线的中断标志位
//中断标志位必须清除
//否则中断将连续不断地触发,导致主程序卡死
}
}
什么样的设备需要用到外部中断,使用外部中断的好处
使用外部中断模块的特性
对于STM32来说,想要获取的信号是外部驱动的很快的突发信号
比如:旋转编码器的输出信号,这个信号是突发的,STM32不知道什么时候会来,同时旋转编码器是外部驱动的,STM32只能被动读取,旋转编码器的输出信号非常快,STM32稍晚来一点,就会错过很多波形信号,因此就要使用外部中断;红外遥控接收头的输出。
按键不推荐使用外部中断来读取按键:虽然按键的动作是外部驱动的突发事件,但外部中断不好处理按键抖动和松手检测的问题,对于按键来说,他的输出波形也不是转瞬即逝的,要求不高的话可以在主程序中循环读取或者定时器中断读取的方式,这样既可以做到后台读取按键值、不阻塞主程序,也很好的处理按键抖动和松手检测的问题。
旋转编码器介绍
旋转编码器功能∶用来测量位置、速度或旋转方向的装置,当其旋转轴旋转时,其输出端可以输出与旋转速度和方向对应的方波信号,读取方波信号的频率和相位信息即可得知旋转轴的速度和方向
类型:机械触点式/霍尔传感器式/光栅式
中间的圆的金属片是一个按键,可以按下去
旋转编码器是怎么区分正转和反转?
编码盘,是一系列像光栅一样的东西,上面是金属触点,在旋转时,依次接通和断开两边的触点,这个金属盘的位置是经过设计的,它能让两侧触点的通断产生一个90°的相位差,配合外部电路,编码器的两个输出就会输出这样的波形:正转时,B的波形方波信号相较于A的波形滞后90°;反转时,A的波形方波信号相较于B的波形方波信号滞后90°。这样就区分了正转和反转。这种相位相差90°的波形就叫正交波形。带正交波形信号输出的编码器是可以用来测方向的。
还有其他测方向的方法:一个引脚输出一组方波信号代表转速,另一个输出高低电平代表旋转方向,正转高电平,反转低电平。
外部中断代码部分
旋转编码器头文件
Encoder.h
#ifndef __ENCODER_H
#define __ENCODER_H
void Encoder_Init(void);
int16_t Encoder_Get(void);
#endif
旋转编码器.c文件代码
Encoder.c
#include "stm32f10x.h" // Device header
int16_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线路的抢占优先级为1
NVIC_InitStructure.NVIC_IRQChannelSubPriority = 1; //指定NVIC线路的响应优先级为1
NVIC_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线路的抢占优先级为1
NVIC_InitStructure.NVIC_IRQChannelSubPriority = 2; //指定NVIC线路的响应优先级为2
NVIC_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号线的中断标志位
//中断标志位必须清除
//否则中断将连续不断地触发,导致主程序卡死
}
}