STM32平衡車開發實戰教程:從零基礎到項目精通
一、項目概述與基本原理
1.1 平衡車工作原理
平衡車是一種基于倒立擺原理的兩輪自平衡小車,其核心控制原理類似于人類保持平衡的過程。當人站立不穩時,會通過腿部肌肉的快速調整來維持平衡。平衡車同樣通過傳感器檢測車身傾斜角度,利用電機驅動實現動態平衡。
核心控制原理:
- 姿態感知:通過MPU6050六軸傳感器(三軸加速度計+三軸陀螺儀)實時檢測小車的傾斜角度和角速度
- 控制算法:使用PID算法計算電機控制量,其中:
- 比例項§快速響應角度偏差
- 積分項(I)消除靜態誤差
- 微分項(D)抑制振蕩
- 執行機構:電機根據控制量調整轉速,保持平衡
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最小系統連接:
- 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);
}
卡爾曼濾波實現:
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實現:
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 主程序框架
int main(void) {// 系統初始化HAL_Init();SystemClock_Config();MX_GPIO_Init();MX_TIM1_Init(); // PWM定時器MX_TIM2_Init(); // 編碼器1MX_TIM4_Init(); // 編碼器2MX_I2C1_Init(); // MPU6050MX_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, // 5ms0.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初始化與數據讀取:
void MPU6050_Init(void) {// 復位設備MPU6050_WriteByte(MPU6050_RA_PWR_MGMT_1, 0x80);HAL_Delay(100);// 喚醒設備,選擇時鐘源MPU6050_WriteByte(MPU6050_RA_PWR_MGMT_1, 0x01);// 設置陀螺儀量程 ±2000°/sMPU6050_WriteByte(MPU6050_RA_GYRO_CONFIG, 0x18);// 設置加速度計量程 ±2gMPU6050_WriteByte(MPU6050_RA_ACCEL_CONFIG, 0x00);// 設置低通濾波器帶寬 44HzMPU6050_WriteByte(MPU6050_RA_CONFIG, 0x03);// 設置采樣率 1kHzMPU6050_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]);
}
編碼器接口配置:
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參數整定方法
調參步驟:
-
確定機械中值
- 將小車放在地面上,尋找能夠自然平衡的角度
- 記錄這個角度作為TARGET_ANGLE(通常在-3°到3°之間)
-
直立環調參
- 先調Kp(比例項):
- 從較小值開始(如10)
- 逐漸增大直到小車出現低頻振蕩
- 典型值范圍:20-50
- 再調Kd(微分項):
- 從0.1開始
- 逐漸增大抑制振蕩
- 過大則會出現高頻抖動
- 典型值范圍:0.3-0.8
- 先調Kp(比例項):
-
速度環調參
- 先調Kp:
- 從0.1開始
- 增大使小車能抵抗外力
- 過大則會出現前后擺動
- 典型值范圍:0.2-0.5
- Ki與Kp保持比例關系(Ki ≈ Kp/200):
- 消除靜態誤差
- 過大則積分飽和
- 先調Kp:
-
轉向環調參(可選)
- 使用單獨的Kp控制
- 根據轉向靈敏度調整
- 典型值范圍:0.1-1.0
調試技巧:
- 使用藍牙或串口實時調整參數
- 記錄數據并分析響應曲線
- 采用"試湊法"結合理論分析
- 先調內環再調外環
五、進階優化與功能擴展
5.1 系統優化策略
實時性優化:
-
使用定時器中斷確保控制周期精確
void HAL_TIM_PeriodElapsedCallback(TIM_HandleTypeDef *htim) {if(htim->Instance == TIM6) { // 5ms定時器// 執行控制算法Control_Task();} }
-
優化傳感器數據讀取速度
- 使用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; // 停止}}
}
超聲波避障功能:
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;
}
數據記錄與分析:
- 使用SD卡模塊記錄運行數據
- 通過無線模塊上傳到云端
- 使用MATLAB/Python分析數據
六、項目總結與進階學習
6.1 常見問題解決
問題1:小車無法保持平衡
- 檢查傳感器數據是否正確
- 確認PID參數極性是否正確
- 檢查電機轉向是否正確
問題2:小車出現高頻振蕩
- 減小微分項Kd
- 檢查機械結構是否牢固
- 增加傳感器數據濾波
問題3:小車向一邊偏移
- 檢查機械結構對稱性
- 校準傳感器
- 調整機械中值
6.2 學習資源推薦
開源項目參考:
- 平衡小車之家開源項目
- Cleanflight/Betaflight飛控代碼
- 小馬哥四軸開源項目
推薦書籍:
- 《STM32庫開發實戰指南》
- 《自動控制原理》
- 《嵌入式實時操作系統》
進階方向:
- 改用RTOS實現多任務
- 加入機器學習算法
- 實現集群控制
- 開發手機APP控制界面
6.3 項目展示與分享
博客撰寫要點:
- 項目背景與意義
- 系統設計與實現
- 關鍵技術難點與解決方案
- 效果展示(視頻/圖片)
- 經驗總結與未來改進
面試項目介紹要點:
- 突出技術難點和解決方案
- 展示對系統原理的深入理解
- 說明個人貢獻和收獲
- 準備技術細節的深入討論
通過本教程,您已經掌握了STM32平衡車從硬件設計到軟件實現的完整開發流程。建議按照步驟實際動手實踐,在實踐中深化理解。平衡車項目是學習嵌入式系統和控制算法的絕佳平臺,希望您能在此基礎上不斷探索和創新!