8浏览
查看: 8|回复: 0

[项目] 给小智AI装上“麦克纳姆轮”:双掌控板语音全向移动

[复制链接]
本帖最后由 云天 于 2026-8-4 20:34 编辑

       让小智AI不仅能听会说,还能驾驶麦克纳姆轮小车实现全向移动——前进、后退、横移、斜移、原地旋转,一切尽在语音指挥之中。
【项目缘起】
       小智AI是一款开源的端侧语音助手项目,基于ESP32实现,支持唤醒词、语音识别、大模型对话等功能。而掌控板作为国内创客教育常用的主控板,集成了OLED屏幕、按键、I2C接口等资源,性价比极高。之前我在DF创客社区看到了不少关于麦克纳姆轮小车的项目[color=var(--dsw-alias-label-secondary)]-,也有将小智AI移植到掌控板的尝试。但它们大多采用遥控器或手机APP控制,还没有人把小智AI的语音能力与麦克纳姆轮的全向移动特性结合起来。
       于是,我萌生了一个想法:用两块掌控板——一块跑小智AI固件作为“大脑”,一块跑MCP服务作为“手脚”——打造一辆可以通过自然语言语音控制的麦克纳姆轮全向移动小车。
       经过一段时间的折腾,我成功实现了这个想法。本文将分享完整的制作过程,希望能给同样想折腾的朋友一些启发。
【准备工作】
       硬件清单

硬件
数量
说明
掌控板 2.02块一块跑小智AI,一块跑MCP服务
行空板K10 IO扩展板1块连接掌控板与电机驱动模块
L298N直流电机驱动模块 DF-MD V1.32块驱动四个直流电机
麦克纳姆轮(含直流电机)4个左前、左后、右前、右后
I2S数字麦克风1个语音输入
I2S数字扬声器1个语音输出
7.4V锂电池1块为电机和驱动模块供电
杜邦线、螺丝等若干连接与固定
给小智AI装上“麦克纳姆轮”:双掌控板语音全向移动图3


给小智AI装上“麦克纳姆轮”:双掌控板语音全向移动图4

       软件环境
  • 小智AI端:ESP-IDF v5.x + 小智AI开源代码(从 GitHub 克隆)
  • MCP服务端:Arduino IDE 2.x + ESP32开发板包
  • 依赖库:xiaozhi-mcp、WebSockets、ArduinoJson

       系统架构
       整个系统采用双掌控板分工协作的架构:
给小智AI装上“麦克纳姆轮”:双掌控板语音全向移动图1
       工作流程:
  • 用户对小智AI说“前进3秒”
  • 小智AI通过ASR识别语音,LLM理解意图
  • 小智AI调用云端MCP接入点,通过WebSocket向MCP服务掌控板发送move_robot工具调用
  • MCP服务掌控板解析指令,驱动四个电机按麦轮运动学模型运转
  • 3秒后自动停止

【一、小智AI掌控板:让掌控板“能听会说”】
       1.1 硬件连接
       小智AI掌控板负责语音交互,需要连接I2S麦克风、I2S扬声器、OLED屏幕和按键。

