news 2026/10/17 3:56:27

平衡小车car资源包:STM32+MPU6050三环PID完整工程解析

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
平衡小车car资源包:STM32+MPU6050三环PID完整工程解析

简介:平衡小车自主直立与运动控制学习资料包,面向自动控制初学者、嵌入式开发人员及科创竞赛爱好者,可用于研究双轮自平衡系统的姿态解算、多传感器数据融合与闭环控制。压缩包内共1420个文件,其中C源码439个、头文件379个,文本说明121个,另有工程配置、链接脚本、汇编启动代码、编译中间文件及少量文档图片,总体积36.17MB,目录结构清晰,便于按功能模块对照阅读。目前已有1746人学习下载。资料覆盖从底层传感器驱动到上层控制算法的完整链路,涉及陀螺仪与加速度计数据读取、滤波处理、PID参数整定、电机驱动等关键环节;同时提供完整工程与编译配置,可帮助读者复现项目,理解状态观测器在平衡控制中的实际用法,并为移植到其他主控平台提供改造基础。对于准备课程设计或智能车竞赛的读者,可从这份资源快速搭建实验环境,结合源码梳理硬件连接与控制逻辑,缩短从理论到真机调试的距离。

1. 平衡小车car资源包:一套能立住、能跑通、能改的完整工程

把平衡小车从手里放下来的那一刻,是最容易怀疑人生的:它要么朝一个方向冲出去撞墙,要么原地高频抖动,要么干脆躺平。这个项目真正的难点不在接线,而在让三个PID环同时工作还不互相打架。这份资源包是一套完整的平衡小车car工程,STM32主控、MPU6050姿态传感器、带编码器的直流减速电机、蓝牙遥控和OLED显示全串在一条链路里,从硬件接线到控制算法都给你铺好了。它适合正在做课设、被直立环调参折磨的学生,也适合想用最短路径摸一遍惯导与闭环控制完整链路的一线开发。按工程里的接线和参数配置,照着烧录、照着调,车就能自己立住。下面从硬件选型讲到踩坑记录,最后给几条改造方向。

2. 硬件与架构:从传感器选型到三环控制回路的信号流

2.1 硬件分层:传感器层、执行层与控制层怎么分工

拆这套资源的第一步是先把硬件链路画清楚。平衡车和普通小车最大的区别是它没有静态平衡点,车身一倾斜就必须靠电机反向补偿,所以整个硬件其实就是围绕“测倾角—算补偿—输出力矩”这条闭环来搭的。常见做法是分三层:传感器层负责感知姿态,控制层负责运算,执行层负责输出力矩。

传感器层用MPU6050六轴惯性传感器,I2C接口挂到主控总线上,输出三轴加速度和三轴角速度。加速度计用来算静态倾角,陀螺仪用来获得瞬时角速度,两者互补,正好为后面的姿态解算提供原始数据。需要注意MPU6050的供电是3.3V,和主控电平一致,接线时SCL、SDA别接反,否则读回的全是乱码。

控制层是主流的STM32F103系列,Cortex-M3内核,72MHz主频,定时器资源多,生成多路PWM很方便,这对双电机差速控制来说属于刚需资源。资源包里主控的PA口和PB口管脚分配都写在头文件里,换芯片时主要改引脚映射和定时器通道。执行层是带霍尔编码器的直流减速电机,减速比常见1:30,配合TB6612双路电机驱动,输出PWM同时控制方向和占空比。

电源部分在平衡车里最容易被忽略。2S锂电池7.4V供电,经过降压模块分别给主控和传感器供电。编码器虽然只消耗小电流,但供电纹波大会导致测速跳变,一般会在编码器电源脚并一个10uF陶瓷电容,低成本但很有效。

2.2 软件架构:中断时基与控制周期的分配

硬件确定后,代码层面的核心问题是节奏:控制算法多久跑一次,传感器多久读一次,PWM多久更新一次。这套工程的软件结构是典型的前后台架构,一个定时器中断提供时基,主循环里跑控制任务。

控制周期直接决定平衡效果。常见做法是直立环跑1ms,速度环和转向环跑5ms,也就是主循环里用一个计数器分频,累加到5次才执行一次速度环。这里的依据是:直立环需要高刷新率来抑制倾角变化,而速度环反馈的是电机转速,本身有编码器采样延迟,没必要跑那么快,跑太快反而容易引入噪声。

