machine 三件兵器(Pin/PWM/ADC)实战:数字输出、按键输入、调光、读模拟量——第七章在树莓派上干过的事,这里用另一套更贴近硬件的 API 再干一遍,最后合成【电位器控灯】闭环。
7-3(数字输入输出)、7-4(PWM 调光)、7-5(模拟输入)在树莓派上用 gpiozero 做过——gpiozero 的思路是【LED、Button 这些高级对象替你包好】;本节的 machine 是反面:只有 Pin、PWM、ADC 这类裸对象,逻辑全要自己写。对比完你会发现【抽象层高=好写、贴近硬件=好懂】。8-5 的串口知识在 MicroPython 里对应 machine.UART(本节点到为止)。9-3 已把 Thonny 链路打通,本节全部实验都能落地。
一、数字输出:machine.Pin 三行起步
from machine import Pin
import time
led = Pin(15, Pin.OUT) # GP15 做输出(接一颗 LED + 220Ω 电阻,接法同 7-3)
led.on() # 拉高 3.3V
led.off() # 拉低
led.value(1) # on/off 的通用写法,1=高 0=低
和 gpiozero 对照(7-3 的知识搬过来):gpiozero 是 led = LED(17) 然后 led.on()——连输出方向都不用声明;machine.Pin 必须显式写 Pin.OUT 或 Pin.IN。少一层抽象,多一分明白:方向、电平,原来都是自己一句话一句话定的。
二、数字输入:上拉电阻要自己开口要
button = Pin(14, Pin.IN, Pin.PULL_UP) # GP14 做输入,启用内部上拉
while True:
if button.value() == 0: # 按下=接通 GND=读到 0
led.on()
else:
led.off()
三件事和 7-3 完全同理,但都要自己写:
- 上拉要显式声明(
Pin.PULL_UP)——gpiozero 的Button默认就带,这里不写就是浮空乱跳 - 按下读到 0 不是 1——按钮另一端接 GND(上拉的规矩,7-3 的警告框在这里原样成立)
- 防抖也要自己来:检测到按下后
time.sleep_ms(20)再确认,或记ticks_ms()比时间
三、PWM:duty_u16 的十六位世界
from machine import Pin, PWM
pwm = PWM(Pin(15))
pwm.freq(1000) # 1kHz —— 人眼看着不闪的最低舒适频率(7-4 的知识)
pwm.duty_u16(32768) # 半亮度:占空比 32768/65536 ≈ 50%
pwm.duty_u16(0) # 全灭
pwm.duty_u16(65535) # 最亮
duty_u16 是 16 位占空比:0 ~ 65535(u16 = unsigned 16-bit)。换算口诀:
平均电压 = 3.3V × duty_u16 ÷ 65535 —— 7-4 的公式原样成立,只是刻度从 gpiozero 的 0~1 换成了 0~65535。 想沿用 0~1 的手感:
pwm.duty_u16(int(value * 65535))。
用完记得释放(单片机内存金贵,9-2 说过):pwm.deinit()。
四、ADC:Pico 自带,这次不用买芯片
7-5 的最大痛点在这里消失——树莓派读模拟量要外接 MCP3008,Pico 芯片里就有 3 路 12 位 ADC(GP26/GP27/GP28):
from machine import Pin, ADC
pot = ADC(Pin(26)) # 电位器中间脚接 GP26,两端接 3V3 和 GND
raw = pot.read_u16() # 返回 16 位数 0~65535(内部 12 位折算上来)
voltage = raw / 65535 * 3.3 # 换算成电压
print(raw, round(voltage, 2))
两个刻度要分清:内部 ADC 是 12 位(0~4095),但 read_u16() 把它折算到 16 位刻度(0~65535)返回——统一刻度方便和 PWM 对接。换算电压时用 65535 做分母(对着返回值算),别用 4095。
闭环就用一行(电位器控灯亮度,7-5 末尾那个项目的 MicroPython 版):
pwm.duty_u16(pot.read_u16()) # ADC 和 PWM 刻度天然相同,直接对接!
五、定时与串口:认识两个名字
machine.Timer:硬件定时器,到点自动回调一个函数——不用while True干等。本课认识即可,进阶项目用它替代【轮询 ticks_ms】。machine.UART:UART(0, baudrate=9600, tx=Pin(0), rx=Pin(1))——GP0/GP1 正是 9-1 表里的 UART0。Pico 与树莓派/Arduino 的串口对话走它,行协议的规矩(换行定界、逗号分字段、解析不了就丢弃)与 8-5 完全一致。本课点到为止。
六、老教程里已失效的写法(这一节防坑值最高)
MicroPython 和 Pico 硬件这几年换代,老书老博客里这几处照抄必挂:
- ⚠️W 型号上
Pin(25)控制不了板载 LED——Pico W/Pico 2 W 的 GP25 接的是无线芯片,不是灯!正确写法:python led = Pin("WL_GPIO0", Pin.OUT) # W 型号的板载灯 # 或新版固件:from machine import LED; led = LED("WLED")老教程的 Blink 十有八九栽在这里——代码能跑、灯不亮,还不报错。 adc.read()已废弃——老代码的read()(返回 12 位)被read_u16()取代;对照读两份资料时数字差 16 倍就是栽在这。duty()与duty_u16()是两套刻度——duty()是 0~1023(10 位),duty_u16()是 0~65535。老教程写duty(512)【半亮】,直接搬进新代码要写duty_u16(32768)。- 固件版本看官方页——老书印的版本号一律以
micropython.org/download当前稳定版为准(当前 v1.28)。
七、动手 Demo:电位器调灯——三个兵器连成一条闭环
接线三根(对照 7-5 的接法,但这次不用买任何芯片):
| 电位器脚 | 接到 |
|---|---|
| 两端的两个脚 | 3V3 / GND |
| 中间脚 | GP26 |
| LED 阳极(长脚) | GP15(串 220Ω 电阻到 GND) |
程序:
from machine import Pin, PWM, ADC
pot = ADC(Pin(26))
pwm = PWM(Pin(15))
pwm.freq(1000)
while True:
duty = pot.read_u16() # 读模拟量(ADC 兵器)
pwm.duty_u16(duty) # 输出亮度(PWM 兵器)
print("duty =", duty) # Thonny Shell 里看数值变化
看到什么算成功:拧电位器,灯从灭到最亮平滑变化,Shell 里 duty 数值跟着从 0 附近爬到 65535 附近。
读结果:
- ADC 和 PWM 同刻度(都是 16 位),所以【读多少、输出多少】一行直连——7-5 里 gpiozero 靠
value0~1 归一化做到的事,这里靠刻度天然一致,连换算都省了 - 数值偶尔跳一下是 ADC 噪声,拧到头读到 65535 就是满量程 3.3V(7-5 讲的【最大值是满量程不是过载】同理)
- 这就是闭环控制的雏形:传感器进来 → 处理 → 执行器出去,第七章的项目思想 + 本节的裸 API = 完整闭环
八、30 秒实验:算占空比与电压
拧电位器让 Shell 显示 duty = 20000:
- 灯的平均电压是多少?
- 这相当于 gpiozero(0~1 刻度)里的 value 多少?
参考答案:1. 3.3 × 20000 ÷ 65535 ≈ 1.01V(按 7-4 的公式代入 16 位刻度)。2. 20000 ÷ 65535 ≈ 0.31——如果你手边有树莓派,把这个 0.31 填进 7-4 的 PWMLED.value,两块板亮度应该几乎一样。
九、常见误区
- 【用树莓派的 BCM 号找 Pico 的针】 —— Pico 用 GP 号(9-1 的坑),接线看板子丝印。
- 【W 型号上跑 Blink 教程灯不亮,以为板子坏了】 —— W 型的 GP25 接的是无线芯片!用
Pin("WL_GPIO0")或LED("WLED")。 - 【
read_u16()读到 65535 是坏了】 —— 那是满量程(电位器拧到头、输入 3.3V);读到 0 是另一头。中间乱跳才是真的有问题(检查接线)。 - 【duty 与 duty_u16 随便用】 —— 两套刻度差 64 倍(0~1023 vs 0~65535),照抄老代码亮度会全乱。
- 【Pico 也要像树莓派那样外接 ADC 芯片】 —— 不用!3 路 12 位内置(GP26~GP28),这是它对比树莓派最直接的优势。
自测三问(点开对答案)
1. `machine.Pin` 的输入模式为什么要写 `Pin.PULL_UP`?gpiozero 里呢? 2. `duty_u16(16384)` 大约是几成亮度? 3. 读模拟量该用哪个针脚、哪个类? **参考答案** 1. 不写上拉,输入脚是**浮空**的、读数乱跳。gpiozero 的 `Button` **默认带内部上拉**,`machine.Pin` 必须**显式写**——抽象层的差异就在这类地方。 2. **16384 ÷ 65535 ≈ 0.25,约四分之一亮度**,平均电压约 0.83V(3.3 × 0.25)。 3. **GP26/GP27/GP28 三选一**,用 `machine.ADC` 类;`read_u16()` 取 16 位值,电压 = 值 ÷ 65535 × 3.3。- ☑ 第九章 完结 —— 教材这一章已读完四节:认清 Pico 这块【家族里的单片机】、掌握 MicroPython 的语言与环境差异、用 Thonny 打通【刷固件→连接→保存 main.py】全流程,最后用 machine 三件兵器(Pin/PWM/ADC)把第七章的闭环项目原样重建了一遍
- ☑ 第十章 相机模块与视频串流 —— 已完结:使用外接 USB 网络摄像头(USB 摄像头走 V4L2 另一套栈,fswebcam 与 OpenCV 接管)
9-1 认识它(家族里的【另一种东西】)→ 9-2 掌握它的语言(语法不变、环境全新)→ 9-3 打通工具链(Thonny 一个软件管两头)→ 9-4 亲手控制它(三件兵器重建第七章的闭环)。 走完这一章,你手里那套【分工明确的小系统】又多了一个成员:树莓派负责【想】、Arduino 负责【扛造的实时】、Pico 负责【会 Python 的实时+自带 ADC】。 回头看 9-1 埋的那句【别和树莓派混——Pico 自带 ADC】:7-5 要买芯片才能做的事,Pico 出厂就会——这就是【按需选板】最直观的一课。 下一章按教材顺序,等波仔报标题。
相关笔记
- 认识 Raspberry Pi Pico 开发板 —— 本章起点:GP 编号体系与全家型号
- 使用 Thonny 建立 MicroPython 程序 —— 上一节:本节所有实验的运行环境
- 数字输出与输入 —— 本节第一、二节的【高级抽象版】,对照读最有收获
- 模拟输出 —— PWM 原理与平均电压公式的出处,本节只是换了刻度
- 模拟输入 —— 【要外接芯片才能读模拟量】的痛,本节被 Pico 治好了
- 在树莓派使用 Python 建立串口通信 —— 行协议三规矩在
machine.UART里原样适用 - 认识树莓派的相机模块 —— 下一章起点:CSI 相机家族与排线门道
- 使用外接 USB 网络摄像头 —— 下一章收束:USB 摄像头的 V4L2 支线
- 🍓 树莓派 · 索引 —— 全课路线图