外设
掌控板丝印
ESP32 GPIO
说明
麦克风 WSP1GPIO32I2S字选
麦克风 SCKP8GPIO26I2S时钟
麦克风 DINP13GPIO18I2S数据
扬声器 DOUTP14GPIO19I2S数据输出
扬声器 BCLKP15GPIO21I2S位时钟
扬声器 LRCKP16GPIO5I2S左右时钟
OLED SDA-GPIO23I2C数据
OLED SCL-GPIO22I2C时钟
按键A(唤醒)P5GPIO0BOOT按键
按键B(ASR)P11GPIO2语音识别触发
给小智AI装上“麦克纳姆轮”:双掌控板语音全向移动图5

       1.2 引脚配置修改
       小智AI的板级文件位于 main/boards/ 目录下。以 bread-compact-esp32 为模板,创建掌控板配置文件 main/boards/handpy-esp32/config.h:
  1. // main/boards/handpy-esp32/config.h
  2. #ifndef _BOARD_CONFIG_H_
  3. #define _BOARD_CONFIG_H_
  4. #include <driver/gpio.h>
  5. #define AUDIO_INPUT_SAMPLE_RATE  16000
  6. #define AUDIO_OUTPUT_SAMPLE_RATE 24000
  7. // ----- I2S 麦克风引脚 -----
  8. #define AUDIO_I2S_MIC_GPIO_WS   GPIO_NUM_32   // P1
  9. #define AUDIO_I2S_MIC_GPIO_SCK  GPIO_NUM_26   // P8
  10. #define AUDIO_I2S_MIC_GPIO_DIN  GPIO_NUM_18   // P13
  11. // ----- I2S 扬声器引脚 -----
  12. #define AUDIO_I2S_SPK_GPIO_DOUT GPIO_NUM_19   // P14
  13. #define AUDIO_I2S_SPK_GPIO_BCLK GPIO_NUM_21   // P15
  14. #define AUDIO_I2S_SPK_GPIO_LRCK GPIO_NUM_5    // P16
  15. // ----- 按键引脚 -----
  16. #define BOOT_BUTTON_GPIO        GPIO_NUM_0    // P5, A键(唤醒)
  17. #define TOUCH_BUTTON_GPIO       GPIO_NUM_NC
  18. #define ASR_BUTTON_GPIO         GPIO_NUM_2    // P11, B键(语音识别触发)
  19. // ----- OLED 显示屏(SH1106, 128x64, I2C)-----
  20. #define DISPLAY_SDA_PIN         GPIO_NUM_23
  21. #define DISPLAY_SCL_PIN         GPIO_NUM_22
  22. #define DISPLAY_WIDTH           128
  23. #define DISPLAY_HEIGHT          64
  24. #define DISPLAY_MIRROR_X        false
  25. #define DISPLAY_MIRROR_Y        false
  26. // ----- LED -----
  27. #define BUILTIN_LED_GPIO        GPIO_NUM_NC
  28. #endif
复制代码
      1.3 OLED屏幕配置(idf.py menuconfig)
       小智AI默认使用SSD1306驱动,掌控板使用的是SH1106(128x64),需要在配置中修改:
  1. cd xiaozhi-esp32
  2. idf.py menuconfig
复制代码
       进入路径:Component config → Xiaozhi Assistant → Display Configuration:
  • Display Type:选择 SH1106
  • Display Width:128
  • Display Height:64
  • I2C SDA Pin:23
  • I2C SCL Pin:22

       1.4 编译与烧录
  1. idf.py set-target esp32
  2. idf.py build
  3. idf.py flash monitor
复制代码
      1.5配置设备
       第一步:Wi-Fi 配网(为设备配置网络)
       烧录成功后,第一次启动设备会自动进入配网模式。
       连接设备热点:使用手机或电脑,在Wi-Fi列表中找到并连接以 Xiaozhi-XXXX 命名的热点,这个热点由掌控板发出,没有密码。
       访问配置页面:
              连接成功后,部分设备会自动弹出配置页面。
              如果没有自动弹出,请打开浏览器,在地址栏输入 http://192.168.4.1 并访问。
       配置Wi-Fi:
              在打开的页面中,选择你家里或办公室的2.4G Wi-Fi网络。
              输入该Wi-Fi的密码,点击“连接”按钮。
       等待重启:配置成功后,设备会显示“登录成功”并自动重启。重启后,设备将自动连接你刚才配置的Wi-Fi网络。
       第二步:添加设备到管理后台
       设备联网后,需要在小智AI平台完成绑定才能正常使用。
       获取设备验证码:
             设备成功联网后,屏幕会显示一个6位数字验证码。
              如果设备没有屏幕,也可以通过语音唤醒(说“你好,小智”)来获取语音播报的验证码。
       登录小智AI平台:在电脑或手机浏览器上访问 https://xiaozhi.me,点击“控制台”,并使用手机号注册或登录。
       创建智能体(如无可跳过):如果还没有智能体,需要在控制台创建一个。进入“智能体”页面,点击“新建智能体”并命名即可。
       添加设备:
              在控制台页面,点击“添加设备”。
              输入设备上显示的6位验证码,点击确定。
              在协议选择中,选择“开源版”。
       完成绑定:点击“开始使用”,设备即绑定成功。之后便可以正常与小智AI对话了。
给小智AI装上“麦克纳姆轮”:双掌控板语音全向移动图2

【二、MCP服务掌控板:让小车“动起来”】

       2.1 硬件连接
       MCP服务掌控板通过行空板K10 IO扩展板连接两块L298N电机驱动模块。
       电机分配:

电机
掌控板丝印
ESP32 GPIO
L298N模块
L298N端子
左后方向P8GPIO26模块1M1
左后PWMP9GPIO25模块1E1
左前方向P6GPIO16模块1M2
左前PWMP7GPIO17模块1E2
右前方向P13GPIO18模块2M1
右前PWMP14GPIO19模块2E1
右后方向P15GPIO21模块2M2
右后PWMP16GPIO5模块2E2
给小智AI装上“麦克纳姆轮”:双掌控板语音全向移动图6

       2.2 Arduino IDE程序
       MCP服务掌控板使用Arduino IDE编程,通过xiaozhi-mcp库注册move_robot工具。
       完整代码:
  1. #include <Arduino.h>
  2. #include <WiFi.h>
  3. #include <WebSocketMCP.h>
  4. #include <ArduinoJson.h>
  5. /********** WiFi 配置 ***********/
  6. const char* WIFI_SSID = "你的WiFi名称";
  7. const char* WIFI_PASS = "你的WiFi密码";
  8. /********** MCP 服务器地址 ***********/
  9. const char* MCP_ENDPOINT = "wss://api.xiaozhi.me/mcp/?token=你的token";
  10. /********** 电机引脚定义(掌控板丝印→GPIO) ***********/
  11. // 左后轮 (电机1, 模块1)
  12. #define DIR_LB  26   // P8
  13. #define PWM_LB  25   // P9
  14. // 左前轮 (电机2, 模块1)
  15. #define DIR_LF  16   // P6
  16. #define PWM_LF  17   // P7
  17. // 右前轮 (电机1, 模块2)
  18. #define DIR_RF  18   // P13
  19. #define PWM_RF  19   // P14
  20. // 右后轮 (电机2, 模块2)
  21. #define DIR_RB  21   // P15
  22. #define PWM_RB  5    // P16
  23. /********** 全局变量 ***********/
  24. WebSocketMCP mcpClient;
  25. bool wifiConnected = false;
  26. bool mcpConnected = false;
  27. bool isMoving = false;
  28. unsigned long moveStartTime = 0;
  29. float moveDuration = 0;
  30. String currentDirection = "stop";
  31. /********** setup ***********/
  32. void setup() {
  33.   Serial.begin(115200);
  34.   Serial.println("\n[MCP 麦克纳姆轮小车] 初始化...");
  35.   // 初始化电机引脚
  36.   pinMode(DIR_LB, OUTPUT); pinMode(PWM_LB, OUTPUT);
  37.   pinMode(DIR_LF, OUTPUT); pinMode(PWM_LF, OUTPUT);
  38.   pinMode(DIR_RF, OUTPUT); pinMode(PWM_RF, OUTPUT);
  39.   pinMode(DIR_RB, OUTPUT); pinMode(PWM_RB, OUTPUT);
  40.   stopAllMotors();
  41.   // 连接WiFi
  42.   WiFi.begin(WIFI_SSID, WIFI_PASS);
  43.   while (WiFi.status() != WL_CONNECTED) { delay(500); Serial.print("."); }
  44.   wifiConnected = true;
  45.   Serial.println("\nWiFi连接成功! IP: " + WiFi.localIP().toString());
  46.   // 初始化MCP
  47.   if (mcpClient.begin(MCP_ENDPOINT, onMcpConnectionChange)) {
  48.     Serial.println("[MCP] 初始化成功");
  49.   }
  50. }
  51. /********** loop ***********/
  52. void loop() {
  53.   mcpClient.loop();
  54.   // 定时停止检查
  55.   if (isMoving && millis() - moveStartTime >= (unsigned long)(moveDuration * 1000)) {
  56.     stopAllMotors();
  57.     isMoving = false;
  58.     Serial.println("[运动] 时间到,自动停止");
  59.   }
  60. }
  61. /********** MCP连接回调 ***********/
  62. void onMcpConnectionChange(bool connected) {
  63.   mcpConnected = connected;
  64.   if (connected) {
  65.     Serial.println("[MCP] 已连接,注册工具...");
  66.     registerMcpTools();
  67.   }
  68. }
  69. /********** 注册MCP工具 ***********/
  70. void registerMcpTools() {
  71.   mcpClient.registerTool(
  72.     "move_robot",
  73.     "控制麦克纳姆轮小车运动",
  74.     R"({
  75.       "type": "object",
  76.       "properties": {
  77.         "direction": {
  78.           "type": "string",
  79.           "enum": ["forward","backward","left","right",
  80.                    "left_forward","right_forward",
  81.                    "left_backward","right_backward",
  82.                    "left_shift","right_shift","stop"],
  83.           "description": "运动方向"
  84.         },
  85.         "duration": {"type": "number", "description": "持续时间(秒)"},
  86.         "speed": {"type": "integer", "description": "速度 0~255", "default": 200}
  87.       },
  88.       "required": ["direction","duration"]
  89.     })",
  90.     [](const String& args) -> WebSocketMCP::ToolResponse {
  91.       DynamicJsonDocument doc(256);
  92.       deserializeJson(doc, args);
  93.       String direction = doc["direction"].as<String>();
  94.       float duration = doc["duration"].as<float>();
  95.       int speed = doc.containsKey("speed") ? doc["speed"].as<int>() : 200;
  96.       if (direction == "stop" || duration <= 0) {
  97.         stopAllMotors();
  98.         return WebSocketMCP::ToolResponse("{"success":true,"message":"已停止"}");
  99.       }
  100.       setRobotMotion(direction, speed);
  101.       moveStartTime = millis();
  102.       moveDuration = duration;
  103.       isMoving = true;
  104.       return WebSocketMCP::ToolResponse(
  105.         "{"success":true,"direction":"" + direction +
  106.         "","duration":" + String(duration) + "}"
  107.       );
  108.     }
  109.   );
  110. }
  111. /********** 电机控制函数 ***********/
  112. void setMotor(int dirPin, int pwmPin, int speed, bool forward) {
  113.   digitalWrite(dirPin, forward ? HIGH : LOW);
  114.   analogWrite(pwmPin, speed);
  115. }
  116. void stopAllMotors() {
  117.   analogWrite(PWM_LB, 0); analogWrite(PWM_LF, 0);
  118.   analogWrite(PWM_RF, 0); analogWrite(PWM_RB, 0);
  119. }
  120. void setRobotMotion(const String &direction, int speed) {
  121.   int spLB=0, spLF=0, spRF=0, spRB=0;
  122.   bool dirLB=true, dirLF=true, dirRF=true, dirRB=true;
  123.   if (direction == "forward") {
  124.     spLB = spLF = spRF = spRB = speed;
  125.   } else if (direction == "backward") {
  126.     spLB = spLF = spRF = spRB = speed;
  127.     dirLB = dirLF = dirRF = dirRB = false;
  128.   } else if (direction == "left") {          // 原地左转
  129.     spLB = spLF = spRF = spRB = speed;
  130.     dirLB = dirLF = false;  // 左轮反转
  131.     dirRF = dirRB = true;   // 右轮正转
  132.   } else if (direction == "right") {         // 原地右转
  133.     spLB = spLF = spRF = spRB = speed;
  134.     dirLB = dirLF = true;   // 左轮正转
  135.     dirRF = dirRB = false;  // 右轮反转
  136.   } else if (direction == "left_shift") {    // 水平左移
  137.     spLF = spRB = speed; spLB = spRF = speed;
  138.     dirLF = false; dirRB = false;
  139.     dirLB = true;  dirRF = true;
  140.   } else if (direction == "right_shift") {   // 水平右移
  141.     spLF = spRB = speed; spLB = spRF = speed;
  142.     dirLF = true;  dirRB = true;
  143.     dirLB = false; dirRF = false;
  144.   } else if (direction == "left_forward") {  // 左前斜移
  145.     spLF = spLB = speed; spRF = spRB = 0;
  146.     dirLF = dirLB = true;
  147.   } else if (direction == "right_forward") { // 右前斜移
  148.     spRF = spRB = speed; spLF = spLB = 0;
  149.     dirRF = dirRB = true;
  150.   } else if (direction == "left_backward") { // 左后斜移
  151.     spLF = spLB = speed; spRF = spRB = 0;
  152.     dirLF = dirLB = false;
  153.   } else if (direction == "right_backward") {// 右后斜移
  154.     spRF = spRB = speed; spLF = spLB = 0;
  155.     dirRF = dirRB = false;
  156.   } else { stopAllMotors(); return; }
  157.   setMotor(DIR_LB, PWM_LB, spLB, dirLB);
  158.   setMotor(DIR_LF, PWM_LF, spLF, dirLF);
  159.   setMotor(DIR_RF, PWM_RF, spRF, dirRF);
  160.   setMotor(DIR_RB, PWM_RB, spRB, dirRB);
  161. }
