树莓派 Pico GPIO 控制入手指南:从点灯到中断与PWM
2026/9/6 14:43:44 网站建设 项目流程

机器码不神秘:先用一盏灯搞懂树莓派 Pico 的 GPIO 控制

想上手树莓派 Pico,第一道绕不开的门槛就是 GPIO,也就是那块板子上伸出来的一排金属引脚。很多朋友第一次拿到板子,看着芯片型号、引脚丝印和网上各种缩写,第一反应就是拿 accelerometer、SPI、I2C 一堆名词先把自己难住。其实千万别想复杂,嵌入式开发最基础也是最常用的操作,就是直接用 machine.Pin 这个类去控制引脚的电平状态——说白了,就是让某个引脚输出高电平或者低电平,从而点亮一个 LED、驱动一个蜂鸣器,或者读取一个按键是否被按下。

这篇文章我会从 machine.Pin 的核心方法讲起,用树莓派 Pico 配 MicroPython 固件环境,带你把引脚控制这件事彻底吃透,顺便把 GPIO 工作模式怎么选、pin 和 GPIO 到底是啥关系、什么时候用 PWM、什么时候用中断这类平时容易绕晕的问题一次讲清楚。内容不挑基础,哪怕你之前只写过几行 Python、完全没碰过单片机,照着操作也能跑起来;如果你已经会点 Arduino 或者 STM32,那这篇文章能帮你快速把经验迁移到 Pico 上,少走不少弯路。

1. 引脚控制前必须搞懂的几个底层概念

在打开编辑器写代码之前,我建议你先花几分钟把几个基础概念磨明白。这不是为了掉书袋,而是因为这些概念直接决定了你后续代码能不能跑、引脚会不会烧,以及遇到问题的时候能不能自己排查。

1.1 GPIO、pin、引脚编号到底是什么关系

GPIO 的全称是 General-Purpose Input/Output,通用输入输出口。你可以把它想象成一块板子上的“活动接线柱”:既能对外输出信号(比如给 LED 供电),也能从外部读取信号(比如按键按下时的电平变化)。“引脚”则是物理上那些金手指或者排针,是信号进出芯片的通道。至于 Pico 上常说的 Pin 编号,拿树莓派 Pico 举例,板载芯片是 RP2040,它有 26 个 GPIO 引脚(GP0 到 GP28 之间,部分被板载功能占用),定义这些引脚在 MicroPython 里就是传入一个整数编号:

from machine import Pin led = Pin(25, Pin.OUT) # 编号 25 对应 Pico 板载 LED

这里要特别提醒新手:Pin 后面的数字不是物理位置顺序,也不是丝印上的编号。以 Pico 为例,丝印标的是 GP0、GP1,MicroPython 里传的也是 0、1。但有些开发板,比如 ESP32,MicroPython 里传的编号和丝印 GPIO 编号基本一致,而 Arduino 的引脚编号逻辑又不一样。所以拿到任何一块新板子之后,第一件事应该是找官方引脚图对照,不要想当然。

1.2 输出模式、输入模式与开漏模式的区别

GPIO 工作模式最常遇到的说法是“8 种模式”,这其实是 STM32 那类芯片引入的概念。在 Pico 的 MicroPython 环境里,我们更常直接和输出、输入、开漏、下拉、上拉这些打交道。

输出模式表示引脚由芯片内部主动驱动,强行输出高电平或低电平。输入模式表示引脚对外部信号表现为高阻抗,外部电平是什么就读什么,也可以根据需要使能内部上拉或下拉电阻。开漏模式则是引脚不主动输出高电平,要么接地(低电平),要么“悬空”由外部上拉电阻拉到高电平,这种模式适合多设备共享一条信号线,用在哪里?后面会讲。

Pico 的 MicroPython 中,我们可以通过以下方式配置引脚模式:

