STM32按键设计三之两个按键操控整个系统十几乃至几十种功能

本文主要是介绍STM32按键设计三之两个按键操控整个系统十几乃至几十种功能,希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!

本篇任务

前言:
按键,基本上所有设备的必备,可以毫不夸张的说:目之所及,皆有按键。
几个按键合适?根据系统控制需要,量才而用。按键是大才,能轻易的控制整个系统进行不同状态或者行为的切换,同时也是耗材,浪费IO口,占据大量体积。所以,需要量才而用,买足系统需求的前提下,越精简越好。

本篇将在上一篇按键中断的基础上,实现两个按键控制系统十几乃至几十中状态,节省器件,节约空间,节约IO口,同时又能实现复杂功能,目标就两个字”节约精简“。

按键实现

需要了解本篇,需要对上篇有个大致了解。只需要知道上篇按键中断里面预留的接口函数,本篇将构建与上篇的接口之上。上一篇传送门:STM32按键设计二之按键中断。

  1. 按照质量守恒定律,如果按键即是质量中的量,软件为质量中的质。在减少了按键的数量的同时需要想要实现复杂按键的功能,需要提高软件的质。计算机中的代码优化和很多算法优化大多也是如此,都是在空间复杂度和时间复杂度中做权衡。
  2. 多功能按键需要在软件中将底层的按键设备抽离到软件层面,封装为一个大的结构体类型,整体上进行思考。
  3. 需要抽离出一个软件定时器类型,用于调度整个过程,甚至控制整个系统。
  4. 编写软件的过程实际是在反复验证逻辑的缜密性,很多时候需要大脑对整个过程进行模拟。

多功能按键实现基本逻辑图

在这里插入图片描述

  • 以上是按键实现的基本逻辑图,基本有两点:
  1. 需要在按键按下到松开的时候,进行按键按下时间长度的采集。用作分别设置短按,长按和复位按多种形式。
  2. 在本次按键和下次按键按下之间进行时间判定,如果超时,本次连续按键事件结束,系统控制程序运行。需要设置连续按键间隔时间,可以根据各自的系统按键情况进行设置。一般连按时间多很小。

按键软件实现

实现按键前,需要明白两个接口函数KEY0_Down_callback和KEY0_Up_callback,这是在硬件按键层设置的按键按下和松开接口处理函数,如此实现抽象出来了接口,为了更加容易维护和复用。实现方式和上篇中的传送门:STM32按键设计二之按键中断一模一样,没有做任何更改,可以直接使用。(整个代码篇幅太长,很多不是重点的内容或者重复内容不展示了)

软件定时器实现全局调度功能

  • 通过以上逻辑图,可以看出整个过程需要对时间进行计时,本篇采用软件定时器实现。也可以通过基本硬件定时器实现,控制方式都是一样的,但实现原理上软件定时器稍微复制一些,但是可以提供给整个系统使用,方便万能。直接贴代码:
  • 软件定时器结构体定义
/* 软件定时器结构定义 ---------------- */
STRUCT(Timer_t)
{Timer_Sta_t state;  /* 是否在使用 */int32_t val;        /* 下次触发值 */int32_t arr_val;    /* 重装载值 */u32 cycles;         /* 循环次数: 0xFF: 永远循环,1 ~ 0xFE: 循环次数 */TimerCallback_t callback;Callback_Block_t is_block;
};
  • 首先,定义一个软件定时器列表,用于
Timer_t Timers[TIMERS_SIZE];
  • 软件定时器设置函数,提供给系统任务,每个任务都可以为自己或者其他任务设置软件定时器
