前日,江协科技发了第二期的编程技巧,我也是近日学习完之后,突发奇想记录一下。

        内容主要是在第一期:定时器实现非阻塞式程序,的基础上,实现按键的单击、双击、长按操作。这里来主要讲解一下代码怎么写。

        先来看一下我写的代码:

这是main的代码

#include "stm32f10x.h"                  // Device header
#include "Delay.h"
#include "OLED.h"
#include "KEY.H"
#include "TIMER.H"


uint16_t Num1;
uint16_t Num2;


int main (void)
{
	OLED_Init();
	Key_Init();
	TIMER_Init();
	

	while(1)
	{
		
		
		//示例1:
//		if(Key_Check(KEY_HOLD))
//		{
//			Num1 = 1;
//		}
//		else
//		{
//			Num1 = 0;
//		}
		
		
		//示例2:
//		if(Key_Check(KEY_DOWN))
//		{
//			Num1++;
//		}
//		if(Key_Check(KEY_UP))
//		{
//			Num2++;
//		}
		
		
		//示例3:
//		if(Key_Check(KEY_SINGLE))
//		{
//			Num1++;
//		}
//		if(Key_Check(KEY_DOUBLE))
//		{
//			Num1+=100;
//		}
//		if(Key_Check(KEY_LONG))
//		{
//			Num1 = 0;
//		}
		
		
		//示例4:
//		if(Key_Check(KEY_1,KEY_SINGLE)||Key_Check(KEY_1,KEY_REPEAT))		//单击或长按重复,长按快速自增
//		{
//			Num1++;
//		}
//		if(Key_Check(KEY_2,KEY_SINGLE)||Key_Check(KEY_2,KEY_REPEAT))		//单击或长按重复,长按快速自减
//		{
//			Num1--;
//		}
//		if(Key_Check(KEY_3,KEY_SINGLE))										//单击+1,长按快速自增
//		{
//			Num1 = 0;
//		}
//		if(Key_Check(KEY_4,KEY_LONG))										//长按,长按快速自增
//		{
//			Num1 = 9999;
//		}
		
		OLED_ShowNum(1,6,Num1,5);
		OLED_ShowNum(2,6,Num2,5);
	}
	
}


void TIM2_IRQHandler(void)
{
	if(TIM_GetITStatus(TIM2,TIM_IT_Update)==SET)
	{
		
		Key_Tick(KEY_COUNT);

		TIM_ClearITPendingBit(TIM2,TIM_IT_Update);
		
	}
	
}

这是Key.c的代码: 

#include "stm32f10x.h"                  // Device header


#define KEY_1				0
#define KEY_2				1
#define KEY_3				2
#define KEY_4				3

#define KEY_PRESSED			1
#define KEY_UNPRESSED		0
#define KEY_HOLD		    0X01
#define KEY_DOWN			0X02
#define KEY_UP				0X04
#define KEY_SINGLE			0X08
#define KEY_DOUBLE			0X10
#define KEY_LONG		    0X20
#define KEY_REPEAT			0X40

#define KEY_TIME_DOUBLE		200			//双击时间阈值:第一次按下松开后,隔多长时间,再次按下算双击
#define KEY_TIME_LONG		2000		//长按时间阈值:按下不放,过多长的时间,算是长按
#define KEY_TIME_REPEAT		100			//重复时间阈值:长按之后,每隔多久REPEAT置1一次

#define KEY_COUNT			4

uint8_t Key_Flag[KEY_COUNT];			//用二进制表示第0位为HOLD,DOWN,UP,SINGLE,DOUBLE,LONG,REPEAT


void Key_Init(void)
{
	RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOB, ENABLE);
	
	GPIO_InitTypeDef GPIO_InitStructure;
	//下接按键
	GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IPU;
	GPIO_InitStructure.GPIO_Pin = GPIO_Pin_1 | GPIO_Pin_11;
	GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
	GPIO_Init(GPIOB, &GPIO_InitStructure);
	//上接按键
	GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IPD;
	GPIO_InitStructure.GPIO_Pin = GPIO_Pin_13 | GPIO_Pin_15;
	GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
	GPIO_Init(GPIOB, &GPIO_InitStructure);
	
}



//获取按键状态
uint8_t Key_GetState(uint8_t n)
{
	if(n == KEY_1)
	{
		if(GPIO_ReadInputDataBit(GPIOB,GPIO_Pin_1)==0)		//上接按键,按下为低电平
		{
			return KEY_PRESSED;								//表示按键按下
		}
	}
	else if(n == KEY_2)
	{
		if(GPIO_ReadInputDataBit(GPIOB,GPIO_Pin_11)==0)
		{
			return KEY_PRESSED;								
		}
	}
	else if(n == KEY_3)
	{
		if(GPIO_ReadInputDataBit(GPIOB,GPIO_Pin_13)==1)		//下接按键,按下为高电平
		{
			return KEY_PRESSED;								
		}
	}
	else if(n == KEY_4)
	{
		if(GPIO_ReadInputDataBit(GPIOB,GPIO_Pin_15)==1)
		{
			return KEY_PRESSED;								
		}
	}
	return KEY_UNPRESSED;									//表示按键未按下
	
}


uint8_t Key_Check(uint8_t n ,uint8_t Flag)
{
	if(Key_Flag[n] & Flag)
	{
		if(Flag != KEY_HOLD)
		{
			Key_Flag[n] &=~Flag;
		}
		
		return 1;
	}
	
	return 0;
	
}


