第13章 GPIO—按键中断

一、关于 STM32 的 EXTI

  前面“第10章 基础重点—中断系统”介绍了STM32的中断和中断优先级,知道了所有外设中断都由 NVIC管理,比如USART、ADC、I2C、SPI等。GPIO产生的中断也不例外,但在给NVIC管理之前,还有一个EXTI(External interrupt/event controller,外部中断/事件控制器)先处理一下,如图 13.1.1 所示。
在这里插入图片描述
  STM32F103系列的EXTI支持19个外部中断/事件请求(互联型系列的STM32支持20个),每个中断/事件都有独立的触发和屏蔽设置,支持中断模式和事件模式。
  中断模式是指外部信号产生电平变化时,EXTI将该信号给NVIC处理,从而触发中断,执行中断服务函数,完成对应操作。
  事件模式是指外部信号产生电平变化时,EXTI根据配置,联动ADC或TIM执行相关操作。
  中断和事件的产生源是一样的,中断需要软件实现相应功能,而事件是由硬件触发后执行相应操作。前者需要CPU参与功能实现,可以实现的功能更多,后者无需CPU参与,具有更高的响应速度。
  EXTI的结构如图 13.1.2 所示,图中画斜线“/”的信号线表示这样的线共有19根。外部信号输入后,首先经过边缘检测电路,可以实现对上升沿或下降沿信号进行检测,从而得到硬件触发,也可由软件中断事件寄存器产生软件触发信号。无论是硬件触发还是软件触发,如果中断屏蔽寄存器允许,则产生中断给NVIC处理(绿色路线);如果事件屏蔽寄存器允许,则产生事件,脉冲发生器产生脉冲供其它模块使用(黄色路线)。
  STM32F103的GPIO挂载APB总线上,如果要使用GPIO引脚作为外部中断/事件功能,则必须使能APB总 线上该引脚对应端口的时钟和AFIO复用功能。
在这里插入图片描述
  STM32F103ZET6有7组GPIO,每组16个引脚,即112个GPIO引脚,但EXTI只支持19个外部中断/事件请求,因此需要将多个GPIO合成一组,共用一个中断线,STM32F103系列中断线分组如表 13.1.1 所示。
在这里插入图片描述
  结合图 13.1.1 所示,EXTI0~EXTI15作为GPIO中断线使用,同组的GPIO共享一条中断线,比如EXTI0组,PA0作为了中断源,则此时PB0~PG0不能作为中断源。

总结

  STM32有众多异常和中断,其中内部中断源(USART、ADC等)直接由NVIC处理。GPIO引脚可以产生外部中断或事件,如是中断则交由NVIC处理,如果是事件则产生脉冲信号联动其它模块工作。
  无论是内部中断源,还是GPIO产生的中断,都由NVIC管理分组,然后根据中断优先级分组确定抢占优先级级数和子优先级级数。
  GPIO引脚众多,将引脚数字相同的作为一组,共享一个中断线。

二、硬件设计

三、软件设计

3.1 软件设计思路

  实验目的:本实验通过使用外部中断功能去判断按键的状态,通过中断的形式能够更加灵敏的读取到GPIO的电平,让用户更加直观的感受到STM32F103的中断,并学会如何使用和开发其中断功能。

  1. 按键初始化:GPIO端口时钟使能、AFIO复用功能时钟使能、GPIO引脚设置为下降沿触发中断(PA0,PG15, PC13, PE3);
  2. 填充每个按键中断处理函数:读取按键GPIO状态,操作对应LED灯亮灭;
  3. 主函数调用LED和按键初始化后,无需任何操作;

3.2 软件设计讲解

3.2.1 GPIO宏定义与接口宏定义

/*********************
 * 按键引脚状态定义
**********************/
#define PUSH_DOWN                       GPIO_PIN_RESET
#define SPRING_UP                       GPIO_PIN_set

/*********************
 * 引脚宏定义
**********************/
#define KEY_UP_GPIO_PIN                 GPIO_PIN_0
#define KEY_UP_GPIO_PORT                GPIOA
#define KEY_UP_GPIO_CLK_EN()            __HAL_RCC_GPIOA_CLK_ENABLE()

#define KEY_DOWN_GPIO_PIN               GPIO_PIN_15
#define KEY_DOWN_GPIO_PORT              GPIOG
#define KEY_DOWN_GPIO_CLK_EN()          __HAL_RCC_GPIOG_CLK_ENABLE()

