10浏览
查看: 10|回复: 6

[项目] 【花雕】ESP32 + 大功率双向 ESC|机器人底盘动力控制方案

[复制链接]
【花雕】ESP32 + 大功率双向 ESC|机器人底盘动力控制方案图2【花雕】ESP32 + 大功率双向 ESC|机器人底盘动力控制方案图1
【花雕】ESP32 + 大功率双向 ESC|机器人底盘动力控制方案图3

【花雕】ESP32 + 大功率双向 ESC|机器人底盘动力控制方案图4

驴友花雕  高级技神
 楼主|

发表于 2 小时前

【花雕】ESP32 + 大功率双向 ESC|机器人底盘动力控制方...

1、ESP32 + 标准双向ESC的CAN总线控制
场景:中型机器人底盘,需要高功率和精确速度控制。
核心逻辑:ESP32通过CAN总线发送DShot协议指令,控制多个ESC同步。

  1. #include <SimpleFOC.h>
  2. #include <CAN.h>  // ESP32 CAN库
  3. #include <esp32-hal-timer.h>
  4. // BLDC电机对象
  5. BLDCMotor motor1 = BLDCMotor(7);
  6. BLDCMotor motor2 = BLDCMotor(7);
  7. // ESC参数
  8. #define ESC_MIN_PULSE 1000   // 1ms脉冲宽度 (微秒)
  9. #define ESC_MAX_PULSE 2000   // 2ms脉冲宽度
  10. #define ESC_NEUTRAL 1500     // 1.5ms中性点
  11. #define CAN_ID_ESC1 0x101
  12. #define CAN_ID_ESC2 0x102
  13. // DShot600协议参数
  14. #define DSHOT_CMD_MOTOR_STOP 0
  15. #define DSHOT_CMD_BEACON1 1
  16. #define DSHOT_CMD_BEACON2 2
  17. #define DSHOT_CMD_BEACON3 3
  18. #define DSHOT_CMD_BEACON4 4
  19. #define DSHOT_CMD_BEACON5 5
  20. #define DSHOT_ESC_ARM 6
  21. // CAN总线设置
  22. #define CAN_TX_PIN GPIO_NUM_5
  23. #define CAN_RX_PIN GPIO_NUM_4
  24. hw_timer_t* dshotTimer = NULL;
  25. volatile uint32_t dshotPacket1 = 0;
  26. volatile uint32_t dshotPacket2 = 0;
  27. void setup() {
  28.   Serial.begin(115200);
  29.   
  30.   // 1. CAN总线初始化
  31.   CAN.setPins(CAN_RX_PIN, CAN_TX_PIN);
  32.   if (!CAN.begin(1000E3)) {  // 1Mbps CAN FD
  33.     Serial.println("CAN初始化失败!");
  34.     while(1);
  35.   }
  36.   Serial.println("CAN总线就绪");
  37.   
  38.   // 2. 电机FOC初始化
  39.   motor1.init();
  40.   motor2.init();
  41.   motor1.initFOC();
  42.   motor2.initFOC();
  43.   
  44.   // 3. DShot定时器初始化
  45.   dshotTimer = timerBegin(0, 80, true);  // 80MHz/80 = 1MHz, 1us分辨率
  46.   timerAttachInterrupt(dshotTimer, &dshotISR, true);
  47.   timerAlarmWrite(dshotTimer, 5, true);  // 5us中断 (DShot600 ~2.6us bit)
  48.   timerAlarmEnable(dshotTimer);
  49.   
  50.   // 4. ESC上电自检
  51.   escArmSequence();
  52.   
  53.   Serial.println("系统初始化完成");
  54. }
  55. void loop() {
  56.   // 1. 读取控制输入 (例如: 遥控器、上位机指令)
  57.   float throttle = getThrottleInput();  // -1.0 ~ 1.0
  58.   float steering = getSteeringInput();  // -1.0 ~ 1.0
  59.   
  60.   // 2. 差速计算
  61.   float leftPower = throttle + steering;
  62.   float rightPower = throttle - steering;
  63.   
  64.   // 3. 限制和死区处理
  65.   leftPower = constrain(leftPower, -1.0, 1.0);
  66.   rightPower = constrain(rightPower, -1.0, 1.0);
  67.   if (abs(leftPower) < 0.05) leftPower = 0;
  68.   if (abs(rightPower) < 0.05) rightPower = 0;
  69.   
  70.   // 4. 转换为ESC指令
  71.   uint16_t escCmd1 = powerToDshot(leftPower);
  72.   uint16_t escCmd2 = powerToDshot(rightPower);
  73.   
  74.   // 5. 发送CAN帧
  75.   sendDshotOverCAN(CAN_ID_ESC1, escCmd1);
  76.   sendDshotOverCAN(CAN_ID_ESC2, escCmd2);
  77.   
  78.   // 6. 更新FOC内部状态
  79.   motor1.move(leftPower);
  80.   motor2.move(rightPower);
  81.   
  82.   // 7. 读取ESC遥测数据
  83.   if (CAN.parsePacket()) {
  84.     processESCTelemetry(CAN.read());
  85.   }
  86.   
  87.   delay(5);  // 200Hz控制频率
  88. }
  89. uint16_t powerToDshot(float power) {
  90.   // 将-1.0~1.0功率转换为DShot指令
  91.   if (power == 0) return 0;  // 电机停止
  92.   
  93.   // 双向ESC: 1500±500us
  94.   uint16_t pulse = ESC_NEUTRAL + (int16_t)(power * 500);
  95.   pulse = constrain(pulse, ESC_MIN_PULSE, ESC_MAX_PULSE);
  96.   
  97.   // 转换为DShot值 (0~2047)
  98.   uint16_t dshotValue = map(pulse, ESC_MIN_PULSE, ESC_MAX_PULSE, 0, 2047);
  99.   dshotValue = constrain(dshotValue, 48, 2047);  // 有效范围
  100.   
  101.   return dshotValue;
  102. }
  103. void sendDshotOverCAN(uint32_t id, uint16_t command) {
  104.   // 构建DShot包: 11位命令 + 1位遥测请求 + 4位CRC
  105.   uint16_t packet = (command << 1) | 0x1;  // 设置遥测请求位
  106.   uint8_t crc = (packet ^ (packet >> 4) ^ (packet >> 8)) & 0x0F;
  107.   uint16_t dshotPacket = (packet << 4) | crc;
  108.   
  109.   CAN.beginPacket(id, 2);  // 2字节数据
  110.   CAN.write((uint8_t)(dshotPacket >> 8));
  111.   CAN.write((uint8_t)(dshotPacket & 0xFF));
  112.   CAN.endPacket();
  113. }
  114. void escArmSequence() {
  115.   // ESC上电解锁序列
  116.   Serial.println("ESC解锁中...");
  117.   for(int i=0; i<3; i++) {
  118.     sendDshotOverCAN(CAN_ID_ESC1, 0);
  119.     sendDshotOverCAN(CAN_ID_ESC2, 0);
  120.     delay(100);
  121.   }
  122.   sendDshotOverCAN(CAN_ID_ESC1, DSHOT_ESC_ARM);
  123.   sendDshotOverCAN(CAN_ID_ESC2, DSHOT_ESC_ARM);
  124.   delay(200);
  125.   Serial.println("ESC解锁完成");
  126. }
  127. void IRAM_ATTR dshotISR() {
  128.   // DShot时序生成 (在定时器中断中执行)
  129.   static uint8_t bitCount = 0;
  130.   // 实现DShot600时序 (600kbps, 1.67us/bit)
  131.   // 实际代码需要处理位时序生成
  132. }
