news 2026/9/9 2:11:48

STM32两轮自平衡小车实战:从硬件选型到PID调参全攻略

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
STM32两轮自平衡小车实战:从硬件选型到PID调参全攻略

简介:一份基于STM32的两轮自平衡小车制作教程资料包,面向嵌入式初学者与DIY爱好者,系统讲解从硬件搭建到软件控制的完整实现路径。资料围绕传感器数据采集、PID平衡算法、电机驱动等核心模块展开,原理图、源码、使用说明与开发笔记一应俱全,既适合入门者按部就班完成实物制作,也能帮助开发者深入理解自平衡系统的调参思路。包内共1430个文件,以C语言源码(c/h)、编译中间文件(d/o/crf)为主,辅以PDF文档、工程配置(uvproj/uvopt)、烧录文件(hex/axf)及少量txt说明等,整体大小53.19MB,目录结构清晰,便于快速定位所需内容。目前已有4106人学习下载。通过研读这份资料,可以掌握STM32外设配置、MPU6050姿态读取、PID控制器实现与PWM电机控制等关键技能,同时也能借鉴作者在开发笔记中记录的排错与优化经验,是一份理论与实践结合紧密的优质参考资源。

1. 为什么我依然推荐你把两轮自平衡小车作为STM32练手项目

接触STM32的这几年里,我前前后后做过不少小东西——四轴飞控、智能车、机械臂、各种传感器采集器。但如果让我只推荐一个项目来“检验自己是否真的入门了”,我会毫不犹豫地说:两轮自平衡小车。

理由很简单:这个项目要用到的知识面,几乎覆盖了嵌入式开发的所有基本功。

  • 你要读MPU6050的原始数据——这是I2C通信,涉及寄存器配置、时序理解;
  • 你要做姿态解算——这是数据处理,涉及互补滤波、四元数或者方向余弦矩阵这些概念;
  • 你要写PID控制器——这是控制理论的核心,也是整个项目的灵魂;
  • 你要输出PWM驱动电机——这是定时器和GPIO的综合运用;
  • 你要配合上位机调试——这是通信协议的编写和调试工具的使用;
  • 你还得处理电池电压跌落、机械结构摩擦、传感器零漂这些“模拟世界的不确定性”。

说白了,平衡小车不是“焊个板子跑跑流水灯”那种入门玩具,而是一个真正让你把STM32当“控制器”而不是“单片机”来用的项目。它逼着你去理解整个系统的延迟、时序、噪声,而不是写完代码点个灯就完事。

从我个人的经验来看,做平衡小车最大的收获不是小车站起来那一刻的成就感,而是“调参”过程中建立起来的直觉——对PID参数、对传感器噪声、对机械谐振、对控制周期的直觉。这种东西教科书上不会写,只有亲手把小车轮子转起来、看着它在临界点来回晃,才能真正建立。

当然,这也就意味着,这个项目的坑相当多。为了帮你少走弯路,这篇文章我会从硬件选型、核心原理、代码组织、调参方法、细节避坑五个方向,把我自己的实操经验和踩过的坑全部写出来。无论你是刚学完STM32标准库、还是正在做毕业设计需要参考,这篇文章的内容都够你一步步搭出一台能自己站起来的小车。

2. 硬件选型:钱花在哪、省在哪,踩坑最少的一套配置

很多新手做平衡小车,第一步栽在硬件上。不是买贵了,就是买错了,更常见的是买回来的模块之间“兼容性”稀烂,一上电就各种奇怪现象。这里直接给你一套我实测下来最稳妥、性价比最高的配置。

2.1 主控:STM32F103C8T6就够了,别盲目上F407

这是最容易出现“配置焦虑”的地方。不少同学上来就想用F407、F429,觉得主频越高、资源越多越稳。但平衡小车这个项目,计算量真的不大:姿态解算加串级PID,整个控制周期1ms,F103跑72MHz绰绰有余。我实际测过,在标准库环境下,整个控制回路一次运算不到50微秒,CPU占用率还不到5%。

选F103C8T6还有一个现实原因:资料极其丰富。网上随便搜都能找到参考代码,遇到问题也更容易找到人问。用F407的话,很多代码不能直接用,你得自己移植,反而给项目增加了不必要的难度。

