回到首页 返回首页
回到顶部 回到顶部
返回上一页 返回上一页

小智语音控制ESP32 S3 双电机小车|极简MCP插件教程 简单

头像 rzyzzxw 2025.08.12 38 0

8.12

 

【目标任务】

小智语音控制ESP32 S3,想控制双电机,做小车,控制舵机,然后可以控制我的仰望小车了。

在这个帖子中,记录的是双电机小车的探索。所用的电机驱动模块是经典的红色电机驱动模块L298N。

87647464fd2b75fe12d1b58980d6858.jpg

小车做成样子:

0f679f9195f1317341811f071b41091.jpg

编程环境配置教程:

让小智语音控制ESP32 S3点灯|极简MCP插件教程- Makelog(造物记)

材料清单

  • K10小智AI X1
  • L298N电机驱动模块 X1
  • TT电机 X2
  • 车架及车轮 X1
  • ESP32 S3-DevKitC-1开发板(N16R8) X1

步骤1 L298N的基本参数和两轮小车电机接线

L298N参数:
参数一般只关心如下两个:
逻辑电压(控制端口电压):5V
驱动电压(电机电压):5V-35V (我计划用7V锂电供电,在这个范围中)
驱动电流(电机电流):2A(MAX单桥)

意味着这个板子适合用5V引脚的主控板驱动(例如arudino系列),而3.3V引脚的主控板可能就不能用了(例如mcirobit、掌控板、esp32)。这个板子驱动的一般都是小电流的电机,例如TT电机(小黄马达)、N20电机这一类的。

接下来看L298N的控制方法:

image.png

引脚说明:
输出A、输出B:接两个电机,不分正负,反了只会反转。(换线头可以调方向)
GND:电源地,需要与主控板的GND用一根杜邦线连接到一起(共地)。(这一点很重要的)

12V供电:给电机供电的接口,此处要独立供电。
5V供电:此为电压输出口,可以使用这个口给主控板供5V电源,但不推荐这么用,所以就让它空着好了。
板载5V使能:此跳线帽接起来之后上面的“5V供电”口才有5V电压输出。