复制代码



回复

使用道具 举报

驴友花雕  高级技神
 楼主|

发表于 2 小时前

【花雕】ESP32 + 大功率双向 ESC|机器人底盘动力控制方案

2、ESP32 + VESC 6.0 高性能FOC控制
场景:大功率机器人底盘,需要最高性能的FOC控制与能量回馈。
核心逻辑:ESP32通过UART与VESC通信,使用VESC Tool协议。

  1. #include <SimpleFOC.h>
  2. #include <VescUart.h>  // VESC UART协议库
  3. // VESC对象
  4. VescUart vescLeft;
  5. VescUart vescRight;
  6. // ESP32硬件串口
  7. #define UART_LEFT_NUM UART_NUM_1
  8. #define UART_RIGHT_NUM UART_NUM_2
  9. #define UART_LEFT_TX 17
  10. #define UART_LEFT_RX 16
  11. #define UART_RIGHT_TX 5
  12. #define UART_RIGHT_RX 4
  13. // 遥测数据结构
  14. struct VescTelemetry {
  15.   float rpm;
  16.   float current;
  17.   float duty;
  18.   float voltage;
  19.   uint32_t tachometer;
  20. };
  21. VescTelemetry telemetryLeft, telemetryRight;
  22. void setup() {
  23.   Serial.begin(115200);
  24.   
  25.   // 1. 初始化VESC UART连接
  26.   vescLeft.setSerialPort(&Serial1);
  27.   vescRight.setSerialPort(&Serial2);
  28.   
  29.   Serial1.begin(115200, SERIAL_8N1, UART_LEFT_RX, UART_LEFT_TX);
  30.   Serial2.begin(115200, SERIAL_8N1, UART_RIGHT_RX, UART_RIGHT_TX);
  31.   
  32.   // 2. 设置VESC工作模式
  33.   delay(1000);
  34.   setVescMode(VESC_MODE_CURRENT, 30.0);  // 电流控制模式, 30A限流
  35.   
  36.   // 3. 配置ESP32的PWM输出用于模拟油门信号 (备用)
  37.   ledcSetup(0, 500, 8);  // 通道0, 500Hz, 8位分辨率
  38.   ledcAttachPin(25, 0);  // 引脚25连接ESC油门线
  39.   
  40.   // 4. 启动安全检测
  41.   startSafetyMonitor();
  42.   
  43.   Serial.println("VESC 6.0动力系统就绪");
  44. }
  45. void loop() {
  46.   static uint32_t lastTelemetryTime = 0;
  47.   static uint32_t lastControlTime = 0;
  48.   uint32_t now = millis();
  49.   
  50.   // 1. 20Hz控制环
  51.   if (now - lastControlTime >= 50) {
  52.     float throttle = getThrottleCommand();  // 来自遥控/上位机
  53.     float steering = getSteeringCommand();
  54.    
  55.     // 计算差速功率
  56.     float leftCurrent = throttle + steering;
  57.     float rightCurrent = throttle - steering;
  58.    
  59.     // 电流限制
  60.     leftCurrent = constrain(leftCurrent, -30.0, 30.0);
  61.     rightCurrent = constrain(rightCurrent, -30.0, 30.0);
  62.    
  63.     // 发送电流指令到VESC
  64.     vescLeft.setCurrent(leftCurrent);
  65.     vescRight.setCurrent(rightCurrent);
  66.    
  67.     lastControlTime = now;
  68.   }
  69.   
  70.   // 2. 10Hz遥测读取
  71.   if (now - lastTelemetryTime >= 100) {
  72.     if (vescLeft.getVescValues()) {
  73.       telemetryLeft.rpm = vescLeft.data.rpm;
  74.       telemetryLeft.current = vescLeft.data.avgMotorCurrent;
  75.       telemetryLeft.voltage = vescLeft.data.inpVoltage;
  76.       telemetryLeft.tachometer = vescLeft.data.tachometerAbs;
  77.     }
  78.    
  79.     if (vescRight.getVescValues()) {
  80.       telemetryRight.rpm = vescRight.data.rpm;
  81.       telemetryRight.current = vescRight.data.avgMotorCurrent;
  82.       telemetryRight.voltage = vescRight.data.inpVoltage;
  83.       telemetryRight.tachometer = vescRight.data.tachometerAbs;
  84.     }
  85.    
  86.     // 监控和保护
  87.     monitorSafety();
  88.    
  89.     lastTelemetryTime = now;
  90.   }
  91.   
  92.   // 3. 能量回馈制动处理
  93.   handleRegenerativeBraking();
  94.   
  95.   // 4. 串口调试输出
  96.   if (now % 500 < 50) {  // 每500ms输出一次
  97.     debugOutput();
  98.   }
  99. }
  100. void setVescMode(uint8_t mode, float limit) {
  101.   // 设置VESC工作模式
  102.   switch(mode) {
  103.     case VESC_MODE_CURRENT:
  104.       // 通过VESC UART命令设置电流控制模式
  105.       // vescLeft.setCurrentControl();
  106.       break;
  107.     case VESC_MODE_DUTY:
  108.       // 占空比控制模式
  109.       break;
  110.     case VESC_MODE_RPM:
  111.       // 转速控制模式
  112.       break;
  113.   }
  114. }
  115. void handleRegenerativeBraking() {
  116.   // 能量回馈制动逻辑
  117.   if (telemetryLeft.rpm > 100 && telemetryLeft.current < -2.0) {
  118.     // 电机转速高且电流为负,处于发电状态
  119.     float brakePower = abs(telemetryLeft.current) * telemetryLeft.voltage;
  120.     if (brakePower > 50.0) {  // 回馈功率大于50W
  121.       // 限制回馈电流,保护电池
  122.       vescLeft.setCurrent(-5.0);  // 限制回馈电流为5A
  123.     }
  124.   }
  125. }
  126. void monitorSafety() {
  127.   // 安全监控
  128.   if (telemetryLeft.voltage < 20.0 || telemetryRight.voltage < 20.0) {
  129.     emergencyShutdown("电池电压过低!");
  130.   }
  131.   if (abs(telemetryLeft.current) > 35.0 || abs(telemetryRight.current) > 35.0) {
  132.     emergencyShutdown("电流过载!");
  133.   }
  134.   if (telemetryLeft.rpm > 10000 || telemetryRight.rpm > 10000) {
  135.     emergencyShutdown("转速过高!");
  136.   }
  137. }
