最近在做的公司给我练手的小项目,是在MCU上做一个管理系统,其中需要用按键的长短按来控制开关机。最初第一个方案是使用延时,可是很快就发现弊端,这样的长按时间很不稳定,经常出差错,所以很快就被我否定了,项目再简单也是项目毕竟不是学校里面的课设。所以我决定用TIM定时器中断来判断开关状态以此来完成长按短按功能。

一、标志位法

通过一些资料的搜集,发现使用标志位法,首先完成TIM定时器的初始化,我这里使用的是CUBE。

首先打开TIM定时器并且初始化好,以及TIM定时器更新中断:

配置好分频系数以及计数器周期。

打开定时器更新中断。

这样关于定时器的初始化就完成了,其他的外设大家可以根据需要配置。

最终得到初始化好的代码:

#include "tim.h"TIM_HandleTypeDef htim1;/* TIM1 init function */
void MX_TIM1_Init(void)
{TIM_ClockConfigTypeDef sClockSourceConfig = {0};TIM_MasterConfigTypeDef sMasterConfig = {0};htim1.Instance = TIM1;htim1.Init.Prescaler = 800-1;htim1.Init.CounterMode = TIM_COUNTERMODE_UP;htim1.Init.Period = 100-1;htim1.Init.ClockDivision = TIM_CLOCKDIVISION_DIV1;htim1.Init.RepetitionCounter = 0;htim1.Init.AutoReloadPreload = TIM_AUTORELOAD_PRELOAD_DISABLE;if (HAL_TIM_Base_Init(&htim1) != HAL_OK){Error_Handler();}sClockSourceConfig.ClockSource = TIM_CLOCKSOURCE_INTERNAL;if (HAL_TIM_ConfigClockSource(&htim1, &sClockSourceConfig) != HAL_OK){Error_Handler();}sMasterConfig.MasterOutputTrigger = TIM_TRGO_RESET;sMasterConfig.MasterSlaveMode = TIM_MASTERSLAVEMODE_DISABLE;if (HAL_TIMEx_MasterConfigSynchronization(&htim1, &sMasterConfig) != HAL_OK){Error_Handler();}/* USER CODE BEGIN TIM1_Init 2 */HAL_TIM_Base_Start_IT(&htim1); //使能定时器 1和定时器 1 更新中断/* USER CODE END TIM1_Init 2 */}void HAL_TIM_Base_MspInit(TIM_HandleTypeDef* tim_baseHandle)
{if(tim_baseHandle->Instance==TIM1){__HAL_RCC_TIM1_CLK_ENABLE();HAL_NVIC_SetPriority(TIM1_UP_IRQn, 0, 0);HAL_NVIC_EnableIRQ(TIM1_UP_IRQn);}
}void HAL_TIM_Base_MspDeInit(TIM_HandleTypeDef* tim_baseHandle)
{if(tim_baseHandle->Instance==TIM1){__HAL_RCC_TIM1_CLK_DISABLE();HAL_NVIC_DisableIRQ(TIM1_UP_IRQn);}
}

其中,第三十五行代码是用来使能定时器以及定时器更新中断的,这个需要手动添加,不然定时器和中断不会工作:

HAL_TIM_Base_Start_IT(&htim1);

定时器配置好了,那么我们来算算多久会激活一次定时器更新中断,上述代码可见我的分频系数是800,周期是100。

定时器的时钟为 8Mhz,分频系数为 800,所以分频后的计数频8Mhz/800=10KHz,然后计数到 100,所以时长为 100/10000=0.01s,也就是 10ms。所以说每1ms都会触发一次定时器更新中断。所以我们在中断回调函数里面判断开关状态:

1、首先设置按键标志位(为了方便看清楚,提供一个思路,不然一组标志位就够了):

int iButtonCount;      //ButtonCount表示按键计数变量
int iButtonFlag;       //ButtonFlag表示重按键标志,1代表重新按键_0为没有重新按键
int g_iButtonState;    //g是globle代表全局变量,会在其他地方引用;ButtonState表示按键标志_1代表按下_0代表松开int longcount;
int longflag;
int longstate;

2、编写中断回调函数(两组标志位完全没卵用,只是分开给人看着更清晰一些):

void HAL_TIM_PeriodElapsedCallback(TIM_HandleTypeDef *htim)//按键中断回调
{if(htim==(&htim1)){if( HAL_GPIO_ReadPin(GPIOE,GPIO_PIN_3) == GPIO_PIN_RESET )//如果弿关丿引脚棿测到为使{    longcount++;iButtonCount++; //按键按下,计数iButtonCount加一if(iButtonCount>=30) //1ms中断服务函数里运行一次,iButtonCount大于等于10,即按键已稳定按30ms{if(iButtonFlag==0) //判断有没有重按键{g_iButtonState=1; //设置按键标志iButtonCount=0;iButtonFlag=1; //设置重按键标志}else //如果重按键,则重新计数iButtonCount=0; }else //如果没有稳定按下10ms,则代表没有按下按键g_iButtonState=0;if(longcount >= 300)//3s长按{if(longflag == 0){longState = 1;longcount = 0;longflag = 1;}elselongcount = 0;//如果重按键,则重新计数}elselongState = 0;//如果没有稳定按下3s,则代表没有长按}else //如果一直没有检测到测到低电平,即一直无按键按下{iButtonCount=0; //清零iButtonCountg_iButtonState=0; //清除按键标志iButtonFlag=0; //清除重按键标志longState = 0;longcount = 0;longflag = 0;}}
}

在主函数加入相关判断(通过led变化分辨):

    if(g_iButtonState == 1){HAL_GPIO_WritePin(GPIOE,GPIO_PIN_5, GPIO_PIN_RESET);  g_iButtonState = 0;   }if(longState == 1){HAL_GPIO_WritePin(GPIOE,GPIO_PIN_5, GPIO_PIN_SET);longState = 0;}

如此功能确实是实现了,但是问题也来了,这样长按的时候短按也触发了一次,尽管是两个标志位(其实一个两个都没插别,因为终究是一个计数器)。所以这样很明显不行,不够严谨。所以这个时候就要用到状态机,将长按和短按状态划分开来。

二、状态机

切换成状态机也就说明前面的回调可以说白写了,当然前面的工作也没有完全白做,起码定时器还是需要的。状态机我没写过不清楚原理,所以是抄了一位大佬的作业,可以去看看原文(名字是「Net_Walke)。原文链接:https://blog.csdn.net/qq_34142812/article/details/119721386

首先,初始化状态枚举,第一个枚举是按键状态枚举,三个成员分别代表,检测状态,按下状态、释放状态;第二个枚举则是代表按键值,分别为空、短按、长按。

#define Key  HAL_GPIO_ReadPin(GPIOA,GPIO_PIN_0)typedef enum
{KEY_CHECK = 0,KEY_COMFIRM = 1,KEY_RELEASE = 2
}KEY_STATE;typedef enum
{NULL_KEY = 0,SHORT_KEY =1,LONG_KEY
}KEY_TYPE;KEY_STATE KeyState = KEY_CHECK;  // 初始化按键状态为检测状态
u8 g_KeyFlag = 0;                // 按键有效标志,0: 按键值无效; 1:按键值有效KEY_TYPE g_KeyActionFlag;        //用于区别长按和短按

按键可能没什么难的,但是还是会惊叹别人清晰的思路,对KEYstate进行判断:

void Key_Scan(void)
{static uint32_t TimeCnt = 0;static uint8_t lock = 0;switch (KeyState){//按键未按下状态,此时判断Key的值case   KEY_CHECK:    if(Key)   {KeyState =  KEY_COMFIRM;  //如果按键Key值为1,说明按键开始按下,进入下一个状态}TimeCnt = 0;                  //计数复位lock = 0;break;case   KEY_COMFIRM:if(Key)                     //查看当前Key是否还是1,再次确认是否按下            {if(!lock){lock = 1;}                    TimeCnt++;  /*按键时长判断*/if(TimeCnt >= 300)            // 长按 3 s{g_KeyActionFlag = LONG_KEY;TimeCnt = 0;  lock = 0;               //重新进入检查模式KeyState =  KEY_RELEASE;    // 需要进入按键释放}                }   else                       {if(lock==1)                // 不是第一次进入,释放按键才执行{g_KeyActionFlag = SHORT_KEY;          // 短按KeyState =  KEY_RELEASE;    // 要进入按键释放状态 }else                          // 当前Key值为1,确认为抖动,则返回上一个状态{KeyState =  KEY_CHECK;    // 返回上一个状态}} break;case  KEY_RELEASE:if(!Key)                     //当前Key值为1,说明按键已经释放,返回检测状态{ KeyState =  KEY_CHECK;    }break;default: break;}
}
//修改回调函数为:
void HAL_TIM_PeriodElapsedCallback(TIM_HandleTypeDef *htim)//定时器中断回调
{if(htim == (&htim1)){    Key_Scan();}
}

最后在主函数,对回调函数返回的值进行判断:

switch(g_KeyActionFlag){case SHORT_KEY:printf("short time\r\n");HAL_GPIO_WritePin(GPIOE,GPIO_PIN_5, GPIO_PIN_RESET);g_KeyActionFlag = 0;        //回到空break;case LONG_KEY://printf("long time,standy now!!\r\n");HAL_GPIO_WritePin(GPIOE,GPIO_PIN_5, GPIO_PIN_SET);HAL_GPIO_WritePin(GPIOB,GPIO_PIN_5, GPIO_PIN_SET);g_KeyActionFlag = 0;        //回到空// Sys_Enter_Standby();break;default: break;}

这样对长按和短按有了完美划分,就不存在按键冲突问题,长按不松手是没办法进入短按的。状态之间一环扣一环,不会随随便便切换成其他状态。

TIM定时器控制按键(按键长短按)相关推荐

  1. 三档按键定时器c语言程序,单片机C语言程序设计:定时器控制4个LED滚动闪烁

    /*  名称:定时器控制 4 个 LED 滚动闪烁 说明:4 只 LED 在定时器控制下滚动闪烁. */ #include #define uchar unsigned char #define ui ...

  2. STM32硬件SPI控制TM1638 按键数码管LED显示模块

    STM32硬件SPI控制TM1638按键数码管LED显示模块   从淘宝买来的,TM1638专门是控制LED的,LED组合起来就可以变成数码管,还有按键,这个我就没管了,不想管了,发这个帖子只是为了记 ...

  3. STM32F4 定时器TIM(1)定时器控制输出【使用库函数】

    高级时钟控制定时器TIM1&TIM8简介: STM32F4的高级控制定时器包含一个自动重装载计数器,计数器的输入是一个被预分频的系统时钟. 这个定时器有多种用途,包括车辆输入信号长度(输入捕获 ...

  4. 《ESP8266学习笔记》之 采用定时器内的按键扫描方法,摒弃传统的延时按键消抖

    简介:传统的按键扫描程序,大部分都是采用 delay_ms(5); 这样的语句来进行按键消抖,但当你把它放在你高速运行的程序中时,这5ms可能会拖慢你的成语运行,导致体验感受下降,因此,我便找到了新的 ...

  5. 【51定时器】独立按键-短按与长按

    之前的代码用Delay(xms)延时,是阻塞性延时,程序会卡住20ms,还有while写检测按键松手一般会卡个500ms,这样程序会慢很多. 用定时器写按键就不会出现上面的问题 //定时器0初始化模板 ...

  6. STM32 TIM定时器的使用(1)——定时

    1.定时器简介 STM32中,定时器的应用非常广泛,涉及计时.信号检测.电机控制等等,并且定时器章节的介绍在STM32F1的手册里面也占据了大量的篇幅,足以看出定时器的重要性. 我将会做5个实验来学习 ...

  7. 每节课都是一个项目 手把手用STM32打造联网气象站-4-STM32基础三件套-TIM定时器和SYSTICK初始化

    STM32基础系列包含了三件套,掌握了这三件套,类似于掌握了程咬金三板斧,就可以开始干项目,创造价值了.毕竟,真正的编程是一项实战性很强的技术,掌握编程主要靠实战,而不是靠知识灌输. STM32的编程 ...

  8. STM32学习笔记(四)丨TIM定时器及其应用(定时中断、内外时钟源选择)

    本篇文章包含的内容 一.TIM 定时器 1.1 TIM 定时器简介 1.2 TIM 定时器类型及其工作原理简介 1.2.1 基本定时器工作原理及其结构 1.2.2 通用定时器工作原理及其结构 1.2. ...

  9. STM32学习笔记(六)丨TIM定时器及其应用(输入捕获丨测量PWM波形的频率和占空比)

    本篇文章包含的内容 一.输入捕获 1.1 输入捕获简介 1.2 输入捕获通道的工作原理 1.3 输入捕获的主从触发模式 1.4 输入捕获和PWMI结构 二.频率的测量方法 2.1 测频法 2.2 测周 ...

最新文章

  1. 技术总监的反思录:我是如何失去团队掌控的?
  2. Hibernate, 想说爱你不容易
  3. nginx学习笔记(7)Nginx如何处理一个请求---转载
  4. H264分辨率解码概述
  5. ElementUI中el-form实现表单重置以及将方法抽出为全局方法
  6. 【Python】编程笔记11
  7. iphone UITableView及UIWebView的使用
  8. 为什么Redux需要reducer成为“纯函数”
  9. 【TensorFlow】TensorFlow从浅入深系列之十三 -- 教你深入理解模型持久化(模型保存、模型加载)
  10. matlab状态空间法算反馈阵,matlab中已知系统的状态方程怎样绘制系统阶跃响应曲线...
  11. Avalon and Indigo CTP- March 2005提供公开下载!
  12. Win10电脑如何查看本机mac地址
  13. java vk减号_Vue入门经常使用指令
  14. 三、Win10 64位PyCharm下打包.py程序为可执行exe文件且兼容32位和64位
  15. java操作.ini文件
  16. windows环境 java jdbc 连接impala (kerberos认证)
  17. c语言程序设计西华大学,知到C语言程序设计(西华大学)章节答案
  18. ubuntu18.04 安装软件中心(software-center)
  19. R markdown的笔记02
  20. 国内IDC数据中心星级评判标准怎么划分

热门文章

  1. 最短路径(Dijkstra算法和Floyd算法)
  2. 【HDR Imaging Digital Overlap】时域多帧HDR技术
  3. Linux(CentOS7)在VMware上的安装以及认识操作系统
  4. 图像处理: Canny边缘检测
  5. java queryinterface_Inside COM读书笔记------QueryInterface接口
  6. webconfig machineKey
  7. HTML5是什么与什么合作推出的语言,H5和Html5是一回事吗?-- -H5和Html5问答
  8. python微信群定时发早安_Python每天定时发早安晚安语录
  9. 单片机汇编语言程序学习
  10. 玩转k8s(二)—— Kubernetes架构