from machine import Pin # 普通输出 pin_out = Pin(0, Pin.OUT) # 普通输入,不开上下拉 pin_in = Pin(1, Pin.IN) # 输入并开启内部下拉电阻(保证无输入时读到低电平) pin_pull_down = Pin(2, Pin.IN, Pin.PULL_DOWN) # 输入并开启内部上拉电阻(保证无输入时读到高电平) pin_pull_up = Pin(3, Pin.IN, Pin.PULL_UP) # 开漏输出,要配合外部上拉电阻使用 pin_od = Pin(4, Pin.OPEN_DRAIN)

选择哪种模式的核心判断标准就是:这个引脚接的是什么外设,你需要向外部提供电平,还是从外部检测电平。

1.3 为什么用 3.3V 而不是 5V 给引脚供电

树莓派 Pico 的 GPIO 工作电压是 3.3V,这意味着引脚输出的高电平最多就是 3.3V,而且引脚本身也不耐 5V 直灌。很多新手最容易踩的坑就是拿 5V 单片机的逻辑电平直接接到 Pico 引脚上,结果轻则读到的电平逻辑混乱,重则直接烧毁引脚。

举个例子,如果你用一个 5V 供电的传感器模块向 Pico 的引脚输出高电平信号,这个信号超过了 3.3V 的容忍范围。正确处理办法是用电平转换模块,或者选用明确标明 3.3V 兼容的传感器模块。如果你只是点个 LED,那更简单,串联一个合适阻值的电阻限流,供电和信号都可以直接用 Pico 的 3.3V 引脚。

2. machine.Pin 核心方法逐个拆解

确认基础概念没问题之后,我们就可以正式开始操作 machine.Pin 了。MicroPython 的 Pin 类把常用操作封装成了非常简洁的 API,整体不难,但每个方法背后都有一些值得注意的细节。

2.1 初始化:Pin(id, mode, pull=None) 参数里的门道

创建一个 Pin 对象最常见的方式是:

from machine import Pin # 方式一:创建时带模式 led = Pin(25, Pin.OUT) # 方式二:先用 Pin(mode=None) 创建对象,后续再调用 init button = Pin(15) button.init(Pin.IN, Pin.PULL_UP)

第一个参数 id 就是引脚编号,在 Pico 上通常是整数。第二个参数 mode 支持 Pin.OUT、Pin.IN、Pin.OPEN_DRAIN,对应输出、输入、开漏。第三个参数 pull 是可选的上拉/下拉电阻配置,只在输入模式下有实际意义,可选 Pin.PULL_UP、Pin.PULL_DOWN 或 None。

实际写代码的时候,我更推荐养成“创建对象时不带模式,之后用 init 初始化”的习惯。原因有两点:一是代码可读性更高,初始化这个过程单独一个 init 调用,阅读代码时一眼就能看到引脚最终配置;二是后续如果需要切换模式,直接再调一次 init 就行,代码改动最小。比如按键和 LED 复用同一个引脚这种特殊场景,就能靠这种方式切换。

2.2 控制输出电平:value() 与高低电平的完整逻辑

输出控制最核心的方法就一个:

led.value(1) # 输出高电平 led.value(0) # 输出低电平

value() 这个方法支持两种行为:传参数就是设置电平,不传参数就是读取当前电平。你可以利用这个特性做“读回来确认”的操作,调试时非常有用。

另外有两个更语义化的方法:

led.on() # 等同于 value(1) led.off() # 等同于 value(0)

我个人的习惯是:如果代码里只有一种类型的设备(全部是 LED,全部是继电器),用 on/off 更直观;如果同一块板子上设备种类多,比如 LED、蜂鸣器、继电器混在一起,我统一用 value(1)/value(0),因为不同的 on 在不同设备上语义不一样,比如继电器模块可能是低电平触发。

控制输出电平这件事看起来简单,但有一个细节提醒:Pico 的 GPIO 输出电流能力是有限的,单个引脚最大输出电流建议不超过几十毫安。如果你直接用一个引脚去驱动电机、电磁锁之类的大电流设备,一定会出问题,轻则重启,重则烧板。

