STM32中断+定时器实现长按、短按(一)

         之前文章

        1.0、 STM32中断+定时器实现长按、短按(一)

        1.1 、STM32中断+定时器实现单击、多击和长击(二)

        优化后文章

        1.2、 STM32按键的长按和短按(一)

        1.3、 STM32按键的长按和连按(二)

项目,要实现长按、短按、还有多按,用按键来控制单片机开关机,关机后进入到睡眠低功耗,所以我将按键配置中断,用中断来唤醒睡眠,加上定时器完成长短按键,话不多说,开始进入正题。

单片机选择STM32F103C8T6最小系统板

轻触按键

在学习单片机时,接触最多的外设除了LED就是按键,所以按键的介绍就只是简单说一下。

        按键原理图

根据按键原理图可知,GPIO引脚P0.0(PA0)、P0.1(PA1)、P5.2在没有按键没有按下时连接的是R5、R6、R8三个电阻接地线,所以在按键没有按下时处于低电平,按键K4、K3、K2 == 0;至于二极管D2、D3是按键K3、K4应用于影响按键K2的,不使用可以不用管。

当按键K4按下时,电源VDD接通经过电阻R5到达地线GND,GPIO引脚P0.0可检测到高电平,按键K4 == 1,

按键是高电平响应,代码可以这这样写:

//#include "key.c"

void KEY_Init(void)
{
	GPIO_InitTypeDef GPIO_InitStructure;					//定义GPIO结构体
	GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IPU;				//上拉输入
	GPIO_InitStructure.GPIO_Pin = GPIO_Pin_0;					//PA0引脚
	GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;			//输出频率
	GPIO_Init(GPIOA, &GPIO_InitStructure);						//结构体配置完成初始化
}

uint8_t KEY_Stare(void) 
{
	static uint8_t key_state = 0;//按键状态位
	
	if(KEY_STATA && !key_state) {
		printf("key_ON\r\n");//按下按键
		key_state = 1;
	}
	if(!KEY_STATA && key_state) {
		printf("KEY_OFF\r\n");//松开按键
		key_state = 0;
	}
	return key_state;
}



//#include "key.h"

#define KEY_STATE	!!(GPIOA->IDR & 0x0001)//寄存器读取按键状态位
#define KEY_STATA	!!GPIO_ReadInputDataBit(GPIOA, GPIO_Pin_0)//标准库读取

void KEY_Init(void);
uint8_t KEY_Stare(void);

如果读者按键是低电平响应,只需要修改KEY_STATA的取反即可。

//#include "key.c"

void KEY_Init(void)
{
	GPIO_InitTypeDef GPIO_InitStructure;					//定义GPIO结构体
	GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IPU;				//上拉输入
	GPIO_InitStructure.GPIO_Pin = GPIO_Pin_0;					//PA0引脚
	GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;			//输出频率
	GPIO_Init(GPIOA, &GPIO_InitStructure);						//结构体配置完成初始化
}

uint8_t KEY_Stare(void) 
{
	static uint8_t key_state = 0;//按键状态位
	
	if(!KEY_STATA && !key_state) {//此处修改即可
		printf("key_ON\r\n");//按下按键
		key_state = 1;
	}
	if(KEY_STATA && key_state) {
		printf("KEY_OFF\r\n");//松开按键
		key_state = 0;
	}
	return key_state;
}



//#include "key.h"

#define KEY_STATE	!!(GPIOA->IDR & 0x0001)//寄存器读取按键状态位
#define KEY_STATA	!!GPIO_ReadInputDataBit(GPIOA, GPIO_Pin_0)//标准库读取

void KEY_Init(void);
uint8_t KEY_Stare(void);

定时器

 

选择STM32F103C8T6的通用定时器TIM2。 

这两张图来自江协科技,想了解定时器细节建议去看原视频:

STM32入门教程-2023持续更新中_哔哩哔哩_bilibili

定时器配置

定时器常规配置不详细介绍,  计数器溢出进入中断,Tout(溢出时间) = (ARR+1)(PSC+1)/Tclk(时钟分割)

① 配置GPIO结构体初始化;

② 配置NVIC结构体初始化;

        TIM_TimeBaseInitTypeDef TIM_TimeBaseInitStructure;    //定义TIM时基结构体      

        NVIC_InitTypeDef NVIC_InitStructure;                            //定义NVIC(嵌套向量中断)结构体

编写TIM中断服务函数

当按键按下后,中断服务函数void EXTI0_IRQHandler(void)中,使能定时器TIM_Cmd(TIM2, ENABLE),定时器开始计数,计数器溢出后,开启中断。

Tout(溢出时间) = (ARR+1)(PSC+1)/Tclk(时钟分割)

进入中断服务函数后,g_Time_Count溢出次数变量开始自增,每自增一次代表10ms。

//#include "time.c"