void Key_Tick(uint8_t n)
{
	static uint8_t Count,i;
	static uint8_t CurrState[KEY_COUNT];						//本次状态
	static uint8_t PrevState[KEY_COUNT];						//上一次状态
	static uint8_t S[KEY_COUNT];
	static uint16_t Time[KEY_COUNT];							//用于递减计时
	
	for(i=0;i<KEY_COUNT;++i)
	{
		if(Time[i] >0)
		{
			Time[i]--;
		}
	}
	
	
	Count++;
	if(Count>=20)
	{
		Count = 0;
		
		for(i=0;i<KEY_COUNT;++i)
		{
			PrevState[i] = CurrState[i];
			CurrState[i] = Key_GetState(i);
			//低级事件的检测
			if(CurrState[i] == KEY_PRESSED)
			{
				Key_Flag[i] |= KEY_HOLD;			//HOLD == 1
			}
			else
			{
				Key_Flag[i] &= ~KEY_HOLD;			//HOLD == 0
			}
			
			if(CurrState[i] == KEY_PRESSED && PrevState[i] == KEY_UNPRESSED)
			{
				Key_Flag[i] |= KEY_DOWN;			//DOWN == 1
			}
			
			if(CurrState[i] == KEY_UNPRESSED && PrevState[i] == KEY_PRESSED)
			{
				Key_Flag[i] |= KEY_UP;				//UP == 1
			}
		
			//高级事件的检测(状态机)
			if(S[i] == 0)											//检测按键按下
			{
				if(CurrState[i] == KEY_PRESSED)
				{
					Time[i] = KEY_TIME_LONG;
					S[i] = 1;
				}
			}
			else if(S[i] == 1)		//检测按键松开,等待长按时间
			{
				if(CurrState[i] == KEY_UNPRESSED)
				{
					Time[i] = KEY_TIME_DOUBLE;						//设定双击时间
					S[i] = 2;
				}
				else if(Time[i] == 0)								//长按时间到
				{
					Time[i] = KEY_TIME_REPEAT;						//设定重复时间
					Key_Flag[i] |= KEY_LONG;						//LONG = 1;
					S[i] = 4;
				}
			}
			else if(S[i] == 2)		//检测按键按下,等待双击时间
			{
				if(Time[i] == 0)									//双击时间到
				{
					Key_Flag[i] |= KEY_SINGLE;						//SINGLE = 1
					S[i] = 0;
				}
				else if(CurrState[i] == KEY_PRESSED)				//按键按下
				{
					Key_Flag[i] |= KEY_DOUBLE;						//DOUBLE = 1
					S[i] = 3; 
				}
			}
			else if(S[i] == 3)		//检测按键松开
			{
				if(CurrState[i] == KEY_UNPRESSED)
				{
					S[i] = 0;
				}
			}
			else if(S[i] == 4)		//检测按键松开,等待重复时间
			{
				if(CurrState[i] == KEY_UNPRESSED)
				{
					S[i]= 0;
				}
				else if(Time[i]== 0)
				{
					Time[i] = KEY_TIME_REPEAT;
					Key_Flag[i] |= KEY_REPEAT;						//REPEAT = 1
					S[i] = 4;
				}
			}
		}
		
		
		
	}
}

这是Key.h的代码: 

#ifndef __KEY_H
#define __KEY_H

#define KEY_1				0
#define KEY_2				1
#define KEY_3				2
#define KEY_4				3

#define KEY_PRESSED			1
#define KEY_UNPRESSED		0
#define KEY_HOLD		    0X01
#define KEY_DOWN			0X02
#define KEY_UP				0X04
#define KEY_SINGLE			0X08
#define KEY_DOUBLE			0X10
#define KEY_LONG		    0X20
#define KEY_REPEAT			0X40

#define KEY_TIME_DOUBLE		200
#define KEY_TIME_LONG		2000
#define KEY_TIME_REPEAT		100

#define KEY_COUNT			4


void Key_Init(void);
uint8_t Key_GetState(uint8_t n);
void Key_Tick(uint8_t n);
uint8_t Key_Check(uint8_t n ,uint8_t Flag);


#endif

        我这里两个文件都写了宏定义,江协的视频是直接在Key.c文件里#include"KEY.H",我不是很喜欢这样写,一般用也是只打开Key.h文件,所以我就都写了一遍,这个无伤大雅,看个人喜好。

        下面来看一下代码的核心部分:

        首先就是Key_Flag的运用,我们定义它为uint8_t类型,在使用时我们将其转化成二进制,对它二进制数的每一位0-1进行定义,我们图上只使用了7位,第7位作为预留位,这里第7位的控留,留着后面增加新的标志位,若要加入的标志位多于8个,可以改成uint16_t类型,这个都因人而异。

        下面是对单击、双击、长按/重复的波形图。依据波形图的样式来写代码对单击、双击、长按/重复进行判断。

        将上述的内容用代码实现,编程思路可以采用状态机的思路,定义变量S用于记录当前程序所处的状态,对每一个状态转向下一个状态进行判断,在每一个状态下,执行相应的操作。     

         最后,要注意的就是上接按键和下接按键了。

Logo

智能硬件社区聚焦AI智能硬件技术生态,汇聚嵌入式AI、物联网硬件开发者,打造交流分享平台,同步全国赛事资讯、开展 OPC 核心人才招募,助力技术落地与开发者成长。

更多推荐