因为在做华为LiteOS任务挂起和恢复需要使用到按键去触发任务挂起和恢复动作,因为我就萌发出使用状态机这种架构做一个按键检测触发。回想已经也做过按键状态机,不过因为那时候是在裸机上做的按键状态机,比较简单,只有按下和释放功能,而且是单个按键检测的。
这里我借此实验机会使用面向对象的编程思想设计一个可按用户按键个数的按键状态机。面向对象思想可以是驱动程序适合在多种平台快速移植开发,可读性高。
状态机是一个抽象概念,表示把一个过程抽象为若干个状态之间的切换,这些状态之间存在一定的联系。状态机的设计主要包括4个要素:
①现态:是指当前所处的状态。
②条件:当一个条件满足,将会触发一个动作,或者执行一次状态的迁移。
③动作:表示条件满足后执行动作。动作执行完毕后,可以迁移到新的状态,也可以仍旧保持原状态。动作要素不是必需的,当条件满足后,也可以不执行任何动作,直接迁移到新状态。
④次态:表示条件满足后要迁往的新状态。
从上面解释可以清晰地知道,保存当前状态,判断条件是否满足,设置相应的动作标志(这是用于实际的事件触发的),然后将当前状态通过上面的条件判断设置到相应的下一个状态。
这里我还提供了程序框图让大家参考,应该能解决大家对“有按键状态了,为什么还需要按键事件”这个问题,其实就是按键状态状态机循环扫描按键状态的根本,而按键事件时在按键状态中触发的动作。(按键实际状态只有:初始未按下态、按下态、长按态、释放态)
LiteOS 系统中的每一个任务都有多种运行状态,它们之间的转换称为任务状态迁移。如下图所示。
这里亮代码让大家理解了。
key.h
#ifndef __KEY_H
#define __KEY_H
#include "stm32f10x.h"
#include "usart1.h"
#define KEY_UP_GPIO_CLK RCC_APB2Periph_GPIOA
#define KEY_UP_GPIO_PORT GPIOA
#define KEY_UP_GPIO_PIN GPIO_Pin_0
#define KEY0_GPIO_CLK RCC_APB2Periph_GPIOE
#define KEY0_GPIO_PORT GPIOE
#define KEY0_GPIO_PIN GPIO_Pin_4
#define KEY1_GPIO_CLK RCC_APB2Periph_GPIOE
#define KEY1_GPIO_PORT GPIOE
#define KEY1_GPIO_PIN GPIO_Pin_3
#define KEY2_GPIO_CLK RCC_APB2Periph_GPIOE
#define KEY2_GPIO_PORT GPIOE
#define KEY2_GPIO_PIN GPIO_Pin_2
/* 按键状态 */
typedef enum {
KEY_NULL = 0, //初始空状态
KEY_WAIT_CONFIRM_DOWN, //等待确认按下状态
KEY_CONFIRN_DOWN, //确认按下状态
KEY_LONG_DOWN, //长按状态
KEY_UP, //抬起状态
}Key_State_List;
/* 按键类,此处添加按键 */
typedef enum {
KEYUP = 0, //key_up按键
KEY0, //Key0按键
KEY1, //Key1按键
KEY2, //Key2按键
KEY_CLASS_NUM,//按键类个数,即多少个按键
}Key_Class;
/* 按键外设初始化结构体,移植可修改,不同单片机配置外设参数不一样 */
typedef struct {
uint32_t RCC_APB2Periph_GPIOx; //时钟
GPIO_TypeDef* GPIOx; //端口
uint16_t GPIO_Pin_x; //引脚
GPIOMode_TypeDef GPIO_Mode; //模式
}Key_Init;
/* 状态机初始化结构体,移植可修改函数指针,不同单片机读取函数不一样 */
typedef struct {
FunctionalState Key_Use_State; //按键使用状态,使能还是失能
BitAction Key_Press_Level; //表示按键按下,与实际的按下电平对应的
BitAction Key_Current_Level; //表示按键标志,1表示按下,0表示松开
uint8_t Key_Count; //按键长按的时间
Key_State_List Key_State; //用于状态机按键状态切换的
Key_State_List Key_Event; //按键触发事件,用于外部按键判断是否按下,是长按还是短按
BitAction (* Read_Pin_Level)(Key_Init KeyInit); //函数指针,用于读取对应按键IO的输入电平
}Key_State_Machine;
/* 按键结构体,类 */
typedef struct {
Key_Init Key_Init; //按键外设初始化
Key_State_Machine Key_State_Machine; //按键状态机初始化
}Key_Config;
/* 定义长按时间 (47+3)T=按下的时间,T=20ms,Time=1S,移植可修改,根据个数要求定义长按的时间长度*/
#define Key_Long_Pree_Time 47
extern Key_Config KEY_CLASS[KEY_CLASS_NUM];
void Key_Config_parameter(void);
void Read_Key_State(void);
#endif
key.c
#include "key.h"
/* 定义按键,便于添加按键 */
Key_Config KEY_CLASS[KEY_CLASS_NUM];
/* 读按键对应IO电平函数,移植可修改读取电平函数 */
static BitAction Key_Read_Pin_Level(Key_Init KeyInit) {
return (BitAction)GPIO_ReadInputDataBit(KeyInit.GPIOx,KeyInit.GPIO_Pin_x);
}
/* 创建按键,即初始化外设和状态机,Key_State_Machine* Key_State_Machine */
void Creat_Key(Key_Init* KeyInit) {
uint8_t i; //用于逐个对按键状态机结构体赋值
GPIO_InitTypeDef GPIO_InitStructure;
for(i=0;i<KEY_CLASS_NUM;i++) {
/*将配置好的Key_Init结构体参数赋予Key_Config结构体定义好的按键类对应的Key_Init结构体参数*/
KEY_CLASS[i].Key_Init = *(KeyInit+i);
/* 按键初始化结构体复制初始化,移植可修改 */
RCC_APB2PeriphClockCmd(KEY_CLASS[i].Key_Init.RCC_APB2Periph_GPIOx,ENABLE); //时钟使能
GPIO_InitStructure.GPIO_Pin = KEY_CLASS[i].Key_Init.GPIO_Pin_x; //IO引脚
GPIO_InitStructure.GPIO_Mode = KEY_CLASS[i].Key_Init.GPIO_Mode; //IO模式
GPIO_Init(KEY_CLASS[i].Key_Init.GPIOx,&GPIO_InitStructure); //初始化IO
/* KEYUP按键结构体中Key_State_Machine结构体赋值 */
KEY_CLASS[i].Key_State_Machine.Key_Use_State = ENABLE; //使能改按键
KEY_CLASS[i].Key_State_Machine.Key_Count = 0; //长按次数初始化为0
KEY_CLASS[i].Key_State_Machine.Key_Current_Level = Bit_RESET; //初始化按键,为松开状态
KEY_CLASS[i].Key_State_Machine.Key_State = KEY_NULL; //当前按键状态为未按下状态,也就是初始状态
KEY_CLASS[i].Key_State_Machine.Read_Pin_Level = Key_Read_Pin_Level; //将函数指针实例化,指向定义的读IO电平函数
/* 判断输入模式,移植可修改,不同的单片机库的输入模式不一样,这里是下拉输入和上拉输入 */
if(KEY_CLASS[i].Key_Init.GPIO_Mode == GPIO_Mode_IPD) {
KEY_CLASS[i].Key_State_Machine.Key_Press_Level = Bit_SET; //设置按键按下的实际电平为高电平
} else {
KEY_CLASS[i].Key_State_Machine.Key_Press_Level = Bit_RESET;
}
}
}
/* 按键配置并初始化 */
void Key_Config_parameter(void) {
/* 配置按键外设参数,移植可修改 */
Key_Init KeyInit[KEY_CLASS_NUM] = {
{KEY_UP_GPIO_CLK,KEY_UP_GPIO_PORT,KEY_UP_GPIO_PIN,GPIO_Mode_IPD},
{KEY0_GPIO_CLK,KEY0_GPIO_PORT,KEY0_GPIO_PIN,GPIO_Mode_IPU},
{KEY1_GPIO_CLK,KEY1_GPIO_PORT,KEY1_GPIO_PIN,GPIO_Mode_IPU},
{KEY2_GPIO_CLK,KEY2_GPIO_PORT,KEY2_GPIO_PIN,GPIO_Mode_IPU},
};
/* 创建并实例化按键类 */
Creat_Key(KeyInit);
}
/* 获取按键值 */
static void Get_Key_Level(void)
{
uint8_t i;
for(i=0;i<KEY_CLASS_NUM;i++) {
/* 判断按键是否使能,如果失能就判断下一个按键 */
if(KEY_CLASS[i].Key_State_Machine.Key_Use_State==DISABLE) {
continue; //下一次循环
}
/* 读取当前按键IO的输入电平是否与该按键按下状态的电平一样 */
if(KEY_CLASS[i].Key_State_Machine.Read_Pin_Level(KEY_CLASS[i].Key_Init)==KEY_CLASS[i].Key_State_Machine.Key_Press_Level) {
KEY_CLASS[i].Key_State_Machine.Key_Current_Level = Bit_SET; //设置当前为按下状态
} else {
KEY_CLASS[i].Key_State_Machine.Key_Current_Level = Bit_RESET;
}
}
}
/* 读取按键的状态,状态机的4个要素:现态、条件、动作、次态 */
void Read_Key_State(void) {
uint8_t i;
Get_Key_Level(); //获取当前按键值
for(i=0;i<KEY_CLASS_NUM;i++) {
switch(KEY_CLASS[i].Key_State_Machine.Key_State) {
/* 状态0:KEY_NULL按键初始状态,空状态,未按下状态 */
case KEY_NULL:
if(KEY_CLASS[i].Key_State_Machine.Key_Current_Level==Bit_SET) { //有按键按下
/* 将按键状态进入下一个按键等待确认按下状态 */
KEY_CLASS[i].Key_State_Machine.Key_State = KEY_WAIT_CONFIRM_DOWN;
KEY_CLASS[i].Key_State_Machine.Key_Event = KEY_NULL; //空事件触发
} else {
KEY_CLASS[i].Key_State_Machine.Key_Event = KEY_NULL; //空事件触发
}
break;
/* 第二次扫描,判断按键进入按键等待确认按下状态 */
case KEY_WAIT_CONFIRM_DOWN:
/* 再次判断按键是否按下,起到消抖作用 */
if(KEY_CLASS[i].Key_State_Machine.Key_Current_Level==Bit_SET) {
/* 按键进入按键确认按键状态 */
KEY_CLASS[i].Key_State_Machine.Key_State = KEY_CONFIRN_DOWN;
KEY_CLASS[i].Key_State_Machine.Key_Event = KEY_CONFIRN_DOWN; //按键按下触发
KEY_CLASS[i].Key_State_Machine.Key_Count = 0; //按键计数清零,为下面判断是否为长按做准备
} else {
/* 假按下,所有按键没有按下 */
KEY_CLASS[i].Key_State_Machine.Key_State = KEY_NULL;
KEY_CLASS[i].Key_State_Machine.Key_Event = KEY_NULL;
}
break;
/* 第三次扫描,按键按下,是否是长按还是短按 */
case KEY_CONFIRN_DOWN:
/* 如果按键在第三次扫描中判断出按键没有按下,说明是短按,按键已经释放 */
if(KEY_CLASS[i].Key_State_Machine.Key_Current_Level!=Bit_SET) {
KEY_CLASS[i].Key_State_Machine.Key_State = KEY_NULL; //按键释放了,进入最开始状态
KEY_CLASS[i].Key_State_Machine.Key_Event = KEY_UP; //按键释放状态
/* 判断按键仍然按下,并且按下是时间达到长按时间要求 */
} else if((KEY_CLASS[i].Key_State_Machine.Key_Current_Level==Bit_SET) && (++KEY_CLASS[i].Key_State_Machine.Key_Count>=Key_Long_Pree_Time)){
KEY_CLASS[i].Key_State_Machine.Key_State = KEY_LONG_DOWN; //按键为长按状态
KEY_CLASS[i].Key_State_Machine.Key_Event = KEY_LONG_DOWN; //触发按键长按事件
KEY_CLASS[i].Key_State_Machine.Key_Count = 0; //按键计数清零,记录长按的了多少个扫描周期,即为按键触发了多少次按下事件
} else {
/* 按下状态但未达到长按状态 */
KEY_CLASS[i].Key_State_Machine.Key_Event = KEY_NULL; //按键不触发事件
}
break;
/* 第四次扫描,进入长按状态,需要计算按下长按的周期个数 */
case KEY_LONG_DOWN:
/* 如果按键进入长按状态,但是进入后此状态很快就释放了 */
if(KEY_CLASS[i].Key_State_Machine.Key_Current_Level!=Bit_SET) {
KEY_CLASS[i].Key_State_Machine.Key_State = KEY_NULL; //按键为达成长按计算目的,重新回对初始状态
KEY_CLASS[i].Key_State_Machine.Key_Event = KEY_UP; //按键按下后触发按键释放事件
/* 如果按键仍然在按下,那么就记录长按了多少个扫描周期,即连续按了多少下 */
} else if(KEY_CLASS[i].Key_State_Machine.Key_Current_Level==Bit_SET) {
KEY_CLASS[i].Key_State_Machine.Key_Event = KEY_LONG_DOWN; //仍然处于长按触发事件
++KEY_CLASS[i].Key_State_Machine.Key_Count; //累加按键按下扫描周期个数
}
break;
default:
break;
}
}
}
main.c
#include "stm32f10x.h"
#include "los_sys.h "
#include "los_typedef.h"
#include "los_task.ph"
#include "led.h"
#include "usart1.h"
#include "key.h"
/* 定义任务 ID 变量 */
UINT32 Test1_Task_Handle;
UINT32 Test2_Task_Handle;
UINT32 Key_Task_Handle;
/* 函数声明 */
static UINT32 AppTaskCreate(void); //用于创建任务管理
static UINT32 Creat_Test1_Task(void); //创建任务1函数
static UINT32 Creat_Test2_Task(void); //创建任务2函数
static UINT32 Creat_Key_Task(void); //创建任务3函数
static void Test1_Task(void); //任务1函数
static void Test2_Task(void); //任务2函数
static void Key_Task(void); //任务3函数
int main(void)
{
UINT32 uwRet = LOS_OK; //定义一个任务创建的返回值,默认为创建成功
/*中断优先级分组为 4*/
NVIC_PriorityGroupConfig(NVIC_PriorityGroup_4);
/* 板载相关初始化 */
Usart1_Config();
LED_GPIO_Config();
Key_Config_parameter();
printf("KEYUP = %d\r\n",KEY_CLASS[KEYUP].Key_Init.GPIO_Pin_x);
printf("KEY0 = %d\r\n",KEY_CLASS[KEY0].Key_Init.GPIO_Pin_x);
printf("KEY1 = %d\r\n",KEY_CLASS[KEY1].Key_Init.GPIO_Pin_x);
printf("KEY2 = %d\r\n",KEY_CLASS[KEY2].Key_Init.GPIO_Pin_x);
printf("正点原子战舰开发板-LiteOS-SRAM 动态创建多任务!\r\n\r\n");
/* LiteOS 内核初始化 */
uwRet = LOS_KernelInit();
if (uwRet != LOS_OK) {
printf("LiteOS核心初始化失败!任务代码0X%X\r\n",uwRet);
return LOS_NOK;
}
uwRet = AppTaskCreate();
if (uwRet != LOS_OK) {
printf("AppTaskCreate任务创建失败!任务代码0X%X\r\n",uwRet);
return LOS_NOK;
}
/* 开启 LiteOS 任务调度 */
LOS_Start();
while(1){ //上面任务失败会进入此
GPIO_SetBits(GPIOB, GPIO_Pin_5);
GPIO_SetBits(GPIOE,GPIO_Pin_5);
}
}
/*******************************************************************
* @ 函数名 : AppTaskCreate
* @ 功能说明: 任务创建,为了方便管理,所有的任务创建函数都可以放在这个函数里面
* @ 参数 : 无
* @ 返回值 : 无
*************************************************************/
static UINT32 AppTaskCreate(void)
{
/* 定义一个返回类型变量,初始化为 LOS_OK */
UINT32 uwRet = LOS_OK;
uwRet = Creat_Test1_Task();
if (uwRet != LOS_OK) {
printf("Test1_Task任务创建失败!失败代码0X%X\r\n",uwRet);
return uwRet;
}
uwRet = Creat_Test2_Task();
if (uwRet != LOS_OK) {
printf("Test2_Task任务创建失败!失败代码0X%X\r\n",uwRet);
return uwRet;
}
uwRet = Creat_Key_Task();
if (uwRet != LOS_OK) {
printf("Key_Task 任务创建失败!失败代码0X%X\r\n",uwRet);
return uwRet;
}
return LOS_OK;
}
/******************************************************************
* @ 函数名 : Creat_Test1_Task
* @ 功能说明: 创建 Test1_Task 任务
* @ 参数 :
* @ 返回值 : 无
******************************************************************/
static UINT32 Creat_Test1_Task()
{
//定义一个创建任务的返回类型,初始化为创建成功的返回值
UINT32 uwRet = LOS_OK;
//定义一个用于创建任务的参数结构体
TSK_INIT_PARAM_S task_init_param;
task_init_param.usTaskPrio = 5; /* 任务优先级,数值越小,优先级越高 */
task_init_param.pcName = "Test1_Task";/* 任务名 */
task_init_param.pfnTaskEntry = (TSK_ENTRY_FUNC)Test1_Task; //任务入口函数
task_init_param.uwStackSize = 1024; /* 栈大小 */
uwRet = LOS_TaskCreate(&Test1_Task_Handle, &task_init_param); //Test1_Task_Handle任务ID
return uwRet;
}
/******************************************************************
* @ 函数名 : Creat_Test1_Task
* @ 功能说明: 创建 Test1_Task 任务
* @ 参数 :
* @ 返回值 : 无
******************************************************************/
static UINT32 Creat_Test2_Task()
{
//定义一个创建任务的返回类型,初始化为创建成功的返回值
UINT32 uwRet = LOS_OK;
//定义一个用于创建任务的参数结构体
TSK_INIT_PARAM_S task_init_param;
task_init_param.usTaskPrio = 4; /* 任务优先级,数值越小,优先级越高 */
task_init_param.pcName = "Test2_Task";/* 任务名 */
task_init_param.pfnTaskEntry = (TSK_ENTRY_FUNC)Test2_Task; //任务入口函数
task_init_param.uwStackSize = 1024; /* 栈大小 */
uwRet = LOS_TaskCreate(&Test2_Task_Handle, &task_init_param); //Test1_Task_Handle任务ID
return uwRet;
}
/*******************************************************************
* @ 函数名 : Creat_Key_Task
* @ 功能说明: 创建 Key_Task 任务
* @ 参数 :
* @ 返回值 : 无
******************************************************************/
static UINT32 Creat_Key_Task() {
// 定义一个创建任务的返回类型,初始化为创建成功的返回值
UINT32 uwRet = LOS_OK;
TSK_INIT_PARAM_S task_init_param;
task_init_param.usTaskPrio = 3; /* 任务优先级,数值越小,优先级越高 */
task_init_param.pcName = "Key_Task"; /* 任务名*/
task_init_param.pfnTaskEntry = (TSK_ENTRY_FUNC)Key_Task;
task_init_param.uwStackSize = 1024; /* 栈大小 */
uwRet = LOS_TaskCreate(&Key_Task_Handle,&task_init_param);/*创建任务 */
return uwRet;
}
/******************************************************************
* @ 函数名 : Test1_Task
* @ 功能说明: Test1_Task 任务实现
* @ 参数 : NULL
* @ 返回值 : NULL
*****************************************************************/
static void Test1_Task(void) {
int i = 0;
/* 任务都是一个无限循环,不能返回 */
while(1) {
if(i%2==0) {
GPIO_ResetBits(GPIOB, GPIO_Pin_5);
} else {
GPIO_SetBits(GPIOB, GPIO_Pin_5);
}
printf("任务1---1000ms\r\n");
i++;
if(i>100) i=0;
LOS_TaskDelay(1000);
}
}
/******************************************************************
* @ 函数名 : Test2_Task
* @ 功能说明: Test2_Task 任务实现
* @ 参数 : NULL
* @ 返回值 : NULL
*****************************************************************/
static void Test2_Task(void) {
int i = 0;
/* 任务都是一个无限循环,不能返回 */
while(1) {
if(i%2==0) {
GPIO_SetBits(GPIOE,GPIO_Pin_5);
} else {
GPIO_ResetBits(GPIOE,GPIO_Pin_5);
}
printf("任务2---500ms\r\n");
i++;
if(i>100) i=0;
LOS_TaskDelay(500);
}
}
/******************************************************************
* @ 函数名 : Key_Task
* @ 功能说明: Key_Task 任务实现
* @ 参数 : NULL
* @ 返回值 : NULL
*****************************************************************/
static void Key_Task(void) {
UINT32 uwRet = LOS_OK;
UINT8 i,trigger_state;
UINT32 j=0; //是用来检测长按按下的扫描周期数的
while(1) {
Read_Key_State(); //启动状态机检测按键状态
for(i=0;i<KEY_CLASS_NUM;i++) {
trigger_state = KEY_CLASS[i].Key_State_Machine.Key_Event; //获取按键触发状态
if((trigger_state==KEY_CONFIRN_DOWN) && (i==KEYUP)) {
printf("挂起LED_Test2_Task 任务!\r\n");
uwRet = LOS_TaskSuspend(Test2_Task_Handle);
if (uwRet == LOS_OK) {
printf("挂起LED_Test2_Task 任务成功!\r\n");
}
}
if((trigger_state==KEY_CONFIRN_DOWN) && (i==KEY0)) {
printf("恢复 LED_Test2_Task 任务!\r\n");
uwRet = LOS_TaskResume(Test2_Task_Handle);
if (uwRet == LOS_OK) {
printf("恢复 LED_Test2_Task 任务成功!\r\n");
}
}
/* 如果为KEY1和长按事件和长按的周期不为0次,那么我就自减按下的周期数 */
if((trigger_state==KEY_LONG_DOWN) && (i==KEY1) && (KEY_CLASS[i].Key_State_Machine.Key_Count>=0)) {
KEY_CLASS[i].Key_State_Machine.Key_Count--;
++j;
printf("count = %d\r\n",j);
} else if((trigger_state==KEY_UP) && (i==KEY1)) {
j = 0;
}
}
LOS_TaskDelay(20);
}
}
实验现象如下图所示。
小编能力有限,希望大家多多体谅,大家一起相互学习探讨。
工程文件包:华为LiteOS任务挂起和恢复及按键状态机