2.3 读取输入电平:多次读取的稳定策略

读取按键状态是 GPIO 输入最典型的应用,写法如下:

from machine import Pin button = Pin(15, Pin.IN, Pin.PULL_UP) if button.value() == 0: print("按键被按下") else: print("按键未按下")

这里引出一个很多新手都会遇到的困惑:为什么按下按键之后读到的值反而是 0?这取决于你的电路设计。如果你把按键一脚接到 GND,另一脚接到 GPIO,同时 GPIO 使能内部上拉电阻,那按键未按下时引脚被内部上拉到 3.3V,读回来是高电平 1;按下后引脚被短接到 GND,读回来是低电平 0。所以读 0 表示按下,这种电路叫“低有效”。

实际项目里,按一次按键,程序可能在一个循环里跑很多次,每次读到 0 都会触发一次逻辑。这时候需要用“消抖”和“状态变化检测”来控制。我常见的做法是在检测到按键按下之后,先 delay 一段时间,再确认一次电平状态;或者直接用中断的方式,避免轮询占用 CPU。

2.4 模式切换:init() 与高阻态的用法

init() 方法可以不改变对象本身,直接把引脚模式换成另一种:

pin = Pin(20, Pin.IN, Pin.PULL_UP) # ... 准备输出一段波形 ... pin.init(Pin.OUT) pin.value(0)

某些场景下,切换输入输出模式是高阻态这种状态,可以用Pin.IN不开启任何上下拉来实现。所谓高阻态,就是引脚既不对外提供电平,也不从外部吸取电流,相当于“断开”了。这在多主设备共享总线时要特别常用,一个设备不讲话的时候,就把自己的输出引脚切成输入模式,让总线处于释放状态,不影响其他设备通信。

2.5 反转输出:toggle() 的实际应用

想要实现“按一下按键,LED 状态反转”这类逻辑,最快的方法:

btn = Pin(15, Pin.IN, Pin.PULL_UP) led = Pin(25, Pin.OUT) while True: if btn.value() == 0: led.toggle() # 将当前电平反转 while btn.value() == 0: pass # 简单按键等待松开,避免按住时反复触发

toggle() 方法内部实现就是读取当前输出值再取反写入,比你自己写led.value(1 - led.value())要简洁可靠,而且省掉了一次读取寄存器的操作,对时间要求比较高的场景有帮助。

3. 从基础到进阶:PWM、中断和实用组合

会了输出、输入和基本的状态控制之后,你就已经能完成一大批常见应用了。接下来这几个能力,是让 GPIO 控制从“点亮”进阶到“真实项目”的关键。

3.1 用 PWM 实现呼吸灯和舵机控制

PWM(脉冲宽度调制)是 GPIO 实现“假装模拟输出”的标准方式。原理很简单:引脚以固定的频率在高电平和低电平之间快速切换,通过改变高电平在一个周期内占的时间比例(也就是占空比),让外部设备感受到的平均电压发生变化。LED 亮度、电机转速、舵机角度都是靠这个原理控制的。

MicroPython 的 PWM 在 Pico 上相当容易使用:

from machine import Pin, PWM # 创建 PWM 对象,注意要传入引脚编号 pwm_led = PWM(Pin(25)) # 设置 PWM 频率,单位 Hz pwm_led.freq(1000) # 设置占空比,Pico 上范围是 0~65535 pwm_led.duty_u16(32768) # 50% 占空比

控制舵机是 Pico 高频玩法之一。舵机最常用的控制信号是 50Hz 频率,也就是周期 20ms,脉宽在 0.5ms 到 2.5ms 之间对应 0 到 180 度。换算成 MicroPython 的 duty_u16 值,满周期 20ms 相当于从 0 到 65535,那么 0.5ms 脉宽对应的占空比是0.5ms / 20ms * 65535 ≈ 1638,2.5ms 对应2.5ms / 20ms * 65535 ≈ 8192。实操代码可以这样写:

