news 2026/8/28 10:03:59

Picodevil:LLM辅助开发的树莓派Pico W桌面设备

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
Picodevil:LLM辅助开发的树莓派Pico W桌面设备

Picodevil 是一个基于树莓派 Pico W 的桌面小设备,名字来自 Pico 和 Devil 的组合。这个项目最特别的地方在于:我从需求拆分、代码编写到错误排查,几乎每一步都在用 LLM 辅助完成,而且设备本身也通过 Wi-Fi 调用 LLM 接口,把云端或本地模型返回的文本显示在 OLED 屏幕上。也就是说,Picodevil 既是用 LLM 造出来的,又能调用 LLM 工作。

这不是一篇只讲“AI 帮我写代码”的泛泛文章。要真正把 LLM 用到电子 DIY 项目里,必须清楚哪些事情模型能帮你省时间,哪些事情必须靠人判断。接下来会按照从需求拆解到环境准备、代码生成、烧录验证、问题排查、工程化改造的顺序走一遍。如果你正好想用 LLM 开发一个嵌入式小项目,或者想把手边的 Pico W 变成带有简单 AI 交互的桌面摆件,这条路可以照着走。

需要先说明,Picodevil 只是示例项目名,功能定义按照我的场景展开。你可以保留同一套流程,去构建自己的“某某 devil”或者其他桌面设备。

1. Picodevil 到底要做什么:把一个想法拆成 LLM 能理解的任务

1.1 功能与硬件清单

Picodevil 的交互定义不能太复杂。作为示例项目,我把它限定为四件事:

  • 用户按下按钮后,Picodevil 连接 Wi-Fi。
  • 调用云端或本地 LLM 接口,请求一句“恶魔但友善”的英文短句。
  • 把返回文本显示在 SSD1306 OLED 屏幕上。
  • 用一颗 NeoPixel LED 表示“正在思考”和“回答完成”两种状态。

这个功能范围适合第一次做 LLM 加微控制器的项目。如果一开始就加语音识别、多轮对话、触摸屏,LLM 生成的代码会变得很难验证,排查问题也会同时面对硬件、网络、模型行为三条链路。

硬件部分可以按这个清单准备:

组件规格/说明用途
树莓派 Pico W带 Wi-Fi 的 RP2040 开发板主控
SSD1306 OLED128x64,I2C 接口,常见地址 0x3C显示文本
NeoPixel LED内置 WS2812 的灯珠,信号引脚用 3.3V 电平即可状态指示
按钮轻触开关,按下接地触发交互
面包板/杜邦线原型阶段连接电路搭建和调试
外壳可选,3D 打印或亚克力固定成品

在真正向 LLM 提问之前,先把这个清单和功能范围写清楚。LLM 不能在硬件层面替你做判断,但它会根据你提供的引脚和协议生成更准确的代码。

1.2 交互时序和数据流

Picodevil 的交互时序看起来不复杂,但写代码前需要把数据流固定下来。

按钮按下 -> 主循环检测到低电平 -> NeoPixel 亮青色 -> WLAN 连接 -> urequests.post(API_URL) -> 解析 JSON -> OLED 显示文本 -> NeoPixel 亮红色 -> 恢复等待状态

这个数据流在排查问题时非常重要。代码如果出问题,要先看它卡在哪一步:是 Wi-Fi 没连上,是请求超时,还是 JSON 字段路径不对。没有数据流,就只能反复拔 USB 重启。

1.3 给 LLM 的项目提示词模板

生成代码前,LLM 需要看到完整约束。我第一次生成的代码因为只写了“Picodevil 显示一句话”,结果模型给了一段 PC 上才能运行的 Python 脚本,里面还出现了requests库。这个教训说明:提示词必须包含硬件环境、接口协议、功能要求和输出格式。

我给 LLM 的提示词模板如下:

你是一名熟悉树莓派 Pico W 和 MicroPython 的嵌入式工程师。 请为一个名为 Picodevil 的桌面设备编写 main.py。 硬件: - Raspberry Pi Pico W - SSD1306 OLED 128x64,I2C 使用 GPIO0(SDA) 和 GPIO1(SCL) - NeoPixel 接 GP16 - 按钮接 GP14,按下为低电平 功能: - 开机后 OLED 显示 Press button - 按下按钮后,通过 urequests 调用 OpenAI 兼容的 LLM 接口 - 得到英文短句后,全文显示在 OLED 上 - NeoPixel 在请求期间显示青色,完成后显示红色 - 请求失败时 OLED 显示 Error,不能卡死 输出: - 只输出 main.py 代码,不要输出解释 - 使用 ssd1306 库,引用来自 ssd1306.py

这个模板的核心作用,是把“随机生成代码”变成“在约束下生成代码”。省略任何一项,都很容易得到引脚错误、库不存在或者运行时崩溃的代码。

