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

[项目] 给小智AI装上“暴力心脏”:ESP32-C3语音控制无刷电机

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

       让小智AI不仅能听会说,还能驱动无刷电机高速旋转——从RGB灯测试到暴力风扇,一步步打造你的AI语音控制风扇。
【项目缘起】
       小智AI是一款开源的端侧语音助手项目,基于ESP32实现,支持唤醒词、语音识别、大模型对话等功能。它的代码结构清晰,硬件抽象层完善,非常适合作为智能硬件的“大脑”。而MCP(Model Context Protocol) 则让AI可以直接调用设备端的工具函数,实现语音控制硬件——小智AI利用Qwen/DeepSeek等大模型的AI能力,通过MCP协议实现多端控制。
       这前我做了一个麦克纳姆轮小车项目,用两块掌控板分别跑小智AI和MCP服务,实现了语音控制小车全向移动。做完小车后我就在想:能不能用同样的思路,做一个更“暴力”的东西?
       于是就有了这个项目:用ESP32-C3跑MCP服务,通过小智AI语音控制无刷电机。无刷电机响应快、转速高、噪音大,配上大尺寸扇叶,吹出来的风堪称“暴力”。夏天在桌前喊一声“风扇开到50%”,瞬间凉风扑面——这种体验比按遥控器爽太多了。
       本文将分享完整的制作过程,从RGB灯测试开始,到最终实现语音控制无刷电机转速和运行时间。
【项目架构】
       整个系统采用 “小智AI(云端大脑)+ MCP服务(本地执行)” 的分工架构:
给小智AI装上“暴力心脏”:ESP32-C3语音控制无刷电机图1
       工作流程:
  • 用户对小智AI说“风扇开到50%,运行5秒”
  • 小智AI通过ASR识别语音,LLM理解意图
  • 小智AI调用云端MCP接入点,通过WebSocket向ESP32-C3发送set_motor工具调用
  • ESP32-C3解析指令,生成对应PWM信号驱动ESC
  • 无刷电机按指定转速运转,5秒后自动停止

【准备工作】
       硬件清单

硬件
数量
说明
ESP32-C3开发板1块核心主控
无刷电机(BLDC)1个建议30A以上,KV值2000左右
无刷电机电调(ESC)1个30A以上,支持50Hz PWM信号
7.4V~11.1V锂电池1块为ESC和电机供电
大尺寸风扇扇叶1个适配电机轴径
RGB LED模块1个前期测试用(可选)
杜邦线、电源线若干连接与固定
     
  安全提醒:无刷电机转速极高,测试时务必卸下扇叶,待程序调试完成、确认电机转向正确后再安装扇叶!
