STM32项目学习9——外部中断
打开startup_stm32f10x_hd.s,文件中包含多种中断函数,项目采用的是EXTI0通道,所以找到EXTI0_IRQHandler函数。STM32中有20条外部中断线,其中16条是GPIO中断线(0~15),EXTI线16连接PVD输出,EXTI线17连接RTC闹钟事件,EXTI线18连接USB唤醒事件,EXTI线19连接以太网唤醒事件(只适用于互联产品)如图所示,以PA0为例,PA0
中断
在主程序运行过程中,出现特定的中断触发条件,CPU会暂停当前运行的程序,转而运行中断程序,中断程序执行完成后返回暂停的位置继续执行。
外部中断通用I/O映像如下图

STM32中有20条外部中断线,其中16条是GPIO中断线(0~15),EXTI线16连接PVD输出,EXTI线17连接RTC闹钟事件,EXTI线18连接USB唤醒事件,EXTI线19连接以太网唤醒事件(只适用于互联产品)
外部中断/事件控制器框图

1、边沿检测电路:上升沿、下降沿或双边沿
2、软件配置中断或事件寄存器
3、屏蔽中断寄存器或事件寄存器
4、请求挂起寄存器
5、中断请求发送给NVIC中断控制器
6、事件产生一个脉冲响应
复用功能
STM32的引脚默认是一个普通的GPIO,但是引脚可以被复用其他功能。也就是说有些GPIO除了输入输出的功能以外,还可以复用为定时器、计数器等启动功能
Pin定义

如图所示,以PA0为例,PA0既可以作为普通的I/O口用作输入输出,还可以作为TIM2_CH1_ETR,定时器2通道1的外部触发源
重映射
重映射属于复用功能的另一个功能,可以把具有特殊功能的引脚分配到其他引脚。如果某个功能陪重映射,该功能不遵循默认分配方式

如图,CAN1_RX有三个引脚可以实现:PA11、PB8、PD0。默认是PA11,但是如果CAN_REMAP的取值不同,那么CAN1_RX的引脚也会不同。假设PA11引脚被作为其他寄存器的引脚,此时又要CAN1启动,那么可以改变CAN_REMAP,通过其他引脚启用CAN_RX;此时PD0引脚是无法用作CAN1_RX。
中断嵌套控制器NVIC
所用的是STM32F10X芯片包含16个内核(异常)中断,可屏蔽中断有60个。当有多个中断触发,程序需要给这些中断排优先级,应该优先处理哪个中断。优先级号码越小,优先级越高。

stm32f10x.h内部所列中断

这60个可屏蔽中断就涉及到STM32中断分组。STM32将60个中断分成5个组,组0-组4,;同时每个中断都设置了抢占优先级和响应优先级或子优先级,配置有SCB->AIRCR寄存器的bits10-8定义。SCB->AIRCR是CM3内核定义的。
具体配置关系如下


CM3定义了8位用于设置中断优先级,但是STM32只选用了其中4位,抢占优先级高于响应优先级,同样数值越小优先级越高。
高优先级的抢占优先级是可以打断正在进行的低抢占优先级中断的
抢占优先级相同的中断,高响应优先级不可以打断低响应优先级的中断
抢占优先级相同的中断,当两个中断同时发生的情况下,哪个响应优先级高,哪个先执行
如果两个中断的抢占优先级和响应优先级都是一样的话,则看哪个中断先发生就先执行
打断情况只关于抢占优先级,与响应优先级无关
一般情况下,只设置一次中断优先级分组,例如分组2,设置好组后一般不改变分组。随意分组会导致管理混乱,程序出错。
中断优先级控制函数
1、中断优先级控制函数结构体
NVIC_SetPRriorityGrouping()
2、中断优先级结构体参数
NVIC_IRQChanel //定义中断类型,在stm32f10x.h找对应中断名称,如USART1_IRQN
NVIC_IRQChannelPreemptionPriority //定义中断抢断优先级
NVIC_IRQChannelSubPriority //定义中断响应优先级
NVIC_IRQChannelCmd //中断使能或失能
NVIC_Init //初始化NVIC中断控制器
中断优先级设置步骤
1、系统运行后先设置中断优先级分组,调用函数
2、针对每个中断,分配对应的抢占优先级和响应优先级
3、如果需要挂起或解挂,查看中断激活状态,分别调用对应函数即可
外部中断按键控制LED
USER新建EXTI文件夹,内部新建EXTI.c和EXTI.h
keil工程文件USER导入EXTI.c
以SW2,PA0引脚为中断触发,SW2按下,PA0接地,PA1输出低电平,LED亮。
首先先构建GPIO结构体,然后使能时钟信号。确定PA0是输入模式,当PA0接地,即开关按下,PA1输出低电平。但是外部中断还需要外部中断的时钟信号,但是在源文件没有发现RCC_APB2Periph_EXTI这个参数,因为EXTI外部中断时钟信号集成到AFIO中,所以还需要再加上AFIO。

