STM32单片机中断系统

什么是中断?中断有什么用?

中断的作用?

用于响应和处理突发事件紧急事件,提高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位的响应优先级。

  1. 抢占优先级高的可以中断嵌套
  2. 响应优先级高的可以优先排队
  3. 抢占优先级和响应优先级均相同的按中断号排队(中断号为表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主要任务:

  1. 复用功能引脚重映射
  2. 中断引脚的选择

从这个图也可以反映出为什么相同的Pin不能同时触发中断

从前面的GPIO口里选择16个通道加上PVD、RTC、USB、ETH外设组成了EXTI20个输入信号

总结外部中断代码书写流程

根据外部中断整体结构图进行配置

配置RCC

什么是RCC(Reset and Clock Control 复位和时钟控制)

  1. 负责管理微控制器的时钟树,还有外设的晶振、内部振荡器以及各种时钟源的分频和倍频。
  2. 可以控制各个外设的时钟使能,确保只有当前使用设备才会消耗能量。

开启要使用的接口及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

  1. 定义结构体
  2. 引出结构体成员
    1. 选择端口输入模式(浮空、上拉、下拉)。不知道选择什么模式可以看参考手册,在外设的GPIO配置一章
    2. 选择Pin口
    3. GPIO_Speed不是很重要,默认设为GPIO_Speed_50MHz
  3. 最后初始化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);        //清除中断挂起的标志位
//总结就是:如果想在主程序中查看和清除标志位,就用上面两个函数;如果想在中断函数中查看和清除标志位,就用下面两个函数。本质上,这四个函数都是对状态寄存器的读写
  1. 定义EXTI结构体变量
  2. 将所有结构体成员引出
    • 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;        //指定触发信号的有效边沿,指定外部中断线为下降沿触发
  3. 最后把结构体变量放在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);
  1. 配置优先级分组:先占优先级(抢占优先级)和从占优先级(响应优先级)
    1. 参数取值,几位抢占、几位响应,具体选择要根据实际需求,一般中断不多,难导致中断冲突,对优先级分组来说比较随意。
    2. 注意 : 这个分组方式整个芯片只能用一种,按理说这个分组代码整个工程只需要执行一次就行了,如果把它放在模块里进行分组,那你就要确保每个模块都选的是同一个分组,要不就把这个代码放在主函数最开始,这样模块里就不用在进行分组
  2. 定义NVIC结构体变量
  3. 引出结构体成员变量
  4. 给各个成员变量赋值
  5. 最后调用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的中断函数。
中断函数都是无参无返回值的中断函数名字不能写错,写错了就进不了中断,最好是从启动文件复制过来。

  1. 先进行中断标志位的判断,确保是我们想要的中断源触发的这个函数,因为这个函数EXTI10到EXTI15都能进来,所以要判断是否是外部中断14号线触发的中断
  2. 再执行中断程序
  3. 最后一定要清除中断标志位
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号线的中断标志位
													//中断标志位必须清除
													//否则中断将连续不断地触发,导致主程序卡死
	}
}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值