from machine import Pin, PWM servo = PWM(Pin(16)) # 舵机信号线接 GP16 servo.freq(50) # 转到 0 度 servo.duty_u16(int(1638)) # 转到 180 度 servo.duty_u16(int(8192))

注意,Pico 的 PWM 通道输出能力也有限,舵机直接吃 GPIO 电流不太现实,通常要给舵机单独供 5V 电源,然后共地连接,GPIO 只输出信号。

3.2 用中断替代轮询,解放 CPU

输入判断还有另一种思路:中断。不用写了 while 循环一直盯着电平,而是配置好条件,满足条件时 MicroPython 自动执行你定义的回调函数。

from machine import Pin def button_handler(pin): print("按键触发,当前引脚电平:", pin.value()) btn = Pin(15, Pin.IN, Pin.PULL_UP) btn.irq(trigger=Pin.IRQ_FALLING, handler=button_handler)

这里 trigger 有两种最常用的选项:Pin.IRQ_RISING 表示电平从低跳到高的瞬间触发,Pin.IRQ_FALLING 表示电平从高跳低的瞬间触发。你可能会问,什么时候用轮询、什么时候用中断?如果你只有一个按键,轮询完全够用且逻辑直观;如果系统里有多个异步事件,比如同时要响应按键、传感器超时、通信数据到达,轮询会让主循环变得很混乱,用中断把每个事件拆开是更干净的做法。

中断回调函数里切记不要做耗时操作,比如长时间延时或者复杂计算。回调里只应该设置标志位、保存数据,真正的处理放回主循环去做。原因很简单:MicroPython 的中断上下文里很多操作不可重入,处理不当会导致系统异常。

3.3 GPIO 和 Pin 的“八种模式”在 Pico 上怎么对应

网上经常能搜到“GPIO 的8种工作模式”,这个说法对刚接触 Pico 的人来说挺容易产生误解。我在很多地方看到有人把这个概念原封不动搬过来,结果想着在 Pico 上找输入浮空、输入上拉、输入下拉、模拟输入、开漏输出、推挽输出这些配置方式,却发现 API 里没有这么细。原因在于,8 种模式是对 STM32 等较复杂芯片内部寄存器配置的描述,Pico 的 RP2040 在 MicroPython 封装下,Pin 类已经把这些配置抽象成了几个维度:模式(输出/输入/开漏)、上下拉(上拉/下拉/None)、引脚复用功能。你在 MicroPython 环境中只需用这三个维度组合出你想要的物理行为就行,没必要纠结 8 种模式的名目。

4. 典型实操案例:从零搭建一个按钮控制 LED 项目

概念拆完,我们来做一个小项目把整套流程串起来。目标:用一个按键控制 Pico 板载 LED 的开关状态,同时按一下会反转一次。这个案例麻雀虽小,但把输入、输出、消抖、状态检测全用上了。

4.1 硬件连接与材料清单

你需要的东西非常简单:

  • 树莓派 Pico 一块,烧录好 MicroPython 固件
  • 面包板一块
  • 轻触按键一个(或者用杜邦线直接手动接触地线模拟按键)
  • 杜邦线若干

接线关系如下:

  • Pico 的 GP15 接按键一端
  • 按键另一端接 Pico 的 GND
  • 如果按键没有自带上拉,在代码里让 GP15 使能内部上拉电阻

注意,Pico 板载 LED 默认在 GP25,我们直接用这个作为输出演示,不用额外接 LED。

4.2 完整代码与逐行解释

from machine import Pin import time # 初始化:板载 LED 为输出模式 led = Pin(25, Pin.OUT) # 按键引脚为输入模式,使能内部上拉电阻 button = Pin(15, Pin.IN, Pin.PULL_UP) # 记录上一次的电平状态,用于检测“按下”这个动作 previous_value = 1 while True: current_value = button.value() # 检测电平从高到低的瞬间 if previous_value == 1 and current_value == 0: # 一个小延时消抖,然后再确认一次 time.sleep_ms(20) if button.value() == 0: led.toggle() # 提示信息(仅在串口终端能看到) print("LED state: %d" % led.value()) # 等待按键松开,防止长按期间反复触发 while button.value() == 0: time.sleep_ms(10) # 更新状态 previous_value = current_value time.sleep_ms(10)