建议直接买一块带最小系统的板子,引脚排针引出来那种就行,大概十几二十块钱。千万别买那种“集成度超高”的开发板——板上还自带LED矩阵、按键、USB转串口用着没事,但排针密密麻麻很难接线,调试时崩到你怀疑人生。

2.2 惯性传感器:MPU6050依旧是新手最优解

MPU6050是一颗六轴惯性传感器(三轴陀螺仪加三轴加速度计),好买、便宜、资料多。这个没得说,首选。

需要注意的是,市面上MPU6050模块分为“带稳压”和“不带稳压”两种。我强烈建议买带稳压的模块,因为小车供电是锂电池,电压在3.7V到4.2V之间波动,不带稳压的模块可能直接烧掉或者输出异常数据。第一次做的时候我贪便宜买了不带稳压的,结果满电时能读数据,电放掉一点数据就开始漂得离谱,排查了半天最后发现就是供电的问题。

如果你预算稍微宽裕一点,可以考虑ICM20602、MPU6500这类更新一代的传感器,性能更好、温度漂移更小。但新手就用MPU6050,够了。

2.3 电机:N20微型减速电机,便宜够用,别上来就空心杯

电机是另一个容易走偏的地方。有人追求性能直接上空心杯电机——转速高、响应快,但问题也很明显:贵、需要专门的驱动电路、电流大、电池掉电快,而且对新手来说,空心杯的“灵敏”反而是个麻烦,参数一不合适小车就开始高频抖震,根本压不下来。

我更推荐N20微型直流减速电机,带编码器那种。减速比选1:30到1:50之间的型号,单价也就十几块钱。自带霍尔编码器可以用来测速,方便后续做速度闭环。虽然响应速度没有空心杯那么快,但做两轮平衡车完全够用,而且容错性更好,调参空间大。

电机驱动我首选TB6612FNG模块,比L298N轻、效率高、逻辑简单,而且支持低电压工作。接法也很直接:VM接电池正极,VCC接5V,PWMA/PWMB接控制器的PWM引脚,AIN1/AIN2接GPIO控制方向。千万别直接用L298N给小型N20电机供电——那个压降太大了,2.5V工作电压的电机被它拖到1.8V,扭矩直接废掉。

2.4 电源设计:BOM里最容易“炸”的部分

很多人的小车第一次上电就出问题,十有八九在电源。这个我得单独拿出来说。

  • 电池统一用两节18650锂电池串联,也就是7.4V。不要用单节3.7V,因为N20电机在3.7V下扭矩很勉强,根本没力气把车撑起来;
  • 主控和传感器供电通过AMS1117-3.3V稳压模块从电池取电,注意这个模块的输入耐压上限是12V,7.4V没问题;
  • TB6612的VM直接接电池正极,逻辑电平VCC接5V或3.3V都可以,但注意TB6612的逻辑高电平阈值,用3.3V主控时建议VCC接5V更稳妥;
  • 滤波电容一定不能省——在电池正负极两端并联一个470uF电解电容和一个100nF陶瓷电容,前者对付电机启停带来的大电流波动,后者滤高频噪声。没有这两个电容,MPU6050的数据会抖到你怀疑人生。

之所以强调电源顺序,是因为电机产生的EMI(电磁干扰)和供电跌落都会直接“传染”给传感器,导致姿态角数据异常,最后整个控制逻辑全部崩掉。做硬件的都知道,很多看似软件问题,根源其实是地线没处理好。

3. 核心原理:姿态解算和串级PID到底在做什么

开始写代码之前,建议把原理想清楚。否则你会陷入“抄了代码但不知道为什么要这样做,改一个参数只能靠玄学”的状态。

3.1 姿态解算:加速度计和陀螺仪怎么互相“救场”

要控制小车不倒,第一件事是知道小车当前倾斜了多少度。这个角度怎么来?MPU6050给了你两套原始数据。

加速度计测量的是三个轴的加速度。当小车静止时,加速度计读到的只有重力加速度。而重力的方向始终朝向地面,所以根据重力在三轴上的分量,你就可以算出小车的倾角。方法很简单,反正切就行。它的特性是:长期稳定,不会漂,但一有震动或运动,读到的数据就混杂了大量噪声和运动加速度干扰,直接算出来的角度会疯狂抖动。

