STM32按键设计二之按键中断

按键中断

  • STM32使用按键中断需要配置端口IO,EXTI和NVIC。
  • 当按键按下的时候,引脚电平发生转变,同时触发沿触发EXTI中断,进而打断CPU(如果正在执行非中断程序或者中断级别低的程序)正在执行的程序,使程序跳转到EXTI中断中执行。

上篇使用了扫描处理中断,本篇将使用按键中断控制LED灯。按键按下则LED灯点亮,松开按键LED灯灭。【传送门:STM32按键设计一之扫描

按键中断程序设计

  • 首先,在key.h中定义一个中断模式开启开关的宏定义,可以切换按键扫描和按键中断,使在程序设计的过程中拥有更高的自由度。
/* 定义使用中断模式 */
#define KEY_INTERRUPT_MODE       1
  • 按键状态位定义,同时定义SCAN模式和按键中断模式的枚举值。切换KEY_INTERRUPT_MODE的值0和1就可以切换程序使用SCAN模式或者终端模式。
#if !KEY_INTERRUPT_MODE
/* 用于SCAN模式 ----------- */
ENUM(KEY_STAT)
{
    KEY0_PRESS = 0x01,
    KEY1_PRESS = 0x02,
    WK_UP_PRESS = 0x04
};

#else
/* 按键中断模式 ----------- */
ENUM(KEY0_State)
{
    KEY0_DOWN,  /* 当KEY0按下时,IO口状态为低电平 */
    KEY0_UP
};

#define KEY0_EXTI_IRQn                  EXTI4_IRQn
#define KEY0_EXTIn_IRQHandler           EXTI4_IRQHandler
#define KEY0_EXTI_Line                  EXTI_Line4

#endif
  • key.c中定义按键初始化函数,同时使用于SCAN模式和按键中断模式。
/**
 * @name: KEY_Init
 * @description: 按键初始化函数,用于扫描模式和中断模式
 * @param {*}
 * @return {*}
 */
void KEY_Init(void)
{
    KEY_GPIO_Init();
#if KEY_INTERRUPT_MODE
    KEY_EXTI_Init();
#endif
}

定义GPIO口初始化函数和外部中断函数,在实际项目中采用定义的中断开关进行模式控制,方便使用。

/**
 * @name: KEY_GPIO_Init
 * @description: KEY IO口初始化 
 * @param {*}
 * @return {*}
 */
static void KEY_GPIO_Init(void)
{
    GPIO_InitTypeDef GPIO_InitStruct;

    RCC_APB2PeriphClockCmd(KEY0_RCC_APB2Periph_CLK | KEY_UP_RCC_APB2Periph_CLK, ENABLE);

    GPIO_InitStruct.GPIO_Pin = KEY0_GPIO_Pin;
    GPIO_InitStruct.GPIO_Mode = GPIO_Mode_IPU;
    GPIO_InitStruct.GPIO_Speed = GPIO_Speed_2MHz;
    GPIO_Init(KEY0_GPIO_Port, &GPIO_InitStruct);

    GPIO_InitStruct.GPIO_Pin = KEY1_GPIO_Pin;
    GPIO_Init(KEY1_GPIO_Port, &GPIO_InitStruct);

    GPIO_InitStruct.GPIO_Pin = KEY_UP_GPIO_Pin;
    GPIO_InitStruct.GPIO_Mode = GPIO_Mode_IPD;
    GPIO_Init(KEY_UP_GPIO_Port, &GPIO_InitStruct);
}

#if KEY_INTERRUPT_MODE
/**
 * @name: KEY_EXTI_Init
 * @description: 按键外部中断初始化
 * @param {*}
 * @return {*}
 */
static void KEY_EXTI_Init(void)
{

    EXTI_InitTypeDef EXTI_InitStruct;
    NVIC_InitTypeDef NVIC_InitStruct;
    /* exti 开启afio时钟 */
    RCC_APB2PeriphClockCmd(RCC_APB2Periph_AFIO, ENABLE);

    EXTI_DeInit();
    /* exti中断IO口配置 */
    GPIO_EXTILineConfig(GPIO_PortSourceGPIOE, GPIO_PinSource4);
    /* exti配置 */
    EXTI_InitStruct.EXTI_Line = KEY0_EXTI_Line;
    EXTI_InitStruct.EXTI_Mode = EXTI_Mode_Interrupt;
    EXTI_InitStruct.EXTI_Trigger = EXTI_Trigger_Falling;
    EXTI_InitStruct.EXTI_LineCmd = ENABLE;
    EXTI_Init(&EXTI_InitStruct);

    EXTI_ClearITPendingBit(KEY0_EXTI_Line);

    /* exti中断nvic配置 */
    NVIC_InitStruct.NVIC_IRQChannel = KEY0_EXTI_IRQn;
    NVIC_InitStruct.NVIC_IRQChannelPreemptionPriority = 1;
    NVIC_InitStruct.NVIC_IRQChannelSubPriority = 1;
    NVIC_InitStruct.NVIC_IRQChannelCmd = ENABLE;
    NVIC_Init(&NVIC_InitStruct);
}
  • 定义中断处理函数中需要用到的EXTI中断使能和EXTI中断触发沿配置函数。
    自己定义一个EXTI中断使能函数,在STM32提供的库函数中没有单独的中断开关函数需要使用可以调用EXTI_Init()函数进行,但是EXTI需要操纵的寄存器太多不够简洁,同时也可以参考库函数中比较经典的最后两行位操作进行(库函数的操作更加简洁)。
