网站开发公司前台模板,网站返回顶部怎么做,iis 没有新建网站,企业所得税怎么算举例STM32按键神操作#xff01;短按长按稳如狗#xff0c;回调函数让代码爽到飞起#xff5e;
做STM32项目时#xff0c;你是不是也遇到过这些糟心事儿#xff1f;按键按一下抖三下#xff0c;短按长按傻傻分不清#xff0c;想改个功能还得在按键驱动里翻来翻去#xff0c…STM32按键神操作短按长按稳如狗回调函数让代码爽到飞起做STM32项目时你是不是也遇到过这些糟心事儿按键按一下抖三下短按长按傻傻分不清想改个功能还得在按键驱动里翻来翻去代码缠得像乱麻别急今天就给大家分享一个“神仙方案”——用回调函数搞定按键的短按、长按识别不仅防抖稳如老狗还能让按键逻辑和业务逻辑彻底“分家”后续改代码再也不用头秃一、核心设计思路要实现稳定的按键识别灵活的逻辑解耦关键就两点硬件接对、软件“分工明确”咱们一步步说清楚硬件层面简单到不用动脑按键的接法超简单拿PA0举例一端接PA0引脚一端接VCC然后把GPIO配置成“下拉输入”。说白了就是没按的时候PA0是低电平像个安静的乖宝宝一按下去就变成高电平相当于给单片机发了个“我被按啦”的明确信号不用搞复杂电路软件层面给按键配个“专属管家”软件的核心逻辑就三件事像给按键找了个24小时待命的管家把所有杂活都包了定时器“巡场”选个定时器比如TIM2每隔10ms“巡视”一次按键电平——这既是为了消抖后面细说也是为了计时时长“判身份”记录按键按下的时间按下后不到1秒就松开算“短按”按住超过1秒直接认定为“长按”回调“传消息”定义一个“回调函数”相当于给按键和业务逻辑留了个“专属联络方式”。管家识别出短按/长按后不用跑去找业务逻辑直接“打电话”调用回调函数通知双方互不打扰。二、完整代码实现以STM32F103HAL库为例下面的代码直接抄就能用每部分都给大家讲明白“为啥这么写”新手也能看懂1. 头文件定义key.h给按键“定规矩”头文件主要是明确“游戏规则”——比如按键有哪些状态、会发生哪些事件、需要记录哪些信息相当于给按键建了个“身份档案”。#ifndef__KEY_H#define__KEY_H#includestm32f1xx_hal.h// 按键事件明确按键能触发啥动作typedefenum{KEY_EVENT_NONE,// 无事件啥也没干KEY_EVENT_SHORT_PRESS,// 短按事件KEY_EVENT_LONG_PRESS// 长按事件}Key_Event_E;// 回调函数类型规定“联络方式”的格式参数是按键事件typedefvoid(*Key_Callback_Func)(Key_Event_E event);// 按键状态记录按键当前在干啥typedefenum{KEY_STATE_IDLE,// 空闲未按下KEY_STATE_PRESSED,// 已按下消抖后确认KEY_STATE_LONG_PRESSED// 已长按超过1秒}Key_State_E;// 按键“身份档案”存所有关键信息typedefstruct{GPIO_TypeDef*gpio_port;// 按键所在GPIO端口比如GPIOAuint16_tgpio_pin;// 按键所在引脚比如GPIO_PIN_0Key_State_E state;// 当前状态uint16_tpress_cnt;// 按下时长计数10ms记1次100次1秒Key_Callback_Func cb;// 回调函数联络方式}Key_Handle_S;// 外部声明按键档案示例PA0按键名叫key0_handleexternKey_Handle_S key0_handle;// 函数声明后面要用到的“工具”voidKey_Init(Key_Handle_S*key_handle,GPIO_TypeDef*gpio_port,uint16_tgpio_pin);voidKey_Register_Callback(Key_Handle_S*key_handle,Key_Callback_Func cb);voidKey_Scan_Task(Key_Handle_S*key_handle);// 定时器调用的扫描函数#endif2. 源文件实现key.c管家的具体工作流程这部分是核心相当于把管家的工作流程写死——怎么消抖、怎么判断长短按、怎么通知业务逻辑都在这。#includekey.h// 长按阈值10ms×1001000ms1秒超过这个数就算长按#defineKEY_LONG_PRESS_THRESHOLD100// 消抖阈值10ms×220ms连续检测2次按下才确认避免手抖#defineKEY_DEBOUNCE_THRESHOLD2// 初始化PA0按键的“身份档案”Key_Handle_S key0_handle{.gpio_portGPIOA,.gpio_pinGPIO_PIN_0,.stateKEY_STATE_IDLE,.press_cnt0,.cbNULL// 初始没绑定联络方式};/** * brief 按键档案初始化给按键填档案 * param key_handle: 按键档案指针 * param gpio_port: GPIO端口 * param gpio_pin: GPIO引脚 * retval 无 */voidKey_Init(Key_Handle_S*key_handle,GPIO_TypeDef*gpio_port,uint16_tgpio_pin){key_handle-gpio_portgpio_port;key_handle-gpio_pingpio_pin;key_handle-stateKEY_STATE_IDLE;// 初始状态空闲key_handle-press_cnt0;// 初始计数0}/** * brief 给按键绑定“联络方式”注册回调函数 * param key_handle: 按键档案指针 * param cb: 回调函数业务逻辑的联系方式 * retval 无 */voidKey_Register_Callback(Key_Handle_S*key_handle,Key_Callback_Func cb){if(key_handle!NULLcb!NULL){key_handle-cbcb;// 把联系方式存到档案里}}/** * brief 按键扫描任务定时器10ms调用一次管家巡场 * param key_handle: 按键档案指针 * retval 无 */voidKey_Scan_Task(Key_Handle_S*key_handle){// 读取按键当前电平PA0下拉输入按下是高电平GPIO_PIN_SETuint8_tkey_levelHAL_GPIO_ReadPin(key_handle-gpio_port,key_handle-gpio_pin);switch(key_handle-state){caseKEY_STATE_IDLE:// 空闲状态没按按键if(key_levelGPIO_PIN_SET){// 检测到按键按下key_handle-press_cnt;// 开始计数// 消抖连续2次20ms都检测到按下才确认是真按下if(key_handle-press_cntKEY_DEBOUNCE_THRESHOLD){key_handle-stateKEY_STATE_PRESSED;// 状态改成“已按下”key_handle-press_cnt0;// 重置计数准备算长按时间}}else{key_handle-press_cnt0;// 没按下计数清零}break;caseKEY_STATE_PRESSED:// 已按下状态按键按着没松if(key_levelGPIO_PIN_SET){// 还在按key_handle-press_cnt;// 继续计数// 计数到1001秒触发长按事件if(key_handle-press_cntKEY_LONG_PRESS_THRESHOLD){key_handle-stateKEY_STATE_LONG_PRESSED;// 改成“已长按”状态// 调用回调函数通知业务逻辑“长按了”if(key_handle-cb!NULL){key_handle-cb(KEY_EVENT_LONG_PRESS);}}}else{// 按键松开了// 没到1秒就松触发短按事件if(key_handle-press_cntKEY_LONG_PRESS_THRESHOLD){if(key_handle-cb!NULL){key_handle-cb(KEY_EVENT_SHORT_PRESS);}}// 恢复空闲状态等待下一次按下key_handle-stateKEY_STATE_IDLE;key_handle-press_cnt0;}break;caseKEY_STATE_LONG_PRESSED:// 已长按状态按着超过1秒了if(key_levelGPIO_PIN_RESET){// 按键松开key_handle-stateKEY_STATE_IDLE;// 恢复空闲key_handle-press_cnt0;}// 这里可以扩展比如长按期间持续触发事件像长按音量一直加break;default:key_handle-stateKEY_STATE_IDLE;// 异常状态重置为空闲break;}}3. 定时器配置与中断实现tim.c给管家整个“闹钟”定时器就是管家的闹钟每隔10ms响一次提醒管家去扫描按键。这里用TIM2举例系统时钟72MHz。#includetim.hTIM_HandleTypeDef htim2;// 定时器档案/** * brief TIM2初始化10ms中断闹钟设置 * param 无 * retval 无 */voidMX_TIM2_Init(void){TIM_ClockConfigTypeDef sClockSourceConfig{0};TIM_MasterConfigTypeDef sMasterConfig{0};// 定时器基础配置72MHz时钟→分频后10kHz→再分100次10mshtim2.InstanceTIM2;htim2.Init.Prescaler7199;// 预分频72MHz/(71991)10kHzhtim2.Init.CounterModeTIM_COUNTERMODE_UP;// 向上计数htim2.Init.Period99;// 自动重装10kHz/(991)100Hz→10ms一次中断htim2.Init.ClockDivisionTIM_CLOCKDIVISION_DIV1;htim2.Init.AutoReloadPreloadTIM_AUTORELOAD_PRELOAD_DISABLE;if(HAL_TIM_Base_Init(htim2)!HAL_OK){Error_Handler();}sClockSourceConfig.ClockSourceTIM_CLOCKSOURCE_INTERNAL;if(HAL_TIM_ConfigClockSource(htim2,sClockSourceConfig)!HAL_OK){Error_Handler();}sMasterConfig.MasterOutputTriggerTIM_TRGO_RESET;sMasterConfig.MasterSlaveModeTIM_MASTERSLAVEMODE_DISABLE;if(HAL_TIMEx_MasterConfigSynchronization(htim2,sMasterConfig)!HAL_OK){Error_Handler();}HAL_TIM_Base_Start_IT(htim2);// 开启定时器中断}/** * brief TIM2中断服务函数闹钟响了的“提示音” * param 无 * retval 无 */voidTIM2_IRQHandler(void){HAL_TIM_IRQHandler(htim2);// 调用HAL库中断处理函数}/** * brief 定时器更新中断回调函数闹钟响了让管家干活 * param htim: 定时器档案 * retval 无 */voidHAL_TIM_PeriodElapsedCallback(TIM_HandleTypeDef*htim){if(htim-InstanceTIM2){// 确认是TIM2的闹钟Key_Scan_Task(key0_handle);// 调用扫描函数管家巡场}}4. 业务层调用示例main.c给联络方式填“具体业务”这部分最灵活相当于给按键的“联络方式”填具体内容——短按要干啥、长按要干啥这里改就行不用动上面的驱动代码。#includemain.h#includetim.h#includegpio.h#includekey.h/** * brief 按键回调函数具体业务逻辑短按/长按要做啥 * param event: 按键事件短按/长按 * retval 无 */voidKey_Callback_Func_Example(Key_Event_E event){switch(event){caseKEY_EVENT_SHORT_PRESS:// 短按翻转LED假设LED接PB0按一下亮再按一下灭HAL_GPIO_TogglePin(GPIOB,GPIO_PIN_0);break;caseKEY_EVENT_LONG_PRESS:// 长按关闭LED不管当前亮不亮长按就灭HAL_GPIO_WritePin(GPIOB,GPIO_PIN_0,GPIO_PIN_RESET);break;default:break;}}intmain(void){HAL_Init();// 初始化HAL库SystemClock_Config();// 配置72MHz系统时钟MX_GPIO_Init();// 初始化GPIOPA0按键、PB0 LEDMX_TIM2_Init();// 初始化TIM210ms中断闹钟// 初始化按键绑定回调函数给按键填档案留联系方式Key_Init(key0_handle,GPIOA,GPIO_PIN_0);Key_Register_Callback(key0_handle,Key_Callback_Func_Example);while(1){// 主循环啥也不用干按键事件全靠定时器回调函数自动处理}}三、关键知识点这些坑别踩1. 消抖逻辑让按键“冷静一下”按键是机械结构按下去会有几十毫秒的“抖动”电平反复跳变直接检测会误触发。咱们让定时器每隔10ms扫一次连续2次20ms都检测到按下才确认是真按下——相当于让按键“冷静20ms再说话”完美解决抖动问题。2. 时长判断10ms一个“计数单位”定时器10ms中断一次press_cnt变量每中断一次加1短按按下后松开press_cnt没到100也就是不到1秒长按按下后press_cnt加到100刚好1秒直接触发长按事件。3. 回调函数解耦的“灵魂”这是最核心的优点按键驱动key.c、tim.c负责“识别事件”业务逻辑main.c的回调函数负责“处理事件”两者互不依赖想把短按从“翻转LED”改成“打开串口”只改回调函数不用动驱动想加个按键控制蜂鸣器新增一个按键档案绑定新的回调函数就行不影响旧按键。4. 可扩展性想加功能超简单多按键支持新增key1_handle比如PB1按键在TIM2中断里多调用一次Key_Scan_Task(key1_handle)新增事件想加“长按松开”“短按连按”在Key_Scan_Task的状态机里加个状态就行调整阈值觉得1秒太长把KEY_LONG_PRESS_THRESHOLD改成500.5秒觉得消抖不够把KEY_DEBOUNCE_THRESHOLD改成330ms。四、硬件适配不同接法怎么改如果你的按键是“上拉输入”一端接GND一端接GPIO按下时是低电平这时候只要改key.c里的电平判断// 原来的下拉输入按下是高电平GPIO_PIN_SETuint8_tkey_levelHAL_GPIO_ReadPin(key_handle-gpio_port,key_handle-gpio_pin);// 改成上拉输入按下是低电平GPIO_PIN_RESETuint8_tkey_level(HAL_GPIO_ReadPin(key_handle-gpio_port,key_handle-gpio_pin)GPIO_PIN_RESET)?1:0;定时器也能换想用电TIM3、TIM4只要改定时器初始化参数预分频、自动重装值和中断回调里的定时器实例判断其他代码完全不变怎么样这个方案是不是既稳定又灵活不管是做小项目练手还是做复杂产品都能直接用再也不用为按键抖、代码乱头疼了 赶紧抄代码试试让你的STM32按键从此“听话又好改”