复制代码


回复

使用道具 举报

驴友花雕  高级技神
 楼主|

发表于 2 小时前

【花雕】ESP32 + 大功率双向 ESC|机器人底盘动力控制方案

3、ESP32 + 多ESC同步的SWD调试与OTA升级
场景:四轮全向移动机器人,需要精确同步和远程维护。
核心逻辑:ESP32通过SPI控制多个ESC,集成SWD调试和OTA功能。

  1. #include <SimpleFOC.h>
  2. #include <Update.h>  // OTA升级
  3. #include <WiFi.h>
  4. #include <WebServer.h>
  5. #include <esp_spi.h>
  6. // 四电机Mecanum轮底盘
  7. BLDCMotor motorFL, motorFR, motorRL, motorRR;
  8. // ESC SPI通信
  9. #define ESC_SPI_HOST SPI2_HOST
  10. #define ESC_SPI_MISO 19
  11. #define ESC_SPI_MOSI 23
  12. #define ESC_SPI_SCLK 18
  13. #define ESC_CS1_PIN 15
  14. #define ESC_CS2_PIN 2
  15. #define ESC_CS3_PIN 4
  16. #define ESC_CS4_PIN 5
  17. spi_device_handle_t esc_spi;
  18. // OTA和WiFi
  19. WebServer server(80);
  20. const char* ssid = "Robot_AP";
  21. const char* password = "robot1234";
  22. // 同步控制结构
  23. struct SyncControl {
  24.   float targetSpeed[4];
  25.   float actualSpeed[4];
  26.   uint32_t syncCounter;
  27.   bool syncEnabled;
  28. };
  29. SyncControl syncCtrl;
  30. void setup() {
  31.   Serial.begin(115200);
  32.   
  33.   // 1. 初始化ESC SPI通信
  34.   spi_bus_config_t buscfg = {
  35.     .mosi_io_num = ESC_SPI_MOSI,
  36.     .miso_io_num = ESC_SPI_MISO,
  37.     .sclk_io_num = ESC_SPI_SCLK,
  38.     .quadwp_io_num = -1,
  39.     .quadhd_io_num = -1,
  40.     .max_transfer_sz = 4096
  41.   };
  42.   spi_bus_initialize(ESC_SPI_HOST, &buscfg, SPI_DMA_CH_AUTO);
  43.   
  44.   // 2. 初始化各个ESC设备
  45.   initESCDevice(ESC_CS1_PIN, 0);
  46.   initESCDevice(ESC_CS2_PIN, 1);
  47.   initESCDevice(ESC_CS3_PIN, 2);
  48.   initESCDevice(ESC_CS4_PIN, 3);
  49.   
  50.   // 3. 初始化WiFi和OTA
  51.   WiFi.softAP(ssid, password);
  52.   IPAddress IP = WiFi.softAPIP();
  53.   Serial.print("AP IP: "); Serial.println(IP);
  54.   
  55.   server.on("/", handleRoot);
  56.   server.on("/update", HTTP_POST, handleUpdate, handleUpload);
  57.   server.begin();
  58.   
  59.   // 4. 初始化同步控制
  60.   syncCtrl.syncEnabled = true;
  61.   syncCtrl.syncCounter = 0;
  62.   
  63.   // 5. 启动同步定时器
  64.   startSyncTimer();
  65.   
  66.   Serial.println("多ESC同步系统就绪");
  67. }
  68. void loop() {
  69.   // 1. 处理Web服务器请求
  70.   server.handleClient();
  71.   
  72.   // 2. 读取控制指令
  73.   if (readControlInput()) {
  74.     // 3. 计算Mecanum运动学
  75.     calculateMecanumKinematics();
  76.    
  77.     // 4. 同步控制所有ESC
  78.     if (syncCtrl.syncEnabled) {
  79.       synchronizeESCs();
  80.     }
  81.    
  82.     // 5. 发送SPI指令
  83.     sendSPICommands();
  84.    
  85.     // 6. 读取遥测
  86.     readTelemetrySPI();
  87.   }
  88.   
  89.   // 7. 监控系统状态
  90.   monitorSystem();
  91. }
  92. void synchronizeESCs() {
  93.   // 精确同步算法
  94.   static uint32_t lastSync = 0;
  95.   uint32_t now = micros();
  96.   float dt = (now - lastSync) / 1e6;
  97.   
  98.   if (dt >= 0.01) {  // 100Hz同步
  99.     // 计算同步误差
  100.     float speedErrors[4];
  101.     for (int i = 0; i < 4; i++) {
  102.       speedErrors[i] = syncCtrl.targetSpeed[i] - syncCtrl.actualSpeed[i];
  103.     }
  104.    
  105.     // 交叉耦合补偿
  106.     float avgError = (speedErrors[0] + speedErrors[1] +
  107.                      speedErrors[2] + speedErrors[3]) / 4.0;
  108.    
  109.     // 应用补偿
  110.     for (int i = 0; i < 4; i++) {
  111.       syncCtrl.targetSpeed[i] += (avgError - speedErrors[i]) * 0.1;
  112.     }
  113.    
  114.     lastSync = now;
  115.     syncCtrl.syncCounter++;
  116.   }
  117. }
  118. void sendSPICommands() {
  119.   // 批量SPI传输
  120.   uint8_t txData[4][8];
  121.   uint8_t rxData[4][8];
  122.   
  123.   for (int esc = 0; esc < 4; esc++) {
  124.     // 构建ESC命令包
  125.     packESCCommand(txData[esc], syncCtrl.targetSpeed[esc]);
  126.    
  127.     spi_transaction_t t = {
  128.       .tx_buffer = txData[esc],
  129.       .rx_buffer = rxData[esc],
  130.       .length = 8 * 8,
  131.       .rxlength = 8 * 8
  132.     };
  133.    
  134.     // 选择对应的ESC
  135.     digitalWrite(getCSPin(esc), LOW);
  136.     spi_device_polling_transmit(esc_spi, &t);
  137.     digitalWrite(getCSPin(esc), HIGH);
  138.    
  139.     // 解析响应
  140.     parseESCResponse(rxData[esc], esc);
  141.   }
  142. }
  143. void handleUpdate() {
  144.   // OTA固件更新处理
  145.   HTTPUpload& upload = server.upload();
  146.   if (upload.status == UPLOAD_FILE_START) {
  147.     Serial.printf("更新开始: %s\n", upload.filename.c_str());
  148.     if (!Update.begin(UPDATE_SIZE_UNKNOWN)) {
  149.       Update.printError(Serial);
  150.     }
  151.   } else if (upload.status == UPLOAD_FILE_WRITE) {
  152.     if (Update.write(upload.buf, upload.currentSize) != upload.currentSize) {
  153.       Update.printError(Serial);
  154.     }
  155.   } else if (upload.status == UPLOAD_FILE_END) {
  156.     if (Update.end(true)) {
  157.       Serial.printf("更新成功: %u字节\n", upload.totalSize);
  158.       server.send(200, "text/plain", "更新成功,系统将重启");
  159.       delay(1000);
  160.       ESP.restart();
  161.     } else {
  162.       Update.printError(Serial);
  163.     }
  164.   }
  165. }