定时器中断里只做一件事:置一个标志位并累加时基计数。真正的姿态读取、PID计算、PWM输出全部放在主循环里。这样设计的好处是控制任务的耗时不会阻塞中断,中断只负责节奏,不会因为传感器I2C读取出问题而丢时基。如果某个I2C读操作卡住,最多是主循环卡住,定时器中断仍能维持系统心跳。

2.3 信号流向:从角速度到PWM的完整链路

把整个信号流从传感器一路捋到电机,就能看到完整闭环了。MPU6050输出原始加速度和角速度,经过互补滤波得到车身倾角;倾角进入直立PD环算出补偿力矩;编码器测出的轮速进入速度PI环,输出一个角度修正量叠加到目标倾角上;转向环根据你的遥控目标或角度偏差给出差速量。最终左右电机的PWM就是这三路输出的组合。

这条链路上最容易翻车的是方向约定。加速度计的正方向、陀螺仪角速度的正方向、电机正转时编码器的计数方向、PWM输出极性,这四者必须一致。资源包的头文件里有角度方向和电机方向的宏定义,调参前先确认这些定义和你的实际接线一致,否则后面全是白调。太多人花一晚上调PID,结果发现是陀螺仪符号写反了,这种玄学问题往往都在方向约定上。

3. 核心代码拆解:姿态解算与三环PID的实现细节

3.1 姿态解算:互补滤波融合加速度计与陀螺仪

平衡车的第一公里是拿到一个可信的车身倾角。陀螺仪短时间积分准确,但长时间会漂移;加速度计长时间稳定,但瞬时值充满振动噪声。这套资源用的是互补滤波,把两者按比例融合。核心代码很简洁:

#define DT 0.001f // 控制周期 1ms #define ACC_WEIGHT 0.98f // 陀螺仪权重 float angle_balance = 0.0f; // 融合后的车身倾角 float gyro_y = 0.0f; // 俯仰角速度 void Get_Angle(void) { int16_t acc_x, acc_y, acc_z; float acc_angle; MPU6050_Read_Raw(&acc_x, &acc_y, &acc_z); // I2C读取原始数据 acc_angle = atan2f((float)acc_y, (float)acc_z) * 57.2958f; gyro_y = (float)gyro_raw / 16.4f; // ±2000°/s量程灵敏度 angle_balance = ACC_WEIGHT * (angle_balance + gyro_y * DT) + (1.0f - ACC_WEIGHT) * acc_angle; }

这段代码的逻辑是:先用加速度计的Y轴和Z轴分量通过atan2算出倾角,atan2的好处是能覆盖±180度且不需要额外判断象限。陀螺仪原始值除以16.4是因为量程±2000°/s时,16.4是每LBS对应的角速度值。融合公式里,(angle_balance + gyro_y * DT)是陀螺仪积分的结果,短时间可信;acc_angle是加速度计的瞬时观测,用来长期校正积分漂移。0.98和0.02的比例决定了信任程度,如果车身振动大,就把0.98调小一点,让滤波更平滑,换取响应变慢。

3.2 三环PID:直立环、速度环、转向环怎么合成

姿态有了,接下来是控制。平衡车和四轴不一样,它没有独立的姿态环,而是把直立、速度、转向三个环的输出叠加成最终PWM。直立环是核心,本质是一个PD控制器,比例项把倾角拉回零,微分项阻尼掉振荡:

float Kp_balance = 150.0f; // 直立环比例 float Kd_balance = 1.8f; // 直立环微分 int16_t Balance_PID(float angle, float gyro) { return (int16_t)(Kp_balance * angle + Kd_balance * gyro); }

注意这里的微分项直接用了陀螺仪输出,而不是对角度做差分。两者效果近似,但陀螺仪本身就是角速度,信噪比远高于角度差分,而且不存在差分运算放大高频噪声的问题。Kp调到多少合适,取决于车身重量分布和电机力矩,资源包给的150只是一个起点,换电池、换底盘后必须重调。

速度环用来防止小车往一个方向慢慢溜走。它把编码器的轮速积分成位移,输出一个角度修正值,加到直立环的目标倾角上:

