RISC-V开发板跑AI机械臂:YOLOE+MCP+视觉闭环实践
2026/9/8 13:55:53 网站建设 项目流程

前几天朋友来工作室串门,看到桌上一台 VisionFive 2 连着 6 自由度机械臂,正按我写在白板上的指令把螺丝钉从一个格子抓到另一个格子,第一句话就是:“这玩意儿也能跑机器人?”他甚至没问跑得好不好,直接问能不能跑。这也难怪,大多数人印象里 RISC-V 开发板还停留在跑个 Linux、点个灯的阶段,和“机器人”这个词放在一起总觉得有点违和。

我给了一个很明确的答案:能跑,而且我跑的不是一个“只会转舵机”的玩具 demo,而是一条完整的感知-决策-控制闭环——YOLOE 负责视觉感知,Agent API 负责对外提供可编程接口,MCP 负责让大模型智能体像调用普通工具一样调用机械臂,最后用视觉反馈闭环把东西准确抓起来。整个过程在板卡本地完成推理,不需要云端参与。

这篇文章就是这次实践的完整记录。我会从硬件环境、架构设计、视觉模型部署、闭环控制、Agent API 和 MCP 集成几个部分展开,最后把一路踩过的坑整理成速查表。对想尝试 RISC-V 边缘 AI、或者想在没有 GPU 的板子上做机器人原型验证的朋友,这篇应该能帮你省下不少时间。

1. 先回答那个最扎心的问题:RISC-V 到底能不能跑机器人?

先说结论:能跑,但“能跑”和“跑得爽”是两回事。

RISC-V 是一个开源指令集架构,它本身不规定硬件性能上限,台积电先进工艺可以造出很强的 RISC-V 芯片,但那是服务器和超算场景。市面上面向开发者的 RISC-V 单板计算机,比如这次用的 VisionFive 2,定位基本对标树莓派 4,CPU 性能在入门级 ARM 板卡的 70%~80% 左右,而且绝大多数都没有 NPU,深度学习推理只能靠 CPU 硬扛。

这意味着你不能指望它实时跑一个大模型做视频流目标检测。但机器人不是只有“实时视频流”这一种形态,桌面级的抓取任务里,目标物体不会每秒移动几米,工作台环境相对固定,检测周期控制在 1 秒左右完全能接受。把模型量化、把输入分辨率降下来、把推理管线改成按帧触发而不是按视频流触发,RISC-V 板卡完全撑得住。

我这次跑通的流程是:USB 摄像头采集桌面画面,YOLOE 在板卡上检测目标物体和位置,Agent API 把“看到什么、能抓什么”封装成接口,MCP Server 把这些接口暴露给大模型智能体,智能体根据用户指令调用机械臂执行抓取和放置,整个过程通过视觉闭环不断修正误差。一顿操作下来,这套系统能稳定完成“把红色螺丝刀放到坐标 (300, 200)”这种指令,从下达指令到机械臂复位,大约 5~8 秒。

所以如果你问 RISC-V 能不能跑机器人,我的回答是:跑桌面级、教学级、验证级的 AI 机器人,完全没问题;跑高速产线或者复杂环境导航,别难为它。这篇文章面向的是前者,我会把整套工程的选型思路、核心代码和踩坑记录都写出来。

2. 系统怎么搭:YOLOE、Agent API 和 MCP 各管哪一段

很多第一次做机器人项目的朋友,上来就装 ROS,然后陷入依赖地狱。我这次刻意没有用 ROS,而是采用一个更轻量、更适合 AI Agent 时代的分层结构,每一层职责清晰,替换起来也方便。

2.1 感知、决策、控制三层是怎么分的

整个系统拆成三层:

第一层是感知层,跑 YOLOE。它的任务是回答“工作台上有什么、目标在哪个像素位置”。和传统固定类别检测器不同,YOLOE 支持在推理阶段通过文本描述或参考图片动态指定检测目标,不需要重新训练模型。这一点对机器人场景太关键了,今天抓螺丝刀,明天抓橡皮,后天抓一个用户随手放上去的陌生物件,模型不用动,换一句提示词就行。

