ARTICLE · INTELLIGENCE

战地情报 · 详情页

来自尧图项目组的一线实战观察与深度解析

树莓派 Pico GPIO 完全指南:引脚映射、MicroPython 编程与实战避坑

树莓派 Pico GPIO 完全指南:引脚映射、MicroPython 编程与实战避坑 1. Pico 引脚布局与 GPIO 映射先搞清楚物理引脚和编号的关系拿到树莓派 Pico第一眼看到的是那块长条形的开发板两侧各有 20 个金属引脚总共 40 个物理引脚。这块板子本身的尺寸和一块口香糖差不多引脚却藏着不少门道。新手最容易踩的坑就是把物理引脚编号和 GPIO 编号搞混。Pico 板上的引脚分三类信号电源引脚3V3、5V、GND、控制引脚GP0~GP28、以及几个特殊功能引脚如 ADC_VREF、AGND、RUN。很多人以为板子上丝印标注的数字就是 GPIO 编号其实不然。Pico 的丝印标注的是物理引脚序号从 1 到 40而 MicroPython 里操作时用的是 GPIO 编号。我整理了一张对应关系表方便你速查物理引脚GPIO 编号说明1GP0通用输入输出可复用 PWM/ADC2GP1通用输入输出可复用 PWM/ADC4GP2通用输入输出6GP3通用输入输出9GP4通用输入输出11GP5通用输入输出SPI 片选14GP6通用输入输出16GP7通用输入输出21GP16通用输入输出24GP18通用输入输出26GP20通用输入输出31GP26ADC0 专用输入32GP27ADC1 专用输入34GP28ADC2 专用输入板子右上角那颗小小的板载 LED接在 GP25 上。调试时想快速验证代码能不能跑直接操作这颗 LED 就是最快的方案不需要接任何外部元器件。还有一点必须说明Pico 的 GPIO 电平是 3.3V不是 5V。如果你以前玩过 Arduino习惯了 5V 逻辑电平换到 Pico 上直接驱动 5V 器件轻则读不到高电平重则烧毁引脚。往 GPIO 上接外部模块时先确认模块是否兼容 3.3V 逻辑电平。2. machine.Pin 类核心方法构造、模式、读写究竟怎么用在 MicroPython 中操作 GPIO 的统一入口就是machine.Pin类。理解了这个类的设计思路后面写再复杂的控制逻辑都能举一反三。2.1 构造一个 Pin 对象必须搞懂的 id、mode、pull创建一个 Pin 对象的基本语法如下from machine import Pin # 最基本的形式只指定引脚编号默认输出模式 led Pin(25, Pin.OUT) # 完整形式指定编号、模式、默认电平、上下拉电阻 btn Pin(16, Pin.IN, Pin.PULL_UP)这里有两个关键参数mode和pull。mode只有Pin.OUT和Pin.IN两种取值分别对应输出模式和输入模式。Pico 的每个 GPIO 引脚内部结构决定了它同一时刻只能作为一种模式工作你想让它既读又写比如读取按键的同时驱动 LED那需要两个不同的 Pin 对象或者用复用功能。pull参数只在输入模式下有意义它控制引脚内部的上拉或下拉电阻。Pico 内部集成了可编程的上拉/下拉电阻取值为Pin.PULL_UP、Pin.PULL_DOWN或None默认不启用内部电阻。这个设计很实用比如接一个按键到 GND 和某个 GPIO 之间只需要启用内部上拉按键未按下时读到高电平按下时读到低电平完全不需要外部电阻。2.2 输出模式value、toggle、高低电平的底层逻辑创建了输出模式的 Pin 对象后最常用的方法就是value()。这个方法有两种角色无参数调用时是读取引脚当前电平有参数调用时是设置引脚输出电平。from machine import Pin import time led Pin(25, Pin.OUT) # 设置 LED 亮 led.value(1) # 等效于 led.on() time.sleep(1) # 设置 LED 灭 led.value(0) # 等效于 led.off() time.sleep(1) # 翻转电平 led.toggle()value(1)会让引脚输出高电平value(0)输出低电平。另外还有on()和off()两个简写方法逻辑语义更清晰on()对应高电平off()对应低电平。toggle()方法没有参数每次调用把当前电平取反。这个在闪烁灯、状态机切换场景里非常方便省去了先读再写的操作。底层来看value(1)的本质是把引脚的输出数据寄存器对应位写 1电平转换时间在微秒级。Pico 主频 133MHz一次电平翻转大概需要 7 个时钟周期左右也就是约 50 纳秒。这个速度对大多数传感器读取、LED 控制、继电器开关绰绰有余但如果你要做高速 PWM 输出或者精确时序通信建议直接用硬件外设PWM、SPI、I2C而不是靠手动翻转 GPIO。2.3 输入模式读取外部信号的关键细节把引脚配置为输入模式后核心方法是value()它返回 0 或 1。但在实际使用中有几个绕不开的细节悬空引脚的问题。如果你把引脚配置为输入模式既不用上拉也不用下拉引脚就处于高阻态。这时读取到的电平完全取决于外部环境——可能受旁边的走线干扰也可能受手指触摸影响结果不稳定。所以读取外部信号前要么启用内部上拉/下拉要么外部接电阻确保引脚电平是确定的。按键消抖。机械按键在按下和释放的瞬间触点会产生 10~20ms 的抖动电平在这段窗口内会快速跳变多次。如果不做消抖处理一次按键可能被误判成多次。常见的软件消抖思路是延时后再确认一次from machine import Pin import time btn Pin(16, Pin.IN, Pin.PULL_UP) led Pin(25, Pin.OUT) while True: if btn.value() 0: # 检测到按下 time.sleep_ms(10) # 延时避开抖动窗口 if btn.value() 0: # 再次确认确实按下 led.toggle() while btn.value() 0: # 等待松开 time.sleep_ms(10)这种检测-延时-再检测的消抖方式原理简单且足够应对大部分场景。更精细的做法是用定时器定期采样引脚状态结合状态机判断按键事件但对普通项目来说第一种方案已经够用了。3. GPIO 的 8 种工作模式从原理到选择标题提到 GPIO 的 8 种工作模式这是从 STM32 那套体系里流传出来的概念。在 STM32 中GPIO 可配置为输入浮空、输入上拉、输入下拉、模拟输入、开漏输出、推挽输出、复用开漏输出、复用推挽输出这 8 种模式。Pico 的 machine.Pin 并没有完全照搬这 8 种模式但理解这些模式可以帮助你更好地理解外设接口设计。Pico 的 MicroPython 固件中Pin 类支持的模式其实就两种Pin.IN和Pin.OUT但结合上下拉电阻和复用功能可以模拟出类似的效果。对应关系大致如下STM32 模式Pico 等价实现典型应用场景输入浮空Pin.IN不启用上拉/下拉外部已接上拉/下拉电阻的信号读取输入上拉Pin.IN, Pin.PULL_UP按键到 GND 读取输入下拉Pin.IN, Pin.PULL_DOWN按键到 3.3V 读取模拟输入machine.ADC类而不是 Pin 类模拟电压采样推挽输出Pin.OUTLED 驱动、电平输出开漏输出Pin.OUT不直接支持需外部上拉I2C 总线、电平转换复用推挽输出由外设驱动如 PWM、SPI波形输出、通信协议复用开漏输出I2C 外设自动配置I2C 通信模式选择的判断逻辑其实很简单这个引脚是负责读外部信号还是输出控制信号读的话外部信号是主动拉高拉低还是需要内部电阻帮忙确定电平输出的话是驱动常规数字器件还是需要开漏结构开漏输出这点值得展开说。开漏输出的特点是引脚只能主动拉低到 GND无法主动输出高电平。高电平必须靠外部上拉电阻提供。这种设计特别适合两个场景I2C 总线的线与逻辑以及不同电压域之间的电平转换。Pico 的 machine.Pin 没有直接暴露开漏模式但 MicroPython 固件内部用 I2C 外设时会自动把 SDA/SCL 配置为开漏结构不需要你手动干预。如果你非要用普通 GPIO 模拟开漏效果也不是不行但效率很低不推荐。4. 从 LED 到按钮一个完整的气象站示例串联全部知识点前面讲了一堆零散的知识点现在用一个完整的实例把它们全部串起来感受一下。假设你要做一个室内气象站功能是用 DHT11 温湿度传感器读取温度和湿度用两个按键切换显示模式通过 I2C 接口的 OLED 屏幕显示数据LED 在数据更新时闪烁一次作为反馈这个项目会用到 GPIO 的输入模式按键、输出模式LED、以及两个复用功能I2C 的 SDA/SCL 引脚。接线方案如下外设引脚GPIODHT11 数据引脚物理引脚 4GP2按键1物理引脚 16GP16按键2物理引脚 18GP17OLED SDA物理引脚 21GP16? 不对是 GP16 已被按键占用看完上面这段你可能已经警觉了GP16 被按键占用了OLED 该接哪里Pico 的 I2C 外设支持多个引脚组合常用的有 GP0/GP1、GP4/GP5、GP18/GP19。选择 GP4物理引脚 6和 GP5物理引脚 7作为 SDA 和 SCL按键改接 GP16 和 GP17。完整代码如下from machine import Pin, I2C, ADC import dht import time # 输出模式板载 LED led Pin(25, Pin.OUT) # 输入模式两个按键启用内部上拉 btn1 Pin(16, Pin.IN, Pin.PULL_UP) btn2 Pin(17, Pin.IN, Pin.PULL_UP) # DHT11 数据引脚 sensor dht.DHT11(Pin(2)) # I2C 初始化GP4SDA, GP5SCL i2c I2C(0, sclPin(5), sdaPin(4), freq400000) # OLED 驱动 from ssd1306 import SSD1306_I2C oled SSD1306_I2C(128, 64, i2c) display_mode 0 # 0:温度 1:湿度 while True: try: sensor.measure() temp sensor.temperature() hum sensor.humidity() # 数据更新LED 闪烁一次 led.value(1) time.sleep_ms(50) led.value(0) # 按键切换显示模式 if btn1.value() 0: time.sleep_ms(20) if btn1.value() 0: display_mode 0 if btn2.value() 0: time.sleep_ms(20) if btn2.value() 0: display_mode 1 # OLED 显示 oled.fill(0) if display_mode 0: oled.text(Temp: {} C.format(temp), 0, 0) else: oled.text(Humidity: {}%.format(hum), 0, 0) oled.show() except Exception as e: print(Error:, e) time.sleep_ms(1000)这个项目的价值在于把输入输出、复用功能、消抖、外设驱动全部整合在一起。后面做更复杂的项目无非是在这个框架上增加更多外设但 GPIO 操作的底层逻辑完全一致。5. 实战避坑程序反复报错的常见常见问题排查第一次上手 Pico GPIO 的人十个里有八个会在下面这些问题上卡壳。我把踩过的坑按频率排了个序你对照着排查能省不少时间。坑一引脚的编号写错。GPIO 编号不是物理引脚序号。GP0 对应物理引脚 1GP1 对应物理引脚 2GP2 对应物理引脚 4。物理引脚 3、5、7 这些奇数编号都是 GND 或电源引脚不是 GPIO。新手最容易犯的错误是把物理引脚 3 当 GP3 用实际上 GP3 是物理引脚 5。坑二忘了 Pico 是 3.3V 逻辑电平。驱动 5V 继电器模块、5V LED 灯带这类设备直接用 GPIO 引脚接过去就是自寻短路。解决办法是加电平转换模块或者用光耦隔离。坑三输入悬空导致误触发。按键、传感器这类输入引脚必须在软件上启用内部上拉或下拉否则引脚电平漂浮不定读到的数据毫无意义。坑四ADC 引脚当成普通 GPIO。GP26、GP27、GP28 这 3 个引脚既是普通 GPIO也是 ADC 通道。但 ADC 输入电压范围是 0~3.3V超过这个范围必须分压否则直接烧毁引脚。坑五PWM 和中断的引脚冲突。Pico 虽然每个引脚几乎都支持 PWM但同一时刻一个引脚只能承担一种复用功能。如果代码里先初始化 PWM又尝试在这个引脚上读取中断触发大概率会得到不符合预期的行为。坑六MicroPython 里没有GPIO.setmode()之类的函数。按照 Arduino 和 STM32 的惯性很多人想知道如何配置引脚模式。在 MicroPython 中创建 Pin 对象时直接指定 mode 参数就完成了配置不存在独立的配置函数。6. 进阶玩法从 GPIO 到舵机控制你注意到热搜词里有树莓派 pico 控制舵机控制舵机用的是 PWM 功能恰好是对 GPIO 概念的延伸。这里展开说说。舵机的控制原理是以 50Hz20ms 周期的频率发送 PWM 信号脉宽 0.5ms~2.5ms 对应舵机角度 0°~180°。Pico 的任何一个 GPIO 引脚在 MicroPython 里都能配置成 PWM 输出不需要额外硬件。from machine import Pin, PWM import time # 将 GP15 初始化为 PWM servo PWM(Pin(15)) servo.freq(50) # 50Hz 对应 20ms 周期 # 计算占空比 # 脉宽 1ms - 占空比 1ms / 20ms 5% # MicroPython PWM 的 duty_u16 范围是 0~65535 duty_1ms int(65535 * 0.05) # 脉宽 1ms duty_1_5ms int(65535 * 0.075) # 脉宽 1.5ms舵机中位 duty_2ms int(65535 * 0.1) # 脉宽 2ms # 从 0° 到 180° servo.duty_u16(duty_1ms) time.sleep(1) servo.duty_u16(duty_1_5ms) time.sleep(1) servo.duty_u16(duty_2ms) time.sleep(1) # 停止输出 servo.deinit()注意这里的duty_u16()方法接收的值是 0~65535 的占空比级别不是直接用毫秒数。换算公式是duty 脉宽(ms) / 周期(ms) × 65535这个坑我踩过一开始我按直觉把duty_u16(1500)当成 1.5ms 用结果舵机直接转到奇怪的角度。后来才反应过来MicroPython 的 PWM 占空比用的是一整个 16 位寄存器的值不是微秒。7. 引脚冲突的全景排查一次外设无法工作的完整链路排错最后分享一次真实的排错经历。当时做一个环境监控项目I2C 接的 OLED 屏幕死活不显示数据但代码逻辑检查多次没有任何问题。排查过程如下大家可以看看排查思路第一步检查接线。SDA 接 GP4物理引脚 6SCL 接 GP5物理引脚 7都核对无误。VCC 和 GND 分别为 3.3V 和 GND。第二步硬件检测。用万用表量 SDA 和 SCL 的电平发现两个引脚都是 0V。正常应该是 3.3V 高电平I2C 空闲时靠上拉电阻拉高。怀疑是总线被拉死了。第三步软件检查。查阅 I2C 初始化代码i2c I2C(0, sclPin(5), sdaPin(4), freq400000)看着没问题但再用i2c.scan()扫描设备地址时返回了空列表。第四步排查硬件故障。拔掉 OLED单测 SDA 和 SCL 电平发现仍然是 0V。问题不在 OLED而在 Pico 本身。第五步回溯引脚复用。检查自己的代码发现之前初始化过一个 Button 对象刚好用了物理引脚 5GPIO 4做输入。虽然代码逻辑上没有冲突但在 MicroPython 中同一个物理引脚如果被前面代码占用了后面的 I2C 初始化不会报错但引脚电平已经被前面的配置改变了。释放引脚需要调用Pin.deinit()或重启。第六步重启板子并调整初始化顺序。让 I2C 初始化放在按键之前问题解决。这个案例暴露了一个容易被忽视的问题MicroPython 不支持重复初始化同一个引脚。同一个物理引脚在代码中被创建了多次 Pin 对象每次初始化的配置参数可能不同后面的初始化可能无效或者覆盖前一个配置。严格来说一个引脚最好只初始化一次。另外I2C 的 SDA/SCL 引脚需要接上拉电阻约 4.7kΩ这是 I2C 协议的硬件要求。Pico 板载了部分引脚的 I2C 上拉电阻但不是所有引脚组合都有。如果你用的是非板载默认的 I2C 引脚组合记得在外部加上拉电阻。根据我个人经验做 GPIO 相关项目前先画一张引脚分配表把每个 GPIO 分配给哪个外设、用到了什么复用功能写清楚。看似多花了五分钟实际能省下后面几小时的排错时间。Pico 的引脚资源虽然丰富但一旦牵扯到复用功能冲突排查就很痛。希望这篇能帮你把 Pico 的 GPIO 玩得顺手。
RELATED READING

延伸阅读

更多一线实战笔记与深度复盘,助您持续精进