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

        之前文章

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

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

        优化后文章

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

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

        目录

        1、轻触按键

        2、按键长按和连按原理

        3、定时器配置

        4、按键配置

        5、按键扫描

        6、主函数


        上一节用实现了按键的长、短按键,这一节在原本的功能上加一个多击功能,可以实现双击、三击、四击等等,下面开始吧。

1、轻触按键

        上一章有说过按键,不过不是每个人都看过上一章,这里稍微重复一下。

        按键原理图

        根据原理图可知,在按键没有按下处于断开时,引脚PA0连接电阻R1接地,属于弱下拉,此时PA0为低电平状态;在按键按下处于闭合时,引脚PA0与VCC连接,属于上拉,此时PA0为高电平状态。简单来说,按键按下时为高电平,按键松开时为低电平。

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

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_Scan(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;
}

2、按键长按和连按原理

        判断按键长按和短按的依据就是按下时间的长短,就需要一个计时器来记录时间。这里有两种选择,一种是延时计时,利用delay延时函数通过不断轮询记录时间;另一种是利用单片机的外设——定时器计时,利用定时器的基础计时功能在溢出中断记录时间,两种方法都可以完成计时功能,选择哪一种就看个人喜好,这里用的是第二种。

        有了计时器就可以计时实现按键的长短按,由下面两个图简单说明:

         长按实现:按键按下时,计时器开启开始记录时间;停止计时不是通过按键松开来决定,而是记录时间达到2s(长按时间)时计时器关闭,长按完成后,按键松开无影响。

        连按实现:连按由两个或两个以上短按组成,不一样的是,连按在按键松开后,只清除记录时间,不关闭计时器,然后紧接着记录松开时间,等待多次按键的到来,如果500ms内有按键按下,则开启连按,如果500ms内没有按键按下,则连按结束关闭计时器。

        自定义变量类型,自定义一个变量类型里面包含了,按键计数器valcnt来记录连按了几次,按键计数结束标志位flag来表示单按或者连按结束了,按键长按vallong记录长按,最后一个定时器溢出计数器tim2_cnt用来记录时间,把这些变量整合在一起方便一些。 

typedef struct
{
	uint8_t valcnt;		// 按键计数器
	uint8_t flag;		// 按键计算结束标志位
	uint8_t vallong; 	// 按键长按
	uint16_t tim2_cnt;	// tim2溢出计数器 
}Keyval_TypeDef;

3、定时器配置

        基本配置不变,使用通用定时器TIM2中断溢出计时,定时器常规配置:

  1. 开启时钟
  2. tim2时基初始化
  3. tim2配置内部时钟
  4. tim2中断配置
  5. NVIC设置中断优先级
  6. tim2使能        
/**
  * @brief  tim2初始化
  * @param  psc -- 预分频值
  * @param  arr -- 自动重装载值
  * @retval None
  */
void TIM2_Init(uint32_t psc, uint32_t arr)
{
	TIM_TimeBaseInitTypeDef tim2_init_struct;
	NVIC_InitTypeDef NVIC_init_struct;
	
	//1、开启时钟
	RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM2, ENABLE);
	
	//2、tim2时基初始化
	tim2_init_struct.TIM_ClockDivision = TIM_CKD_DIV1;		//不分频
	tim2_init_struct.TIM_CounterMode = TIM_CounterMode_Up;	//向上计数
	tim2_init_struct.TIM_Prescaler = psc - 1;				//预分频值
	tim2_init_struct.TIM_Period = arr - 1;				//自动重装载值
	tim2_init_struct.TIM_RepetitionCounter = 0;				//重复计数器; 高级定时器才使用
	TIM_TimeBaseInit(TIM2, &tim2_init_struct);
	
	//3、tim2配置内部时钟
	TIM_InternalClockConfig(TIM2);
	
	//4、tim2中断配置
	TIM_ITConfig(TIM2, TIM_IT_Update, ENABLE);
	
	//5、tim2设置中断优先级
	NVIC_init_struct.NVIC_IRQChannel = TIM2_IRQn;
	NVIC_init_struct.NVIC_IRQChannelPreemptionPriority = 2;
	NVIC_init_struct.NVIC_IRQChannelSubPriority = 0;
	NVIC_init_struct.NVIC_IRQChannelCmd = ENABLE;
	NVIC_Init(&NVIC_init_struct);
	
	//6、tim2使能