/**
 * @name: EXTI_ITConfig
 * @description: 开关exti中断
 * @param {u32} EXTI_LINEn exti中断线
 * @param {FunctionalState} state exti中断状态
 * @return {*}
 */
static void EXTI_ITConfig(u32 EXTI_LINEn, FunctionalState state)
{
    if(state)
        EXTI->IMR |= EXTI_LINEn;
    else
        EXTI->IMR &= ~EXTI_LINEn;

}

定义一个给变触发边沿的函数,此函数也能实现对中断的开关

/**
 * @name: KEY_EXTI_Config
 * @description: exti结构体配置
 * @param {u32} EXTI_LINEn  exti线
 * @param {EXTITrigger_TypeDef} EXTITrigger_x   exti触发模式
 * @param {FunctionalState} state  exti中断使能
 * @return {*}
 */
static void KEY_EXTI_Config(u32 EXTI_LINEn, EXTITrigger_TypeDef EXTITrigger_x, FunctionalState state)
{
    EXTI_InitTypeDef EXTI_InitStruct;

    EXTI_InitStruct.EXTI_Line = EXTI_LINEn;
    EXTI_InitStruct.EXTI_Trigger = EXTITrigger_x;
    EXTI_InitStruct.EXTI_LineCmd = state;
    EXTI_InitStruct.EXTI_Mode = EXTI_Mode_Interrupt;
    EXTI_Init(&EXTI_InitStruct);
}
  • 中断处理函数
/**
 * @name: KEY0_EXTIn_IRQHandler
 * @description: exti中断处理函数
 * @param {*}
 * @return {*}
 */
void KEY0_EXTIn_IRQHandler(void)
{
    /* 局部静态变量,用于触发沿判断 */
    static uint8_t is_rising = 0;

    if(!is_rising)
    {
        /* Falling Trigger */
        EXTI_ITConfig(KEY0_EXTI_Line, DISABLE); /* 关EXTI线中断 */
        delay_ms(10);   /* 去抖 */
        if(KEY0 == KEY0_DOWN)   /* 是否按下 */
        {
            /* 配置下次上升沿触发 */
            KEY_EXTI_Config(KEY0_EXTI_Line, EXTI_Trigger_Rising, ENABLE);
            is_rising = 1;

            KEY0_Down_callback();
        }
        else
        {
            EXTI_ITConfig(KEY0_EXTI_Line, ENABLE);
        }
    }
    else
    {
        /* Rising Trigger */
        EXTI_ITConfig(KEY0_EXTI_Line, DISABLE); /* 关EXTI线中断 */
        delay_ms(10); /* 去抖 */
        if(KEY0 == KEY0_UP) /* 是否松开 */
        {
            /* 配置下次下降沿触发 */
            KEY_EXTI_Config(KEY0_EXTI_Line, EXTI_Trigger_Falling, ENABLE);
            is_rising = 0;

            KEY0_Up_callback();
        }
        else
        {
            EXTI_ITConfig(KEY0_EXTI_Line, ENABLE);
        }
    }
    EXTI_ClearITPendingBit(KEY0_EXTI_Line);
}

在中断处理函数中进行了以下操作:

  1. 判断是上升沿触发还是下降沿触发,通过局部static变量实现
  2. 关闭EXTI中断
  3. 软件延时去抖
  4. 如果按下状态,则配置松开按键中断检测的边沿
  5. 设置下次中断沿为上升沿或者下降沿标志,即改变局部static变量的值
  6. 任务调用,向外部提供一个接口调用,通过接口调用可以实现分层设计,同时可以方便的实现不同任务。具体任务调用在接口中进行,不要有阻塞程序
  • 实现接口调用函数,在app.c文件中定义如下代码:
/* 具体任务 */
void LED_Callback(void)
{
    LED1 = !LED1;
}

/* 任务回调函数 */
void KEY0_Down_callback(void)
{
    LED_Callback();
    /* 本篇或者之前的,在中断中进行打印只为了测试 */
    printf("led toggle in key down interrupt ! \n");
}

/* 任务回调函数 */
void KEY0_Up_callback(void)
{
    LED_Callback();
    /* 本篇或者之前的,在中断中进行打印只为了测试 */
    printf("led toggle in key up interrupt ! \n");
}

这样就可以实现按键中断实现简单的按下按键点亮LED,松开关闭LED。

效果

在main.c中初始化,并调用:

int main(void)
{
	uint8_t key_sta = 0;
	uint32_t t = 0;

	NVIC_PriorityGroupConfig(NVIC_PriorityGroup_2);
	initSysTick();
	Usart1_Init(115200);
	LED1_Init();
	LED2_Init();
	KEY_Init();

	printf("Init Hardware OK ... \n");

	for(;;)
	{
		t++;
		if(t >= 2000)
			t = 0;

#if !KEY_INTERRUPT_MODE	/* 仅用于测试 */
		/* 按键开关程序 */
		key_sta = KEY_Scan(0);
		if(key_sta == KEY0_PRESS)
		{
			LED1_Open();
			printf("key0 press! \n");
		}
		if(key_sta == KEY1_PRESS)
		{
			LED1_Close();
			printf("key1 press! \n");
		}
#endif

		/* 系统正常指示灯 */
		if(0 == t % 100)	
		{
			LED2_Toggle();
		}
		delay_ms(10);
	}
}

按键中断测试结果

  • 需要宏定义中KEY_INTERRUPT_MODE置为1
    在这里插入图片描述

按键扫描模式测试结果

  • 需要宏定义中KEY_INTERRUPT_MODE置为0
    在这里插入图片描述


喜欢请点个赞哦,谢谢!欢迎指正

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值