STM32平衡车开发实战教程:从零基础到项目精通

STM32平衡车开发实战教程:从零基础到项目精通

一、项目概述与基本原理

1.1 平衡车工作原理

平衡车是一种基于倒立摆原理的两轮自平衡小车,其核心控制原理类似于人类保持平衡的过程。当人站立不稳时,会通过腿部肌肉的快速调整来维持平衡。平衡车同样通过传感器检测车身倾斜角度,利用电机驱动实现动态平衡。

核心控制原理

  1. 姿态感知:通过MPU6050六轴传感器(三轴加速度计+三轴陀螺仪)实时检测小车的倾斜角度和角速度
  2. 控制算法 :使用PID算法计算电机控制量,其中:
    • 比例项§快速响应角度偏差
    • 积分项(I)消除静态误差
    • 微分项(D)抑制振荡
  3. 执行机构:电机根据控制量调整转速,保持平衡

1.2 系统组成与功能

基本功能模块

  • 主控制器:STM32F103C8T6(性价比高,资源丰富)
  • 姿态传感器:MPU6050(检测倾角)
  • 电机驱动:TB6612(高效驱动电机)
  • 编码器:测量电机转速(反馈控制)
  • 电源管理:锂电池供电系统
  • 通信模块:蓝牙/WiFi(无线控制)
  • 显示模块:OLED(状态显示)

进阶功能扩展

  • 超声波避障
  • 红外循迹
  • 手机APP遥控
  • 语音控制

二、硬件设计与组装

2.1 硬件选型指南

部件 推荐型号 关键参数 注意事项
主控芯片 STM32F103C8T6 Cortex-M3内核,72MHz主频,64KB Flash 确保有足够定时器资源
姿态传感器 MPU6050 ±2g加速度,±2000°/s陀螺仪 注意安装方向与位置
电机 MG315带编码器 减速比1:30,编码器11线 需配对使用
电机驱动 TB6612FNG 双通道,1.2A连续电流 比L298N效率高
电源 18650锂电池×3 12V供电 需配保护板
蓝牙模块 HC-06 蓝牙4.0,串口通信 注意主从模式
显示模块 0.96寸OLED I2C接口,128×64分辨率 可选SPI接口版本

2.2 详细电路连接

STM32最小系统连接

  1. 8MHz晶振连接OSC_IN/OSC_OUT
  2. 复位电路连接NRST
  3. Boot0通过10K电阻接地
  4. 3.3V稳压电路(AMS1117-3.3)

MPU6050连接

  • VCC → 3.3V
  • GND → GND
  • SCL → PB6
  • SDA → PB7
  • INT → PA0(外部中断)

TB6612电机驱动连接

  • PWMA → PA8(TIM1_CH1)
  • PWMB → PA11(TIM1_CH4)
  • AIN1 → PC13, AIN2 → PC14
  • BIN1 → PC15, BIN2 → PD2
  • STBY → 3.3V(常使能)

编码器连接

  • 左编码器A相 → PA0(TIM2_CH1)
  • 左编码器B相 → PA1(TIM2_CH2)
  • 右编码器A相 → PB6(TIM4_CH1)
  • 右编码器B相 → PB7(TIM4_CH2)

电源系统设计

  1. 主电源:3节18650锂电池(12V)
  2. 5V降压:MP1584EN模块(为传感器供电)
  3. 3.3V稳压:AMS1117-3.3(为MCU和传感器供电)

2.3 PCB设计与制作

设计要点

  1. 电机驱动部分走线加宽(至少1mm)
  2. 模拟部分(传感器)与数字部分分开布局
  3. 添加电源滤波电容(10uF电解+0.1uF陶瓷)
  4. MPU6050尽量靠近MCU放置

常见问题解决

  • 电机干扰导致复位:加强电源滤波,缩短电机线长度
  • 传感器数据跳动:确保I2C线上拉电阻(4.7K)正确连接
  • 电机不转:检查STBY引脚电平,确认PWM信号正常

三、软件架构与核心算法

3.1 系统软件架构

