1. 为什么我劝你从 MicroPython 开始玩 Pico
Raspberry Pi Pico 这块小板子刚出来的时候,我其实没太当回事。一块双核 RP2040 芯片、264KB 的 SRAM、2MB 的 Flash,售价却低得离谱,第一反应是“便宜没好货”。但真正上手之后,我发现它可能是近几年最适合嵌入式入门的开发板之一,没有之一。原因很简单:它把“写代码”和“看到硬件反应”之间的门槛压到了最低,而 MicroPython 正是那把钥匙。
这篇文章面向的是刚拿到 Pico、或者还在犹豫要不要入手的朋友。我会从零开始,把 MicroPython 在 Pico 上的开发环境搭建、GPIO 操作、PWM 输出、串口通信这些核心内容全部讲透,并且给出可以直接复现的代码和实操步骤。你不需要有单片机基础,只要会一点 Python,就能跟着做下来。如果你之前玩过 STM32 或者 Arduino,也能从里面找到 Pico 的差异化玩法。
先说结论:Pico + MicroPython 的组合,适合快速验证想法、做小项目、教学演示,以及那些对实时性要求不那么极端的场景。它不适合跑复杂算法或者对时序要求到纳秒级的任务,但作为入门第一课,它几乎没有对手。
2. 开发环境搭建:别在第一步就卡住
2.1 固件烧录:把 Pico 变成一台“Python 电脑”
拿到 Pico 的第一件事,不是写代码,而是给它刷固件。Pico 出厂时 Flash 是空的,需要你手动把 MicroPython 固件写进去。这个过程比想象中简单,但有几个细节容易踩坑。
首先,按住板子上的 BOOTSEL 按钮不放,然后用 USB 线连接到电脑。注意,是按住之后再插线,顺序反了就会直接进入正常运行模式,电脑上不会出现可移动磁盘。连接成功后,电脑上会弹出一个名为 RPI-RP2 的 U 盘,容量很小,里面只有两个文件。
接下来去 Raspberry Pi 官网下载最新的 MicroPython UF2 固件文件。这里有个经验:不要盲目追最新版,尤其是你后面要用某些特定库的时候。我一般会选最近三个月内发布、且社区反馈稳定的版本。下载完成后,直接把 .uf2 文件拖进 RPI-RP2 磁盘里。拖进去的瞬间,Pico 会自动重启,磁盘消失,固件烧录完成。
注意:如果你用的是 macOS,拖拽完成后可能会提示“磁盘未正常推出”,这是正常现象,不用管它。Windows 下如果拖进去没反应,检查一下 USB 线是不是只能供电不能传数据的那种。
烧录完成后,Pico 上电就会自动运行 MicroPython 解释器。这时候你通过串口连接上去,就能看到一个交互式的 Python 环境,也就是 REPL。这是 MicroPython 最迷人的地方:你可以像在电脑上敲 Python 一样,一行一行地控制硬件。
2.2 工具链选择:Thonny 还是 VS Code
开发工具这块,我试过好几种组合,最后稳定下来的是两套方案。
第一套是 Thonny,适合纯新手。它内置了 MicroPython 支持,安装完之后在右下角选择解释器为 MicroPython (Raspberry Pi Pico),然后选对应的串口,就能直接连上 Pico。Thonny 的好处是 REPL 和文件管理集成在一起,你可以直接在界面上看到 Pico 里的文件,右键上传下载,非常直观。缺点是代码补全和项目管理比较弱,写大一点的项目会难受。
第二套是 VS Code + MicroPico 插件。这套方案适合有一定开发经验的人。安装 MicroPico 插件后,VS Code 可以自动识别 Pico 的串口,提供代码补全、语法检查、一键运行等功能。更重要的是,你可以用 VS Code 的工程化管理能力,把代码分成多个文件,用 Git 做版本控制。我现在的习惯是:快速验证用 Thonny,正式项目用 VS Code。
不管用哪套工具,核心逻辑是一样的:通过串口和 Pico 上的 MicroPython 解释器通信,把 .py 文件传上去执行。理解这一点,工具切换就没有成本。
2.3 串口连接与 REPL 调试
串口是 Pico 和电脑之间的生命线。MicroPython 启动后,会在 USB 上虚拟出一个串口设备。Windows 下通常是 COMx,Linux 和 macOS 下是 /dev/ttyACM0 或 /dev/tty.usbmodem 开头的设备。
连接串口后,你会看到 >>> 提示符,这就是 REPL。在这里可以直接输入 Python 代码并立即执行。比如输入print("hello"),回车就能看到输出。更关键的是,你可以直接操作硬件:
from machine import Pin led = Pin(25, Pin.OUT) led.value(1)这几行代码会让 Pico 板载的 LED 亮起来。注意,Pico 的板载 LED 接在 GPIO25 上,这是官方设计,不用外接任何元件就能看到效果。对于第一次上手的人来说,这一瞬间的反馈非常重要,它证明整条链路是通的。
REPL 还有一个隐藏用法:当你写的 main.py 出现死循环或者异常时,可以按 Ctrl+C 中断程序,回到 REPL 提示符。这个操作在调试时救过我很多次,尤其是 PWM 输出把电机转飞了的时候。
3. GPIO 操作:从点亮一颗 LED 到读懂八种模式
3.1 GPIO 到底是什么,用生活化方式理解
GPIO 全称 General Purpose Input/Output,翻译过来就是通用输入输出。你可以把它想象成 Pico 上的一排开关和灯泡:有的引脚可以输出高电平或低电平,相当于控制灯泡亮灭;有的引脚可以读取外部信号,相当于检测开关是否被按下。
RP2040 一共有 30 个 GPIO 引脚(Pico 板子引出了 26 个),每个引脚都可以独立配置为输入或输出。但“输入”和“输出”只是最粗的分类,实际使用中还有很多细节模式,比如上拉、下拉、开漏等。这些模式的选择,直接决定了你的电路能不能正常工作。
我见过太多新手因为 GPIO 模式选错,导致按键读不到、LED 亮度不对、甚至烧掉引脚。所以这一节我会把常用的几种模式讲清楚,并且给出选择依据。
3.2 输出模式:点亮 LED 和驱动负载
输出模式是最直观的。配置一个引脚为输出,你就可以用代码控制它输出高电平(3.3V)或低电平(0V)。Pico 的 GPIO 输出电流能力有限,单个引脚最大约 4mA,所有引脚加起来不超过 50mA。这意味着你不能直接用 GPIO 驱动电机、继电器或者大功率 LED,必须加三极管或 MOS 管做驱动。
点亮 LED 的电路很简单:GPIO 接 LED 正极,LED 负极接限流电阻,电阻另一端接 GND。电阻值怎么算?假设 LED 正向压降 2V,目标电流 2mA,那么电阻 = (3.3V - 2V) / 0.002A = 650Ω。实际选 680Ω 或 1kΩ 都可以,亮度差别不大。
from machine import Pin import time led = Pin(15, Pin.OUT) while True: led.value(1) time.sleep(0.5) led.value(0) time.sleep(0.5)这段代码会让接在 GPIO15 上的 LED 以 1Hz 的频率闪烁。注意time.sleep()的单位是秒,0.5 就是 500 毫秒。如果你觉得闪烁太快,改成 1 就是 1 秒一次。
3.3 输入模式与上拉下拉:按键读取的正确姿势
输入模式用来读取外部信号。最简单的场景是按键:按键一端接 GPIO,另一端接 GND。当按键按下时,GPIO 被拉到 GND,读到低电平;按键松开时,GPIO 悬空,读到什么就不一定了。
悬空是万恶之源。引脚悬空时,电平会随机漂移,受周围电磁干扰影响,读到的值可能在 0 和 1 之间跳变。解决办法就是上拉或下拉电阻:给引脚一个确定的默认电平。
Pico 的 MicroPython 支持内部上拉和下拉,不需要外接电阻:
from machine import Pin button = Pin(14, Pin.IN, Pin.PULL_UP) while True: if button.value() == 0: print("按键按下")这里配置了内部上拉,所以按键松开时引脚被拉到高电平,读到 1;按键按下时被拉到 GND,读到 0。如果你把按键另一端接的是 3.3V,那就应该用下拉,逻辑反过来。
实操心得:内部上拉电阻大约在 50kΩ 到 80kΩ 之间,对于大多数按键场景够用。但如果你用长导线连接按键,或者环境干扰很强,建议外接一个 10kΩ 的上拉电阻,稳定性会好很多。
3.4 GPIO 八种工作模式的对照与选择
虽然 MicroPython 把 GPIO 配置简化成了几个参数,但底层 RP2040 的 GPIO 其实支持多种工作模式。理解这些模式,能帮你在遇到问题时快速定位。
| 模式 | 特点 | 典型场景 | MicroPython 配置 |
|---|---|---|---|
| 推挽输出 | 强驱动高低电平 | LED、数字信号输出 | Pin.OUT |
| 开漏输出 | 只能拉低,高电平靠外部上拉 | I2C 总线、电平转换 | 需底层寄存器配置 |
| 输入浮空 | 高阻态,电平不定 | 外部已有驱动源 | Pin.IN |
| 输入上拉 | 内部上拉到高电平 | 按键到 GND | Pin.IN, Pin.PULL_UP |
| 输入下拉 | 内部下拉到低电平 | 按键到 VCC | Pin.IN, Pin.PULL_DOWN |
| 模拟输入 | 关闭数字功能,接 ADC | 电位器、传感器 | ADC(Pin(n)) |
| 复用功能 | 交给外设如 UART、PWM | 串口、PWM 输出 | 由外设自动配置 |
| 中断模式 | 电平变化触发回调 | 按键中断、脉冲计数 | Pin.irq() |
这张表建议收藏。我刚开始玩的时候,经常把开漏和推挽搞混,结果 I2C 总线上拉电阻没接,设备死活不通信。后来才明白,I2C 的 SDA 和 SCL 必须用开漏模式,靠外部上拉电阻把总线拉高,这样才能实现多设备共享总线而不冲突。
4. PWM 输出:让数字引脚输出“模拟量”
4.1 PWM 的本质:用时间换电压
PWM 全称 Pulse Width Modulation,脉冲宽度调制。数字引脚只能输出 0 或 3.3V,但通过快速切换,控制高电平占整个周期的比例,就能在宏观上得到介于 0 和 3.3V 之间的等效电压。这个比例叫占空比。
举个例子:如果 PWM 频率是 1000Hz,周期就是 1ms。占空比 50% 意味着每 1ms 里,前 0.5ms 输出高电平,后 0.5ms 输出低电平。如果接一个 LED,因为切换速度远超人眼分辨能力,你会看到亮度是最大亮度的一半。如果接电机,电机感受到的等效电压也是 1.65V,转速大约是一半。
RP2040 的 PWM 外设很灵活,一共有 8 个 PWM 切片,每个切片有两个通道,总共 16 路 PWM 输出。每个通道可以独立设置频率和占空比,频率范围从几 Hz 到几十 MHz 都行。
4.2 MicroPython 下的 PWM 配置
在 MicroPython 里配置 PWM 非常简单:
from machine import Pin, PWM import time pwm = PWM(Pin(15)) pwm.freq(1000) pwm.duty_u16(32768) while True: for duty in range(0, 65536, 256): pwm.duty_u16(duty) time.sleep_ms(5) for duty in range(65535, -1, -256): pwm.duty_u16(duty) time.sleep_ms(5)这段代码会让 LED 呼吸:亮度从暗到亮再到暗,循环往复。duty_u16()接受 0 到 65535 的整数,对应 0% 到 100% 的占空比。32768 就是 50%。
注意freq()设置的是频率。对于 LED,1000Hz 足够,不会有闪烁感。对于电机,通常用 1kHz 到 20kHz,太低会有啸叫,太高可能超出驱动电路响应能力。对于舵机,必须用 50Hz,这是标准。
4.3 舵机控制:50Hz 下的角度映射
舵机是 PWM 最经典的应用之一。标准舵机要求 50Hz 的 PWM 信号,周期 20ms。高电平持续时间在 0.5ms 到 2.5ms 之间,对应舵机角度 0 到 180 度。
在 MicroPython 里,duty_u16()的值和脉宽的关系是:duty = 脉宽 / 周期 * 65535。周期是 20ms,所以 0.5ms 对应 1638,2.5ms 对应 8192。
from machine import Pin, PWM import time servo = PWM(Pin(16)) servo.freq(50) def set_angle(angle): min_duty = 1638 max_duty = 8192 duty = int(min_duty + (max_duty - min_duty) * angle / 180) servo.duty_u16(duty) while True: set_angle(0) time.sleep(1) set_angle(90) time.sleep(1) set_angle(180) time.sleep(1)注意:不同品牌舵机的脉宽范围可能略有差异。如果发现舵机转到极限位置时发出异响或抖动,说明脉宽超出了机械限位,需要适当缩小 min_duty 和 max_duty 的范围。我一般会先用示波器确认实际脉宽,再写进代码。
4.4 PWM 驱动电机与 MOS 管发热问题
用 PWM 驱动直流电机时,GPIO 不能直接接电机,必须通过驱动模块。常见的有 L298N、TB6612、MOS 管模块等。这里重点说 MOS 管方案,因为它效率高、发热小,但选型和布线有讲究。
MOS 管作为开关,栅极接 PWM 信号,漏极接电机负极,源极接 GND。电机正极接电源正极。当 PWM 为高时,MOS 管导通,电机通电;PWM 为低时,MOS 管关断,电机断电。由于电机是感性负载,关断瞬间会产生反向电动势,必须在电机两端并联续流二极管,否则可能击穿 MOS 管。
MOS 管发热通常有三个原因:一是导通电阻太大,选型时要注意 Rds(on) 参数,越小越好;二是开关频率太高,导致开关损耗增加;三是栅极驱动不足,MOS 管没有完全导通,工作在放大区,功耗剧增。Pico 的 GPIO 输出 3.3V,对于逻辑电平 MOS 管没问题,但对于标准 MOS 管可能不够,需要加栅极驱动电路。
我踩过的坑:第一次用 MOS 管驱动电机,没接续流二极管,结果运行几分钟后 MOS 管烫得不能摸,电机也时转时停。后来加了二极管,换了低导通电阻的型号,温度立刻降下来了。
5. 串口通信与项目实践:把碎片拼成完整项目
5.1 UART 基础与 Pico 的串口资源
UART 是最常用的串口通信方式,全称通用异步收发器。它只需要两根线:TX 发送和 RX 接收,交叉连接即可通信。Pico 的 RP2040 有两路硬件 UART,分别映射在不同的 GPIO 上。
MicroPython 下使用 UART:
from machine import UART, Pin uart = UART(0, baudrate=9600, tx=Pin(0), rx=Pin(1)) uart.write("hello\n") data = uart.read(10)这里 UART0 的默认引脚是 GPIO0 和 GPIO1。如果你要用其他引脚,可以在初始化时指定。波特率常用 9600 或 115200,双方必须一致,否则收到的就是乱码。
串口在实际项目中用途很广:和电脑通信打印调试信息、和蓝牙模块通信、和另一个单片机通信。我经常用 Pico 的串口连接一个 GPS 模块,读取经纬度数据,然后在 REPL 里打印出来。
5.2 综合项目:按键控制 LED 亮度并串口上报
把前面学的 GPIO 输入、PWM 输出、串口通信串起来,做一个完整的小项目:两个按键,一个增加亮度,一个降低亮度,当前亮度值通过串口发送到电脑。
from machine import Pin, PWM, UART import time led = PWM(Pin(15)) led.freq(1000) led.duty_u16(32768) btn_up = Pin(14, Pin.IN, Pin.PULL_UP) btn_down = Pin(13, Pin.IN, Pin.PULL_UP) uart = UART(0, baudrate=115200, tx=Pin(0), rx=Pin(1)) brightness = 32768 step = 4096 while True: if btn_up.value() == 0: brightness = min(65535, brightness + step) led.duty_u16(brightness) uart.write("brightness: {}\n".format(brightness)) time.sleep_ms(200) if btn_down.value() == 0: brightness = max(0, brightness - step) led.duty_u16(brightness) uart.write("brightness: {}\n".format(brightness)) time.sleep_ms(200) time.sleep_ms(10)这个项目虽然简单,但涵盖了 GPIO 输入输出、PWM、串口、按键消抖等核心知识点。你可以在此基础上扩展:加一个 OLED 显示当前亮度,或者把亮度值保存到文件里,下次上电恢复。
5.3 常见问题速查与排查技巧
| 现象 | 可能原因 | 排查方法 |
|---|---|---|
| 板载 LED 不亮 | 固件未烧录或代码未运行 | 检查 REPL 是否正常,手动执行 led.value(1) |
| 按键读取不稳定 | 引脚悬空或未配置上拉 | 确认 Pin.PULL_UP 已设置,必要时外接电阻 |
| PWM 无输出 | 引脚被其他外设占用 | 检查是否同时配置了 UART 或 I2C |
| 串口乱码 | 波特率不匹配 | 双方统一为 115200 或 9600 |
| 电机不转或抖动 | 驱动能力不足或续流缺失 | 检查驱动模块,加续流二极管 |
| 舵机抖动发热 | 脉宽超出机械限位 | 缩小 duty 范围,用示波器确认 |
| 程序跑飞 | 死循环或异常未捕获 | Ctrl+C 中断,加 try-except |
独家避坑:Pico 的 GPIO 在复位后默认是输入模式,但如果你在代码里配置了输出并输出高电平,然后按复位键,引脚会短暂保持高电平再变回输入。这个瞬间如果接的是电机驱动,可能会让电机“抽搐”一下。解决办法是在电机驱动使能脚加一个下拉电阻,确保复位期间驱动是关闭的。
6. 从入门到进阶的路线建议
MicroPython 在 Pico 上的入门曲线很平缓,但要想真正做出有意思的东西,还需要补一些底层知识。我的建议是:先把这篇文章里的代码全部跑一遍,理解每一行的作用;然后尝试改参数、改逻辑,观察硬件反应;最后找一个实际需求,比如做个温湿度记录仪、做个遥控小车,在做的过程中遇到问题再查资料。
RP2040 的 PIO 是它最独特的功能,可以编程实现自定义外设,比如精确的脉冲计数、WS2812 灯带驱动、甚至 VGA 输出。MicroPython 也支持 PIO,但语法稍微复杂一些,建议在熟悉基础 GPIO 和 PWM 之后再碰。
另外,如果你发现 MicroPython 的性能不够用,比如需要微秒级定时或者复杂中断处理,可以转向 C SDK 开发。Pico 的 C SDK 文档很完善,而且和 MicroPython 可以共存:你可以用 C 写底层驱动,用 MicroPython 写上层逻辑,通过串口或共享内存通信。
我个人在实际操作中的体会是,MicroPython 最大的价值不是性能,而是“快速把想法变成现实”的能力。很多项目在验证阶段根本不需要极致性能,能跑起来、能看到效果,比什么都重要。等你确认了方向,再考虑用 C 重写关键部分也不迟。