/* 函数定义 ------------------------------------------------------- */
/*** @name: Timer_SetAlarm* @description: 添加定时器* @param {u32} cycles* @param {u32} arr* @param {TimerCallback_t} callback* @return {*}*/
int8_t Timer_SetAlarm(u32 cycles, u32 arr, TimerCallback_t callback, Callback_Block_t is_block)
{Timer_t *timer;int8_t timer_index;int8_t ret = TIMER_NONE;for(timer_index = 0, timer = Timers; timer_index <= timers_ptr + 1 && timer_index < (sizeof(Timers) / sizeof(Timers[0])); timer_index++, timer++){if((timer->state == TIMER_FREE) && callback){uint16_t next_time;/* 设置软件定时器重装载值 */timer->arr_val = arr;/* Timers指针增加条件 */if(timer_index == timers_ptr + 1) timers_ptr++;next_time = getNextTimerInterrupt();if(arr < next_time){total_sleep_time += next_wakeup - next_time;next_wakeup = arr + next_wakeup - next_time;setTimer(arr);}/* 设置定时时间和状态 */timer->state = TIMER_WORKING;timer->cycles = cycles;timer->val = arr + next_wakeup - next_time;timer->callback = callback;timer->is_block = is_block;ret = timer_index;break;}}  return ret;
}
  • 软件定时器实现基本调度,在硬件定时器中断中进行调用,本篇硬件定时器配置的时钟分频为10us。
/*** @name: Timer_Dispatch* @description: 时间调度,可以在中断中进行* @param {*}* @return {*}*/
void Timer_Dispatch(void)
{Timer_t *timer;int8_t timer_index;u32 temp = 0xffffffff;for(timer_index = 0, timer = Timers; timer_index <= timers_ptr && timer_index < sizeof(Timers) / sizeof(Timers[0]); timer_index++, timer++){if(timer->state == TIMER_WORKING){timer->val -= next_wakeup;if(timer->val <= 0){                if(timer->cycles == 0xFF){timer->val = timer->arr_val;}else if(timer->cycles > 1){timer->val = timer->arr_val;timer->cycles--;}else if(timer->cycles == 1){/* 当软件定时器执行一次的时候,执行完成 */timer->state = TIMER_FREE;}/* 将定时器时间到达,需要执行的函数加入回调函数列表 */if(timer->is_block == CALLBACK_NOBLOCK){Timers_Callback_noblock[++callback_noblock_ptr] = timer->callback;}else{Timers_Callback_block[++callback_block_ptr] = timer->callback;}}else{if((u32)timer->val < temp)temp = timer->val;}}}/* 设置下次定时器时间 */next_wakeup = temp > TIMn_ARR ? TIMn_ARR : ((uint16_t)temp);setTimer(next_wakeup);}

由于软件定时器不是本篇重点,所以很多内容从简(主要由于写完篇幅过长)。

按键实现

  • 在本篇中依然调用了这两个接口函数进行。
/* 任务回调函数,按键key.c中的接口回调 */
void KEY0_Down_callback(void)
{KEY_Down(KEY0_DEV);
}/* 任务回调函数,按键key.c中的接口回调 */
void KEY0_Up_callback(void)
{KEY_Up(KEY0_DEV);
}

KEY_Down和KEY_Up是具体的处理函数,后续有源码。

  • 使用一个位段,对按键结构进行状态设置,根据状态进行相应的操作
typedef struct _tag_KEY_STA_BIT_t KEY_STA_BIT_t;
struct _tag_KEY_STA_BIT_t
{uint8_t using: 1;           /* 正在采集标志位 */uint8_t processing: 1;      /* 采集完成需要处理标志位 */  uint8_t press_long: 1;    /* 长按标志位 */uint8_t press_3s: 1;    /* 长按标志位 */uint8_t times :4;           /* 按键按下次数 */   
};
  • 同时定义一个共用体,方便对状态位进行一次性操作
typedef union _tag_KEY_STA_t KEY_STA_t;
union _tag_KEY_STA_t
{uint8_t flag;   /* 通过flag操作state,方便清0 */KEY_STA_BIT_t state;
};
  • 定义按键结构体,进行按键的操作,此后基本的按键都基于此