第二层是决策层,落在一个 Agent API 服务上。这层用 FastAPI 实现,把底层视觉和机械臂控制能力封装成 HTTP 接口,比如查看当前物体列表、执行闭环抓取、执行放置。大模型智能体通过这套 API 感知世界、做出决策,API 返回的结构化 JSON 就是智能体的“眼睛和手”。

第三层是控制层,直接和机械臂驱动打交道。这一层接收决策层下发的目标坐标,完成逆运动学求解、舵机角度下发、夹爪开合控制。控制层是封闭的,不用向外暴露太多细节。

MCP 在这里充当智能体和 Agent API 之间的连接器。MCP 的全称是 Model Context Protocol,可以理解成 AI 应用界的 USB-C 接口——任何大模型客户端通过这个标准化协议,就能发现并调用外部工具。我写了一个机械臂 MCP Server,把 Agent API 的几个核心能力包装成 MCP Tools,这样 Claude Desktop、Cursor 甚至我自己写的小智能体都能直接“对话式”地控制这台机械臂。

2.2 为什么用 MCP 而不用传统函数调用

这里有个很实际的问题:为什么不直接写个 Python 函数库让大模型调用,非要套一层 MCP?

因为 MCP 解决的是“通用对接”问题。直接函数调用要求调用方和提供方用同一种语言、同一套运行环境,大模型客户端不可能为了你的机械臂去装一堆依赖。MCP 把工具定义、参数 schema、调用结果都标准化成 JSON-RPC,客户端只需要实现一个协议客户端,就能碰任何 MCP Server。现在 MCP 生态里连蓝湖、Figma、Mastergo 这些设计工具都有官方 Server,机器人控制这种长尾领域,自己写一个 Server 的成本其实很低,价值却很直接。

另外很多人把 MCP 和 Computer Use 混为一谈。Computer Use 是模型截屏、模拟鼠标键盘操作软件,优点是啥都能碰,缺点是慢、脆弱、不可控;MCP 则是把能力做成结构化接口,参数明确、返回明确。做机器人控制我肯定选 MCP,稳定性和可控性不是一个量级。

3. 硬件与环境:VisionFive 2 上装系统、接外设的真实体验

这一节讲硬件准备和系统搭建。如果你已经有一块跑起来的 VisionFive 2,可以跳过前面直接看 3.3。

3.1 VisionFive 2 的纸面参数和实际感受

VisionFive 2 用的是赛昉科技的 JH7110 芯片,4 核 SiFive U74 RISC-V 核心,主频 1.5GHz,可选 2/4/8GB LPDDR4 内存,板载千兆网口、USB 3.0、HDMI、M.2 接口和 40 针 GPIO。我手里的这台是 8GB 版本,跑这次工程绰绰有余。

实际用下来,CPU 性能大概是树莓派 4 的七成到八成,日常跑 Python、Node、Web 服务都没压力,瓶颈主要在浮点运算和 SIMD 向量指令上。RISC-V 虽然规定了 V 向量扩展,但官方镜像里的软件基本没启用,所以碰到矩阵运算密集型任务,比如深度学习推理,就比较吃亏。没有 NPU 这件事也得认清,JH7110 不带神经网络加速单元,所以我后面做模型量化是被逼出来的。

3.2 系统安装与 RISC-V 软件生态的第一次碰撞

VisionFive 2 的系统安装比树莓派稍微麻烦一点。官方提供 Debian 镜像,下载后用工具烧录到 TF 卡或 M.2 SSD,接 HDMI 显示器、键盘,开机就能进桌面。如果不想用显示器,也可以直接烧录后改配置文件,或者接串口线用串口终端登录,我为了省内存,全程是 headless 模式,只留 SSH 和串口。

真正折腾人的是软件生态。很多常用 Python 包在 riscv64 架构上没有预编译 wheel,比如 OpenCV、ONNX Runtime 这种 C++ 扩展库。试过直接 pip install,发现编译过程要半小时起步,还有可能因为内部汇编优化分支失败。解决办法有两个:一是去找社区构建好的 riscv64 wheel,二是源码编译前先关掉架构相关的优化开关。我最后在社区镜像里找到了几个关键包,省下了大量时间。