float speed_target = 0.0f; // 目标速度,遥控时改成非零值 float speed_integral = 0.0f; float Kp_speed = 20.0f; float Ki_speed = 0.5f; int16_t Speed_PID(int16_t speed_now) { int16_t error = (int16_t)(speed_target - speed_now); speed_integral += error; speed_integral = clamp(speed_integral, -500, 500); // 限幅防积分饱和 return (int16_t)(Kp_speed * error + Ki_speed * speed_integral); }

速度环的输出不是直接怼到电机上,而是作为直立环的目标角。这句是整个平衡车代码的精髓:车想要往前走,不是直接给电机正转,而是让车身往前倾斜一点点,靠直立环去追这个倾斜角,车自然就往前走。速度环的目标speed_target在静止平衡时是0,做遥控功能时把串口收到的速度值填进来就能控制前后移动。

转向环则简单得多,一个P控制器,输出左右差速:

float Kp_turn = 3.0f; int16_t Turn_PID(int16_t turn_target) { return (int16_t)(Kp_turn * turn_target); }

最终合成逻辑是:左电机PWM等于直立环输出减去速度环输出加上转向环输出,右电机PWM等于直立环输出减去速度环输出再减去转向环输出。减号的意义是差速转向,转向环为正时左快右慢,车身右转。三个环的方向如果有一个反了,整套控制就全乱,这也是后面避坑章节里最常见的翻车点。

3.3 PWM输出与电机换向

PID算出的输出是带符号的整型,正负代表方向,数值代表占空比。驱动芯片TB6612需要方向信号和PWM信号配合输出。资源包里封装了一个电机设置函数:

#define PWM_MAX 7200 // 定时器ARR值,对应约16kHz PWM频率 void Motor_Set(int16_t pwm_left, int16_t pwm_right) { uint8_t left_dir = pwm_left > 0 ? 1 : 0; uint8_t right_dir = pwm_right > 0 ? 1 : 0; HAL_GPIO_WritePin(AIN1_GPIO_Port, AIN1_Pin, left_dir); HAL_GPIO_WritePin(AIN2_GPIO_Port, AIN2_Pin, !left_dir); HAL_GPIO_WritePin(BIN1_GPIO_Port, BIN1_Pin, right_dir); HAL_GPIO_WritePin(BIN2_GPIO_Port, BIN2_Pin, !right_dir); __HAL_TIM_SET_COMPARE(&htim1, TIM_CHANNEL_1, abs(pwm_left)); __HAL_TIM_SET_COMPARE(&htim1, TIM_CHANNEL_2, abs(pwm_right)); }

AIN1与AIN2的逻辑组合决定电机转向,PWM比较值决定速度。这里有一个隐藏细节:PID输出不允许出现负的PWM比较值,所以必须先取绝对值再写进定时器,否则会写成一个很大的数导致电机全速猛转。PWM_MAX取7200是因为定时器ARR设成7200,配合72MHz主频得到约16kHz开关频率,这已经超出人耳可听范围。如果频率设成几百赫兹,电机就会发出刺耳的尖叫声,那基本是PWM频率配置错了。

PWM频率、定时器分频、ARR值这三个参数都集中在定时器初始化里,改的时候注意三处联动:ARR值、分频系数和Motor_Set里的PWM_MAX宏。三处不一致,输出就会莫名其妙地成比例缩放。

4. 运行与调参:从工程烧录到小车直立的完整路径

4.1 工程目录结构:这套代码的文件组织方式

拿到资源解压后,是一个完整的Keil工程目录。典型的平衡车工程会把驱动外设的代码和业务逻辑分开,目录一般长这样:

Car/ ├── USER/ # main.c + 中断入口文件 ├── HARDWARE/ # mpu6050.c / motor.c / oled.c / bluetooth.c ├── SYSTEM/ # 延时与串口驱动 ├── CORE/ # 启动文件与内核寄存器定义 ├── bsp/ # 定时器与GPIO初始化 └── OBJ/ # 编译输出,烧录hex在这里

main.c里只做三件事:初始化外设、开定时器中断、进主循环调用控制任务。所有硬件驱动都放在HARDWARE目录下,每个模块一个.c和一个.h,接口尽量简单,比如MPU6050模块只暴露Get_Angle和Get_Gyro两个函数,电机模块只暴露Motor_Set。这样改代码时不需要在main.c里翻来翻去。

4.2 编译烧录:从Keil到ST-Link的完整操作

