【RT-Thread Nano】作业3:实现按键长按、短按检测

实验任务

本实验为RT-Thread Nano 入门线上培训作业题目3:利用按键实现某个动态线程创建和删除。

实验目的

熟悉RT-Thread按键的检测,熟悉动态线程的创建和删除。

实验环境

1、硬件环境:野火STM32霸道开发板
2、软件环境:RT-Thread Nano 3.1.3,MDK 5.25

实验思路

1、编写按键驱动程序。按键驱动程序要完成的任务就是初始化GPIO,然后实现检测按键状态的检测函数,这个函数一般会被定时器周期调用,当检测到按键有动作时,通过消息队列的形式,向外发送按键消息。
2、创建按键的处理线程,当按键1按下的时候,创建一个新的线程A,当按键2按下的时候,删除线程A。
3、为了方便观察,我这里动态创建和删除的线程A的功能是实现1s间隔的蜂鸣器鸣响。

废话不多说,直接上代码。
4、首先是按键对应的GPIO初始化。

static void ButtonGpioInit(void)
{
	GPIO_InitTypeDef GPIO_InitStructure;
	GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
	//-------------------------------------------------------------------------------------------
	// 对应外设时钟与GPIO配置
	RCC_APB2PeriphClockCmd(GPIO_CLK_Key1 , ENABLE );			// 使能通道时钟
	GPIO_InitStructure.GPIO_Pin  = GPIO_Pin_Key1;				// IO口选择
	GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Key1;				// 配置模式
	GPIO_Init(GPIO_Key1, &GPIO_InitStructure);					// 使能
	//-------------------------------------------------------------------------------------------
	// 对应外设时钟与GPIO配置 
	RCC_APB2PeriphClockCmd(GPIO_CLK_Key2 , ENABLE );			// 使能通道时钟
	GPIO_InitStructure.GPIO_Pin  = GPIO_Pin_Key2;				// IO口选择
	GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Key2;				// 配置模式
	GPIO_Init(GPIO_Key2, &GPIO_InitStructure);					// 使能					
	/****************************************************************************************/
	//用户添加自定义按键配置	

	/****************************************************************************************/
}

5、然后需要实现按键的检测函数,这边使用的是本次线上培训何老师做的按键检测软件包,本质上是一个状态机。我们先来看看软件包所抽象出来的关键数据结构———按键结构体。这个结构体将检测按键需要用到的一些参数进行抽象。这样在检测按键状态的的时候就会比较清晰。

typedef struct
{
	unsigned char  (*IsKeyDownFunc)(void); 					// 函数指针,指向判断按键是否按下的函数 
	unsigned char 	wFilterCount;							// 滤波器计数器 
	unsigned char 	wFilterTime;							// 滤波时间(最大255,表示255ms) 
	unsigned short	wLongCount;								// 长按计数器 
	unsigned short	wLongTime;								// 长按键时间, 0表示不检测长按 
	unsigned char  	byState;								// 按键当前状态(按下还是弹起) 	
	unsigned char 	byKeyCodeUp;							// 按键弹起的键值代码, 0表示不检测按键弹起 	
	unsigned char 	byKeyCodeDown;							// 按键按下的键值代码, 0表示不检测按键按下 
	unsigned char 	byKeyCodeLong;							// 按键长按的键值代码, 0表示不检测长按 	
}Button_t;

6、还有几个比较关键的宏定义

#define BUTTON_FILTER_TIME 	10  	// 滤波时间,单位为ms
#define BUTTON_LONG_TIME 	1000	// 持续1秒,认为长按事件

//-----------------------------------------------------------------------------------------------
// Key按键按下时的电平,=0,按下时为低电平;=1,按下时为高电平
#define KeyPressedLevel 1

//-----------------------------------------------------------------------------------------------
// 获取按键按下函数	
static unsigned char IsKey1Down(void) 		{if (Key1In != KeyPressedLevel) return 0; return 1;}
static unsigned char IsKey2Down(void) 		{if (Key2In != KeyPressedLevel) return 0; return 1;}

7、接下来就是对按键进行初始化,我这边使用了2个按键,就初始化2个按键,其实就是设置他们的回调函数,滤波时间,是否检测长按,等等,也就是对Button结构体赋值。