安装完系统第一件事建议是换一个网络延迟低的软件源,然后一次性把编译工具链装齐:build-essential、python3-dev、pip、git、cmake。后期编译、装包绕不开这些。

3.3 外设接线:相机、舵机臂和电平转换

硬件连接相对简单,但有一个坑必须提醒:GPIO 电平兼容。

VisionFive 2 的 40 针 GPIO 是 3.3V 逻辑电平,很多舵机机械臂的控制板是 5V 逻辑。中间没有电平转换模块的话,通信会出现随机丢帧,严重时会烧引脚。我的方案是给舵机臂控制板单独供电,UART 通信线上串了双向电平转换模块,信号用 3.3V 一侧接板子、5V 一侧接舵机控制板,实测非常稳定。

相机用的是普通 USB UVC 摄像头,分辨率 1280x720,插 USB 3.0 口。Linux 下用 v4l2 驱动,OpenCV 的 VideoCapture 直接能读到。选 USB 而不是 CSI 摄像头,是因为 CSI 接口在 RISC-V 板卡上的驱动支持参差不齐,USB 摄像头是标准协议,省心。

机械臂是常见的 6 自由度舵机机械臂,控制板通过串口通信,波特率 115200,支持角度指令和夹爪控制。我封装了一个 Python 驱动类,对外只暴露set_pose(x, y, z)open_gripper()close_gripper()这几个方法,内部做逆运动学求解和舵机角度下发。具体协议各家不一样,这里就不贴了,原则是把所有和硬件相关的代码隔离在一个模块里,其他层永远不直接碰串口。

4. 视觉感知:把 YOLOE 部署到 RISC-V 板卡

感知层是整个系统里最“重”的部分,也是这次工程最花时间的地方。不是 YOLOE 本身难用,而是让它在一颗没有 NPU 的 RISC-V 处理器上跑起来,需要做不少工程取舍。

4.1 为什么选 YOLOE 而不是传统固定类别检测器

传统 YOLO 系列模型训练时固定了一组类别,比如 COCO 的 80 类。你要检测一个模型没见过的物体,必须重新标注、重新训练,哪怕新物体和旧物体长得再像也没用。

YOLOE 的思路完全不同,它引入了可重参数化的视觉语言分类器,推理时可以通过文本提示或参考图片动态指定检测目标。简单说,模型在训练阶段学会了“什么是物体”的通用表征,检测什么由你下指令的那一刻决定。这特别适合机械臂场景:用户说“抓螺丝刀”,智能体把“螺丝刀”这个文本传给 YOLOE,模型就只检测螺丝刀;用户改口“抓红色杯子”,不需要重新训练,换个提示词就生效。

在我这台板卡上,我甚至不需要把全部 80 类都做进输出头,而是导出模型时只保留文本编码和检测头,推理时动态传入类别 embedding。这样做还有一个额外好处:减少了输出层的计算量。

4.2 模型导出、量化和推理代码

YOLOE 官方仓库提供了 PyTorch 实现和预训练权重,桌面 x86 机器上先导出 ONNX。导出时把动态轴固定住,尤其是 batch 维度和输入尺寸,后面量化会省很多事。导出完后,在 x86 机器上先用 ONNX Runtime 验证输出一致性,确保导出的模型和 PyTorch 原模型结果一致,再拿到板卡上跑。

量化是必须做的一步。VisionFive 2 没有 GPU 也没有 NPU,FP32 推理慢到没法用,int8 量化后速度能提升 2~3 倍,内存占用也会明显降低。我用的是 onnxruntime 自带的 PTQ 量化工具,校准数据就是工作台上几十张不同光照、不同物体摆放的照片。量化后精度下降一点,但对抓取任务来说,检测框稍微偏移的问题可以靠视觉闭环修正,后面会讲。

推理核心代码不长,我直接贴出来做个示范:

import cv2 import numpy as np import onnxruntime as ort class YOLOEInference: def __init__(self, onnx_path, conf_thres=0.35): self.session = ort.InferenceSession( onnx_path, providers=["CPUExecutionProvider"] ) self.conf_thres = conf_thres self.input_name = self.session.get_inputs()[0].name self.input_size = self.session.get_inputs()[0].shape[2] # 比如 320 def preprocess(self, frame_bgr): img = cv2.cvtColor(frame_bgr, cv2.COLOR_BGR2RGB) img = cv2.resize(img, (self.input_size, self.input_size)) img = img.astype(np.float32) / 255.0 img = np.transpose(img, (2, 0, 1))[None] return np.ascontiguousarray(img) def run(self, frame_bgr, text_embedding): input_tensor = self.preprocess(frame_bgr) outputs = self.session.run( None, {self.input_name: input_tensor, "text_emb": text_embedding}, ) return self.postprocess(outputs) def postprocess(self, outputs): # 这里假设导出的输出格式是 [1, N, 7],每行 x1,y1,x2,y2,score,cls boxes, scores = [], [] for det in outputs[0][0]: if det[4] >= self.conf_thres: boxes.append(det[:4].astype(int)) scores.append(float(det[4])) return boxes, scores

注意,text_emb是 YOLOE 文本提示对应的 embedding 向量,可以在板卡上用一个轻量文本编码器现场生成,也可以预先在 x86 机器上把常见物体名字都算好存下来。我选择后者,省去板上跑文本编码器的开销。

4.3 性能实测与提速手段

量化后的 YOLOE-S 在 VisionFive 2 上,我实际测下来:

  • 输入尺寸 640x640,int8,单帧推理约 2.5~3.5 秒。
  • 输入尺寸 320x320,int8,单帧推理约 0.9~1.4 秒。

这个速度做不了实时视频流目标跟踪,但对桌面抓取场景完全够用。我的做法是把相机采集和推理解耦,OpenCV 只负责抓帧,推理用独立线程按需触发,检测结果缓存起来。目标不会自己跑掉,一秒刷新一次位置信息,机械臂完全追得上。

如果想进一步提速,还有几个招:一是把 CPU 调成性能模式,绑定推理线程到固定核心,减少调度抖动;二是把预处理里的 BGR2RGB 和 resize 改成手写优化版本,省几个毫秒;三是对模型做结构化剪枝,不过这个工作量就大了,初学者不建议碰。我实测下来,绑核和性能模式大约能带来 15%~20% 的提升,聊胜于无,但顺手就做了。

5. 视觉闭环:从像素坐标到机械臂抓取

视觉系统看到的是像素坐标,机械臂运动需要的是台面坐标。这一步的精度直接影响抓取成功率,也是我这次实践里收获最大的一部分。

5.1 桌面场景的单应矩阵标定

很多机器人教程上来就让做相机内外参标定、畸变校正,对桌面机械臂来说有点杀鸡用牛刀。工作台是一个平面,相机固定俯拍,像素坐标和台面坐标之间近似满足一个单应变换,用一个 3x3 的单应矩阵 H 就能描述。

标定方法很简单:在工作台上放四个已知坐标的标记点,用视觉识别出它们的像素坐标,再对应到机械臂基座坐标系的物理坐标,调用 cv2.findHomography 求解 H。

import cv2 import numpy as np # 四个点在图像里的像素坐标,实际用视觉识别获得 pix = np.array( [[120, 240], [520, 260], [500, 520], [140, 500]], dtype=np.float32 ) # 对应在机械臂台面上的坐标,单位 mm,实际用示教或直尺量 base = np.array( [[100, 100], [300, 100], [300, 280], [100, 280]], dtype=np.float32 ) H, _ = cv2.findHomography(pix, base) def pixel_to_base(u, v): p = np.array([[[u, v]]], dtype=np.float32) out = cv2.perspectiveTransform(p, H) return float(out[0][0][0]), float(out[0][0][1])