【硬件组装】
给小智AI装上“暴力心脏”:ESP32-C3语音控制无刷电机图2

       ESP32-S3圆形显示屏开发板,安装小智固件(https://www.dfrobot.com.cn/goods-4194.html)。

给小智AI装上“暴力心脏”:ESP32-C3语音控制无刷电机图4


给小智AI装上“暴力心脏”:ESP32-C3语音控制无刷电机图6


给小智AI装上“暴力心脏”:ESP32-C3语音控制无刷电机图3


给小智AI装上“暴力心脏”:ESP32-C3语音控制无刷电机图5

【软件环境】
  • MCP服务端:Arduino IDE 2.x + ESP32开发板包 2.0.17
  • 依赖库:xiaozhi-mcp、WebSockets、ArduinoJson

【第一步:前期测试——控制RGB灯】
       在驱动无刷电机之前,我建议先用RGB灯做测试。原因很简单:RGB灯比电机安全得多,万一代码写错了,最多灯不亮,不会造成物理伤害。而且通过控制RGB灯,可以完整验证整个MCP通信链路是否畅通。
       硬件连接
       这里用的是DFRobot的“柔性RGB LCD多彩发光板”,本质上是一个由红、绿、蓝三色LED组成的共阳极发光板。引脚从上到下依次为:红、电源正极、绿、蓝。
  • VCC(共阳极)→ ESP32-C3的3.3V
  • R(红) → GPIO0
  • G(绿) → GPIO1
  • B(蓝) → GPIO2

      
       注意:该发光板极限正向电流为3-15mA【用户提供】。建议在红、绿、蓝三个控制引脚与ESP32之间各串联一个约200Ω的限流电阻,防止电流过大损坏引脚。
       Arduino代码(RGB灯MCP控制)
  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. /********** RGB LED 引脚定义 ***********/
  11. #define RED_PIN   0
  12. #define GREEN_PIN 1
  13. #define BLUE_PIN  2
  14. /********** 全局变量 ***********/
  15. WebSocketMCP mcpClient;
  16. bool wifiConnected = false;
  17. bool mcpConnected = false;
  18. void setup() {
  19.   Serial.begin(115200);
  20.   Serial.println("\n[ESP32-C3 MCP RGB灯] 初始化...");
  21.   pinMode(RED_PIN, OUTPUT);
  22.   pinMode(GREEN_PIN, OUTPUT);
  23.   pinMode(BLUE_PIN, OUTPUT);
  24.   // 连接WiFi
  25.   WiFi.begin(WIFI_SSID, WIFI_PASS);
  26.   while (WiFi.status() != WL_CONNECTED) { delay(500); Serial.print("."); }
  27.   wifiConnected = true;
  28.   Serial.println("\nWiFi连接成功!");
  29.   // 初始化MCP
  30.   if (mcpClient.begin(MCP_ENDPOINT, onMcpConnectionChange)) {
  31.     Serial.println("[MCP] 初始化成功");
  32.   }
  33. }
  34. void loop() {
  35.   mcpClient.loop();
  36. }
  37. void onMcpConnectionChange(bool connected) {
  38.   mcpConnected = connected;
  39.   if (connected) {
  40.     Serial.println("[MCP] 已连接,注册工具...");
  41.     registerMcpTools();
  42.   }
  43. }
  44. void registerMcpTools() {
  45.   mcpClient.registerTool(
  46.     "set_rgb",
  47.     "设置RGB LED颜色",
  48.     R"({
  49.       "type": "object",
  50.       "properties": {
  51.         "r": {"type": "integer", "minimum": 0, "maximum": 255},
  52.         "g": {"type": "integer", "minimum": 0, "maximum": 255},
  53.         "b": {"type": "integer", "minimum": 0, "maximum": 255}
  54.       },
  55.       "required": ["r", "g", "b"]
  56.     })",
  57.     [](const String& args) -> WebSocketMCP::ToolResponse {
  58.       DynamicJsonDocument doc(256);
  59.       deserializeJson(doc, args);
  60.       int r = doc["r"].as<int>();
  61.       int g = doc["g"].as<int>();
  62.       int b = doc["b"].as<int>();
  63.       // 共阳极:取反输出
  64.       analogWrite(RED_PIN, 255 - r);
  65.       analogWrite(GREEN_PIN, 255 - g);
  66.       analogWrite(BLUE_PIN, 255 - b);
  67.       return WebSocketMCP::ToolResponse(
  68.         "{"success":true,"r":" + String(r) +
  69.         ","g":" + String(g) + ","b":" + String(b) + "}"
  70.       );
  71.     }
  72.   );
  73. }
复制代码
      通过这个测试,可以验证:
  • WiFi连接是否正常
  • MCP接入点token是否有效
  • 小智AI能否正确调用工具

       测试成功后,再进入下一步——驱动无刷电机。
【第二步:无刷电机控制原理】
       ESC(电子调速器)工作原理
       无刷电机本身有三根相线,需要ESC(Electronic Speed Controller,电子调速器) 来驱动。ESC接收的是50Hz的PWM信号,脉宽范围通常为:

油门
脉宽
说明
0%1000µs最小油门,电机停止
50%1500µs中速
100%2000µs最大油门

       这个信号和舵机控制信号完全一样,所以我们可以用Servo库或LEDC外设来生成-。
       为什么用LEDC而非Servo库?
       在ESP32-C3上,我尝试过两种方案:
  • ESP32Servo库:代码简洁,但ESP32-C3的Servo库支持不够完善,容易出现编译问题-。
  • LEDC直接控制:使用ESP32的LEDC外设,手动配置50Hz频率和10位分辨率,信号更稳定、可控性更强。

       最终我选择了LEDC方案,信号精度更高,且不受库版本限制。
【第三步:ESP32-C3 MCP服务端代码】
       硬件连接

