Micropython按键检测模块,支持防抖、持续触发
项目需要一个同时支持防抖和持续触发的按键检测模块,github上找了一圈没合适的代码,于是自己DIY了一个如下:
import machine
from machine import Pin, Timer
"""
micropython switch check module
By @Jim 2023-2025
"""
class SwitchButton:
"""Switch Button Class
支持防抖及持续按的按键检测模块
"""
check_period = 80 # 检测周期,实际触发在check_period*checks的时间
continuous_period = 500 # 持续按每500ms触发一次
checks = 2 # 检测几次有效才认为是正确
def __init__(self, pin: Pin):
"""
:param pin:
"""
self.__pin = pin
self.__pin.irq(handler=self._switch_change, trigger=machine.Pin.IRQ_FALLING) # 下降沿触发
self.__check_timer = Timer(-1)
self.__pressed = False
self.__continuous = False
self.__debounce_checks = 0
def _switch_change(self, pin):
self.__debounce_checks = 0
self._start_debounce_timer()
# Disable IRQs for GPIO pin while debouncing
self.__pin.irq(trigger=0)
def _start_debounce_timer(self):
self.__check_timer.init(period=SwitchButton.check_period, mode=machine.Timer.ONE_SHOT,
callback=self._check_debounce)
def _start_continuous_timer(self): # 持续按键检测
self.__check_timer.init(period=SwitchButton.continuous_period, mode=machine.Timer.ONE_SHOT,
callback=self._check_continuous)
def _check_continuous(self, _):
if self.__pin.value() == 0: # 按下
self.__pressed = True
self.__continuous = True
self._start_continuous_timer()
else:
self._release_btn()
def _release_btn(self):
self.__debounce_checks = 0
self.__pressed = False
self.__continuous = False
self._start_debounce_timer()
def pull_state(self) -> tuple(bool, bool):
result = (self.__pressed, self.__continuous)
self.__pressed = False
self.__continuous = False
return result
def _check_debounce(self, _):
if self.__pin.value() == 0: # 按下
self.__debounce_checks = self.__debounce_checks + 1
if self.__debounce_checks == SwitchButton.checks: # N次检测一致,才认为是有效
# Values are the same, debouncing done
self.__pressed = True
self.__continuous = False
self.__pin.irq(handler=self._switch_change, trigger=machine.Pin.IRQ_FALLING) # 恢复下降沿触发
self._start_continuous_timer()
else:
self._start_debounce_timer() # 没达到触发数,继续检测
else: # 松开
self._release_btn()
if __name__ == '__main__':
my_switch = SwitchButton(Pin(2, Pin.IN, Pin.PULL_UP)) # 这里可以换成你自己需要的PIN,默认高电平,短接GND触发
print('start')
while True:
pressed = continuous = False
irq_state = machine.disable_irq()
[pressed, continuous] = my_switch.pull_state()
machine.enable_irq(irq_state)
if pressed:
print(f'OK 被{"持续按下" if continuous else "按下"}')
以上代码虽然实现了防抖和持续触发,但是存在一个问题:使用了稀缺的定时器资源,而要命的是,当再生成一个Timer(-1)的定时器时,会占用了之前的定时器,导致多键触发时,_start_debounce_timer会执行不到。
因此,当多键同时操作时,应该使用irq+协程检测循环的方式,至于防抖,就不用debounce了,使用时间标签做节流throttle。最终修改版本如下:
import machine
from machine import Pin, Timer
import time
"""
micropython switch check module
By @Jim 2023-2025
"""
class SwitchButton:
"""Switch Button Class
支持防抖及持续按的按键检测模块
"""
continuous_period = 1000 # 连续按多少秒进入持续触发
throttle_ms = 400 # 坑爹会触发多次,做一下节流
def __init__(self, pin: Pin):
"""
:param pin:
"""
self.__pin = pin
self.__pin.irq(handler=self.__switch_change, trigger=machine.Pin.IRQ_FALLING) # 下降沿触发
self.__pressed = False
self.__continuous = False
self.__press_tick = 0
def __switch_change(self, _):
if time.ticks_diff(time.ticks_ms(), self.__press_tick) < SwitchButton.throttle_ms: # throttle
return
self.__pressed = True
self.__press_tick = time.ticks_ms()
self.__continuous = False
def check_continuous(self):
if self.__pin.value() == 0: # 按下
if time.ticks_diff(time.ticks_ms(), self.__press_tick) > SwitchButton.continuous_period:
self.__continuous = True
self.__pressed = True
def pull_state(self) -> tuple(bool, bool):
result = (self.__pressed, self.__continuous)
self.__pressed = False
self.__continuous = False
return result
def sleep(self): # 休眠irq
self.__pin.irq(trigger=0)
def awake(self):
self.__pin.irq(handler=self.__switch_change, trigger=machine.Pin.IRQ_FALLING) # 下降沿触发
if __name__ == '__main__':
my_switch = SwitchButton(Pin(2, Pin.IN, Pin.PULL_UP))
print('start')
while True: # 此部分可考虑在协程里检测
my_switch.check_continuous()
pressed = continuous = False
irq_state = machine.disable_irq()
[pressed, continuous] = my_switch.pull_state()
machine.enable_irq(irq_state)
time.sleep_ms(100)
if pressed:
print(f'OK 被{"持续按下" if continuous else "按下"}')
这种模式可以支持多键同时按下,例如,同时按住左和上按钮实现向左上角的移动
上面测试的接线把IO2短接到GND,会看到按键动作触发
注意disable_irq和enable_irq之间的代码尽量要短,建议只传递状态变量到全局存储或队列,不要做复杂逻辑