void TIME_Init(uint16_t psc, uint16_t arr)
{
	TIM_TimeBaseInitTypeDef TIM_TimeBaseInitStructure;				//定义TIM时基结构
	NVIC_InitTypeDef NVIC_InitStructure;							//定义NVIC(嵌套向量中断)结构体
	
	RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM2, ENABLE);			//开启低速总线时钟
	TIM_InternalClockConfig(TIM2);
	
	TIM_TimeBaseInitStructure.TIM_ClockDivision = TIM_CKD_DIV1;		//时钟分割
	TIM_TimeBaseInitStructure.TIM_CounterMode = TIM_CounterMode_Up;	//向上计数
	TIM_TimeBaseInitStructure.TIM_Period = arr - 1;					//自动重装载值
	TIM_TimeBaseInitStructure.TIM_Prescaler = psc - 1;				//预分频系数
	TIM_TimeBaseInitStructure.TIM_RepetitionCounter = 0;			
	TIM_TimeBaseInit(TIM2, &TIM_TimeBaseInitStructure);
	
	TIM_ClearFlag(TIM2, TIM_FLAG_Update);							//清除TIM的挂起标志
	TIM_ITConfig(TIM2, TIM_IT_Update, ENABLE);						//使能指定的TIM中断,允许更新中断
	
	NVIC_PriorityGroupConfig(NVIC_PriorityGroup_2);					//配置优先级分组:抢占优先级和子优先级
	NVIC_InitStructure.NVIC_IRQChannel = TIM2_IRQn;					//中断源
	NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE;					//使能中断通道
	NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 2;		//抢占优先级
	NVIC_InitStructure.NVIC_IRQChannelSubPriority = 1;				//子优先级
	NVIC_Init(&NVIC_InitStructure);
	
//	TIM_Cmd(TIM2, ENABLE);	//使能定时器 -- 这里暂时不要使能
}

uint16_t Time_Count;//TIM溢出次数 -- 10ms  	Tout(溢出时间) = (ARR+1)(PSC+1)/Tclk(时钟分割)
void TIM2_IRQHandler(void)
{
	if (TIM_GetITStatus(TIM2, TIM_IT_Update) == SET) {
		//进入中断表示定时器计数(CNT)溢出, 自增(10ms溢出一次, 可通过配置ARR, PSC和Tclk自定义溢出时间)
		Time_Count++;				
		
		TIM_ClearITPendingBit(TIM2, TIM_IT_Update);
	}
}
//#include "time.h"

extern uint16_t Time_Count;                //外部声明--让key.c中也可以用到全局变量
void TIME_Init(uint16_t psc, uint16_t arr);

按键配置

首先,按键常规配置不详细介绍,加了外部中断双边沿触发,按下、松开都可以触发中断。

① 配置GPIO结构体初始化;

② 配置外部中断EXTI结构体初始化;

③ 配置NVIC结构体初始化;

        GPIO_InitTypeDef GPIO_InitStructure;        //定义GPIO结构体
        EXTI_InitTypeDef EXTI_InitStructure;          //定义EXTI(外部中断)结构体
        NVIC_InitTypeDef NVIC_InitStructure;         //定义NVIC(嵌套向量中断)结构体

其次、编写中断服务函数

进入中断服务函数后,读取宏定义KEY_STATA函数,判断按键是按下还是松开。

1、按下按键触发中断,按键状态位Keystate = 1进入到按下状态,清空定时器计数,使能定时器,开始计数。

2、松开按键又触发中断,按键状态位Keystate = 0进入到松开状态,失能定时器,看定时器计数来判断长短按键。

3.长按,因为长按的响应与短按的响应不同,短按是按下松开后响应,长按是按下后确定关机后才松开,所以长按的响应不能放在松开按键的响应中。

//#include "key.c"

void KEY_Init(void)
{
	GPIO_InitTypeDef GPIO_InitStructure;					//定义GPIO结构体
	EXTI_InitTypeDef EXTI_InitStructure;					//定义EXTI(外部中断)结构体
	NVIC_InitTypeDef NVIC_InitStructure;					//定义NVIC(嵌套向量中断)结构体
	
	RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA | RCC_APB2Periph_AFIO, ENABLE); 	//开启时钟
	
	GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IPU;				//上拉输入
	GPIO_InitStructure.GPIO_Pin = GPIO_Pin_0;					//PA0引脚
	GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;			//输出频率
	GPIO_Init(GPIOA, &GPIO_InitStructure);						//结构体配置完成初始化
	
	GPIO_EXTILineConfig(GPIO_PortSourceGPIOA, GPIO_PinSource0); //选择EXTI的信号源
    EXTI_InitStructure.EXTI_Line = EXTI_Line0;					//选择EXIT事件线
    EXTI_InitStructure.EXTI_Mode = EXTI_Mode_Interrupt;			//EXTI为中断模式
    EXTI_InitStructure.EXTI_Trigger = EXTI_Trigger_Rising_Falling;//双边沿中断
    EXTI_InitStructure.EXTI_LineCmd = ENABLE;					//使能中断
    EXTI_Init(&EXTI_InitStructure);
	
	NVIC_PriorityGroupConfig(NVIC_PriorityGroup_2);				//配置优先级分组:抢占优先级和子优先级
	NVIC_InitStructure.NVIC_IRQChannel = EXTI0_IRQn;			//中断源
	NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE;				//使能中断通道
	NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 2;	//抢占优先级
	NVIC_InitStructure.NVIC_IRQChannelSubPriority = 1;			//子优先级
	NVIC_Init(&NVIC_InitStructure);
	
}


