1、STM32F40x外部中断介绍
串口中断
| 外部中断
|
中断源:串口接收完成之后,状态寄存器SR中有一个标志位置1
| 中断源:边沿(上升沿、下降沿)
外部中断最常用与按键处理,按键动作产生边沿信号,该信号可以出发芯片产生一个中断。
|
学习外部中断的目的:实现中断处理按键,按键中断处理的优点:响应及时。因为按键按下时,产生了按键中断。
中断按键:响应及时
扫描按键:需要不断的扫描,响应不及时,耗费CPU。
2、STM32F40x外部中断框图
- 外部中断控制器的中断源有两种:①外部的边沿信号;②软件事件寄存器的相应为置1
- 边沿触发信号:可以选择为上升沿、可以选择为下降沿、也可以选择为双边沿。
- “挂起请求寄存器”,其实相当于状态标识。
- “中断屏蔽寄存器”,相当于是否允许某路中断源触发产生中断。
- 脉冲发生器,主要是产生一个脉冲,进而触发产生其他片上外设进行工作。
- 上图中“23”:指有23条中断线:支持多达 23 个软件事件/中断请求。
STM32F40x 外部中断/事件控制器一共包含多达 23 个用于产生事件/中断请求的边沿检测器。每个输入线可
以独立地配置输出类型(脉冲或挂起)以及对应的触发事件(上升沿或下降沿触发或者双边沿触发),每个输入线都
可以独立地被屏蔽。挂起寄存器保持着状态线的中断请求。外部中断 0~4 有单独的中断入口,外部中断 5~9
共用一个中断入口,外部中断 10~15 共用一个中断入口。
EXTI 线 0~15:对应的外部 IO 口的输入中断
EXTI 线 16:连接到 PVD 输出
EXTI 线 17:连接到 RTC 闹钟事件
EXTI 线 18:连接到 USB OTG FS 唤醒事件
EXTI 线 19:连接到以太网唤醒事件
EXTI 线 20:连接到 USB OTG HS 唤醒事件
EXTI 线 21:连接到 RTC 入侵和时间戳事件
EXTI 线 22:连接到 RTC 唤醒事件
3、外部中断线路映象
(1)中断线0的输入信号只能所有IO0口中的一个。
(2)如何选择中断线的输入? 通过寄存器SYSCFG_EXTICRx (x:1~4),来进行选择
- STM32F40X的中断线有23条,其中有16条中断线是作为IO口中断线使用。每条中断线的输入信号只能有一个,通过SYSCFG_EXTICRx来进行选择。
- 中断控制器中的所有寄存器:上升沿触发选择寄存器、下降沿触发选择寄存器、中断屏蔽寄存器、挂起请求寄存器、软件中断事件寄存器、事件屏蔽寄存器,都是只有23位有效,每一位对应一条中断线。
4、外部中断相关寄存器
4.1、上升沿触发选择寄存器 (EXTI_RTSR)
每一位对应一条中断线。
0:禁止上升沿触发
1:允许上升沿触发
TR0~TR22是对应EXTI0~EXTI22
举例中断线3的触发信号配置为上升沿触发:EXTI->RTSR |= 1<<3;
4.2、下降沿触发选择寄存器 (EXTI_FTSR)
每一位对应一条中断线。
0:禁止下降沿触发
1:允许下降沿触发
TR0~TR22是对应EXTI0~EXTI22
举例中断线9的触发信号配置为下降沿触发,EXTI->FTST |= 1<<9;
4.3、中断屏蔽寄存器 (EXTI_IMR)
每一位对应一条中断线。
0:屏蔽来自 x 线的中断请求
1:开放来自 x 线的中断请求
MR0~MR22是对应EXTI0~EXTI22
举例:开放中断线5的中断请求·:EXTI->IMR |= 1<<5;
4.4、挂起寄存器 (EXTI_PR)
每一位对应一条中断线。
0:没有发生触发请求
1:发生了选择的触发的请求。即:产生了中断请求,中断请求是有相应的边沿信号来触发的。
PR0~PR22是对应EXTI0~EXTI22
当在外部中断线上发生了选择的边沿事件,该位被置“ 1”。
如何清零?答:在此位中写入“ 1”可以清零。
举例:
void EXTI0_IRQHandler(void) //中断线0的中断函数
{
if(EXTI->PR &(1<<0)) //判断挂起寄存器的位0是否为1
{
EXTI->PR |=(1<<0); //写入1,将相应的位清零
}
}
4.5、SYSCFG 外部中断配置寄存器 1 (SYSCFG_EXTICR1)
例如:位3~0,EXTI0[3:0]
用于选择外部中断线0的中断源输入管脚
0000: PA0 引脚
0001: PB0引脚
0010: PC0引脚
0011: PD0引脚
0100: PE0引脚
0101: PF0 引脚
0110: PG0 引脚
0111: PH0引脚
1000: PI0 引脚
举例:将GPIOB2配置为外部中断线2的中断源输入管脚,SYSCFG->EXTICR[0] |= 1<<8;
注意:写代码是,没有SYSCFG->EXTICR1,而是写成SYSCFG->EXTICR[0]。
4.6、SYSCFG 外部中断配置寄存器 2 (SYSCFG_EXTICR2)
4.7、SYSCFG 外部中断配置寄存器 4 (SYSCFG_EXTICR3)
4.8、SYSCFG 外部中断配置寄存器 4 (SYSCFG_EXTICR4)
作用和SYSCFG 外部中断配置寄存器 1 (SYSCFG_EXTICR1)一样,只是作用的管脚不一样
4.9、RCC APB2 外设时钟使能寄存器 (RCC_APB2ENR)
位14:
0:禁止系统配置控制器时钟
1:使能系统配置控制器时钟
注意:在配置SYSCFG_EXTICRx (x:1~4)之前,必须使能系统配置控制器的时钟
即:RCC->APB2ENR |= 1<<14;
5、外部中断线和中断函数
外部中断线有16条,但是并不会每条中断线都会对应有一个中断函数。请参考:
提问:配置GPIOD8是外部中断线8的中断输入管脚,配置GPIOE7是外部中断线7的中断输入管脚,如果产生了一次中断,并且进入了中断函数EXTI9_5_IRQHandler ,如何判断是哪个中断源管脚的边沿信号导致芯片产生中断?
答:每个中断线都有独立的挂起标志位,判断PR寄存器中的挂起标志位可以辨别导致产生进入EXTI9_5_IRQHandler的因素是什么。或者你也可以判断当前GPIO9-5的输入引脚电平
6、原理图
PA0:按下为高电平,所以设置上升沿触发
PE2~4:按下为低电平,选择为下降沿触发
- 确定中断线的中断触发信号
- 开放来自中断线 x 线的中断请求
- 使能系统配置控制器时钟
- 选择外部中断线的中断源输入管脚
- 使能中断通道NVIC_EnableIRQ(中断通道编号)
- 使能相应的GPIO口时钟
- 配置GPIO口为输入模式
7、中断函数的编程思路
- 找到中断线对应的中断函数
- 在中断函数中判断挂起寄存器的相应位是否为1,
- 如果为1则清零
- 延时消抖
- 判断按键是不是稳定的按下状态
- 如果是,则认为按键按下
外部中断0:中断源输入管脚配置为PA0
外部中断2:中断源输入管脚配置为PE2
外部中断3:中断源输入管脚配置为PE3
外部中断4:中断源输入管脚配置为PE4
8、软件设计
寄存器版
void delay(u32 i)
{
while(i--);
}
void LED_Init(void)
{
RCC->AHB1ENR |= 1 << 5;//使能GPIOF的时钟
RCC->AHB1ENR |= 1 << 2;//使能GPIOC的时钟
//LED1->PF6
GPIOF->MODER |= 1 << 12;//输出模式
GPIOF->OTYPER &=~(1<<6);//推挽模式
//LED2->PF9
GPIOF->MODER |= 1 << 18;
GPIOF->OTYPER &=~(1<<9);
//LED3->PF10
GPIOF->MODER |= 1 << 20;
GPIOF->OTYPER &=~(1<<10);
//LED4->PC0
GPIOC->MODER |= 1 << 0;
GPIOC->OTYPER &=~(1<<0);
}
void key_Init(void)
{
RCC->AHB1ENR |= 1 << 0;//使能GPIOA的时钟
RCC->AHB1ENR |= 1 << 4;//使能GPIOE的时钟
//KEY1->PA0
GPIOA->MODER &= ~(3 << 0);//输出模式
GPIOA->PUPDR &= 2 << 0;//PA0设置为下拉,平时为低电平
//KEY2->PE2
GPIOE->MODER &= ~(3 << 4);//PE2,3,4设置为上拉,平时为高电平
GPIOE->PUPDR |= 1 << 4;
//KEY3->PE3
GPIOE->MODER &= ~(3 << 6);
GPIOE->PUPDR |= 1 << 6;
//KEY4->PE4
GPIOE->MODER &= ~(3 << 8);
GPIOE->PUPDR |= 1 << 8;
}
void EXTI0_IRQHandler(void)
{
if(EXTI->PR &(1<<0)) //判断挂起寄存器的位0是否为1
{
GPIOF->ODR ^= 1 << 6;//led1状态翻转
EXTI->PR=1<<0; //清除 LINE0 上的中断标志位
}
}
//外部中断 2 服务程序
void EXTI2_IRQHandler(void)
{
if(EXTI->PR &(1<<2)) //判断挂起寄存器的位0是否为1
{
GPIOF->ODR ^= 1 << 9;//led2状态翻转
EXTI->PR=1<<2; //清除 LINE2 上的中断标志位
}
}
//外部中断 3 服务程序
void EXTI3_IRQHandler(void)
{
if(EXTI->PR &(1<<3)) //判断挂起寄存器的位0是否为1
{
GPIOF->ODR ^= 1 << 10;//led3状态翻转
EXTI->PR=1<<3; //清除 LINE3 上的中断标志位
}
}
//外部中断 4 服务程序
void EXTI4_IRQHandler(void)
{
if(EXTI->PR &(1<<4)) //判断挂起寄存器的位0是否为1
{
GPIOC->ODR ^= 1 << 0;//led4状态翻转
EXTI->PR=1<<4; //清除 LINE0 上的中断标志位
}
}
void ExitX_Init(void)
{
EXTI->RTSR |= 1<<0;//中断线0设置为上升沿触发
EXTI->IMR |= 1<<0;//开放来自exit0的请求
SYSCFG->EXTICR[0] |= 1<<0;//将GPIOA0配置为Exit0的中断源输入管脚
RCC->APB2ENR |= 1<<14;//使能系统配置控制器时钟,必须设置
NVIC_EnableIRQ(EXTI0_IRQn); //使能中断函数
EXTI->FTSR |= 1<<2;
EXTI->IMR |= 1<<2;
SYSCFG->EXTICR[0] |= 4<<8;
NVIC_EnableIRQ(EXTI2_IRQn);
EXTI->FTSR |= 1<<3;
EXTI->IMR |= 1<<3;
SYSCFG->EXTICR[0] |= 4<<12;
NVIC_EnableIRQ(EXTI3_IRQn);
EXTI->FTSR |= 1<<4;
EXTI->IMR |= 1<<4;
SYSCFG->EXTICR[1] |= 4<<0;
NVIC_EnableIRQ(EXTI4_IRQn);
}
int main(void)
{
LED_Init();
key_Init();
ExitX_Init();
while(1)
{
}
}
库函数版
void LED_Init(void)
{
GPIO_InitTypeDef LED_InitTypeDef;
RCC_AHB1PeriphClockCmd(RCC_AHB1Periph_GPIOF,ENABLE); //使能 F 端口
RCC_AHB1PeriphClockCmd(RCC_AHB1Periph_GPIOC,ENABLE); //使能 C 端口
LED_InitTypeDef.GPIO_Mode =GPIO_Mode_OUT; //配置为输出模式
LED_InitTypeDef.GPIO_OType =GPIO_OType_PP; //配置为推挽输出
LED_InitTypeDef.GPIO_Pin =GPIO_Pin_9|GPIO_Pin_10|GPIO_Pin_6; //初始化GPIOF6/9/10;
LED_InitTypeDef.GPIO_PuPd =GPIO_PuPd_NOPULL; //配置为无上下拉状态
LED_InitTypeDef.GPIO_Speed =GPIO_Speed_50MHz; //速度设置为 50MHz
GPIO_Init(GPIOF,&LED_InitTypeDef);
LED_InitTypeDef.GPIO_Mode =GPIO_Mode_OUT; //配置为输出模式
LED_InitTypeDef.GPIO_OType =GPIO_OType_PP; //配置为推挽输出
LED_InitTypeDef.GPIO_Pin =GPIO_Pin_0; //初始化 GPIOC0;
LED_InitTypeDef.GPIO_PuPd =GPIO_PuPd_NOPULL; //配置为无上下拉状态
LED_InitTypeDef.GPIO_Speed =GPIO_Speed_50MHz; //速度设置为 50MHz
GPIO_Init(GPIOC,&LED_InitTypeDef); //初始化 GPIOC0;
}
void key_Init(void)
{
GPIO_InitTypeDef LED_InitTypeDef;
RCC_AHB1PeriphClockCmd(RCC_AHB1Periph_GPIOA|RCC_AHB1Periph_GPIOE,ENABLE); //使能GPIOA 和 GPIOE 时钟
LED_InitTypeDef.GPIO_Mode =GPIO_Mode_IN; //配置为输入模式
LED_InitTypeDef.GPIO_Pin =GPIO_Pin_2|GPIO_Pin_3|GPIO_Pin_4; //初始化 GPIOE2/3/4;
LED_InitTypeDef.GPIO_PuPd =GPIO_PuPd_UP; //上拉
GPIO_Init(GPIOE,&LED_InitTypeDef);
LED_InitTypeDef.GPIO_Mode =GPIO_Mode_IN; //配置为输入模式
LED_InitTypeDef.GPIO_Pin =GPIO_Pin_0; //初始化 GPIOA0;
LED_InitTypeDef.GPIO_PuPd =GPIO_PuPd_DOWN; //下拉
GPIO_Init(GPIOA,&LED_InitTypeDef); //初始化 GPIOA0;
}
void EXTI0_IRQHandler(void)
{
if(EXTI_GetITStatus(EXTI_Line0)) //判断挂起寄存器的位0是否为1
{
GPIO_ToggleBits(GPIOF,GPIO_Pin_6); //led1状态翻转
EXTI_ClearITPendingBit(EXTI_Line0); //清除 LINE0上的中断标志位
}
}
//外部中断 2 服务程序
void EXTI2_IRQHandler(void)
{
if(EXTI_GetITStatus(EXTI_Line2)) //判断挂起寄存器的位0是否为1
{
GPIO_ToggleBits(GPIOF,GPIO_Pin_9);//led2状态翻转
EXTI_ClearITPendingBit(EXTI_Line2); //清除 LINE2 上的中断标志位
}
}
//外部中断 3 服务程序
void EXTI3_IRQHandler(void)
{
if(EXTI_GetITStatus(EXTI_Line3)) //判断挂起寄存器的位0是否为1
{
GPIO_ToggleBits(GPIOF,GPIO_Pin_10);//led3状态翻转
EXTI_ClearITPendingBit(EXTI_Line3); //清除 LINE3 上的中断标志位
}
}
//外部中断 4 服务程序
void EXTI4_IRQHandler(void)
{
if(EXTI_GetITStatus(EXTI_Line4)) //判断挂起寄存器的位0是否为1
{
GPIO_ToggleBits(GPIOC,GPIO_Pin_0);//led4状态翻转
EXTI_ClearITPendingBit(EXTI_Line4); //清除 LINE4 上的中断标志位
}
}
void ExitX_Init(void)
{
EXTI_InitTypeDef EXTI_InitTypeSter;
NVIC_InitTypeDef NVIC_InitTypeSter;
RCC_APB2PeriphClockCmd(RCC_APB2Periph_SYSCFG,ENABLE); //使能中断时钟
SYSCFG_EXTILineConfig(EXTI_PortSourceGPIOE, EXTI_PinSource2);
SYSCFG_EXTILineConfig(EXTI_PortSourceGPIOE, EXTI_PinSource3);
SYSCFG_EXTILineConfig(EXTI_PortSourceGPIOE, EXTI_PinSource4);
SYSCFG_EXTILineConfig(EXTI_PortSourceGPIOA, EXTI_PinSource0);
EXTI_InitTypeSter.EXTI_Line =EXTI_Line2|EXTI_Line3|EXTI_Line4; //外部中断线 2,3,4
EXTI_InitTypeSter.EXTI_LineCmd =ENABLE; //使能
EXTI_InitTypeSter.EXTI_Mode =EXTI_Mode_Interrupt; //外部中断模式
EXTI_InitTypeSter.EXTI_Trigger =EXTI_Trigger_Falling; //下降沿触发
EXTI_Init(&EXTI_InitTypeSter);
EXTI_InitTypeSter.EXTI_Line =EXTI_Line0; //外部中断线 0
EXTI_InitTypeSter.EXTI_LineCmd =ENABLE; //使能
EXTI_InitTypeSter.EXTI_Mode =EXTI_Mode_Interrupt; //外部中断模式
EXTI_InitTypeSter.EXTI_Trigger =EXTI_Trigger_Rising; //上升沿触发
EXTI_Init(&EXTI_InitTypeSter);
NVIC_InitTypeSter.NVIC_IRQChannel =EXTI2_IRQn; //外部中断 2
NVIC_InitTypeSter.NVIC_IRQChannelCmd =ENABLE; //使能外部中断 2
//在不需要优先级的情况下可以不设置,寄存器版我就懒得设置
// NVIC_InitTypeSter.NVIC_IRQChannelPreemptionPriority =1; //抢占优先级 1
// NVIC_InitTypeSter.NVIC_IRQChannelSubPriority =0; //子优先级 0
NVIC_Init(&NVIC_InitTypeSter);
NVIC_InitTypeSter.NVIC_IRQChannel =EXTI3_IRQn; //外部中断 3
// NVIC_InitTypeSter.NVIC_IRQChannelCmd =ENABLE; //使能外部中断 3
// NVIC_InitTypeSter.NVIC_IRQChannelPreemptionPriority =2; //抢占优先级 2
// NVIC_InitTypeSter.NVIC_IRQChannelSubPriority =0; //子优先级 0
NVIC_Init(&NVIC_InitTypeSter);
NVIC_InitTypeSter.NVIC_IRQChannel =EXTI4_IRQn; //外部中断 4
NVIC_InitTypeSter.NVIC_IRQChannelCmd =ENABLE; //使能外部中断 4
// NVIC_InitTypeSter.NVIC_IRQChannelPreemptionPriority =3; //抢占优先级 3
// NVIC_InitTypeSter.NVIC_IRQChannelSubPriority =0; //子优先级 0
NVIC_Init(&NVIC_InitTypeSter);
NVIC_InitTypeSter.NVIC_IRQChannel =EXTI0_IRQn; //外部中断 0
NVIC_InitTypeSter.NVIC_IRQChannelCmd =ENABLE; //使能外部中断 0
// NVIC_InitTypeSter.NVIC_IRQChannelPreemptionPriority =0; //抢占优先级 1
// NVIC_InitTypeSter.NVIC_IRQChannelSubPriority =0; //子优先级 0
NVIC_Init(&NVIC_InitTypeSter);
}
int main(void)
{
LED_Init();
key_Init();
ExitX_Init();
while(1)
{
}
}
1、STM32F40x外部中断介绍
串口中断
| 外部中断
|
中断源:串口接收完成之后,状态寄存器SR中有一个标志位置1
| 中断源:边沿(上升沿、下降沿)
外部中断最常用与按键处理,按键动作产生边沿信号,该信号可以出发芯片产生一个中断。
|
学习外部中断的目的:实现中断处理按键,按键中断处理的优点:响应及时。因为按键按下时,产生了按键中断。
中断按键:响应及时
扫描按键:需要不断的扫描,响应不及时,耗费CPU。
2、STM32F40x外部中断框图
- 外部中断控制器的中断源有两种:①外部的边沿信号;②软件事件寄存器的相应为置1
- 边沿触发信号:可以选择为上升沿、可以选择为下降沿、也可以选择为双边沿。
- “挂起请求寄存器”,其实相当于状态标识。
- “中断屏蔽寄存器”,相当于是否允许某路中断源触发产生中断。
- 脉冲发生器,主要是产生一个脉冲,进而触发产生其他片上外设进行工作。
- 上图中“23”:指有23条中断线:支持多达 23 个软件事件/中断请求。
STM32F40x 外部中断/事件控制器一共包含多达 23 个用于产生事件/中断请求的边沿检测器。每个输入线可
以独立地配置输出类型(脉冲或挂起)以及对应的触发事件(上升沿或下降沿触发或者双边沿触发),每个输入线都
可以独立地被屏蔽。挂起寄存器保持着状态线的中断请求。外部中断 0~4 有单独的中断入口,外部中断 5~9
共用一个中断入口,外部中断 10~15 共用一个中断入口。
EXTI 线 0~15:对应的外部 IO 口的输入中断
EXTI 线 16:连接到 PVD 输出
EXTI 线 17:连接到 RTC 闹钟事件
EXTI 线 18:连接到 USB OTG FS 唤醒事件
EXTI 线 19:连接到以太网唤醒事件
EXTI 线 20:连接到 USB OTG HS 唤醒事件
EXTI 线 21:连接到 RTC 入侵和时间戳事件
EXTI 线 22:连接到 RTC 唤醒事件
3、外部中断线路映象
(1)中断线0的输入信号只能所有IO0口中的一个。
(2)如何选择中断线的输入? 通过寄存器SYSCFG_EXTICRx (x:1~4),来进行选择
- STM32F40X的中断线有23条,其中有16条中断线是作为IO口中断线使用。每条中断线的输入信号只能有一个,通过SYSCFG_EXTICRx来进行选择。
- 中断控制器中的所有寄存器:上升沿触发选择寄存器、下降沿触发选择寄存器、中断屏蔽寄存器、挂起请求寄存器、软件中断事件寄存器、事件屏蔽寄存器,都是只有23位有效,每一位对应一条中断线。
4、外部中断相关寄存器
4.1、上升沿触发选择寄存器 (EXTI_RTSR)
每一位对应一条中断线。
0:禁止上升沿触发
1:允许上升沿触发
TR0~TR22是对应EXTI0~EXTI22
举例中断线3的触发信号配置为上升沿触发:EXTI->RTSR |= 1<<3;
4.2、下降沿触发选择寄存器 (EXTI_FTSR)
每一位对应一条中断线。
0:禁止下降沿触发
1:允许下降沿触发
TR0~TR22是对应EXTI0~EXTI22
举例中断线9的触发信号配置为下降沿触发,EXTI->FTST |= 1<<9;
4.3、中断屏蔽寄存器 (EXTI_IMR)
每一位对应一条中断线。
0:屏蔽来自 x 线的中断请求
1:开放来自 x 线的中断请求
MR0~MR22是对应EXTI0~EXTI22
举例:开放中断线5的中断请求·:EXTI->IMR |= 1<<5;
4.4、挂起寄存器 (EXTI_PR)
每一位对应一条中断线。
0:没有发生触发请求
1:发生了选择的触发的请求。即:产生了中断请求,中断请求是有相应的边沿信号来触发的。
PR0~PR22是对应EXTI0~EXTI22
当在外部中断线上发生了选择的边沿事件,该位被置“ 1”。
如何清零?答:在此位中写入“ 1”可以清零。
举例:
void EXTI0_IRQHandler(void) //中断线0的中断函数
{
if(EXTI->PR &(1<<0)) //判断挂起寄存器的位0是否为1
{
EXTI->PR |=(1<<0); //写入1,将相应的位清零
}
}
4.5、SYSCFG 外部中断配置寄存器 1 (SYSCFG_EXTICR1)
例如:位3~0,EXTI0[3:0]
用于选择外部中断线0的中断源输入管脚
0000: PA0 引脚
0001: PB0引脚
0010: PC0引脚
0011: PD0引脚
0100: PE0引脚
0101: PF0 引脚
0110: PG0 引脚
0111: PH0引脚
1000: PI0 引脚
举例:将GPIOB2配置为外部中断线2的中断源输入管脚,SYSCFG->EXTICR[0] |= 1<<8;
注意:写代码是,没有SYSCFG->EXTICR1,而是写成SYSCFG->EXTICR[0]。
4.6、SYSCFG 外部中断配置寄存器 2 (SYSCFG_EXTICR2)
4.7、SYSCFG 外部中断配置寄存器 4 (SYSCFG_EXTICR3)
4.8、SYSCFG 外部中断配置寄存器 4 (SYSCFG_EXTICR4)
作用和SYSCFG 外部中断配置寄存器 1 (SYSCFG_EXTICR1)一样,只是作用的管脚不一样
4.9、RCC APB2 外设时钟使能寄存器 (RCC_APB2ENR)
位14:
0:禁止系统配置控制器时钟
1:使能系统配置控制器时钟
注意:在配置SYSCFG_EXTICRx (x:1~4)之前,必须使能系统配置控制器的时钟
即:RCC->APB2ENR |= 1<<14;
5、外部中断线和中断函数
外部中断线有16条,但是并不会每条中断线都会对应有一个中断函数。请参考:
提问:配置GPIOD8是外部中断线8的中断输入管脚,配置GPIOE7是外部中断线7的中断输入管脚,如果产生了一次中断,并且进入了中断函数EXTI9_5_IRQHandler ,如何判断是哪个中断源管脚的边沿信号导致芯片产生中断?
答:每个中断线都有独立的挂起标志位,判断PR寄存器中的挂起标志位可以辨别导致产生进入EXTI9_5_IRQHandler的因素是什么。或者你也可以判断当前GPIO9-5的输入引脚电平
6、原理图
PA0:按下为高电平,所以设置上升沿触发
PE2~4:按下为低电平,选择为下降沿触发
- 确定中断线的中断触发信号
- 开放来自中断线 x 线的中断请求
- 使能系统配置控制器时钟
- 选择外部中断线的中断源输入管脚
- 使能中断通道NVIC_EnableIRQ(中断通道编号)
- 使能相应的GPIO口时钟
- 配置GPIO口为输入模式
7、中断函数的编程思路
- 找到中断线对应的中断函数
- 在中断函数中判断挂起寄存器的相应位是否为1,
- 如果为1则清零
- 延时消抖
- 判断按键是不是稳定的按下状态
- 如果是,则认为按键按下
外部中断0:中断源输入管脚配置为PA0
外部中断2:中断源输入管脚配置为PE2
外部中断3:中断源输入管脚配置为PE3
外部中断4:中断源输入管脚配置为PE4
8、软件设计
寄存器版
void delay(u32 i)
{
while(i--);
}
void LED_Init(void)
{
RCC->AHB1ENR |= 1 << 5;//使能GPIOF的时钟
RCC->AHB1ENR |= 1 << 2;//使能GPIOC的时钟
//LED1->PF6
GPIOF->MODER |= 1 << 12;//输出模式
GPIOF->OTYPER &=~(1<<6);//推挽模式
//LED2->PF9
GPIOF->MODER |= 1 << 18;
GPIOF->OTYPER &=~(1<<9);
//LED3->PF10
GPIOF->MODER |= 1 << 20;
GPIOF->OTYPER &=~(1<<10);
//LED4->PC0
GPIOC->MODER |= 1 << 0;
GPIOC->OTYPER &=~(1<<0);
}
void key_Init(void)
{
RCC->AHB1ENR |= 1 << 0;//使能GPIOA的时钟
RCC->AHB1ENR |= 1 << 4;//使能GPIOE的时钟
//KEY1->PA0
GPIOA->MODER &= ~(3 << 0);//输出模式
GPIOA->PUPDR &= 2 << 0;//PA0设置为下拉,平时为低电平
//KEY2->PE2
GPIOE->MODER &= ~(3 << 4);//PE2,3,4设置为上拉,平时为高电平
GPIOE->PUPDR |= 1 << 4;
//KEY3->PE3
GPIOE->MODER &= ~(3 << 6);
GPIOE->PUPDR |= 1 << 6;
//KEY4->PE4
GPIOE->MODER &= ~(3 << 8);
GPIOE->PUPDR |= 1 << 8;
}
void EXTI0_IRQHandler(void)
{
if(EXTI->PR &(1<<0)) //判断挂起寄存器的位0是否为1
{
GPIOF->ODR ^= 1 << 6;//led1状态翻转
EXTI->PR=1<<0; //清除 LINE0 上的中断标志位
}
}
//外部中断 2 服务程序
void EXTI2_IRQHandler(void)
{
if(EXTI->PR &(1<<2)) //判断挂起寄存器的位0是否为1
{
GPIOF->ODR ^= 1 << 9;//led2状态翻转
EXTI->PR=1<<2; //清除 LINE2 上的中断标志位
}
}
//外部中断 3 服务程序
void EXTI3_IRQHandler(void)
{
if(EXTI->PR &(1<<3)) //判断挂起寄存器的位0是否为1
{
GPIOF->ODR ^= 1 << 10;//led3状态翻转
EXTI->PR=1<<3; //清除 LINE3 上的中断标志位
}
}
//外部中断 4 服务程序
void EXTI4_IRQHandler(void)
{
if(EXTI->PR &(1<<4)) //判断挂起寄存器的位0是否为1
{
GPIOC->ODR ^= 1 << 0;//led4状态翻转
EXTI->PR=1<<4; //清除 LINE0 上的中断标志位
}
}
void ExitX_Init(void)
{
EXTI->RTSR |= 1<<0;//中断线0设置为上升沿触发
EXTI->IMR |= 1<<0;//开放来自exit0的请求
SYSCFG->EXTICR[0] |= 1<<0;//将GPIOA0配置为Exit0的中断源输入管脚
RCC->APB2ENR |= 1<<14;//使能系统配置控制器时钟,必须设置
NVIC_EnableIRQ(EXTI0_IRQn); //使能中断函数
EXTI->FTSR |= 1<<2;
EXTI->IMR |= 1<<2;
SYSCFG->EXTICR[0] |= 4<<8;
NVIC_EnableIRQ(EXTI2_IRQn);
EXTI->FTSR |= 1<<3;
EXTI->IMR |= 1<<3;
SYSCFG->EXTICR[0] |= 4<<12;
NVIC_EnableIRQ(EXTI3_IRQn);
EXTI->FTSR |= 1<<4;
EXTI->IMR |= 1<<4;
SYSCFG->EXTICR[1] |= 4<<0;
NVIC_EnableIRQ(EXTI4_IRQn);
}
int main(void)
{
LED_Init();
key_Init();
ExitX_Init();
while(1)
{
}
}
库函数版
void LED_Init(void)
{
GPIO_InitTypeDef LED_InitTypeDef;
RCC_AHB1PeriphClockCmd(RCC_AHB1Periph_GPIOF,ENABLE); //使能 F 端口
RCC_AHB1PeriphClockCmd(RCC_AHB1Periph_GPIOC,ENABLE); //使能 C 端口
LED_InitTypeDef.GPIO_Mode =GPIO_Mode_OUT; //配置为输出模式
LED_InitTypeDef.GPIO_OType =GPIO_OType_PP; //配置为推挽输出
LED_InitTypeDef.GPIO_Pin =GPIO_Pin_9|GPIO_Pin_10|GPIO_Pin_6; //初始化GPIOF6/9/10;
LED_InitTypeDef.GPIO_PuPd =GPIO_PuPd_NOPULL; //配置为无上下拉状态
LED_InitTypeDef.GPIO_Speed =GPIO_Speed_50MHz; //速度设置为 50MHz
GPIO_Init(GPIOF,&LED_InitTypeDef);
LED_InitTypeDef.GPIO_Mode =GPIO_Mode_OUT; //配置为输出模式
LED_InitTypeDef.GPIO_OType =GPIO_OType_PP; //配置为推挽输出
LED_InitTypeDef.GPIO_Pin =GPIO_Pin_0; //初始化 GPIOC0;
LED_InitTypeDef.GPIO_PuPd =GPIO_PuPd_NOPULL; //配置为无上下拉状态
LED_InitTypeDef.GPIO_Speed =GPIO_Speed_50MHz; //速度设置为 50MHz
GPIO_Init(GPIOC,&LED_InitTypeDef); //初始化 GPIOC0;
}
void key_Init(void)
{
GPIO_InitTypeDef LED_InitTypeDef;
RCC_AHB1PeriphClockCmd(RCC_AHB1Periph_GPIOA|RCC_AHB1Periph_GPIOE,ENABLE); //使能GPIOA 和 GPIOE 时钟
LED_InitTypeDef.GPIO_Mode =GPIO_Mode_IN; //配置为输入模式
LED_InitTypeDef.GPIO_Pin =GPIO_Pin_2|GPIO_Pin_3|GPIO_Pin_4; //初始化 GPIOE2/3/4;
LED_InitTypeDef.GPIO_PuPd =GPIO_PuPd_UP; //上拉
GPIO_Init(GPIOE,&LED_InitTypeDef);
LED_InitTypeDef.GPIO_Mode =GPIO_Mode_IN; //配置为输入模式
LED_InitTypeDef.GPIO_Pin =GPIO_Pin_0; //初始化 GPIOA0;
LED_InitTypeDef.GPIO_PuPd =GPIO_PuPd_DOWN; //下拉
GPIO_Init(GPIOA,&LED_InitTypeDef); //初始化 GPIOA0;
}
void EXTI0_IRQHandler(void)
{
if(EXTI_GetITStatus(EXTI_Line0)) //判断挂起寄存器的位0是否为1
{
GPIO_ToggleBits(GPIOF,GPIO_Pin_6); //led1状态翻转
EXTI_ClearITPendingBit(EXTI_Line0); //清除 LINE0上的中断标志位
}
}
//外部中断 2 服务程序
void EXTI2_IRQHandler(void)
{
if(EXTI_GetITStatus(EXTI_Line2)) //判断挂起寄存器的位0是否为1
{
GPIO_ToggleBits(GPIOF,GPIO_Pin_9);//led2状态翻转
EXTI_ClearITPendingBit(EXTI_Line2); //清除 LINE2 上的中断标志位
}
}
//外部中断 3 服务程序
void EXTI3_IRQHandler(void)
{
if(EXTI_GetITStatus(EXTI_Line3)) //判断挂起寄存器的位0是否为1
{
GPIO_ToggleBits(GPIOF,GPIO_Pin_10);//led3状态翻转
EXTI_ClearITPendingBit(EXTI_Line3); //清除 LINE3 上的中断标志位
}
}
//外部中断 4 服务程序
void EXTI4_IRQHandler(void)
{
if(EXTI_GetITStatus(EXTI_Line4)) //判断挂起寄存器的位0是否为1
{
GPIO_ToggleBits(GPIOC,GPIO_Pin_0);//led4状态翻转
EXTI_ClearITPendingBit(EXTI_Line4); //清除 LINE4 上的中断标志位
}
}
void ExitX_Init(void)
{
EXTI_InitTypeDef EXTI_InitTypeSter;
NVIC_InitTypeDef NVIC_InitTypeSter;
RCC_APB2PeriphClockCmd(RCC_APB2Periph_SYSCFG,ENABLE); //使能中断时钟
SYSCFG_EXTILineConfig(EXTI_PortSourceGPIOE, EXTI_PinSource2);
SYSCFG_EXTILineConfig(EXTI_PortSourceGPIOE, EXTI_PinSource3);
SYSCFG_EXTILineConfig(EXTI_PortSourceGPIOE, EXTI_PinSource4);
SYSCFG_EXTILineConfig(EXTI_PortSourceGPIOA, EXTI_PinSource0);
EXTI_InitTypeSter.EXTI_Line =EXTI_Line2|EXTI_Line3|EXTI_Line4; //外部中断线 2,3,4
EXTI_InitTypeSter.EXTI_LineCmd =ENABLE; //使能
EXTI_InitTypeSter.EXTI_Mode =EXTI_Mode_Interrupt; //外部中断模式
EXTI_InitTypeSter.EXTI_Trigger =EXTI_Trigger_Falling; //下降沿触发
EXTI_Init(&EXTI_InitTypeSter);
EXTI_InitTypeSter.EXTI_Line =EXTI_Line0; //外部中断线 0
EXTI_InitTypeSter.EXTI_LineCmd =ENABLE; //使能
EXTI_InitTypeSter.EXTI_Mode =EXTI_Mode_Interrupt; //外部中断模式
EXTI_InitTypeSter.EXTI_Trigger =EXTI_Trigger_Rising; //上升沿触发
EXTI_Init(&EXTI_InitTypeSter);
NVIC_InitTypeSter.NVIC_IRQChannel =EXTI2_IRQn; //外部中断 2
NVIC_InitTypeSter.NVIC_IRQChannelCmd =ENABLE; //使能外部中断 2
//在不需要优先级的情况下可以不设置,寄存器版我就懒得设置
// NVIC_InitTypeSter.NVIC_IRQChannelPreemptionPriority =1; //抢占优先级 1
// NVIC_InitTypeSter.NVIC_IRQChannelSubPriority =0; //子优先级 0
NVIC_Init(&NVIC_InitTypeSter);
NVIC_InitTypeSter.NVIC_IRQChannel =EXTI3_IRQn; //外部中断 3
// NVIC_InitTypeSter.NVIC_IRQChannelCmd =ENABLE; //使能外部中断 3
// NVIC_InitTypeSter.NVIC_IRQChannelPreemptionPriority =2; //抢占优先级 2
// NVIC_InitTypeSter.NVIC_IRQChannelSubPriority =0; //子优先级 0
NVIC_Init(&NVIC_InitTypeSter);
NVIC_InitTypeSter.NVIC_IRQChannel =EXTI4_IRQn; //外部中断 4
NVIC_InitTypeSter.NVIC_IRQChannelCmd =ENABLE; //使能外部中断 4
// NVIC_InitTypeSter.NVIC_IRQChannelPreemptionPriority =3; //抢占优先级 3
// NVIC_InitTypeSter.NVIC_IRQChannelSubPriority =0; //子优先级 0
NVIC_Init(&NVIC_InitTypeSter);
NVIC_InitTypeSter.NVIC_IRQChannel =EXTI0_IRQn; //外部中断 0
NVIC_InitTypeSter.NVIC_IRQChannelCmd =ENABLE; //使能外部中断 0
// NVIC_InitTypeSter.NVIC_IRQChannelPreemptionPriority =0; //抢占优先级 1
// NVIC_InitTypeSter.NVIC_IRQChannelSubPriority =0; //子优先级 0
NVIC_Init(&NVIC_InitTypeSter);
}
int main(void)
{
LED_Init();
key_Init();
ExitX_Init();
while(1)
{
}
}
举报