复制代码

要点解读
通信协议的选择决定系统性能上限
CAN总线(案例一):工业级可靠性,支持多节点(ESC)同步,1Mbps速率满足实时控制。DShot over CAN是高端ESC的常见配置,支持双向遥测。
UART(案例二):VESC标准协议,带宽足够,但线缆复杂。VESC Tool协议提供丰富的遥测和配置参数。
SPI(案例三):最高速率(可达10Mbps),可实现微秒级同步,适合多ESC精确协同,但布线要求高,抗干扰能力弱于CAN。
ESC的配置模式与控制策略
DShot数字协议:替代传统PWM,抗干扰强,分辨率高(11位),支持遥测请求。案例一的powerToDshot()函数实现功率到DShot值的映射。
VESC的电流控制模式:最精确的扭矩控制,直接控制相电流。案例二中setCurrent()是VESC的高级API,需要精确的电机参数配置。
同步控制必要性:多ESC的毫秒级不同步会导致底盘“扭动”。案例三的synchronizeESCs()通过交叉耦合补偿算法,让4个ESC的速度误差相互补偿,实现真正的同步。
大功率系统的安全与保护
电流限制:必须硬件(保险丝、断路器)和软件双重保护。案例二的setVescMode(VESC_MODE_CURRENT, 30.0)设置软件限流。
能量回馈处理:双向ESC在减速时会将动能转化为电能回充电池。案例二的handleRegenerativeBraking()必须限制回馈电流,防止电池过充。
温度监控:大功率ESC的MOSFET温度需实时监控,通常通过ESC遥测获取,超过85°C应降额运行。
ESP32的双核优势利用
Core 0:运行控制循环(FOC算法、运动学计算),必须保证实时性。可使用FreeRTOS任务绑定到核心0。
Core 1:处理WiFi、OTA、HTTP服务器等非实时任务。案例三的Web服务器运行在Core 1,避免干扰控制任务。
定时器中断:案例一的dshotISR()在定时器中断中生成精确的DShot时序,不依赖主循环,保证了时序精度。
系统集成与远程维护
OTA升级:案例三的handleUpdate()实现完整的Web端固件更新,这对于现场部署的机器人至关重要。
SWD调试接口:通过ESP32的JTAG引脚可连接外部调试器,实现实时变量查看、断点调试。需在代码中保留调试符号。
配置持久化:ESC参数(PID、电流限制、电机极对数)应存储于ESP32的NVS或外部EEPROM,支持断电保存和远程配置。

