Micropython按键检测模块,支持防抖、持续触发

文章介绍了一个用Micropython编写的按键检测模块,该模块支持防抖动处理和持续按键触发功能。作者首先分享了初始版本的代码,然后指出其使用定时器资源的问题,并提出了改进方案,利用时间戳做节流处理,以支持多键同时操作,优化了资源占用并提高了检测的准确性。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

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之间的代码尽量要短,建议只传递状态变量到全局存储或队列,不要做复杂逻辑

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

FoxMale007

文章非V全文可读,觉得好请打赏

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值