
简介本资源是一套面向高校嵌入式系统课程设计与期末大作业的完整实践项目专为WHEELTEC B570平衡小车平台开发适用于嵌入式初学者及课程实践者解决从原理理解、代码调试到功能验证的一站式学习需求。压缩包共315个文件涵盖44个C源文件核心控制逻辑、44个头文件外设与算法接口定义、71个HAL库相关源码如stm32f1xx_hal_tim.c、stm32f1xx_hal_uart.c等、44个编译中间文件.o/.d以及Makefile、链接脚本.ld、工程配置.ioc/.mxproject和固件输出.hex/.elf等关键构建要素总大小仅1.13MB结构规范、依赖清晰。已有266人下载学习资源含全程中文注释、模块化函数设计与DMP姿态解算inv_mpu_dmp_motion_driver.c、PID平衡控制等核心实现配套文档详述硬件连接、编译部署与现象调试方法开箱即用助学生高效完成高分课程设计。1. 项目缘起从零到一一个“满分”平衡小车项目的诞生最近在整理过去的项目资料翻到了几年前做的一个嵌入式课程设计——基于WHEELTEC B570的两轮自平衡小车。这个项目当时拿了满分源码和文档都还保存得比较完整。今天正好借着这个机会把它重新梳理一遍分享出来。对于正在学习STM32、PID控制或者想动手做一个属于自己的平衡小车的朋友来说这个项目应该能提供一个非常完整的参考。平衡小车听起来很酷做起来也确实能学到不少东西。它本质上是一个典型的“倒立摆”控制系统问题核心就是通过传感器通常是MPU6050这类惯性测量单元实时感知车体的倾斜角度和角速度然后通过控制器我们用的是经典的PID算法计算出合适的电机控制量驱动两个轮子朝倾斜的方向加速或减速从而维持车体的直立平衡。这整个过程从硬件选型、电路搭建、传感器数据读取与融合、控制算法实现到最后的参数整定与调试几乎涵盖了嵌入式开发中从底层驱动到上层应用逻辑的所有关键环节。我用的主控是STM32F103C8T6也就是大家常说的“蓝桥杯”最小系统板或者“核心板”性价比极高。电机驱动用的是TB6612传感器是MPU6050。整个项目的代码是用标准库写的结构清晰注释也比较详细。文档部分则包含了原理分析、硬件连接图、软件流程图以及详细的调试记录。接下来我就把这个项目的核心实现逻辑、关键代码解析以及调试过程中踩过的那些“坑”毫无保留地分享给大家。2. 硬件平台深度解析为什么是WHEELTEC B570与STM32F103做嵌入式项目硬件是地基。地基没打牢后面的软件写得再漂亮也是空中楼阁。这个项目基于WHEELTEC的B570小车底盘主控选择了经典的STM32F103C8T6这套组合在入门级平衡小车项目中非常典型其背后的选型逻辑值得深究。2.1 底盘与电机B570的机械结构考量WHEELTEC B570是一个成熟的两轮差分底盘套件。选择它而不是自己从零开始加工亚克力板、安装电机主要出于以下几点考虑集成度高它已经将两个带编码器的减速直流电机、轮子、底盘结构件完美集成在一起。电机参数如减速比、额定电压、空载转速匹配合理机械结构稳定省去了大量机械设计和装配调试的时间。编码器电机自带增量式光电编码器这是实现速度闭环控制的关键。编码器输出通常是AB两相正交脉冲通过STM32的定时器编码器接口可以轻松读取电机的转速和方向。可扩展性底盘上预留了丰富的安装孔位和空间方便固定主控板、电池、传感器模块和各种扩展模块。这里有一个容易被忽略的细节电机的减速比。B570配套的电机减速比通常在1:30到1:50之间。这个参数直接影响最终小车的“力气”和“速度”。减速比大扭矩大带负载能力强但最高转速低反之亦然。在软件中设置速度环PID参数时需要心里有这个数。我们的控制量最终是电机的PWM占空比但它通过减速机构作用到轮子上的实际效果是和减速比强相关的。2.2 主控芯片STM32F103C8T6的性价比之选为什么是STM32F103C8T6在项目初期我也考虑过STM32F4甚至F7系列或者更简单的51单片机。最终选择“F103”是基于一个平衡车项目对算力、外设和成本的实际需求算力需求平衡车的核心控制周期通常在5-10ms。在这个周期内需要完成MPU6050的I2C数据读取约几百微秒、姿态解算如互补滤波几十微秒、直立环和速度环PID运算微秒级以及通过PWM驱动电机。STM32F103C8T672MHz主频的算力对此绰绰有余。盲目追求高主频芯片对于入门项目来说是一种浪费。外设需求这是关键。一个平衡小车至少需要2个定时器用于产生两路PWM信号控制两个电机的速度。2个定时器的编码器接口模式用于读取两个电机编码器的脉冲。1个I2C接口用于连接MPU6050。1个UART接口用于连接蓝牙模块实现无线调试和控制可选但强烈建议。若干GPIO用于控制电机驱动芯片的方向引脚、使能引脚等。 STM32F103C8T6完全满足以上需求并且引脚数量48脚也刚好够用不会造成资源浪费。生态与成本STM32的生态极其完善标准库、HAL库资料海量遇到问题几乎都能找到解决方案。C8T6核心板价格低廉降低了项目的试错成本。注意使用STM32F103时要特别注意其I2C硬件接口在标准库下的“坑”。很多人反映其I2C不稳定容易卡死。在我们的项目中为了避免这个问题我采用了“软件模拟I2C”Software I2C的方式来驱动MPU6050。虽然效率稍低但稳定性极高这对于需要持续可靠读取传感器数据的平衡小车来说至关重要。代码中会看到GPIO_I2C_Write和GPIO_I2C_Read这样的函数这就是软件模拟的实现。2.3 核心外围电路驱动、传感与供电电机驱动TB6612 vs L298N早期很多教程用L298N但它发热大、效率低。TB6612是更优的选择它集成了MOSFET H桥效率高、发热小而且驱动逻辑简单两个IN引脚控制方向一个PWM引脚控制速度。接线时务必注意TB6612的VM电机电源接电池7.4V锂电VCC逻辑电源接STM32的5V或3.3V。STM32的PWM信号和方向控制信号连接到TB6612的对应引脚。姿态传感器MPU6050平衡小车的“眼睛”。它集成了三轴加速度计和三轴陀螺仪。加速度计测量的是重力加速度在三个轴上的分量可以用来计算倾角但动态情况下小车运动时数据噪声大陀螺仪测量角速度积分可以得到角度但存在漂移温漂、零漂。因此必须通过算法如互补滤波、卡尔曼滤波将两者数据融合得到一个相对准确、稳定的姿态角。MPU6050还自带数字运动处理器DMP可以直接输出解算后的四元数但在这个项目中为了深入理解原理我们选择自己编写互补滤波算法。供电系统重中之重这是很多新手第一个栽跟头的地方。系统需要两种电压电机驱动电压直接给TB6612的VM供电常用7.4V 2S锂电池。电池的放电能力C数要足够否则在大负载下电压会被拉低导致单片机复位。控制系统电压给STM32、MPU6050等芯片供电通常是3.3V。绝对不能将电机电池直接接到STM32的VCC正确的做法是7.4V电池先经过一个降压模块如LM2596降到5V然后用这个5V给TB6612的VCC逻辑部分供电同时再经过STM32核心板上的AMS1117-3.3稳压芯片降到3.3V给MCU和传感器供电。或者使用单独的5V稳压模块给逻辑部分供电。务必确保电机电源和控制电源是共地的。3. 软件架构与核心算法实现硬件连接妥当后软件就是小车的“大脑”。我们的程序采用了一个清晰的前后台超级循环架构配合定时器中断来保证控制周期的精确性。3.1 程序主循环与定时中断的分工整个软件的运行骨架如下int main(void) { // 1. 初始化 System_Init(); // 系统时钟、延时函数初始化 Motor_Init(); // 电机PWM、编码器定时器初始化 MPU6050_Init(); // 初始化I2C和MPU6050 UART_Init(); // 初始化串口用于调试 PID_Init(); // 初始化所有PID参数 // 2. 等待传感器数据稳定上电后静止几秒 Delay_ms(3000); // 3. 进入主循环 while(1) { // 后台任务非实时性要求高的任务 Data_Process(); // 处理接收到的蓝牙指令如设置目标速度 Update_OLED(); // 刷新OLED显示角度、速度、PID参数等 Check_Button(); // 检测按键 // ... 其他任务 } } // 定时器中断服务函数例如配置为5ms触发一次 void TIMx_IRQHandler(void) { if(TIM_GetITStatus(TIMx, TIM_IT_Update) ! RESET) { TIM_ClearITPendingBit(TIMx, TIM_IT_Update); // 前台任务实时性要求高的核心控制任务 Get_MPU6050_Data(); // 读取原始加速度和陀螺仪数据 Angle_Calculate(); // 互补滤波计算当前姿态角俯仰角 Get_Motor_Speed(); // 通过编码器计算电机实际转速 Balance_PID_Calculate(); // 直立环PID计算 Speed_PID_Calculate(); // 速度环PID计算 Motor_Output(); // 将最终控制量转换为PWM输出 } }分工解析定时器中断前台承担了所有对时序要求苛刻的任务。5ms的中断周期意味着控制频率是200Hz这对于平衡控制来说是一个常用的、合理的频率。在这个中断里必须完成从传感器采样到电机输出的完整控制链。中断服务函数要尽可能高效避免复杂运算和长时间阻塞。主循环后台处理那些实时性要求不高的任务比如人机交互OLED显示、按键响应、无线通信数据处理、参数的非实时更新等。3.2 姿态解算互补滤波的实践与调参MPU6050读出的原始数据是加速度计的三轴加速度单位通常是g和陀螺仪的三轴角速度单位通常是°/s。我们需要从中解算出小车前后倾斜的角度俯仰角Pitch。基本原理加速度计角度当小车静止或匀速运动时加速度计测得的重力加速度分量可以用来计算角度。Angle_acc atan2(Ay, Az) * 57.29578f;将弧度转为度。但小车加速或减速时额外的运动加速度会混入导致角度计算严重失真。陀螺仪角度对角速度进行积分可以得到角度变化。Angle_gyro Gyro_x * dt;dt为采样周期如0.005s。积分会累积陀螺仪的零偏误差导致角度随时间“漂移”。互补滤波顾名思义取两者之长补两者之短。它的思想是在高频部分变化快信任陀螺仪因为它动态响应好在低频部分变化慢信任加速度计因为它长期稳定。公式简洁而有效Angle (0.98) * (Angle Gyro_x * dt) (0.02) * Angle_acc;这个公式里0.98和0.02是滤波系数它们的和必须为1。0.98意味着当前角度估计值的98%来自于上一时刻的角度加上陀螺仪的积分2%来自于加速度计计算的角度。这个系数决定了信任的权重。代码实现关键点float Complementary_Filter(float acc_angle, float gyro_rate) { static float angle 0.0f; const float dt 0.005f; // 5ms const float K 0.98f; // 互补滤波系数 // 陀螺仪积分得到角度预测 angle gyro_rate * dt; // 用加速度计角度进行校正 angle K * angle (1.0f - K) * acc_angle; return angle; }调参心得系数K这是最需要调的参数。K值越大系统对陀螺仪越信任响应快但抗静态漂移能力弱长时间会歪。K值越小对加速度计越信任静态角度准但动态响应差小车运动时角度反馈滞后容易振荡。对于平衡小车K值通常在0.95-0.99之间。我的经验是从0.98开始调。陀螺仪零偏上电后让小车在绝对水平面上静止几秒在这段时间内计算陀螺仪X轴输出的平均值这就是零偏Gyro_zero。在每次积分时需要减去这个零偏angle (gyro_rate - Gyro_zero) * dt;。加速度计滤波加速度计原始数据噪声较大在计算Angle_acc前可以先对Ay,Az进行一阶低通滤波平滑掉一些高频噪声。3.3 控制核心串级PID的详细拆解平衡小车普遍采用串级PID控制。外环是速度环内环是直立环。直立环是“保命环”速度环是“镇定环”。控制框图简化理解目标速度 (0) -- [速度环PID] -- 目标角度 | v 实际速度 -- [电机] -- [PWM] -- [直立环PID] -- 实际角度直立环内环输入是期望角度由速度环输出给出平衡时通常为0度和当前实际角度来自互补滤波。输出是一个控制量用于让小车快速跟踪角度变化维持直立。直立环通常只用比例P控制有时加一点微分D。绝对不能用积分I因为积分会累积误差当小车持续受到一个方向的力时积分项会越来越大导致小车突然加速跑飞根本无法平衡。// 直立环PD控制 float balance_pid_output Balance_Kp * (target_angle - current_angle) Balance_Kd * ( - gyro_rate);注意这里的微分项D直接使用了陀螺仪测得的角速度gyro_rate取反而不是计算角度的微分。这是因为角速度信号本身就代表了角度变化的趋势且噪声比差分计算的角度微分小得多。这是一个非常重要的实践技巧。速度环外环输入是目标速度通常我们想让小车静止所以目标速度为0和当前实际速度通过编码器计算得到。输出是直立环的期望角度。速度环需要用到PI控制。// 速度环PI控制 speed_error target_speed - current_speed; speed_integral speed_error; // 积分项累加 // 积分限幅防止积分饱和 if(speed_integral 1000) speed_integral 1000; if(speed_integral -1000) speed_integral -1000; float speed_pid_output Speed_Kp * speed_error Speed_Ki * speed_integral;速度环的输出speed_pid_output就作为直立环的target_angle。例如如果小车向前倾斜实际速度为正速度环的误差为负0 - 正速度经过PI运算输出一个负的target_angle。这个负的角度目标传给直立环直立环就会控制小车向后运动产生一个向后的加速度来减速同时这个向后的运动也会帮助小车回正角度。最终的电机输出// 将两个环的输出叠加并分配到左右电机 int motor_output balance_pid_output speed_pid_output; // 处理转向控制如果存在 int motor_left motor_output turn_output; int motor_right motor_output - turn_output; // 限幅并转换为PWM值 motor_left constrain(motor_left, -MAX_PWM, MAX_PWM); motor_right constrain(motor_right, -MAX_PWM, MAX_PWM); // 设置电机方向和PWM Set_Motor(MOTOR_LEFT, motor_left); Set_Motor(MOTOR_RIGHT, motor_right);4. PID参数整定从理论到实践的“玄学”艺术PID调参是让小车站起来并站稳的最后一步也是最考验耐心和经验的一步。网上有很多口诀比如“先比例后积分再微分”但对于串级PID我们有更具体的步骤。4.1 调试前的准备工作安全保障用东西把小车架起来让轮子悬空这是最重要的安全措施。防止参数不对导致小车突然猛冲出去损坏硬件或伤到人。调试接口一定要连接串口USB转TTL到电脑使用串口助手如XCOM、SSCOM或更好用的上位机如匿名科创上位机、VOFA来实时查看和图形化显示角度、速度、PID输出等关键数据。没有可视化调试调参就是盲人摸象。参数初始化将所有PID参数Kp Ki Kd设置为0。关闭速度环将其输出设为0先调直立环。4.2 分步调试法直立环 - 速度环 - 转向环第一步调试直立环只有P和D设定Balance_Kp和Balance_KdSpeed_Kp0, Speed_Ki0。用手轻轻推动小车观察轮子的反应。如果反应太慢增大Balance_Kp。Kp是小车“反抗”倾斜的力度。Kp太小小车软绵绵的一推就倒Kp太大小车会剧烈振荡。如果来回振荡增大Balance_Kd。微分项相当于“阻尼”能抑制振荡。但D太大也会导致系统反应迟钝。目标让小车在受到轻微扰动后能快速回到平衡位置并且不振荡。在轮子悬空状态下用手给一个倾斜角度松开后轮子应该快速反向转动使车模回正并且最多来回摆动一两次就停下。第二步加入速度环PI保持直立环参数不变开始调节Speed_Kp和Speed_Ki。此时可以将小车放在地上了做好随时提起的准备。观察现象小车在原地缓慢地前后移动这说明速度环在起作用但积分项Speed_Ki可能为0或很小速度误差被累积导致目标角度缓慢变化小车随之移动。这是正常现象说明速度环的P项在起作用。小车朝一个方向加速跑飞这是最危险的情况。根本原因通常是直立环的零点不对或者速度环的积分项I过大、没有限幅。速度环的积分会不断累积导致给直立环的目标角度越来越大小车为了达到这个不可能的角度而持续加速。解决方案确保小车在绝对平衡时互补滤波计算出的角度为0或一个很小的值。给速度环的积分项加上严格的限幅。例如限制其绝对值不超过50。这能防止积分饱和Windup是保证系统稳定的关键。适当减小Speed_Ki甚至可以先设为0只调Speed_Kp。调参技巧先调Speed_Kp。给小车一个轻微的速度命令比如通过蓝牙发送一个很小的目标速度看小车是否能平滑地启动和停止。Speed_Kp影响速度跟踪的响应速度。再调Speed_Ki。Speed_Ki用于消除静差。如果设置目标速度为0但小车总是缓慢地向一个方向漂移就需要加入一点Speed_Ki来抵消这个漂移。Ki一定要非常小通常比Kp小两个数量级。例如 Kp1.0 Ki0.001。慢慢增加Ki直到漂移被抑制即可切勿贪多。第三步调试转向环如果实现转向环相对独立通常也是一个PID输入是目标转向角速度来自遥控器和当前转向角速度通过左右轮速差估算输出是叠加在左右电机上的差动量。调参思路类似先P后D在保证直线行驶稳定的基础上加入转向控制。4.3 核心调试记录与参数参考以下是我在最终稳定版本中使用的一组参数注意这组参数高度依赖于具体的硬件电机、车轮直径、电池电压、重心位置和软件控制周期、编码器分辨率换算绝对不可以直接照抄但可以作为数量级上的参考控制环参数数值说明直立环Balance_Kp25.0比例项决定“扶正”力度。Balance_Kd0.8微分项抑制振荡。直接使用负的陀螺仪X轴角速度。速度环Speed_Kp1.2比例项决定速度跟踪响应。Speed_Ki0.005积分项消除静差。必须加限幅如±50。Speed_Kd0.0速度环一般不需要微分。控制周期dt0.005s (5ms)定时器中断周期。编码器换算Speed_ratio0.05将编码器脉冲/周期换算为实际速度m/s的系数。调试中遇到的典型问题与解决问题小车一上电轮子就高速向一个方向旋转完全不受控。排查首先检查电机输出极性是否正确。用手扶正小车观察角度读数和电机输出。如果角度为正前倾电机应该向前转使小车向前运动以追上前倾的重心。如果方向反了将Balance_Kp改为负数或者交换电机驱动线的顺序。检查MPU6050的安装方向。确保传感器模块的X轴通常印在板子上与小车的前后方向一致。如果装反了角度和角速度的符号都会错。问题小车能站住但身体一直在高频“哆嗦”。排查这是典型的微分不足或噪声过大。首先尝试增大Balance_Kd。如果效果不明显检查陀螺仪数据是否噪声太大。可以在互补滤波前对陀螺仪数据进行简单的滑动平均滤波。另外检查电源是否稳定电机PWM频率是否合适通常10kHz左右频率太低会导致电机运转不平稳产生振动。问题小车站得很稳但轻轻一碰就倒恢复很慢。排查直立环的Balance_Kp太小或者Balance_Kd太大过阻尼。适当增大Balance_Kp或略微减小Balance_Kd。5. 项目进阶与扩展思考一个能站稳的小车只是起点。基于这个稳定的平台我们可以做很多有趣的扩展这也是课程设计拿高分的关键。5.1 无线遥控与上位机调试通过HC-05或HC-06蓝牙模块让小车与手机APP或电脑连接。可以实现遥控运动发送前进、后退、转向指令。在代码中这些指令会转化为速度环的目标值。参数无线调试这是最实用的功能无需重新烧录程序通过串口发送特定格式的指令如kp25.0\r\n就能实时修改PID参数并立即看到效果极大提升调试效率。数据回传将小车的角度、速度、电机输出等数据打包发送到上位机软件进行波形显示便于分析系统状态。5.2 功能扩展避障、循迹与物联网超声波避障加装HC-SR04超声波模块当检测到前方障碍物时修改速度环目标值为负后退或结合转向环实现绕障。红外循迹使用TCRT5000等红外传感器阵列实现黑线循迹。这需要增加一个“位置环”PID输入是传感器检测到的中心偏差输出是转向环的目标值。物联网应用接入ESP8266 WiFi模块将小车状态上传到云平台如OneNET、阿里云实现远程监控和控制打造一个简单的“物联网平衡车”。5.3 从标准库到HAL库与CubeMX本项目基于标准库开发对于理解底层寄存器操作有帮助。但现在ST主推HAL库和CubeMX图形化配置工具。你可以尝试用CubeMX重新生成项目框架用CubeMX配置时钟、定时器PWM、编码器模式、I2C、UART等。使用HAL库函数重写MPU6050驱动、PID计算等应用层代码。对比两种开发方式的效率差异体会HAL库在跨芯片移植和快速原型开发上的优势。5.4 代码优化与架构反思回顾项目代码仍有优化空间数据滤波可以尝试更先进的滤波算法如卡尔曼滤波理论上能得到更优的姿态估计。控制算法PID虽然经典但也有其局限性。可以研究模糊PID、自适应PID或者尝试用更现代的控制理论如LQR来设计控制器比较性能差异。模块化与解耦将传感器驱动、控制算法、电机驱动等模块进一步解耦定义清晰的接口提高代码的可读性和可移植性。例如可以设计一个Controller抽象层下面有PIDController、FuzzyController等具体实现方便切换算法。这个“满分项目”带给我的远不止一个会站立的小车。它是一次完整的嵌入式系统开发实战涵盖了硬件选型、电路设计、传感器应用、实时控制算法、软件调试等全流程。最大的收获不是那组PID参数而是解决问题的思维方式和调试过程中积累的工程直觉。当你看到亲手焊接组装的小车在自己编写的代码控制下颤巍巍地站起来并最终稳稳立住的那一刻所有的调试、改bug、查资料的辛苦都值了。希望这份详细的复盘能帮你少走些弯路更快地体验到这种乐趣。本文还有配套的精品资源点击获取