陀螺仪测量的是角速度。对时间积分就能得到角度变化量。它的特性恰好相反:短期精准,响应快,不受加速度干扰影响,但积分会累积零漂误差——几秒钟看不出,一两分钟后角度就开始慢慢“跑偏”。

所以就有了经典解法:互补滤波。先用加速度计算出一个有噪声但长时可靠的角度值,再用陀螺仪的角速度做一个短期可信的角度预测,然后把两者按比例融合。逻辑上就是:你信陀螺仪多一点(因为它动态响应快),但每隔一段时间用加速度计的结果把漂移“拉回来”。

互补滤波的代码很短,核心就一行:

// 互补滤波融合,alpha一般取0.95~0.98 angle = 0.95 * (angle + gyro_y * dt) + 0.05 * accel_angle;

你别小看这一行。它之所以能work,是因为0.95这系数意味着你在95%的程度上相信陀螺仪积分的短期结果,用剩下5%的权重去纠正长期漂移。选择合适的alpha就是找一个“抗抖动”和“抗漂移”的平衡点。

当然,更“高级”的做法是用卡尔曼滤波或者Mahony姿态解算算法。但就平衡小车这个场景来说,俯仰轴单轴互补滤波完全够用,而且代码简单、运算量小。卡尔曼滤波的参数调整难度比较大,新手容易调不收敛,那就很难受了。

3.2 为什么一定得用串级PID,而不是一个PID搞定

很多新手第一次看到“内外环”就懵了。这里我尝试讲明白一点。

单级PID控制的目标很直接:让角度保持在0度。你要是只用一个角度环,把倾角作为误差输入,输出PWM给电机,马上会发现一个问题——小车确实有“站起来的趋势”,但你给大了它冲过头,给小了它撑不住,总在平衡点附近大幅度来回摆动。因为角度环本身没有阻尼。

解决办法就是引入“角速度反馈”。你可以把串级PID想象成一个熟练的杂技演员找平衡的过程:

  • 外环(角度环)的输入是“目标角度 - 当前角度”,输出是希望小车转动的角速度值;
  • 内环(角速度环)的输入是“期望角速度 - 当前角速度”,输出才是真正的PWM值,直接控制电机力矩。

内环的作用是关键:当小车角度尚未来得及大范围变化,但角速度已经发生变化时,内环能立刻响应,输出一个抑制这个变化趋势的力矩——这就像阻尼一样,把系统的“冲量”吸收掉。加了内环之后,小车会明显变得“稳重”很多,不会再来回大幅摆动了。

控制结构用公式表达会更直观:

// 外环角度环:误差是角度差,输出是期望角速度 pid_pitch_outer.error = target_angle - measure_angle; pid_pitch_outer.output = pid_calculate(&pid_pitch_outer); // 输出作为内环目标 // 内环角速度环:误差是期望角速度与实际角速度的差 pid_pitch_inner.error = pid_pitch_outer.output - measure_gyro; pid_pitch_inner.output = pid_calculate(&pid_pitch_inner); // 输出作为PWM

顺序上,先调内环,再调外环。内环调好了,小车会表现出“你用手碰一下它,它会有一股反向力矩抵抗,但整体还是会倒”的状态;外环调好了,小车才能真正站稳。

3.3 控制周期到底选多少:1ms还是5ms?

控制周期是整个系统的重要参数。选长了,响应太慢,小车根本来不及纠正倾斜就已经倒了;选短了,执行器(电机)反应不过来,反而浪费了CPU。

对于平衡小车这个场景,我实测下来的比较稳的控制周期是 1ms到2ms之间,也就是控制频率500Hz到1kHz。N20减速电机本身带减速箱,响应时间在几毫秒级别,你给它一个1kHz的PWM更新频率,它能响应得过来,也不会因为频繁切换方向而发热严重。

更关键的是,你需要在代码里严格保证控制周期稳定。我在自己的代码里用的是STM32的定时器中断来做时基,比如TIM4配置成1ms中断一次:

void TIM4_IRQHandler(void) { if (TIM_GetITStatus(TIM4, TIM_IT_Update) != RESET) { TIM_ClearITPendingBit(TIM4, TIM_IT_Update); control_task_1ms(); // 每1ms执行一次控制回路 } }

在主循环里只做显示刷新、串口打印这类非实时任务。这个结构要记住,不是所有事情都能放主循环里跑的。

4. 工程实现:从传感器数据到PWM输出的完整链路

原理清楚了,工程就好办了。这一节我们沿着数据流一路走下来——从MPU6050读原始数据,到最后输出PWM驱动电机,中间每个环节做什么、为什么这么做,一次说透。

4.1 传感器初始化与数据读取:I2C时序必须理顺

这一步是所有数据的源头。MPU6050接了I2C,地址是0xD0(7位地址0x68左移一位)。初始化要做这样几件事:

  • 解除休眠模式:往电源管理寄存器(0x6B)写0x00;
  • 配置陀螺仪量程:往陀螺仪配置寄存器(0x1B)写0x08,即±500°/s量程;
  • 配置加速度计量程:往加速度计配置寄存器(0x1C)写0x00,即±2g量程;
  • 配置数字低通滤波器(DLPF):往配置寄存器(0x1A)写0x05,即截止频率约10Hz。

量程的选择是有讲究的。平衡小车倾倒的角度速度一般在±300°/s以内,选±500°/s量程既能保证精度又不会溢出。DLPF建议开启,把高频噪声滤掉,但截止频率不能设太低,否则相位延迟太大会让控制系统“反应迟钝”。

读取数据也值得注意。加速度计和陀螺仪各有三个轴的16位数据,共6个寄存器,从0x3B开始连续读取。读取频率就是你的控制频率——每1ms读一次。这里我建议你直接用硬件I2C(F103的I2C外设),配好DMA后每次读取只占CPU非常少的时间。很多人说STM32的硬件I2C难用,但只要你把时序和错误处理写好,它比软件模拟I2C要省心得多。

4.2 互补滤波的实现细节:不要忽略姿态角的符号问题

姿态解算听起来简单,但实际写代码时很容易掉进符号的坑里。我这里把我用的计算步骤完整列出来:

// 定义常量 #define RAD_TO_DEG 57.29578f #define ALPHA 0.98f #define DT 0.001f // 控制周期1ms // 全局变量 float angle_est; // 融合后的俯仰角 float gyro_y_rate; // 陀螺仪Y轴角速度(单位:度/秒) float accel_angle; // 加速度计计算的俯仰角 // 读MPU6050原始数据后转成物理单位(示例,具体寄存器地址请对照数据手册) int16_t gyro_y_raw = read_gyro_y(); // 陀螺仪灵敏度:±500°/s时,16.4 LSB/°/s gyro_y_rate = gyro_y_raw / 16.4f; int16_t accel_x_raw = read_accel_x(); int16_t accel_z_raw = read_accel_z(); // 加速度计灵敏度:±2g时,16384 LSB/g float accel_x = accel_x_raw / 16384.0f; float accel_z = accel_z_raw / 16384.0f; // 反正切得到角度,注意单位转换 accel_angle = atan2f(accel_x, accel_z) * RAD_TO_DEG; // 互补滤波:先积分陀螺仪,再融合加速度计 angle_est = (angle_est + gyro_y_rate * DT) * ALPHA + accel_angle * (1.0f - ALPHA);

关键点在于:你必须搞清楚陀螺仪和加速度计的轴方向在安装时是否和你的期望一致。一般小车立起来时,MPU6050的Y轴是前后方向,绕Y轴的旋转才是前后倾斜。但不同模块的丝印标注位置可能不一样,建议拿到模块后先做一次“静态标定”——把小车分别往前倾5度、10度,看角度输出是否符合预期,不符合就交换符号。

这个坑特别常见,而且会导致一个非常诡异的现象:PID参数无论怎么调,小车都往一个方向猛冲,仿佛“不受控制”。其实原因仅仅是传感器符号反了。

4.3 PID计算:把代码写成分层结构,方便后续调试

PID控制器本身很简单,但工程实现上我建议分两个文件:pid.cpid.h,定义好结构体,方便同时维护多个PID实例。

// pid.h 里的核心数据结构 typedef struct { float target; // 目标值 float actual; // 当前值 float error; // 当前误差 float last_error; // 上一次误差 float integral; // 积分累积 float kp; // 比例系数 float ki; // 积分系数 float kd; // 微分系数 float output; // PID输出 float max_output; // 输出限幅 } PID_TypeDef;

然后是PID计算函数:

// pid.c float pid_calculate(PID_TypeDef *pid, float target, float actual) { pid->target = target; pid->actual = actual; pid->error = pid->target - pid->actual; // 积分分离:误差过大时不累积积分,防止积分饱和 if (fabs(pid->error) < 10.0f) { pid->integral += pid->error * DT; } else { pid->integral = 0.0f; } // 积分限幅 if (pid->integral > 200.0f) pid->integral = 200.0f; if (pid->integral < -200.0f) pid->integral = -200.0f; // PID组合 float diff = (pid->error - pid->last_error) / DT; pid->output = pid->kp * pid->error + pid->ki * pid->integral + pid->kd * diff; // 输出限幅 if (pid->output > pid->max_output) pid->output = pid->max_output; if (pid->output < -pid->max_output) pid->output = -pid->max_output; pid->last_error = pid->error; return pid->output; }

注意积分分离和积分限幅这两个细节。积分项是PID参数里最容易“搞事”的:误差长时间不消除时,积分累积得非常大,会导致输出饱和,表现为小车一直在朝一个方向冲,完全刹不住。我实际调试中发现,平衡小车这个场景对积分的要求不高——因为姿态控制本身就是个动态稳定的过程,角速度环已经有阻尼作用了。

4.4 控制主循环:所有环节串起来,优先级怎么排

把整个控制回路写进1ms定时器中断里,主循环只管打印和显示。大概长这样:

void control_task_1ms(void) { // 1. 读传感器 read_mpu6050_all(); // 2. 姿态解算:互补滤波更新角度 update_attitude(); // 3. 计算串级PID float speed_target = pid_pitch_outer_calc(target_angle, angle_est); float pwm_output = pid_pitch_inner_calc(speed_target, gyro_y_rate); // 4. 输出PWM到电机 set_motor_pwm(pwm_output); }

你会发现这里没有死循环、没有延时。整个时序由定时器中断保证,确定性非常强。这是嵌入式实时控制的基本素养——控制回路必须严格定时执行,任何一次延时都会让控制周期抖动,导致PID参数“失效”(实际是在那个参数下系统不稳定)。

在你写完这些之后,小车上电,理论上电机已经会对倾斜做出反应了——但大概率是剧烈抖动或者乱冲。真正的挑战才刚刚开始:调参。

5. PID调参实录:让小车从“疯狂抽搐”到稳稳站立

调参是这个项目最磨人的环节,也是收获最大的环节。我把自己真实的调参过程按顺序记录下来,你可以照着做,避免像我当时一样毫无头绪地瞎试参数。

5.1 第一步:用上位机看波形,别靠肉眼猜

调参前必须把数据可视化。很多同学直接改一个参数、上电、看小车能不能站起来——这是效率最低的做法,因为你只能看到“结果”,看不到中间状态。

推荐用VOFA+或者匿名上位机(网上都能找到)。通过串口把角度值、角速度值、PID输出值一起发上来,实时看波形。

调参时观察什么?很简单,就三条:

  • 实际角度是否在目标角度附近震荡?震荡频率多高?
  • 角速度波形是否平滑?有没有异常毛刺?
  • PID输出是否频繁打到限幅值?

如果角度在平衡点附近以大概2~3Hz的频率来回晃,说明比例系数偏低;如果频率高到看不清,那就是参数过大导致系统接近不稳定。

5.2 第二步:先调内环角速度环的Kp,找到“阻尼感”

这里我直接说顺序:内环Kp → 内环Kd → 外环Kp → 外环Kd(如果需要)。

先把外环的Kp设成0,也就是暂时不管角度,只让内环工作。然后从小到大调内环Kp。

怎么判断“调到哪了”?用手拿住小车,让它保持竖直,然后快速左右旋转小车——你会感觉到电机产生一个反向的抵抗力矩,这就是内环在起作用。Kp越大,这个抵抗力矩越强,但太大之后小车会开始高频颤抖。找到一个“有阻尼感、但不颤抖”的点,记下这个Kp值。

然后是内环Kd。理论上角速度环的微分作用是抑制角速度的变化率,可以进一步增加阻尼。但实际体验是,N20电机自身机械阻尼就不小,Kd加太大反而会引入更多噪声,所以在平衡小车这种场景下,内环Kd通常设得很小甚至为零,不用强求。

5.3 第三步:逐渐加入外环Kp,“小车开始有站起来的意识”

内环稳定之后,开始增加外环Kp。此时小车会表现出一个明显的特征:你把它往一边推,它会努力“反向追”,但追过头又往另一边倒——整体呈低频摇摆,幅度逐渐缩小或者发散。这时候外环Kp太小,继续加大,直到摇摆幅度逐渐收敛,小车能保持一两秒的直立状态。

到这一步你会找到一个现象:小车能站起来,但两秒后开始幅度越来越大,最后还是倒下去。这通常是上电初始角度误差累积导致的。可以尝试加上外环Kd,让角度变化率也参与控制。外环Kd的作用类似“预判”——角度还在变大时提前给反向力矩。

5.4 我调的一个真实案例:从“抽搐”到“稳定”的参数变化

把参数变化记录下来,方便你有个参考尺度。我这台车轴距约15cm,电机是N20减速1:30,轮径6.5cm,车体重心在轮轴上方约6cm:

调参阶段内环Kp内环Kd外环Kp现象
初始乱试25.02.050.0疯狂抽搐,一上电就抖到跳起来了
清空外环5.00.00.0手转时有阻尼感,不抖
稳定内环8.00.20.0阻尼很强,响应干脆
加外环Kp8.00.212.0能撑住一小会儿,来回摆动幅度大
加外环Kd8.00.212.0有明显“定住”的感觉
最终微调10.00.315.0能从手松开到稳定直立,持续不倒

这套参数并不是可以照抄的“标准答案”,因为不同车重心、轮径、电机减速比都会影响结果。但它能让你看出调参的趋势:内环参数通常比外环小一个量级,最终比例参数的外环Kp比内环Kp略大。

5.5 调参中的常见现象速查表

调参最烦的就是“车倒了但不知道是哪个参数的问题”。我整理了常见的现象和原因,方便你对照排查:

现象大概率原因处理方式
上电就开始高频锯齿抖动内环Kp/Kd太大先调小内环Kp,再关掉内环Kd
低频来回大摆,幅度逐渐减小外环Kp太小,阻尼不够加大外环Kp或外环Kd
能撑几秒然后越摆越大外环Kd不足,积分项错误加外环Kd,检查是否累积了积分
往固定方向加速冲倒传感器符号有误或重心偏移先检查陀螺仪/加速度计符号,再检查机械结构
角度波形毛刺严重传感器电源噪声太大检查滤波电容、电源走线,降低DLPF截止频率
参数死活调不出来机械结构松动或电机死区先解决问题,再谈调参

6. 这些细节才是项目成败的分水岭

硬件、原理、代码、调参都讲完了,最后再补充几个决定成败但容易被忽略的细节。这些都是我实际踩坑踩出来的体验,比任何教科书都实用。

6.1 机械结构:重心越低越稳,轴间摩擦越小越好

很多人的车调不出来,问题根本不在代码——车架太轻,重心太高,整体结构松散。

平衡小车的稳定性和重心高度有直接关系。重心越低,系统响应的“自然频率”就越低,PID参数容忍度越高。网上有人做过实验,重心高度从5cm降到3cm,能让稳定的Kp范围扩大将近一倍。所以设计车架时,电池这种重物尽量放低,不要立在车体上方。

另外,轮轴和车架的摩擦是很隐蔽的敌人。摩擦太大会导致电机在小PWM信号下根本不动,形成“死区”;摩擦太小又可能导致车轮打滑。我建议车轮和轴接触的地方加一点润滑,保证手拨动车轮能顺畅空转。

6.2 电池电压跌落:为什么小车跑一会儿就开始晃了

锂电池放电到后半程,电压会明显下降,电机扭矩跟着变小。如果PID参数是在满电时调的,那低电量时小车就会变得“软绵绵”,甚至直接倒下去。

我的解决方案是做一个简单的“电压补偿”——用ADC采集电池电压,把它换算成衰减系数,乘到PID输出上。电压高时输出被压缩,电压低时输出被放大。这样能让小车在不同电量下保持大致相同的表现。

6.3 陀螺仪零漂:静态看起来正常,动态慢慢偏

即便用了互补滤波,陀螺仪零漂也不能完全消除。如果你发现小车能站住,但会非常缓慢地朝一个方向“溜走”,十有八九是零漂造成的。

解决办法是在上电初始化时,让小车保持静止,连续采集几百次陀螺仪数据求平均值,作为零偏值存下来。之后每次读陀螺仪时减去这个零偏值。代码就几行:

// 上电时采集200次陀螺仪数据取平均作为零偏 for (int i = 0; i < 200; i++) { gyro_zero_bias += read_gyro_y_raw(); delay(1ms); } gyro_zero_bias /= 200; // 使用时: gyro_y_rate = (read_gyro_y_raw() - gyro_zero_bias) / 16.4f;

这个步骤虽然简单,但很多人不做,导致后续加编码器速度环时发现控制效果怎么都不对。基础不牢,地动山摇。

6.4 拓展玩法:速度环和转向环,让车真正“可控制”

小车能站稳之后,你还可以加两个环让它更实用:速度环和转向环。速度环是在角度环的外面再套一层,用编码器测速,通过改变目标倾角来控制车速——想要前进就给一个小幅的前倾角目标,后退就给后倾角。转向环则是通过两个轮子的PWM差值实现原地旋转或差速转向。

这两个环是典型的“锦上添花”功能。先掌握好直立控制,再加这些,别一口吃个胖子。

最后分享两个小技巧

做完这台车之后,我最大的感受是:平衡小车项目没有捷径,但有些小技巧能让你少折腾好几个晚上。

第一个技巧——调参之前一定要把车放在柔软的地方上面操作(比如被子或者瑜伽垫),并且用一根绳子把车拴住。否则每次调参失败,你都要弯腰捡车、重新扶着、再上车,一轮下来手都酸了,效率极低。拴住车还有个额外好处:它不会摔坏,传感器也能反复用。

第二个技巧——每次改参数前,在串口日志里打一行带时间戳的记录,写明改了什么值、现象是什么。别嫌麻烦,调参调到后来你会发现,上午调的某个参数组看起来很有潜力,但当时没记录,下午已经回忆不起来了。相信我,这个日志的价值比PID参数本身还高。

这是我第二遍做平衡小车时才养成的习惯。第一次做的时候,断断续续折腾了两个礼拜,失败了无数次。第二遍有了清晰的调参思路和硬件选型,一下午就站起来了。希望这篇笔记能帮你从“被车折磨”的状态里早一点解脱出来。

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

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

TI C2000 F28P550调试实战:CLA/CAN/PWM协同调试陷阱与硬件级定位

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

作者头像 李华
网站建设 2026/9/9 2:11:22

RS-485总线终端电阻与上下拉电阻:区别、计算与现场调试

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

作者头像 李华
网站建设 2026/9/9 2:10:52

Vue大文件上传完全指南:分片、续传与商业方案选型

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

作者头像 李华
网站建设 2026/9/9 2:10:37

RBF神经网络C++实现:从高斯基函数到工业级实时预测

简介&#xff1a;这是一份基于C实现的RBF&#xff08;径向基函数&#xff09;神经网络完整源码&#xff0c;面向需要解决函数近似、模式识别、系统辨识等非线性问题的开发者和初学者&#xff0c;也适合机器学习课程实践与算法原理验证。资源包含117个文件&#xff0c;压缩包约9…

作者头像 李华
网站建设 2026/9/9 2:08:22

NVIDIA Triton推理服务架构源码解析与生产调优实践

1. 模型服务化之前&#xff0c;我经历的那些“低配”做法先说个亲历的场面。两三年前我在团队里负责把几个视觉模型推上线&#xff0c;当时最“省事”的方案就是Python FastAPI PyTorch&#xff0c;一个模型起一个服务进程&#xff0c;模型各自独享一份显存。最初只有两个模型…

作者头像 李华
网站建设 2026/9/9 2:04:54

GC10-DET:YOLO全系通用目标检测数据底盘

简介&#xff1a;GC10-DET是一个面向目标检测算法研究者与工程开发者的专用YOLO系列模型训练数据集&#xff0c;适用于YOLOv5、YOLOv8、YOLOv10及新兴YOLO11等版本的端到端训练与性能验证&#xff0c;尤其适配自动驾驶、智能监控、无人机识别等实时视觉场景。资源共2000个文件&…

作者头像 李华