这次我们来深入探讨单片机平衡车的进阶调试方法。很多初学者在制作平衡车时,往往只会通过串口打印角度数据来调试,这种方法虽然基础,但效率低下且功能有限。本文将带你从串口打印的初级阶段,升级到更专业的调试手段。
平衡车项目的核心在于姿态感知和PID控制算法。STM32F103C8T6作为常用的主控芯片,配合MPU6050陀螺仪模块,能够实时获取车辆的俯仰角、横滚角等关键数据。但仅仅通过串口打印这些数据是远远不够的,我们需要更直观、更高效的调试方式。
本文将重点介绍OLED实时显示、蓝牙无线调试、PID参数在线调整等进阶技术。这些方法不仅能大幅提升调试效率,还能让你更深入地理解平衡车的控制原理。适合已经完成基础平衡车搭建,希望提升调试水平和项目完整度的开发者。
1. 核心能力速览
| 能力项 | 说明 |
|---|---|
| 主控芯片 | STM32F103C8T6(其他STM32系列也可参考) |
| 核心传感器 | MPU6050陀螺仪模块(姿态感知) |
| 电机驱动 | TB6612FNG电机驱动模块 |
| 调试方式 | 串口打印、OLED显示、蓝牙无线调试 |
| 通信接口 | UART、I2C、SPI |
| 电源管理 | 12V输入,LM2596降压至5V,AMS1117稳压至3.3V |
| 适合场景 | 平衡车调试优化、PID参数整定、实时监控 |
2. 适用场景与使用边界
平衡车调试技术的升级主要适用于以下场景:
适合场景:
- 已完成基础平衡车硬件搭建,需要进行PID参数整定
- 希望实现实时数据监控,而不仅仅是事后分析串口日志
- 需要无线远程调试,避免有线连接的限制
- 项目展示时需要更直观的数据显示方式
- 参加电子设计竞赛或课程设计,需要完善调试功能
技术边界:
- OLED显示适合近距离实时监控,不适合远距离观测
- 蓝牙调试距离通常限制在10米以内
- PID参数调试需要一定的控制理论基础知识
- 硬件资源有限时,需要权衡各种调试方式的内存占用
安全提醒:
- 平衡车调试时确保车辆有安全防护,避免突然失控
- 蓝牙通信要注意数据安全,避免被恶意控制
- 电源模块要做好绝缘防护,防止短路
3. 环境准备与前置条件
3.1 硬件准备
- STM32F103C8T6最小系统板
- MPU6050陀螺仪模块
- TB6612FNG电机驱动模块
- 直流电机与车轮套件
- 0.96寸OLED显示屏(I2C接口)
- HC-05或HC-06蓝牙模块
- 12V锂电池电源
- LM2596降压模块、AMS1117稳压模块
- 杜邦线、面包板或PCB板
3.2 软件环境
- Keil MDK或STM32CubeIDE开发环境
- STM32CubeMX配置工具
- 串口调试助手(如SSCOM、XCOM)
- 蓝牙调试APP(如蓝牙串口助手)
- ST-Link或J-Link下载器
3.3 基础代码准备
确保已经完成平衡车的基础功能:
- MPU6050数据读取和姿态解算
- 电机PWM驱动控制
- 基本的PID控制算法
- 串口通信功能
4. OLED实时显示实现
4.1 OLED模块连接
OLED显示屏通常采用I2C接口,连接方式如下:
STM32F103C8T6 OLED显示屏 PB6 ---> SCL PB7 ---> SDA 3.3V ---> VCC GND ---> GND4.2 OLED驱动代码
首先需要初始化I2C和OLED显示:
// OLED初始化函数 void OLED_Init(void) { // I2C初始化代码 I2C_Init(); // OLED初始化序列 OLED_Write_Cmd(0xAE); // 关闭显示 OLED_Write_Cmd(0xD5); // 设置时钟分频因子 OLED_Write_Cmd(0x80); // 设置分频因子 OLED_Write_Cmd(0xA8); // 设置多路复用率 OLED_Write_Cmd(0x3F); // 默认值 // ... 更多初始化命令 OLED_Write_Cmd(0xAF); // 开启显示 OLED_Clear(); // 清屏 } // 显示角度数据函数 void OLED_Show_Angle(float pitch, float roll) { char buffer[16]; // 清空显示区域 OLED_Clear_Part(0, 0, 128, 16); // 显示俯仰角 sprintf(buffer, "Pitch:%.2f", pitch); OLED_Show_String(0, 0, buffer); // 显示横滚角 sprintf(buffer, "Roll:%.2f", roll); OLED_Show_String(0, 2, buffer); }4.3 主循环中的显示更新
在平衡车的主控制循环中定期更新显示:
while(1) { // 读取MPU6050数据 MPU6050_Read_Data(&mpu_data); // 姿态解算 pitch = Kalman_Filter(mpu_data.Accel_X, mpu_data.Gyro_X); roll = Kalman_Filter(mpu_data.Accel_Y, mpu_data.Gyro_Y); // 更新OLED显示(每100ms更新一次) if(HAL_GetTick() - last_display_time > 100) { OLED_Show_Angle(pitch, roll); OLED_Show_PID(pid_pitch.kp, pid_pitch.ki, pid_pitch.kd); last_display_time = HAL_GetTick(); } // PID计算和电机控制 // ... }5. 蓝牙无线调试实现
5.1 蓝牙模块配置
HC-05蓝牙模块配置步骤:
// 进入AT模式(按住按键上电) void Bluetooth_AT_Mode(void) { // 配置串口波特率为38400 USART1->BRR = 0x1D4C; // 38400bps // 发送AT指令 Bluetooth_Send_CMD("AT"); Bluetooth_Send_CMD("AT+NAME=BalanceCar"); // 设置设备名称 Bluetooth_Send_CMD("AT+PSWD=1234"); // 设置配对密码 Bluetooth_Send_CMD("AT+UART=9600,0,0"); // 设置通信波特率 } // 正常通信模式初始化 void Bluetooth_Init(void) { // 配置串口波特率为9600 USART1->BRR = 0xEA6; // 9600bps }5.2 蓝牙数据接收处理
实现蓝牙命令解析功能:
// 蓝牙数据接收缓冲区 uint8_t bluetooth_rx_buffer[100]; uint8_t bluetooth_rx_index = 0; // 串口中断接收处理 void USART1_IRQHandler(void) { if(USART1->SR & USART_SR_RXNE) { uint8_t data = USART1->DR; if(data == '\n') // 命令结束符 { bluetooth_rx_buffer[bluetooth_rx_index] = '\0'; Bluetooth_CMD_Parse(bluetooth_rx_buffer); bluetooth_rx_index = 0; } else { bluetooth_rx_buffer[bluetooth_rx_index++] = data; if(bluetooth_rx_index >= 99) bluetooth_rx_index = 0; } } } // 命令解析函数 void Bluetooth_CMD_Parse(char* cmd) { if(strncmp(cmd, "KP=", 3) == 0) { float kp = atof(cmd + 3); pid_pitch.kp = kp; Bluetooth_Send_Data("KP set to: %.3f", kp); } else if(strncmp(cmd, "KI=", 3) == 0) { float ki = atof(cmd + 3); pid_pitch.ki = ki; Bluetooth_Send_Data("KI set to: %.3f", ki); } else if(strncmp(cmd, "KD=", 3) == 0) { float kd = atof(cmd + 3); pid_pitch.kd = kd; Bluetooth_Send_Data("KD set to: %.3f", kd); } else if(strcmp(cmd, "STATUS") == 0) { Bluetooth_Send_Status(); } }5.3 手机APP调试界面
可以使用现成的蓝牙串口APP或自定义开发:
推荐调试命令格式: KP=1.5 // 设置比例系数为1.5 KI=0.2 // 设置积分系数为0.2 KD=0.05 // 设置微分系数为0.05 STATUS // 获取当前状态6. 进阶调试技巧
6.1 数据波形显示
通过蓝牙将数据发送到上位机显示波形:
// 发送数据到上位机(兼容匿名科创地面站协议) void Send_To_UpperComputer(float pitch, float roll, float output) { uint8_t send_buf[20]; // 帧头 send_buf[0] = 0xAA; send_buf[1] = 0xAF; // 数据长度 send_buf[2] = 12; // 功能字 send_buf[3] = 0x01; // 数据内容 memcpy(&send_buf[4], &pitch, 4); memcpy(&send_buf[8], &roll, 4); memcpy(&send_buf[12], &output, 4); // 校验和 send_buf[16] = Check_Sum(send_buf, 16); // 发送 Bluetooth_Send_Bytes(send_buf, 17); }6.2 SD卡数据记录
对于长时间调试,可以使用SD卡记录数据:
// SD卡数据记录结构 typedef struct { uint32_t timestamp; float pitch; float roll; float pitch_rate; float roll_rate; float motor_output; } Data_Record_t; // 数据记录函数 void Data_Record_SDCard(Data_Record_t* data) { FIL file; FRESULT res; res = f_open(&file, "data.log", FA_WRITE | FA_OPEN_APPEND); if(res == FR_OK) { f_write(&file, data, sizeof(Data_Record_t), NULL); f_close(&file); } }6.3 自动参数整定
实现简单的自动PID整定算法:
// Ziegler-Nichols方法自动整定 void Auto_Tune_PID(void) { float ku, tu; // 临界增益和周期 float step_response[100]; int i = 0; // 逐步增加比例系数直到系统振荡 for(float kp = 0.1; kp < 5.0; kp += 0.1) { pid_pitch.kp = kp; pid_pitch.ki = 0; pid_pitch.kd = 0; // 记录系统响应 Delay_ms(100); step_response[i++] = Get_Pitch_Angle(); if(Is_Oscillating(step_response, i)) { ku = kp; // 记录临界增益 break; } } // 计算PID参数 pid_pitch.kp = 0.6 * ku; pid_pitch.ki = 1.2 * ku / tu; pid_pitch.kd = 0.075 * ku * tu; }7. PID参数调试实战
7.1 调试步骤
- 先调P参数:将I和D设为0,逐渐增大P直到系统开始振荡
- 再调D参数:加入D参数抑制振荡,提高系统稳定性
- 最后调I参数:加入I参数消除静差,提高控制精度
- 微调优化:根据实际效果微调三个参数
7.2 参数调试示例
// 初始参数设置 PID_InitTypeDef pid_pitch = { .kp = 0, // 从0开始 .ki = 0, // 初始为0 .kd = 0, // 初始为0 .max_output = 1000, .integral_limit = 1000 }; // 调试过程记录 void Debug_Process(void) { // 第一阶段:调P参数 pid_pitch.kp = 1.0; // 太小,响应慢 pid_pitch.kp = 2.0; // 开始有响应 pid_pitch.kp = 3.0; // 响应加快,但开始振荡 // 第二阶段:加入D参数 pid_pitch.kd = 0.1; // 抑制振荡 pid_pitch.kd = 0.2; // 振荡明显减弱 // 第三阶段:加入I参数 pid_pitch.ki = 0.05; // 消除静差 }7.3 调试注意事项
- 每次只调整一个参数,观察系统响应
- 调整幅度要小,避免系统失控
- 记录每次调整的效果,便于回溯
- 在安全环境下调试,防止平衡车损坏
8. 硬件优化建议
8.1 电源管理优化
// 电源监测功能 void Power_Monitor(void) { float voltage = Read_Battery_Voltage(); float current = Read_Current(); OLED_Show_Power(voltage, current); // 低电压保护 if(voltage < 10.5f) // 12V电池保护电压 { Motor_Stop(); OLED_Show_String(0, 4, "LOW POWER!"); } }8.2 电机驱动优化
使用PID速度环+位置环双闭环控制:
// 双闭环PID控制 void Double_Loop_PID_Control(void) { // 外环:角度环(位置环) float angle_target = 0; // 平衡目标角度 float angle_error = angle_target - current_pitch; float speed_target = pid_angle.Calculate(angle_error); // 内环:速度环 float speed_error = speed_target - current_speed; float motor_output = pid_speed.Calculate(speed_error); // 输出到电机 Motor_Set_Speed(motor_output); }8.3 传感器滤波优化
结合卡尔曼滤波和互补滤波:
// 改进的姿态解算 float Improved_Attitude_Estimation(float accel, float gyro) { // 卡尔曼滤波预测 kalman_predict(gyro); // 卡尔曼滤波更新 float angle = kalman_update(accel); // 互补滤波辅助 angle = 0.98 * angle + 0.02 * accel; return angle; }9. 常见问题与解决方案
9.1 调试问题排查表
| 问题现象 | 可能原因 | 排查方法 | 解决方案 |
|---|---|---|---|
| 平衡车无法站立 | PID参数不合适 | 查看角度数据是否准确 | 重新调试PID参数 |
| 电机抖动严重 | D参数过大或过小 | 观察波形是否振荡 | 调整D参数抑制振荡 |
| 存在静差 | I参数不足 | 检查稳态误差 | 适当增加I参数 |
| 响应迟钝 | P参数过小 | 测试阶跃响应 | 增大P参数 |
| 蓝牙连接失败 | 模块未正确配置 | 检查AT指令响应 | 重新配置蓝牙模块 |
| OLED不显示 | I2C通信失败 | 检查硬件连接 | 确认地址和时序 |
9.2 硬件问题排查
- 电源问题:使用万用表测量各点电压
- 通信问题:使用逻辑分析仪检查I2C/SPI波形
- 传感器问题:通过读取原始数据验证传感器工作状态
- 电机问题:单独测试电机驱动是否正常
9.3 软件调试技巧
- 使用断点调试关键函数
- 添加调试日志输出重要变量
- 使用版本控制记录参数修改历史
- 建立测试用例验证核心算法
10. 项目扩展与进阶方向
10.1 功能扩展
- 远程监控:通过WiFi模块实现网页远程监控
- 路径规划:添加GPS模块实现自动导航
- 避障功能:集成超声波或红外避障传感器
- 手机APP:开发专用手机控制APP
10.2 性能优化
- 算法优化:使用更先进的控制算法(如模糊PID、自适应PID)
- 硬件升级:选用性能更好的主控芯片(如STM32F4系列)
- 传感器融合:结合多种传感器提高姿态解算精度
10.3 实际应用
- 教育展示:用于控制理论教学演示
- 竞赛项目:参加电子设计竞赛或机器人竞赛
- 产品原型:作为智能平衡车产品的技术验证
通过本文介绍的进阶调试方法,你可以摆脱单纯依赖串口打印的初级调试方式,实现更高效、更专业的平衡车开发。从OLED实时显示到蓝牙无线调试,从基础PID整定到高级控制算法,这些技术将显著提升你的项目开发水平。
实际调试过程中,建议先确保硬件连接正确,然后逐步添加调试功能。记得保存每次调试的参数记录,建立自己的经验库。平衡车调试是一个需要耐心的过程,但只要方法得当,一定能获得满意的效果。