复制代码
【三、麦克纳姆轮运动原理】
       麦克纳姆轮的设计原理是在车轮外环中安装了与轴心成45度角排列的辊子。当轮子旋转时,辊子与地面接触产生与轮轴呈45度的反推力,这个斜向推力可以被分解为纵向和横向两个向量。
       通过控制四个轮子的旋转速度和方向,可以将各轮的斜向推力叠加或抵消,从而实现:

运动模式
左前轮
右前轮
左后轮
右后轮
前进正转正转正转正转
后退反转反转反转反转
左移正转反转反转正转
右移反转正转正转反转
原地左转反转正转反转正转
原地右转正转反转正转反转

       注意:具体转向取决于麦克纳姆轮的安装方向(左旋/右旋),上述表格可能需要根据实际轮子方向微调。
【四、MCP通信原理】
       MCP(Model Context Protocol) 是小智AI实现设备控制的核心协议。其工作流程如下:
  • 设备注册:MCP服务掌控板通过WebSocket连接到小智AI云端MCP接入点,注册move_robot工具及其参数
  • 语音解析:用户对小智AI说“前进3秒”,ASR转为文本,LLM解析意图
  • 工具调用:小智AI根据意图生成JSON参数{"direction":"forward","duration":3},通过WebSocket发送给MCP服务掌控板
  • 执行反馈:MCP服务掌控板解析参数,驱动电机,返回执行结果

       这种架构的优势在于:小智AI只负责“听懂”和“决策”,具体的硬件控制逻辑由MCP服务掌控板完成,两者通过标准协议解耦,便于扩展和维护。