分层设计

  1. 硬件抽象层(HAL)

    • 传感器驱动(MPU6050)
    • 电机驱动(TB6612)
    • 编码器接口
    • 通信接口(蓝牙/串口)
  2. 算法层

    • 姿态解算(互补滤波/卡尔曼滤波)
    • PID控制算法
    • 数据滤波处理
  3. 应用层

    • 任务调度
    • 人机交互(按键/显示)
    • 遥控处理
  4. 通信层

    • 蓝牙协议处理
    • 串口调试接口

3.2 姿态解算算法

互补滤波实现

c 复制代码
// 互补滤波函数
float ComplementaryFilter(float accelAngle, float gyroRate, float dt, float alpha) {
    static float angle = 0;
    angle = alpha * (angle + gyroRate * dt) + (1 - alpha) * accelAngle;
    return angle;
}

// 调用示例
void IMU_Update() {
    // 读取传感器原始数据
    MPU6050_ReadData(&accelX, &accelY, &accelZ, &gyroX, &gyroY, &gyroZ);
    
    // 计算加速度角度
    float accelAngle = atan2(accelY, accelZ) * 180.0 / PI;
    
    // 获取陀螺仪角速度(转换为度/秒)
    float gyroRate = gyroX / 16.4f;
    
    // 互补滤波
    float dt = 0.005f; // 5ms采样周期
    float alpha = 0.98f; // 滤波系数
    currentAngle = ComplementaryFilter(accelAngle, gyroRate, dt, alpha);
}

卡尔曼滤波实现

c 复制代码
typedef struct {
    float Q_angle; // 过程噪声协方差
    float Q_bias;  // 过程噪声协方差
    float R_measure; // 测量噪声协方差
    
    float angle;  // 计算得到的最优角度
    float bias;   // 陀螺仪偏置
    float P[2][2]; // 误差协方差矩阵
} KalmanFilter;

float Kalman_Update(KalmanFilter *kf, float newAngle, float newRate, float dt) {
    // 预测步骤
    kf->angle += dt * (newRate - kf->bias);
    kf->P[0][0] += dt * (dt*kf->P[1][1] - kf->P[0][1] - kf->P[1][0] + kf->Q_angle);
    kf->P[0][1] -= dt * kf->P[1][1];
    kf->P[1][0] -= dt * kf->P[1][1];
    kf->P[1][1] += kf->Q_bias * dt;
    
    // 更新步骤
    float y = newAngle - kf->angle;
    float S = kf->P[0][0] + kf->R_measure;
    float K[2];
    K[0] = kf->P[0][0] / S;
    K[1] = kf->P[1][0] / S;
    
    // 更新估计值
    kf->angle += K[0] * y;
    kf->bias += K[1] * y;
    
    // 更新协方差矩阵
    float P00_temp = kf->P[0][0];
    float P01_temp = kf->P[0][1];
    
    kf->P[0][0] -= K[0] * P00_temp;
    kf->P[0][1] -= K[0] * P01_temp;
    kf->P[1][0] -= K[1] * P00_temp;
    kf->P[1][1] -= K[1] * P01_temp;
    
    return kf->angle;
}

3.3 PID控制算法

串级PID实现

c 复制代码
typedef struct {
    float Kp, Ki, Kd;
    float integral;
    float prevError;
    float integralLimit;
} PID_Controller;

float PID_Update(PID_Controller *pid, float error, float dt) {
    // 比例项
    float proportional = pid->Kp * error;
    
    // 积分项
    pid->integral += error * dt;
    // 积分限幅
    if(pid->integral > pid->integralLimit) pid->integral = pid->integralLimit;
    else if(pid->integral < -pid->integralLimit) pid->integral = -pid->integralLimit;
    float integral = pid->Ki * pid->integral;
    
    // 微分项
    float derivative = pid->Kd * (error - pid->prevError) / dt;
    pid->prevError = error;
    
    return proportional + integral + derivative;
}

// 直立环PD控制
float Balance_PID(float angle, float targetAngle, float gyroRate) {
    static PID_Controller pid = {20.0f, 0.0f, 0.5f, 0, 0, 1000};
    float error = angle - targetAngle;
    return PID_Update(&pid, error, 0.005f) + pid.Kd * gyroRate;
}

