STM32——外部中断

Posted daker-code

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了STM32——外部中断相关的知识,希望对你有一定的参考价值。

  外部中断实验(瞎写)

  本文以 正点原子 的战舰 为 基础 进行实验, 实验目标为 按键0 熄灭所有, 按键2点亮led0,led1,按键1 蜂鸣器响,原理图如下:

蜂鸣器:

  技术图片技术图片

 

 

 

LED:

  技术图片

技术图片      技术图片

按键:

  技术图片 技术图片

 

 

 

 

  蜂鸣器和LED 正常配置即可

技术图片
#ifndef BSP_LED_H
#define BSP_LED_H

#include "stm32f10x.h"

#define BSP_BEEP_PIN                GPIO_Pin_8
#define BSP_BEEP_GPIO_PORT          GPIOB
#define BSP_BEEP_GPIO_CLK           RCC_APB2Periph_GPIOB

#define BSP_BEEP_ON         1
#define BSP_BEEP_OFF        0

#define    digitalHi(p,i)             {p->BSRR=i;}        //设置为高电平
#define digitalLo(p,i)             {p->BSRR=i<<16;}        //输出低电平
// #define digitalToggle(p,i)          {p->ODR ^=i;}        //输出反转状态

#define BEEP_TOGGLE        digitalToggle(BSP_BEEP_GPIO_PORT,BSP_BEEP_PIN)
#define BEEP_ON                digitalHi(BSP_BEEP_GPIO_PORT,BSP_BEEP_PIN)
#define BEEP_OFF            digitalLo(BSP_BEEP_GPIO_PORT,BSP_BEEP_PIN)           

void BEEP_GPIO_Config(void);

#endif // !BSP_LED_H
beep.h
技术图片
#include "bsp_beep.h"

/**
 * @brief 蜂鸣器初始化
 * 
 */
void BEEP_GPIO_Config(void)
{
    GPIO_InitTypeDef GPIO_InitStructure;

    RCC_APB2PeriphClockCmd(BSP_BEEP_GPIO_CLK, ENABLE);

    GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_PP;
    GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
    GPIO_InitStructure.GPIO_Pin = BSP_BEEP_PIN;

    GPIO_Init(BSP_BEEP_GPIO_PORT, &GPIO_InitStructure);
}
beep.c
技术图片
#ifndef __BSP_LED_H
#define    __BSP_LED_H

#include "stm32f10x.h"

//引脚定义
/*******************************************************/
//R 红色灯
#define LED1_PIN                  GPIO_Pin_5                 
#define LED1_GPIO_PORT            GPIOB                      
#define LED1_GPIO_CLK             RCC_APB2Periph_GPIOB

//G 绿色灯
#define LED2_PIN                  GPIO_Pin_5                 
#define LED2_GPIO_PORT            GPIOE                      
#define LED2_GPIO_CLK             RCC_APB2Periph_GPIOE

// //B 蓝色灯
// #define LED3_PIN                  GPIO_Pin_8                 
// #define LED3_GPIO_PORT            GPIOF                       
// #define LED3_GPIO_CLK             RCC_AHB1Periph_GPIOF
/************************************************************/


/** 控制LED灯亮灭的宏,
    * LED低电平亮,设置ON=0,OFF=1
    * 若LED高电平亮,把宏设置成ON=1 ,OFF=0 即可
    */
#define ON  0
#define OFF 1

/* 带参宏,可以像内联函数一样使用 */
#define LED1(a)    if (a)    
                    GPIO_SetBits(LED1_GPIO_PORT,LED1_PIN);                    else                            GPIO_ResetBits(LED1_GPIO_PORT,LED1_PIN)

