
1. 项目概述从旋钮到数字的魔法玩过音响上的音量旋钮或者台灯上的调光旋钮吗那种旋转时“咔哒”作响或者平滑顺滑的手感背后藏着一个非常基础的电子元件——电位器。本质上它就是一个可变的电阻。当我们把它接到树莓派 Pico 上旋转它Pico 就能“读懂”我们旋转的角度或位置并将其转换成一个具体的数字。这个项目就是实现这个“读懂”的过程它是连接物理世界模拟量和数字世界数字量最经典、也最入门的一扇门。为什么说它重要因为现实世界中绝大多数信号都是连续变化的模拟量温度、光照强度、声音大小、压力、角度……而我们的单片机、计算机只能处理离散的数字量。Pico 上那个叫做 ADC模数转换器的模块就是负责这项翻译工作的核心。通过读取电位器的值你实际上是在学习如何让 Pico 感知并量化一个连续变化的物理量。掌握了这个你就能轻松地让 Pico 根据光照自动调节屏幕亮度、根据温度控制风扇转速或者制作一个由旋钮控制速度的小车这才是嵌入式开发真正开始有趣的地方。这个项目非常适合刚拿到树莓派 Pico已经点过 LED、读过按键想要更进一步接触模拟信号处理的开发者。它所需的材料极少一块 Pico、一个电位器、几根杜邦线代码也简洁但背后涉及的 ADC 原理、电路连接和软件滤波思想却是后续无数高级项目比如机器人传感器数据采集、简易示波器、环境监测站的基石。我会带你从电路焊接开始到代码编写、数据观察最后深入聊聊如何让读取的数值更稳定可靠避开那些新手常踩的坑。2. 核心硬件解析与电路搭建2.1 认识三位主角Pico、电位器与ADC在动手连接之前我们必须先搞清楚手头这几个关键部件到底是什么以及为什么这样选。树莓派 Pico是我们的核心大脑。它基于 RP2040 双核微控制器价格亲民且性能足够。对于本项目我们最需要关注的是其GPIO 引脚中带有 ADC 功能的那些。Pico 有 3 个专用的 ADC 输入通道GP26、GP27、GP28它们连接到一个 12 位精度的 ADC 模块上。12 位意味着它可以将 0-3.3V 的输入电压划分为 2^12 4096 个不同的数字等级。所以理论上它的电压分辨率是 3.3V / 4096 ≈ 0.0008V0.8mV。这为我们精确读取电位器分压值提供了硬件基础。电位器我们通常选用最常用的旋转式三引脚电位器例如 B10K即阻值 10kΩ。它有三个引脚两侧的固定端通常标记为 A 和 B和一个中间的滑动端Wiper。其原理很简单在 A 和 B 之间是一个总阻值为 10kΩ 的电阻片滑动端 W 可以在上面移动。当旋转旋钮时W 与 A 之间的电阻值会改变同时 W 与 B 之间的电阻值也会相应改变两者之和恒等于总阻值。我们将 A 接电源3.3VB 接地GND那么 W 端的电压就会随着旋转在 0V 到 3.3V 之间线性变化。这就是我们提供给 Pico ADC 引脚测量的模拟电压信号。ADC模数转换器是本次的“翻译官”。它的任务就是持续地对 GP26我们即将使用的引脚上的电压进行采样并将这个连续的电压值按照固定的规则12位精度0-3.3V量程转换成一个 0 到 65535 之间的整数。这里有个细节MicroPython 为了编程方便将 12 位的原始值0-4095左移了 4 位映射到了 0-65535 的范围。所以你在代码里读到的数值范围是 0-65535但实际硬件精度依然是 12 位。记住这个映射关系对后续计算实际电压值很重要。2.2 电路连接详解与安全须知连接电路非常简单但正确的连接是成功和安全的前提。请务必在 Pico未通电的情况下进行焊接或插线。所需材料清单树莓派 Pico 开发板 x110kΩ 旋转电位器 x1杜邦线母对母x3可选面包板 x1方便连接避免焊接接线步骤与原理电位器供电将电位器两侧的固定引脚一端连接到 Pico 的3V3(OUT)引脚第36脚这是 Pico 提供的 3.3V 电源另一端连接到 Pico 的任意一个GND引脚例如第38脚。这一步为电位器建立了电压基准一端是 3.3V高电平一端是 0V低电平。信号输出将电位器的中间滑动引脚Wiper用杜邦线连接到 Pico 的GP26引脚第31脚。GP26 是 ADC0 通道专门用于模拟输入。此时滑动引脚上的电压即我们想要测量的分压值就被引入了 Pico 的 ADC 模块。共地确保 Pico 和电位器使用同一个 GND 参考点这是所有电路正常工作的基础。我们已经在上一步完成了。重要安全提示绝对不要将电位器的两端直接接到VSYS5V输入和 GND 上然后再把中间引脚接到 GP26。Pico 的 ADC 引脚输入电压范围是 0~3.3V超过 3.3V 的电压可能会永久损坏 ADC 模块甚至整个芯片使用 3V3(OUT) 供电是最安全的选择。连接完成后你的电路可以这样理解一个由 3.3V 电源、电位器总电阻10kΩ构成的回路。滑动触点像一个可移动的分压点将 3.3V 的总电压“切”出一部分送给 GP26。旋转电位器就等于在改变这个分压点的位置。3. 软件环境配置与核心代码实现3.1 MicroPython 固件与开发工具选择Pico 的编程方式多样但对于快速原型开发和初学者MicroPython是绝佳选择。它语法简单交互性强无需复杂的编译环境。首先你需要确保你的 Pico 已经烧录了最新的 MicroPython 固件。去树莓派基金会官网下载.uf2格式的固件文件按住 Pico 上的 BOOTSEL 按钮的同时将其通过 USB 连接到电脑直到它出现像一个 U 盘一样的驱动器然后把固件文件拖进去Pico 会自动重启并完成烧录。开发工具我强烈推荐Thonny。它是一款对初学者极其友好的 Python IDE内置了 MicroPython 支持能自动识别连接的 Pico并提供一个交互式命令行REPL和文件管理器。安装 Thonny 后在右下角选择解释器为 “MicroPython (Raspberry Pi Pico)”并选择正确的串口你就可以开始编写和运行代码了。当然如果你习惯 VS Code 配合 Pico-Go 等插件或者使用命令行工具rshell、ampy也完全没问题。3.2 单次读取与连续监测代码解析代码的核心在于使用machine模块中的ADC类。下面我们从最简单的单次读取逐步构建一个实用的连续监测程序。基础单次读取import machine import utime # 初始化 ADC 对象指定引脚为 GP26 (ADC0) pot machine.ADC(26) # 单次读取并打印 value pot.read_u16() # 读取一个 0-65535 之间的无符号16位整数 print(“当前电位器数值”, value)machine.ADC(26)创建了一个绑定到 GP26 引脚的 ADC 对象。read_u16()方法执行一次 ADC 转换并返回映射后的 16 位值。运行这段代码在 Thonny 的 Shell 窗口里你会看到一个数字。旋转电位器再次运行数字会改变。进阶连续监测与电压换算单次读取显然不够。我们更需要一个能持续读取、并显示更直观电压值的程序。import machine import utime # 初始化 pot machine.ADC(26) # 换算系数将读取的数值转换为实际电压 # 参考电压为 3.3V数值范围是 0-65535 conversion_factor 3.3 / 65535 print(“开始读取电位器值按 CtrlC 停止”) try: while True: # 1. 读取原始数值 raw_value pot.read_u16() # 2. 转换为电压值 (单位伏特 V) voltage raw_value * conversion_factor # 3. 也可以换算回更直观的 12 位原始值 (0-4095) adc_12bit raw_value 4 # 右移4位相当于除以16 # 打印所有信息 print(“Raw (0-65535): {:5d} | Voltage: {:.3f}V | ADC (0-4095): {:4d}”.format( raw_value, voltage, adc_12bit)) # 延时避免打印刷屏太快 utime.sleep(0.2) except KeyboardInterrupt: print(“\n程序已停止。”)这段代码构成了一个完整的监测循环。conversion_factor是关键它建立了数字值与真实电压之间的桥梁。打印信息包含了原始16位值、计算出的电压以及还原的12位ADC值这能帮助你从不同角度理解数据。utime.sleep(0.2)将读取频率控制在约 5Hz对于手动旋转电位器观察来说足够了。实操心得在 REPL 中运行此类循环程序时如何优雅退出除了使用CtrlC更好的实践是在循环条件中加入一个标志位或者通过读取一个按键状态来退出。对于 Thonny直接点击红色停止按钮有时会导致端口死锁CtrlC是最可靠的方式。4. 数据稳定性优化与滤波算法当你兴奋地运行上面的代码并旋转电位器时可能会发现一个现象即使手完全不动打印出来的数值最后几位也总是在跳变比如在 34567 和 34569 之间来回晃动。这不是你的电路有问题而是电子世界中的常态——噪声。4.1 理解ADC读数跳变的原因ADC 读数不稳定主要源于几个方面电源噪声Pico 的 3.3V 输出并非理想纯净的直线其本身有微小的纹波。热噪声电阻包括电位器内部的电子热运动会产生随机电压。电磁干扰周围的电源线、手机、电脑等都会辐射微弱的电磁波被导线像天线一样接收。ADC 本身的量化误差和噪声任何 ADC 芯片都有其固有的非理想特性。这些噪声叠加在我们真实的信号上导致单次采样值不可靠。我们的目标就是通过软件方法从这些波动的数据中提取出稳定、平滑的“真实”信号。4.2 软件滤波算法实战让数据“沉稳”下来滤波算法有很多这里介绍两种最实用、最易实现的你可以直接复制到代码中。方法一滑动平均滤波这是最简单有效的滤波方法。其原理是维护一个固定长度的数据队列每次新读数进来就挤掉一个最老的读数然后计算队列中所有数的平均值作为本次输出。它能有效平滑随机噪声。import machine import utime pot machine.ADC(26) conversion_factor 3.3 / 65535 # 滑动平均滤波参数 window_size 10 # 滤波窗口大小通常取 5-20 readings [] # 用于存储历史数据的列表 print(“启动滑动平均滤波...”) try: while True: raw_value pot.read_u16() # 将新数据加入列表 readings.append(raw_value) # 如果列表长度超过窗口大小移除最旧的数据 if len(readings) window_size: readings.pop(0) # 计算列表平均值只有当列表填满窗口后才开始输出稳定值 if len(readings) window_size: filtered_value sum(readings) / window_size voltage filtered_value * conversion_factor print(“滤波后 Voltage: {:.3f}V”.format(voltage)) else: # 窗口未填满时可以输出原始值或等待 print(“初始化滤波窗口...当前值: {:.3f}V”.format(raw_value * conversion_factor)) utime.sleep(0.05) # 采样间隔可以更短一些 except KeyboardInterrupt: print(“停止.”)窗口大小window_size的选择是关键值越大滤波效果越平滑但对信号变化的响应也越慢延迟越大。对于手动调节的电位器取 5-10 即可。你会立刻发现输出的电压值变得非常稳定小数点后第三位才可能缓慢变化。方法二中位值平均滤波这种方法更“强壮”能有效抵抗偶尔出现的、幅度很大的异常跳变脉冲干扰。它的步骤是连续采样 N 次去掉其中的最大值和最小值然后对剩下的 N-2 个值求平均。import machine import utime pot machine.ADC(26) conversion_factor 3.3 / 65535 sample_count 7 # 单次采样的次数建议取奇数如5,7,9 print(“启动中位值平均滤波...”) try: while True: # 1. 连续采样 sample_list [] for i in range(sample_count): sample_list.append(pot.read_u16()) utime.sleep_us(100) # 微秒级延时避免采样过于密集 # 2. 排序并去掉首尾最大值和最小值 sample_list.sort() trimmed_list sample_list[1:-1] # 去掉第一个和最后一个 # 3. 求平均值 filtered_value sum(trimmed_list) / len(trimmed_list) voltage filtered_value * conversion_factor print(“滤波后 Voltage: {:.3f}V”.format(voltage)) utime.sleep(0.1) except KeyboardInterrupt: print(“停止.”)这种方法计算量稍大但抗干扰能力极强。sample_count取 7 是一个很好的平衡点。在实际项目中如果传感器线很长环境干扰大我通常会优先使用这种方法。注意事项滤波在让数据稳定的同时必然会引入延迟。你需要根据项目的实时性要求来权衡。例如一个通过电位器实时控制电机转速的系统延迟必须非常小可能只使用很小的滑动窗口甚至不用滤波转而从硬件上优化如加滤波电容。而一个用于记录环境温度缓慢变化的系统则可以使用很强的滤波来获取极其平稳的数据。5. 项目应用拓展与高级主题成功读取一个稳定的电位器值这只是一个起点。这个简单的输入可以触发无数有趣的应用。5.1 基础应用控制LED亮度与舵机角度PWM调光LEDPico 的另一个强大功能是 PWM脉冲宽度调制它可以模拟模拟输出。我们可以用电位器的值直接控制一个 LED 的亮度。import machine import utime pot machine.ADC(26) led machine.PWM(machine.Pin(15)) # 假设LED接在GP15 led.freq(1000) # 设置PWM频率为1kHz while True: pot_value pot.read_u16() # 将电位器值0-65535映射到PWM占空比0-65535 led_duty pot_value led.duty_u16(led_duty) utime.sleep(0.01)旋转电位器LED 的亮度就会平滑变化。这里duty_u16()方法直接接受 0-65535 的范围所以映射非常简单。控制舵机角度标准舵机的控制信号是一个周期为20ms脉宽在0.5ms到2.5ms之间的PWM波。我们可以用电位器值来控制脉宽。import machine import utime pot machine.ADC(26) servo machine.PWM(machine.Pin(15)) servo.freq(50) # 舵机标准频率50Hz # 映射函数将输入值从一个范围线性变换到另一个范围 def map_value(x, in_min, in_max, out_min, out_max): return (x - in_min) * (out_max - out_min) // (in_max - in_min) out_min while True: pot_val pot.read_u16() # 将0-65535映射到舵机脉宽对应的占空比1638-8192对应0.5ms-2.5ms duty map_value(pot_val, 0, 65535, 1638, 8192) servo.duty_u16(duty) utime.sleep(0.02)这样你就做出了一个手动控制的舵机云台可以用来控制摄像头方向、机械臂关节等。5.2 进阶探索多路ADC与硬件优化多路ADC读取Pico 有多个ADC通道GP26, GP27, GP28。你可以同时接多个电位器或模拟传感器实现多参数控制。adc0 machine.ADC(26) # 电位器1 adc1 machine.ADC(27) # 电位器2 adc2 machine.ADC(28) # 光照传感器 while True: val1 adc0.read_u16() val2 adc1.read_u16() val3 adc2.read_u16() # 分别处理三个值... utime.sleep(0.1)需要注意的是RP2040的ADC是单路复用的虽然可以快速切换读取多路但无法实现真正的同步采样。对于要求不高的场景顺序读取完全足够。硬件滤波在软件滤波之前硬件上的一点点改进能极大减轻软件负担。最简单的办法是在 ADC 输入引脚GP26和 GND 之间并联一个 0.1uF104的陶瓷电容。这个电容就像一个微型水库可以吸收瞬间的电压毛刺让输入到 ADC 的信号更干净。对于大多数应用加上这个电容后你会发现原始数据的跳动幅度明显减小。这是成本最低、效果最显著的硬件抗干扰措施。校准与提高精度Pico 的 ADC 参考电压默认是内部产生的它并非绝对精确的 3.3V可能会有 ±0.1V 左右的偏差。如果项目对电压测量精度要求很高比如用于测量电池电压可以考虑使用一个外部精准的 3.3V 基准电压源芯片如 REF3033为电位器供电并连接到 Pico 的 ADC_VREF 引脚但 Pico 的该引脚未直接引出需修改板载连接操作较复杂。对于绝大多数创意项目内部的精度已经绰绰有余。6. 故障排查与调试心得即使按照步骤操作你也可能会遇到一些小问题。这里汇总了一些常见的情况和我的解决思路。6.1 常见问题速查表现象可能原因排查步骤与解决方案读数始终为 01. 电位器中间引脚未接对接到了固定端。2. ADC 引脚配置错误如错用了数字IO。3. 电位器两端接反滑动端电压始终为0。1. 用万用表通断档确认中间引脚。旋转时中间引脚对两端的电阻应变化。2. 检查代码machine.ADC(26)中的引脚编号是否正确。3. 交换电位器两侧引脚的接线。读数始终为最大值655351. 电位器中间引脚与 3.3V 端短路。2. ADC 引脚内部上拉被意外启用概率极低。1. 检查接线是否有误触。测量中间引脚对地电压旋转时应在 0-3.3V 间变化若始终接近3.3V则可能是与电源短路。2. 确保代码中未对引脚进行Pin.PULL_UP等操作。读数变化不线性或跳变剧烈1. 电位器本身质量差阻值变化不均匀。2. 电源噪声大或接触不良。3. 未使用滤波算法。1. 更换一个质量好的电位器如 Bourns, ALPS 品牌。2. 检查杜邦线连接是否牢固尝试在 Pico 的 3V3 和 GND 间加一个 10uF 的电解电容稳压。3.务必实施本章第4节的软件滤波这是标准操作。Thonny 无法连接 Pico / 报错1. Pico 未进入编程模式固件丢失。2. 驱动问题或串口被占用。3. USB 线仅供电无数据。1. 重新按住 BOOTSEL 键上电拖入 MicroPython 固件进行烧录。2. 重启 Thonny更换 USB 口关闭可能占用串口的其他软件。3. 使用一条已知良好的、带数据传输功能的 USB 线。ADC 读数有固定偏移ADC 的绝对精度误差。这是正常现象。如需精确测量可以进行两点校准测量一个已知的精准电压如 1.65V记录读数在代码中计算出一个校准系数进行补偿。6.2 调试思维与工具运用当问题出现时系统化的调试能帮你快速定位。化整为零分段验证不要一下子怀疑整个系统。首先用万用表测量电位器中间引脚的对地电压旋转旋钮看电压是否在 0~3.3V 平稳变化。如果这里就不对问题出在电路部分。如果这里正确但 Pico 读出的值不对问题就出在 Pico 或代码上。简化代码隔离问题编写一个最简单的、只读取 ADC 并打印的程序排除复杂逻辑和滤波算法的干扰。使用print(pot.read_u16())看最原始的数据。利用 REPL 交互式调试在 Thonny 的 ShellREPL里你可以逐行输入命令来测试。例如直接输入import machine; p machine.ADC(26); p.read_u16()这能最快验证 ADC 功能是否正常比运行整个脚本更直接。观察电源模拟电路对电源质量非常敏感。如果你的项目里还有电机、舵机等大电流设备务必为它们单独供电并与 Pico 的逻辑电源隔离共地即可否则电机启动的瞬间可能会拉低 Pico 的 3.3V导致 ADC 读数全部异常。最后分享一个我自己的心得在连接电位器这类纯模拟传感器时如果追求极高的稳定性除了软件滤波和并联小电容我会特意使用一根绞合线或屏蔽线来连接信号线GP26和地线GND并将它们紧密地绞在一起。这能极大地降低导线作为天线接收到的空间电磁干扰效果立竿见影。对于更复杂的系统ADC 的采样时机也要注意最好避开系统中大功率器件如无线模块发射时工作的瞬间可以通过合理的程序时序来控制。