嵌入式玩家必看!Quecpython 外部中断,手把手教你搞懂实时响应

各位玩嵌入式的小伙伴,是不是总遇到需要 “实时盯紧引脚状态” 的场景?比如按键按下去要立马有反应、传感器信号变了要及时捕获 —— 今天咱们就来唠唠Quecpython 的外部中断,这玩意儿能精准捕捉 GPIO 引脚的电平变化,还能通过回调函数秒级响应,低功耗场景、高效响应需求都能 hold 住!

一、先搞懂:外部中断是啥?能干啥?

简单说,外部中断就是 “GPIO 引脚的电平突然变高 / 变低时,系统立马停下手里的活,去执行你指定的操作”。比如:

● 按键检测:按一下按键,引脚电平变了,中断触发,立马打印 “按键被按啦”;

● 传感器捕获:温湿度传感器输出信号跳变,中断触发,赶紧读取数据;

● 低功耗场景:平时系统休眠,引脚一有动静就唤醒,超省电!

而且 ExtInt(外部中断模块)还支持引脚上下拉配置(让引脚有默认电平)、中断触发滤波(过滤按键抖动这种小干扰),实用性拉满~

二、GPIO 输入模式:上拉输入先吃透!

咱们先从最常用的 “上拉输入” 说起,毕竟按键检测、传感器配合常用它!

1. 原理:内部藏了个 “小电阻”

模组内部悄悄集成了一个 30~50kΩ 的上拉电阻。这就意味着:

● 没人碰引脚的时候(无外部输入),电阻会把引脚 “拉到” 高电平;

● 外部给低电平时(比如按键按下),引脚就会变成低电平(可以理解成 “低电平把高电平‘拉下来’了”)。

2. 为啥选它?3 个优点很实在

● 抗干扰强:默认高电平,不容易被外界杂波影响,按键 / 传感器用着稳;

● 配置简单:在 Quectel 模块里直接写Pin.PULL_PU就行;

● 场景适配广:单键检测(按按键拉低引脚)、I2C/SPI 从机使能,都能用它。

三、实例类函数:参数 & 用法一次说清

要用上外部中断,核心是machine.ExtInt这个类,咱们把它的参数和常用函数掰开揉碎讲!

1. 类的初始化:5 个参数要记牢

创建中断对象的格式是这样的:class machine.ExtInt(GPIOn, mode, pull, callback, [filter_time])

每个参数啥意思?看这张表就懂:

2. 常用函数:开启中断、查计数都靠它

创建完中断对象,这些函数能帮你操控中断:

**● 开启中断:**extint.enable()功能:让中断开始 “盯” 引脚,触发了就调用回调函数返回值:0 = 成功,-1 = 失败(失败了记得查引脚配置哦)

**● 查中断计数:**extint.read_count(is_reset)功能:看引脚上升沿 / 下降沿触发了多少次参数:is_reset=0(读完不重置计数),is_reset=1(读完清空计数)返回值:[上升沿次数, 下降沿次数](比如[3,2]就是上升 3 次、下降 2 次)

**● 清计数:**extint.count_reset()功能:把中断计数归零,避免数值溢出(高频触发场景必用!)返回值:0 = 成功

**● 读当前电平:**extint.read_level()功能:看引脚现在是高还是低返回值:0 = 低电平,1 = 高电平

四、代码实战:两个例子搞定按键检测

光说不练假把式,咱们上两个实用例子,直接跑起来就能用!

示例 1:双按键计数 —— 按一次记一次

需求:两个按键(key1 接 GPIO12,key2 接 GPIO13),按一次就打印计数和触发状态。

from machine import ExtInt
import utime
def fun1(args):
    global A
    A +=1
    print('Count:{},state:{}'.format(A, args))
    print("now is key1 press")
def fun2(args):
    global B
    B += 1
    print('Count:{},state:{}'.format(B, args))
    print("now is key2 press")
def main():
    global A
    global B
    A = 0
    B = 0
    extint1 = ExtInt(ExtInt.GPIO12, ExtInt.IRQ_RISING, ExtInt.PULL_PD, fun1)
    extint1.enable()
    extint2 = ExtInt(ExtInt.GPIO13, ExtInt.IRQ_FALLING, ExtInt.PULL_PU, fun2)
    extint2.enable()
    while True:
        utime.sleep_ms(5000)
        # print("。。。。。。。。。")
if __name__ == '__main__':
    main()

运行效果:按一次跳一次

示例 2:长按检测 —— 按 3 秒才算 “长按”

需求:一个按键(接 GPIO13),短按打印 “RELEASED”,长按(超过 3 秒)打印 “LONG PRESS”。

核心逻辑:按下按键→触发回调→启动 3 秒定时器;如果 3 秒内松开→取消定时器(算短按);如果 3 秒后没松开→定时器触发,打印 “长按”。

import machine
from machine import ExtInt
import time
class Button:
    def __init__(self, pin, timer_id):
        self.pin = machine.ExtInt(pin, ExtInt.IRQ_RISING_FALLING, ExtInt.PULL_PU, self.callback)
        self.pin.enable()
        self.counter = 0
        self.prev_state = self.pin.read_level()
        self.debounce_count = 5
        self.click_timestamp = None
        self.long_press_time = 3000  # 1 second
        self.state = "UP"
        self.reported_long_press = False
        self.timer = machine.Timer(timer_id)
    def callback(self, pin):
        # Software counter debounce
        if self.pin.read_level() == self.prev_state:
            self.counter += 1
            if self.counter < self.debounce_count:
                return
        self.counter = 0
        self.prev_state = self.pin.read_level()
        current_time = time.ticks_ms()
        # Button pressed
        if self.pin.read_level() == 0:
            self.click_timestamp = current_time
            self.reported_long_press = False
            print("Button PRESSED")
            # Set timer for long press detection
            self.timer.start(period=self.long_press_time, mode=machine.Timer.ONE_SHOT, callback=self.long_press_handler)
        # Button released
        else:
            self.timer.stop()  # Cancel long press timer
            if self.click_timestamp:
                press_duration = time.ticks_diff(current_time, self.click_timestamp)
                if not self.reported_long_press:
                    if press_duration > self.long_press_time:
                        self.reported_long_press = True
                    else:
                        print("Button RELEASED")
                        self.state = "UP"
                self.click_timestamp = None
    def long_press_handler(self, timer):
        if not self.reported_long_press:
            print("Button LONG PRESS")
            self.reported_long_press = True
            self.click_timestamp = None
#Usage
button = Button(ExtInt.GPIO13, timer_id=0)  # Button on GPIO 2, Timer 0

运行效果:短按 vs 长按一眼分

五、避坑指南:这些细节别踩雷!

1.模块兼容性要注意

● EC600E/EC800E 不支持双边沿触发(别写IRQ_RISING_FALLING),只能用单边沿;

● filter_time(滤波时间)不是所有模块都有,比如 EG912N/EC600M 支持,其他型号先查手册!

2.上下拉配置有讲究

传感器 / 按键是 “低电平有效”(比如按下变低)→用PULL_PU(默认高电平);是 “高电平有效”(比如触发变高)→用PULL_PD(默认低电平),别搞反了!

3.回调函数别 “偷懒”

别在回调里写耗时操作(比如写文件、发网络请求),会拖慢中断响应!建议只设个 “标志位”(比如is_pressed = True),让主程序慢慢处理。

4.高频触发要清计数

如果引脚频繁触发中断(比如传感器每秒跳几十次),记得定期调用count_reset()清计数,不然数值会溢出,最后读不到正确结果~