#define LED2(a)    if (a)    
                    GPIO_SetBits(LED2_GPIO_PORT,LED2_PIN);                    else                            GPIO_ResetBits(LED2_GPIO_PORT,LED2_PIN

/* 直接操作寄存器的方法控制IO */
#define    digitalHi(p,i)             {p->BSRR=i;}        //设置为高电平
#define digitalLo(p,i)             {p->BSRR=i<<16;}        //输出低电平
#define digitalToggle(p,i)          {p->ODR ^=i;}        //输出反转状态

/* 定义控制IO的宏 */
#define LED1_TOGGLE        digitalToggle(LED1_GPIO_PORT,LED1_PIN)
#define LED1_OFF            digitalHi(LED1_GPIO_PORT,LED1_PIN)
#define LED1_ON                digitalLo(LED1_GPIO_PORT,LED1_PIN)

#define LED2_TOGGLE        digitalToggle(LED2_GPIO_PORT,LED2_PIN)
#define LED2_OFF            digitalHi(LED2_GPIO_PORT,LED2_PIN)
#define LED2_ON                digitalLo(LED2_GPIO_PORT,LED2_PIN)

void LED_GPIO_Config(void);

#endif /* __LED_H */
led.h
技术图片
#include "bsp_led.h"

/**
  * @brief  初始化控制LED的IO
  * @param  无
  * @retval 无
  */
void LED_GPIO_Config(void)
{
    /*定义一个GPIO_InitTypeDef类型的结构体*/
    GPIO_InitTypeDef GPIO_InitStructure;

    /*开启LED相关的GPIO外设时钟*/
    RCC_APB2PeriphClockCmd(LED1_GPIO_CLK | LED2_GPIO_CLK, ENABLE);

    /*选择要控制的GPIO引脚*/
    GPIO_InitStructure.GPIO_Pin = LED1_PIN;

    /*设置引脚模式为输出模式*/
    GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_PP;

    /*设置引脚速率为2MHz */
    GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;

    /*调用库函数,使用上面配置的GPIO_InitStructure初始化GPIO*/
    GPIO_Init(LED1_GPIO_PORT, &GPIO_InitStructure);

    /*选择要控制的GPIO引脚*/
    GPIO_InitStructure.GPIO_Pin = LED2_PIN;
    GPIO_Init(LED2_GPIO_PORT, &GPIO_InitStructure);

}
/*********************************************END OF FILE**********************/
led.c

 然后我们来看一下按键如何处理

103中有20个线路可以被配置成软件中断/事件线 ,其中通用I/O端口以下图的方式连接到16个外部中断/事件线上

技术图片

  但是预定义的外部中断服务函数只有6个

 

 

EXPORT EXTI0_IRQHandler
EXPORT EXTI1_IRQHandler
EXPORT EXTI2_IRQHandler
EXPORT EXTI3_IRQHandler
EXPORT EXTI4_IRQHandler
EXPORT EXTI9_5_IRQHandler
EXPORT EXTI15_10_IRQHandler

  0~1各自独占一个, 5~9用一个, 10~15用一个,按键的三个引脚 分别为 GPIOE2~4,正常配置按键

按键的代码有些杂乱,就不贴了。

  因为涉及到了外部中断,就和 AFIO 扯上关系了,所以需要使能 AFIO 的时钟  RCC_APB2PeriphClockCmd(RCC_APB2Periph_AFIO, ENABLE); ,如图 AFIO 中有 与外部中断相关的寄存器 

技术图片

  上面说了 通用I/O端口连接到16个外部中断/事件线上 ,所以估计是靠AFIO来实现引脚复用的, 所以,在去手册里看库函数之前,来看看 这个寄存器 配置啥的 

技术图片

 

 

   随手摘了一个,其他的差不多,很显然 这就是一个 连接 GPIO端口 和 中断源产生的 引脚 的寄存器, 理论上,初始化完毕寄存器后 AFIO_EXTICR2 的 值应该是 0x04, AFIO_EXTICR1的值因该是0x44

好了,接下来去找一下库函数(个人习惯库函数,因为从野火的407入手的)

  凭直觉,这玩意肯定和exti和gpio有关,不是gpio_extixxx 就是 exti_gpioxxx ,果然,函数长这样 void GPIO_EXTILineConfig(uint8_t GPIO_PortSource, uint8_t GPIO_PinSource) 当然不能这么粗暴的找,直接从 库文件里找 stm32f10x_gpio.h 中最底下 直接看函数 这是我看到的

技术图片

 

 

   扫下来好像就是这一个 有 EXTI 的, 看一看.c文件怎么写的

技术图片

技术图片

 

看这个英文因该没错了, 选择GPIO引脚作为 中断源使用 ,而且所操作的 也是 AFIO 的 EXTICR寄存器.

brief 是函数功能简介 param这里是介绍参数 retval是返回值

理论上 连接 连接 PE2 为 外部中断源2 的 代码 为    GPIO_EXTILineConfig(GPIO_PortSourceGPIOE,GPIO_PinSource2);    

这里说过如何寻找库函数后,后面就不再重复说明了,找到这个函数名后,更详细的信息可以去库文件 技术图片里查找, 个人觉得自己 查看 库文件也行,毕竟文档也是按照注释生成的.

 

 

配置完引脚剩下的就是外部中断的部分,嗯? 等等,中断 咱们还有 中断优先级需要配置,嗯……先配置外部中断相关的寄存器吧,默默的打开了参考手册,

  又是功能框图

技术图片

     红色的线是产生中断的,蓝色线是产生事件的

    信号从输入线流入 边沿检测电路,边沿检测电路会根据上升沿触发选择寄存器(EXTI_RTSR) 和 下降沿触发选择寄存器(EXTI_FTSR)对应位的设置来控制信号触发。

  边沿检测电路以输入线作为信号输入端,如果检测到有边沿跳变就输出有效信号 1 给 或门,否则输出无效信号 0

  而 EXTI_RTSR EXTI_FTSR 两个寄存器可以控制器需要检测哪些类型的电平跳变程,可以是上升沿触发、下降沿触发 or 上升沿和下降沿都触发。

    

    或门一个输入来 边沿检测 电路,另外一输入来自软件中断事件寄存器(EXTI_SWIER)

  EXTI_SWIER 允许我们通过程序控制就可以启动中断/事件线(我到现在都没明白事件是啥)。

  或门 有1即为真,所以这两个输入随便一个有有效信号 1 就可以输出 1 给 上面的与门 或者 给下面的与门 

  上面的与门一个输入 或门 ,另外一个输入来自中断屏蔽寄存器(EXTI_IMR)

    

    与门电路要求输入都为 1 才输出 1,导致的结果如果 EXTI_IMR 设置为  0 时,那不管 或门 电路的输出信号是 1 还是 0,最终编号 与门 电路输出的信号都为 0

  如果 EXTI_IMR 设置为 1 时,最终编号 4 电路输出的信号才由编号 3 电路的输出信号决定,这样我们可以简单的控制 EXTI_IMR 来实现是否产生中断的目的。

  与门 电路输出的信号会被保存到挂起寄存器(EXTI_PR)内,如果确定 与门 电路输出为 1 就会把 EXTI_PR 对应位1

  EXTI_PR 寄存器内容输出到 NVIC 内,从而实现系统中断事件控制

 

下面那条线看不太明白 所以就 说了,感兴趣自行研究。

  上面 涉及 到了 EXTI_RTSR , EXTI_FTSR  ,EXTI_IMR  , EXTI_SWIER, EXTI_PR ,我们按顺序来看一下

技术图片

 

 

   由于 EXTI_RTSR , EXTI_FTSR 两个寄存器类似,所以这里只看一个。

  按照描述,以及按键电路,如果要设计抬手检测那就需要写 上升沿加测,如果是按下的话,则是下降沿检测

无论配置为哪一种 寄存器的值因该为0x1c 4 3 2 三位置1

技术图片

 

 

 

  作用是开放 中断 ,所以理论值因该是 4 3 2 置1 所以也是0x1c ,仿真的之后可以特别注意一下,是不是 中断被屏蔽了

技术图片

 

 

   这个类似于标志寄存器,当 符合触发源限制时, 触发源锁在的线 置 1 理论上我们这个只会出现3各位置1, 需要手动将该位置0

 

技术图片

  这个是挂起中断寄存器,因为所有中断都默认挂起,所以默认为0,然后 不需要手动清0(其实,我也不太明白这个寄存器,仿真的时候数据一直没变,奇奇怪怪的,还是我菜了)

NVIC 自行配置优先级

下面就是,代码实现了,函数自行到函数库查找:

 

函数

 

 

 

技术图片

 

结构体

 技术图片

 

 余下的枚举自行查看 标准库.chm

  exti配置文件

技术图片exti.h
技术图片
#include "bsp_exti.h"
#include "bsp_key.h"

/**
 * @brief 
 * 
 * @param x              中断源
 * @param ParentPriority 父优先级
 * @param subPriority    子优先级
 */
static void NVIC_Configuration(enum IRQn x, int ParentPriority, int subPriority)
{
    NVIC_InitTypeDef NVIC_InitStructure;

    /* 配置NVIC为优先级组2 */
    NVIC_PriorityGroupConfig(NVIC_PriorityGroup_2);

    /* 配置中断源:x */
    NVIC_InitStructure.NVIC_IRQChannel = x;

    /* 配置抢占优先级:3 */
    NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 3;

    /* 配置子优先级:4 */
    NVIC_InitStructure.NVIC_IRQChannelSubPriority = 4;

    /* 使能中断通道 */
    NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE;
    NVIC_Init(&NVIC_InitStructure);
}

#if 1

/**
 * @brief EXTI 外部中断引脚初始化
 * 
 */
static void EXTI_GPIO_Init(void)
{
    // 直接用的按键初始化
    Key_GPIO_Config();
    // GPIO_InitTypeDef GPIO_InitStructure;
    // RCC_APB2PeriphClockCmd(EXTI1_INT_GPIO_CLK | EXTI2_INT_GPIO_CLK | EXTI3_INT_GPIO_CLK, ENABLE);

    // //选择按键1的引脚
    // GPIO_InitStructure.GPIO_Pin = EXTI1_INT_GPIO_PIN;
    // GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IPD;
    // /* 使用上面的结构体初始化按键 */
    // GPIO_Init(EXTI1_INT_GPIO_PORT, &GPIO_InitStructure);

    // GPIO_InitStructure.GPIO_Pin = EXTI2_INT_GPIO_PIN;
    // GPIO_Init(EXTI2_INT_GPIO_PORT, &GPIO_InitStructure);

    // GPIO_InitStructure.GPIO_Pin = EXTI3_INT_GPIO_PIN;
    // GPIO_Init(EXTI2_INT_GPIO_PORT, &GPIO_InitStructure);

    // 使能 AFIO 寄存器
    RCC_APB2PeriphClockCmd(RCC_APB2Periph_AFIO, ENABLE);
}

/**
 * @brief EXTI 配置
 * 
 */
void EXTI_Init_Config(void)
{
    EXTI_InitTypeDef EXTI_InitStructure;

    // GPIO 配置
    EXTI_GPIO_Init();


    // 连接中断源
    GPIO_EXTILineConfig(EXTI1_INT_EXTI_PORTSOURCE, EXTI1_INT_EXTI_PINSOURCE);
    GPIO_EXTILineConfig(EXTI2_INT_EXTI_PORTSOURCE, EXTI2_INT_EXTI_PINSOURCE);
    GPIO_EXTILineConfig(EXTI3_INT_EXTI_PORTSOURCE, EXTI3_INT_EXTI_PINSOURCE);
    

    /* 选择 EXTI 中断源 */
    EXTI_InitStructure.EXTI_Line = EXTI1_INT_EXTI_LINE;

    /* 中断模式 */
    EXTI_InitStructure.EXTI_Mode = EXTI_Mode_Interrupt;

    /* 下降沿触发 */
    EXTI_InitStructure.EXTI_Trigger = EXTI_Trigger_Falling;

    /* 使能中断/事件线 */
    EXTI_InitStructure.EXTI_LineCmd = ENABLE;
    EXTI_Init(&EXTI_InitStructure);


    /* 选择 EXTI 中断源 */
    EXTI_InitStructure.EXTI_Line = EXTI2_INT_EXTI_LINE;
    EXTI_Init(&EXTI_InitStructure);
   
    /* 选择 EXTI 中断源 */
    EXTI_InitStructure.EXTI_Line = EXTI3_INT_EXTI_LINE;
    EXTI_Init(&EXTI_InitStructure);


    // 优先级配置
    NVIC_Configuration(EXTI1_INT_EXTI_IRQ, 2, 3);
    NVIC_Configuration(EXTI2_INT_EXTI_IRQ, 2, 2);
    NVIC_Configuration(EXTI3_INT_EXTI_IRQ, 2, 1);
}

#endif
exti.c

  下面是中断服务函数:

/**
 * @brief 按键2
 * 
 */
void EXTI2_IRQHandler(void)
{
  // 消抖
  delayms(10);
  if (!KEY2)
  {// 这里建议使用EXTI_GetITStatus(EXTI_Linex)!=RESET进行判断
    //我这里是偷懒,直接用按键
    LED1_ON;
    LED2_ON;
  }
  // 消除 PR 寄存器 的标志位
  EXTI_ClearITPendingBit(EXTI1_INT_EXTI_LINE);
}
/**
 * @brief 按键1
 * 
 */
void EXTI3_IRQHandler(void)
{
  delayms(10);
  if (!KEY1)
  {
    BEEP_ON;
  }
  EXTI_ClearITPendingBit(EXTI2_INT_EXTI_LINE);
}
/**
 * @brief 按键0
 * 
 */
void EXTI4_IRQHandler(void)
{
  delayms(10);
  if (!KEY0)
  {
    LED1_OFF;
    LED2_OFF;
    BEEP_OFF;
  }
  EXTI_ClearITPendingBit(EXTI3_INT_EXTI_LINE);
}

  主函数我直接留空 初始化后while(1)

到这里,整个实验也就完成了,还有很多没明白的地方,例如事件,

如果无法达到效果,可以注意一下, AFIO是否打开,以及, 中断号是否正确, PR寄存器标志位是否清除.

  第一份32的文章,个人能力有限,理解程度,如有错误或建议,望各位大佬留言指出 orz orz orz.

 

以上是关于STM32——外部中断的主要内容,如果未能解决你的问题,请参考以下文章

STM32CubeMX笔记--外部中断,使用按键KEY检测

stm32 外部中断打断PWM

stm32cubemx 外部中断

stm32cubemx 外部中断

基于STM32F103入门3——外部中断

基于STM32F103入门3——外部中断