简介:平衡小车car--葛凤佩.rar 是一份面向自动控制、嵌入式与机器人方向学习者的工程实践包,围绕平衡小车的姿态检测、电机驱动与闭环控制组织内容,可用于对照PID调节、卡尔曼滤波、状态观测等知识点理解算法如何落地。压缩包共1420个文件、约36.17MB,主体为439个C源文件与379个头文件,并配有121个txt说明、汇编启动文件、链接脚本,以及Keil/IAR工程配置、HEX/AXF固件和doc、pdf、chm等文档,覆盖从代码阅读、工程编译到烧录验证的主要环节。已有1748人学习,说明其在平衡车学习中具备一定参考热度。读者可重点梳理传感器数据融合、控制量计算与执行器输出的代码组织方式,结合工程配置和固件理解参数设置、编译选项与硬件适配关系,也可借助说明文档复核调试思路,适合作为课程设计、毕业设计或平衡车类项目的对照材料。
1. 平衡小车资源包里到底装了什么:从一份能编译的工程到一台能站住的车
平衡小车这个题目几乎所有做嵌入式控制的人都会撞上一次:两个减速电机、一块主控、一颗六轴传感器,硬件成本不高,但真正通电以后十台里有八台是原地抽搐或者干脆扑倒。这份资源包填的正好是"装完了"到"站住了"中间那段空白——它把姿态解算、三环 PID、电机驱动这几段最容易出问题的代码拆开平铺在一起,配上可对照的调参记录,而不是只丢一个 main 文件让人自己猜。
它适合两类人:一类是车架焊好、程序能烧进去、但姿态就是稳不下来的;另一类是刚接触倒立摆模型,想看清楚一条控制链路究竟怎么闭环的。包里是典型的单片机工程 + 原理图 + 调参笔记的组合,代码风格偏寄存器直写,注释密度不低。
2. 姿态解算链路:MPU6050 采样、滤波与角度零点标定的可抄配置
车能不能站住,七成取决于送进 PID 的那个角度准不准。这条链路是"传感器原始值 → 零偏扣除 → 滤波融合 → 倾角输出",其中任何一环偷懒,后面调参就是纯玄学。
2.1 采样率、量程与 DLPF 三个寄存器的取舍
很多人拿到例程直接跑,从不看初始化里写了什么。实际上 MPU6050 有四组寄存器决定了后面所有参数的天花板:采样率分频、数字低通滤波(DLPF)、陀螺量程、加速度量程。资源包里给的配置是 200Hz 采样、DLPF 44Hz、陀螺 ±2000dps、加速度 ±8g,这套组合对应 5ms 的控制周期,属于平衡车场景里比较稳的一档。
/* MPU6050 上电配置:顺序不能乱,先解休眠再切时钟源 */ static void MPU6050_Config(void) { I2C_WriteReg(MPU6050_ADDR, PWR_MGMT_1, 0x00); /* 解除休眠,先用内部 8M 时钟 */ delay_ms(50); I2C_WriteReg(MPU6050_ADDR, PWR_MGMT_1, 0x01); /* 切到 X 轴陀螺 PLL,抖动明显变小 */ I2C_WriteReg(MPU6050_ADDR, SMPLRT_DIV, 0x04); /* 1k/(1+4) = 200Hz 输出速率 */ I2C_WriteReg(MPU6050_ADDR, CONFIG, 0x03); /* DLPF 44Hz,群延迟 4.9ms */ I2C_WriteReg(MPU6050_ADDR, GYRO_CONFIG, 0x18); /* ±2000dps,16.4 LSB/(deg/s) */ I2C_WriteReg(MPU6050_ADDR, ACCEL_CONFIG,0x10); /* ±8g,4096 LSB/g */ }这里每一步都有理由。先写 0x00 再写 0x01 是因为芯片从休眠唤醒后寄存器需要时间稳定,直接一步到位切 PLL 经常读回全零。采样率定在 200Hz 而不是 1kHz,是因为控制周期本身只有 5ms,再快的采样只是增加 I2C 总线占用,对角度没有实质帮助。DLPF 选 44Hz 是一个折中:设成 260Hz 延迟低但电机振动会串进来,设成 5Hz 干净但延迟接近 20ms,串级 PID 里这个延迟足以让直立环振荡。
量程选择要跟实际姿态角速度匹配。±2000dps 看着浪费精度,但车在临界倒下时角速度能瞬间冲到 300dps 以上,±500dps 会直接饱和削顶,滤波器输出就被拉平了。加速度选 ±8g 同理,落地瞬间的冲击加速度远超 1g。这两项配错了,表现为"角度在剧烈晃动时突然卡住不变",很容易被误判成滤波算法有问题。
提示:改完量程一定要同步改代码里的灵敏度常数,16.4 和 4096 这两个数字写错,角度会整体放大或缩小几倍,症状是车往一边持续加速。
2.2 互补滤波与卡尔曼滤波:两条路线的代码与延迟对比
资源包里两种滤波都给了,实际跑下来平衡车用互补滤波完全够。卡尔曼的优势在无人机那种长时间、大动态的姿态估计,而平衡车的姿态变化范围小、周期短,卡尔曼多出来的计算量和调参维度(Q、R 两个协方差矩阵)反而让新手更容易翻车。
互补滤波的核心思路一句话:陀螺仪积分短期准、长期漂,加速度计长期准、短期噪声大,用系数把两者按频段拼起来。
#define ALPHA 0.98f /* 信任陀螺仪的比例,0.95~0.99 之间调 */ #define RAD2DEG 57.29578f float pitch = 0.0f; /* 全局姿态角,单位度 */ void Attitude_Update(float dt) { float acc_angle, gyro_rate; MPU6050_Read(&acc, &gyro); /* 读原始值,内部已减零偏 */ /* 加速度计解算倾角:车头朝前时用 Y/Z 两轴,atan2 免去象限判断 */ acc_angle = atan2f(acc.y, acc.z) * RAD2DEG; /* 陀螺仪角速度先积分,再与加速度角加权融合 */ gyro_rate = gyro.y / 16.4f; /* 原始值转成 deg/s */ pitch = ALPHA * (pitch + gyro_rate * dt) + (1.0f - ALPHA) * acc_angle; }参数上,ALPHA 直接决定截止频率,按 f = ALPHA / (2π·dt·(1-ALPHA)) 估算,dt=5ms、ALPHA=0.98 时截止频率约 1.5Hz,意思是 1.5Hz 以下的慢变化主要信加速度计,以上信陀螺仪。车身晃动的机械频率一般在 2~4Hz,正好落在陀螺仪主导的区间,所以直立环拿到的是一个很"跟手"的角度。ALPHA 调到 0.99 以上,加速度计的修正力度太弱,零点漂移会把角度慢慢带偏;调到 0.9 以下,电机振动会直接灌进角度里,波形上表现为毛刺。
| 对比项 | 互补滤波 | 卡尔曼滤波 |
|---|---|---|
| 计算量 | 约 10 次浮点运算 | 矩阵运算,几十次乘加 |
| 可调参数 | 1 个(ALPHA) | 至少 4 个(Q 角、Q 偏、R) |
| 静态漂移抑制 | 中等,依赖加速度计修正 | 强 |
| 动态延迟 | 约 5~10ms | 约 10~20ms |
| 适合场景 | 平衡车、云台 | 无人机、长时间姿态记录 |
实测下来,平衡车换卡尔曼收益很小,因为车永远不会连续倾斜超过 30 度,加速度计在这么小的角度范围内线性度足够好。倒是有一个例外:如果车要在坡道上长时间静止,卡尔曼的零偏估计会更稳,这时候才值得换。
2.3 零点标定:每次上电必须走的那两秒
这一节是血泪经验。同一块板子,冷机开机和热机开机读出来的陀螺零偏能差 20~30 LSB,折算成角速度接近 2 deg/s,积分两秒就是 4 度——足够让车往一边缓缓加速直到撞墙。所以每次上电必须做静态标定,而且必须在车完全静止、电源稳定的前提下做。
float gyro_offset = 0.0f; /* 上电后调用一次,车必须静止放在水平面上 */ void Gyro_Calibrate(void) { int32_t sum = 0; for (int i = 0; i < 1000; i++) { sum += MPU6050_ReadRawGyroY(); delay_ms(2); /* 2ms 一次,总耗时 2 秒 */ } gyro_offset = (float)sum / 1000.0f; /* 均值即零偏 */ }1000 次采样取均值是惯用做法,样本数再往上加收益不大,因为噪声已经被平均压到一个可接受的水平。这 2 秒不能省,也不能在车还晃的时候触发——常见的错误是把标定放在初始化函数里,而初始化又在上电瞬间执行,此时电源还没完全建立,标定出来的零偏本身就是错的。正确顺序是:上电 → 延时 500ms 等电源稳定 → 蜂鸣器提示 → 用户确认车已放稳 → 标定 → 进主循环。
标定完成后还要存一个"机械中值",也就是车真正平衡时那个角度值,它通常不是 0 度,因为电池、主控板的重心分布不会完美对称。这个值靠手动找:扶住车让它静止在自然平衡位置,读一次 pitch,写进代码当 target。后面所有 PID 都是围绕这个 target 收敛的。
3. 三环 PID 串级:直立环、速度环、转向环的落地顺序与整定手法
三环不假,但不能同时调。资源包里的调参笔记写得很直白:先让车能站(哪怕站不住三秒),再让它站定,最后才谈转向。这个顺序反了,后面根本调不出来。
3.1 串级结构:为什么直立环必须跑在最内层
整个控制链路是三层环路嵌套:最外层是转向环,中间是速度环,最内层是直立环。执行周期也是由内向外递减——直立环 5ms 跑一次,速度环 20~50ms,转向环可以更慢。
原因在于响应速度。直立环面对的是"车正在倒"这个物理事实,从倾斜到倒地只有几百毫秒,必须用最快的周期去纠正。速度环关心的是"车整体在往哪走",它是一个慢变量,周期太快会把编码器的量化噪声放大成抖动。转向环更慢,因为转向是建立在车已经稳住的前提下的差速动作。
串级还有一个关键点:外环的输出不是直接给电机,而是作为内环的目标值。速度环算出来的不是 PWM,而是一个"应该倾斜多少度"的角度补偿量,叠加到直立环的角度目标上。车想往前走,就先把重心往前压,靠直立环的纠偏动作自然产生前进的加速度。这个逻辑想通了,参数就好调了。
3.2 直立环 PD:用陀螺仪做阻尼,别去微分角度
直立环用 PD 不用 PID,因为它不需要消除静差——角度有静差车就倒了,根本不给你积分的时间。而且这里有个坑:微分项不能用角度微分,必须直接用陀螺仪角速度。
/* 直立环:角度误差 × Kp,角速度 × Kd,两项相减 */ #define KP_ANGLE 30.0f /* 起始值,实测常用 20~60 */ #define KD_ANGLE 0.8f /* 与 Kp 大致成 1:40 关系 */ float Balance_Out(float angle, float gyro_rate, float target_angle) { float err = target_angle - angle; /* target 是机械中值 */ return KP_ANGLE * err - KD_ANGLE * gyro_rate; }角度微分和陀螺仪的区别在于噪声。角度是滤波后的结果,本身带延迟,微分之后延迟叠加,相位滞后更严重,容易振荡;陀螺仪是直接测量值,几乎没有延迟,做阻尼效果好得多。这就是为什么无数教程强调"Kd 用陀螺仪"。
整定顺序是先 Kp 后 Kd。Kp 从 10 开始,逐次加 5,加到车能明显往反方向拉但开始低频摇摆为止;然后 Kd 从 0.2 起加,加到摇摆被压住、车身变"硬"为止。Kd 加过头会怎么样?车会发出高频的"嗡嗡"声,用手摸电机能感觉到明显的高频抖动,这是阻尼过强导致的,减回去一点就行。
这里的输出量是 PWM 基础值,范围要和电机驱动的输入匹配。如果驱动用的是 0~1000 的占空比,Kp=30 时 10 度误差就饱和了,实际上更常见的是把输出限制在 ±8000 这样的范围里,具体看定时器 ARR 值。
3.3 速度环 PI:把"站住"变成"站定"
只有直立环的车表现是:能站,但会缓慢往一个方向漂,撞到东西才停。这是因为直立环只保证角度回到中值,不保证位置不变。速度环的作用就是让车在原点附近来回微调,实现"站定"。
/* 速度环:PI 控制,输出是角度补偿量 */ #define KP_SPEED 80.0f #define KI_SPEED 0.6f #define INTEG_LIMIT 300.0f /* 积分限幅,防止饱和 */ float Speed_Loop(float speed_now, float speed_set, float dt) { static float integ = 0.0f; float err = speed_set - speed_now; integ += KI_SPEED * err * dt; if (integ > INTEG_LIMIT) integ = INTEG_LIMIT; if (integ < -INTEG_LIMIT) integ = -INTEG_LIMIT; /* 车已静止且目标静止时慢慢泄放积分,否则零点会越积越偏 */ if (fabsf(speed_now) < 5.0f && fabsf(speed_set) < 5.0f) integ *= 0.98f; return KP_SPEED * err + integ; }这段代码有三个细节值得说。第一是积分限幅,没有限幅的话,车被人按住不放时积分会一直累积,松手瞬间输出爆掉,直接翻车。限幅值取多少要看它叠加到角度上的量级,一般不超过 ±5 度对应的数值。第二是静止泄放,速度 PI 的积分项在静止时会因为编码器噪声缓慢累积,表现是车原地站十分钟后突然自己往前走一段,这个 0.98 的衰减系数就是后悔药。第三是速度反馈的获取方式,编码器读的是脉冲数,必须除以采样周期才能得到速度,单位统一成"脉冲/20ms"比换算成 m/s 更省事,因为 PID 参数本来就是靠试凑,量纲不影响结果。
速度环的 Kp 调法比较特殊:先给一个不大的值,让车出现明显的前后低频摆动,然后加 Kp 把摆动频率提高、幅度压小,最后用 Ki 消除残余的缓慢漂移。Ki 加多了典型的症状是车头持续低频点头,像在鞠躬。
3.4 转向环与编码器:差速转向的最小实现
转向靠左右轮差速。最简单的实现是给两个轮子叠加一个相反的偏置量,一边加速一边减速,车身就转过来了。
/* 转向环:目标角速度与实测角速度的偏差,叠加左右轮差速 */ #define KT_YAW 12.0f #define KT_DIFF 1.5f int16_t Steer_Out(int16_t target_yaw, int16_t gyro_z, int16_t diff_speed) { /* gyro_z 是 Z 轴角速度,转向前馈用;diff_speed 是左右轮速差 */ return KT_YAW * (target_yaw - gyro_z) + KT_DIFF * diff_speed; }参数 KT_YAW 决定转向跟随的灵敏度,太大车会左右摆头,太小转向迟钝。KT_DIFF 是差速前馈,一般给个很小的值就行,主要作用是让转向时左右轮的动作更协调。转向环的输出最后是这样用的:左轮 PWM = 直立输出 - 转向输出,右轮 PWM = 直立输出 + 转向输出。注意这个叠加是在直立环输出之后,不能影响直立环本身的计算。
编码器部分通常用定时器的编码器模式,四倍频计数。资源包里给的是 1ms 定时中断里累加计数、20ms 结算一次的做法,等于每 20ms 算一次速度。这个周期不能再长了,再长速度环的相位滞后会导致振荡;也不能太短,编码器本身有量化误差,周期太短噪声占比高。
4. 执行层落地:PWM 死区、正反转与供电噪声的四个硬约束
控制算法算得再漂亮,最后都要落到两个轮子上。执行层这一块属于"看起来没有算法含量,但翻车率最高"的部分,尤其是供电。
4.1 驱动选型:TB6612 与 L298N 的取舍
资源包里两种驱动方案都提到了,实际用哪种取决于电机电流和车重。
| 对比项 | TB6612FNG | L298N |
|---|---|---|
| 单路持续电流 | 1.2A(峰值 3.2A) | 2A |
| 导通压降 | 约 0.5V | 约 2V |
| 支持 PWM 频率 | 100kHz | 约 25kHz |
| 待机电流 | 微安级 | 毫安级 |
| 散热需求 | 基本不用 | 必须加散热片 |
| 适配电机 | 小型减速电机 | 中大型减速电机 |
平衡小车的电机一般是 12V 直流减速电机,堵转电流 1~2A,TB6612 完全够用,而且它的低导通压降意味着电池的利用率更高。L298N 的 2V 压降在 12V 系统里就是 17% 的能量直接变成热,电池续航会明显变短,所以除非电机特别大,否则优先 TB6612。
选型之外还有一个容易忽略的点:TB6612 的 STBY 引脚必须拉高才能工作,很多板子把它直接接 VCC 了,这会导致待机功能失效,但正常使用没影响;如果接的是单片机 IO,初始化时必须输出高电平,忘了这一步就表现为"程序跑得好好的,电机一动不动",属于典型的新手坑。
4.2 PWM 频率、死区与正反转切换
PWM 频率的选择有一个权衡:太低(1kHz 以下)电机会发出可听见的啸叫,而且电流纹波大;太高会超出驱动的响应能力,TB6612 支持到 100kHz,但实际上 10~20kHz 是最舒服的区间,超出人耳听觉范围,电流也平滑。
真正的麻烦在死区。直流电机有一个启动电压,占空比低于某个值时电机根本不转,只是发热。如果代码里直接按计算结果输出 PWM,那么 PID 输出一个小值的时候,轮子不动,直立环发现角度没变化,继续加大输出,一旦超过阈值电机突然转起来,表现为低速时的顿挫和抖动。
/* 死区补偿:绝对值小于阈值的输出直接归零,超过阈值的部分整体平移 */ #define DEAD_ZONE 600 /* 实测值,不同电机差异很大,需实际测 */ int16_t DeadZone_Comp(int16_t pwm) { if (pwm > 0) return (pwm < DEAD_ZONE) ? 0 : (pwm - DEAD_ZONE + 1); else if (pwm < 0) return (pwm > -DEAD_ZONE) ? 0 : (pwm + DEAD_ZONE - 1); return 0; }补偿之后,输出的有效范围是 DEAD_ZONE 到最大值,线性度会改善很多。DEAD_ZONE 的实测方法是:写一段固定占空比逐个递增的测试代码,观察轮子从哪一刻开始转,把那个值记下来。左右电机必须分别测,两个电机的死区很少完全一样,用同一个值补偿会导致车一直往一边偏。
正反转切换也需要保护。电机是感性负载,瞬间反向会产生反向电动势,电流倒灌回驱动器。常见做法是在切换方向前先输出 0 并延时 1~2ms,或者确保 PWM 输出经过限幅后的变化率不超过每周期 200 个计数。
4.3 供电与共地:电压跌落是姿态数据的头号污染源
这一条是这份资源包里最值钱的部分之一,因为它很难通过看代码发现。
电池在电机启动瞬间会出现明显的电压跌落,从 12V 掉到 10V 甚至更低。如果传感器和主控用的是同一路电,跌落会直接反映到 ADC 参考电压和 I2C 时序上,表现是:车静止时角度数据干干净净,电机一启动,角度上就叠了一层毛刺。这种毛刺进到 PID 里会被当成真实的姿态变化,形成正反馈,车身开始抽搐。
解决办法有两个层次。硬件层面,传感器供电加 LC 滤波或者单独用 LDO 从电池降压,电机电源和逻辑电源分开走线,最后在电池负极单点接地。软件层面,可以在电机启动后延时几十毫秒再读一次传感器,或者在角度输出上做一个简单的中值滤波。但根本办法还是硬件,软件只能缓解。
共地是另一个高频问题。主控、驱动、传感器三者必须共地,而且地线要足够粗。如果共地不良,会出现"串口打印正常、电机也转、但两个一结合就乱"的现象,因为电机的大电流在地线上产生了压差,传感器读到的地电平在浮动。判断方法很简单:用万用表量驱动板 GND 和主控 GND 之间的电压,电机运转时如果有几十毫伏的波动,就是地线阻抗太高。
5. 避坑与排查:车不倒、抽搐、跑偏、烧驱动的五份现场记录
这一章记录的都是实际调试中碰到的、看代码看不出来的问题。每一条按"现象 → 原因 → 解决"写,碰到类似症状可以直接对号入座。
5.1 上电后车往一边冲,越冲越快
现象:摆正后松手,车往固定一个方向加速,直立环像是没起作用。
原因有三层。最常见的是目标角度写反了符号,代码里target - angle逻辑没问题,但传感器装反以后 angle 的正方向也跟着反了,PID 的纠正方向变成加速方向,这是标准的正反馈。第二层是陀螺仪项符号错了,-KD * gyro里的减号写成了加号,阻尼变成助推。第三层是电机接线左右接反,直立环输出的"前进"实际驱动的是"后退"。
解决:先断开电机只接传感器,手扶车往前倾,看串口打印的 angle 是不是朝预期的方向变化;确认后接上电机,把 Kp 调到很小(比如 5),轻推车身,看轮子是不是往抵抗倾斜的方向转。这两步能定位 90% 的正反馈问题。
5.2 车身高频抖动,电机发出嗡嗡声
现象:车能勉强站住,但整个车身在肉眼可见地高频颤抖,手摸电机能感觉到明显的振动。
原因:Kd 过大或者滤波延迟过大。Kd 过大时阻尼过强,系统在目标附近反复过冲;滤波延迟大则是相位滞后累积,同样引发振荡。还有一种可能是 PWM 频率落在电机共振点上,尤其是 1kHz 附近的长音。
解决:先把 Kd 减半观察型号是否减弱,确认是阻尼过强就逐步回调。如果是滤波延迟,把 DLPF 从 44Hz 提到 98Hz 或 188Hz,牺牲一点噪声换取响应速度。PWM 频率统一提到 15kHz 以上,避开可听频段。
5.3 静止时能站住,一走就倒
现象:不给速度指令时车稳稳站着,一旦给前进指令,车加速一段就失去平衡倒下。
原因:速度环的输出直接叠加到角度目标上,但叠加量没有限幅。车启动瞬间速度误差很大,PI 输出一个很大的角度偏置,车体前倾过度,直立环来不及恢复。也可能是速度环积分饱和,加速结束后积分数值还很大,车会继续前冲。
解决:给速度环输出加一个角度限幅,一般不超过 ±5 度。同时在速度环里做积分限幅和静止泄放(上一章的代码已经包含)。另外速度环的 Kp 不要一次性给到位,从 20 开始逐次加。
5.4 轮子发烫、驱动板摸不得,甚至冒烟
现象:车调试了十几分钟,摸驱动芯片烫手,电机外壳也明显升温,严重时驱动烧毁。
原因:一是短路刹车,也就是 PWM 输出为 0 时驱动器仍然处于"制动"状态,电机被短接,电流全部变成热。二是死区补偿没做,PID 长时间输出一个小值,电机堵转。三是正反转频繁切换,反向电动势反复冲击。四是电源电压超过驱动额定值。
解决:确认驱动器在 PWM 为 0 时进入的是滑行而不是制动;加死区补偿;方向切换前插入 2ms 的零输出;检查电池满电电压是否超出驱动上限,12V 电池满电接近 13V,要留余量。散热片别省,尤其 L298N。
5.5 串口波形正常,但车完全不动
现象:打开上位机看角度曲线,跟手得很,姿态数据完全正常,但电机一点反应都没有。
原因:按出现频率排,STBY 引脚没拉高、电机驱动供电没接、PWM 定时器没启动、编码器定时器没使能、直立环输出被限幅成 0。还有一种隐蔽的:PID 计算用了整数,误差乘参数之后被截断成 0。
解决:先拿万用表量驱动板 VM 引脚电压,再量 STBY 电平,然后单独写一段固定占空比的测试代码直接驱动电机。这三步做完还没转,就往定时器配置上查。浮点 vs 定点这个问题很常见,主控没有 FPU 时编译器会把浮点运算做得很慢,甚至在某些优化等级下行为异常,直接改用整数放大 100 倍运算更稳妥。
6. 从站住到站定:串口波形整定与参数固化的一次完整走查
调参最怕的情况是改一个参数、烧一次程序、看两眼、再改——一轮下来五分钟,调一天也没记忆。更有效的做法是把关键量全部通过串口吐出来,用上位机同时看四条曲线:角度、角速度、速度环输出、左右轮 PWM。看曲线的价值在于能看到相位关系,比如角度开始振荡的时刻,PWM 输出是提前还是滞后,这直接决定该加 Kp 还是加 Kd。
串口协议不用复杂,固定帧头加定长数据就够了,波特率 115200,10ms 发一帧,不影响控制周期。
/* 10ms 定时中断里调用,一帧 12 字节:帧头 + 4 个 int16 + 校验 */ void Uart_Report(float angle, float gyro, float speed, int16_t pwm) { uint8_t buf[12]; buf[0] = 0xAA; buf[1] = 0x55; int16_t a = (int16_t)(angle * 100); /* 放大 100 倍保留两位小数 */ int16_t g = (int16_t)(gyro * 10); int16_t s = (int16_t)(speed * 10); memcpy(&buf[2], &a, 2); memcpy(&buf[4], &g, 2); memcpy(&buf[6], &s, 2); memcpy(&buf[8], &pwm, 2); buf[10] = buf[2] ^ buf[3] ^ buf[4] ^ buf[5] ^ buf[6] ^ buf[7]; buf[11] = 0x0D; Uart_Send(buf, 12); }放大倍数要跟量程匹配,角度 ±30 度乘 100 是 ±3000,int16 放得下;如果角度可能更大就要降倍数,否则溢出翻转,波形上会看到突然从 +32767 跳到 -32768 的断崖,这种波形会误导判断。
上位机的用法也有讲究。不要一上来就看整体趋势,先把时间窗缩到 2 秒,看单个振荡周期的波形形状;确认形状是正弦还是方波,正弦说明是线性区的振荡,调增益有效,方波说明已经饱和,得先降增益。再看角度和 PWM 的相位差,PWM 滞后角度超过 90 度,系统必然振荡,这时候加 Kd 没用,得减少滤波延迟或者加快控制周期。
参数调好之后必须固化,否则下次上电全白干。常见做法是把 PID 参数存在 Flash 的指定扇区,上电读出来,串口支持在线修改并触发保存。
| 参数 | 初值 | 调整方向 | 典型范围 |
|---|---|---|---|
| KP_ANGLE | 30 | 车拉不住就加,低频摇摆就减 | 20 ~ 60 |
| KD_ANGLE | 0.8 | 高频抖就减,回中太慢就加 | 0.3 ~ 2.0 |
| KP_SPEED | 80 | 前后摆动大就减 | 40 ~ 150 |
| KI_SPEED | 0.6 | 缓慢漂移就加,点头就减 | 0.2 ~ 1.5 |
| KT_YAW | 12 | 摆头就减,转向迟钝就加 | 5 ~ 30 |
| DEAD_ZONE | 600 | 实测确定,左右分别测 | 300 ~ 900 |
调参记录表一定要手写或者存文件,每次改了什么、波形变成什么样,都记一笔。我吃过最大的亏是某次调好了没记,第二天改结构拧了两颗螺丝,重心变了,参数全废,只能从头再来一遍,前后折腾了三个晚上。从那以后我每次动过机械结构,或者换过电池,都强制走一遍完整流程:静止标定零偏 → 串口看五分钟波形确认没有漂移 → 从小 Kp 开始重调直立环 → 再叠速度环。这套流程看着笨,但比凭感觉瞎调快得多。
希望帮到你。
本文还有配套的精品资源,点击获取