技术分享:在ArduinoUNOQ上搭个AI嘴替,让元宝帮你控制openclaw点亮LED矩阵
最近在Arduino Uno Q上面部署了openclaw,并接入了元宝,实现了在元宝中,通过对话来进行AI编程,并成功驱动LED点阵。这篇文档就给大家分享这次的经验。
一、故事背景
最近拿到一块 Arduino UNO Q——这玩意儿不是普通 Arduino,它内部是一颗 Linux Debian 13(高通 QRB2210)+ 一颗 STM32U585(Zephyr RTOS),板载 13×8 灰度 LED 矩阵。
我想搞的事情是:在微信上(元宝)说一句"显示 你好世界",板子就立刻滚出这行字,不用烧录、不用重启 app。
走完一遍下来发现路径比想象顺,于是把它写成一份完整复盘:怎么把 AI Agent 装上 Uno Q、怎么让 Agent 用元宝对话、怎么给 Agent 编 skill 让它能驱动 LED 矩阵,最后顺手把家里另一块 Arduino UNO R4 WiFi 的板载矩阵也接进来。
二、第一步:在 Uno Q 上装 OpenClaw
2.1 板子情况
Uno Q 是双核异构板:
Linux 侧:Debian 13,arduino@my-uno-q,跑系统服务
MCU 侧:STM32U585 (Zephyr RTOS),管 LED 矩阵 + 通讯
桥接:两核之间用 RouterBridge(MsgPack RPC over /dev/ttyHS1 @ 115200),不是裸串口——这一点很重要,后面写 sketch 的人会踩坑
2.2 装 OpenClaw
Uno Q 的 Linux 侧可以直接 apt,但我推荐用 npm(更可控、升级快):
# 装 node 22+(板子一般预装) node -v # 应该 ≥ 22 # 全局装 OpenClaw CLI npm i -g openclaw@latest # 或 pnpm(推荐) npm i -g pnpm pnpm i -g openclaw@latest # 验证 openclaw --version # OpenClaw 2026.6.1 (xxx) # 起 gateway(系统服务方式,开机自启) openclaw gateway install # 注册 systemd user 服务 openclaw gateway start openclaw gateway status # Gateway: bind=loopback (127.0.0.1), port=18789 # Runtime: running (pid xxx, state active)
装完之后你会得到:
CLI: /home/arduino/.npm-global/bin/openclaw
配置:~/.openclaw/openclaw.json
Gateway 监听 127.0.0.1:18789
WebUI: http://127.0.0.1:18789/
小坑:loopback 模式只允许本机访问。如果你要从另一台机器控制 Uno Q,开 tailscale 或者把 gateway.bind 改成 0.0.0.0(注意改 auth)。
2.3 给 Agent 一个工作目录
OpenClaw 是基于 workspace 的 Agent。每个 Agent 启动时加载一个工作目录里的 markdown 当人设/记忆。我用的是默认 /home/arduino/.openclaw/workspace,里面几个关键文件:
workspace/ ├── AGENTS.md # Agent 行为准则("对你的人类好"那种) ├── SOUL.md # 人设(我是"阿Q",俏皮嘴贫) ├── IDENTITY.md # 我叫什么、长啥样 ├── USER.md # 我的人类是谁 ├── TOOLS.md # 本地笔记(板子型号、路径、偏好) ├── HEARTBEAT.md # 心跳检查清单(默认空) ├── MEMORY.md # 长期记忆(主会话才加载) └── memory/ # 每日笔记 (memory/YYYY-MM-DD.md)
这一套设计很贴心——主会话会加载 MEMORY.md,群聊/陌生人会话不会,天然防泄漏。
三、第二步:把 Agent 对接到腾讯元宝
OpenClaw 原生支持很多 IM(Signal/Telegram/Discord/WhatsApp…),元宝(Tencent Yuanbao)走 plugin 接入。
3.1 装元宝插件
openclaw plugins install openclaw-plugin-yuanbao
3.2 拿到 appKey / appSecret
到腾讯元宝开放平台(yuanbao.tencent.com 开发者后台):
创建一个机器人,得到 appKey 和 appSecret
记下来,配置一次就好
3.3 写到 openclaw.json
{
"plugins": {
"entries": {
"openclaw-plugin-yuanbao": { "enabled": true }
}
},
"channels": {
"yuanbao": {
"appKey": "dPTE87AVZ0GRNWsuFmeKAUGZ66kZIXbQ",
"appSecret": "***",
"name": "阿Q",
"enabled": true
}
}
}安全提示:appKey 和 appSecret 等同于密码,提交到 git 前记得脱敏(我那份用了 demo 占位符,别直接用我的)。
3.4 重启 gateway 让配置生效
openclaw gateway restart openclaw gateway status
3.5 配模型
OpenClaw 不绑定模型,可以接任何 OpenAI/Anthropic 兼容 API。我用的 MiniMax:
{
"agents": {
"defaults": {
"model": { "primary": "minimax-portal/MiniMax-M3" }
}
},
"models": {
"providers": {
"minimax-portal": {
"baseUrl": "https://api.minimaxi.com/anthropic/v1",
"api": "anthropic-messages",
"authHeader": true
}
}
},
"auth": {
"profiles": {
"minimax-portal:default": {
"provider": "minimax-portal",
"mode": "oauth"
}
}
}
}Anthropic 兼容协议的话 authHeader: true,API key 走 Authorization: Bearer。OAuth 模式下 key 走 ~/.openclaw/auth-profiles.json,gateway 帮你管理 token 刷新。
3.6 验收
打开元宝 App,找到你创建的机器人,发条"/status"。它会通过 gateway 调到 LLM,回复你当前的状态 。