//	TIM_Cmd(TIM2, ENABLE);
}

 编写TIM中断服务函数

        当使能定时器后,TIM2的寄存器CNT溢出后就会触发中断,进入中断服务函数,每次进入全局变量tim2_cnt自增一次,代表记下一定时间,这个时间根据TIM2_Init()函数的两个参数psc和arr决定。

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

        如果TIM2_Init(72, 1000*10),那么每自增一次代表经过10ms。

        第一个if判断,按键按下时,计时器记录时间达到2000ms实现长按;第二个if判断,按键松开时,计时器记录时间达到500ms实现有无按键按下实现连按。

/**
  * @brief  This function handles TIM2 interrupt request.
  * @param  None
  * @retval None
  */
void TIM2_IRQHandler(void)
{
	if(TIM_GetITStatus(TIM2, TIM_IT_Update)) {
		if((g_key1.tim2_cnt++ >= 200) && KEY1_STATE()) {
			TIM_Cmd(TIM2, DISABLE);	// 按键按下时, 溢出计数器达到2s, 表示长按
			g_key1.vallong = 1;		// 按键长按
			g_key1.valcnt = 0;		// 清空连按次数
		}
		else if((g_key1.tim2_cnt >= 50) && !KEY1_STATE()) {
			TIM_Cmd(TIM2, DISABLE);	// 按键松开时, 溢出计数器达到500ms, 表示连按结束
			g_key1.flag = 1;
		}
	}
	TIM_ClearITPendingBit(TIM2, TIM_IT_Update);
}

4、按键配置

        基本配置不变,使用GPIO外部中断,常规配置:

  1. 开启时钟;
  2. gpio初始化;
  3. 选择信号来源;
  4. 开始EXTI1配置;
  5. 开始NVIC配置;
/**
  * @brief  KEY初始化
  * @param  None
  * @retval None
  */
void KEY_Init(void)
{
	GPIO_InitTypeDef gpio_init_struct;
	EXTI_InitTypeDef exti_init_struct;
	NVIC_InitTypeDef nvic_init_struct;

	//1、开启时钟
	RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA, ENABLE);
	RCC_APB2PeriphClockCmd(RCC_APB2Periph_AFIO, ENABLE);
	
	//2、gpio初始化
	gpio_init_struct.GPIO_Mode = GPIO_Mode_IPU;
	gpio_init_struct.GPIO_Pin = GPIO_Pin_0;
	gpio_init_struct.GPIO_Speed = GPIO_Speed_50MHz;
	GPIO_Init(GPIOA, &gpio_init_struct);
	
	//3、选择信号来源
	GPIO_EXTILineConfig(GPIO_PortSourceGPIOA, GPIO_PinSource0);
	
	//4、开始EXTI1配置
	exti_init_struct.EXTI_Line = EXTI_Line0;//中断通道选择
	exti_init_struct.EXTI_Mode = EXTI_Mode_Interrupt;//中断响应模式
	exti_init_struct.EXTI_Trigger = EXTI_Trigger_Rising_Falling;//双边沿触发
	exti_init_struct.EXTI_LineCmd = ENABLE;//EXTI使能
	EXTI_Init(&exti_init_struct);
	
	//5、开始NVIC配置
	nvic_init_struct.NVIC_IRQChannel = EXTI0_IRQn;//打开中断通道
	nvic_init_struct.NVIC_IRQChannelPreemptionPriority = 3;//抢占优先级
	nvic_init_struct.NVIC_IRQChannelSubPriority = 0;//响应优先级
	nvic_init_struct.NVIC_IRQChannelCmd = ENABLE;//NVIC使能
	NVIC_Init(&nvic_init_struct);
}

编写EXTI中断服务函数:

        进入中断服务函数后,读取宏定义KEY_STATA函数,判断按键是按下还是松开,由原理图可知,当宏函数为真(高电平)时,按键是按下的,当宏函数为假(低电平)时,按键是松开的。

        按下按键计时器开启,开始计时,按键松开判断是否为有效按键,按键计数器自增,清空计时器记录时间,等待下一次按键的到来,按键按下30ms~2000ms为有效按键。

/**
  * @brief  This function handles EXTI0 interrupt request.
  * @param  None
  * @retval None
  */