通道A、通道B使能(ENA、ENB):此接口高电平则电机可以运行,低电平则电机停止运行,一般直接跳线帽短接到高电平,即一直使能,可以拔掉跳线帽接到主控板pwm模拟输入端口实现调速(我要调速,就要拔掉跳帽了

逻辑输入(IN1、IN2、IN3、IN4):这两个接口控制电机正反转,IN1和IN2控制电机M1,IN3和IN4控制电机M2.

具体控制信号如下图(重点图片):
image.png
简单理解(亮工神总结)
电源接线:
从12V和GND供6-12V的电压。
L298N和主控板的GND使用一根线连接起来。
板载5V使能跳线帽不动。

电机接线:
两个电机分别接到M1和M2上。

信号控制:
ENA和ENB跳线帽拔掉,使用杜邦线分别接到主控板两个PWM模拟输出口。
IN1和IN2,IN3和IN4分别接到主控板数字口。


一图说明:
Arduino uno为例
image.png

我用L298N_红色直流电机驱动模块和ESP32 S3的接线方式:

image.png

L298N电源

image.png

ESP32单独供电


提示:记得L298N和主控板的GND使用一根杜邦线连接起来
电机1、2分别接电机接口 不分正负,换线头可以调方向
7V锂电正极 接 12V接口 负极接GND口
ESP32 S3用3.7v锂电供电
image.png

步骤2 L298N库安装和电机转动测试

工具-库管理:安装L298N库(轻量级基础控制)​​

​特点​​:专为L298N设计,接口简单(如DCMotor motor1(L298N::A, L298N::B)),支持速度(setSpeed())和方向控制。

​适用场景​​:基础正反转、调速需求,代码简洁易上手。

​优点​​:

无需复杂配置,直接兼容ESP32的PWM引脚。

资源占用低,适合实时性要求高的小车项目。

​缺点​​:功能较基础,不支持高级运动控制(如闭环调速)。

​安装​​:通过Arduino库管理器搜索安装。

image.png

先用示例测试一个电机能否运行。

代码
#include <L298N.h>
// 定义电机引脚(以ESP32 S3为例)
#define ENA 14  // PWM使能引脚
#define IN1 12  // 方向引脚1
#define IN2 13  // 方向引脚2

L298N motor(ENA, IN1, IN2);  // 初始化电机对象

void setup() {
  motor.setSpeed(150);  // 初始速度(0-255)
}

void loop() {
  motor.forward();  // 前进
  delay(1000);
  motor.backward(); // 后退
  delay(1000);
}

通过了,再测试双电机能否运行。

代码
#include <L298N.h>
// 定义电机引脚(以ESP32 S3为例)

#define ENA 14
#define IN1 12
#define IN2 13

#define ENB 17
#define IN3 15
#define IN4 16

// 初始化电机对象
L298N leftMotor(ENA, IN1, IN2);
L298N rightMotor(ENB, IN3, IN4);

void setup() {
  leftMotor.setSpeed(150);  // 初始速度(0-255)
  rightMotor.setSpeed(150); 
}

void loop() {
  leftMotor.forward();  // 前进
  rightMotor.forward();
  delay(1000);
  leftMotor.backward(); // 后退
  rightMotor.backward(); 
  delay(1000);
}

步骤3 小车控制基本版

整合基础示例和L298N基本功能,得到小车控制基础版代码,简单控制小车的前进、后退、左转、右转和停止,并且可以调节速度。

代码
#include <WiFi.h>
#include <WebSocketMCP.h>
#include <L298N.h>
#include <ArduinoJson.h>

// 硬件配置(根据实际接线修改)
#define ENA 14   // 左侧电机PWM
#define IN1 12   // 左侧电机方向1
#define IN2 13   // 左侧电机方向2
#define ENB 17   // 右侧电机PWM
#define IN3 15   // 右侧电机方向1
#define IN4 16   // 右侧电机方向2

// WiFi配置
const char* ssid = "******";
const char* password = "******";
const char* mcpEndpoint = "ws://api.xiaozhi.me/mcp/?token=*******";
WebSocketMCP mcpClient;

// 电机对象初始化
L298N leftMotor(ENA, IN1, IN2);
L298N rightMotor(ENB, IN3, IN4);

// 全局变量
int currentSpeed = 150;  // 默认速度(0-255)

// 方向枚举
enum Direction { STOP, FORWARD, BACKWARD, LEFT, RIGHT };

// 电机控制函数(简化版)
void controlMotors(Direction dir) {
  switch(dir) {
    case FORWARD:
      leftMotor.forward();  // 左轮正转
      rightMotor.forward(); // 右轮正转
      break;
    case BACKWARD:
      leftMotor.backward(); // 左轮反转
      rightMotor.backward();// 右轮反转
      break;
    case LEFT:
      leftMotor.backward(); // 左轮反转 → 向右转弯
      rightMotor.forward(); // 右轮正转
      break;
    case RIGHT:
      leftMotor.forward();  // 左轮正转
      rightMotor.backward();// 右轮反转 → 向左转弯
      break;
    default:  // STOP
      leftMotor.stop();
      rightMotor.stop();
  }
  // 应用当前速度
  leftMotor.setSpeed(currentSpeed);
  rightMotor.setSpeed(currentSpeed);
}

// 连接状态回调
void onConnectionStatus(bool connected) {
  Serial.println(connected ? "[MCP] 已连接" : "[MCP] 断开");
  if (connected) registerMcpTools();
}

// 注册MCP工具(语义化参数)
void registerMcpTools() {
  // 运动控制工具
  mcpClient.registerTool(
    "car_move",
    "控制小车运动",
    "{\"type\":\"object\",\"properties\":{\"action\":{\"type\":\"string\",\"enum\":[\"FORWARD\",\"BACKWARD\",\"LEFT\",\"RIGHT\",\"STOP\"]}}}",
    [](const String& args) {
      DynamicJsonDocument doc(256);
      deserializeJson(doc, args);
      String action = doc["action"].as<String>();
      
      if (action == "FORWARD") controlMotors(FORWARD);
      else if (action == "BACKWARD") controlMotors(BACKWARD);
      else if (action == "LEFT") controlMotors(LEFT);
      else if (action == "RIGHT") controlMotors(RIGHT);
      else controlMotors(STOP);
      
      return WebSocketMCP::ToolResponse("{\"status\":\"OK\",\"action\":\"" + action + "\"}");
    }
  );

  // 速度调节工具
  mcpClient.registerTool(
    "car_speed",
    "调节电机速度",
    "{\"type\":\"object\",\"properties\":{\"speed\":{\"type\":\"integer\",\"minimum\":0,\"maximum\":255}}}",
    [](const String& args) {
      DynamicJsonDocument doc(256);
      deserializeJson(doc, args);
      currentSpeed = doc["speed"];
      
      // 立即应用新速度
      leftMotor.setSpeed(currentSpeed);
      rightMotor.setSpeed(currentSpeed);
      
      return WebSocketMCP::ToolResponse("{\"status\":\"OK\",\"speed\":" + String(currentSpeed) + "}");
    }
  );
  Serial.println("[MCP] 控制工具已注册");
}

void setup() {
  Serial.begin(115200);
  
  // 电机安全初始化
  leftMotor.stop();
  rightMotor.stop();
  leftMotor.setSpeed(currentSpeed);
  rightMotor.setSpeed(currentSpeed);

  // 连接WiFi
  Serial.printf("连接WiFi: %s\n", ssid);
  WiFi.begin(ssid, password);
  while (WiFi.status() != WL_CONNECTED) {
    delay(500);
    Serial.print(".");
  }
  Serial.printf("\nWiFi已连接\nIP地址: %s\n", WiFi.localIP().toString());

  // 启动MCP客户端
  mcpClient.begin(mcpEndpoint, onConnectionStatus);
}

void loop() {
  mcpClient.loop();  // 处理网络通信
}

步骤4 控制进阶,运动时间可控

​功能说明

​运动控制​​:

支持五种指令:前进(forward)、后退(backward)、左转(left)、右转(right)、停止(stop)

可指定持续时间(如前进2秒)

转向采用差速控制:左转时右轮前进/左轮后退,右转时左轮前进/右轮后退

​速度调节​​:

独立的速度控制工具(set_speed)

速度范围0-255(PWM占空比)

实时生效,不影响当前运动状态

​控制协议​​:

基于JSON的WebSocket通信

支持同步响应和状态反馈

超时自动断开连接处理

使用示例

​前进2秒​​:

{ "action": "forward", "duration": 2000 }

​左转1秒​​:

{ "action": "left", "duration": 1000 }

​停止小车​​:

{ "action": "stop" }

​调节速度​​:

{ "speed": 200 }

注意事项

实际接线需根据电机转向调整IN1-IN4的相位

转向灵敏度可通过调整controlCar()中的差速逻辑优化

若需要更精确的转向控制,可扩展为独立设置左右电机速度

确保L298N的使能引脚(ENA/ENB)已连接至ESP32的PWM引脚

代码
#include <WiFi.h>
#include <WebSocketMCP.h>
#include <L298N.h>
#include <ArduinoJson.h>

// 电机引脚定义 (ESP32 S3)
#define ENA 14
#define IN1 12
#define IN2 13
#define ENB 17
#define IN3 15
#define IN4 16

// 初始化电机对象
L298N leftMotor(ENA, IN1, IN2);
L298N rightMotor(ENB, IN3, IN4);

// WiFi配置
const char* ssid = "******";
const char* password = "******";
const char* mcpEndpoint = "ws://api.xiaozhi.me/mcp/?token=******";

// 创建WebSocketMCP实例
WebSocketMCP mcpClient;

// 小车控制状态
enum CarState { STOP, FORWARD, BACKWARD, LEFT, RIGHT };
CarState currentState = STOP;
int currentSpeed = 150;  // 默认速度(0-255)

// 连接状态回调
void onConnectionStatus(bool connected) {
  if (connected) {
    Serial.println("[MCP] 已连接到服务器");
    registerMcpTools();
  } else {
    Serial.println("[MCP] 与服务器断开连接");
  }
}

// 控制小车动作
void controlCar(CarState state, int speed = -1) {
  if (speed >= 0) currentSpeed = speed;
  
  switch (state) {
    case FORWARD:
      leftMotor.setSpeed(currentSpeed);
      rightMotor.setSpeed(currentSpeed);
      leftMotor.forward();
      rightMotor.forward();
      break;
    case BACKWARD:
      leftMotor.setSpeed(currentSpeed);
      rightMotor.setSpeed(currentSpeed);
      leftMotor.backward();
      rightMotor.backward();
      break;
    case LEFT:
      leftMotor.setSpeed(currentSpeed);
      rightMotor.setSpeed(currentSpeed);
      leftMotor.backward();  // 差速转向
      rightMotor.forward();
      break;
    case RIGHT:
      leftMotor.setSpeed(currentSpeed);
      rightMotor.setSpeed(currentSpeed);
      leftMotor.forward();
      rightMotor.backward();  // 差速转向
      break;
    case STOP:
      leftMotor.stop();
      rightMotor.stop();
      break;
  }
  currentState = state;
}

// 注册MCP控制工具
void registerMcpTools() {
  // 运动控制工具
  mcpClient.registerTool(
    "car_control",
    "控制小车运动(forward/backward/left/right/stop)",
    "{\"type\":\"object\",\"properties\":{"
    "\"action\":{\"type\":\"string\",\"enum\":[\"forward\",\"backward\",\"left\",\"right\",\"stop\"]},"
    "\"duration\":{\"type\":\"integer\",\"description\":\"持续时间(ms)\"},"
    "\"speed\":{\"type\":\"integer\",\"minimum\":0,\"maximum\":255}}"
    ",\"required\":[\"action\"]}",
    [](const String& args) {
      DynamicJsonDocument doc(256);
      deserializeJson(doc, args);
      
      String action = doc["action"].as<String>();
      int duration = doc["duration"] | 0;  // 可选参数
      int speed = doc["speed"] | -1;        // 可选参数
      
      // 执行动作
      if (action == "forward") controlCar(FORWARD, speed);
      else if (action == "backward") controlCar(BACKWARD, speed);
      else if (action == "left") controlCar(LEFT, speed);
      else if (action == "right") controlCar(RIGHT, speed);
      else if (action == "stop") controlCar(STOP);
      
      // 延时执行(非阻塞)
      if (duration > 0) {
        unsigned long start = millis();
        while (millis() - start < duration) {
          mcpClient.loop();
          delay(10);
        }
        controlCar(STOP);
      }
      
      return WebSocketMCP::ToolResponse("{\"success\":true,\"action\":\"" + action + "\",\"speed\":" + String(currentSpeed) + "}");
    }
  );
  
  // 速度调节工具
  mcpClient.registerTool(
    "set_speed",
    "设置小车速度(0-255)",
    "{\"type\":\"object\",\"properties\":{\"speed\":{\"type\":\"integer\",\"minimum\":0,\"maximum\":255}},\"required\":[\"speed\"]}",
    [](const String& args) {
      DynamicJsonDocument doc(128);
      deserializeJson(doc, args);
      currentSpeed = doc["speed"];
      
      // 更新当前运动状态的速度
      if (currentState != STOP) {
        controlCar(currentState);
      }
      
      return WebSocketMCP::ToolResponse("{\"success\":true,\"speed\":" + String(currentSpeed) + "}");
    }
  );
}

void setup() {
  Serial.begin(115200);
  
  // 初始停止电机
  controlCar(STOP);
  
  // 连接WiFi
  Serial.print("连接到WiFi: ");
  Serial.println(ssid);
  WiFi.begin(ssid, password);
  while (WiFi.status() != WL_CONNECTED) {
    delay(500);
    Serial.print(".");
  }
  Serial.println("\nWiFi已连接");
  Serial.println("IP地址: " + WiFi.localIP().toString());

  // 初始化MCP客户端
  mcpClient.begin(mcpEndpoint, onConnectionStatus);
}

void loop() {
  mcpClient.loop();
  delay(10);
}

上传,全部功能测试通过。

image.png

【提示】

这个帖子中的代码有一个bug,它可以用带速度和时间的指令,但是如果只是前进、后退、左转、右转,它只会执行两秒。所以,后面还会有优化,另一个帖子见。

评论

user-avatar