
拿到一块树莓派 Pico大多数人的第一个念头都是先点亮板载那颗绿油油的 LED。而这背后真正要面对的就是“Pico GPIO 怎么控制”这个问题。在 MicroPython 环境下所有引脚操作的入口几乎都指向同一个核心类machine.Pin。GPIO 的输入、输出、上拉下拉、中断、PWM 复用全都可以靠它搞定。这篇文章不打算贴官方文档复读而是把我实际摸 Pico 时踩过的坑、验证过的方法完整整理一遍适合刚入手 Pico 想快速上手的初学者也适合用 STM32/ESP32 转过来了、想看 MicroPython 引脚控制差异的老手。1. 先弄懂 Pico 的 GPIO 与 machine.Pin 的关系1.1 GPIO 和 Pin一个物理概念一个思辨对象很多人刚看 MicroPython 代码时有个疑问网上教程一会儿讲 GPIO一会儿讲 Pin这俩到底是不是一回事严格来说GPIO 是“通用输入输出引脚”的缩写描述的是硬件能力而 machine.Pin 是 MicroPython 中用来操作这颗引脚的软件对象。你写下的Pin(25, Pin.OUT)本质是告诉 MicroPython把编号 25 的 GPIO 复用成普通输出模式。在树莓派 Pico 上RP2040 芯片一共引出 30 个 GPIO编号从 GPIO0 到 GPIO29对应板子上那些排针。要注意的是GPIO 编号和物理引脚编号完全是两套系统。Pico 板子上的物理引脚是从 1 到 40GPIO0 对应的物理引脚可能是 1 号也可能是 2 号具体得看板子上印的丝印和官方引脚图。我在群里见过好几个新手对着物理引脚号写代码怎么都不亮其实就是把编号搞混了。MicroPython 里的Pin(id)绝大多数情况用 GPIO 编号唯一的例外是像Pin(LED)这种特殊名称。1.2 从 STM32 的 8 种模式说起MicroPython 抽象掉了什么如果你之前在 STM32 上写过寄存器或者 HAL 库一定对“GPIO 的 8 种工作模式”不陌生输入浮空、输入上拉、输入下拉、模拟输入、开漏输出、推挽输出、推挽复用、开漏复用。到了 MicroPython 的 machine.Pin 这边这些模式被高度抽象了你可能只会看到Pin.IN、Pin.OUT、Pin.OPEN_DRAIN这几个常量。这并不意味着 8 种模式消失了而是 MicroPython 把配置逻辑封装进了构造函数。输入浮空对应Pin.IN不传 pull 参数输入上拉和下拉对应Pin.IN配合Pin.PULL_UP或Pin.PULL_DOWN模拟输入在 Pico 上不靠 Pin 模式表示而是直接创建ADC对象推挽输出对应Pin.OUT开漏输出对应Pin.OPEN_DRAIN。至于“复用模式”在 MicroPython 里你根本不用手动指定创建 PWM、I2C、SPI、UART 对象时底层会自动完成引脚复用配置。刚开始别被“8 种模式”吓住在 MicroPython 里记好四种常用场景就够普通输入选Pin.IN普通输出选Pin.OUT按键检测带上拉或下拉模拟量采集直接用 ADC。2. machine.Pin 的基本用法构造参数与模式选择2.1 Pin 构造函数的三个核心参数Machine.Pin 的构造函数长这样from machine import Pin pin Pin(id, mode, pull, value)常用参数就三个id是 GPIO 编号mode是工作模式pull是上下拉设置。第四个参数value只有输出模式时才有意义用来指定初始化时输出高电平还是低电平。比如我想让 GPIO14 一开始就是高电平pin Pin(14, Pin.OUT, value1)这个写法其实等价于先Pin(14, Pin.OUT)再pin.value(1)但一行搞定更简洁。如果省略pull和value输入模式默认浮空输出模式默认低电平。id的写法比较灵活。原版 Pico 的板载 LED 接在 GPIO25可以直接写成Pin(25, Pin.OUT)。Pico W 的板载 LED 有点特殊它不直接连在 RP2040 上而是连在无线芯片上MicroPython 做了特殊封装可以写Pin(LED, Pin.OUT)来操作。这里有个容易忽略的坑Pico W 的 GPIO0 是被无线模块占用的虽然有些代码能用Pin(0)创建对象但大概率不是你想要的行为建议避开。2.2 mode 参数什么时候选 IN什么时候选 OUT什么时候选 OPEN_DRAIN选Pin.OUT最直观驱动 LED、蜂鸣器、继电器模块都靠它。需要注意一点Pico 的 GPIO 输出高电平是 3.3V驱动能力非常有限直接带大功率器件很容易把引脚搞坏。我给继电器模块接信号线时吃过亏后来一律用三极管或者光耦做隔离。Pin.IN是读取外部电平用的接按键、传感器数字输出、编码器信号都靠它。输入模式下引脚是高阻态外部不接东西时电压会飘忽不定所以一定得配合上下拉或者外部信号源。Pin.OPEN_DRAIN开漏模式平时用得不多但在 I2C 这类总线场景必须理解。开漏的意思是引脚只能主动拉低不能主动拉高高电平靠外部上拉电阻提供。这样多个设备可以并联在同一根线上谁需要发送低电平谁就去拉低不会出现两个设备同时输出高低电平打架的情况。如果你自己搭 I2C 总线可以用Pin(0, Pin.OPEN_DRAIN, Pin.PULL_UP)模拟开漏输出。2.3 pull 参数上拉和下拉的选择逻辑pull参数解决的是“输入引脚悬空”的问题。悬空引脚的电平是随机的可能受到旁边走线干扰按键按下时也读不到稳定信号。解决办法有两个方向外部加电阻或者直接用芯片内部的上拉/下拉电阻。MicroPython 里设置非常简单# 按键一端接GND一端接GPIO14 key Pin(14, Pin.IN, Pin.PULL_UP)这个配置的思路是没按下时内部上拉把引脚拉到 3.3V读取到 1按下时引脚被按键短路到 GND读取到 0。反过来如果把按键一端接 3.3V另一端接 GPIO就应该启用下拉Pin.PULL_DOWN。我见过不少新手把按键接法和上拉下拉选反导致逻辑永远不对。记住一个口诀按键接 GND 用上拉按键接电源用下拉。还有一点如果引脚本身已经接了外部上拉电阻内部上拉就别同时开了两者并联虽然不太会坏但会让逻辑电平切换变慢极端情况下可能引发误判。3. machine.Pin 核心控制方法速通3.1 输出控制value、high、low、toggle、on、offmachine.Pin 提供了好几种输出控制方法功能上有重叠但使用场景略有差异。最基本的value(x)方法传1或0设置高/低电平不传参数时返回当前电平。high()和low()分别是value(1)和value(0)的别名语义更明确。on()和off()也是别名但字面意思容易让人困惑——到底是“打开引脚输出”还是“引脚输出高电平”MicroPython 官方定义on()等价于value(1)off()等价于value(0)。实际项目里我很少纠结用哪个统一用value()最稳妥。不过toggle()这个方法值得单拎出来说它的作用是翻转当前电平写跑马灯或者按键切换状态时特别省事from machine import Pin import time led Pin(25, Pin.OUT) while True: led.toggle() # LED电平翻转 time.sleep(0.5)这段代码比先读值再取反写回去干净多了而且toggle()是在芯片底层直接翻转不存在读-改-写的竞态问题。3.2 输入读取value() 和浮空电平的隐患输入模式读取电平同样用value()返回整数 0 或 1。功能简单但实际使用中要特别留意“浮空输入”这个状态。所谓浮空就是引脚外部既没接高电平也没接低电平此时引脚电压不确定读取结果可能是 0也可能是 1甚至反复跳变。我调试一个接在 GPIO16 上的按键时因为没配置上下拉LED 偶尔自己闪烁查了半天才发现程序逻辑没问题是引脚悬空导致的。所以用value()读输入之前先确认两件事外部信号是否真正驱动了引脚以及内部pull参数是否配置正确。实在不行可以用万用表量一下引脚电压。如果读到 1.5V 这种“半吊子”电压基本可以断定引脚悬空或者信号源驱动能力不足。3.3 中断irq() 实现按键触发很多场景不想让主循环一直轮询按键而是要“按键按下时立刻做某件事”。这时用irq()注册中断回调最合适from machine import Pin import time led Pin(25, Pin.OUT) key Pin(14, Pin.IN, Pin.PULL_UP) flag False def on_key(pin): global flag flag not flag led.value(flag) key.irq(handleron_key, triggerPin.IRQ_FALLING)trigger参数决定什么边沿触发回调Pin.IRQ_RISING是上升沿从低到高Pin.IRQ_FALLING是下降沿从高到低。按键接 GND 配上拉时按下瞬间引脚从高跳低应该用Pin.IRQ_FALLING如果按键接 3.3V 配下拉松开时才变化用Pin.IRQ_RISING。中断回调里有三个大坑要记住第一回调函数不能太耗时不要在里面time.sleep()否则会阻塞整个系统严重时直接看门狗复位第二回调函数是在中断上下文执行的访问全局变量前要加global声明否则只会创建一个局部变量程序表现会非常诡异第三机械按键有抖动按一次可能触发多次中断一般要加软件消抖。最简单的方法是记录时间戳过滤掉 20 毫秒内的重复触发。3.4 复用功能PWM、ADC 与 pin 的关系严格说PWM 和 ADC 不属于 machine.Pin 的直接方法但它们的创建过程都和 Pin 密不可分。Pico 上要做 PWM 输出先要指定一个 GPIO 引脚然后创建 PWM 对象from machine import Pin, PWM pwm PWM(Pin(15)) pwm.freq(1000) # 频率1kHz pwm.duty_u16(32768) # 占空比50%范围0~65535同样的ADC 也从引脚出发from machine import ADC adc ADC(26) # GPIO26对应ADC0 value adc.read_u16() # 返回0~65535所以你会发现machine.Pin 不只是简单的数字输入输出对象它还是整个引脚复用系统的“入口”。你创建一个引脚的 Pin 对象时它处于最普通的模式一旦你把它传给 PWM 或 ADC底层就会自动重新配置引脚功能。4. 实战案例从点亮 LED 到驱动舵机4.1 入门案例按键控制板载 LED带消抖先来一个最常规的入门场景按键控制 LED 翻转。这个案例虽然简单但把输入、输出、上拉、消抖全串起来了。假设按键一端接 GND另一端接 GPIO14LED 用板载的 GPIO25。from machine import Pin import time led Pin(25, Pin.OUT) key Pin(14, Pin.IN, Pin.PULL_UP) last_press_time 0 def on_key(pin): global last_press_time now time.ticks_ms() if now - last_press_time 20: return last_press_time now led.toggle() key.irq(handleron_key, triggerPin.IRQ_FALLING) while True: time.sleep(1)这段代码里中断回调只做两件事消抖过滤、翻转 LED。主循环空转等待即可。实测效果是按键每按一次LED 状态翻转一次不会出现一次按压多次翻转。如果不加消抖有时按一下 LED 会连续翻转两次就是因为按键触点回弹产生多个下降沿。4.2 进阶案例Pico 驱动 SG90 舵机舵机在机器人小项目里很常见Pico 控制舵机的原理不复杂周期 20ms高电平脉宽在 0.5ms~2.5ms 之间对应 0°~180°。这个信号本质上就是 PWM所以用 machine.Pin machine.PWM 就能搞定。先算一下 duty 值。PWM 的duty_u16()参数范围是 0~65535对应整个周期的占空比。周期是 20ms那么脉宽对应的 duty 可以这样算def angle_to_duty(angle): pulse_us 500 (angle / 180) * 2000 # 0.5ms~2.5ms duty int(pulse_us / 20000 * 65535) return duty完整控制代码from machine import Pin, PWM servo PWM(Pin(15)) servo.freq(50) # 50Hz对应20ms周期 def set_angle(angle): duty int((500 angle / 180 * 2000) / 20000 * 65535) servo.duty_u16(duty) set_angle(0) time.sleep(1) set_angle(90) time.sleep(1) set_angle(180)这里有个踩过坑的提醒Pico 的 GPIO 输出只有 3.3V电流驱动能力很弱直接给舵机供电完全不行舵机转动时电流会瞬间把电压拉垮导致 Pico 复位。正确做法是舵机电源单独接 5V 外部电源Pico 的 GND 和外部电源的 GND 一定要连在一起这叫“共地”。如果不共地PWM 信号参考地和舵机电源地是两套体系舵机大概率不会正常工作。另外不同型号舵机的脉宽范围和角度映射略有差异。有些 180° 舵机使用 0.5ms~2.5ms有些使用 1ms~2ms具体看舵机参数手册。调节到极限角度时如果听到舵机“滋滋”响且抖动说明给的脉宽已经超出机械范围赶紧减小角度范围否则会烧舵机齿轮或驱动芯片。4.3 综合案例用 OLED 显示 Pico 网络状态很多 Pico W 用户面临的一个小问题是板子连上路由器后怎么方便地查看 IP 地址在电脑上打开 Thonny 的 REPL 当然能敲wlan.ifconfig()但板子脱离电脑独立运行时最直观的方式就是把 IP 打到一块小 OLED 屏幕上。OLED 屏幕SSD1306走的是 I2C 接口正好能展示 machine.Pin 在 I2C 场景下的用法。接线方式SDA 接 GPIO16SCL 接 GPIO17VCC 接 3.3VGND 接 GND。from machine import Pin, I2C import network import ssd1306 import time # 初始化I2C和OLED i2c I2C(0, sdaPin(16), sclPin(17), freq400000) oled ssd1306.SSD1306_I2C(128, 64, i2c) # 连接WiFi wlan network.WLAN(network.STA_IF) wlan.active(True) wlan.connect(your_ssid, your_password) while not wlan.isconnected(): time.sleep(0.5) ip wlan.ifconfig()[0] # 显示IP地址 oled.fill(0) oled.text(IP Address:, 0, 0) oled.text(ip, 0, 20) oled.show()代码很简单但有一个关键点machine.I2C的第一个参数0表示使用 Pico 的 I2C0 外设随便哪个 GPIO 都可以通过指定sda和scl来关联。这是 RP2040 的灵活性所在不像某些 MCU 的 I2C 引脚被死死固定在特定编号上。如果连接不上屏幕先检查 I2C 地址是不是 0x3C大部分 SSD1306 是这个地址可以用i2c.scan()扫描确认。5. 常见问题与排查技巧实录5.1 引脚编号混乱GPIO 号、物理号、板载 LED新手最常犯的错就是把 GPIO 编号和物理引脚编号搞混。Pico 板上那排针有清晰的丝印标注比如GP0、GP1、GP15这些都是 GPIO 编号直接对应Pin()的第一个参数。而板子边缘从 1 到 40 的数字是物理引脚编号不能直接塞进Pin()。另外原版 Pico 和 Pico W 的板载 LED 引脚不一样。原版 Pico 的 LED 在 GPIO25Pico W 的 LED 在无线芯片上必须用Pin(LED)。如果你在 Pico W 上执行Pin(25, Pin.OUT).value(1)灯是绝对不会亮的。我测试过用万用表量 GPIO25 电压确实会被拉高但那个引脚没有接任何 LED。所以代码里写着Pin(25)却不亮灯先确认板子型号。5.2 PWM 引脚的 slice 冲突问题RP2040 的 PWM 分成 8 个 slice每个 slice 有两个通道 A 和 B。同一个 slice 的两个通道默认共享同一个频率只有占空比可以独立设置。如果你同时驱动两个舵机恰好用了同一个 slice 的两个通道比如 GPIO63A和 GPIO73B那么两个舵机必须工作在相同频率下。对舵机来说这没问题因为舵机标准频率就是 50Hz但如果你要让一个引脚输出 50Hz 舵机信号、另一个引脚输出 1kHz 蜂鸣器信号就绕不开了需要换成不同 slice 的引脚。MicroPython 的 PWM 对象会自动配置复用日常用不需要背映射表但遇到频率调不上的诡异现象可以查一下官方引脚复用表看看是不是被某个 slice 共享坑了。5.3 按键误触发和电平不稳按键中断在电机、继电器这类大功率设备旁边特别容易误触发。主要原因是干扰和抖动。排查时先断开所有外部设备只留按键和 Pico看问题是否复现。如果断开后正常说明是干扰问题可以在按键到 GND 之间并一个 0.1uF 电容或者把中断回调里的消抖时间从 20ms 提高到 50ms。如果断开后依然误触发就得怀疑代码逻辑或者引脚是否被复用。5.4 中断回调里的“隐形坑”已经提过回调函数里不能time.sleep()这里补充一个更隐蔽的问题回调里不要执行print()这种 IO 操作。MicroPython 的 print 会走 USB 串口输出在中断上下文触发 USB 通信可能导致不可预期的问题表现是程序跑着跑着就卡死或崩溃。我见过一个项目中断回调里加了个 print 调试板子每隔几分钟就死一次把 print 去掉后连续跑了三天都没问题。正确做法是中断回调里只做最轻量的标记和计数具体的耗时操作放到主循环里。5.5 舵机供电不足导致 Pico 反复重启这可能是最“经典”的供电问题。SG90 舵机堵转时电流可能达到 500mA 甚至 1A靠 USB 口的 5V 供电瞬间压降就会触发 Pico 的欠压复位。表现是舵机一转动LED 闪烁一下程序重新开始运行像在不断重启。解决思路有三步第一舵机电源独立别从 Pico 的 VIN 取电第二确保外部电源的 GND 和 Pico 的 GND 相连第三如果电源有波动在舵机电源两端并联一个 470uF 左右的电解电容能吸收尖峰电流。这样处理之后舵机基本不会再拖垮系统。最后分享一个我个人的操作习惯拿到新板子或新的 GPIO 扩展板时先不要急着接任何外设直接写一段“全引脚扫描”程序把所有 GPIO 轮流设为输入并配上拉然后短接每个引脚到 GND确认都能稳定读到 0。这个测试能快速发现焊接短路、引脚虚焊、GPIO 损坏等问题比接上传感器后一脸懵再排错高效得多。Pico 的 GPIO 本身很皮实但工程中出问题的往往是接线、供电和编号这些看似不起眼的小事把这些基础盘顺了后面的开发会顺畅很多。