回复

使用道具 举报

驴友花雕  高级技神
 楼主|

发表于 2 小时前

【花雕】ESP32 + 大功率双向 ESC|机器人底盘动力控制方案

4、基础双向ESC控制(PWM信号)
功能:通过ESP32的PWM控制大功率双向ESC,实现机器人底盘的正反转、加速/减速及停止。
硬件连接:

ESP32 GPIO23 → ESC PWM信号线(白色/橙色)
ESC电源线 → 独立锂电池(24V/36V)
电机 → 6.5寸无刷轮毂电机(带编码器)

  1. #include <Arduino.h>
  2. #include <driver/timer.h>
  3. #define ESC_PIN 23
  4. #define PWM_FREQ 50  // 50Hz PWM周期20ms
  5. void setup() {
  6.   Serial.begin(115200);
  7.   ledcSetup(0, PWM_FREQ, 10);  // 10位分辨率(0-1023)
  8.   ledcAttachPin(ESC_PIN, 0);
  9.   
  10.   // ESC校准(部分大功率ESC需要)
  11.   Serial.println("ESC Calibration Start...");
  12.   ledcWrite(0, 512);  // 最大油门(约2000μs)
  13.   delay(2000);
  14.   ledcWrite(0, 102);  // 最小油门(约1000μs)
  15.   delay(2000);
  16.   Serial.println("Calibration Done!");
  17. }
  18. void loop() {
  19.   // 正转(油门1500μs-2000μs)
  20.   for (int i = 102; i <= 512; i += 10) {  // 1000μs→2000μs
  21.     ledcWrite(0, i);
  22.     Serial.print("Throttle: "); Serial.println(i);
  23.     delay(100);
  24.   }
  25.   
  26.   // 停止(1500μs)
  27.   ledcWrite(0, 306);  // 1500μs
  28.   delay(1000);
  29.   
  30.   // 反转(油门1000μs-1500μs)
  31.   for (int i = 306; i >= 102; i -= 10) {
  32.     ledcWrite(0, i);
  33.     Serial.print("Reverse Throttle: "); Serial.println(i);
  34.     delay(100);
  35.   }
  36. }
