# motor_test **Repository Path**: yuezht/motor_test ## Basic Information - **Project Name**: motor_test - **Description**: No description available - **Primary Language**: Unknown - **License**: Not specified - **Default Branch**: master - **Homepage**: None - **GVP Project**: No ## Statistics - **Stars**: 0 - **Forks**: 0 - **Created**: 2024-03-23 - **Last Updated**: 2026-04-11 ## Categories & Tags **Categories**: Uncategorized **Tags**: None ## README # BalanceBot - ESP32-S3 二轮自平衡小车 基于 ESP32-S3 的二轮自平衡机器人,采用串级 PID 控制算法(角度环 + 速度环),MPU6050 互补滤波姿态解算,内置 WiFi 网页遥控与实时调参界面。 ## 目录 - [硬件构成](#硬件构成) - [系统架构](#系统架构) - [控制原理](#控制原理) - [软件方案](#软件方案) - [快速上手](#快速上手) - [调测方法](#调测方法) - [参数参考](#参数参考) - [常见问题](#常见问题) --- ## 硬件构成 ### 硬件清单 | 组件 | 型号/规格 | 数量 | 说明 | | ---------- | ------------------------ | ---- | ----------------------------------- | | 主控制器 | ESP32-S3 DevKit | 1 | 双核 240MHz,内置 WiFi/BLE | | 姿态传感器 | MPU6050 | 1 | 六轴 IMU(三轴加速度 + 三轴陀螺仪) | | 电机驱动 | TB6612FNG | 1 | 双路 H 桥驱动,最大 1.2A/通道 | | 直流电机 | 减速电机 110RPM 带编码器 | 2 | 减速比视具体型号而定 | | 车轮 | 65mm 橡胶轮 | 2 | 直径 65mm | ### 接线总图 ```mermaid graph LR subgraph ESP32-S3 direction TB GPIO6["GPIO6 (E1A)"] GPIO7["GPIO7 (E1B)"] GPIO14["GPIO14 (PWMA)"] GPIO12["GPIO12 (AIN1)"] GPIO13["GPIO13 (AIN2)"] GPIO11["GPIO11 (STBY)"] GPIO10["GPIO10 (BIN1)"] GPIO9["GPIO9 (BIN2)"] GPIO46["GPIO46 (PWMB)"] GPIO4["GPIO4 (E2A)"] GPIO5["GPIO5 (E2B)"] GPIO20["GPIO20 (SDA)"] GPIO19["GPIO19 (SCL)"] end subgraph TB6612FNG PWMA_IN["PWMA"] AIN1_IN["AIN1"] AIN2_IN["AIN2"] STBY_IN["STBY"] BIN1_IN["BIN1"] BIN2_IN["BIN2"] PWMB_IN["PWMB"] AO1["AO1/AO2"] BO1["BO1/BO2"] end subgraph LEFT_MOTOR["左电机 (Motor A)"] LM["电机线圈"] LE["编码器 A/B"] end subgraph RIGHT_MOTOR["右电机 (Motor B)"] RM["电机线圈"] RE["编码器 A/B"] end subgraph MPU6050 SDA["SDA"] SCL["SCL"] end GPIO14 --> PWMA_IN GPIO12 --> AIN1_IN GPIO13 --> AIN2_IN GPIO11 --> STBY_IN GPIO10 --> BIN1_IN GPIO9 --> BIN2_IN GPIO46 --> PWMB_IN AO1 --> LM BO1 --> RM LE --> GPIO6 LE --> GPIO7 RE --> GPIO4 RE --> GPIO5 GPIO20 --> SDA GPIO19 --> SCL ``` ### GPIO 引脚分配表 | 功能 | GPIO | 方向 | 说明 | | -------------- | ------- | ------------ | --------------------- | | 左编码器 A 相 | GPIO 6 | INPUT_PULLUP | 中断触发,计数脉冲 | | 左编码器 B 相 | GPIO 7 | INPUT_PULLUP | 判断旋转方向 | | 左电机 PWM | GPIO 14 | OUTPUT | LEDC Channel 0, 50kHz | | 左电机方向 IN1 | GPIO 12 | OUTPUT | HIGH=正转 | | 左电机方向 IN2 | GPIO 13 | OUTPUT | HIGH=反转 | | TB6612 使能 | GPIO 11 | OUTPUT | HIGH=芯片工作 | | 右电机方向 IN1 | GPIO 10 | OUTPUT | HIGH=正转 | | 右电机方向 IN2 | GPIO 9 | OUTPUT | HIGH=反转 | | 右电机 PWM | GPIO 46 | OUTPUT | LEDC Channel 1, 50kHz | | 右编码器 A 相 | GPIO 4 | INPUT_PULLUP | 中断触发,计数脉冲 | | 右编码器 B 相 | GPIO 5 | INPUT_PULLUP | 判断旋转方向 | | MPU6050 SDA | GPIO 20 | I2C | 400kHz | | MPU6050 SCL | GPIO 19 | I2C | 400kHz | --- ## 系统架构 ### 整体架构 ```mermaid graph TB subgraph SENSORS["传感器层"] MPU["MPU6050
加速度+陀螺仪"] ENC_L["左编码器"] ENC_R["右编码器"] end subgraph PROCESS["数据处理层"] CF["互补滤波
α=0.98"] SPD["速度计算
脉冲/秒"] end subgraph CONTROL["控制层"] SPID["速度 PID
外环 50Hz"] APID["角度 PID
内环 200Hz"] TURN["转向叠加"] end subgraph ACTUATOR["执行层"] TB["TB6612FNG
电机驱动"] ML["左电机"] MR["右电机"] end subgraph COMM["通信层"] WIFI["WiFi AP
192.168.4.1"] WS["WebSocket
Port 81"] WEB["Web UI
Port 80"] end MPU --> CF ENC_L --> SPD ENC_R --> SPD CF -->|"pitch角度"| APID SPD -->|"平均速度"| SPID SPID -->|"目标角度
±8°"| APID APID -->|"电机功率"| TURN TURN -->|"左轮PWM"| TB TURN -->|"右轮PWM"| TB TB --> ML TB --> MR WEB <-->|"HTTP"| WIFI WS <-->|"双向实时"| WIFI WIFI -->|"目标速度
转向值
PID参数"| SPID WIFI -->|"转向指令"| TURN APID -->|"遥测数据"| WS ``` ### 控制循环时序 ```mermaid sequenceDiagram participant TM as Timer participant ML as MainLoop participant IM as IMU participant PD as PID participant MT as Motor Note over TM: 5ms HW Timer ISR TM->>ML: controlFlag = true ML->>IM: I2C read 14 bytes IM-->>ML: accel + gyro raw ML->>ML: complementary filter Note over ML: pitch angle ready rect rgb(255,182,193) Note over ML,PD: Speed loop every 20ms ML->>ML: read encoder count ML->>PD: speed PID compute PD-->>ML: target angle end ML->>PD: angle PID compute PD-->>ML: motor power ML->>ML: add turn offset ML->>MT: setMotor L and R ``` --- ## 控制原理 ### 平衡原理 二轮平衡小车本质上是一个**倒立摆系统**。车体绕轮轴的俯仰角(pitch)是被控量,通过驱动轮子前后运动来抵消重力引起的倾倒趋势: - 车体前倾 → 轮子向前加速 → 将支撑点移回重心下方 - 车体后倾 → 轮子向后加速 → 同理纠偏 ### 串级 PID 控制 系统采用**双环串级 PID** 架构,内环(角度环)负责快速平衡响应,外环(速度环)负责位置/速度控制: ```mermaid graph LR TARGET["目标速度
(WiFi指令)"] --> SSUM(("+
-")) SSUM --> SPID["速度 PID
(PI控制)
50Hz"] SPID -->|"目标角度
±8°"| ASUM(("+
-")) ASUM --> APID["角度 PID
(PD控制)
200Hz"] APID -->|"+转向"| LM["左电机"] APID -->|"-转向"| RM["右电机"] AVG_SPD["编码器
平均速度"] -->|"反馈"| SSUM PITCH["MPU6050
pitch角度"] -->|"反馈"| ASUM style SPID fill:#2980b9,color:#fff style APID fill:#e74c3c,color:#fff style TARGET fill:#27ae60,color:#fff ``` #### 角度环(内环) - 200Hz - **类型**:PD 控制器(比例 + 微分,不用积分避免低频振荡) - **输入**:MPU6050 互补滤波后的 pitch 角度 - **设定值**:来自速度环输出(机械零点附近 ±8°) - **输出**:电机 PWM 功率值 (-255 ~ +255) - **作用**:快速响应车体倾斜,是维持平衡的核心 #### 速度环(外环) - 50Hz - **类型**:PI 控制器(比例 + 积分,积分消除稳态速度误差) - **输入**:左右编码器平均速度 - **设定值**:WiFi 遥控目标速度(静止时为 0) - **输出**:角度环设定值(限幅 ±8°) - **作用**:控制小车不会持续漂移,实现定速前进/后退 ### 姿态解算 - 互补滤波 MPU6050 提供两种测量角度的方式,各有优缺点: | 传感器 | 优点 | 缺点 | | -------- | ---------------------- | ------------------------ | | 加速度计 | 无漂移,长期稳定 | 对振动敏感,短期噪声大 | | 陀螺仪 | 短期精确,不受振动影响 | 存在积分漂移,长期不可靠 | **互补滤波**将两者优势融合: ``` pitch = α × (pitch + gyro × dt) + (1 - α) × accel_pitch ``` - `α = 0.98`:98% 信任陀螺仪(短期精确),2% 信任加速度计(长期校准) - 计算量极小,适合 200Hz 高频运行 ```mermaid graph LR GYRO["陀螺仪
角速度 °/s"] -->|"积分"| INT["pitch += gyro × dt"] INT -->|"× 0.98"| SUM(("+")) ACCEL["加速度计
atan2计算角度"] -->|"× 0.02"| SUM SUM --> PITCH["融合后 pitch 角度"] style GYRO fill:#3498db,color:#fff style ACCEL fill:#e67e22,color:#fff style PITCH fill:#2ecc71,color:#fff ``` ### 电机驱动 TB6612FNG 通过 IN1/IN2 引脚组合控制方向,PWM 引脚控制转速: | IN1 | IN2 | PWM | 状态 | | ---- | ---- | ---- | ---------------- | | HIGH | LOW | duty | 正转,速度=duty | | LOW | HIGH | duty | 反转,速度=duty | | LOW | LOW | - | 刹车(短路制动) | 软件使用**有符号速度** (-255 ~ +255) 统一控制,正值前进、负值后退、零值刹车,并内置**死区补偿**(最小 PWM=25)防止电机在低功率时卡死。 ### 编码器测速 采用**硬件中断**方式读取编码器: - A 相上升沿触发中断 - 在 ISR 中读取 B 相电平判断方向(B=HIGH 则正转,B=LOW 则反转) - 速度 = 脉冲累计数 / 时间间隔(每 20ms 计算一次) --- ## 软件方案 ### 代码结构 全部代码集中在单个 `motor_test.ino` 文件中,通过注释分区组织为 14 个模块: ```mermaid graph TB subgraph FILE["motor_test.ino"] direction TB S1["1. 头文件引用
Wire, WiFi, WebServer, WebSockets"] S2["2. 引脚定义
编码器、电机驱动、I2C"] S3["3. 配置常量
PID参数、滤波系数、频率"] S4["4. 数据结构
Motor, Car, PIDController, MPU6050Data"] S5["5. 全局变量"] S6["6. MPU6050 驱动
init / calibrate / readRaw / updateAngle"] S7["7. 编码器中断 & 速度计算
ISR(IRAM_ATTR) / calculateSpeed"] S8["8. PID 控制器
pidInit / pidCompute / pidReset"] S9["9. 电机控制
setMotor(有符号速度) / moveCar"] S10["10. WiFi & WebSocket
AP模式 / 命令解析 / 遥测推送"] S11["11. HTML 页面
PROGMEM raw string literal"] S12["12. 平衡控制主函数
balanceControl() + 安全保护"] S13["13. setup()"] S14["14. loop()"] S1 --> S2 --> S3 --> S4 --> S5 S5 --> S6 --> S7 --> S8 --> S9 S9 --> S10 --> S11 --> S12 S12 --> S13 --> S14 end style S6 fill:#3498db,color:#fff style S8 fill:#e74c3c,color:#fff style S10 fill:#27ae60,color:#fff style S12 fill:#8e44ad,color:#fff ``` ### 核心数据结构 ```c // 电机 typedef struct { uint16_t encA, encB; // 编码器引脚 uint16_t pwmPin, in1, in2; // 驱动引脚 uint8_t channel; // LEDC PWM 通道 int16_t speed; // 当前速度 (-255~+255) volatile long encoderCount; // 编码器脉冲计数 (ISR写入) } Motor; // PID 控制器 typedef struct { float Kp, Ki, Kd; // 三个增益 float setpoint; // 目标值 float integral, lastError; // 内部状态 float outputMin, outputMax; // 输出限幅 float output; // 当前输出 } PIDController; // IMU 数据 typedef struct { float accelX, accelY, accelZ; // 加速度 (g) float gyroX, gyroY, gyroZ; // 角速度 (°/s) float gyroOffsetX, gyroOffsetY, gyroOffsetZ; // 校准偏移 float pitch; // 滤波后俯仰角 } MPU6050Data; ``` ### 定时器架构 系统使用 **标志位驱动** 模式而非在 ISR 中直接执行控制逻辑,因为 I2C 通信不能在中断服务程序中进行: ```mermaid graph LR HW["硬件定时器
5ms 周期"] -->|"ISR: 置标志位"| FLAG["controlFlag = true"] FLAG -->|"loop()检测"| BC["balanceControl()"] BC --> IMU["I2C 读 MPU6050"] BC --> PID["PID 计算"] BC --> MOT["电机输出"] style HW fill:#e74c3c,color:#fff style BC fill:#8e44ad,color:#fff ``` ### WiFi 遥控协议 通过 WebSocket (端口 81) 进行低延迟双向通信: **控制命令(客户端 → 小车):** | 命令 | 格式 | 示例 | 说明 | | ------- | --------------------------- | --------------------- | ----------------- | | 前进 | `F:<速度>` | `F:80` | 设置正向目标速度 | | 后退 | `B:<速度>` | `B:80` | 设置反向目标速度 | | 左转 | `L:<力度>` | `L:40` | 差速左转 | | 右转 | `R:<力度>` | `R:40` | 差速右转 | | 停止 | `S` | `S` | 速度和转向归零 | | 使能 | `E:1` / `E:0` | `E:1` | 启用/禁用平衡控制 | | PID调参 | `P:aKp,aKi,aKd,sKp,sKi,sKd` | `P:25,0,0.8,2,0.05,0` | 在线修改PID参数 | **遥测数据(小车 → 客户端,10Hz JSON):** ```json { "pitch": 1.23, "speed": 45.6, "aOut": 120.0, "sOut": 0.50, "lM": 130, "rM": 128, "en": 1 } ``` ### 依赖库 | 库名 | 来源 | 用途 | | -------------------- | -------------- | ------------------- | | `Wire.h` | ESP32 内置 | I2C 通信(MPU6050) | | `WiFi.h` | ESP32 内置 | WiFi AP 模式 | | `WebServer.h` | ESP32 内置 | HTTP 网页服务 | | `WebSocketsServer.h` | **需手动安装** | WebSocket 双向通信 | > `WebSocketsServer` 来自 **"WebSockets" by Markus Sattler**,需在 Arduino IDE 的 Library Manager 中搜索安装。 --- ## 快速上手 ### 1. 环境准备 1. 安装 [Arduino IDE](https://www.arduino.cc/en/software) (2.x 推荐) 2. 添加 ESP32 开发板支持: - 菜单 `File → Preferences → Additional Board Manager URLs` 添加: ``` https://espressif.github.io/arduino-esp32/package_esp32_index.json ``` - `Tools → Board Manager` 搜索安装 `esp32` 3. 安装 WebSockets 库: - `Tools → Manage Libraries` 搜索 `WebSockets`,安装 **Markus Sattler** 版本 ### 2. 编译上传 1. 选择开发板:`Tools → Board → ESP32S3 Dev Module` 2. 选择端口:`Tools → Port → /dev/cu.usbmodemXXXX`(或对应串口) 3. 点击上传 ### 3. 首次启动 1. 上传完成后打开串口监视器(115200 波特率) 2. 看到 `Calibrating MPU6050... Keep robot still!` 时**保持小车静止** 3. 校准完成后串口输出 `Self-Balancing Robot Ready` 4. 手机/电脑连接 WiFi: - **SSID**:`BalanceBot` - **密码**:`12345678` 5. 浏览器打开 `http://192.168.4.1` 6. 在网页上点击 **Enable** 启动平衡控制 ### 4. 初始化流程 ```mermaid graph TD START["上电"] --> SERIAL["串口初始化
115200"] SERIAL --> WIFI["WiFi AP 启动
BalanceBot"] WIFI --> WS["WebServer +
WebSocket 就绪"] WS --> MPU_INIT["MPU6050 初始化
唤醒 / 设量程 / DLPF"] MPU_INIT --> CAL["陀螺仪校准
500次采样取平均
⚠️ 保持静止"] CAL --> MOT_INIT["电机 & 编码器初始化
挂载中断"] MOT_INIT --> PID_INIT["PID 控制器初始化
角度环 + 速度环"] PID_INIT --> TIMER["启动硬件定时器
5ms / 200Hz"] TIMER --> READY["系统就绪
等待 Enable 指令"] style CAL fill:#e74c3c,color:#fff style READY fill:#27ae60,color:#fff ``` --- ## 调测方法 ### 阶段一:验证 MPU6050 姿态输出 **目标**:确认 pitch 角度输出正确,前倾为正、后倾为负(或反之,取决于安装方向)。 **方法**: 1. 上传代码后打开 Arduino Serial Plotter(`Tools → Serial Plotter`) 2. 串口输出中的 `Pitch:xx.x` 即为当前角度 3. 手持小车前后倾斜,观察角度变化是否连续、无跳变 4. 如果轴方向不对,修改 `mpuUpdateAngle()` 中的 `gyroY` 为 `gyroX`(取决于 MPU6050 安装朝向) **预期**: - 静止竖直放置:pitch 接近 0° - 前倾 45°:pitch 约 45° - 后倾 45°:pitch 约 -45° - 快速晃动后静止:角度迅速回归正确值(无明显漂移) ### 阶段二:验证编码器 **目标**:确认编码器计数随车轮转动正确变化。 **方法**: 1. 观察串口输出中 `L:xxx R:xxx` 的电机速度值 2. 用手拨动车轮,确认数值变化 3. 正转和反转应产生相反符号 ### 阶段三:PID 调参 这是最关键也最耗时的步骤。推荐使用网页界面的 **PID TUNING** 面板实时调整。 #### 调参流程 ```mermaid graph TD A["第1步: 只调角度环"] --> A1["先设 A.Kp=15, A.Kd=0"] A1 --> A2{"手持小车
Enable 平衡"} A2 --> A3{"倾斜时电机
反应方向正确?"} A3 -->|"反了"| A4["交换电机接线
或取反 pitch"] A3 -->|"正确"| A5["逐步增大 A.Kp"] A5 --> A6{"车体振荡?"} A6 -->|"是"| A7["略降 A.Kp
增大 A.Kd"] A6 -->|"否,反应慢"| A5 A7 --> A8["角度环调好
能基本站立"] A8 --> B["第2步: 加入速度环"] B --> B1["设 S.Kp=1, S.Ki=0.01"] B1 --> B2{"小车漂移?"} B2 -->|"缓慢漂走"| B3["增大 S.Ki"] B2 -->|"不漂移"| B4["速度环OK"] B3 --> B2 B4 --> C["第3步: 精调"] C --> C1["在网页上实时微调
观察数据曲线"] style A fill:#e74c3c,color:#fff style B fill:#2980b9,color:#fff style C fill:#27ae60,color:#fff ``` #### 调参速查表 | 现象 | 原因 | 调整 | | ------------------- | ------------ | --------- | | 高频快速振荡 | 角度 Kp 过大 | 降低 A.Kp | | 倒下不纠偏 | 角度 Kp 过小 | 增大 A.Kp | | 低频缓慢摇摆 | 角度 Kd 不足 | 增大 A.Kd | | 高频抖动(嗡嗡声) | 角度 Kd 过大 | 降低 A.Kd | | 持续向一个方向漂移 | 速度 Ki 不足 | 增大 S.Ki | | 刹车后大幅前冲/后退 | 速度 Kp 过大 | 降低 S.Kp | | 长时间才停稳 | 速度 Kp 不足 | 增大 S.Kp | ### 阶段四:WiFi 遥控测试 1. 确认平衡稳定后,在网页上按方向键测试前进/后退/转向 2. 按住按钮时运动,松开自动回到静止平衡 3. 调整网页 JS 中的速度值(默认前后 80、转向 40)适配你的小车 ### 安全保护机制 系统内置以下安全措施: - **倾角保护**:pitch 超过 ±45° 时自动切断电机并重置 PID,防止失控 - **使能开关**:网页 Enable/Disable 按钮控制平衡是否激活 - **启动时禁用**:上电后默认不启动平衡,需手动 Enable - **死区补偿**:低 PWM 值自动提升到 25,防止电机嗡鸣不转 --- ## 参数参考 ### 可调常量一览 | 常量名 | 默认值 | 位置 | 说明 | | ---------------------- | ---------- | ------------- | ------------------------------ | | `COMP_FILTER_ALPHA` | 0.98 | 代码常量 | 互补滤波系数,越大越信任陀螺仪 | | `CONTROL_PERIOD_US` | 5000 (5ms) | 代码常量 | 角度环控制周期 | | `SPEED_LOOP_DIVIDER` | 4 | 代码常量 | 速度环分频(50Hz) | | `MOTOR_DEAD_ZONE` | 25 | 代码常量 | 电机最小有效 PWM | | `ANGLE_KP` | 25.0 | 代码/网页可调 | 角度环比例增益 | | `ANGLE_KI` | 0.0 | 代码/网页可调 | 角度环积分增益 | | `ANGLE_KD` | 0.8 | 代码/网页可调 | 角度环微分增益 | | `SPEED_KP` | 2.0 | 代码/网页可调 | 速度环比例增益 | | `SPEED_KI` | 0.05 | 代码/网页可调 | 速度环积分增益 | | `SPEED_KD` | 0.0 | 代码/网页可调 | 速度环微分增益 | | `SAFETY_TILT_LIMIT` | 45° | 代码常量 | 安全倾角上限 | | `ANGLE_SETPOINT_LIMIT` | 8° | 代码常量 | 速度环输出角度限幅 | ### 运行频率 | 任务 | 频率 | 说明 | | -------------- | ------ | ----------------- | | 角度环 PID | 200 Hz | 硬件定时器驱动 | | 速度环 PID | 50 Hz | 每4个角度环周期 | | WebSocket 遥测 | 10 Hz | loop() 中定时发送 | | 串口调试输出 | 2 Hz | loop() 中定时打印 | --- ## 常见问题 **Q: 上电后小车不动?** A: 默认 `balanceEnabled = false`,需通过网页点击 Enable 或发送 WebSocket 命令 `E:1`。 **Q: 电机转向反了(倾斜时往错误方向纠偏)?** A: 两种解决办法: 1. 交换电机接线(IN1/IN2 互换) 2. 在 `mpuUpdateAngle()` 中将 pitch 取反 **Q: 角度数据在 0 附近跳动很大?** A: 检查 MPU6050 的 I2C 连接是否牢固,确认 SCL/SDA 没有接反。必要时降低 I2C 速率到 100kHz。 **Q: 校准后 pitch 不为 0?** A: 校准时小车必须绝对静止。如果机械结构导致 MPU6050 有固定偏移,可在 `mpuUpdateAngle()` 后加一个固定偏移补偿。 **Q: WiFi 连不上?** A: 确认手机/电脑连接的是 `BalanceBot` 热点(密码 `12345678`),访问 `http://192.168.4.1`。部分手机会因"无互联网"自动断开,需在 WiFi 设置中关闭"自动切换到移动数据"。 **Q: 编译报错找不到 WebSocketsServer.h?** A: 在 Arduino IDE 中 `Tools → Manage Libraries`,搜索 `WebSockets`,安装 **Markus Sattler** 的版本。