【凌鸥LKS32】基础篇(三)·EXTI外部中断按键检测功能设计

目录
1. GPIO 概述
GPIO(General Purpose Input Output)即通用输入输出端口,是芯片可直接控制的引脚。通过将 GPIO 引脚与外部设备连接,可实现与外部设备的通讯、控制及数据采集等功能。所有 GPIO 引脚均具备基本的输入和输出能力:
| 功能类型 | 原理 | 核心作用 | 典型应用 |
| 输出 | 由MCU控制引脚输出高、低电平,实现开关控制 | 控制外部设备通断 / 状态 | LED 驱动、继电器 / 三极管控制 |
| 输入 | 检测外部输入的电平信号,通过电平高低区分不同状态。 | 检测外部电平 / 状态 | 按键检测、传感器信号采集 |
2. 按键
按键机械触点断开、闭合时,由于触点的弹性作用,按键开关不会马上稳定接通或一下子断开,使用按键时会产生图按键抖动说明图中的带波纹信号,需要用软件消抖处理滤波,不方便输入检测。可以利用电容充放电的延时,消除了波纹,从而简化软件的处理,软件只需要直接检测引脚的电平即可。

3. GPIO输出功能验证
首先宏定义P2.12引脚,方便后续调用:
#define KEY_STOP_PIN GPIO_Pin_12
#define KEY_STOP_PORT GPIO2
#define KEY_STOP_Source GPIO_PinSource_12
#define KEY_STOP GPIO_ReadInputDataBit(KEY_STOP_PORT, KEY_STOP_PIN)
初始化按键为输入模式:
void GPIO_init_key(void)
{
GPIO_InitTypeDef GPIO_InitStruct;
GPIO_StructInit(&GPIO_InitStruct);
GPIO_InitStruct.GPIO_Mode = GPIO_Mode_IN;
GPIO_InitStruct.GPIO_Pin = KEY_STOP_PIN;
GPIO_Init(KEY_STOP_PORT, &GPIO_InitStruct);
}
然后通过 GPIO_ReadInputDataBit() 函数读取指定 GPIO 引脚的电平状态(读取IDR(输入数据寄存器)),首先判断按键是否被按下,如果按下延时消抖一段时间,然后再次检测按键是否被按下,如果按下则将按键标志位置0:
if(KEY_STOP == 0 && key_released) /* 检测到按键按下,并且按键之前是释放状态 */
{
SoftDelay(0x500); /* 延时消抖 */
if(KEY_STOP == 0) /* 再次检测到按键按下 */
{
key_released = 0; /* 按键按下,标志位置0 */
return 1;
}
}
如果检测到按键释放,则将按键标志位置1:
if(KEY_STOP != 0) /* 检测到按键释放 */
{
key_released = 1; /* 按键释放,标志位置1 */
}
此时按键检测代码:
#include "gpio_key.h"
#include "delay.h"
int gpio_key_Scan(void)
{
static u8 key_released = 1; /* 按键释放标志,按键按下时为0,按键释放时为1 */
if(KEY_STOP == 0 && key_released) /* 检测到按键按下,并且按键之前是释放状态 */
{
SoftDelay(0x500); /* 延时消抖 */
if(KEY_STOP == 0) /* 再次检测到按键按下 */
{
key_released = 0; /* 按键按下,标志位置0 */
return 1;
}
}
if(KEY_STOP != 0) /* 检测到按键释放 */
{
key_released = 1; /* 按键释放,标志位置1 */
}
return 0;
}
主函数就很简单了,检测到按键按下,切换一次电平:
#include "lks32mc07x_lib.h"
#include "hardware_init.h"
#include "gpio_led.h"
#include "gpio_key.h"
#include "delay.h"
int main(void)
{
u8 led_state = 0;
Hardware_init();
while(1)
{
if(gpio_key_Scan() == 1) /* 检测到按键按下 */
{
led_state = !led_state; /* 切换LED状态 */
}
if(led_state == 1) /* LED状态为点亮 */
{
led_all_on(); /* 点亮所有LED */
}
else
{
led_all_off(); /* 熄灭所有LED */
}
}
}
4. 双按键功能实现
其实就是将上面流程走一遍,在上面代码的基础上,先进行宏定义对P2.11:
#define KEY_START_PIN GPIO_Pin_11
#define KEY_START_PORT GPIO2
#define KEY_START_Source GPIO_PinSource_11
#define KEY_START GPIO_ReadInputDataBit(KEY_START_PORT, KEY_START_PIN)
然后初始化:
void GPIO_init_key(void)
{
GPIO_InitTypeDef GPIO_InitStruct;
GPIO_StructInit(&GPIO_InitStruct);
GPIO_InitStruct.GPIO_Mode = GPIO_Mode_IN;
GPIO_InitStruct.GPIO_Pin = KEY_STOP_PIN;
GPIO_Init(KEY_STOP_PORT, &GPIO_InitStruct);
GPIO_InitStruct.GPIO_Mode = GPIO_Mode_IN;
GPIO_InitStruct.GPIO_Pin = KEY_START_PIN;
GPIO_Init(KEY_START_PORT, &GPIO_InitStruct);
}
然后添加START按键的函数:
if(KEY_START == 0 && key_released_start) /* 检测到按键按下,并且按键之前是释放状态 */
{
SoftDelay(0x500); /* 延时消抖 */
if(KEY_START == 0) /* 再次检测到按键按下 */
{
key_released_start = 0; /* 按键按下,标志位置0 */
return 2;
}
}
if(KEY_START != 0) /* 检测到按键释放 */
{
key_released_start = 1; /* 按键释放,标志位置1 */
}
完整的代码:
#include "gpio_key.h"
#include "delay.h"
int gpio_key_Scan(void)
{
static u8 key_released_stop = 1; /* 按键释放标志,按键按下时为0,按键释放时为1 */
static u8 key_released_start = 1; /* 按键释放标志,按键按下时为0,按键释放时为1 */
if(KEY_STOP == 0 && key_released_stop) /* 检测到按键按下,并且按键之前是释放状态 */
{
SoftDelay(0x500); /* 延时消抖 */
if(KEY_STOP == 0) /* 再次检测到按键按下 */
{
key_released_stop = 0; /* 按键按下,标志位置0 */
return 1;
}
}
if(KEY_STOP != 0) /* 检测到按键释放 */
{
key_released_stop = 1; /* 按键释放,标志位置1 */
}
if(KEY_START == 0 && key_released_start) /* 检测到按键按下,并且按键之前是释放状态 */
{
SoftDelay(0x500); /* 延时消抖 */
if(KEY_START == 0) /* 再次检测到按键按下 */
{
key_released_start = 0; /* 按键按下,标志位置0 */
return 2;
}
}
if(KEY_START != 0) /* 检测到按键释放 */
{
key_released_start = 1; /* 按键释放,标志位置1 */
}
return 0;
}
之后回到主函数做判断:
#include "lks32mc07x_lib.h"
#include "hardware_init.h"
#include "gpio_led.h"
#include "gpio_key.h"
#include "delay.h"
int main(void)
{
u8 led_state = 0;
Hardware_init();
while(1)
{
u8 key_value = gpio_key_Scan(); /* 检测按键值 */
if(key_value == 1) /* 检测到STOP按键按下 */
{
led_state = !led_state; /* 切换LED状态 */
}
else if(key_value == 2) /* 检测到START按键按下 */
{
led_state = !led_state; /* 切换LED状态 */
}
if(led_state == 1) /* LED状态为点亮 */
{
led_all_on(); /* 点亮所有LED */
}
else
{
led_all_off(); /* 熄灭所有LED */
}
}
}
此时实现的功能是START和STOP两个按键都可以对LED进行电平反转。
5. EXTI简介
EXTI(Extern Interrupt)外部中断 EXTI可以监测指定GPIO口的电平信号,当其指定的GPIO口产生电平变化时,EXTI将立即向NVIC发出中断申请,经过NVIC裁决后即可中断CPU主程序,使CPU执行EXTI对应的中断程序。
简单来说:电平变化,触发中断
支持的触发方式如下:
/**
* EXTI触发信号定义
*/
typedef enum
{
EXTI_Trigger_None = 0x00, /**< 不触发*/
EXTI_Trigger_Negedge = 0x01, /**< 下降沿触发*/
EXTI_Trigger_Posedge = 0x02, /**< 上升沿触发*/
EXTI_Trigger_Edge = 0x03 /**< 上升沿、下降沿均触发*/
} EXTIMode_TypeDef;
上升沿触发:数字电平从低电平(数字“0”)变为高电平(数字“1”)的那一瞬间叫作上升沿。 上升沿触发是当信号有上升沿时的开关动作,当电位由低变高而触发输出变化的就叫上升沿触发。也就是当测到的信号电位是从低到高也就是上升时就触发,叫做上升沿触发。
下降沿触发:数字电路中,数字电平从高电平(数字“1”)变为低电平(数字“0”)的那一瞬间叫作下降沿。下降沿触发是当信号有下降沿时的开关动作,当电位由高变低而触发输出变化的就叫下降沿触发。也就是当测到的信号电位是从高到低也就是下降时就触发,叫做下降沿触发。
那么我们可以很好的理解两种触发:
上升沿触发 就是当电压从低变高时触发中断
下降沿触发 就是当电压从高变低时触发中断
双边沿触发:可以看做上升沿和下降沿的结合,也就是二者都可以触发中断。
6. 通过外部中断实现按键功能
在使用之前,我们需要先了解,该引脚是否支持EXTI,通过芯片手册我们可以发现STOP也就是P0_12引脚具备EXTI功能:

