| 
 
| 一、项目概述与基本原理 1.1 平衡车工作原理
 平衡车是一种基于倒立摆原理的两轮自平衡小车,其核心控制原理类似于人类保持平衡的过程。当人站立不稳时,会通过腿部肌肉的快速调整来维持平衡。平衡车同样通过传感器检测车身倾斜角度,利用电机驱动实现动态平衡。
 
 核心控制原理:
 
 姿态感知:通过MPU6050六轴传感器(三轴加速度计+三轴陀螺仪)实时检测小车的倾斜角度和角速度
 控制算法:使用PID算法计算电机控制量,其中:
 比例项§快速响应角度偏差
 积分项(I)消除静态误差
 微分项(D)抑制振荡
 执行机构:电机根据控制量调整转速,保持平衡
 1.2 系统组成与功能
 基本功能模块:
 
 主控制器:STM32F103C8T6(性价比高,资源丰富)
 姿态传感器:MPU6050(检测倾角)
 电机驱动:TB6612(高效驱动电机)
 编码器:测量电机转速(反馈控制)
 电源管理:锂电池供电系统
 通信模块:蓝牙/WiFi(无线控制)
 显示模块:OLED(状态显示)
 进阶功能扩展:
 
 超声波避障
 红外循迹
 手机APP遥控
 语音控制
 二、硬件设计与组装
 2.1 硬件选型指南
 
 
   
 2.2 详细电路连接
 STM32最小系统连接:
 
 8MHz晶振连接OSC_IN/OSC_OUT
 复位电路连接NRST
 Boot0通过10K电阻接地
 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)
 电源系统设计:
 
 主电源:3节18650锂电池(12V)
 5V降压:MP1584EN模块(为传感器供电)
 3.3V稳压:AMS1117-3.3(为MCU和传感器供电)
 2.3 PCB设计与制作
 设计要点:
 
 电机驱动部分走线加宽(至少1mm)
 模拟部分(传感器)与数字部分分开布局
 添加电源滤波电容(10uF电解+0.1uF陶瓷)
 MPU6050尽量靠近MCU放置
 常见问题解决:
 
 电机干扰导致复位:加强电源滤波,缩短电机线长度
 传感器数据跳动:确保I2C线上拉电阻(4.7K)正确连接
 电机不转:检查STBY引脚电平,确认PWM信号正常
 三、软件架构与核心算法
 3.1 系统软件架构
 分层设计:
 
 硬件抽象层(HAL):
 
 传感器驱动(MPU6050)
 电机驱动(TB6612)
 编码器接口
 通信接口(蓝牙/串口)
 算法层:
 
 姿态解算(互补滤波/卡尔曼滤波)
 PID控制算法
 数据滤波处理
 应用层:
 
 任务调度
 人机交互(按键/显示)
 遥控处理
 通信层:
 
 蓝牙协议处理
 串口调试接口
 3.2 姿态解算算法
 互补滤波实现:
 
 // 互补滤波函数
 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;
 }
 
 c
 运行
 
 
 3.3 PID控制算法
 串级PID实现:
 
 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
 }
 
 c
 运行
 
 
 四、系统实现与调试
 4.1 主程序框架
 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控制周期
 }
 }
 
 c
 运行
 
 
 4.2 关键模块实现
 MPU6050初始化与数据读取:
 
 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)类似...
 }
 
 c
 运行
 
 
 
 4.3 PID参数整定方法
 调参步骤:
 
 确定机械中值
 
 将小车放在地面上,寻找能够自然平衡的角度
 记录这个角度作为TARGET_ANGLE(通常在-3°到3°之间)
 直立环调参
 
 先调Kp(比例项):
 从较小值开始(如10)
 逐渐增大直到小车出现低频振荡
 典型值范围:20-50
 再调Kd(微分项):
 从0.1开始
 逐渐增大抑制振荡
 过大则会出现高频抖动
 典型值范围:0.3-0.8
 速度环调参
 
 先调Kp:
 从0.1开始
 增大使小车能抵抗外力
 过大则会出现前后摆动
 典型值范围:0.2-0.5
 Ki与Kp保持比例关系(Ki ≈ Kp/200):
 消除静态误差
 过大则积分饱和
 转向环调参(可选)
 
 使用单独的Kp控制
 根据转向灵敏度调整
 典型值范围:0.1-1.0
 调试技巧:
 
 使用蓝牙或串口实时调整参数
 记录数据并分析响应曲线
 采用"试凑法"结合理论分析
 先调内环再调外环
 五、进阶优化与功能扩展
 5.1 系统优化策略
 实时性优化:
 
 使用定时器中断确保控制周期精确
 
 void HAL_TIM_PeriodElapsedCallback(TIM_HandleTypeDef *htim) {
 if(htim->Instance == TIM6) { // 5ms定时器
 // 执行控制算法
 Control_Task();
 }
 }
 
 c
 运行
 
 优化传感器数据读取速度
 
 使用DMA传输减少CPU开销
 提高I2C时钟频率(400kHz)
 关键代码使用汇编优化
 
 PID计算等关键算法
 稳定性优化:
 
 增加软件看门狗
 异常状态检测与保护
 角度过大时切断电机
 通信异常处理
 5.2 功能扩展实现
 蓝牙遥控功能:
 
 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;
 }
 
 c
 运行
 
 
 
 数据记录与分析:
 
 使用SD卡模块记录运行数据
 通过无线模块上传到云端
 使用MATLAB/Python分析数据
 六、项目总结与进阶学习
 6.1 常见问题解决
 问题1:小车无法保持平衡
 
 检查传感器数据是否正确
 确认PID参数极性是否正确
 检查电机转向是否正确
 问题2:小车出现高频振荡
 
 减小微分项Kd
 检查机械结构是否牢固
 增加传感器数据滤波
 问题3:小车向一边偏移
 
 检查机械结构对称性
 校准传感器
 调整机械中值
 6.2 学习资源推荐
 开源项目参考:
 
 平衡小车之家开源项目
 Cleanflight/Betaflight飞控代码
 小马哥四轴开源项目
 推荐书籍:
 
 《STM32库开发实战指南》
 《自动控制原理》
 《嵌入式实时操作系统》
 进阶方向:
 
 改用RTOS实现多任务
 加入机器学习算法
 实现集群控制
 开发手机APP控制界面
 6.3 项目展示与分享
 博客撰写要点:
 
 项目背景与意义
 系统设计与实现
 关键技术难点与解决方案
 效果展示(视频/图片)
 经验总结与未来改进
 面试项目介绍要点:
 
 突出技术难点和解决方案
 展示对系统原理的深入理解
 说明个人贡献和收获
 准备技术细节的深入讨论
 通过本教程,您已经掌握了STM32平衡车从硬件设计到软件实现的完整开发流程。建议按照步骤实际动手实践,在实践中深化理解。平衡车项目是学习嵌入式系统和控制算法的绝佳平台,希望您能在此基础上不断探索和创新!
 ————————————————
 
 版权声明:本文为博主原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。
 
 原文链接:https://blog.csdn.net/niuTyler/article/details/146976690
 
 
 | 
 |