标定时有两点经验:一是四个点尽量铺满整个工作区域,不要挤在一个角落,否则外推区域误差会很大;二是标记点最好用 Aruco 码,角点检测比手动点选稳定得多。我后来补了第五个验证点,每次标定完先验证一下,映射误差控制在 5mm 以内再投入使用。

5.2 闭环抓取流程与核心代码

标定只是开环映射,实际运行中还会有各种误差:舵机角度有死区、机械结构有间隙、夹爪中心不一定和计算坐标完全重合。如果只靠单应矩阵算一次坐标就让机械臂去抓,成功率大概只有六成。

闭环的思路是:抓取失败后,用视觉观察实际抓取位置和目标位置的偏差,把这个偏差反馈到下一次尝试的坐标里,不断逼近真实目标。这在控制论里就是典型的闭环反馈,只不过反馈信号来自视觉。

核心流程如下:

  1. YOLOE 检测目标物体,得到像素中心。
  2. 单应矩阵换算成台面坐标 (x, y)。
  3. 加上累计的闭环修正量 correction 后,控制机械臂移动到目标上方。
  4. 下探、夹爪闭合、抬起。
  5. 视觉验证目标是否还在原位置:还在说明没抓到,计算偏差更新 correction;不在了说明抓取成功。

代码如下:

def closed_loop_pick(self, target_label, max_retry=3): for attempt in range(max_retry): dets = self.detect_target(target_label) if not dets: return {"ok": False, "reason": "target not found"} u, v = self.target_center(dets[0]) x, y = self.pixel_to_base(u, v) x += self.correction[0] y += self.correction[1] self.arm.set_pose(x, y, self.pre_height) self.arm.open_gripper() self.arm.set_pose(x, y, self.grab_height) self.arm.close_gripper() self.arm.set_pose(x, y, self.pre_height) if self.verify_grab(target_label): self.correction = [0, 0] return {"ok": True, "attempts": attempt + 1} else: self.correction = self.compute_correction(target_label) return {"ok": False, "reason": "max retry exceeded"}

verify_grab怎么实现?我用了最简单的目标消失检测:夹爪抬起后,再次调用 YOLOE 看目标是否还在原来的抓取位置。如果目标消失了,说明被夹住了;如果还在,说明刚才那一下抓空了。有些场景目标会被夹爪遮挡,这时候可以加一道判断:夹爪闭合后检测舵机电流变化,堵转说明夹到东西了。两者结合,鲁棒性更高。

compute_correction的逻辑是:在当前视角下,识别目标当前像素位置,和预期抓取位置的像素位置做差,投影到台面坐标,乘以一个小于 1 的比例系数作为修正量。这里直接乘 1.0 容易振荡,乘 0.5 左右更稳,相当于控制论里的比例控制。

5.3 为什么“闭环”能容忍标定误差

我以前做过一版纯开环的系统:标定做得很仔细,单应矩阵误差在 3mm 以内,但实际抓取成功率还是上不去。后来发现误差主要不是来自标定,而是来自机械臂自身——舵机每次运动到位角度不完全一致,齿轮间隙、皮带打滑都会导致实际末端位置偏移。

闭环系统最妙的地方在于,它不关心误差具体来自哪里,只要视觉能观测到结果,就能把误差消掉。第一次抓偏了 10mm,视觉反馈回来,第二次就修正 5mm,第三次就修到 1mm 以内了。这就是为什么我做这个项目时,把视觉闭环当成比模型精度更重要的工程环节。模型检测框偏几个像素,在闭环面前根本不算事。

6. Agent API:用 FastAPI 给大模型一把控制机械臂的钥匙

感知和控制都就绪后,接下来要考虑怎么让 AI 智能体用起来顺手。我选择在板卡上跑一个 FastAPI 服务,把所有能力收敛成几个语义清晰的 HTTP 接口。

6.1 API 层要暴露哪些能力

设计 API 时我坚持一个原则:接口的语义要面向“智能体能理解的任务”,而不是面向硬件操作。比如我不会暴露“发送 0x01 0x03 到串口”这种接口,而是暴露“检测当前工作台上的物体”“抓取某个物体并放置到指定坐标”这类接口。