2. 环境准备:先让 Pico W 能被计算机控制

2.1 硬件与工具准备

在让 LLM 生成 main.py 之前,先把开发环境跑通。环境不通,生成的代码再好也只能在电脑上“看起来正确”。

电脑端需要准备:

  • Python 3.8 或更高版本。
  • mpremote命令行工具,用于上传文件和进入交互命令。
  • USB 数据线,注意有些线只能充电不能传数据。
  • MicroPython 官方发布的 .uf2 固件文件。

不需要使用完整桌面 IDE。Thonny 也可以,但命令行工具更适合写博客和脚本化操作。

2.2 烧录 MicroPython 固件

刷固件的步骤是固定的:

  1. 从 MicroPython 官方下载页选择 Raspberry Pi Pico W 对应的 .uf2 文件。
  2. 按住 Pico W 上的 BOOTSEL 按钮,再插入 USB 线。
  3. 电脑上会多出一个名为 RPI-RP2 的 U 盘。
  4. 把下载好的 .uf2 文件直接拖到 U 盘里。
  5. 文件复制完成后,Pico W 会自动重启。

这一步非常容易出错的地方是选错固件。如果下载了普通 Pico 固件而不是 Pico W 固件,后面代码里会出现network模块不存在或者 Wi-Fi 功能失效。固件文件名通常带有 PICO_W 字样,下载前先确认。

2.3 安装 mpremote 并检查串口连接

安装 mpremote:

python -m pip install mpremote

查看是否识别到设备:

mpremote version mpremote info

正常输出会显示 mpremote 版本和 Pico W 的板卡信息。如果电脑上有多个串口设备,可以指定端口:

mpremote connect COM3 info

在 Linux 下常见路径是 /dev/ttyACM0,Windows 下常见 COM3 或 COM4。如果 mpremote 找不到设备,优先检查 USB 线、BOOTSEL 状态和驱动。

还可以用一条短命令确认 MicroPython 环境:

mpremote exec "import sys; print(sys.platform, sys.implementation)"

这一步完成,说明 Pico W 已经可以接受代码上传。之后再让 LLM 生成程序,就不会出现“写完代码不知道怎么跑”的问题。

3. 用 LLM 生成第一版 MicroPython 代码

3.1 推荐生成方式:分模块生成,不要一次生成整个工程

如果直接让 LLM 一次生成“完整可运行的 Picodevil”,效果通常不理想。原因不是模型能力不够,而是最终代码里既有 OLED 驱动、按钮逻辑,又有网络请求和 JSON 解析,一旦出错很难定位。

推荐分四轮生成:

  • 第一轮:让 LLM 生成 OLED 显示测试代码。
  • 第二轮:让 LLM 生成按钮输入代码,并打印当前电平。
  • 第三轮:让 LLM 生成 Wi-Fi 连接和 LLM 接口调用代码。
  • 第四轮:把前两轮和第三轮的结果合并成 main.py。

每一轮都有明确验证点。OLED 能显示,说明接线和 I2C 地址正确;按钮能打印电平,说明 GPIO 配置正确;能连 Wi-Fi,说明网络环境正确。最后合并时,代码即使报错,问题也被限制在“集成”这一层。

3.2 完整可运行示例代码

下面是我最后用 LLM 生成的 main.py 示例。它依赖一个ssd1306.py驱动文件,这个驱动需要和 main.py 一起上传到 Pico W,microPython 固件默认不包含该文件。