复制代码



回复

使用道具 举报

驴友花雕  高级技神
 楼主|

发表于 2 小时前

【花雕】ESP32 + 大功率双向 ESC|机器人底盘动力控制方案

5、基于编码器的闭环速度控制(PID)
功能:通过编码器反馈实现电机速度闭环控制,解决大功率电机启动冲击问题。
硬件扩展:

电机编码器 → ESP32 GPIO16(CLK)、GPIO17(DATA)
使用Encoder库读取脉冲数

  1. #include <Encoder.h>
  2. #include <PID_v1.h>
  3. #define MOTOR_PWM_PIN 23
  4. #define ENCODER_PIN_A 16
  5. #define ENCODER_PIN_B 17
  6. Encoder enc(ENCODER_PIN_A, ENCODER_PIN_B);
  7. double setpoint = 1000;  // 目标转速(RPM)
  8. double input, output;
  9. PID pid(&input, &output, &setpoint, 2.0, 0.5, 0.1, DIRECT);  // P=2.0, I=0.5, D=0.1
  10. void setup() {
  11.   Serial.begin(115200);
  12.   pinMode(MOTOR_PWM_PIN, OUTPUT);
  13.   pid.SetMode(AUTOMATIC);
  14.   pid.SetOutputLimits(-1023, 1023);  // 限制PWM输出范围
  15. }
  16. void loop() {
  17.   long encValue = enc.read();  // 读取编码器脉冲数
  18.   input = (encValue / 20.0) * 60.0;  // 转换为RPM(假设20脉冲/转)
  19.   
  20.   pid.Compute();
  21.   int pwmValue = (int)output;
  22.   
  23.   // 双向控制:正转/反转
  24.   if (pwmValue > 0) {
  25.     ledcWrite(0, map(pwmValue, 0, 1023, 306, 512));  // 1500μs→2000μs
  26.   } else {
  27.     ledcWrite(0, map(abs(pwmValue), 0, 1023, 306, 102));  // 1500μs→1000μs
  28.   }
  29.   
  30.   Serial.print("Target RPM: "); Serial.print(setpoint);
  31.   Serial.print(" Actual RPM: "); Serial.println(input);
  32.   delay(100);
  33. }