烧录用的是ST-Link的SWD四线接口:SWDIO、SWCLK、GND、3.3V。接线完成后在Keil里选好调试器,然后在Flash Download选项卡里勾选Reset and Run,这样烧录完会自动复位运行,不用每次手动按复位键。

串口调参是这套工程里最常用到的能力。资源包默认配置了UART1,波特率115200,在main.c主循环里周期性发送角度、PWM输出和三个环的当前值。用任意串口助手打开,能看到类似这样的输出:

angle:0.53 gyro:-12.4 balance_out:65 speed_out:-3 turn_out:0

先看角度和陀螺仪数值。用手把车身掰正,角度应该在0附近;迅速前倾再回正,陀螺仪应该有正负两个方向的突变。这两个数据正常,说明传感器链路是通的,再往下调PID才有意义。

4.3 调参顺序:先直立环,再速度环,最后转向环

调参顺序在整个平衡车项目里决定了效率。正确顺序是把速度环和转向环先禁用,单独调直立环。具体操作是先把速度环输出设为0,只让直立环工作,然后从上电开始逐步加大Kp。

Kp很小时,车身会往一边慢慢倒下,电机几乎没有反应;Kp增大到某个临界值,车身开始能短暂回正,但会来回摆动;继续增大,高频振荡越来越剧烈,发出嗡嗡声。当Kp大到车身持续高频抖动时,加入Kd,抖动会明显被压制。典型参数起点可以看这张表:

参数初始值调整方向
Kp_balance120-180太小会倒,太大会抖
Kd_balance1.0-3.0太大车身发僵
Kp_speed10-30太大起步猛冲
Ki_speed0.2-1.0太大低速震荡
Kp_turn2-5太大转向发飘

直立环稳定后,再逐步放开速度环。判断速度环方向是否正确的方法是:用手推一下车身,如果小车往推的方向加速冲过去,说明速度环方向反了,需要把编码器两根线对调或把速度环输出取反。速度环正确时,车被推一下会后退一点然后回到原位,这个“回位”动作就是速度环在起作用。

调参时看波形比看数值高效得多。用串口助手的虚拟示波器功能,同时监看角度和直立环输出,观察阻尼是否合适。调参过程中不要一次改多个参数,每改一个就重新抓一次波形,养成记录参数的习惯,改坏了才知道回到哪一版。

5. 避坑手册:平衡车调试中最常见的五个问题

5.1 电机不动但代码在跑

现象:串口有数据输出,角度也在正常变化,但两个电机完全没反应。 原因:九成是TB6612的STBY引脚没有拉高。STBY是待机控制脚,低电平时驱动芯片整个休眠,PWM输入再正常也不会输出。 解决:确认主控GPIO对STBY的初始化,把它配置为推挽输出并拉高。另外检查PWM通道是否正确映射到电机驱动芯片的PWMA和PWMB引脚,定时器通道号写错也会导致没有输出。

5.2 一上电就往一个方向猛冲

现象:刚上电,车身还没放稳,电机直接以最大占空比猛转,车瞬间窜出去。 原因:这是平衡车调参最经典的坑,几乎都是方向约定不一致。陀螺仪方向或者加速度计方向反了,控制系统把“向前倒”识别成“向后倒”,于是输出反而加速了倾倒。 解决:先把角度方向验证一遍——用手让车身向前倾斜,看串口打印的角度是正还是负,如果方向不对,在MPU6050驱动里把对应轴取反。同理验证电机方向:把电机PWM设为一个固定正值,看车轮转向是否与代码预期一致。四个方向全部对齐后再接线调参。

5.3 车身高频抖动

现象:车能立住,但站在旁边能看到车身以很高的频率微微颤抖,同时电机温度上升很快。 原因:直立环的Kp太大,或者机械结构重心偏高,系统进入临界振荡状态。重心越高,稳定裕度越小,同样的Kp在小车上正常,换个高重心底盘就会抖。 解决:先把Kp降到能稳住的最低值,再加入适量Kd。如果还抖,检查控制周期,确保直立环确实是1ms跑一次。如果主循环里塞了OLED刷新或蓝牙解析导致周期变长,PID参数会整体失灵。

5.4 电池电压变化后参数失效

