ARTICLE DETAIL

资讯详情

深耕郑州网站建设与运营推广的一线实战洞察。

五向按键在树莓派Pico上的MicroPython实现与状态机消抖解析

五向按键在树莓派Pico上的MicroPython实现与状态机消抖解析 五向按键这东西在嵌入式项目里出现的频率远超你的想象。小到单片机开发板上的菜单选择大到仪器仪表的参数设置几乎都能看到它的身影——五个方向共用一个封装比单独接五个轻触开关省掉一堆IO和PCB面积逻辑上也天然就是一组“上/下/左/右/确认”的语义。但真把它接到树莓派 Pico以下简称 Pico上跑 MicroPython很多新手会栽在同一个坑里硬件上看着是五个按键代码读出来却乱跳、串键甚至死活读不到某个方向。这篇文章不绕弯子直接拆开讲明白五向按键的触点结构、在 Pico 上的两种接线方案ADC 分压法和 GPIO 直连法、MicroPython 状态判断的完整思路最后给出一份可以直接抄走的工程级代码。这套内容适合正在做菜单交互、手持设备、小型游戏机或者任何需要“方向确认”操作场景的朋友。无论你手里是那种带金属弹片的摇杆式五向键还是贴片式五向轻触开关原理都一致照着改就能用。1. 内容整体设计与思路拆解1.1 先弄清五向按键的“五向”是怎么来的五向按键从机械结构上说是在一个基座上集成了五个独立触点——上、下、左、右、中。外部是一个可以活动的操作帽按下某个方向时对应方向的内部簧片或导电胶就会把公共端Common和该方向的信号端接通。这里有个非常关键的电气细节五个方向的触点不是独立地各管各而是共用一个公共引脚。市面上常见的五向按键一般引出 6 个引脚其中一个公共端通常标记为 COM 或 GND另外五个分别对应五个方向。按下“上”时COM 和“上”引脚导通按下“中”时COM 和“中”引脚导通同时按下“上”和“中”这种奇葩操作虽然物理上可能但在普通设计里基本不会去处理当作无效组合处理就行。理解了这一点你在画原理图的时候就不会犯“五个按键各接各的IO”这种错。五向按键的输出本质上是一个单刀多掷开关实际上是多个单刀单掷开关的集合公共端接一个确定电平五个方向引脚分别接 Pico 的输入引脚或 ADC 通道按下时对应引脚被拉高/拉低松开时恢复默认状态。1.2 Pico 读五向按键的两种主流方案数字直连与 ADC 分压Pico 的 GPIO 本身支持数字输入所以最直观的做法是每个方向引脚接一个 GPIO公共端接 3.3V 或 GND配合内部上拉/下拉电阻按下时读到电平变化。这种方案优点是完全独立、互不干扰缺点是占用 5 个 GPIO。另一种更省 IO 的经典方案是 ADC 分压法把五向按键做成一个“模拟摇杆”公共端接 3.3V五个方向引脚分别通过不同阻值的电阻接地Pico 用一路 ADC 去读公共端的电压。按下不同方向时分压比不同ADC 读到的电压值就不同程序根据电压区间判断是哪个方向。这种方案只占 1 个 ADC 引脚代价是结构上要处理模拟电压的噪声、电阻精度、漂移等问题。我个人的建议是如果 Pico 的 GPIO 够用绝大多数项目够用优先用数字直连。原因有三个第一数字信号判断逻辑简单不存在阈值漂移第二调试时逻辑分析仪直接看高低电平排查问题快得多第三MicroPython 里读 GPIO 比读 ADC 快、稳代码也好写。ADC 分压法更适合 GPIO 紧张的场景或者按键本身集成在模拟摇杆模块里、硬件上已经确定分压电阻的场合。1.3 为什么用状态机而不是简单延时轮询很多新手写按键扫描最爱干的事就是while True里time.sleep(0.05)然后照着读电平、打印方向。这在单独一个按键、单独一个任务的 demo 里能跑但一旦你的项目里同时有 OLED 刷新、舵机控制、串口通信这种阻塞式轮询立刻变成灾难——按一下键整个系统卡 50 毫秒屏幕闪一下舵机顿一下。正确的思路是把按键扫描拆成两层底层负责在极短的时间片内采样并消抖上层通过状态标志或队列感知按键事件。五向按键因为有五个方向天然适合用“按键状态机”——每个方向维护一个独立的状态状态在“未按下-消抖中-已按下-释放中”之间迁移。状态机的好处是它知道按键“刚被按下”和“一直被按住”的区别这对做菜单交互太重要了短按一下菜单项移动一格长按连续滚动都是同一个按键不同逻辑。所以这篇文章后面的代码会直接给出一个精简的状态机框架而不是简单的if gpio.value() 0: print(pressed)那种玩具代码。2. 核心细节解析与实操要点2.1 触点抖动到底是怎么一回事为什么必须消抖无论是金属簧片还是导电胶按键按下和松开的瞬间机械触点都不是一下子稳定导通/断开而是在几毫秒到几十毫秒内反复通断这个过程叫抖动bounce。你拿示波器看按下瞬间的电平波形会看到一串毛刺而不是干净的高低电平跳变。Pico 的 CPU 主频 133MHzMicroPython 的读取速度虽然比不上 C但也足够在毫秒级的时间内读到好几次这个毛刺。如果不做处理程序可能把一次按下识别成“按下-松开-按下-松开”的十几次切换菜单直接飞出去好几格。消抖分硬件和软件两条路。硬件上在按键信号线上并联 0.1uF 电容或在公共端串联电阻可以显著减小抖动宽度但电容不能乱加——太大了按键响应变慢太小了没效果。软件上最经典的做法是“连续多次读取值稳定才算数”或者“检测到变化后延时 10~20ms 再读一次确认”。在 MicroPython 这种高级语言环境下软件消抖足够可靠配合状态机用“进入消抖状态后每隔 5ms 再读一次连续两次一致才确认”的策略效果非常好。2.2 数字直连方案上拉还是下拉内部电阻够不够用Pico 的 GPIO 内部有可编程的上拉和下拉电阻MicroPython 里Pin.PULL_UP和Pin.PULL_DOWN可以直接启用。这里要特别注意一个常见设计误区很多教程默认用上拉输入——公共端接地引脚内部上拉按下时引脚被拉低。这个方案在按键距离 Pico 很近时没问题但如果按键通过长飞线连接外部电磁干扰可能造成误触发。我习惯用另一种更稳的做法公共端接 3.3V引脚内部下拉按下时引脚读到高电平。原因有三个按下读到高电平在直觉上更符合“按下有效”的语义代码里if key_on()一目了然。如果外部环境电磁干扰强高电平抗干扰能力略好于低电平干扰源多为地弹和共模噪声。万一你选的无源蜂鸣器、LED 之类的器件和按键共用一个电源高电平有效不容易产生意外的误动作。需要注意的是Pico 内部上拉/下拉电阻的典型值是 56kΩ不算很强。如果你的应用现场特别恶劣建议外部再并一个 10kΩ 电阻拉到底。不过在常规桌面项目里内部的够用。2.3 ADC 分压方案电阻阻值怎么选阈值怎么定如果你决定用 ADC 方案最常见的设计是公共端接 3.3V五个方向脚各接一个到地的电阻阻值从小到大排列Pico 的 ADC0 接公共端。按下“上”时3.3V 经按键内部接通到“上”引脚再经该引脚对应的下拉电阻到地ADC 读到的就是 3.3V 在该电阻与其他并联在公共端上的电阻不实际上是按键内部导通公共端与该方向引脚该方向引脚上有个电阻到地这样就形成了一个分压——严格说如果公共端直接接 ADC而不是接在电阻中点上老式五向按键内部并不是继电器式的机械多路开关而是一组按钮分别连接公共端按某一个方向时只有该方向对应的电阻被接入分压。实际接法是这样的按键公共端COM接 3.3V五个方向脚分别各串联一个电阻后接地中间的分压点接 ADC。按下“上”时上方的电阻被接入3.3V 在该电阻和接地回路之间分压ADC 读到的电压就是该电阻上的分压值相对于 3.3V。五个方向用五个不同阻值电压就会分成五个区间。举例五个电阻从小到大用 1k、2k、4.7k、10k、22k接地的另一端即分压点的电压分别是1k3.3 * (1 / (1 0)) 这种算法不对——实际上应该理解成 3.3V 经按键到该电阻到地ADC 读到电阻上的电压。如果按键到公共端是导通等效电路是 3.3V 直接加在该电阻上所以电压就是 3.3V不对你得把公共端当电源电阻在按键和地之间按下即相当于 3.3V 直接给电阻供电那么电阻上的电压就是 3.3V这一端接 ADC 的话读到的是 3.3V——这就没有分压了。经典的五向按键 ADC 接法是把分压网络做成3.3V 经过一个“上拉电阻”接到 ADC 点ADC 点再分别经五个按键引脚接不同的下拉电阻到地。按下某个方向时上拉电阻与该方向的下拉电阻形成分压ADC 读取中点电压。所以五个方向电压不同是因为下拉电阻不同。讲到这里我怕模糊直接说一个我实测过的配置你抄作业就行上拉电阻从 3.3V 到 ADC 输入点10kΩ五个方向的下拉电阻上1kΩ下2kΩ左4.7kΩ右10kΩ中22kΩ计算分压V_adc 3.3 * R_down / (R_pullup R_down)上3.3 * 1 / 11 ≈ 0.30V下3.3 * 2 / 12 ≈ 0.55V左3.3 * 4.7 / 14.7 ≈ 1.05V右3.3 * 10 / 20 ≈ 1.65V中3.3 * 22 / 32 ≈ 2.27V这个间隔够大、阈值好设而且 ADC 读数的波动在 ±0.02V 以内不会误判。唯一要注意的是 ADC 输入阻抗要远大于你的分压网络总阻值Pico 的 ADC 输入阻抗大约 100kΩ 级10k22k32k 的总阻抗完全能接受。2.4 接线与引脚规划以数字直连方案为例我建议把五个方向映射到 Pico 的 GPIO 上。蜂鸣器、OLED、舵机这些常见外设和按键放在一起做个小板子时相邻引脚更好走线。常用引脚分配示例功能引脚GPIO编号方向上GP0输入PULL_DOWN下GP1输入PULL_DOWN左GP2输入PULL_DOWN右GP3输入PULL_DOWN中GP4输入PULL_DOWN公共端3.3V-GPIO5 之后留给 I2COLED、PWM舵机、UART调试等。这样做的坏处是默认上电时这些引脚都是输入、高阻外接按键不会造成短路。好处是 GP0~GP4 在 Pico 上全部暴露且支持中断后面如果要扩展成“按键中断唤醒”也方便。实际操作中我建议接线时把公共端接地/接电源的线用不同颜色区分并且每个方向脚串联一个 100Ω 小电阻再进 Pico 引脚防止接错时电流过大。这个电阻不带也行但带了会在你插拔杜邦线的时候保护引脚。3. 实操过程与核心环节实现3.1 硬件搭建与验证步骤先把硬件准备好。你需要树莓派 Pico任意版本RP2040 或 RP2350 均可五向按键模块裸按键或模块都行裸按键要自己接电阻面包板、杜邦线若干万用表可选但有的话排查问题效率翻倍操作步骤如下用万用表通断档测五向按键的引脚定义。公共端COM在按下任意方向时都和其他五个脚导通。把表笔固定在某个脚上按下每个方向找到公共端。这个步骤一定不要跳过——不同厂家的五向按键引脚顺序不一样我之前吃过亏按资料接好发现“上”和“下”反了。公共端接 3.3V用数字直连方案时五个方向脚分别接 Pico 的 GP0~GP4。写一个临时测试脚本挨个读五个 GPIO 的值并打印。按下每个方向确认对应的 GPIO 读到 1高电平松开回到 0。这一步排除硬件接线错误。然后再开始写正式的按键扫描代码。3.2 MicroPython 状态机按键扫描从底层到上层这里给出一套我在多个项目里用过的五向按键驱动框架MicroPython 直接能跑。核心设计是每个方向一个Button对象维护按下/释放状态。底层scan()方法在 1ms 周期里被调用完成采样和消抖。上层通过is_pressed()、was_clicked()、long_press_detected()等方法获取按键语义。写框架前先定义方向常量from machine import Pin import time UP const(0) DOWN const(1) LEFT const(2) RIGHT const(3) CENTER const(4)然后是按键类class FiveWayButton: def __init__(self, pin_ids, debounce_ms15, long_press_ms800): self.pins [Pin(id, Pin.IN, Pin.PULL_DOWN) for id in pin_ids] self.debounce_ms debounce_ms self.long_press_ms long_press_ms self._state [0] * 5 # 0释放, 1消抖中, 2按下, 3长按已触发 self._last_raw [0] * 5 self._change_time [0] * 5 self._press_time [0] * 5 self._click_pending [False] * 5 self._long_press_fired [False] * 5 def scan(self): now time.ticks_ms() for i, pin in enumerate(self.pins): raw pin.value() if raw self._last_raw[i]: continue # 状态有变化进入消抖 if now - self._change_time[i] self.debounce_ms: self._last_raw[i] raw self._change_time[i] now if raw: # 刚按下 if self._state[i] 0: self._state[i] 1 self._press_time[i] now self._long_press_fired[i] False else: # 刚释放 if self._state[i] in (1, 2, 3): if not self._long_press_fired[i]: self._click_pending[i] True self._state[i] 0 # 长按检测 if self._state[i] 1 and now - self._press_time[i] self.long_press_ms: self._state[i] 2 elif self._state[i] 2 and not self._long_press_fired[i]: if now - self._press_time[i] self.long_press_ms 50: self._long_press_fired[i] True # 在这里可以设置一个长按事件标志 def is_down(self, direction): return self._state[direction] ! 0 def was_clicked(self, direction): if self._click_pending[direction]: self._click_pending[direction] False return True return False def long_press_fired(self, direction): if self._long_press_fired[direction]: self._long_press_fired[direction] False # 消费掉事件 return True return False这段代码里有几个细节要说明。scan()方法的调用周期不需要非常精确你可以放在主循环里每轮调用一次。因为消抖判断用的是time.ticks_ms()时间差和你调用scan()的频率没有直接关系。只要两次调用之间的间隔明显小于消抖时间15ms效果就稳定。主循环里做别的事导致某次间隔 50ms也只是可能让消抖判定变慢不会误判。was_clicked()和long_press_fired()是“消费型”接口读一次就清除标志。这样上层逻辑里就不用担心重复处理同一个按键事件。菜单逻辑里常用的是短按一次方向键移动一格长按方向键连续滚动——长按触发后每隔一定时间自动再触发一次滚动。这个功能可以在长按事件里做也可以在上层用long_press_fired()配合时间戳实现。3.3 主循环集成示例五向按键控制舵机和 OLED下面给一个我把五向按键用在实际项目里的场景——控制舵机角度加 OLED 显示当前状态。这个场景能很好地展示状态机的价值按键扫描不阻塞主循环舵机控制流畅。from machine import Pin, I2C, PWM import time from ssd1306 import SSD1306_I2C # 初始化五向按键 btn FiveWayButton([0, 1, 2, 3, 4]) # 初始化舵机GPIO15 servo PWM(Pin(15), freq50) # 50Hz周期20msMicroPython新版支持 freq 参数 servo_duty_min 2000 # 约500us0度 servo_duty_max 8000 # 约2000us180度 # 初始化OLEDI2C0, GP8SDA, GP9SCL i2c I2C(0, sdaPin(8), sclPin(9), freq400000) oled SSD1306_I2C(128, 64, i2c) current_angle 90 def set_servo_angle(angle): angle max(0, min(180, angle)) duty int(servo_duty_min (servo_duty_max - servo_duty_min) * angle / 180) servo.duty_u16(duty) # Pico 用 duty_u16范围0~65535 oled.fill(0) oled.text(Angle: %d deg % angle, 0, 0) oled.text(Use UP/DN, 0, 20) oled.show() set_servo_angle(current_angle) while True: btn.scan() if btn.was_clicked(UP): current_angle min(180, current_angle 5) set_servo_angle(current_angle) if btn.was_clicked(DOWN): current_angle max(0, current_angle - 5) set_servo_angle(current_angle) if btn.was_clicked(CENTER): current_angle 90 set_servo_angle(current_angle) if btn.long_press_fired(UP): current_angle min(180, current_angle 15) set_servo_angle(current_angle) if btn.long_press_fired(DOWN): current_angle max(0, current_angle - 15) set_servo_angle(current_angle) time.sleep_ms(5)这段代码在主循环里sleep_ms(5)是为了降低 CPU 占用同时保证scan()的调用间隔约 5ms消抖时间 15ms 完全够用。舵机 PWM 是硬件外设不占用 CPU所以即使主循环在忙按键扫描舵机也不会卡顿。OLED 刷新是 I2C 操作本身要一点时间但每次只刷几个字符开销可接受。这个例子里长按的触发间隔我偷懒了——每次long_press_fired()被消费后要再按超过长按阈值50ms 才会再次触发。实际如果你想做“长按连续滚动每隔 200ms 滚一次”的效果建议在FiveWayButton里维护一个长按重复回调机制或者在事件消费时记录一个时间戳上层自己做重复。我在框架代码里预留了这个口子你按需扩展就行。3.4 ADC 版按键判断实现省 IO 方案数字直连方案说完补充一下 ADC 方案怎么在 MicroPython 里读。Pico 的 ADC 有三个可用通道ADC0~ADC2 对应 GP26~GP28内部还有通道4用于温度传感器。所以 ADC 版只能用这三个引脚之一。from machine import ADC, Pin import time adc ADC(Pin(26)) # GP26 # 五个方向的电压阈值边界用 16bit 读数的原始值或转成电压均可 # 这里我用 adc.read_u16() 返回 0~65535 3.3V 对应 65535 # 前面计算得到的典型电压0.30, 0.55, 1.05, 1.65, 2.27 # 对应原始值0.30/3.3*65535≈5957, 0.55/3.3*65535≈10919, # 1.05/3.3*65535≈20877, 1.65/3.3*65535≈32768, 2.27/3.3*65535≈45093 # 映射为方向按下时 ADC 值在这个区间内 DIRECTIONS [ (8000, UP), # 上约0.40V以下都是 (16000, DOWN), # 下约0.80V以下 (27000, LEFT), # 左约1.35V以下 (39000, RIGHT), # 右约1.95V以下 (55000, CENTER), # 中约2.75V以下 ] def read_direction(): val adc.read_u16() for threshold, direction in DIRECTIONS: if val threshold: return direction return -1 # 未按下或值异常 while True: direction read_direction() if direction ! -1: print(pressed:, direction) else: print(none) time.sleep_ms(20)注意阈值一定要落在相邻电压的中点附近而不是直接用计算电压值。比如“上”计算值是 0.30V“下”计算值是 0.55V那阈值取两者的分界大约 0.42V 左右。上面代码里的阈值我取得比较保守是因为还有噪声和电阻精度误差留足裕量。如果按键按下和松开之间 ADC 值区分度不明显先查电阻焊接/接触再用万用表实测每个方向的 ADC 点电压来重新标定。4. 常见问题与排查技巧实录4.1 按下没反应、某个方向不灵先别急着改代码。排查顺序按照“硬件-接线-软件”来万用表通断档测按键本身按下方向公共端与该方向引脚导通松开断开。如果某个方向始终不导通按键坏了。检查公共端连线公共端松了会导致所有方向都失灵只松某一根信号线只影响对应方向。检查 Pico 引脚定义GP0~GP4 可能被你复用了——如果你在别处初始化了 I2C 或 SPI 用到这些引脚Pin 对象会被覆盖按键自然读不到。软件层面打印原始pin.value()看按下时是否变化。如果始终为 0大概率上拉的 PULL_DOWN 没生效或公共端没接对。4.2 按键抖动能消除但响应变慢响应变慢通常和两个参数有关消抖时间debounce_ms和主循环的sleep_ms。如果你把消抖时间设到 50ms那按下到识别到事件最长可能有 50ms 的延迟手感发肉。我一般把消抖控制在 10~20ms 之间再配合状态机里的“按下立即预测消抖后确认”策略手感最好。如果你发现按键按下后要等很久才响应先看主循环里是不是有阻塞代码——比如time.sleep(0.5)这种scan()被阻断再久消抖逻辑也完蛋。4.3 ADC 版按键读值漂移、误判ADC 版常踩的坑包括供电不稳导致 ADC 参考电压波动、分压电阻精度差、长线引入噪声。解决手段在 ADC 引脚和地之间并联一个 0.1uF 陶瓷电容能滤掉大部分高频噪声。电阻用 1% 精度的金属膜电阻不要用碳膜——碳膜电阻温漂大、噪声高。把 ADC 采样做多次平均比如连续读 4 次取平均再进阈值判断。MicroPython 里adc.read_u16()本身速度不慢多读几次没问题。上电后先让 ADC 稳定 100ms 再做首次判断避免刚上电瞬间电压建立不全导致误判。4.4 按键串键/连键一个方向触发两个方向这个问题在数字直连方案里很少见除非某个引脚虚焊导致内部上拉/下拉失效。但在 ADC 方案里很容易发生——按下“左”和“右”的 ADC 值挨得太近噪声一抖就跨过阈值了。解决方式是拉大电阻间隔或者把阈值划分得更靠中间。我上面给的电阻序列间隔就足够大正常不会串键。如果在数字直连方案里出现“按下上键上和下都触发了”大概率是面包板接触不良或引脚之间短路。拿万用表量一下相邻两个 GPIO 之间是否直通。5. 工程级代码优化与扩展5.1 中断驱动扫描让按键响应更实时上面的状态机在主循环里轮询已经能应付大多数场景。但如果你想让 Pico 在按键按下时立刻响应甚至在sleep低功耗模式下也能唤醒可以改用 GPIO 中断。MicroPython 里Pin.irq()支持上升沿/下降沿触发但要注意中断回调函数里不能执行耗时操作比如 OLED 刷新否则会阻塞其他中断和主循环。我的做法是在中断回调里只置一个标志位真正的按键扫描逻辑仍然在主循环里执行。比如key_flag False def on_key(pin): global key_flag key_flag True for pin in btn.pins: pin.irq(handleron_key, triggerPin.IRQ_FALLING | Pin.IRQ_RISING)主循环里if key_flag: btn.scan() ...。这样按键按下立刻打断当前流程把扫描任务排入队列不会漏事件也不会有中断回调里做重活的毛病。5.2 按键事件队列化适配多任务场景如果你的主循环里跑着多个任务舵机、OLED、串口、传感器建议给按键事件加一个队列。MicroPython 没有标准线程安全队列但你可以用collections.deque配合标志位实现一个简单的事件队列from collections import deque events deque((), 16) def post_event(direction, event_type): events.append((direction, event_type))主循环从一个事件队列里消费事件这样即使某个任务偶尔耗时较长按键事件也不会丢失只是处理延迟了一点。注意deque满了会自动覆盖最老的事件所以容量要按最坏情况设计——一般 16 个事件足够。5.3 适配 ESP32、RP2350 等平台这套代码是纯 MicroPython machine模块换到 ESP32-S3 上改引脚编号就能跑。ADC 版的话ESP32 的 ADC 是 12 位分辨率0~4095不是 Pico 的 16 位读数的数值范围不同但电压计算公式一样。OLED 和舵机部分用machine库也是通用的。如果你换到 RP2350 的 Pico 2 上Pin、PWM、ADC的 API 完全兼容直接把代码复制过去就能用。我在 ESP32-S3 上跑过这个框架唯一要改的是 ADC 引脚编号和 PWM 初始化方式ESP32 用LEDC而不是PWMMicroPython 新版已经统一为machine.PWM。只要你用的是较新的 MicroPython 固件跨平台迁移成本很低。6. 避坑总结与个人经验最后说几个我在实际项目中踩过的坑真的是血泪教训。第一个是五向按键的引脚顺序。不同厂家的五向按键引脚排列差异巨大有横排的、有竖排的、有圆形的。千万不要照着某个教程的引脚图闷头焊务必先用万用表通断档把每个引脚的功能实测一遍。我做过一次批量打板就是把引脚顺序画反了五个按键全反着只能用飞线补救非常狼狈。第二个是消抖参数不能拍脑袋。不同物理结构的五向按键抖动时间差异很大——导电胶的抖动短金属弹片的抖动长低成本杂牌按键甚至可能抖 50ms。如果你发现按键偶发失灵或连按先把你手里的按键接到示波器上看一眼实际抖动宽度再回头调debounce_ms。没示波器的话可以用一段测试代码把按下时间戳变化打印出来估算抖动范围。第三个是MicroPython 的垃圾回收GC。MicroPython 在内存碎片严重时会出现偶发的卡顿如果你按键扫描期间正好撞上 GC 暂停事件处理会延迟几十毫秒。解决办法是尽量少在按键回调里创建临时对象比如别用print拼接字符串或者在初始化阶段预分配好对象。如果你发现按键偶尔“卡一下”十有八九是内存碎片问题不是按键逻辑的问题。第四个是公共端的接法不要想当然。如果你的五向按键模块上已经带了电阻那就别按裸按键思路接。模块上通常已经做了上拉或下拉你再看模块原理图决定 Pico 这边要不要配置 PULL_UP/PULL_DOWN。我见过不少人买了现成模块还在 Pico 里配了个相反方向的上拉/下拉结果按键按下读到反电平一脸懵。这个五向按键驱动框架我已经在多块板子上跑过项目了从最简单的 OLED 菜单到配合舵机、步进电机的自动控制面板都能稳定工作。你如果只是点个灯、读个值可以只抄简化版代码如果要做正式项目建议直接上状态机加事件队列这套结构后面扩展需求不用重写。用熟了这一套以后碰到任何矩阵键盘、独立按键底层逻辑都是相通的。
返回列表