ESP32-C3引脚
连接
说明
GPIO0ESC信号线PWM输出
GNDESC地线共地
3.3V(可选)ESC逻辑电源

       ESC供电:ESC的电源线(红/黑)连接锂电池(7.4V~11.1V),必须与ESP32-C3共地。
       完整代码
  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. /********** 无刷电机引脚定义 ***********/
  11. #define MOTOR_PIN  0
  12. /********** ESC 脉宽参数 ***********/
  13. #define MIN_PULSE  1000 // 0% 油门
  14. #define MAX_PULSE  2000 // 100% 油门
  15. #define PWM_PERIOD 20000 // 周期 20000μs
  16. /********** LEDC 参数 ***********/
  17. #define PWM_FREQ   50
  18. #define PWM_RES    10
  19. /********** 全局变量 ***********/
  20. WebSocketMCP mcpClient;
  21. bool wifiConnected = false, mcpConnected = false;
  22. bool motorRunning = false;
  23. unsigned long motorStartTime = 0;
  24. float motorDuration = 0;
  25. int targetSpeed = 0;
  26. int currentSpeed = 0;
  27. unsigned long lastStepTime = 0;
  28. const int STEP_DELAY = 20;
  29. /********** setup ***********/
  30. void setup() {
  31.   Serial.begin(115200);
  32.   Serial.println("\n[ESP32-C3 MCP 无刷电机] 初始化...");
  33.   // 配置LEDC PWM
  34.   ledcSetup(0, PWM_FREQ, PWM_RES);
  35.   ledcAttachPin(MOTOR_PIN, 0);
  36.   // ESC校准
  37.   Serial.println("开始ESC校准...");
  38.   calibrateESC();
  39.   Serial.println("校准完成");
  40.   // 连接WiFi
  41.   WiFi.begin(WIFI_SSID, WIFI_PASS);
  42.   while (WiFi.status() != WL_CONNECTED) { delay(500); Serial.print("."); }
  43.   wifiConnected = true;
  44.   Serial.println("\nWiFi连接成功!");
  45.   // 初始化MCP
  46.   if (mcpClient.begin(MCP_ENDPOINT, onMcpConnectionChange)) {
  47.     Serial.println("[MCP] 初始化成功");
  48.   }
  49. }
  50. /********** loop ***********/
  51. void loop() {
  52.   mcpClient.loop();
  53.   updateMotor();
  54.   // 平滑调速
  55.   if (motorRunning || targetSpeed != currentSpeed) {
  56.     if (millis() - lastStepTime >= STEP_DELAY) {
  57.       lastStepTime = millis();
  58.       if (currentSpeed < targetSpeed) currentSpeed++;
  59.       else if (currentSpeed > targetSpeed) currentSpeed--;
  60.       setThrottle(currentSpeed);
  61.     }
  62.   }
  63. }
  64. /********** ESC校准 ***********/
  65. void calibrateESC() {
  66.   int dutyMax = (MAX_PULSE * 1023) / PWM_PERIOD;
  67.   ledcWrite(0, dutyMax);
  68.   delay(2000);
  69.   int dutyMin = (MIN_PULSE * 1023) / PWM_PERIOD;
  70.   ledcWrite(0, dutyMin);
  71.   delay(2000);
  72.   ledcWrite(0, 0);
  73. }
  74. /********** MCP连接回调 ***********/
  75. void onMcpConnectionChange(bool connected) {
  76.   mcpConnected = connected;
  77.   if (connected) {
  78.     Serial.println("[MCP] 已连接,注册工具...");
  79.     registerMcpTools();
  80.   }
  81. }
  82. /********** 注册MCP工具 ***********/
  83. void registerMcpTools() {
  84.   mcpClient.registerTool(
  85.     "set_motor",
  86.     "控制无刷电机转速(0~100%)和运行时间(秒)",
  87.     R"({
  88.       "type": "object",
  89.       "properties": {
  90.         "speed": {"type": "integer", "minimum": 0, "maximum": 100},
  91.         "duration": {"type": "number", "description": "运行时间(秒)"}
  92.       },
  93.       "required": ["speed"]
  94.     })",
  95.     [](const String& args) -> WebSocketMCP::ToolResponse {
  96.       DynamicJsonDocument doc(256);
  97.       deserializeJson(doc, args);
  98.       int speed = doc["speed"].as<int>();
  99.       speed = constrain(speed, 0, 100);
  100.       float duration = doc.containsKey("duration") ? doc["duration"].as<float>() : 0;
  101.       targetSpeed = speed;
  102.       if (duration > 0) {
  103.         motorRunning = true;
  104.         motorStartTime = millis();
  105.         motorDuration = duration;
  106.       } else {
  107.         motorRunning = false;
  108.       }
  109.       return WebSocketMCP::ToolResponse(
  110.         "{"success":true,"speed":" + String(speed) +
  111.         ","duration":" + String(duration) + "}"
  112.       );
  113.     }
  114.   );
  115. }
  116. /********** 电机控制 ***********/
  117. void setThrottle(int percent) {
  118.   percent = constrain(percent, 0, 100);
  119.   int pulse = map(percent, 0, 100, MIN_PULSE, MAX_PULSE);
  120.   pulse = constrain(pulse, MIN_PULSE, MAX_PULSE);
  121.   int duty = (pulse * 1023) / PWM_PERIOD;
  122.   duty = constrain(duty, 0, 1023);
  123.   ledcWrite(0, duty);
  124. }
  125. void stopMotor() {
  126.   targetSpeed = 0;
  127.   currentSpeed = 0;
  128.   setThrottle(0);
  129.   motorRunning = false;
  130. }
  131. void updateMotor() {
  132.   if (!motorRunning) return;
  133.   if (millis() - motorStartTime >= (unsigned long)(motorDuration * 1000)) {
  134.     stopMotor();
  135.     Serial.println("[电机] 定时结束,自动停止");
  136.   }
  137. }