我们通过如下函数将引脚映射连接到对应的 EXTI 外部中断线路:
EXTI_Trigger_Config(GPIO_TypeDef* GPIOx, uint16_t PinSource, EXTI_TriggerTypeDef EdgeSel);
在之前初始化的代码的基础上,增加代码,意思把 GPIO2 Pin12 这条引脚映射连接到对应的 EXTI 外部中断线路:
void GPIO_init_key(void)
{
GPIO_InitTypeDef GPIO_InitStruct;
GPIO_StructInit(&GPIO_InitStruct);
GPIO_InitStruct.GPIO_Mode = GPIO_Mode_IN;
GPIO_InitStruct.GPIO_Pin = KEY_STOP_PIN;
GPIO_Init(KEY_STOP_PORT, &GPIO_InitStruct);
EXTI_Trigger_Config(KEY_STOP_PORT,KEY_STOP_Source,EXTI_Trigger_Negedge);
GPIO_InitStruct.GPIO_Mode = GPIO_Mode_IN;
GPIO_InitStruct.GPIO_Pin = KEY_START_PIN;
GPIO_Init(KEY_START_PORT, &GPIO_InitStruct);
}
然后初始化部分设置NVIC中断:
NVIC_SetPriority(GPIO_IRQn, 0x00); /* 设置GPIO中断优先级为最高 */
NVIC_EnableIRQ(GPIO_IRQn); /* 使能GPIO中断 */
来到GPIO中断服务函数部分,创建一个状态标志位用于切换流水灯方向:
extern u8 flow_direction;
void GPIO_IRQHandler(void)
{
if(EXTI_GetIRQFlag(KEY_STOP_PORT,KEY_STOP_Source))
{
flow_direction = !flow_direction; /* 切换流水灯方向 */
EXTI_ClearIRQFlag(KEY_STOP_PORT,KEY_STOP_Source);
}
}
流水灯流向控制代码如下,通过更改direction的值用来更改流向:
void led_water_flow(u8 direction)
{
if(direction == 0) /* 正向流水: LED1 → LED2 → LED3 */
{
LED1_ON; LED2_OFF; LED3_OFF;
SoftDelay(0x100000);
LED1_OFF; LED2_ON; LED3_OFF;
SoftDelay(0x100000);
LED1_OFF; LED2_OFF; LED3_ON;
SoftDelay(0x100000);
}
else /* 反向流水: LED3 → LED2 → LED1 */
{
LED1_OFF; LED2_OFF; LED3_ON;
SoftDelay(0x100000);
LED1_OFF; LED2_ON; LED3_OFF;
SoftDelay(0x100000);
LED1_ON; LED2_OFF; LED3_OFF;
SoftDelay(0x100000);
}
}
主函数循环执行流水灯程序:
#include "lks32mc07x_lib.h"
#include "hardware_init.h"
#include "gpio_led.h"
#include "gpio_key.h"
#include "delay.h"
u8 flow_direction = 0; /* 全局变量: 流水灯方向, 0=正向, 1=反向 */
int main(void)
{
Hardware_init();
while(1)
{
led_water_flow(flow_direction); /* 执行流水灯 */
}
}
最终可以发现STOP按键控制流水灯流向,每次点击按键流向改变。


更多推荐
所有评论(0)