
刚拿到树莓派 Pico 的那几天我干的第一件事就是翻出抽屉里的 10K 电位器想做一个最常见的模拟量采集实验。过程比想象中磕绊read_u16()读出来的值不是预想中的 0-4095转速快一点数据像心电图一样跳光看串口数字完全没法判断噪声到底多大。这篇我把从硬件接线、MicroPython 代码到 PC 端串口波形调试的完整链路摊开讲一遍核心关键词就三个Pico ADC、电位器采集、SerialPlot 波形。适合手头有 Pico 板子、想把模拟采集吃透的读者也适合想快速搭一套波形观察环境来调试其它传感器的人。1. Pico 的 ADC 到底有多少位硬件规格与 MicroPython 返回值的关系1.1 RP2040 集成的 SAR ADC 架构与可用通道树莓派 Pico 用的芯片是 RP2040内置 ADC 是一个 12 位逐次逼近型SARADC。逐次逼近的工作方式很像猜数字游戏先把电压和参考电压的一半比较判断高了还是低了再在二分之一的区间内继续二分循环 12 次最终得到一个 0-4095 的整数编码。这种方式的好处是转换速度可以做得比较快坏处是它需要“采样保持”也就是在转换前把输入电压冻结在一个采样电容上如果信号源内阻太大电容充不满测量就会偏低或者抖动。规格表我整理了一份方便对照参数数值ADC 位数12 位分辨率0-4095参考电压3.3V单个 LSB 电压3.3 / 4096 ≈ 0.806mV可用外部引脚通道ADC0-GPIO26、ADC1-GPIO27、ADC2-GPIO28内部通道ADC3 温度传感器、ADC4 接 AGND校准用最大采样率约 500kSPS数据手册标称这里最容易被忽略的是Pico 虽然名义上有 5 个 ADC 通道但真正可以从引脚引出的模拟输入只有 GPIO26、27、28 三个。ADC3 是芯片内部的温度传感器ADC4 在 Pico 开发板上已经固定接到了 AGND用来做偏移误差校准。如果你在代码里写machine.ADC(3)或者machine.ADC(4)得到的数据并不是一个外部电压。所以设计电路时默认你有且只有三个可用的模拟输入脚。1.2read_u16()为什么返回 0-65535 而不是 0-4095用 MicroPython 操作 Pico 的 ADC最常见的困惑就是import machine adc machine.ADC(26) print(adc.read_u16())输出范围是 0-65535而不是 12 位 ADC 应该有的 0-4095。这不是板子坏了也不是 MicroPython 把底层 ADC 换成了 16 位而是 MicroPython 为了统一不同开发板 ADC 的返回语义把底层结果扩大到了 16 位返回。你可以理解为12 位原始结果左移 4 位变成了 16 位4095 × 16 65520接近 65535。我在实际代码里通常这样处理raw_16 adc.read_u16() raw_12 raw_16 4 # 恢复 12 位原始值 voltage raw_16 * 3.3 / 65535 # 换算电压这样做的好处是当你想看真实 ADC 分辨率时用raw_12当你想控制同样是 16 位语义的 PWM 时直接用raw_16减少一次映射。两套量纲各留一份后面写联动控制会非常顺手。1.3 输入阻抗对采样精度的影响SAR ADC 的一个隐蔽坑在于输入阻抗。ADC 采样时内部采样电容需要在采样窗口内充到输入电压。如果信号源是高阻抗的充电时间常数变大采样电容最终充不满读出来的值就会偏低。用 10K 电位器时抽头位置的等效源阻抗大约等于上半段和下半段电阻的并联值。旋到中间时10K 电位器等效输出阻抗约 2.5K这个量级对 Pico 的 ADC 是完全友好的。但如果拿 1M 电位器来做这个实验波形就会明显不稳定数值不光偏低还会伴随随机抖动。所以这个实验固定用 10K 电位器不是为了偷懒是为了让初学者避开一个可能根本意识不到的硬件坑。2. 电路搭建电位器和 LED 该接在哪限流电阻怎么算2.1 元器件清单和一个合理的接线表这次的元器件非常简单我列一下树莓派 Pico 开发板一块10K 单圈旋转电位器一个普通 5mm 红色 LED 一个220Ω 限流电阻一个面包板一块杜邦线若干接线表如下器件引脚接 Pico 引脚电位器 固定端 1任意一个外端3V3OUT电位器 固定端 2另一个外端GND电位器 滑动端中间抽头GPIO26ADC0LED 阳极长脚GPIO15LED 阴极短脚GND限流电阻串联在 GPIO15 和 LED 阳极之间任选一侧串入为什么 LED 放在 GPIO15 而不是 GPIO25GPIO25 是板载 LED你当然也可以拿它来做亮度控制实验但用 GPIO15 的好处是你会被迫把“板载 LED”和“外接 LED”分开代码和电路更接近真实项目的结构。另外 GPIO15 没有和 ADC 通道冲突离 GPIO26 右侧引脚区也比较近面包板上走线会很整齐。2.2 LED 限流电阻的取值计算不能拍脑袋LED 不能直接接 3.3V否则电流过大容易烧毁。红色 LED 正向压降通常在 1.8-2.1V 左右。如果用 220Ω 电阻串联进回路那么电阻两端电压约为 3.3V - 2.0V 1.3V流过 LED 的电流约 1.3V / 220Ω ≈ 6mA。这个电流足够让普通 LED 清晰发光而且对 Pico 的 GPIO 来说负载很轻。如果你换成蓝色或者白色 LED正向压降接近 3V同样的 220Ω 电阻下电流会小很多甚至接近熄灭。所以换 LED 颜色时限流电阻也要跟着换这是一个很容易被忽略的细节。3. 代码分三步走ADC 读取、PWM 调光、软件滤波3.1 第一步裸读 ADC确认电位器输出是线性的先用一段最基础的代码把数据读出来不要急着接 LED避免问题叠加。import machine import utime adc machine.ADC(26) while True: raw_16 adc.read_u16() raw_12 raw_16 4 voltage raw_16 * 3.3 / 65535 print(raw_16, raw_12, round(voltage, 4)) utime.sleep(0.1)用 Thonny 运行后旋转电位器正常情况下应该看到三个数值跟随旋转角度平滑变化。如果旋到两端读数不是 0 和 65535而是卡在某个小范围极有可能是接线问题而不是 ADC 问题。我遇到过旋到底读数还剩一百多的情况检查发现是接地端松了抽头浮空ADC 悬空输入在读取浮空电压。这里有个关键提示ADC 输入脚不要悬空不接传感器时至少用一个电阻拉到 GND 或 3.3V否则读数会随机漂移。3.2 第二步用 PWM 让 LED 亮度跟随电位器确认 ADC 读数正常后加入 GPIO15 的 PWM 输出import machine import utime adc machine.ADC(26) led machine.PWM(machine.Pin(15)) led.freq(1000) led.duty_u16(0) while True: value adc.read_u16() led.duty_u16(value) print(value) utime.sleep(0.05)这里直接使用read_u16()的值作为 PWM 占空比是因为 MicroPython 的 PWM 设置接口duty_u16()同样接收 0-65535两边的语义刚好对齐。省掉了一次比例换算逻辑更简洁。PWM 频率我选择 1000Hz。人眼对超过 100Hz 的亮度变化基本没有闪烁感1000Hz 既能保证肉眼无闪烁功耗也不会太高。如果你将来用同样的思路驱动电机频率要重新按电机特性选LED 不需要那么讲究。3.3 第三步加滑动平均滤波解决波形毛刺ADC 裸读数直接传给 LED低速旋转时能明显看到亮度在某个角度区间轻微抖动。这不一定是电位器坏了更多是环境噪声耦合到模拟引线上。最直接的解决办法是硬件加一个 RC 低通滤波但为了先验证问题我通常会先上软件滤波。import machine import utime adc machine.ADC(26) led machine.PWM(machine.Pin(15)) led.freq(1000) buf_size 10 buf [] while True: value adc.read_u16() buf.append(value) if len(buf) buf_size: buf.pop(0) avg sum(buf) // len(buf) led.duty_u16(avg) print(avg, value) utime.sleep(0.02)滑动平均的原理很简单维护一个固定长度的队列每次取队列平均值。这个滤波对白噪声型毛刺非常有效但代价是延迟。buf_size为 10采样间隔 20ms那么滤波窗口约 200ms也就是说快速旋转电位器时LED 亮度会滞后约 0.2 秒。这个延迟对于调光灯效没问题但如果你要做一个需要快速响应的控制系统就要缩短窗口比如 4-5 点延迟降到 0.1 秒以内噪声抑制能力稍微弱一些但手感好很多。如果遇到的是偶发强脉冲干扰滑动平均反而不如中位滤波。中位滤波取队列里排序后的中间值对尖峰更鲁棒但计算量略大。Pico 跑这两种滤波都毫无压力具体用哪种取决于你面对的噪声类型。4. SerialPlot 串口波形把 ADC 数值变成看得见的实时曲线4.1 为什么需要波形调试而不是只看串口数字串口终端打印一堆数字你很难直观感受“数值到底稳不稳”。人眼看一串数字跳动几百个 LSB和看一条曲线上下波动完全是两种体验。SerialPlot 是一个轻量的跨平台串口波形显示软件它的工作方式是读取串口发来的文本行一行里如果是空格或逗号分隔的多个数值就自动解析成多通道数据并画成实时曲线。用它来观察 ADC 波形比单纯打印数字高效得多。我还经常拿它来调试其它传感器比如光敏电阻、热敏电阻甚至是 IMU 的原始数据。搭一次环境后面可以反复用。4.2 配置文件格式与 MicroPython 代码的配合SerialPlot 默认按文本模式解析数据。每行数据以换行符结束一行里可以有多个数值数值之间用空格、逗号或制表符分隔。SerialPlot 会根据一行里值的个数自动创建对应数量的曲线。打开 SerialPlot 后在右侧面板设置串口参数选择 Pico 对应的 COM 端口波特率选 115200数据位 8停止位 1无校验。这里有个细节Pico 通过 USB 连接电脑时在电脑端枚举出来的是虚拟串口波特率设置实际上不影响 USB CDC 传输但为了匹配调试习惯统一填 115200 没有坏处。配套的 MicroPython 代码里最关键的一条纪律是主循环只输出固定格式的数据行不要混入任何调试文本。比如上面带滑动平均的代码里print(avg, value)这一行输出两个空格分隔的数字SerialPlot 就会识别为两通道数据。第一通道是滤波后的均值第二通道是原始 ADC 读数。一屏里同时显示两条曲线滤波效果一目了然。如果你想看的是电压值而不是 ADC 整数可以这样输出print(avg * 3.3 / 65535, value * 3.3 / 65535)然后在 SerialPlot 的坐标设置里把 Y 轴范围设成 0-3.3V。我在实际调试中更喜欢同时看原始 ADC 整数和电压值因为原始整数能直接反映 LSB 抖动幅度电压值则方便和其他仪器读数对照。4.3 SerialPlot 不工作时的排查思路连接 SerialPlot 常见的问题无非这几种端口被占用。如果 Thonny 或者其他串口工具还开着COM 口被占用SerialPlot 会打开失败。先关掉其它占端口的程序。串口数据乱码或者曲线不更新。检查代码里是不是在某些分支打出了非数字内容。MicroPython 运行时报错信息也会输出到串口一旦混入SerialPlot 解析就会卡住。换行符问题。Windows 下 SerialPlot 通常能自动识别\n但如果你在 Thonny 里看到输出正常而 SerialPlot 不显示可以确认一下 Thonny 是否还连着该串口同一时间只能有一个程序打开虚拟串口。4.4 从波形能读出哪些信息接上 SerialPlot 后旋转电位器你会看到类似“三角波”的曲线。缓慢旋转时曲线平滑上升或下降快速旋转时曲线呈现陡峭台阶。如果波形上有大量高频毛刺说明环境干扰比较明显如果某几个角度反复出现跳变可能是电位器碳膜磨损属于机械噪声。滤波后的波形和原始波形同时显示时你能直观观察到滑动平均的延迟。两个波形之间横向错开的那段时间就是滤波窗口带来的相位滞后。这个现象在数值终端里是看不出来的但波形图上一眼就能看懂。5. 实测中的坑与调试心得从接线到波形显示的完整排错清单5.1 电位器引脚顺序的方向坑电位器三个引脚是一个圆弧排列1、2、3但不同厂家可能方向和定义不一样。你只要记住中间抽头必须接 ADC另外两个固定端头接电源和 GND。如果发现旋转方向是反的交换接 3V3 和 GND 的两根线即可抽头永远不要接电源或 GND否则 ADC 输入会被钳位在固定电平读数不随旋钮变化。5.2 USB 供电带来的 50Hz 纹波用电脑 USB 口供电时波形上偶尔能看到周期性波浪纹频率接近交流电的 50Hz 或 100Hz。这种情况源头通常在供电而不是 ADC。可以尝试换一根短 USB 线或者把 Pico 的 3V3 引出给传感器但一定要和 Pico 共地。如果条件允许用锂电池或者独立稳压电源供电纹波会小很多。我实测下来电脑前置 USB 口和机箱后置 USB 口的供电质量都有差异换接口有时比改代码更有效。5.3 PWM 亮度阶梯感和非线性映射在低速旋转电位器时我最早发现 LED 亮度有肉眼可见的阶梯感中低亮度区间尤其明显。原因有两层一是 ADC 低位抖动会让 PWM 占空比微微突变二是人眼对低亮度的变化比对高亮度的变化更敏感同样的占空比变化在低亮度段被感知得更明显。软件滑动平均能解决一部分抖动问题但阶梯感仍然存在这时候可以用非线性映射让亮度变化更自然duty (value * value) // 65535 led.duty_u16(duty)平方曲线让低亮度段的占空比变化变缓亮度过渡更平滑。我试过之后发现电位器转到一半时亮度明显低于线性映射整体视觉更柔和。缺点是如果你想要“线性亮度可预测”的调光方式非线性映射会引入额外的心理物理对应关系需要按使用场景取舍。5.4 滤波器延迟对响应速度的影响这个项目里 LED 亮度对延迟不敏感10 点滑动平均可以放心用。但如果你换到舵机或者电机控制200ms 延迟会直接体现在手感上整个系统会变得粘滞。控制器项目中优先用 3-5 点窗口或者只在必要时开滤波。我在控制实验里通常先看原始波形判断噪声量级再决定滤波强度而不是一上来就默认开启重滤波。5.5 关于 MicroPython REPL 粘贴长代码的碎碎念用 Thonny 往 Pico 的 REPL 里粘贴多行代码如果之前已经运行过某个程序很容易出现缩进错乱或者代码被拆成多段执行的情况报各种莫名其妙的IndentationError。我的建议是不要直接在 REPL 里粘贴长程序而是把代码保存为main.py然后在 Thonny 里运行文件。这样无论是代码保存、重启上电自动运行还是排查语法错误都更接近正式项目的流程。踩过几次坑之后我对这类“入门级”实验的看法是电位器 ADC LED 看起来简单但它是理解整套模拟量采集链路的最佳载体。硬件上涉及传感器输入、阻抗匹配、限流计算代码上涉及数据转换、滤波、PWM 控制调试上又涉及串口波形观察。这一圈走通之后后面再接光敏电阻、热敏电阻、土壤湿度传感器本质上都是同一个套路把物理量变成电压把电压变成 ADC 数字把数字变成可读信息或控制量。做完这个实验我最大的体会是不管采样后边的算法多复杂先用 SerialPlot 看一遍原始波形永远是第一步很多藏着的数据问题光靠数字终端真的发现不了。