void EXTI0_IRQHandler(void)
{
	static uint8_t keyval = 0;	// 静态按键状态 0 -- 松开  1 -- 按下
	
	if(EXTI_GetITStatus(EXTI_Line0) != RESET) {
		if(!keyval && KEY1_STATE()) {
			keyval = 1;					// 按键按下
			g_key1.tim2_cnt = 0;		// 清空tim2计算器
			TIM_Cmd(TIM2, ENABLE);		// 启动定时器, 开始计时
		}
		else if(keyval && !KEY1_STATE()) {
			keyval = 0;					// 按键松开
			if((g_key1.tim2_cnt >= 3) && (g_key1.tim2_cnt < 200)) {
				//溢出计数器到50ms和2s之间, 表示短按, 按键计数器g_key1.valcnt自增
				printf("key short, cnt:%d0ms\n", g_key1.tim2_cnt);
				g_key1.valcnt++;		// 按键计数器自增
			}
			g_key1.tim2_cnt = 0;		// tim2溢出次数清零
		}
		
		EXTI_ClearITPendingBit(EXTI_Line0);	// 清除中断标志
	}
}

5、按键扫描

         通过全局变量传递参数,按键扫描,再清除按键计数器。

/**
  * @brief  KEY扫描
  * @param  None
  * @retval None
  */
uint8_t KEY_Scan(void)
{
	if(g_key1.flag) {
		uint16_t temp = g_key1.valcnt;
		g_key1.valcnt = 0;
		g_key1.flag = 0;
		printf("key1 presses:%d\n", temp);
		return temp;
	}
	else if(g_key1.vallong) {
		g_key1.vallong = 0;
		printf("key1 long\r\n");
		return 10;
	}
	return 0;
}

6、主函数

int main(void)
{
	uart1_init(115200);
	
	LED_Init();
	KEY_Init();
	TIM2_Init(7200, 100);	//10ms定时中断
	while(1) {
		KEY_Scan();
		
	}
}

运行效果:

代码分享:

通过百度网盘分享的文件:按键(标准库).7z
链接:https://pan.baidu.com/s/10W7D9UnBLv-znrrVctD1Kg?pwd=ydn5 
提取码:ydn5

STM32单片机中的按键通常通过中断的方式来检测其状态变化。当一个按键被按下并保持(即持续按下),你需要配置一个中断服务程序(ISR)来监控按键的状态。以下是基本步骤: 1. **配置GPIO**:首先将按键对应的GPIO端口设置为输入模式,并禁用上拉或下拉电阻。 ```c GPIO_InitTypeDef GPIO_InitStructure; GPIO_InitStructure.GPIO_Pin = KEY_PIN; // 替换为实际的按键引脚 GPIO_InitStructure.GPIO_Mode = GPIO_Mode_In_FLOATING; // 输入浮空模式 GPIO_Init(GPIO_PORT, &GPIO_InitStructure); ``` 2. **初始化中断**:为按键分配一个中断通道,并设置中断触发条件(如边沿触发)回调函数。 ```c IRQn_Type EXTI_IRQn = KEY_EXTI_IRQn; // 替换为实际的中断编号 EXTI_InitTypeDef EXTI_InitStructure; EXTI_InitStructure.EXTI_Line = KEY_LINE; // 替换为实际的线路号 EXTI_InitStructure.EXTI_Mode = EXTI_Mode_Interrupt; EXTI_InitStructure.EXTI_Trigger = EXTI_Trigger_Rising Falling; // 按键上升沿下降沿触发 EXTI_InitStructure.EXTI_LineCmd = ENABLE; // 配置中断 NVIC_InitStructure.NVIC_IRQChannel = EXTI_IRQn; NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 0; NVIC_InitStructure.NVIC_IRQChannelSubPriority = 0; NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE; NVIC_Init(&NVIC_InitStructure); ``` 3. **定义中断处理函数**:这里编写函数来处理按键被按下及释放的事件。例如,可以计数按键按下的次数。 ```c void KEY_IRQHandler(void) { if (EXTI_GetITStatus(KEY_EXTI_LINE)) // 如果有中断请求发生 { EXTI_ClearITPendingBit(KEY_EXTI_LINE); // 清除中断标志 static uint8_t pressCount = 0; if (!GPIO_ReadInputDataBit(GPIO_PORT, KEY_PIN)) // 按键被按下 pressCount++; else // 按键释放 pressCount--; // 根据需要处理按键连续按下,比如打印次数或进入特定功能等 handle_key_press(pressCount); } } ``` 4. **中断主循环中更新状态**:如果在主循环里也需要跟踪按键状态,可以在合适的地方检查按键的状态变量`pressCount`。 记得在你的应用中添加对`handle_key_press()`这个函数的具体实现,根据需要处理按键的不同情况。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值