import time import network import urequests import ujson from machine import Pin, I2C from neopixel import NeoPixel try: from ssd1306 import SSD1306_I2C except ImportError: print("ERROR: upload ssd1306.py first") raise # ===== 配置区 ===== WIFI_SSID = "your-wifi-ssid" WIFI_PASSWORD = "your-wifi-password" API_URL = "http://192.168.1.100:8000/v1/chat/completions" API_KEY = "your-api-key" MODEL = "your-model-name" # ===== 引脚定义 ===== I2C_SDA = Pin(0) I2C_SCL = Pin(1) OLED_ADDR = 0x3C NEOPIXEL_PIN = 16 BUTTON_PIN = 14 # ===== 初始化外设 ===== i2c = I2C(0, sda=I2C_SDA, scl=I2C_SCL, freq=400000) oled = SSD1306_I2C(128, 64, i2c, addr=OLED_ADDR) np = NeoPixel(Pin(NEOPIXEL_PIN, Pin.OUT), 1) btn = Pin(BUTTON_PIN, Pin.IN, Pin.PULL_UP) def oled_show(lines): oled.fill(0) y = 0 for line in lines: if y < 64: oled.text(line, 0, y) y += 16 oled.show() def set_led(rgb): np[0] = rgb np.write() def connect_wifi(): wlan = network.WLAN(network.STA_IF) wlan.active(True) if wlan.isconnected(): return True wlan.connect(WIFI_SSID, WIFI_PASSWORD) for _ in range(20): if wlan.isconnected(): return True time.sleep(0.5) return False def ask_llm(prompt): headers = { "Content-Type": "application/json", "Authorization": "Bearer " + API_KEY } payload = { "model": MODEL, "messages": [ {"role": "system", "content": "You are Picodevil, a diabolical but friendly desk companion. Reply in one English sentence shorter than 20 words."}, {"role": "user", "content": prompt} ], "temperature": 0.8 } resp = urequests.post(API_URL, headers=headers, data=ujson.dumps(payload), timeout=10) result = ujson.loads(resp.text) resp.close() text = result["choices"][0]["message"]["content"].strip() return " ".join(text.split()) def wrap_text(text, max_chars=16): lines = [] while len(text) > max_chars: space = text.rfind(" ", 0, max_chars) if space == -1: space = max_chars lines.append(text[:space]) text = text[space:].strip() if text: lines.append(text) return lines[:4] oled_show(["Picodevil", "Press button"]) set_led((0, 0, 0)) while True: if btn.value() == 0: oled_show(["Thinking..."]) set_led((0, 60, 60)) time.sleep(0.1) if not connect_wifi(): oled_show(["WiFi failed"]) set_led((60, 0, 0)) time.sleep(2) continue try: text = ask_llm("Give me one devilish but friendly one-liner.") oled_show(["Picodevil:"] + wrap_text(text)) set_led((60, 0, 0)) except Exception as err: oled_show(["Error", str(err)]) set_led((60, 0, 0)) # 简单防抖:松开按钮后才会再次触发 while btn.value() == 0: time.sleep(0.05) time.sleep(0.05)

代码中使用的 API 地址是本地或内网服务地址。如果换成云端 HTTPS 接口,urequests可能需要处理证书问题,响应过大也会占用更多内存,这在排错时要留意。

3.3 代码关键点解释

network.WLAN(network.STA_IF)是 Pico W 的 Wi-Fi 站模式,适合连接路由器。

Pin(BUTTON_PIN, Pin.IN, Pin.PULL_UP)启用了内部上拉,按钮另一端接地。这样按钮按下时读到 0,代码用btn.value() == 0判断,逻辑上更符合物理接线。

set_led接收一个 RGB 三元组。NeoPixel 写值后必须调用np.write()才会真正输出到灯珠。

ask_llm使用ujson.dumps序列化请求体,并用urequests.post发送。这里千万不能用 PC 端 Python 的requests库,MicroPython 没有这个库。

wrap_text按空格切分文本,避免 OLED 每行只显示几个字符。因为 SSD1306 的默认字体不支持中文,我让模型只返回英文短句。如果要做中文显示,需要额外制作中文字库或改用支持全字库的屏幕。

4. 烧录验证与日志排查

4.1 上传文件到 Pico W

在 main.py 所在的目录下执行:

mpremote cp ssd1306.py : mpremote cp main.py : mpremote reset

:在这里表示 Pico W 的根目录。如果ssd1306.py不在当前目录,mpremote cp会报源文件错误。上传完成后,mpremote reset会让设备重启,并自动运行 main.py。

如果不想手动重启,也可以进入 REPL 后按 Ctrl+D 执行软复位。

4.2 运行验证与预期输出

把 USB 串口接到电脑上,执行:

mpremote repl

正常现象应包含这几个阶段:

  • 上电后 OLED 显示 “Picodevil” 和 “Press button”。
  • 按下按钮后,OLED 变为 “Thinking...”,NeoPixel 变成青色。
  • 请求成功后,OLED 第一行显示 “Picodevil:”,下面若干行显示英文短句,NeoPixel 变成红色。
  • 松开按钮后,系统回到等待状态。

在除错阶段,建议在ask_llm函数里临时加一句 DEBUG:

print("llm response:", text)

这样返回内容会同时打印到 REPL,方便确认 OLED 显示是否正确。验证完成后可以去掉这行。

4.3 从日志倒推问题的排查顺序

Picodevil 集成的问题,必须按“执行到哪一步失败”来排查。优先顺序是:输入是否正确、文件是否上传、引脚是否接对、依赖是否上传、配置是否生效、日志有什么异常。

现象可能原因检查方式
上电黑屏I2C 地址错误、SDA/SCL 接反、ssd1306.py 未上传用 I2C scan 确认地址
按下按钮无反应GPIO 引脚错误、按钮接线不对、上拉未生效打印btn.value()观察电平
一直显示 WiFi failedSSID/密码错误、固件是普通 Pico 而非 Pico W、路由器拒绝连接打印wlan.status()
API Error接口地址错误、密钥无效、超时、返回 JSON 结构不对先用 PC 端 curl 或 py 脚本测试接口
MemoryErrorAPI 返回内容太大、OLED 或网络库占用过多缩短返回长度、换小模型、关闭无用外设