#define KEY_LEFT_GPIO_PIN               GPIO_PIN_13
#define KEY_LEFT_GPIO_PORT              GPIOC
#define KEY_LEFT_GPIO_CLK_EN()          __HAL_RCC_GPIOC_CLK_ENABLE()

#define KEY_RIGHT_GPIO_PIN              GPIO_PIN_3
#define KEY_RIGHT_GPIO_PORT             GPIOE
#define KEY_RIGHT_GPIO_CLK_EN()         __HAL_RCC_GPIOE_CLK_ENABLE()

/*********************
 * 函数宏定义
**********************/
/*
 * 按键状态读取函数宏定义
*/
#define KEY_UP                  HAL_GPIO_ReadPin(KEY_UP_GPIO_PORT, KEY_UP_GPIO_PIN)
#define KEY_DOWN                HAL_GPIO_ReadPin(KEY_DOWN_GPIO_PORT, KEY_DOWN_GPIO_PIN)
#define KEY_LEFT                HAL_GPIO_ReadPin(KEY_LEFT_GPIO_PORT, KEY_LEFT_GPIO_PIN)
#define KEY_RIGHT               HAL_GPIO_ReadPin(KEY_RIGHT_GPIO_PORT, KEY_RIGHT_GPIO_PIN)

3.2.2 GPIO初始化


/*
 *  函数名:void KeyInit(void)
 *  输入参数:无
 *  输出参数:无
 *  返回值:无
 *  函数作用:初始化按键的引脚,配置为下降沿触发外部中断
*/
void KeyInit(void)
{
    // 定义GPIO的结构体变量
    GPIO_InitTypeDef GPIO_InitStruct = {0};
    // 使能按键的GPIO对应的时钟
    KEY_UP_GPIO_CLK_EN();
    KEY_DOWN_GPIO_CLK_EN();
    KEY_LEFT_GPIO_CLK_EN();
    KEY_RIGHT_GPIO_CLK_EN();

    GPIO_InitStruct.Mode = GPIO_MODE_IT_FALLING;        // 设置为下降沿触发外部中断  设置为下降沿触发外部中断,即按键按下瞬间触发中断。可根据需求设置为上升沿触发,即松开按
键触发中断,双边缘触发,即按下松开都触发中断;
    GPIO_InitStruct.Pull = GPIO_PULLUP;                 // 默认上拉
    GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_HIGH;       // 引脚反转速度设置为快
    
    // 初始化'Up'键引脚配置 
    GPIO_InitStruct.Pin = KEY_UP_GPIO_PIN;              // 选择按键的引脚
    HAL_GPIO_Init(KEY_UP_GPIO_PORT, &GPIO_InitStruct);
    // 初始化'Down'键引脚配置
    GPIO_InitStruct.Pin = KEY_DOWN_GPIO_PIN;            // 选择按键的引脚
    HAL_GPIO_Init(KEY_DOWN_GPIO_PORT, &GPIO_InitStruct);
    // 初始化Left'键引脚配置
    GPIO_InitStruct.Pin = KEY_LEFT_GPIO_PIN;            // 选择按键的引脚
    HAL_GPIO_Init(KEY_LEFT_GPIO_PORT, &GPIO_InitStruct);
    // 初始化'Right'键引脚配置
    GPIO_InitStruct.Pin = KEY_RIGHT_GPIO_PIN;           // 选择按键的引脚
    HAL_GPIO_Init(KEY_RIGHT_GPIO_PORT, &GPIO_InitStruct);
    
    /* EXTI interrupt init*/
    HAL_NVIC_SetPriority(EXTI0_IRQn, 0, 0);
    HAL_NVIC_EnableIRQ(EXTI0_IRQn);

    HAL_NVIC_SetPriority(EXTI3_IRQn, 0, 0);
    HAL_NVIC_EnableIRQ(EXTI3_IRQn);

    HAL_NVIC_SetPriority(EXTI15_10_IRQn, 0, 0);
    HAL_NVIC_EnableIRQ(EXTI15_10_IRQn);
}

3.2.3 中断处理函数

  当中断发生后,则自动跳到代码段 10.2.1 中所对应的中断函数所在位置,执行中断函数的内容,因此我们需要编写中断函数的内容。
  此处有四个按键,理论上也就应该有4个中断处理函数。但在HAL库中,EXTI0~4这五个中断是各自独立的中断服务函数,EXTI5~9共用一个中断服务函数,EXTI10~15共用一个中断服务函数。