GPIO_InitTypeDef exti_gpio_struct;
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA | RCC_APB2Periph_AFIO, ENABLE);
exti_gpio_struct.GPIO_Mode = GPIO_Mode_IPU;
exti_gpio_struct.GPIO_Pin = GPIO_Pin_0;
GPIO_Init(GPIOA, &exti_gpio_struct);
中断需要构建中断结构体用于配置外部中断参数,参数包括中断线;中断模式是中断还是事件;触发方式是上升沿、下降沿还是双边沿;中断线使能还是失能。最后完成配置应用。
EXTI_InitTypeDef exti_exti_struct;
exti_exti_struct.EXTI_Line = EXTI_Line0;
exti_exti_struct.EXTI_Mode = EXTI_Mode_Interrupt;
exti_exti_struct.EXTI_Trigger = EXTI_Trigger_Falling;
exti_exti_struct.EXTI_LineCmd = ENABLE;EXTI_Init(&exti_exti_struct);
中断还需要构建NVIC中断管理器结构体,参数包括中断通道;抢占优先级和响应优先级分配;中断通道使能还是失能。最后完成配置应用。
NVIC_InitTypeDef exti_nvic_struct;
exti_nvic_struct.NVIC_IRQChannel = EXTI0_IRQn;
exti_nvic_struct.NVIC_IRQChannelPreemptionPriority = 0;
exti_nvic_struct.NVIC_IRQChannelSubPriority = 0;
exti_nvic_struct.NVIC_IRQChannelCmd = ENABLE;
NVIC_Init(&exti_nvic_struct);
到这里还没有完成中断的相关工作。首先就是没有让PA0绑定中断线。

从图里可以看到,一条EXTI外部中断线与七个引脚相连,如果没有将PA0绑定EXTI0,那么程序就不知道EXTI0中断线应该监控哪个引脚的中断信号。因此需要把GPIO引脚PA0与中断线EXTI0绑定。
GPIO_EXTILineConfig(GPIO_PortSourceGPIOA, GPIO_PinSource0);
还有一步就是NVIC的分组模式,前面提到STM32一共有5组,分别对应不同的抢占优先级和响应优先级。所以还需要设置分组模式。
NVIC_PriorityGroupConfig(NVIC_PriorityGroup_2);
EXTI.c整体代码如下
#include "EXTI.h"
#include "stm32f10x.h"void Exti_Init(void)
{
GPIO_InitTypeDef exti_gpio_struct;//GPIO配置结构体
EXTI_InitTypeDef exti_exti_struct;//外部中断配置结构体
NVIC_InitTypeDef exti_nvic_struct;//NVIC配置结构体
//使能GPIO和AFIO的APB2总线时钟
//AFIO时钟必须开启才能配置中断线映射
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA | RCC_APB2Periph_AFIO, ENABLE);//配置优先级分组模式:Group2(2位抢占优先级,2位响应优先级)
NVIC_PriorityGroupConfig(NVIC_PriorityGroup_2);
//配置GPIO引脚与中断线映射:PA0映射到外部中断EXTI0
GPIO_EXTILineConfig(GPIO_PortSourceGPIOA, GPIO_PinSource0);
//配置GPIO引脚模式
exti_gpio_struct.GPIO_Mode = GPIO_Mode_IPU;//上拉输入(无信号为高电平)
exti_gpio_struct.GPIO_Pin = GPIO_Pin_0;//操作PA0引脚
GPIO_Init(GPIOA, &exti_gpio_struct);//应用GPIO配置
//配置外部中断参数
exti_exti_struct.EXTI_Line = EXTI_Line0;//使用外部中断线0(对应PA0)
exti_exti_struct.EXTI_Mode = EXTI_Mode_Interrupt;//设置为中断模式
exti_exti_struct.EXTI_Trigger = EXTI_Trigger_Falling;//下降沿触发
exti_exti_struct.EXTI_LineCmd = ENABLE;//使能EXTI线
EXTI_Init(&exti_exti_struct);//应用外部中断配置//配置中断控制器NVIC。Group2模式下,抢占优先级0-3,响应优先级0-3
exti_nvic_struct.NVIC_IRQChannel = EXTI0_IRQn;//中断通道为EXTI0
exti_nvic_struct.NVIC_IRQChannelPreemptionPriority = 0;//抢占优先级0
exti_nvic_struct.NVIC_IRQChannelSubPriority = 0;//响应优先级0
exti_nvic_struct.NVIC_IRQChannelCmd = ENABLE;//使能中断通道
NVIC_Init(&exti_nvic_struct);//应用NVIC配置
}
在主程序中实现EXTI0中断函数不是在main函数内部实现,而是具有特定的中断服务函数实现。

打开startup_stm32f10x_hd.s,文件中包含多种中断函数,项目采用的是EXTI0通道,所以找到EXTI0_IRQHandler函数。外部中断触发的判断采用的是EXTI_GetITStatus,外部中断获取标志位判断中断是否触发。另外,触发中断后需要在每个中断函数后面清除触发EXTI_ClearITPendingBit,否则中断函数只会触发一次,不会重复触发。