I2C 扫描命令可以单独执行:

from machine import I2C, Pin i2c = I2C(0, sda=Pin(0), scl=Pin(1), freq=400000) print(i2c.scan())

如果扫描结果等于[60],说明 SSD1306 的 I2C 地址是 0x3C,接线正确。如果扫描不到设备,优先检查 SDA、SCL 是否插反,以及 OLED 是否需要独立供电。

5. 常见坑:LLM 写嵌入式代码时最容易在哪里翻车

5.1 五个高频问题速查表

LLM 生成 MicroPython 代码时,问题通常不是“模型不懂编程”,而是“模型默认生成 PC 端 Python”。下面是几个高频问题:

问题现象根因解决方案
import requests 失败MicroPython 没有 requests用 urequests,并告诉模型环境是 MicroPython
Pin(14) 被理解成物理引脚 14只写了“接引脚14”,没写 GPIO 编号一律使用 GPIO 编号,例如 GP14
找不到 ssd1306 模块ssd1306.py 未上传到 Pico W从 micropython-lib 拷贝到项目目录并上传
按钮按下后频繁触发没有防抖,或循环速度太快采用松开检测和短延时
API 返回字段解析报错写死了错误的 JSON 路径先打印原始返回,再根据实际字段解析

这五类问题,几乎都能通过“更清楚的提示词约束 + 更小的验证步骤”避免。

5.2 深入分析三个典型坑

坑一:把 PC 端 Python 代码当成 MicroPython 代码。这是遇到最多的坑。LLM 可能会生成import requestsfrom urllib.request import urlopen,甚至建议你用pip install。MicroPython 是运行在微控制器上的精简解释器,没有完整 Python 生态。正确的做法是在提示词里明确写出“只能使用 MicroPython 内置模块和 ssd1306、neopixel,使用 urequests 发 HTTP 请求”。

坑二:引脚编号和物理引脚混淆。Pico W 的芯片引脚叫 GPIO0、GPIO1,但开发板丝印可能标注成 GP0、GP1,物理位置却在不同位置。LLM 如果只收到“用第 0 针和第 1 针”,它可能真的给你写Pin(0)Pin(1),但接线的人接在物理 pin 1 和 pin 2,结果还是能对应。最容易出错的是生成 ESP32 风格代码,例如Pin(14)在 ESP32 和 Pico W 上含义完全不同。

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/8/28 10:02:11

微信小程序短视频去水印的真相:资源溯源而非图像处理

简介&#xff1a;短视频去水印本质上不是前端图像算法问题&#xff0c;而是跨平台资源路径解析与合规重封装的技术实践。其核心原理在于逆向分析抖音、快手、小红书等平台分享链接的跳转逻辑&#xff0c;通过云函数中转获取原始无水印CDN地址&#xff0c;规避小程序域名限制与微…

作者头像 李华
网站建设 2026/8/28 10:01:40

自适应关键帧算法:深度学习驱动的微表情识别核心技术解析

简介&#xff1a;计算机视觉中的表情识别技术&#xff0c;旨在通过分析面部图像或视频序列&#xff0c;自动识别和理解人的情绪状态。其核心原理在于利用图像处理和机器学习算法&#xff0c;提取面部特征并映射到特定情绪类别。这项技术的价值在于能够实现非接触式、客观的情绪…

作者头像 李华
网站建设 2026/8/28 10:01:30

语音函数调用全解析:让语音大模型直接产出函数调用

Spoken Function Calling 是最近在语音大模型方向里越来越值得关注的一个话题。它的目标很直接&#xff1a;让大型音频语言模型&#xff08;Large Audio Language Model&#xff0c;LALM&#xff09;直接根据用户的语音输入判断意图&#xff0c;并产出结构化的函数调用结果&…

作者头像 李华
网站建设 2026/8/28 10:00:01

LangChain 快速上手指南:5 分钟跑通第一个 LLM 应用

LangChain 快速上手指南&#xff1a;5 分钟跑通第一个 LLM 应用 【免费下载链接】langchain The agent engineering platform. 项目地址: https://gitcode.com/GitHub_Trending/la/langchain 想让大模型接上自己的数据、还能随时换模型&#xff1f;LangChain 就是干这个…

作者头像 李华
网站建设 2026/8/28 9:56:08

非科班生如何高效备赛蓝桥杯Python B组:从零到省一的策略与实战

1. 从“零”到“一”&#xff1a;一个非科班生的算法竞赛起点 坦白说&#xff0c;我最初接触“蓝桥杯”和“算法”这两个词的时候&#xff0c;脑子里几乎是一片空白。我不是计算机科班出身&#xff0c;当时连“时间复杂度”是什么都说不清楚&#xff0c;Python也只会用 print(…

作者头像 李华