这段代码最关键的部分是“检测电平变化瞬间”。通过保存上一次读取的previous_value,只有当它从 1 变成 0 时才认为是一次新的按下动作。如果只写if button.value() == 0就执行 toggle,那按住按键的过程中,主循环会反复运行 toggle,LED 会闪个不停,这不是我们想要的效果。

消抖那一小段延时也很重要。机械按键在按下瞬间,金属触点会抖动,几十毫秒内信号会在高低电平之间快速跳变,如果不消抖,一次按下可能被识别成多次。我这里的做法是先检测到下降沿,延时 20ms 再确认一次,如果确实还是低电平,才认为是一次真实按键事件。

4.3 实测效果与调参心得

我按上面代码烧录跑过多次,按键按下时 LED 状态立即反转,串口输出一次提示信息,松开后没有多触发。如果你发现手感不对,比如按一下有时管用有时不管用,大概率是消抖时间太短或者太长。消抖时间太短,抖动期的干扰被当成真实按键;太长则在快速连续按的时候会丢失中间动作。正常情况下 10ms 到 30ms 是比较中间的范围,你可以根据自己的按键手感微调。

还有一个细节是,我把更新previous_value放在了延时之后。这保证了循环读取和状态更新是同步的,不会因为某次延时错过了变化点。

5. 进阶场景:GPIO 在真实项目里最常见的几种组合

学会了 Pin 类的基本方法,你能做的项目已经远远不止点灯。这里我把自己在实际项目里经常用到的一些组合方式整理出来,思路比单个 API 更重要,遇到类似需求可以直接迁移。

5.1 多路输出:同时控制一组 LED 或继电器

项目里经常会出现“一次点亮一组灯”的需求。比如你要做一个模拟红绿灯,三个 LED 分别接在 GP0、GP1、GP2。写代码时不需要创建三个独立变量,直接用列表或者元组组织起来:

from machine import Pin pins = [Pin(0, Pin.OUT), Pin(1, Pin.OUT), Pin(2, Pin.OUT)] def set_lights(state_list): for i, pin in enumerate(pins): pin.value(state_list[i]) # 红灯亮,绿灯熄灭 set_lights([1, 0, 0])

用列表管理的另一个好处是方便后续遍历做闪烁、流水灯等效果。如果要控制继电器模块,接线思路也完全一样,只是继电器模块的输入控制线接到 GPIO,输出侧再接大功率设备。注意继电器模块如果是低电平触发,那 value(0) 才是吸合。

5.2 多个按键输入:如何同时管理

多个按键同时接入时,我的做法是用字典把按键名称与 Pin 对象映射起来,这样主循环里的处理逻辑可读性更好。

from machine import Pin buttons = { 'up': Pin(2, Pin.IN, Pin.PULL_UP), 'down': Pin(3, Pin.IN, Pin.PULL_UP), 'ok': Pin(4, Pin.IN, Pin.PULL_UP), } for name, pin in buttons.items(): if pin.value() == 0: print("%s pressed" % name)

这里可以顺便多说一句:如果按键很多,单片机引脚不够用,可以考虑矩阵键盘方案,用行线和列线组合扫描,但那就是另一套玩法了,一般在用 Pin 类把单个输入搞熟练之后再研究也不迟。

5.3 无源蜂鸣器与有源蜂鸣器的连接差异

玩 GPIO 不久就会遇到蜂鸣器。简单区分一下:无源蜂鸣器需要外部提供一个特定频率的方波才能发声,所以通常接到 PWM 引脚上,通过不同频率发出不同音调;有源蜂鸣器内部自带振荡电路,只要给个高电平就会响,直接接到普通输出引脚就能工作。