// 速度环PI控制
float Velocity_PID(int encoderLeft, int encoderRight) {
    static PID_Controller pid = {0.3f, 0.001f, 0.0f, 0, 0, 10000};
    int speed = (encoderLeft + encoderRight) / 2; // 平均速度
    return PID_Update(&pid, -speed, 0.005f); // 目标速度为0
}

四、系统实现与调试

4.1 主程序框架

c 复制代码
int main(void) {
    // 系统初始化
    HAL_Init();
    SystemClock_Config();
    MX_GPIO_Init();
    MX_TIM1_Init(); // PWM定时器
    MX_TIM2_Init(); // 编码器1
    MX_TIM4_Init(); // 编码器2
    MX_I2C1_Init(); // MPU6050
    MX_USART1_UART_Init(); // 调试串口
    
    // 外设初始化
    MPU6050_Init();
    Motor_Init();
    Encoder_Init();
    OLED_Init();
    Bluetooth_Init();
    
    // 主循环
    while (1) {
        // 1. 读取传感器数据
        MPU6050_ReadData(&imuData);
        
        // 2. 姿态解算
        currentAngle = ComplementaryFilter(
            atan2(imuData.Accel_Y, imuData.Accel_Z) * RAD_TO_DEG,
            imuData.Gyro_X,
            0.005f, // 5ms
            0.98f
        );
        
        // 3. 读取编码器速度
        int speedLeft = Read_Encoder(TIM_ENCODER_LEFT);
        int speedRight = Read_Encoder(TIM_ENCODER_RIGHT);
        
        // 4. PID控制计算
        float balanceOut = Balance_PID(currentAngle, TARGET_ANGLE, imuData.Gyro_X);
        float speedOut = Velocity_PID(speedLeft, speedRight);
        
        // 5. 综合控制输出
        int motorOut = balanceOut + speedOut;
        
        // 6. 电机控制
        Motor_SetPWM(MOTOR_LEFT, motorOut);
        Motor_SetPWM(MOTOR_RIGHT, motorOut);
        
        // 7. 状态显示与通信
        if(HAL_GetTick() - lastDisplayTime >= 100) { // 100ms更新一次显示
            OLED_ShowAngle(currentAngle);
            Bluetooth_SendData(currentAngle);
            lastDisplayTime = HAL_GetTick();
        }
        
        HAL_Delay(5); // 5ms控制周期
    }
}

4.2 关键模块实现

MPU6050初始化与数据读取

c 复制代码
void MPU6050_Init(void) {
    // 复位设备
    MPU6050_WriteByte(MPU6050_RA_PWR_MGMT_1, 0x80);
    HAL_Delay(100);
    
    // 唤醒设备,选择时钟源
    MPU6050_WriteByte(MPU6050_RA_PWR_MGMT_1, 0x01);
    
    // 设置陀螺仪量程 ±2000°/s
    MPU6050_WriteByte(MPU6050_RA_GYRO_CONFIG, 0x18);
    
    // 设置加速度计量程 ±2g
    MPU6050_WriteByte(MPU6050_RA_ACCEL_CONFIG, 0x00);
    
    // 设置低通滤波器带宽 44Hz
    MPU6050_WriteByte(MPU6050_RA_CONFIG, 0x03);
    
    // 设置采样率 1kHz
    MPU6050_WriteByte(MPU6050_RA_SMPLRT_DIV, 0x00);
}

void MPU6050_ReadData(MPU6050_Data *data) {
    uint8_t buf[14];
    MPU6050_ReadBytes(MPU6050_RA_ACCEL_XOUT_H, 14, buf);
    
    data->Accel_X = (int16_t)(buf[0] << 8 | buf[1]);
    data->Accel_Y = (int16_t)(buf[2] << 8 | buf[3]);
    data->Accel_Z = (int16_t)(buf[4] << 8 | buf[5]);
    data->Temp = (int16_t)(buf[6] << 8 | buf[7]);
    data->Gyro_X = (int16_t)(buf[8] << 8 | buf[9]);
    data->Gyro_Y = (int16_t)(buf[10] << 8 | buf[11]);
    data->Gyro_Z = (int16_t)(buf[12] << 8 | buf[13]);
}

编码器接口配置