/* 实际按键设备编号 */
typedef enum _tag_KEY_Dev_t KEY_Dev_t;
enum _tag_KEY_Dev_t
{KEY0_DEV = 1,KEY1_DEV,   
};/* 软件key结构体 */
STRUCT(KEY_t)
{KEY_STA_t key_state;                        /* KEY状态值 */KEY_Dev_t keys_table[KEY_TABLE_SIZE];     /* 按键列表 */u32 time_val;                               /* 按下时的系统时间 */
};
  • 下面就是对开头的函数接口中函数的实现了,按键松开处理函数
/*** @name: KEY_Up* @description: 按键松开处理函数* @param {KEY_Dev_t} key_dev 按键设备号* @return {*}*/
static void KEY_Up(KEY_Dev_t key_dev)
{u32 temp;if(!Key.key_state.state.processing) /* 判断没有按键采集完成,未处理 */{temp = get_SystemTime();if(!Key.key_state.state.using)  /* 没开始采集 */{/* 没采集的操作 */}else{temp = temp > Key.time_val ? temp : (temp + TOTAL_SLEEP_TIME_MAX);if(temp - Key.time_val > KEY_PRESS_3sLONG) /* 长按键3s */{/* 长按键只能单独使用 */Key.key_state.state.processing = 1; /* 结束按键采集 */Key.key_state.state.press_3s = 1;Key.key_state.state.times = 1;}else if(temp - Key.time_val > KEY_PRESS_LONG) /* 长按键 */{/* 长按键只能单独使用 */Key.key_state.state.processing = 1; /* 结束按键采集 */Key.key_state.state.press_long = 1;Key.key_state.state.times = 1;}else    /* 短按键 */{/* 短按键可以叠加 */if(Key.key_state.state.times > KEY_COLLECTION_TIMES){Key.key_state.state.processing = 1; /* 结束按键采集 */}else{Key.key_state.state.times++;    /* 按键次数记录 */callback_num = Timer_SetAlarm(1, NEXT_KEY_SLICE, KEY_Timer_Callback, CALLBACK_NOBLOCK);}}Key.keys_table[Key.key_state.state.times - 1] = key_dev;    /* 按键设备号记录 */}}else{/* 按键未处理,新按键按下,处理程序 */}
}
  1. 首先说明:本系统中长按和复位按(3s按)不会进行连按设置,也就是这两种不支持连按。只有短按(接触按)可以使用连按,主要是考虑到实际当中长按用户的一般使用和体验,如果需要也可能实现其逻辑。
  2. KEY_Up进行了按键事件的采集,并且和按下时间进行计算,分析长按和短按模式。长按和复位按不支持连按会设置按键采集完成,需要处理标志位;短按则会判断按键次数和设置一个定时器时间,用于判断是否一次有效的连续采集。
  3. 最后记录了按键的物理设备按键码。
  • 注意:在写代码的过程中,难免手上动作和思维不一样,想清楚而没写对型。callback_num = Timer_SetAlarm(1, NEXT_KEY_SLICE, KEY_Timer_Callback, CALLBACK_NOBLOCK);只写了Timer_SetAlarm(1, NEXT_KEY_SLICE, KEY_Timer_Callback, CALLBACK_NOBLOCK);。编译,运行都可以,出现了逻辑错误,差点没要老命啊!逻辑错误最难找,后面有图示。
  • 定时器调用的函数,简短
/*** @name: KEY_Timer_Callback* @description: 如果在下次之间没有按键,进行按键收尾设置* @param {*}* @return {*}*/
static void KEY_Timer_Callback(void) 
{Key.key_state.state.processing = 1;
}
  • 按键按下处理函数