【五、组装与调试
       5.1 组装步骤
  • 安装麦克纳姆轮:将四个麦克纳姆轮安装到直流电机上,注意左右两侧轮子的辊子方向应镜像对称
  • 固定电机:将电机安装到小车底盘上
  • 安装扩展板:将行空板K10 IO扩展板插入掌控板(MCP服务端)
  • 连接电机驱动:按引脚表连接两块L298N模块与扩展板
  • 连接小智AI外设:按引脚表连接I2S麦克风、I2S扬声器、OLED屏幕
  • 供电:7.4V锂电池为L298N模块供电,掌控板通过USB或电池供电

       5.2 调试要点
  • WiFi连接:两块掌控板需连接同一局域网
  • MCP接入点:从小智AI平台获取token,填入MCP服务程序
  • 电机方向验证:先不安装轮子,测试每个电机转向是否正确
  • 运动测试:从小到大逐步测试各方向运动

【六、演示效果】

       通过语音指令,小车可以实现:
  • “前进3秒” → 直线前进
  • “后退2秒” → 直线后退
  • “左移5秒” → 水平向左平移
  • “右移5秒” → 水平向右平移
  • “左转” → 原地逆时针旋转
  • “右转” → 原地顺时针旋转
  • “左前” → 向左前方斜移
  • “右后” → 向右后方斜移

【七、注意事项】
  • 电源独立:L298N模块的电机电源(7.4V)与掌控板的逻辑电源(5V/USB)需分开供电,但必须共地
  • 引脚电平:掌控板GPIO为3.3V逻辑,L298N模块兼容3.3V信号
  • 轮子安装:麦克纳姆轮有左旋和右旋之分,安装错误会导致运动方向混乱
  • 安全第一:调试时先卸下轮子,确认电机转向正确后再安装

【八、项目总结】
       本项目实现了双掌控板分工协作的语音控制麦克纳姆轮小车:
  • 小智AI掌控板:通过ESP-IDF编译小智AI固件,适配掌控板引脚,实现语音唤醒、语音识别、大模型对话
  • MCP服务掌控板:通过Arduino IDE + xiaozhi-mcp库,注册MCP工具,实现麦克纳姆轮的全向运动控制
  • 双板协作:小智AI负责“听懂”,MCP服务负责“执行”,通过WebSocket MCP协议通信

       这一方案将小智AI强大的语音交互能力与麦克纳姆轮独特的全向移动特性完美结合,为创客教育提供了一个兼具AI语音交互与机器人运动控制的综合实践项目。
全部代码已在文中开源,欢迎大家一起折腾!如有问题,欢迎在评论区交流讨论。







您需要登录后才可以回帖 登录 | 立即注册

本版积分规则

为本项目制作心愿单
购买心愿单
心愿单 编辑
[[wsData.name]]

硬件清单

  • [[d.name]]
btnicon
我也要做!
点击进入购买页面
上海智位机器人股份有限公司 沪ICP备09038501号-4 备案 沪公网安备31011502402448

© 2013-2026 Comsenz Inc. Powered by Discuz! X3.4 Licensed

mail