main.c代码如下
#include"stm32f10x.h"
#include"main.h"
#include"led.h"
#include"bear.h"
#include"button.h"
#include"Relay.h"
#include"Shake.h"
#include"433M.h"
#include"EXTI.h"int main()
{
Led_Init();
Exti_Init();
GPIO_SetBits(GPIOA, GPIO_Pin_1);
while(1)
{
}
}
void EXTI0_IRQHandler()//EXTI中断服务函数
{if(EXTI_GetITStatus(EXTI_Line0) != RESET)//EXTI0中断标志是否置位
{
GPIO_ResetBits(GPIOA, GPIO_Pin_1);//PA1输出低电平,LED亮
EXTI_ClearITPendingBit(EXTI_Line0);//必须清除标志,否则会重复触发中断
}
}
外部中断震动感应LED
Shake.c文件添加外部中断即可。注意,STM32引脚默认低电平,当震动传感器感受到震动瞬间,DO引脚会输出高电平,所以触发方式应该是上升沿触发,另外注意引脚与EXTI之间的对应关系。
Shake.c文件程序代码如下
#include "Shake.h"
#include "stm32f10x.h"void Shake_Init(void)
{
GPIO_InitTypeDef Shake_GPIO_Struct;
EXTI_InitTypeDef Shake_EXTI_Struct;
NVIC_InitTypeDef Shake_NVIC_Struct;
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOB | RCC_APB2Periph_AFIO, ENABLE);
GPIO_EXTILineConfig(GPIO_PortSourceGPIOB, GPIO_PinSource8);
NVIC_PriorityGroupConfig(NVIC_PriorityGroup_2);
Shake_GPIO_Struct.GPIO_Mode = GPIO_Mode_IPU;
Shake_GPIO_Struct.GPIO_Pin = GPIO_Pin_8;
GPIO_Init(GPIOB, &Shake_GPIO_Struct);
Shake_EXTI_Struct.EXTI_Line = EXTI_Line8;
Shake_EXTI_Struct.EXTI_Mode = EXTI_Mode_Interrupt;
Shake_EXTI_Struct.EXTI_Trigger = EXTI_Trigger_Rising;
Shake_EXTI_Struct.EXTI_LineCmd = ENABLE;
EXTI_Init(&Shake_EXTI_Struct);
Shake_NVIC_Struct.NVIC_IRQChannel = EXTI9_5_IRQn;
Shake_NVIC_Struct.NVIC_IRQChannelPreemptionPriority = 0;
Shake_NVIC_Struct.NVIC_IRQChannelSubPriority = 0;
Shake_NVIC_Struct.NVIC_IRQChannelCmd = ENABLE;
NVIC_Init(&Shake_NVIC_Struct);
}
为了区分按键占用的PA0,震动传感器D0口接到PB8引脚。因此相关GPIO,EXTI,NVIC配置会有所不同
1、PB8引脚对应的是GPIOB,但是AFIO和GPIOB均在APB2总线,所以可以同步启动
2、GPIO与EXTI绑定需要作对应。
3、GPIO配置与外部中断按键没太大区别。
4、EXTI配置注意EXTI_Linex要对应PB8,和触发方式是上升沿触发
5、NVIC中断通道注意只有EXTI0_IRQn~EXTI4_IRQn,EXTI5_IRQn~EXTI9_IRQn集成到一个参数控制:EXTI9_5_IRQn。具体可以在stm32f10x.h可以找到,如下图。

同样的,min.c也需要修改,主要是上面提到,只有0~4的中断通道的单独控制的,EXTI8有EXTI9_5控制。具体代码如下
#include"stm32f10x.h"
#include"main.h"
#include"led.h"
#include"bear.h"
#include"button.h"
#include"Relay.h"
#include"Shake.h"
#include"433M.h"
#include"EXTI.h"int main()
{
Led_Init();
Shake_Init();
GPIO_SetBits(GPIOA, GPIO_Pin_1);
while(1)
{
}
}
void EXTI9_5_IRQHandler()
{
if(EXTI_GetITStatus(EXTI_Line8) != RESET)
{
GPIO_ResetBits(GPIOA, GPIO_Pin_1);
EXTI_ClearITPendingBit(EXTI_Line8);
}
}
至此,还可以加入前面项目用到的蜂鸣器,做一个简单的震动报警器。只需要在main.c加上蜂鸣器函数即可
代码如下
#include"stm32f10x.h"
#include"main.h"
#include"led.h"
#include"bear.h"
#include"button.h"
#include"Relay.h"
#include"Shake.h"
#include"433M.h"
#include"EXTI.h"
void delay(uint16_t time)
{
uint16_t i = 0;
while(time--)
{
i = 12000;
while(i--);
}
}int main()
{
Led_Init();
Bear_Init();
Shake_Init();
GPIO_SetBits(GPIOA, GPIO_Pin_1);
GPIO_SetBits(GPIOA, GPIO_Pin_3);
while(1)
{
}
}
void EXTI9_5_IRQHandler()
{
if(EXTI_GetITStatus(EXTI_Line8) != RESET)
{
GPIO_ResetBits(GPIOA, GPIO_Pin_1);
GPIO_ResetBits(GPIOA, GPIO_Pin_3);
EXTI_ClearITPendingBit(EXTI_Line8);
}
}
更多推荐



所有评论(0)