# 无源蜂鸣器:PWM 输出不同频率 from machine import Pin, PWM buzzer = PWM(Pin(5)) buzzer.freq(1000) # 1kHz 声音 buzzer.duty_u16(32768) # 50% 占空比 # 有源蜂鸣器:普通输出 buzzer2 = Pin(6, Pin.OUT) buzzer2.value(1) # 直接响

使用蜂鸣器时,同样要注意驱动电流的问题,小蜂鸣器可以直接用 GPIO 驱动,大功率蜂鸣器最好加三极管或驱动模块,否则芯片发热甚至复位。

6. Pico 的 GPIO 日常开发避坑指南

最后分享一组我实际踩过坑之后整理出来的注意事项,很多问题不是代码逻辑错,而是硬件习惯或者对环境理解不到位导致的现象,排查起来特别费时间。

6.1 第一个坑:GPIO 引脚与板载外设冲突

Pico 上有一些引脚是和板载 Flash、ADC、调试等功能复用的,比如 GP25 接了板载 LED。如果你试图同时把它当成普通输入口接外部设备,可能会引起冲突,因为板载 LED 电路已经把引脚拉到一个确定的电平状态了。此外,GP0 到 GP28 中有几个引脚在连接某些外部模块时要避开,比如 I2C 和 SPI 接口的标准引脚如果同时接了别的设备,容易互相干扰。最稳妥的方法是每次画电路之前都打开官方 Pinout 图,确认你用的引脚没有和板载资源冲突。

6.2 第二个坑:输入信号需要共地

很多新手第一次用 Pico 读取外部传感器输出,信号明明接了,程序也没问题,但读到的数据乱跳或者永远是同一个值。这时候大概率是供电问题。外部传感器如果自己有一块电源(比如用了 5V 适配器给传感器供电),它的 GND 必须和 Pico 的 GND 连在一起,否则高低电平的参考基准不同,信号根本不存在。这个问题叫“共地”,一句话记忆:所有设备的地线必须接到同一个参考点。

6.3 第三个坑:PWM 频率和占空比的数值范围千万别记错

不同开发板用 MicroPython 控制 PWM 时,占空比的 API 可能不一样。在 Pico 上,常用的是duty_u16(0~65535),但某些教程会提到duty(0~1023)之类的说法,那更多是 ESP32 或者更早期固件版本的接口。直接照着旧教程套到 Pico 上,你会发现运行时占空比数值异常,舵机不转,LED 亮度不对。最好的办法是看当前固件的官方文档,或者直接在 REPL 里敲help(PWM)查看支持的函数和参数范围。

6.4 排查技巧:用 REPL 一行行验证

MicroPython 最舒服的特性就是可以连上串口 REPL 交互调试。我遇到引脚行为诡异的时候,从来不会先花十分钟看完整段代码,而是先一行一行在终端敲进去,比如p = Pin(25, Pin.OUT),然后p.value(1),观察 LED 是否点亮。确认硬件通路没问题之后,再去排查循环和逻辑代码。这个习惯帮我解决过大量“代码看着对但实际没反应”的问题,因为硬件连接错误往往比软件逻辑错误出现得更早、更隐蔽。

7. 从 Pin 类出发,往 Pico 更深处走

到这里,machine.Pin 的核心控制方法已经覆盖得比较全面了:初始化、输入输出、上下拉、开漏、PWM、中断、常见项目组合和避坑经验。说实话,Pico 入门最难的不是某个 API 不会用,而是面对几十个引脚、官方文档和网上的碎片教程时,容易迷失方向。我自己入门的体会是:先解决 8 个引脚以内的小项目,把单个 Pin 对象在不同场景下的行为彻底用熟,再往 I2C、SPI、UART 这些协议拓展,因为那些通信协议本质上也是 GPIO 在不同时序规则下的组合应用。GPIO 这个底子打牢了,后面学什么外围设备都快得多,希望这篇内容能帮你先把这块地基踏实踩稳。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询