c 复制代码
void Encoder_Init(void) {
    TIM_Encoder_InitTypeDef sConfig = {0};
    TIM_MasterConfigTypeDef sMasterConfig = {0};
    
    // 编码器1配置(TIM2)
    htim2.Instance = TIM2;
    htim2.Init.Prescaler = 0;
    htim2.Init.CounterMode = TIM_COUNTERMODE_UP;
    htim2.Init.Period = 65535;
    htim2.Init.ClockDivision = TIM_CLOCKDIVISION_DIV1;
    htim2.Init.AutoReloadPreload = TIM_AUTORELOAD_PRELOAD_DISABLE;
    
    sConfig.EncoderMode = TIM_ENCODERMODE_TI12;
    sConfig.IC1Polarity = TIM_ICPOLARITY_RISING;
    sConfig.IC1Selection = TIM_ICSELECTION_DIRECTTI;
    sConfig.IC1Prescaler = TIM_ICPSC_DIV1;
    sConfig.IC1Filter = 0;
    
    sConfig.IC2Polarity = TIM_ICPOLARITY_RISING;
    sConfig.IC2Selection = TIM_ICSELECTION_DIRECTTI;
    sConfig.IC2Prescaler = TIM_ICPSC_DIV1;
    sConfig.IC2Filter = 0;
    
    HAL_TIM_Encoder_Init(&htim2, &sConfig);
    
    sMasterConfig.MasterOutputTrigger = TIM_TRGO_RESET;
    sMasterConfig.MasterSlaveMode = TIM_MASTERSLAVEMODE_DISABLE;
    HAL_TIMEx_MasterConfigSynchronization(&htim2, &sMasterConfig);
    
    HAL_TIM_Encoder_Start(&htim2, TIM_CHANNEL_ALL);
    
    // 编码器2配置(TIM4)类似...
}

4.3 PID参数整定方法

调参步骤

  1. 确定机械中值

    • 将小车放在地面上,寻找能够自然平衡的角度
    • 记录这个角度作为TARGET_ANGLE(通常在-3°到3°之间)
  2. 直立环调参

    • 先调Kp(比例项):
      • 从较小值开始(如10)
      • 逐渐增大直到小车出现低频振荡
      • 典型值范围:20-50
    • 再调Kd(微分项):
      • 从0.1开始
      • 逐渐增大抑制振荡
      • 过大则会出现高频抖动
      • 典型值范围:0.3-0.8
  3. 速度环调参

    • 先调Kp:
      • 从0.1开始
      • 增大使小车能抵抗外力
      • 过大则会出现前后摆动
      • 典型值范围:0.2-0.5
    • Ki与Kp保持比例关系(Ki ≈ Kp/200):
      • 消除静态误差
      • 过大则积分饱和
  4. 转向环调参(可选)

    • 使用单独的Kp控制
    • 根据转向灵敏度调整
    • 典型值范围:0.1-1.0

调试技巧

  • 使用蓝牙或串口实时调整参数
  • 记录数据并分析响应曲线
  • 采用"试凑法"结合理论分析
  • 先调内环再调外环

五、进阶优化与功能扩展

5.1 系统优化策略

实时性优化

  1. 使用定时器中断确保控制周期精确

    c 复制代码
    void HAL_TIM_PeriodElapsedCallback(TIM_HandleTypeDef *htim) {
        if(htim->Instance == TIM6) { // 5ms定时器
            // 执行控制算法
            Control_Task();
        }
    }
  2. 优化传感器数据读取速度

    • 使用DMA传输减少CPU开销
    • 提高I2C时钟频率(400kHz)
  3. 关键代码使用汇编优化

    • PID计算等关键算法

稳定性优化

  1. 增加软件看门狗
  2. 异常状态检测与保护
    • 角度过大时切断电机
    • 通信异常处理

5.2 功能扩展实现

蓝牙遥控功能

c 复制代码
void Bluetooth_Process(void) {
    if(UART_Receive(&huart3, &bluetoothData, 1) == HAL_OK) {
        switch(bluetoothData) {
            case 'F': targetSpeed += 10; break; // 前进
            case 'B': targetSpeed -= 10; break; // 后退
            case 'L': turnOffset = -5; break;   // 左转
            case 'R': turnOffset = 5; break;    // 右转
            case 'S': targetSpeed = 0; break;   // 停止
        }
    }
}