/*** @name: KEY_Down* @description: 按键按下处理函数* @param {KEY_Dev_t} key_dev 按键设备号* @return {*}*/
static void KEY_Down(KEY_Dev_t key_dev)
{if(!Key.key_state.state.processing) /* 判断没有按键采集完成,未处理 */{printf("key %d down ! \n", key_dev);Key.time_val = get_SystemTime();if(!Key.key_state.state.using)  /* 没开始采集 */{Key.key_state.state.using = 1;  /* 第一次采集 */}else{Timer_DelAlarm(callback_num);}}else{/* 按键未处理,新按键按下,处理程序 */}
}
  1. 首先判断是否上次的按键事件没有正在处理的。
  2. 记录按键按下时间。
  3. 如果是第一次按键按下,将按键使用中标志位置1;如果是连续按键,则需要将软件定时器清除。
  • 主要部分完成。

按键事件状态机实现

多功能按键事件相对简单,可以使用简单的if/else或者switch/case进行实现。对各个状态进行不同的操作可以提供一个函数接口,根据需要对其接口进行实现就可以实现具体的功能。

  • 按键状态机实现代码
/*** @name: KEY_Event_Handler* @description: 按键事件处理函数* @param {*}* @return {*}*/
void KEY_Event_Handler(void)
{u32 short_press_val = 0;if(1 == Key.key_state.state.processing) /* 按键采集完成,需要处理 */{// printf("processing: %d \n", Key.key_state.state.processing);if(1 == Key.key_state.state.press_3s)   /* 按键3s */{KEY_Press_3sLong();} else if(1 == Key.key_state.state.press_long)    /* 长按键处理 */{KEY_Press_Long();}else{   /* 短按键处理 */for(int8_t i = 0; i < Key.key_state.state.times; i++){/* 短按键计算按键结果 */short_press_val += (u32)pow(10, Key.key_state.state.times-1-i) * Key.keys_table[i];}// printf("press short val: %d \n", short_press_val);Short_Press_Handler(short_press_val);}KEY_State_Clear();}
}/*** @name: Short_Press_Handler* @description: 短按键状态机,连按处理方式* @param {u32} val* @return {*}*/
static void Short_Press_Handler(u32 val)
{switch(val){case PRESS_1:KEY_Press_1();break;case PRESS_2:KEY_Press_2();break;case PRESS_11:KEY_Press_11();break;case PRESS_12:KEY_Press_12();break;case PRESS_21:KEY_Press_21();break;case PRESS_22:KEY_Press_22();break;case PRESS_111:KEY_Press_111();break;default:Press_Err_Handler();break;}
}
  1. KEY_Event_Handler 和 Short_Press_Handler一起实现了按键事件的整个状态机,Short_Press_Handler为一个内部函数,只在KEY_Event_Handler 中进行调用,同时又为段案件时间提供了可以编程的接口函数,用于外部实现不同的功能。
  2. 短按键状态判断,根据其按键顺序进行,比如先按1,再按2,则其值为12,调用KEY_Press_12接口进行处理;在Press_Err_Handler未做任何处理。
  3. 所有的函数已经内部实现,但是不影响外部使用,因为内部使用了weak连接进行编译链接,如下放上全部,利于查看调试结果。
/* 按键事件函数接口,对外提供调用接口 ----------------------------------- */
__attribute__((weak)) void Press_Err_Handler(void)
{printf("weak: key press error \n");
}__attribute__((weak)) void KEY_Press_3sLong(void)
{printf("weak: press 3s handle func, system reset! \n");
}__attribute__((weak)) void KEY_Press_Long(void)
{printf("weak: press Long handle func, ! \n");
}__attribute__((weak)) void KEY_Press_1(void)
{printf("weak: KEY_Press_1 \n");
}__attribute__((weak)) void KEY_Press_2(void)
{printf("weak: KEY_Press_2 \n");
}__attribute__((weak)) void KEY_Press_11(void)
{printf("weak: KEY_Press_11 \n");
}__attribute__((weak)) void KEY_Press_12(void)
{printf("weak: KEY_Press_12 \n");
}__attribute__((weak)) void KEY_Press_21(void)
{printf("weak: KEY_Press_21 \n");
}__attribute__((weak)) void KEY_Press_22(void)
{printf("weak: KEY_Press_22 \n");
}__attribute__((weak)) void KEY_Press_111(void)
{printf("weak: KEY_Press_111 \n");
}
  • 同时在APP中对,需要调用接口函数进行了再次实现
void KEY_Press_3sLong(void)
{printf("In APP, 3s press! reset now ... \n");
}void KEY_Press_11(void)
{printf("In APP, press_11 ... \n");
}

结果分析

  • 终于对了
    在这里插入图片描述

以上进行了简单测试

  • 1为只按了一次按键1,调用了默认的接口函数KEY_Press_1 ;
  • 3为连续短按了按键1和按键2,调用了默认的接口函数KEY_Press_12;
  • 4为按下两次按键1,调用了外部实现的任务函数接口KEY_Press_11,打印结果为In APP,说明不是默认函数实现;
  • 5按了3s的按键1,调用系统复位;
  • 6为按下三次按键2,由于此按键事件没有定义和实现,所以调用了默认Press_Err_Handler错误处理函数。
  • 在终于对了之前,出现的问题:
    在这里插入图片描述

多出三只鬼,消灭三只 …

尾声

  • 至此,多功能按键基本完了。
  • 2个按键的多功能实现,按键最多按一次,可以有6种状态,分别为:4+2 = 6
    按键1,按键2,按键1长按,按键2长按,按键1复位按,按键2复位按
  • 2个按键,按键次数设置位2,可以实现10种状态;按键次数设置位3,可以实现18种状态;
  • 总公式为 : 4 + 21 + 22+ … + 2n
  • 推荐使用按键次数设置为2或者3,同时保留长按或者复位按中的一个,这样可以使用的状态有8~~16种之多,如果不够用可以使用状态机,设置不同系统状态下,按键状态对应的功能不一样,有限无穷,周而复始。
  • 按键此时判断在上面KEY_Up函数中,KEY_COLLECTION_TIMES宏定义了按键次数,可以方便更改。


码字不易,请点个赞哦,谢谢!

这篇关于STM32按键设计三之两个按键操控整个系统十几乃至几十种功能的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/768546

相关文章

不懂推荐算法也能设计推荐系统

本文以商业化应用推荐为例,告诉我们不懂推荐算法的产品,也能从产品侧出发, 设计出一款不错的推荐系统。 相信很多新手产品,看到算法二字,多是懵圈的。 什么排序算法、最短路径等都是相对传统的算法(注:传统是指科班出身的产品都会接触过)。但对于推荐算法,多数产品对着网上搜到的资源,都会无从下手。特别当某些推荐算法 和 “AI”扯上关系后,更是加大了理解的难度。 但,不了解推荐算法,就无法做推荐系

基于人工智能的图像分类系统

目录 引言项目背景环境准备 硬件要求软件安装与配置系统设计 系统架构关键技术代码示例 数据预处理模型训练模型预测应用场景结论 1. 引言 图像分类是计算机视觉中的一个重要任务,目标是自动识别图像中的对象类别。通过卷积神经网络(CNN)等深度学习技术,我们可以构建高效的图像分类系统,广泛应用于自动驾驶、医疗影像诊断、监控分析等领域。本文将介绍如何构建一个基于人工智能的图像分类系统,包括环境

水位雨量在线监测系统概述及应用介绍

在当今社会,随着科技的飞速发展,各种智能监测系统已成为保障公共安全、促进资源管理和环境保护的重要工具。其中,水位雨量在线监测系统作为自然灾害预警、水资源管理及水利工程运行的关键技术,其重要性不言而喻。 一、水位雨量在线监测系统的基本原理 水位雨量在线监测系统主要由数据采集单元、数据传输网络、数据处理中心及用户终端四大部分构成,形成了一个完整的闭环系统。 数据采集单元:这是系统的“眼睛”,

嵌入式QT开发:构建高效智能的嵌入式系统

摘要: 本文深入探讨了嵌入式 QT 相关的各个方面。从 QT 框架的基础架构和核心概念出发,详细阐述了其在嵌入式环境中的优势与特点。文中分析了嵌入式 QT 的开发环境搭建过程,包括交叉编译工具链的配置等关键步骤。进一步探讨了嵌入式 QT 的界面设计与开发,涵盖了从基本控件的使用到复杂界面布局的构建。同时也深入研究了信号与槽机制在嵌入式系统中的应用,以及嵌入式 QT 与硬件设备的交互,包括输入输出设

C++11第三弹:lambda表达式 | 新的类功能 | 模板的可变参数

🌈个人主页: 南桥几晴秋 🌈C++专栏: 南桥谈C++ 🌈C语言专栏: C语言学习系列 🌈Linux学习专栏: 南桥谈Linux 🌈数据结构学习专栏: 数据结构杂谈 🌈数据库学习专栏: 南桥谈MySQL 🌈Qt学习专栏: 南桥谈Qt 🌈菜鸡代码练习: 练习随想记录 🌈git学习: 南桥谈Git 🌈🌈🌈🌈🌈🌈🌈🌈🌈🌈🌈🌈🌈�

JAVA智听未来一站式有声阅读平台听书系统小程序源码

智听未来,一站式有声阅读平台听书系统 🌟&nbsp;开篇:遇见未来,从“智听”开始 在这个快节奏的时代,你是否渴望在忙碌的间隙,找到一片属于自己的宁静角落?是否梦想着能随时随地,沉浸在知识的海洋,或是故事的奇幻世界里?今天,就让我带你一起探索“智听未来”——这一站式有声阅读平台听书系统,它正悄悄改变着我们的阅读方式,让未来触手可及! 📚&nbsp;第一站:海量资源,应有尽有 走进“智听

让树莓派智能语音助手实现定时提醒功能

最初的时候是想直接在rasa 的chatbot上实现,因为rasa本身是带有remindschedule模块的。不过经过一番折腾后,忽然发现,chatbot上实现的定时,语音助手不一定会有响应。因为,我目前语音助手的代码设置了长时间无应答会结束对话,这样一来,chatbot定时提醒的触发就不会被语音助手获悉。那怎么让语音助手也具有定时提醒功能呢? 我最后选择的方法是用threading.Time

【区块链 + 人才服务】可信教育区块链治理系统 | FISCO BCOS应用案例

伴随着区块链技术的不断完善,其在教育信息化中的应用也在持续发展。利用区块链数据共识、不可篡改的特性, 将与教育相关的数据要素在区块链上进行存证确权,在确保数据可信的前提下,促进教育的公平、透明、开放,为教育教学质量提升赋能,实现教育数据的安全共享、高等教育体系的智慧治理。 可信教育区块链治理系统的顶层治理架构由教育部、高校、企业、学生等多方角色共同参与建设、维护,支撑教育资源共享、教学质量评估、

软考系统规划与管理师考试证书含金量高吗?

2024年软考系统规划与管理师考试报名时间节点: 报名时间:2024年上半年软考将于3月中旬陆续开始报名 考试时间:上半年5月25日到28日,下半年11月9日到12日 分数线:所有科目成绩均须达到45分以上(包括45分)方可通过考试 成绩查询:可在“中国计算机技术职业资格网”上查询软考成绩 出成绩时间:预计在11月左右 证书领取时间:一般在考试成绩公布后3~4个月,各地领取时间有所不同

系统架构师考试学习笔记第三篇——架构设计高级知识(20)通信系统架构设计理论与实践

本章知识考点:         第20课时主要学习通信系统架构设计的理论和工作中的实践。根据新版考试大纲,本课时知识点会涉及案例分析题(25分),而在历年考试中,案例题对该部分内容的考查并不多,虽在综合知识选择题目中经常考查,但分值也不高。本课时内容侧重于对知识点的记忆和理解,按照以往的出题规律,通信系统架构设计基础知识点多来源于教材内的基础网络设备、网络架构和教材外最新时事热点技术。本课时知识