【图1:元宝 App 里和"阿Q"机器人的对话截图】
四、第三步:让 Agent 能驱动 Uno Q 的 LED 矩阵
光能聊天不够,让 Agent 能动手才有意思。这一步是重点:我们要让 Agent 一收到"显示X",就在 Uno Q 上点亮 13×8 矩阵。
4.1 写一个 Arduino App(scroll-text)
Arduino UNO Q 用的是 App 架构——一个 App = MCU sketch + Linux 容器里的 Python:
arduino-app-cli app new /home/arduino/ArduinoApps/scroll-text
生成的目录:
scroll-text/ ├── app.yaml # 端口映射、容器配置 ├── sketch/ │ └── sketch.ino # STM32 端代码 ├── python/ │ └── main.py # 容器里跑的 Python └── assets/ └── fonts/ # 容器 bind mount 时可用
4.2 sketch.ino(STM32 端,只做 I/O)
这是迭代了 11 版才定下来的架构:MCU 端极简,全部逻辑给 Python。13 行实质代码:
#include <Arduino_RouterBridge.h>
#include <Arduino_LED_Matrix.h>
#include <vector>
Arduino_LED_Matrix matrix;
void setup() {
matrix.begin();
matrix.setGrayscaleBits(3);
matrix.clear();
Bridge.begin();
Bridge.provide("draw", draw); // 注册一个 RPC handler
}
void loop() {
delay(50); // MCU 啥都不做,等 Python 推帧
}
void draw(std::vector<uint8_t> frame) {
if (frame.size() < 104) return;
matrix.draw(frame.data()); // 13×8 = 104 字节
}为什么不让 MCU 跑逻辑? 试过 v5:双线程 + mutex + SPI 状态机 = 死机。把"算下一帧"放到 Linux 那颗 ARM A53 上跑(资源多),STM32 只收 104 字节去点 LED,稳得一逼。

【图2:STM32 vs Python 职责分工的架构图】
4.3 python/main.py(主战场)
Python 端要做的事:
渲染字模(用 PIL + Noto Serif CJK Bold 字体,64px → 缩放到 13×13 → 二值化)
把所有字符拼成一个 banner(比 13×8 长得多的纵向条)
用一个滚动线程,每 N ms 取一帧 13×8 的窗口 → Bridge.call("draw", frame_bytes)
起一个 HTTP server(端口 7099)接收外部控制
字模核心代码(伪):
from PIL import Image, ImageDraw, ImageFont
font = ImageFont.truetype("/app/assets/fonts/NotoSerifCJK-Bold.ttc", 64)
def render_glyph(ch: str) -> np.ndarray:
img = Image.new("L", (64, 64), 0)
d = ImageDraw.Draw(img)
d.text((2, 0), ch, fill=255, font=font)
arr = np.array(img)
# 找非零 bbox → 缩放到 13×13 → 二值化
...
return (arr > 110).astype(np.uint8)滚动循环核心:
def _scroll_loop():
while True:
banner = current_banner() # ndarray, shape (banner_h, 13)
direction = current_direction()
for top in iter_window(direction, banner.shape[0]):
frame = banner[top:top+8, :] # 13×8 窗口
Bridge.call("draw", frame.tobytes())
time.sleep(speed_ms / 1000)HTTP API(用 stdlib http.server + threading,零依赖):
# POST /display {"text": "你好", "speed_level": 10, "direction": "top_down"}
# POST /stop, POST /clear, GET /health4.4 启动 + 验收
arduino-app-cli app start /home/arduino/ArduinoApps/scroll-text
# 它会自动:编译 sketch → 烧录到 STM32 → 起 Python 容器 → 暴露端口
arduino-app-cli app list | grep scroll-text
# 应该看到 scroll-text running
curl -sS -X POST http://127.0.0.1:7099/display \
-H "Content-Type: application/json" \
-d '{"text":"你好世界"}'板子上立刻滚出"你好世界"。从此改字不用烧录。