static void ButtonVarInit(void)
{
	s_tBtnKey1.IsKeyDownFunc=IsKey1Down;					// 检测按键按下函数
	s_tBtnKey1.wFilterCount=0;								// 滤波器计数器 
	s_tBtnKey1.wFilterTime =BUTTON_FILTER_TIME;				// 滤波时间 
	s_tBtnKey1.wLongCount =0;								// 长按计数器 
	s_tBtnKey1.wLongTime=BUTTON_LONG_TIME;					// 长按时间 	
	s_tBtnKey1.byState=0;									// 按键当前状态(按下还是弹起)
	s_tBtnKey1.byKeyCodeUp=KEY1_UP;							// 按键弹起的键值代码, 0表示不检测按键弹起 
	s_tBtnKey1.byKeyCodeDown=KEY1_DOWN;						// 按键按下的键值代码, 0表示不检测按键按下
	s_tBtnKey1.byKeyCodeLong=KEY1_LONG;						// 按键长按的键值代码, 0表示不检测长按 
	
	s_tBtnKey2.IsKeyDownFunc=IsKey2Down;					// 检测按键按下函数
	s_tBtnKey2.wFilterCount=0;								// 滤波器计数器 
	s_tBtnKey2.wFilterTime =BUTTON_FILTER_TIME;				// 滤波时间 
	s_tBtnKey2.wLongCount =0;								// 长按计数器 
	s_tBtnKey2.wLongTime=BUTTON_LONG_TIME;					// 长按时间 	
	s_tBtnKey2.byState=0;									// 按键当前状态(按下还是弹起)
	s_tBtnKey2.byKeyCodeUp=KEY2_UP;							// 按键弹起的键值代码, 0表示不检测按键弹起 
	s_tBtnKey2.byKeyCodeDown=KEY2_DOWN;						// 按键按下的键值代码, 0表示不检测按键按下
	s_tBtnKey2.byKeyCodeLong=KEY2_LONG;						// 按键长按的键值代码, 0表示不检测长按 

	/****************************************************************************************/
	//用户添加自定义按键变量初始化

	/****************************************************************************************/
}

8、然后就是实现按键的检测了。这本质是是一个状态机,并且还做了按键的滤波。这个函数传入的是一个按键结构体,因为之前按键的初始化的时候对两个按键的结构体赋值了,这里就是直接使用他们的值来进行按键的检测。

首先判断传入的按键是否按下,如果按下了,就进行滤波(消抖)操作,因为物理按键按下和抬起的时候,往往会带有几十毫秒的抖动时间,这段时间的电平是忽高忽低的,这里就通过一个滤波计数器进行消抖操作。当检测到按下的时候,滤波计数器就++,检测到抬起的时候,滤波计数器就–,当滤波计数器加到了滤波时间的时候,才认为按下事件发生了。

当按下事件发生了,先判断下原来按键的状态。
如果原来的按键是没有按下的,那现在是按下了,就修改按键的状态,然后通过消息队列发送按下消息。
而如果原来的按键是按下的呢?那就进行长按检测,当长按计数到达长按的时间时,就通过消息队列发送长按消息。

当然按键抬起的检测也是类似的,当滤波计数器减小到0的时候,就说明按键被抬起了。这时候如果原来的按键是按下的,就修改按键状态为抬起,然后通过消息队列发送按键抬起消息。并且将长按计数器清零。

static void ButtonDetect(Button_t *ptBtn)
{
	if(ptBtn->IsKeyDownFunc && ptBtn->IsKeyDownFunc())				// 检测按键函数是否存在,按键是否按下
	{

		if(ptBtn->wFilterCount < ptBtn->wFilterTime)				// 滤波检测,滤波计数器到达滤波时间
		{
			ptBtn->wFilterCount++;									// 计数器加一
			return;													// 退出检测函数
		}
		
		if(ptBtn->byState == 0) 									// 检测是否是按键按下							
		{
			ptBtn->byState = 1;
			rt_mq_send(button_mq,									// 写入(发送)队列的ID(句柄)
								&(ptBtn->byKeyCodeDown),			// 写入(发送)的数据所对应地址 
								sizeof(ptBtn->byKeyCodeDown)		// 数据的长度 
								);
			return;
		}
		if( ptBtn->wLongCount++ == ptBtn->wLongTime)				// 检测是否是按键长按,长按计数器是否到达长按时间
		{
			rt_mq_send(button_mq,									// 写入(发送)队列的ID(句柄)
								&(ptBtn->byKeyCodeLong),  			// 写入(发送)的数据所对应地址 
								sizeof(ptBtn->byKeyCodeLong)		// 数据的长度 
								);
			return;
		}			
	}
	else 
	{		
		if(ptBtn->wFilterCount)										// 滤波检测,滤波计数器是否为0
		{
			ptBtn->wFilterCount--;									// 计数器减一
			return;													// 退出检测函数
		}			
		
		if(ptBtn->byState ==1)										// 检测是否是按键弹起
		{
			ptBtn->byState =0; 										
			rt_mq_send(button_mq,									// 写入(发送)队列的ID(句柄)
								&(ptBtn->byKeyCodeUp),  			// 写入(发送)的数据所对应地址 
								sizeof(ptBtn->byKeyCodeUp)			// 数据的长度 
								);
		}
		
		ptBtn->wLongCount = 0;										// 按键长按计数器清零
	}
}