void EXTI0_IRQHandler(void)
{
	static uint8_t Keystate = 0;	//静态按键状态 0 -- 松开  1 -- 按下
	
	if(EXTI_GetITStatus(EXTI_Line0) != RESET) {
		if(!KEY_STATA && Keystate) {
			
			Keystate = 0;			//松开按键
			TIM_Cmd(TIM2, DISABLE);	//关闭定时器
			if(Time_Count < 3) {
				//按键抖动
				printf("...press, dither time = %d\n", Time_Count);
			}
			else if((Time_Count > 3) && (Time_Count < 200)) {
				//短按、松开后响应
				printf("stort press!\n");
			}
			else if(Time_Count > 200) {
				//长按、松开后响应
				printf("long press!\n");
			}
		}
		else if(KEY_STATA && !Keystate){
			
			Keystate = 1;			//按下按键
			Time_Count = 0;			//清空定时器溢出次数
			TIM_Cmd(TIM2, ENABLE);	//启动定时器
		}
		EXTI_ClearITPendingBit(EXTI_Line0);	//清除中断标志
	}
}
//#include "key.h"

#define KEY_STATE	!!(GPIOA->IDR & 0x0001)                       //寄存器读取按键状态
#define KEY_STATA	!!GPIO_ReadInputDataBit(GPIOA, GPIO_Pin_0)    //标准库读取按键状态

void KEY_Init(void);            //外部声明

定时器中断服务函数更新

前面配置的定时器中断服务函数,可以实现长、短按,但是长按要按下按键默数2s,松开按键才知道是长按,但凡数快一点就成短按了,非常不方便,所以这里我稍作修改。

在按键中断服务函数中,去掉长按,将长按按键放到定时器中断服务函数中。

进入定时器中断服务函数后,Time_Count溢出次数变量开始自增,自增到200及以上,也就是2s以上,就是长按了,然后失能定时器,再清空溢出次数Time_Count = 0。

uint16_t Time_Count;//TIM溢出次数 -- 10ms  	Tout(溢出时间) = (ARR+1)(PSC+1)/Tclk(时钟分割)
void TIM2_IRQHandler(void)
{
	if (TIM_GetITStatus(TIM2, TIM_IT_Update) == SET) {
		//进入中断表示定时器计数(CNT)溢出, 自增(10ms溢出一次, 可通过配置ARR, PSC和Tclk自定义溢出时间)
		Time_Count++;				
		
		if(Time_Count >= 200) {
			TIM_Cmd(TIM2, DISABLE);	//关闭定时器
			Time_Count = 0;			//清空溢出次数
			printf("long press!\n");
		}
		TIM_ClearITPendingBit(TIM2, TIM_IT_Update);
	}
}

void EXTI0_IRQHandler(void)
{
	static uint8_t Keystate = 0;	//静态按键状态 0 -- 松开  1 -- 按下
	
	if(EXTI_GetITStatus(EXTI_Line0) != RESET) {
		if(KEY_STATA && Keystate) {
			
			Keystate = 0;			//松开按键
			TIM_Cmd(TIM2, DISABLE);	//关闭定时器
			if(Time_Count < 3) {
				//按键抖动
				printf("...press, dither time = %d\n", Time_Count);
			}
			else if((Time_Count > 3) && (Time_Count < 200)) {
				//短按、松开后响应
				printf("stort press!\n");
			}
//			else if(Time_Count > 200) {
//				//长按、松开前响应, 所以不能放在这里
//				printf("long press!\n");
//			}
		}
		else if(!KEY_STATA && !Keystate){
			
			Keystate = 1;			//按下按键
			Time_Count = 0;			//清空定时器溢出次数
			TIM_Cmd(TIM2, ENABLE);	//启动定时器
		}
		EXTI_ClearITPendingBit(EXTI_Line0);	//清除中断标志
	}
}

主函数

最后是main.c函数

//#include "main.c"

int main(void)
{
	LED_Init();
	KEY_Init();
	TIME_Init(72, 10000);
	Usart_Init();
	LED_ON;
	printf("KEY + TIM\n");
	
	while (1) {
		
		delay_ms(1000);
	}
}

 运行效果:

源码:链接:https://pan.baidu.com/s/1y3KtWEV_RaXZ5dUeP13dcw?pwd=r0jy 
提取码:r0jy

评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值