STM32
直播中

吴湛

10年用户 756经验值
擅长:可编程逻辑
私信 关注
[问答]

怎么实现STM32F40x外部中断的软件设计?

什么是STM32F40x外部中断?
怎么实现STM32F40x外部中断的软件设计?

回帖(1)

张虎豹

2021-11-17 14:12:23
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)
        {
               
        }
}
举报

更多回帖

发帖
×
20
完善资料,
赚取积分