复制代码
       代码关键点解读
  • ESC校准:calibrateESC() 在上电时先发送最大油门(2000µs),再降至最小油门(1000µs),让ESC学习脉宽范围。这一步非常重要,否则电机可能在中间转速区间出现抖动。
  • 平滑调速:targetSpeed 和 currentSpeed 分离,速度以每20ms步进1%的方式渐变,避免突变导致电机抖动。
  • 定时停止:若指定了duration,超时后自动调用stopMotor()。
  • MCP工具注册:向小智AI平台注册set_motor工具,支持speed(0~100%)和duration(秒)两个参数。

【第四步:配网与设备绑定】
       代码烧录成功后,需要让小智AI掌控板联网并绑定到平台。
       配网步骤
  • 连接设备热点:掌控板启动后会发出名为 Xiaozhi-XXXX 的WiFi热点(无密码),用手机或电脑连接它。
  • 访问配置页面:连接成功后,打开浏览器访问 http://192.168.4.1,会看到配网页面。
  • 配置Wi-Fi:在页面中选择你家里的2.4G Wi-Fi网络,输入密码,点击“连接”。
  • 等待重启:配置成功后,设备会显示“登录成功”并自动重启,之后会自动连接刚才配置的Wi-Fi。

       添加设备到小智AI平台
  • 获取验证码:设备联网后,屏幕会显示一个6位数字验证码。
  • 登录平台:在浏览器访问 [color=var(--dsw-alias-brand-text)]https://xiaozhi.me,点击“控制台”,用手机号注册或登录。
  • 创建智能体(如已创建可跳过):进入“智能体”页面,点击“新建智能体”并命名。
  • 添加设备:点击“添加设备”,输入设备上显示的6位验证码,协议选择“开源版”。
  • 完成绑定:点击“开始使用”,设备即绑定成功。

【第五步:语音指令测试】
       绑定成功后,就可以通过语音控制无刷电机了。
       常用指令示例

语音指令
实际效果
“风扇开到50%,运行5秒”50%油门运转5秒后自动停止
“风扇30%”30%油门持续运转
“关闭风扇”停止电机

       MCP工具调用格式
       小智AI后端会生成如下JSON调用:
  1. {
  2.   "tool": "set_motor",
  3.   "arguments": {
  4.     "speed": 50,
  5.     "duration": 5
  6.   }
  7. }
复制代码

【项目总结】
       本项目实现了 ESP32-C3 + 小智AI + MCP 的语音控制无刷电机风扇:
  • 小智AI端:通过xiaozhi.me平台提供语音识别、意图理解、语音合成能力
  • MCP服务端:ESP32-C3运行Arduino程序,注册set_motor工具,通过LEDC生成50Hz PWM信号控制ESC
  • 语音控制:支持0~100%无级调速,支持定时运行,自动停止

       技术亮点:
  • 使用MCP协议实现AI与硬件的标准化通信-
  • LEDC直接生成PWM信号,无需依赖Servo库
  • ESC校准 + 平滑调速,保证电机稳定运行
  • RGB灯先期测试,降低调试风险





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

本版积分规则

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

硬件清单

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

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

mail