超声波避障功能

c 复制代码
float Ultrasonic_GetDistance(void) {
    // 触发信号
    HAL_GPIO_WritePin(TRIG_GPIO_Port, TRIG_Pin, GPIO_PIN_SET);
    HAL_Delay(0.01);
    HAL_GPIO_WritePin(TRIG_GPIO_Port, TRIG_Pin, GPIO_PIN_RESET);
    
    // 等待回波
    while(HAL_GPIO_ReadPin(ECHO_GPIO_Port, ECHO_Pin) == GPIO_PIN_RESET);
    uint32_t start = HAL_GetTick();
    
    while(HAL_GPIO_ReadPin(ECHO_GPIO_Port, ECHO_Pin) == GPIO_PIN_SET);
    uint32_t end = HAL_GetTick();
    
    // 计算距离(cm)
    return (end - start) * 0.034 / 2;
}

数据记录与分析

  1. 使用SD卡模块记录运行数据
  2. 通过无线模块上传到云端
  3. 使用MATLAB/Python分析数据

六、项目总结与进阶学习

6.1 常见问题解决

问题1:小车无法保持平衡

  • 检查传感器数据是否正确
  • 确认PID参数极性是否正确
  • 检查电机转向是否正确

问题2:小车出现高频振荡

  • 减小微分项Kd
  • 检查机械结构是否牢固
  • 增加传感器数据滤波

问题3:小车向一边偏移

  • 检查机械结构对称性
  • 校准传感器
  • 调整机械中值

6.2 学习资源推荐

开源项目参考

  1. 平衡小车之家开源项目
  2. Cleanflight/Betaflight飞控代码
  3. 小马哥四轴开源项目

推荐书籍

  • 《STM32库开发实战指南》
  • 《自动控制原理》
  • 《嵌入式实时操作系统》

进阶方向

  1. 改用RTOS实现多任务
  2. 加入机器学习算法
  3. 实现集群控制
  4. 开发手机APP控制界面

6.3 项目展示与分享

博客撰写要点

  1. 项目背景与意义
  2. 系统设计与实现
  3. 关键技术难点与解决方案
  4. 效果展示(视频/图片)
  5. 经验总结与未来改进

面试项目介绍要点

  • 突出技术难点和解决方案
  • 展示对系统原理的深入理解
  • 说明个人贡献和收获
  • 准备技术细节的深入讨论

通过本教程,您已经掌握了STM32平衡车从硬件设计到软件实现的完整开发流程。建议按照步骤实际动手实践,在实践中深化理解。平衡车项目是学习嵌入式系统和控制算法的绝佳平台,希望您能在此基础上不断探索和创新!

相关推荐
国科安芯1 天前
航天器多路并联大功率电源系统设计与ASP4644均流特性分析
单片机·嵌入式硬件·fpga开发·安全性测试
茯苓gao1 天前
嵌入式开发笔记:CANopen相关移位运算与通信协议术语详解
网络·嵌入式硬件·学习·信息与通信
梁朝辉1 天前
STM32H750VBT6中ADCINP与INN什么区别
stm32·单片机·嵌入式硬件
省四收割者1 天前
一文详解信号完整性(1)
python·嵌入式硬件·数学建模·信息与通信·信号处理·智能硬件
崇山峻岭之间1 天前
单片机无感FOC驱动之ADC
单片机·嵌入式硬件
Ligocious1 天前
1.点亮一颗小小的LED
单片机·嵌入式硬件
KaifuZeng1 天前
通信与接口协议面试八、CAN通信
单片机·嵌入式硬件·面试·通信与接口协议
智源单片机设计1 天前
基于单片机的直流电机双闭环调速系统设计
单片机·嵌入式硬件
kebidaixu1 天前
两轮车换电 BMS 的模拟前端中颖 SH367306 AFE 芯片详解
stm32
kebidaixu1 天前
两轮车换电 BMS 的模拟前端中颖 SH367306 AFE 芯片调试实录:四个关键问题与解决方案
stm32