【图3:板子实物照片,矩阵上正在滚"你好世界"四字】
容器里没有 PIL 也没有中文字体,要在 assets/fonts/ 放一份字体,Docker bind mount 进容器。
五、第四步:给 Agent 编 skill
到这里,板子能听话了,但 Agent 还不知道怎么调它。"显示X" 对 LLM 来说只是一句话,它不会自动想起 curl。
OpenClaw 提供了 Skill Workshop——一套提案制的工作流,专门用来把"重复流程"沉淀成可复用技能。
5.1 写一个 Uno Q 的 skill(scroll-led)
skill 本质就是一份 markdown 文档,放在 ~/.openclaw/skills/<name>/SKILL.md,告诉 LLM:
什么时候该用这个 skill
该怎么调(命令、参数、边界条件)
出错怎么办
我用 skill_workshop 工具创建:
openclaw skill_workshop create \ --name "scroll-led" \ --description "在 Arduino UNO Q 13×8 LED 矩阵上滚动显示任意文字。说\"显示X\" → HTTP API。支持方向+速度档位 1-10。"
关键章节(截选):
## 何时用 - 用户说"显示 X"、"在板子上显示 X"、"LED 上显 X"、"matrix 显示 X" - 停止/清除/重启滚动时 ## 前置条件 1. scroll-text app **必须 running** 2. HTTP API 监听 http://127.0.0.1:7099 ## HTTP API(端口 7099) | 方法 | 路径 | 说明 | |---|---|---| | POST | /display | 显示并滚动 | | POST | /stop | 停滚动 | | POST | /clear | 清除 + 停 | ## 用户口语映射 | 用户说 | 调什么 | |---|---| | "显示 X" | display text=X | | "从下往上 X" | display text=X direction=bottom_up | | "快一点"/"慢一点" | display text=X speed_level=±2 | | "停"/"清"/"擦掉" | stop / clear |
提案创建后是 pending 状态。Skill Workshop 是个提交流程,编辑 + 评审 + 批准都走它,避免 skill 满天飞。
5.2 验收 skill
在元宝里发:"显示 你好,世界"。
Agent 收到 → 识别匹配 scroll-led skill → 读 SKILL.md → 调 curl http://127.0.0.1:7099/display ... → 板子立刻滚出"你好,世界"。
整个延迟大概 1 秒(LLM 一次往返)。

【图4:元宝对话流程截图】
六、第五步:把 Arduino UNO R4 WiFi 也接进来
家里另一块板子是 Arduino UNO R4 WiFi——瑞萨 RA4M1 + ESP32-S3,板载 12×8 LED 矩阵。它没有 Uno Q 那套 Linux 架构,必须烧 sketch,没有 HTTP API 这种事。