复制代码


回复

使用道具 举报

驴友花雕  高级技神
 楼主|

发表于 2 小时前

【花雕】ESP32 + 大功率双向 ESC|机器人底盘动力控制方案


6、多电机差速转向控制(机器人底盘运动)
功能:通过双电机差速实现机器人底盘的前进、后退、转向及原地旋转。
硬件扩展:

电机1 → ESC1(GPIO23)
电机2 → ESC2(GPIO22)
编码器1 → GPIO16/17
编码器2 → GPIO4/5

  1. #include <Encoder.h>
  2. #define ESC1_PIN 23
  3. #define ESC2_PIN 22
  4. Encoder enc1(16, 17);
  5. Encoder enc2(4, 5);
  6. void setMotorSpeed(int motor, int speed) {
  7.   // motor=1: ESC1, motor=2: ESC2
  8.   // speed范围: -100(全反转)到100(全正转)
  9.   int pwmValue = map(speed, -100, 100, 102, 512);  // 1000μs→2000μs
  10.   if (motor == 1) {
  11.     ledcWrite(0, pwmValue);  // ESC1
  12.   } else {
  13.     ledcWrite(1, pwmValue);  // ESC2
  14.   }
  15. }
  16. void moveRobot(float linearVel, float angularVel) {
  17.   // linearVel: 线速度(m/s),angularVel: 角速度(rad/s)
  18.   // 假设轮距=0.5m,轮半径=0.1m
  19.   float leftSpeed = (linearVel - (angularVel * 0.5)) / 0.1;  // 转换为RPM
  20.   float rightSpeed = (linearVel + (angularVel * 0.5)) / 0.1;
  21.   
  22.   setMotorSpeed(1, leftSpeed);
  23.   setMotorSpeed(2, rightSpeed);
  24. }
  25. void setup() {
  26.   ledcSetup(0, 50, 10);  // ESC1
  27.   ledcSetup(1, 50, 10);  // ESC2
  28.   ledcAttachPin(ESC1_PIN, 0);
  29.   ledcAttachPin(ESC2_PIN, 1);
  30. }
  31. void loop() {
  32.   // 示例:前进1秒,右转0.5秒
  33.   moveRobot(0.5, 0);  // 前进0.5m/s
  34.   delay(1000);
  35.   moveRobot(0, 1.0);  // 右转(角速度1.0rad/s)
  36.   delay(500);
  37.   moveRobot(0, 0);    // 停止
  38.   delay(1000);
  39. }