最终定了四个核心接口:

  • GET /api/objects:返回当前 YOLOE 检测到的所有物体名称和位置。
  • POST /api/pick:视觉闭环抓取指定物体,参数是物体名称。
  • POST /api/place:把夹爪抓着的物体放到指定坐标。
  • GET /api/status:返回机械臂当前状态,包括是否忙、当前位置、夹爪状态。

智能体不需要关心机械臂有几个关节、相机标定矩阵是什么,它只需要说“桌上有螺丝刀,把它放到 (300, 200)”,API 层负责翻译成底层控制指令。

6.2 核心代码与长任务处理

FastAPI 的代码非常直白:

from fastapi import FastAPI, HTTPException from pydantic import BaseModel app = FastAPI(title="Robot Arm Agent API") controller = RobotController() class PickRequest(BaseModel): object_name: str class PlaceRequest(BaseModel): x: float y: float @app.get("/api/objects") def list_objects(): return controller.detect_objects() @app.post("/api/pick") def pick(req: PickRequest): result = controller.vision_closed_loop_pick(req.object_name) if not result["ok"]: raise HTTPException(status_code=500, detail=result) return result @app.post("/api/place") def place(req: PlaceRequest): result = controller.place(req.x, req.y) if not result["ok"]: raise HTTPException(status_code=500, detail=result) return result @app.get("/api/status") def status(): return controller.get_status()

有个细节值得单独说:机械臂抓取一次要几秒钟,如果智能体用同步 HTTP 请求等待响应,长时间占用连接容易超时。我的做法是把抓取这类长任务拆成两步:第一步提交任务,立刻返回task_id;第二步通过轮询或者 WebSocket 获取结果。FastAPI 配合后台任务队列实现起来很顺手。不过这次 demo 为了追求直观,/api/pick还是同步的,我把 HTTP 超时时间调到了 30 秒,智能体那边同样把 tool 的 timeout 参数调大,两者对齐就不容易出问题。

这类 API 一般建议加一个简单的 API Key 认证,毕竟能控制机械臂的服务不应该裸奔在网上。我在中间件里校验了X-API-Key请求头,前后端约定好密钥,成本很低但能挡住绝大多数误访问。

7. MCP:把机械臂变成智能体的标准工具

Agent API 已经是一套干净的 HTTP 接口,但对大模型来说,它还缺少“发现能力”的入口。MCP 就是来补这一块的。MCP,全称 Model Context Protocol,是一套开放协议,定义了 AI 应用如何发现和调用外部工具、数据资源。我在这台 VisionFive 2 上跑了一个 MCP Server,把 Agent API 包装成标准工具,然后接入到大模型客户端。

7.1 五分钟理解 MCP 是什么

如果你写过 Alexa Skill 或者微信小程序,会很容易理解 MCP 的定位:MCP Server 就像小程序的后端,向外暴露若干“工具”和“资源”;MCP Client 负责和 Server 通信;大模型客户端(比如 Claude Desktop、Cursor)是 Host,内置了 MCP Client,用户配置一下就能让模型调用这些工具。

通信格式是 JSON-RPC 2.0,本地进程间传输走 stdio,远程走 HTTP+SSE。我这台板卡直接把 MCP Server 跑在机械臂所在的同一台 VisionFive 2 上,所以用 stdio 就够了,模型客户端通过 SSH 远程在这块板子上拉起 Server 进程,数据不经过公网,也省去认证的麻烦。

MCP 生态发展很快,蓝湖、Figma、Mastergo 这些设计工具都有官方 Server,数据库、浏览器、文件系统也都有成熟的社区实现,机器人控制这种行业垂直领域,自己写一个 Server 其实非常值得。

7.2 用 FastMCP 五分钟写一个机械臂 Server

Python SDK 里提供了FastMCP封装,定义工具只需一个装饰器。我做了一个机械臂 MCP Server,代码很简单:

from mcp.server.fastmcp import FastMCP import requests AGENT_BASE = "http://127.0.0.1:8000" mcp = FastMCP("robot-arm") @mcp.tool() def list_objects() -> list[str]: """返回工作台上当前检测到的所有目标物体名称。""" resp = requests.get(f"{AGENT_BASE}/api/objects", timeout=5) resp.raise_for_status() return [obj["name"] for obj in resp.json()] @mcp.tool() def pick_and_place(object_name: str, place_x: float, place_y: float) -> str: """视觉闭环抓取指定物体,并放置到目标坐标。""" resp = requests.post( f"{AGENT_BASE}/api/pick", json={"object_name": object_name}, timeout=30, ) resp.raise_for_status() pick_result = resp.json() if not pick_result["ok"]: return f"抓取失败: {pick_result}" resp = requests.post( f"{AGENT_BASE}/api/place", json={"x": place_x, "y": place_y}, timeout=30, ) resp.raise_for_status() return str(resp.json()) if __name__ == "__main__": mcp.run(transport="stdio")

这个 Server 暴露了两个工具:一个查询物体列表,一个执行“抓取并放置”。大模型会根据用户指令自动判断调用哪个工具、传什么参数。比如用户说“把桌子上的螺丝刀放到坐标 300 200”,模型会先调用list_objects看看有没有螺丝刀,然后调用pick_and_place,参数从用户指令里提取。

我在测试时发现,MCP 工具描述写得越具体,模型调用越准确。比如pick_and_place的描述里,我明确写了“这个工具会先进行视觉闭环抓取,如果目标不存在会返回失败原因”,模型遇到目标缺失时就不会硬调,而是先list_objects确认。

7.3 接入 Claude Desktop / Cursor 的配置与认证

在 Claude Desktop 的配置里添加 MCP Server,只需要编辑配置文件claude_desktop_config.json

{ "mcpServers": { "robot-arm": { "command": "python", "args": ["/home/starfive/robot_arm_mcp/server.py"] } } }

注意 command 要写绝对路径,因为 MCP Client 启动子进程时的工作目录不是你的项目目录。如果 Python 在虚拟环境里,建议把 command 写成虚拟环境里 python 的绝对路径,否则经常出现“找不到 mcp 模块”的报错。

Cursor 里配置也类似,在 MCP 配置面板里填命令和参数即可。配置完成后,智能体侧会自动发现list_objectspick_and_place两个工具,对话时直接说“帮我看看桌上有啥”,模型就会去调用工具,而不是凭想象回答。

关于认证多说一句:本地 stdio 传输通常不需要额外认证,因为只有本地用户能拉起来这个 Server。但如果把 MCP Server 改成远程模式,放到局域网甚至公网上,就必须考虑认证。MCP 规范现在支持 OAuth 2.1 风格的授权流程——客户端先向授权服务换取 token,再带着 token 访问资源服务上的工具接口,和常见的 GitHub OAuth 逻辑类似。如果你的场景需要远程控制,优先把 Agent API 那层加 API Key,再在 MCP Server 上做用户级授权,两层都别省。

还有多智能体并发的问题。如果多个智能体同时连到同一个 MCP Server,同时调用同一个机械臂,就会出现两个抓取任务互相打架。我在 Agent API 层加了一个全局互斥锁,同一时刻只允许一个抓取或放置任务执行,其他请求直接返回“设备忙”的状态码。实现就是用 FastAPI 依赖注入一个asyncio.Lock,逻辑很简单但对多 Agent 场景是必须的。

8. 实测踩坑记录:这些坑我替你踩过了

整个项目从零到跑通,遇到的问题不少,我把最重要的整理成一张速查表,再挑三个展开细说。

