尧图网站设计 尧图网站设计YAOTU DESIGN
ARTICLE DETAIL

资讯详情

深耕网站设计与一线实操的经验洞察。

树莓派 Pico GPIO 控制入手指南:从点灯到中断与PWM

树莓派 Pico GPIO 控制入手指南:从点灯到中断与PWM 机器码不神秘先用一盏灯搞懂树莓派 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、GP1MicroPython 里传的也是 0、1。但有些开发板比如 ESP32MicroPython 里传的编号和丝印 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, pullNone) 参数里的门道创建一个 Pin 对象最常见的方式是from machine import Pin # 方式一创建时带模式 led Pin(25, Pin.OUT) # 方式二先用 Pin(modeNone) 创建对象后续再调用 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 ≈ 16382.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(triggerPin.IRQ_FALLING, handlerbutton_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那按住按键的过程中主循环会反复运行 toggleLED 会闪个不停这不是我们想要的效果。消抖那一小段延时也很重要。机械按键在按下瞬间金属触点会抖动几十毫秒内信号会在高低电平之间快速跳变如果不消抖一次按下可能被识别成多次。我这里的做法是先检测到下降沿延时 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 这个底子打牢了后面学什么外围设备都快得多希望这篇内容能帮你先把这块地基踏实踩稳。
返回列表