复制代码

要点解读
1、电源隔离与保护
大功率ESC和电机需独立供电(如24V锂电池),避免ESP32因电流过大损坏。
使用隔离DC-DC模块为ESP32供电,并确保电机电源线与信号线分开走线,加装磁环滤波。
2、ESC校准与通信协议
部分大功率ESC需校准(如发送最大/最小油门信号),否则可能无法启动。
双向ESC的PWM范围通常为1000μs(反转)至2000μs(正转),1500μs为停止。
3、闭环控制必要性
开环控制易因负载变化导致速度不稳定,需通过编码器实现PID闭环控制。
案例5中通过编码器反馈调整PWM输出,解决启动冲击和速度波动问题。
4、多电机同步与差速算法
机器人底盘需通过双电机差速实现转向(案例3),需精确计算左右轮速度。
需考虑轮距、轮半径等机械参数,避免转向半径过大或失控。
5、实时性与安全性设计
控制周期需稳定且低延迟(如<50ms),否则会导致轨迹震荡或响应迟缓。
必须设计硬件急停按钮,并在软件中设置超时检测(Watchdog),防止通信中断时电机失控。

注意,以上案例只是为了拓展思路,仅供参考。它们可能有错误、不适用或者无法编译。您的硬件平台、使用场景和Arduino版本可能影响使用方法的选择。实际编程时,您要根据自己的硬件配置、使用场景和具体需求进行调整,并多次实际测试。您还要正确连接硬件,了解所用传感器和设备的规范和特性。涉及硬件操作的代码,您要在使用前确认引脚和电平等参数的正确性和安全性。

【花雕】ESP32 + 大功率双向 ESC|机器人底盘动力控制方案图1

回复

使用道具 举报

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

本版积分规则

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

硬件清单

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

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

mail