/*  说明:如下的四个外部中断函数均可放在stm32f1xx_it.c中,
 *  如果放在其中需要注意包含本文件的.h头文件,此处将中断函数放在本文件只是为了方便理清逻辑
*/
/*
 *  函数名:void EXTI0_IRQHandler(void)
 *  输入参数:无
 *  输出参数:无
 *  返回值:无
 *  函数作用:外部中断0的中断处理函数
*/
void EXTI0_IRQHandler(void)
{
    HAL_GPIO_EXTI_IRQHandler(KEY_UP_GPIO_PIN);
}

/*
 *  函数名:void EXTI3_IRQHandler(void)
 *  输入参数:无
 *  输出参数:无
 *  返回值:无
 *  函数作用:外部中断3的中断处理函数
*/
void EXTI3_IRQHandler(void)
{
    HAL_GPIO_EXTI_IRQHandler(KEY_RIGHT_GPIO_PIN);
}

/*
 *  函数名:void EXTI15_10_IRQHandler(void)
 *  输入参数:无
 *  输出参数:无
 *  返回值:无
 *  函数作用:外部中断10-15的中断处理函数
*/
void EXTI15_10_IRQHandler(void)
{
    HAL_GPIO_EXTI_IRQHandler(KEY_DOWN_GPIO_PIN);
    HAL_GPIO_EXTI_IRQHandler(KEY_LEFT_GPIO_PIN);
}

  每个中断处理函数里,都调用的“HAL_GPIO_EXTI_IRQHandler()”准备后续处理,传入参数为外部中断的引脚号。

/**
  * @brief  This function handles EXTI interrupt request.
  * @param  GPIO_Pin: Specifies the pins connected EXTI line
  * @retval None
  */
void HAL_GPIO_EXTI_IRQHandler(uint16_t GPIO_Pin)
{
  /* EXTI line interrupt detected */
  if (__HAL_GPIO_EXTI_GET_IT(GPIO_Pin) != 0x00u)
  {
    __HAL_GPIO_EXTI_CLEAR_IT(GPIO_Pin);
    HAL_GPIO_EXTI_Callback(GPIO_Pin);
  }
}

/*
 *  函数名:void HAL_GPIO_EXTI_Callback(void)
 *  输入参数:无
 *  输出参数:无
 *  返回值:无
 *  函数作用:外部中断处理函数的回调函数,用以处理不同引脚触发的中断服务最终函数
*/
static volatile bool up_flag = false;           // 定义一个全局静态标志,用以判断按键按下的次数,上下左右键类似
static volatile bool down_flag = false;
static volatile bool left_flag = false;
static volatile bool right_flag = false;
void HAL_GPIO_EXTI_Callback(uint16_t GPIO_Pin)
{
    switch(GPIO_Pin)                    // 判断是哪个按键
    {           
        case KEY_UP_GPIO_PIN:           // 如果是上键
        {           
            up_flag = !up_flag;         // 按下一次标志翻转一次
            RLED(OFF);                  
            GLED(up_flag?OFF:ON);       // 根据标志控制绿灯的亮灭
            BLED(OFF);
            break;  
        }   
        case KEY_DOWN_GPIO_PIN:         // 如果是下键
        {                               
            down_flag = !down_flag;     // 按下一次标志翻转一次
            RLED(down_flag?OFF:ON);     // 三色灯全灭或全亮
            GLED(down_flag?OFF:ON);
            BLED(down_flag?OFF:ON);
            break;  
        }   
        case KEY_LEFT_GPIO_PIN:         // 如果是左键
        {                               
            left_flag = !left_flag;     // 按下一次标志翻转一次
            RLED(left_flag?OFF:ON);     // 根据标志控制红灯的亮灭
            GLED(OFF);
            BLED(OFF);               
            break;
        }
        case KEY_RIGHT_GPIO_PIN:        // 如果是右键
        {                               
            right_flag = !right_flag;   // 按下一次标志翻转一次
            RLED(OFF);                  // 根据标志控制蓝灯的亮灭
            GLED(OFF);
            BLED(right_flag?OFF:ON);
            break;
        }
        default:break;
    }
}

3.2.4 主函数测试

    // 初始化按键
    KeyInit();
    
    //初始化LED
    LedGpioInit();
    
    while(1)
    {
    }

四、代码工程

工程链接:GPIO-按键中断

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值