各位玩嵌入式的小伙伴,是不是总遇到需要 “实时盯紧引脚状态” 的场景?比如按键按下去要立马有反应、传感器信号变了要及时捕获 —— 今天咱们就来唠唠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()清计数,不然数值会溢出,最后读不到正确结果~