现象根本原因解决办法
int8 量化后目标完全检测不到校准数据集太少,量化精度崩了增加不同光照、角度的校准图,重跑量化
推理耗时明显偏高未开启 CPU 性能模式,线程频繁迁移绑定核心,设置 performance 调速器
串口控制机械臂偶发无响应GPIO 电平不匹配或供电不足加电平转换模块,舵机控制板独立供电
OpenCV 无法读取 USB 摄像头v4l2 驱动加载顺序问题先插相机再开机,或手动加载 uvcvideo 模块
MCP Server 能注册但工具调用超时后台任务太长,智能体侧 timeout 不够调大 tool timeout,或改异步任务+轮询
YOLOE 检测框抖动,抓取位置飘输入尺寸变化导致预处理不一致固定推理输入尺寸,关闭摄像头自动曝光
多智能体同时调用导致机械臂乱动没有互斥锁,多个任务并发执行Agent API 层加全局 asyncio.Lock

第一个坑:校准集太少导致量化失败。YOLOE int8 量化后,一开始模型把螺丝刀、杯子全部漏检。排查半天,发现是我偷懒,校准图片只拍了五张,而且都是同一个角度。后来重新拍了几十张,覆盖不同光照、不同摆放角度,量化后的模型才恢复正常。量化的校准集直接影响模型输出质量,这不是玄学,而是统计分布覆盖度的问题,千万别省。

第二个坑:MCP 工具超时。机械臂闭环抓取一次需要 3~5 秒,加上视觉推理可能接近 10 秒,但 MCP 客户端默认 timeout 往往只有 10 秒,抓取稍微慢一点就报错了。这个问题的排查思路是:先用 curl 直接调 Agent API 确认耗时,再检查 MCP Server 日志看工具是否执行完,最后调整客户端 timeout。不要一上来就怀疑 MCP 协议有问题,优先分层排查。

第三个坑:检测框抖动导致抓取位置飘。机械臂已经到位,但每次视觉检测的像素中心略有差异,导致末端的坐标来回跳。排查发现是 USB 摄像头的自动曝光和自动白平衡在作祟,画面亮度一变化,检测框中心就偏移十几个像素。解决办法是在 OpenCV 里手动关闭相机的自动曝光和自动白平衡参数,固定曝光时间后,抖动立刻小了很多。

排查这类问题我有一个习惯:先在 x86 桌面机上把链路完整跑通,再搬到 RISC-V 板卡上。桌面机有 GPU、有预编译 wheel,遇到问题更容易定位在业务代码还是平台差异。交叉验证后,RISC-V 板卡上就只需要处理性能和编译层面的问题,思路清晰很多。

9. 写在最后:RISC-V 机器人真正的门槛不在指令集

整个项目做完,我最大的体会是:RISC-V 跑机器人的瓶颈从来不是指令集本身,而是生态成熟度。

如果你用的芯片是 ARM 架构,OpenCV、ONNX Runtime、PyTorch 这些深度学习基础设施全都提供官方预编译包,pip install 一行命令就完事。到了 riscv64,很多库要么没有 wheel,要么文档不全,要么编译链有坑,开发者大量的时间被消耗在“让库能跑起来”而不是“让功能跑起来”这件事上。所以如果说 ARM 生态是四车道高速,RISC-V 现在更像刚通车的省道,能走,但要做好遇到落石的准备。

但反过来讲,也正因为生态还不完善,做 RISC-V 机器人项目反而倒逼我把每一层接口都拆得很干净:感知、决策、控制、工具接入,层与层之间严格通过 API 和协议通信。代码拆分合理之后,哪怕将来换了更强的 RISC-V 板卡,或者换到 ARM 平台,整个架构迁移成本都很低。这种“被逼出来的模块化”,是我这次实践里最值钱的收获。

再说一个后续可以扩展的方向:现在 MCP Server 只暴露了抓取和放置两个工具,其实可以把视觉闭环里“目标消失检测”也做成一个工具,让智能体自己判断抓取结果;也可以再加一个语音接口,用户直接说话,大模型解析成指令,再走 MCP 控制机械臂。这套架构的扩展空间还很大,RISC-V 板卡虽然算力有限,但作为 AI Agent 控制物理世界的“大脑中枢”,绰绰有余。

回到开头那个问题,下次再有人问我 RISC-V 能不能跑机器人,我会直接说:能跑,而且你完全可以在这上面做一套真正能抓东西的 AI 机械臂。指令集从来不是限制想象力的地方。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询