现象:满电时车很稳,玩十几分钟后开始频繁点头,甚至彻底倒下去。 原因:锂电池电压从8.4V降到7V,电机能输出的峰值力矩明显下降,原来的PID参数是建立在满电压力矩上的。 解决:一个方案是把电源做稳压,让电机驱动供电保持在稳定电压。更实用的方案是在代码里用ADC实时采样电池电压,把它作为PID输出的补偿系数。电压低时适当降低速度环积分强度,避免积分饱和把直立环输出顶到限幅。

5.5 蓝牙连接不上

现象:手机搜索不到蓝牙模块,或者连上后丢包严重。 原因:蓝牙模块是3.3V供电,模块的RXD引脚不能直接接5V串口。如果主控和模块电平不匹配,轻则搜索不到,重则烧模块。数据丢包往往是波特率不一致,主控串口配了115200,蓝牙默认却是9600。 解决:先短接蓝牙模块的EN引脚进入AT模式,用串口助手把波特率设置成和主控一致;再检查RXD和TXD是否交叉连接;最后确认主控与蓝牙共地。这三点全对齐,蓝牙基本就不会出问题。

6. 二次开发:从直立到遥控,再到视觉跟随的改造路径

6.1 先改遥控:把速度环目标值变成串口数据

最值得做的第一个改造是给小车加遥控。实现方法很简单,速度环目标值speed_target原本是常量0,把它改成全局变量,在主循环里解析串口数据并赋值即可。收到前进指令就赋正值,收到后退就赋负值,转向环的目标值同理。改完这一处,车就能从“原地直立”变成“可以开走”。验证方法很简单:先开环测试油门方向,再闭环验证速度环方向,方向无误后遥控功能就通了。

6.2 再做视觉跟随:用目标角度代替人工指令

更进一步的做法是加视觉跟随。常见思路是接一个摄像头识别前方目标,把目标在画面中的水平偏差映射成转向环目标值,把距离偏差映射成速度环目标值。代码层面完全复用三环结构,只需要在控制循环前面加一个视觉回调函数,输出两个目标值。调试时的技巧是先让车静止,只验证转向跟随,再做运动跟随,不要一上来就全链路调参。

这套工程的价值在于三环结构清晰,每个环的入口就是一个目标值,改目标值就是加新功能。我现在每拿到一个平衡车项目,都会强制先走一遍方向验证——电机方向、编码器方向、陀螺仪方向、加速度计方向,四者全部确认后再动手调PID。这个习惯是从一次通宵调参后彻底倒车学来的,从那以后所有闭环项目我都先查方向再调参。希望帮到你。

本文还有配套的精品资源,点击获取

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/10/17 3:56:09

多智能体协作框架实战:角色分工、通信机制与状态管理调优指南

1. 从“单兵作战”到“公司化运营”:多智能体协作到底在解决什么问题如果你最近在折腾大模型应用,大概率经历过这样一个阶段:一开始觉得单个对话机器人挺聪明,能写代码、能查资料、能陪你聊天。但一旦让它干点稍微复杂的活儿&…

作者头像 李华
网站建设 2026/10/17 3:56:06

人机协同中的信任机制:智能体从建交到校准再到修复的核心变量

我接触人-智能体协同这个方向有几年了。最开始和大家一样,以为协作的核心是模型能力:推理够不够强、准确率够不够高、响应够不够快。可真到一线跑起来才发现,最常听到的并不是"这个智能体准不准",而是"我到底该不该…

作者头像 李华
网站建设 2026/10/17 3:56:03

GPU代码生成与动态形状:编译栈最后一公里的工程实践

1. 从图到机器码:GPU 代码生成到底在做什么1.1 为什么编译栈的最后一公里最难走做深度学习编译栈的人都有一个共识:前端图优化做得再漂亮,如果后端代码生成拉胯,整个栈的性能就是纸上谈兵。TensorPlay Stax 这个编译栈我在几个内部…

作者头像 李华
网站建设 2026/10/17 3:55:57

WPF虚拟化WrapPanel:解决大数据量卡片布局卡顿

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

作者头像 李华
网站建设 2026/10/17 3:54:56

OpenClaw技能配置实战:从养虾自动化到智能体编排方法论

1. 为什么“人人养虾”的难点在技能配置最近群里聊得最热的题目就是“OpenClaw人人养虾”。乍一听以为是个养殖创业项目,实际上这是某个开源智能体平台里流行起来的玩法:每个人都能在本地配置一个自己的智能体,让它负责“养虾”这件事。很多人…

作者头像 李华