ARTICLE DETAIL

资讯详情

深耕郑州网站建设与运营推广的一线实战洞察。

树莓派Pico加旋转编码器:从转盘小电视到PWM舵机控制实战

树莓派Pico加旋转编码器:从转盘小电视到PWM舵机控制实战 从 DF 创客周刊 Vol.182 里看到“赛博朋克全息时钟”“树莓派 Pico 加个转盘转出一台小电视”“垃圾桶改造成联网空气净化器”这三个项目后我最感兴趣的不是外观上的酷而是它们背后都指向同一个问题树莓派 Pico 这种几块钱到几十块钱的微控制器到底能把日常硬件玩出多少花样如果只点灯、读传感器很多人的 Pico 买回来就吃灰了。本文以“转盘小电视”为主项目完整拆解树莓派 Pico 读取旋转编码器、驱动 OLED 屏幕、输出 PWM 控制舵机的全过程。即使你没有做过硬件开发只要会一点 Python 基础也能照着接线、烧录、跑起来。1. 项目背景从创客周刊里的三个项目说起1.1 为什么这三个项目都值得拆解赛博朋克风格的“全息时钟”听起来很复杂但刨掉霓虹灯外壳后核心往往只是“时钟显示 特殊视觉效果”。转盘小电视则更有趣它把一个旋转编码器当作交互旋钮通过转动旋钮切换屏幕上的画面从外观看就像在“换台”。垃圾桶净化器看起来是生活小发明内部本质是“传感器采集 风扇控制 Pico W 联网上报”。这三类项目正好覆盖了树莓派 Pico 最常见的三种能力GPIO 输入与中断读取按钮、旋转编码器。驱动屏幕与显示I2C OLED、SPI LCD、LED 点阵。PWM 输出与电机控制舵机、风扇、呼吸灯。如果能把这条链路完整走一遍以后再做其他 Pico 项目基本就是“换传感器、换输出设备”的组合题。1.2 树莓派 Pico 和 Pico W 的区别Raspberry Pi Pico 使用的是 RP2040 芯片后面又推出了带 Wi-Fi 的 Pico W。Pico W 在引脚兼容 Pico 的基础上增加了 CYW43439 无线芯片可以联网做 MQTT、HTTP 上报。本文主项目使用普通 Pico 就够了最后聊空气净化器时会顺带介绍 Pico W 的联网方案。无论哪一块板子开发方式都主要有两种C/C SDK性能强适合嵌入式专业开发。MicroPython上手快适合原型验证、教学和快速做小项目。对于本文这种“一个下午跑通”的教程MicroPython 是最合适的选择。代码可以直接在 REPL 里调试改完重新运行即可不必反复编译烧录。1.3 旋转编码器为什么是“转盘类项目”的灵魂硬件旋钮有电位器和旋转编码器两种常见方案。电位器输出的是模拟电压需要 ADC 读取旋转编码器输出的是方波脉冲通过 GPIO 判断方向和步数。编码器最大的好处是“每转一格有明确的步进感”特别适合做菜单选择、频道切换、音量调节这类交互。“树莓派 Pico 加个转盘转出一台小电视”这个项目正是把旋转编码器变成换台旋钮OLED 屏幕模拟电视画面。转动旋钮频道号随之变化。如果再配上不同频道不同图案就像一台真正的老式旋钮电视。2. 硬件准备与开发环境2.1 硬件清单本文核心项目需要的硬件如下硬件说明Raspberry Pi Pico 开发板普通版即可旋转编码器模块常见 KY-040 或 EC11 模块含 CLK、DT、SW 引脚0.96 寸 I2C OLED 屏幕SSD1306 驱动128x64 分辨率面包板和杜邦线用于快速接线SG90 舵机实战二使用可选Micro USB 数据线给 Pico 供电和烧录程序如果你手上是 Pico W也可以直接用引脚差异不大只需在联网项目里额外处理 Wi-Fi 配置。2.2 固件烧录与开发工具在使用 MicroPython 之前要给 Pico 烧录 MicroPython 固件。步骤如下。第一步在电脑上打开树莓派官方文档页面下载对应 Pico 的 MicroPython UF2 文件。第二步按住 Pico 板子上的 BOOTSEL 按钮用 USB 线连接电脑。第三步松开 BOOTSEL电脑会出现一个名为RPI-RP2的 U 盘。第四步把下载的 UF2 文件直接拖入这个 U 盘Pico 会自动重启并进入 MicroPython 模式。开发工具推荐 Thonny。它内置 MicroPython 解释器插件能识别串口、上传文件、打开 REPL 终端。首次使用时在 Thonny 右下角选择解释器为“MicroPython (Raspberry Pi Pico)”并选择对应的串口。如果识别不到优先检查 USB 驱动和数据线是否支持数据传输。2.3 OLED 驱动库的准备SSD1306 OLED 在 MicroPython 中并不是内置驱动需要把官方驱动库ssd1306.py上传到 Pico 文件系统。操作也很简单在 Thonny 中新建一个 Python 文件。从 MicroPython 官方驱动仓库复制ssd1306.py内容。保存到 Pico 根目录文件名保持ssd1306.py。这样后面代码里from ssd1306 import SSD1306_I2C才能正常导入。3. 核心原理拆解编码器、PWM 与显示3.1 旋转编码器的工作原理旋转编码器内部有两个触点一般记为 A 相和 B 相。旋转时A、B 两相会输出有一定相位差的方波。通过判断相位关系可以知道旋转方向通过统计脉冲数量可以知道旋转了多少格。如果只检测 CLK 引脚电平变化再读取 DT 引脚电平就可以判断方向。常见判断方法如下如果 CLK 发生变化时DT 与 CLK 状态相同可以认为正转。如果 DT 与 CLK 状态不同可以认为反转。这里的方向判断结果可能因为具体模块或接线而相反。如果发现代码里的“正转”实际是“反转”有两种处理办法交换 CLK 和 DT 两根线或者把判断条件里的等号改成不等号。旋转编码器还有一个常见问题叫“抖动”。机械触点闭合、断开的瞬间会产生毛刺可能一次转动被误判成多次。因此程序中需要做去抖处理比较简单的方法是两次状态变化之间至少间隔 2ms 才继续计数。3.2 PWM 与舵机控制的关系舵机的控制并不是通过给高电平和低电平简单驱动而是需要频率为 50Hz 的 PWM 信号。PWM 的“脉宽”决定了舵机转到的角度常见范围是 0.5ms 到 2.5ms对应 0 度到 180 度左右。树莓派 Pico 的 PWM 频率通过freq()设置占空比有两种常见设置方式duty_u16()使用 0 到 65535 表示占空比。duty_ns()直接使用纳秒表示高电平脉宽但是否支持取决于 MicroPython 版本和平台。为了让代码兼容性更好建议统一用duty_u16()。以 50Hz 为例一个周期是 20ms也就是 20000000ns。0.5ms 占 2.5%2.5ms 占 12.5%。对应到 0 到 65535 的范围可以算出最小占空比65535 x 0.025 约等于 1638最大占空比65535 x 0.125 约等于 8192把角度从 0 到 180 映射到 1638 到 8192 之间就能驱动多数常见舵机。不同舵机的脉宽范围略有差异实际使用时可能会出现转不到 0 度、转不到 180 度的情况这时需要按舵机手册微调范围避免长期堵转损坏舵机。3.3 I2C OLED 的显示逻辑0.96 寸 OLED 使用 I2C 通信数据传输只需要 SDA 和 SCL 两根线。MicroPython 中通过machine.I2C初始化然后交给SSD1306_I2C驱动类。初始化代码如下from machine import Pin, I2C from ssd1306 import SSD1306_I2C i2c I2C(0, sdaPin(0), sclPin(1), freq400000) oled SSD1306_I2C(128, 64, i2c)I2C(0)表示使用 Pico 的 I2C0 控制器sda和scl可以映射到不同的 GPIO 引脚。上面代码里 SDA 接 GP0SCL 接 GP1这也是最常见的接法。SSD1306_I2C的参数分别是宽、高、I2C 对象。OLED 显示内容时有个容易忽略的细节所有绘制操作都先缓存在内存里必须调用oled.show()才会真正刷新到屏幕。如果你发现代码运行了但屏幕没变化优先检查是否漏了show()。4. 实战一树莓派 Pico 加转盘做一台小电视4.1 项目结构设计这个小电视项目的目标是旋转编码器充当频道调节旋钮OLED 显示屏模拟电视机画面每转动一格频道号在 1 到 8 之间切换。为了看起来更像一台电视屏幕中间会绘制一个复古的电视测试卡图案。功能拆解如下开机默认显示频道 1。顺时针旋转频道号加 1。逆时针旋转频道号减 1。频道号超过 8 后回到 1低于 1 后跳到 8。按下编码器按钮屏幕显示“雪花屏”静置画面再次按下恢复换台模式。4.2 接线表接线时需要格外注意 OLED 和编码器的 VCC/GND。它们的 VCC 可以接 Pico 的 3V3 引脚GND 接 GND。OLED 的 I2C 引脚不能和编码器冲突这里分配如下OLED/编码器引脚Pico 引脚OLED SDAGP0OLED SCLGP1OLED VCC3V3OLED GNDGND编码器 CLKGP10编码器 DTGP11编码器 SWGP12编码器 3V3编码器 GNDGND如果你手上的旋转编码器模块已经带了上拉电阻Pico 引脚使用Pin.PULL_UP也不会有问题。如果使用的是裸 EC11 编码器则必须把 CLK、DT、SW 通过上拉电阻接到 3.3V。4.3 编写主程序代码下面给出完整代码。文件可以命名为pico_tv.py上传到 Pico 后运行。from machine import Pin, I2C import time from ssd1306 import SSD1306_I2C # 引脚定义 CLK_PIN 10 DT_PIN 11 SW_PIN 12 clk Pin(CLK_PIN, Pin.IN, Pin.PULL_UP) dt Pin(DT_PIN, Pin.IN, Pin.PULL_UP) sw Pin(SW_PIN, Pin.IN, Pin.PULL_UP) # OLED 初始化 i2c I2C(0, sdaPin(0), sclPin(1), freq400000) oled SSD1306_I2C(128, 64, i2c) CH_MAX 8 channel 1 last_clk clk.value() last_ms time.ticks_ms() def set_channel(ch): global channel if ch 1: channel CH_MAX elif ch CH_MAX: channel 1 else: channel ch def draw_testcard(ch): oled.fill(0) oled.text(CH {}.format(ch), 24, 2) # 外框 oled.rect(20, 16, 88, 42, 1) # 对角线 oled.line(20, 16, 108, 58, 1) oled.line(108, 16, 20, 58, 1) # 中间圆点模拟旧电视测试图 for i in range(6): oled.pixel(64 i, 37, 1) oled.pixel(64 - i, 37, 1) oled.pixel(64, 37 i, 1) oled.pixel(64, 37 - i, 1) oled.show() def draw_snow(): oled.fill(0) for i in range(20): x (i * 37) % 128 y (i * 23) % 64 oled.rect(x, y, 3, 2, 1) oled.text(NO SIGNAL, 20, 54) oled.show() # 首次绘制 draw_testcard(channel) while True: current_clk clk.value() # 检测 CLK 电平变化 if current_clk ! last_clk: now_ms time.ticks_ms() # 简单去抖 if time.ticks_diff(now_ms, last_ms) 2: last_ms now_ms if dt.value() ! current_clk: # 方向需要根据实际接线调整 set_channel(channel 1) else: set_channel(channel - 1) draw_testcard(channel) print(channel:, channel) last_clk current_clk # 检测按钮 if sw.value() 0: time.sleep_ms(20) if sw.value() 0: draw_snow() while sw.value() 0: time.sleep_ms(10) # 等松手后恢复换台界面 draw_testcard(channel) time.sleep_ms(2)代码中值得注意的地方有几个。第一主循环不断读取 CLK 电平和上一次保存的电平比较。如果发生了变化说明编码器转动了。每次变化后读取 DT 电平判断方向。第二去抖逻辑限制两次有效变化间隔大于 2ms可过滤机械抖动。第三按钮检测采用“按下后延时再确认”的方式进一步排除抖动。第四显示绘制集中在draw_testcard中屏幕内容只会在频道改变时刷新。4.4 运行与验证把代码保存到 Pico在 Thonny 中点击运行。如果接线正确、驱动库导入成功OLED 会显示“CH 1”和一个测试卡图案。手动旋转编码器OLED 上的频道号会在 1 到 8 之间变化Thonny 的 Shell 窗口会同步打印频道信息。如果 OLED 没有显示内容优先用下面的 I2C 扫描程序确认屏幕地址import machine i2c machine.I2C(0, sdamachine.Pin(0), sclmachine.Pin(1), freq400000) print(i2c.scan())正常会输出类似[60]的列表表示屏幕地址是 0x3C。如果输出为空说明接线或供电有问题。如果旋转编码器方向反了也就是顺时针转动频道反而减小只需要交换代码中的方向判断条件也就是把dt.value() ! current_clk改成dt.value() current_clk或者直接把 CLK 和 DT 两根杜邦线互换位置。4.5 如何让它更像一台“电视”目前代码只是换了频道号效果还比较朴素。想让效果更像电视可以在主循环中加入动态扫描线效果。思路是每隔一段时间在屏幕最下方绘制一行亮点模拟老电视的扫描线。由于屏幕尺寸只有 128x64显示内容不宜太复杂否则视觉会很拥挤。更进一步可以为 8 个频道各准备一组“节目画面”比如用不同的线条、矩形和文字组合表示不同节目。转动旋钮时不再只是数字变化而是整幅画面切换。这类优化可以放到后续扩展中练习。5. 实战二树莓派 Pico 控制舵机5.1 舵机控制为什么常被单独拿出来讲“树莓派 Pico 控制舵机”是社区里很热门的话题。很多机器人小车、云台、机械臂项目第一步都是要让舵机按指定角度转动。舵机的控制不像普通电机那样一直给电就能转它需要一个稳定的周期脉冲直接使用 GPIO 高电平输出是驱动不了的必须借助 PWM 外设。Pico 的每一个引脚经过配置后都可以输出 PWM。在 MicroPython 中只要把引脚包装成machine.PWM对象就可以调节频率和占空比。5.2 接线说明使用时需要区分舵机的三根线。以 SG90 为例舵机线色作用接法红色电源正极外部 5V 电源正极棕色/黑色电源负极外部电源负极同时接 Pico GND橙色/黄色信号线Pico GP15这里特别强调舵机工作电流比普通 LED 大得多不建议直接从 Pico 的 3V3 引脚取电。Pico 的 3.3V 输出能力有限当舵机堵转或快速转动时电流可能瞬间拉高导致板子复位。稳妥的方案是给舵机单独接 5V 电源并把电源负极和 Pico 的 GND 连接到一起形成共地。5.3 角度映射与 PWM 设置先写一个角度转占空比的函数。假设舵机使用 50Hz PWM周期为 20ms脉宽范围从 0.5ms 到 2.5ms那么占空比最小是 2.5%最大是 12.5%。用duty_u16表示时0 到 65535 对应的数值范围就是 1638 到 8192 左右。通用映射代码如下from machine import Pin, PWM servo PWM(Pin(15)) servo.freq(50) MIN_DUTY int(65535 * 0.025) MAX_DUTY int(65535 * 0.125) def angle_to_duty(angle): if angle 0: angle 0 if angle 180: angle 180 return MIN_DUTY int((MAX_DUTY - MIN_DUTY) * angle / 180) def set_angle(angle): servo.duty_u16(angle_to_duty(angle))angle_to_duty做的是线性映射。角度 0 对应最小占空比90 度对应中点180 度对应最大占空比。不同舵机可能不是严格的 0.5ms 到 2.5ms 范围第一次上电时最好先给一个中间角度测试确认舵机不会抖动或堵转。5.4 用旋转编码器控制舵机角度把第 4 节的旋转编码器读取逻辑和舵机控制组合起来就能实现“转动编码器舵机跟着转”的效果。完整代码如下from machine import Pin, PWM import time CLK_PIN 10 DT_PIN 11 PWM_PIN 15 clk Pin(CLK_PIN, Pin.IN, Pin.PULL_UP) dt Pin(DT_PIN, Pin.IN, Pin.PULL_UP) servo PWM(Pin(PWM_PIN)) servo.freq(50) MIN_DUTY int(65535 * 0.025) MAX_DUTY int(65535 * 0.125) angle 90 last_clk clk.value() last_ms time.ticks_ms() def clamp(value, low, high): if value low: return low if value high: return high return value def set_angle(angle): duty MIN_DUTY int((MAX_DUTY - MIN_DUTY) * angle / 180) servo.duty_u16(duty) set_angle(angle) while True: current_clk clk.value() if current_clk ! last_clk: now_ms time.ticks_ms() if time.ticks_diff(now_ms, last_ms) 2: last_ms now_ms if dt.value() ! current_clk: angle 2 else: angle - 2 angle clamp(angle, 0, 180) set_angle(angle) print(angle:, angle) last_clk current_clk time.sleep_ms(2)每次转动一格角度变化 2 度。如果希望能精细控制可以把步长改成 1。如果发现旋钮方向和舵机转动方向相反同样交换 CLK、DT 接线或调整方向判断条件。运行程序后会听到舵机转动时轻微的“咔哒”声。转动旋钮到两端时舵机会停在 0 度或 180 度附近。如果在极限位置听到持续的“嗡嗡”声说明舵机在堵转需要立刻断电检查角度范围配置是否超出舵机实际行程。6. 进阶玩法全息时钟与联网空气净化器6.1 赛博朋克“全息时钟”的实现思路海报上的“全息时钟”听起来很科幻但绝大多数桌面项目用的是伪全息方案。一种常见做法是利用半透明亚克力板或者玻璃将屏幕上的画面通过反射呈现在空中这种光学会叫“佩珀尔幻象”。另一种做法是旋转 LED 灯条利用视觉暂留效果在空中“画”出字符。如果用树莓派 Pico 来复刻成本最低的路线是用 Pico 驱动一块小型 LCD 或 LED 点阵。使用 DS3231 或 Pico 内部 RTC 获取时间。在代码里把时间字符串渲染成霓虹风格字体。再配合半透明亚克力外壳做出悬浮显示效果。从工程角度说真正的难点不在显示而在外壳结构和光学反射角度。屏幕画面必须做镜像处理否则反射出来的文字是反的。如果你打算用点阵屏做伪全息风扇还需要额外处理 POV 扫描的刷新同步难度会明显提升。6.2 用垃圾桶改造成联网空气净化器把一个普通垃圾桶改成空气净化器核心思路很简单垃圾桶作为外壳内部放一个风扇和过滤材料再增加 PM2.5 传感器。树莓派 Pico W 负责读取传感器数据根据空气质量自动开关风扇并把数据发送到局域网或物联网平台。硬件路线可以这样设计传感器使用 PMS5003 这类串口输出的颗粒物传感器。风扇使用 5V 或 12V 直流风扇通过 MOSFET 或继电器控制。联网Pico W 连接 Wi-Fi发布 MQTT 消息。本地显示OLED 显示 PM2.5 数值和风扇状态。这个项目强调安全边界。改造垃圾桶时不要直接改动 220V 市电线路。建议使用低压直流风扇和独立的 USB 电源适配器。所有接线必须做好绝缘传感器进风口和出风口的位置要避免短路风道否则净化效果会大打折扣。联网部分如果只是局域网调试可以先用 MQTT 的本地 Broker如果要远程监控必须做好账号密码保护和权限隔离不要把无鉴权的端口直接暴露到公网。7. 常见问题与排查思路7.1 编码器读数跳变或方向不稳定旋转编码器最常见的现象是转一格频道跳好几格或者方向时好时坏。出现这类问题原因通常有三个使用了裸编码器但没有上拉电阻。代码里没有去抖。杜邦线接触不良或面包板松动。解决方案是检查 CLK、DT 是否都接了上拉电阻提高去抖时间重新插拔杜邦线。如果程序使用中断方式读取还需要注意不要在中断回调里做 OLED 刷新、print等耗时操作。7.2 OLED 白屏或不显示如果 OLED 屏幕只亮背景但不显示内容先运行 I2C 扫描代码确认能够检测到设备。如果扫描不到检查 OLED VCC 是否接 3.3V、GND 是否接对、SDA 与 SCL 是否接反。部分 OLED 模块需要 5V 供电实际以模块说明书为准。如果扫描到设备但内容不显示检查代码里是否调用了show()。SSD1306 驱动有页缓存机制所有绘制操作都发生在缓存中只有show()才会真正刷新。7.3 舵机不转或抖动舵机不转的原因可能是指令角度没有正确映射也可能是信号线接错。先检查舵机是否有“上电自锁”的轻微反应如果完全没反应检查电源和共地。舵机抖动通常意味着 PWM 频率不对或占空比不稳定。MicroPython 中设置 PWM 后如果后面频繁调用freq()可能导致波形抖动。建议在初始化时设置一次频率后续只通过duty_u16()修改占空比
返回列表