【图5:Arduino UNO R4 WiFi 板子实物】
6.1 工具链准备
R4 用 arduino-cli 直接烧:
# 装 core(一次性) arduino-cli core install arduino:renesas_uno@1.6.0 # 装文字库(一次性) arduino-cli lib install ArduinoGraphics # 确认板子在 arduino-cli board list # /dev/ttyACM0 ... Arduino UNO R4 WiFi arduino:renesas_uno:unor4wifi
6.2 sketch 模板
R4 的板载矩阵用 Arduino_LED_Matrix + ArduinoGraphics + TextAnimation 这套异步滚动 API:
#include "ArduinoGraphics.h"
#include "Arduino_LED_Matrix.h"
#include "TextAnimation.h"
ArduinoLEDMatrix matrix;
// 帧缓冲大小公式:N >= (字符数 + 前导空白数) * 5 + 余量
// " 1 2 3 4 5 " = 13 字符 × 5px = 65,留余地写 80
TEXT_ANIMATION_DEFINE(anim, 80)
bool requestNext = false;
void setup() {
matrix.begin();
matrix.beginDraw();
matrix.stroke(0xFFFFFFFF);
matrix.textFont(Font_5x7);
matrix.textScrollSpeed(60); // 值越小滚得越快
matrix.setCallback(matrixCallback);
matrix.beginText(0, 1, 0xFFFFFF);
matrix.println(" 1 2 3 4 5 "); // 前导空白 = 进场留白
matrix.endTextAnimation(SCROLL_LEFT, anim);
matrix.loadTextAnimationSequence(anim);
matrix.play();
}
void matrixCallback() { requestNext = true; }
void loop() {
if (requestNext) {
requestNext = false;
matrix.beginText(0, 1, 0xFFFFFF);
matrix.println(" 1 2 3 4 5 "); // 两处都要改
matrix.endTextAnimation(SCROLL_LEFT, anim);
matrix.loadTextAnimationSequence(anim);
matrix.play();
}
}6.3 烧录
cd /tmp/r4_led_scroll arduino-cli compile --fqbn arduino:renesas_uno:unor4wifi \ --upload -p /dev/ttyACM0 r4_led_scroll.ino # 等 ~10 秒,看到 100% (15/15 pages) 就是烧好了
R4 上立刻循环滚"1 2 3 4 5"。注意:和 Uno Q 不同,R4 是"烧死"的,断电不丢,但改字要重烧。

【图6:R4 板子实物,12×8 矩阵上正在循环滚"1 2 3 4 5"】
6.4 给 R4 也编一个 skill(r4-led-scroll)
写法跟 Uno Q 的类似,但走的是 arduino-cli compile --upload,不是 HTTP:
## 何时用 - "在 R4 上显示 X"、"R4 显示 X"、"UNO R4 显示 X" ## 前置条件 - Arduino UNO R4 WiFi(**R4 Minima 无矩阵**,不能用) - USB 接到本机 /dev/ttyACM0 - arduino-cli 已装 + core arduino:renesas_uno@1.6.0 ## 流程 1. 写 sketch 到 /tmp/r4_led_scroll/r4_led_scroll.ino(替换 TEXT) 2. arduino-cli compile --fqbn arduino:renesas_uno:unor4wifi --upload -p /dev/ttyACM0
七、最终效果
元宝对话框里随手发:
你说谁干活结果
| "在 UnoQ 上显示 Hello" | Agent 调 Uno Q skill → HTTP | 13×8 滚出 Hello |
| "R4 显示 12345" | Agent 调 R4 skill → 烧录 | 12×8 循环滚 12345 |
| "UnoQ 从下往上,慢一点" | Agent 调 Uno Q skill(改参数) | 立即生效,不烧录 |
| "R4 快一点" | Agent 改 sketch 里的 textScrollSpeed → 重烧 | 烧完跑新速度 |
两块板子两套玩法,一套靠 HTTP 热改,一套靠烧录持久。这就是嵌入式 + AI Agent 组合的味道——板子不再是被烧死的设备,而是一个能被 LLM 操纵的 I/O。

【图7:两块板子并排实拍】
八、踩坑清单(速查)
坑症状解决
| Uno Q sketch 用裸 Serial.begin() + AA55 协议 | MCU 完全不响应 | 用 Arduino_RouterBridge,MCU 端 Bridge.provide("name", func) |
| 把滚动逻辑全塞 MCU(双线程 + mutex) | 烧完死机,板子啥都不亮 | MCU 只做 I/O,逻辑全给 Python |
| 滚动方向反 | 字从下往上变成从上往下 | banner 编排方向 + 窗口扫描方向要配合 |
| Uno Q 容器里没 PIL 没字体 | ModuleNotFoundError | 字体放 assets/fonts/,镜像里有 PIL 但要自己处理依赖 |
| R4 Minima 想跑 LED 矩阵 | 编译过了但没反应 | Minima 没有板载矩阵,只有 R4 WiFi 有 |
| R4 TEXT_ANIMATION_DEFINE(anim, N) 设太小 | 文字被截断 | N ≥ (字符数 × 5) + 5 |
| 元宝 appKey 提交到 git | 泄漏 token | 加进 .gitignore 或用环境变量 |
| LLM 不会自动调 skill | 用户说"显示X",Agent 只回了文字 | SKILL.md 描述要够具体,触发关键词要覆盖口语表达 |
我要赚赏金
