1. 项目概述一个能感知环境的互动钓鱼游戏几年前我刚开始玩嵌入式开发时总想着做点既有意思又能把传感器、执行器和物联网串起来的东西。后来看到家里小孩玩的钓鱼玩具灵光一闪能不能做个“智能”版的不是那种磁铁吸的而是真的能感知环境、有逻辑判断的电子游戏。于是就有了这个基于树莓派 Pico W 的智能钓鱼游戏项目。这个项目的核心是用一个微控制器Pico W作为大脑指挥两个“感官”接近传感器和温度传感器和一个“手臂”连续旋转伺服电机协同工作。你通过网页点一下“抛竿”按钮伺服电机会转动模拟放出鱼线。当鱼钩末端挂了个配重块下落到接近传感器附近时系统会播放“咕噜咕噜”的水泡声提示。这时你得赶紧点“收竿”按钮伺服电机反转收回鱼线。成功的关键在于时机——你必须在提示音播放的几秒钟内操作。更有趣的是游戏会根据房间的实时温度决定你能钓到的是热带鱼如鲯鳅、金枪鱼还是冷水鱼如鲑鱼、鳕鱼每次收竿都是一次小小的惊喜。整个系统还接入了 Adafruit IO 这个物联网平台。这意味着你不需要在硬件上接一堆按钮直接用电脑或手机上的网页就能控制游戏状态和结果也能通过云端反馈。它麻雀虽小但五脏俱全涵盖了嵌入式开发里几个最经典的环节传感器数据采集距离、温度、执行器控制电机、本地逻辑处理判断时机、随机选择以及物联网通信远程控制与状态上报。对于想从点灯、读温度迈入更综合项目阶段的爱好者来说这是个绝佳的练手项目。2. 核心硬件选型与设计思路拆解做嵌入式项目硬件是骨架。选对组件项目就成功了一半。这个钓鱼游戏的核心硬件架构可以概括为“一脑、两感、一手、一云”。2.1 控制核心为什么是 Raspberry Pi Pico W主控芯片我选择了Raspberry Pi Pico W。对于这个项目来说它有几个无法替代的优势。首先它基于 RP2040 双核 ARM Cortex-M0 处理器性能对于处理传感器数据、控制电机和运行网络协议栈绰绰有余。其次也是最重要的一点它内置了 WiFi 功能。这是我们能通过 Adafruit IO 进行远程控制的基础。如果选用普通的 Pico不带W就需要额外增加 WiFi 模块不仅接线复杂编程也更麻烦。Pico W 通过wifi和socketpool库就能轻松连网极大地简化了物联网部分的开发。最后Pico 系列的 GPIO 引脚丰富且功能明确支持 I2C、SPI、PWM 等通信协议正好匹配我们所有外设的需求。它的 MicroPython/CircuitPython 开发环境对初学者也非常友好调试信息可以直接通过串口打印省去了专用调试器的麻烦。2.2 感知模块接近与温度的协同判断游戏的互动逻辑依赖于两个传感器Adafruit APDS9960和Adafruit MCP9808。APDS9960 接近传感器在这里扮演了“眼睛”的角色。它通过发射红外光并检测反射光来估算前方物体的距离。在代码中我们读取proximity值这个值越大代表物体离传感器越近。我通过实验将触发阈值设定为 230。当鱼钩配重块下落到这个距离范围内时传感器值超过阈值系统判定为“鱼钩已到位”触发水泡音效。这里有个关键点传感器的安装位置和朝向必须精准。它需要正对鱼钩下落路径并且中间不能有玻璃胶水等影响红外光透过的障碍物否则读数会不准。MCP9808 高精度温度传感器则负责增加游戏的随机性和真实感。它通过 I2C 接口与 Pico W 通信能提供 ±0.25°C 的高精度温度读数。游戏逻辑设定了一个温度分界线例如 72°F 或 22°C。当室温高于这个值时系统从warm_fish暖水鱼列表中随机选择一种鱼低于时则从cool_fish冷水鱼列表中选择。这模拟了真实世界中不同鱼类对水温的偏好。选择 MCP9808 是因为其精度高、使用简单Adafruit 提供了现成的 CircuitPython 库并且和 APDS9960 可以共享同一个 I2C 总线节省引脚。2.3 执行机构连续旋转伺服电机的精准控制动作部分由一颗连续旋转伺服电机完成。它与常见的角度伺服只能转0-180度不同可以连续地朝一个方向旋转转速和方向由 PWM 信号的脉宽控制。在这个项目里它模拟了钓鱼轮的收放线动作。抛竿 (cast)设置throttle -1.0电机全速反向旋转放出鱼线。收竿 (reel)设置throttle 1.0电机全速正向旋转收回鱼线。停止设置throttle 0电机停转。控制的关键在于pwmio.PWMOut(pin, frequency50)。这里的 50Hz 频率是标准伺服电机信号频率。throttle值通常在 -1.0 到 1.0 之间对应不同的旋转速度和方向。我实测发现让电机转动3秒time.sleep(3)能提供一段合适的、肉眼可见的收放线行程。你需要根据你使用的鱼线长度和绕线轴的直径来调整这个时间。2.4 物联网桥梁Adafruit IO 的轻量级集成为了实现远程控制我选择了Adafruit IO作为云平台。它是一个为物联网设备设计的、非常易用的数据托管和可视化服务。Pico W 作为 MQTT 客户端订阅 Adafruit IO 上一个名为fish-feed的 Feed数据流。当你在 Adafruit IO 的 Dashboard 上点击一个关联了cast或reel指令的按钮时这个指令就会作为一条 MQTT 消息发布到fish-feed。Pico W 收到消息后触发相应的电机动作和游戏逻辑。这种架构的优点是控制界面可以非常灵活地部署在网页、手机App上并且 Adafruit 提供了完善的 CircuitPython 库adafruit_minimqtt让物联网通信的实现变得像调用几个函数一样简单。注意电源规划。伺服电机在启动和堵转时电流很大可能超过500mA。切勿仅通过Pico W的USB口5V/500mA同时给Pico W和伺服电机供电这可能导致Pico W重启或损坏。正确的做法是使用一个独立的电池包如4节AA电池盒或大电流的5V适配器通过VBUS引脚为伺服电机供电。Pico W本身仍可通过USB供电或从电池包取电。3. 硬件连接与机械结构搭建详解理清了思路接下来就是动手把一堆零件变成一台能工作的机器。这个过程需要耐心和细致。3.1 分步电路连接指南接线是硬件项目的基础遵循“电源优先信号在后”的原则可以避免很多问题。以下是基于原始资料整理的完整接线表我补充了每个连接的目的和注意事项组件Pico W 引脚功能说明注意事项扬声器使用3.5mm音频插头转接音频地线任意 GND提供音频信号回路连接到音频插头的基座Sleeve音频信号线GP16输出PWM音频信号连接到音频插头的尖端TipSD卡读卡器(SPI通信)确保SD卡格式化为FAT32容量≤32GB3.3V3V3(OUT)供电勿接VBUS(5V)可能烧毁模块GND任意 GND接地CLKGP10 (SCK)SPI时钟线DO/SOGP11 (TX)SPI主机输入从机输出模块数据输出到PicoDI/SIGP12 (RX)SPI主机输出从机输入Pico数据输出到模块CSGP13片选信号温度传感器 MCP9808(I2C通信)与APDS9960共享I2C总线VIN3V3(OUT)供电GND任意 GND接地SDAGP4 (SDA)I2C数据线总线上需要上拉电阻Stemma QT线已内置SCLGP5 (SCL)I2C时钟线总线上需要上拉电阻Stemma QT线已内置接近传感器 APDS9960(I2C通信)通过Stemma QT线直连MCP9808VIN(来自MCP9808)供电通过Stemma QT线从MCP9808取电GND(来自MCP9808)接地SDA(来自MCP9808)I2C数据线SCL(来自MCP9808)I2C时钟线连续旋转伺服电机务必使用独立电源红线 (电源)VBUS电机电源正极接5V。建议接外部电池包正极。棕线 (电源-)任意 GND电机电源负极接GND。务必与Pico W共地。橙线 (信号)GP15PWM控制信号接线实操心得先供电后信号先将所有模块的电源3V3、GND接好检查无误后再连接信号线SDA、SCL、SPI引脚等。共地是关键确保伺服电机的GND、Pico W的GND以及外部电池包的GND是连接在一起的这是电路正常工作的基础。I2C总线共享MCP9808和APDS9960共用GP4和GP5这是I2C总线标准用法。每个设备都有唯一的地址Pico W可以分别与它们通信。使用杜邦线对于引脚连接使用母对母杜邦线最方便。对于伺服电机和音频接口使用鳄鱼夹转杜邦头的线缆会更牢固。3.2 机械结构与外壳制作电路是神经结构是骨骼。一个好的结构能让项目更稳固、更美观。核心设计双层结构我采用了一个木质盒子作为底座顶部放置一个透明的“鱼缸”。这个鱼缸可以用亚克力板激光切割后粘合也可以用现成的透明塑料盒改造。最关键的一点是在鱼缸底部和木盒顶部对应位置需要开一个能让APDS9960传感器“看见”内部的孔。这个孔的大小最好与传感器前端的感应窗口尺寸匹配。制作步骤定位与开孔将APDS9960传感器固定在木盒内部顶板上用笔标记出传感器感应窗口的位置。在木盒顶板和亚克力鱼缸底板的对应位置开一个同样大小的圆孔或方孔。固定传感器使用热熔胶或双面胶将APDS9960传感器从木盒内部粘在顶板开孔处确保其感应窗口正对开孔且没有胶体遮挡。组装将鱼缸对准木盒顶部的开孔放置并固定。这样传感器就能透过两层孔洞探测到鱼缸内部鱼钩配重块的接近情况。安装伺服电机和线轴在木盒侧面安装伺服电机。电机的输出轴上需要固定一个线轴可以用3D打印件、乐高零件或者小卷轴。鱼线一端缠绕在线轴上另一端穿过鱼缸顶部的一个小孔末端系上配重块当作鱼钩。布置与装饰将Pico W、电池包等内部元件整齐地摆放在木盒内用扎带固定。最后可以在鱼缸内放入一些蓝色碎石、水草模型等海洋主题装饰物增加氛围感。提示传感器校准。在正式玩游戏前必须进行接近传感器校准。将配重块鱼钩静止悬挂在传感器正下方你认为的“触发位置”运行一个简单的测试程序连续读取并打印multi_sensor.proximity的值。观察这个稳定值然后将其作为代码中minimal_distance_threshold例如230的设定依据。你可以通过调整鱼钩的重量、形状或传感器的阈值来微调触发灵敏度。4. 软件编程与逻辑实现深度解析硬件搭建完毕接下来是赋予它灵魂的代码部分。我们将使用 CircuitPython 进行开发它比 MicroPython 对硬件库的支持更友好特别是对 Adafruit 的传感器模块。4.1 开发环境配置与核心库导入首先你需要去树莓派官网下载最新的CircuitPythonUF2 固件文件。按住 Pico W 上的 BOOTSEL 按钮的同时将其通过 USB 连接到电脑然后释放按钮。电脑上会出现一个名为RPI-RP2的U盘将下载的 UF2 文件拖进去Pico W 会自动重启并变成 CircuitPython 设备盘符变为CIRCUITPY。接下来需要将必要的库文件复制到 Pico W 的CIRCUITPY盘下的lib文件夹中。本项目需要的库包括adafruit_mcp9808.mpy用于温度传感器。adafruit_apds9960.mpy用于接近传感器。adafruit_motor用于伺服电机控制。adafruit_minimqtt用于 MQTT 通信。adafruit_bus_deviceI2C/SPI通信基础库。audiomp3和audiopwmio用于播放MP3音频如果播放WAV文件则不需要audiomp3。此外还需要在CIRCUITPY盘的根目录下创建一个settings.toml文件用于安全地存储你的 WiFi 和 Adafruit IO 凭证。# settings.toml CIRCUITPY_WIFI_SSID 你的WiFi名称 CIRCUITPY_WIFI_PASSWORD 你的WiFi密码 AIO_USERNAME 你的Adafruit IO用户名 AIO_KEY 你的Adafruit IO Active Key BROKER io.adafruit.com PORT 8883重要安全第一。永远不要将密码和密钥直接硬编码在code.py主程序里。settings.toml文件在 CircuitPython 中被视为受保护的环境变量能有效避免敏感信息因代码分享而泄露。4.2 主程序代码逐段精讲让我们深入剖析核心的code.py理解每一段代码的意图。初始化与硬件设置import time, board, digitalio, pwmio, adafruit_mcp9808, random, os, ssl, socketpool, wifi, mount_sd from adafruit_motor import servo import adafruit_minimqtt.adafruit_minimqtt as MQTT from adafruit_apds9960.apds9960 import APDS9960 from audiopwmio import PWMAudioOut as AudioOut from audiomp3 import MP3Decoder # 配置伺服电机 servo_pin board.GP15 pwm pwmio.PWMOut(servo_pin, frequency50) # 标准伺服频率为50Hz my_servo servo.ContinuousServo(pwm) # 配置接近传感器 i2c board.STEMMA_I2C() # 使用默认I2C引脚GP4, GP5 multi_sensor APDS9960(i2c) multi_sensor.enable_proximity True # 启用接近检测功能 # 配置温度传感器 temp_sensor adafruit_mcp9808.MCP9808(i2c) # 共享同一个I2C总线 # 配置扬声器 audio AudioOut(board.GP16) path /sd/fish_sounds/ # SD卡上存放音效的路径 sound_playing False # 音效播放状态标志这部分代码导入了所有必需的库并初始化了硬件对象。关键点在于pwmio.PWMOut的频率设置为50Hz这是伺服电机识别的标准信号频率。两个传感器共享一个i2c对象这是 CircuitPython 的标准做法非常简洁。游戏逻辑函数def get_random_fish(tempF, warm, warm_fish, cool_fish): if tempF warm: return random.choice(warm_fish) elif tempF warm: return random.choice(cool_fish) else: return None def is_proximity_minimal(): proximity_value multi_sensor.proximity print(fProximity value: {proximity_value}) # 调试信息可注释掉 minimal_distance_threshold 230 minimal_proximity proximity_value minimal_distance_threshold if minimal_proximity: play_mp3(bubbling.mp3) return minimal_proximityget_random_fish函数是游戏的核心逻辑之一。它根据当前温度华氏度和预设的暖水温度阈值warm从不同的鱼列表中随机返回一个鱼的名字。is_proximity_minimal函数则负责检测鱼钩是否到位。它读取传感器值与阈值比较如果达到条件则触发水泡音效。这里的print语句在调试时非常有用你可以通过串口监视器观察实时的接近值从而精确调整阈值和鱼钩位置。MQTT回调函数游戏状态机这是整个程序最精彩的部分它定义了一个基于事件驱动的游戏状态机。def message(client, topic, message): global sound_playing, bubbling_start_time, caught_fish_during_bubbling if topic fish_feed: if message cast: # 抛竿电机反转放线 move_servo(-1.0) time.sleep(3) move_servo(0) # 放线后立即检查是否接近 proximity_value multi_sensor.proximity if proximity_value 230: play_mp3(bubbling.mp3) bubbling_start_time time.monotonic() # 记录音效开始时间 sound_playing True elif message reel and sound_playing: # 收竿电机正转收线 move_servo(1.0) time.sleep(3) move_servo(0) # 关键判断是否在音效播放期间收竿 if sound_playing and (time.monotonic() - bubbling_start_time) 6.43: tempF temp_sensor.temperature * 9 / 5 32 caught_fish get_random_fish(tempF, warm, warm_fish, cool_fish) if caught_fish: audio.stop() # 停止水泡声 print(fYou caught a {caught_fish}!) play_mp3(f{caught_fish}.mp3) # 播放捕获的鱼种音效 else: print(No fish caught this time. Keep trying!) sound_playing False bubbling_start_time Nonemessage函数是 MQTT 客户端的回调函数。当 Adafruit IO 有消息到来时自动执行。收到cast执行抛竿动作然后立即检查接近传感器。如果鱼钩已经在感应范围内比如上次没收线则直接触发水泡声并记录开始时间。这模拟了鱼钩入水即被鱼发现的情景。收到reel首先检查sound_playing是否为True即水泡声是否在播放。这是防止玩家在非提示时段收竿的“门控”逻辑。然后计算当前时间与水泡声开始时间的差值判断是否在有效的收竿窗口内例如6.43秒即音效长度。只有在窗口内收竿才算成功捕获。成功后根据温度随机选择鱼种并播放对应音效。网络连接与主循环# 连接WiFi wifi.radio.connect(os.getenv(WIFI_SSID), os.getenv(WIFI_PASSWORD)) # 创建MQTT客户端并设置回调 mqtt_client MQTT.MQTT(brokeros.getenv(BROKER), portos.getenv(PORT), ...) mqtt_client.on_message message # 指定消息处理函数 mqtt_client.connect() while True: mqtt_client.loop() # 这里可以添加其他需要持续运行的任务例如心跳监测 time.sleep(0.01)主程序在初始化后连接 WiFi 和 MQTT 代理Adafruit IO。mqtt_client.loop()在无限循环中被持续调用用于维护 MQTT 连接、接收消息并触发回调函数。这是一个典型的物联网设备主循环结构。4.3 音频文件准备与SD卡使用游戏音效是体验的重要组成部分。你需要准备几个MP3文件boat.mp3背景音乐可选。bubbling.mp3鱼钩接近时的水泡提示音。这个音效的长度至关重要它决定了玩家收竿的有效时间窗口代码中的6.43秒。你可以用音频编辑软件如Audacity查看或裁剪音效长度。Mahi-Mahi.mp3,Tuna.mp3, …每种鱼对应的捕获音效。文件名必须与代码中warm_fish和cool_fish列表里的名字完全一致包括大小写和连字符。将这些MP3文件放入SD卡根目录下新建的fish_sounds文件夹中然后将SD卡插入读卡器模块。在代码中路径path /sd/fish_sounds/必须与此对应。5. 系统集成、调试与优化实录当所有硬件连接好代码也上传后真正的挑战才刚刚开始让整个系统稳定、可靠地跑起来。5.1 Adafruit IO 控制面板配置你需要先在 Adafruit IO 网站上创建两个组件一个 Feed数据流命名为fish-feed。它用来收发cast和reel指令。一个 Dashboard控制面板创建两个按钮控件都关联到fish-feed这个 Feed。Cast 按钮设置其ON时发布的消息为castOFF时发布的消息为0用于紧急停止电机。Reel 按钮设置其ON时发布的消息为reelOFF时发布的消息为0。配置好后打开 Dashboard你应该能看到这两个按钮。确保你的 Pico W 已经成功连网并订阅了fish-feed串口会打印 “Connected to Adafruit IO!”。5.2 上电调试与问题排查按照以下步骤进行系统调试基础检查上电后首先打开串口监视器如 Mu 编辑器、Thonny 或screen/putty。你应该能看到 CircuitPython 的启动信息和代码打印的调试信息特别是 WiFi 连接和 Adafruit IO 连接状态。传感器测试在循环中临时添加代码持续打印温度temp_sensor.temperature和接近值multi_sensor.proximity。用手在传感器前移动观察接近值的变化是否灵敏、符合预期。检查温度读数是否与环境温度相符。伺服电机测试注释掉 MQTT 部分直接在主循环里调用move_servo(1.0)、sleep(2)、move_servo(0)等命令测试电机正反转是否正常力度是否足够拉动鱼线。音频测试同样可以写一个简单函数循环播放 SD 卡上的不同 MP3 文件检查接线和音频输出是否正常。集成测试最后恢复 MQTT 代码在 Adafruit IO 面板点击按钮。观察串口打印看是否收到正确的cast/reel消息并触发相应的动作和音效。5.3 常见问题与解决方案速查表以下是我在实现过程中遇到的一些典型问题及解决方法问题现象可能原因排查步骤与解决方案Pico W 无法连接 WiFi1.settings.toml配置错误或文件不在根目录。2. WiFi 信号弱或密码错误。3. 网络需要网页认证如酒店网络。1. 检查CIRCUITPY盘根目录下settings.toml文件名和内容格式是否正确。2. 在代码中临时写死 SSID 和密码测试测试完务必改回。3. CircuitPython 通常不支持网页认证请使用家庭路由器网络。连接 Adafruit IO 失败1. AIO_KEY 或 AIO_USERNAME 错误。2. 系统时间不同步SSL证书验证需要。1. 仔细核对 Adafruit IO 后台的 Active Key 和用户名。2. 确保 Pico W 能访问 NTP 服务器企业网络有时会屏蔽。可尝试在代码开始时添加wifi.radio.hostname “pico”有时能解决。接近传感器读数无变化或始终为01. I2C 接线错误SDA/SCL接反或没接。2. 传感器被遮挡或距离物体太远。3. 电源不足。1. 用import board; print(board.STEMMA_I2C())测试 I2C 总线或用 I2C 扫描程序检查设备地址。2. 确保传感器正面朝向被测物体且距离在几厘米内。检查鱼缸和木盒的开孔是否对齐、通透。3. 确保传感器供电稳定3.3V。伺服电机不转或抖动1. 电源功率不足特别是启动时电压被拉低。2. PWM 信号引脚错误或频率不对。3. 电机损坏。1.这是最常见的问题务必为伺服电机提供独立于 Pico W 的 5V/2A 以上电源并与 Pico W 共地。2. 检查橙色信号线是否接在 GP15或你定义的引脚代码中 PWM 频率是否为 50Hz。3. 直接用外部电源给电机供电红5V棕GND然后用导线瞬间触碰信号线到5V看是否转动以判断电机好坏。播放音频时系统卡死或无声音1. SD 卡读取失败。2. 音频文件格式或路径错误。3. 音频输出引脚错误或扬声器未开启。1. 确认 SD 卡格式化为 FAT32容量不超过32GB。检查 SPI 接线CLK, MISO, MOSI, CS。2. 确认 MP3 文件是单声道、低采样率如 22.05kHz高码率文件可能解码困难。检查path变量和文件名是否完全匹配包括大小写。3. 确认音频地线和信号线接在音频插头的正确部位基座和尖端并检查扬声器电源和音量。收竿逻辑不触发钓不到鱼1.sound_playing标志未正确设置。2. 时间窗口判断条件错误。3. 温度传感器读数异常导致get_random_fish返回None。1. 在message函数中添加更多print语句打印sound_playing、bubbling_start_time和计算出的时间差观察逻辑流。2. 确认bubbling.mp3的实际长度与代码中判断的6.43秒一致。3. 打印tempF的值检查温度读取是否正常以及warm_fish/cool_fish列表中的文件名是否存在。MQTT 消息收不到或延迟1. 网络不稳定。2. MQTT 客户端loop()调用不够频繁。3. Adafruit IO 免费账户有速率限制。1. 检查 WiFi 信号强度。2. 确保主循环while True中没有长时间的time.sleep()阻塞mqtt_client.loop()的调用。3. 免费版 Adafruit IO 有每分钟发送消息数的限制请勿过快点击按钮。5.4 项目优化与扩展思路这个基础版本已经很有趣但你完全可以把它变得更强大增加本地按钮除了云端控制可以在盒子上增加物理按钮直接连接到 Pico W 的 GPIO 上实现离线游玩。视觉反馈在鱼缸或盒子上增加 RGB LED 灯带。钓到不同种类的鱼时闪烁不同颜色的灯光体验更炫酷。难度分级引入速度可变的伺服电机。随着游戏进行收放线速度逐渐加快或者水泡声提示窗口时间逐渐缩短增加挑战性。数据记录与统计将每次钓鱼的时间、鱼种、温度记录到 SD 卡中后期可以导出分析或者增加一个小型显示屏实时显示统计信息。多玩家支持利用 Adafruit IO 的多个 Feed创建多个控制面板实现轮流钓鱼或竞赛模式。这个项目最让我满意的不是它最终钓上了虚拟的鱼而是它完整地走通了一个物联网嵌入式产品的原型开发流程从需求分析、硬件选型、电路焊接、结构搭建到软件编程、云服务集成最后是反复调试和优化。每一个环节踩的坑都是宝贵的经验。当你按下按钮听到电机转动、音效响起最终从云端传来“You caught a Marlin!”的语音时那种软硬件协同工作带来的成就感是单纯写代码或焊电路无法比拟的。希望这个详细的拆解能帮你顺利复现这个项目并激发出更多属于自己的创意。