9、然后就实现这两个按键的周期回调函数即可,也就是把两个按键实例传进去。

void ButtonProj(void)
{
	//该函数在定时器中断回调函数中调用,定时中断周期为1ms
	ButtonDetect(&s_tBtnKey1);										// 检测 K1 键 
	ButtonDetect(&s_tBtnKey2);										// 检测 K2 键 
	/****************************************************************************************/
	//用户添加自定义按键变量初始化
  	//例如:ButtonDetect(&s_tBtnKeyN);	
	
	/****************************************************************************************/
}

10、最后就是创建一个按键处理线程,这个线程创建一个软件定时器,1ms周期调用ButtonProj函数检测按键的状态,线程的主要任务就是等待消息队列的数据,当有消息到达的时候,就执行想做的操作。PS:线程A的代码我就不贴了,不是啥关键的东西。

void button_thread_entry(void *parameter)//用户消息处理入口函数
{
	rt_err_t uwRet = RT_EOK;
	uint8_t r_queue;								//用于接收msg_mq消息队列信息
	rt_timer_t button_timer;						//用于定时调用按键检测函数
  	rt_thread_t tid;								//用于记录动态线程创建的对象
						
	button_mq = rt_mq_create("button_mq",			//消息队列名字
							1,  					//消息的最大长度, bytes
							256,					//消息队列的最大容量(个数)
							RT_IPC_FLAG_FIFO		//如果有多个线程等待此消息,则使用先进先出的方式进行线程调用
							);
	if(button_mq != RT_NULL)
		rt_kprintf("button_mq create success\n\n");
	
	ButtonInit();												//按键硬件接口初始化
	
	button_timer = rt_timer_create("button_timer",				//定时器名字
									button_timer_timeout,       //定时器回调函数
									RT_NULL,                    //参数
									1,    						//1ms检测一次按键
									RT_TIMER_FLAG_PERIODIC | 	//周期调用
									RT_TIMER_FLAG_SOFT_TIMER );	//软件定时
	
	if (button_timer != RT_NULL) 
		rt_timer_start(button_timer);	
	
	while(1)
	{  
		 uwRet = rt_mq_recv(button_mq,							//获取队列信息
							&r_queue,
							sizeof(r_queue),
							RT_WAITING_FOREVER
							);
		 if(RT_EOK == uwRet )
		 {
			 switch(r_queue)									//根据接收到的消息内容分别进行处理
			 {
				 case KEY1_DOWN:rt_kprintf("Receive message:KEY1(PA.0) Down\n\n");		 
					if( tid != RT_NULL )
					{
						tid = rt_thread_create("button_process",
												A_thread_entry, RT_NULL,
												BUTTON_THREAD_STACK_SIZE, BUTTON_THREAD_PRIORITY, 10);

						if (tid != NULL)
						{
							rt_thread_startup(tid);
							rt_kprintf("线程A创建成功!\n");
						}
					}
					else
					{
						rt_kprintf("线程A已存在,不能重复创建!\n");
					}
				 break;
				 case   KEY1_UP:rt_kprintf("Receive message:KEY1(PA.0) Up\n\n");break;
				 case KEY1_LONG:rt_kprintf("Receive message:KEY1(PA.0) LongPressed Down\n\n");break;
				 case KEY2_DOWN:rt_kprintf("Receive message:KEY2(PC.13) Down\n\n");
					if( tid != RT_NULL )
					{
						rt_thread_delete(tid);
						rt_kprintf("线程A删除成功!\n");
						tid = RT_NULL;
					}
					else
					{
						rt_kprintf("线程A不存在,无法删除!\n");
					}
					break;	
				 case   KEY2_UP:rt_kprintf("Receive message:KEY2(PC.13) Up\n\n");break;
				 case KEY2_LONG:rt_kprintf("Receive message:KEY2(PC.13) LongPressed Down\n\n");break;
				 default: rt_kprintf("No button Message!\n\n");break;
			 }
			
		 }
		 else
		 {
			 rt_kprintf("数据接收错误,错误代码:0x%lx\n\n",uwRet);
		 }
	}	
	
}
int button_process_init(void)
{
    rt_thread_t tid;
	
    tid = rt_thread_create("button_process",
                           button_thread_entry, RT_NULL,
                           BUTTON_THREAD_STACK_SIZE, BUTTON_THREAD_PRIORITY, 10);


    if (tid != NULL)
        rt_thread_startup(tid);
    return 0;
}
INIT_APP_EXPORT(button_process_init);
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值