资讯动态

STM32两轮自平衡小车:姿态解算与串级PID调参实战

发布时间:2026/10/26 13:28:00 来源:尧图企业网站定制
简介面向自动控制课程设计、毕业设计与平衡小车入门实践的完整工程资料包围绕陀螺仪与加速度计姿态采集、PID调节、卡尔曼滤波及状态观测等核心环节帮助读者从理论走向可运行的嵌入式实现。压缩包共1420个文件约36.17MB以C源码和头文件为主体辅以汇编启动文件、工程配置、链接脚本、HEX/AXF等构建产物以及TXT说明、DOC/PDF文档和图片素材覆盖编码、编译、下载与调试所需链路。已有1748人学习适合需要对照真实工程理解控制算法落地细节的读者。包内可循Keil工程结构梳理传感器数据处理、电机控制与参数整定思路也能借助说明文档和构建产物快速复现实验环境是自控初学者和课程实践者查漏补缺、拆解平衡小车控制系统的实用参考。1. 从一台摔倒在地的小车说起平衡小车练的是什么功夫给一台刚焊完的平衡小车上电十有八九是同一个画面轮子猛地一窜车体朝某一侧栽下去手还没摸到开关它已经躺平了。这一秒的失控正好覆盖了平衡小车全部的技术链路——从姿态传感器读到的角速度到定时器里跑的三环 PID再到驱动板给出的 PWM任何一环没对齐车就站不住。这个包里装的是一台两轮自平衡小车的完整工程源码主控是 STM32F103 一类的 Cortex-M3姿态靠六轴传感器驱动用双路 H 桥配编码器测速、OLED 显示和串口调参。它适合两类人刚学完单片机外设、想找个能把定时器、I2C、PWM、串口全串起来的综合练手项目的人以及已经能把车立起来但速度环一加就抖、转向一给就翻想看清楚串级 PID 到底怎么叠的人。下面按硬件、姿态、控制、排障、改造的顺序拆参数和代码都给到能直接抄的程度。2. 硬件组成与工程结构一台平衡小车由哪些模块拼起来平衡小车这类项目软件再漂亮也架不住硬件配错。我见过太多人卡在“代码明明照抄的就是站不起来”最后发现是电机驱动没接 STBY、编码器线序反了、或者电池电压一掉主控就复位。所以先把硬件最小集和工程目录理清楚后面调参才有意义。2.1 能跑起来的最小硬件集与引脚分配一台能立起来的车硬件上其实只要五样东西主控、姿态传感器、电机驱动、带编码器的减速电机、电源。剩下的 OLED、蓝牙、按键、蜂鸣器都是调试辅助可以先不焊但强烈建议焊——后面调参你会感谢自己。模块典型器件接口作用踩坑点主控STM32F103C8T6 最小系统—跑姿态解算与控制环用 8M 晶振别省姿态六轴陀螺加速度计I2C1PB6/PB7输出角速度与加速度模块要装水平别歪电机驱动双路 H 桥TIM 互补 PWM 4 根方向线驱动左右电机正反转STBY 必须拉高电机带霍尔编码器减速电机TIM 编码器模式提供位移反馈左右轮子镜像安装电源2 节锂电池串联 降压—给驱动和主控供电主控与电机共地引脚分配上有两个经验一是 PWM 尽量用同一个定时器的不同通道这样两路频率天然一致不会出现一边响一边不响二是编码器一定用定时器的硬件编码器模式不要用外部中断去数脉冲电机一转几千赫兹中断里光进光出就把 CPU 吃满了。我一般把左轮编码器接 TIM3、右轮接 TIM4PWM 用 TIM1 的 CH1/CH4方向线随便找两个普通 GPIO 就行。电机减速比和编码器线数决定了速度环的换算系数这个系数写错速度环的 P 就会显得“怎么调都不对”。计算方式很直白编码器线数 × 4四倍频× 减速比 输出轴转一圈的计数。/* motor.h —— 编码器脉冲到物理速度的换算改硬件只改这几个宏 */ #define ENC_LINE 13 /* 编码器每圈线数 */ #define GEAR_RATIO 30 /* 减速箱减速比 */ #define WHEEL_DIAMETER 65.0f /* 轮径单位 mm */ #define PI_F 3.1415926f /* 输出轴转一圈的计数13 * 4 * 30 1560 */ #define CNT_PER_ROUND (ENC_LINE * 4 * GEAR_RATIO) /* 每个计数对应的位移单位 mm */ #define MM_PER_CNT (PI_F * WHEEL_DIAMETER / CNT_PER_ROUND)这几行宏是整个工程里最该先确认的地方。ENC_LINE填错一倍速度环的反馈就整体缩一半表现出来就是“车往前跑得比设定快一倍”很多人第一反应是去调 P越调越乱。WHEEL_DIAMETER用卡尺量实际轮子别用商品页标的标称值差 3mm 在长距离行驶时就是明显的累积误差。2.2 工程目录与启动链路拿到源码包第一件事不是编译是看目录。平衡小车的工程结构基本是固定的分层硬件抽象一层、算法一层、应用一层。照着这个结构读代码比从头顺着 main 往下翻快得多。car/ ├── USER/ │ ├── main.c 上电初始化、主循环、1s 心跳 │ ├── stm32f10x_it.c 定时器中断、串口中断 │ └── system_stm32f10x.c 时钟树配置72MHz ├── HARDWARE/ │ ├── motor.c/.h PWM 输出、方向控制、编码器读取 │ ├── mpu6050.c/.h I2C 读写、原始数据换算 │ ├── oled.c/.h I2C 屏显调试时看角度和 PWM │ ├── encoder.c/.h 定时器编码器模式初始化 │ └── bluetooth.c/.h 串口收发手机端改参数 ├── CONTROL/ │ ├── attitude.c/.h 互补滤波输出可用角度 │ ├── balance.c/.h 直立环 PD │ ├── speed.c/.h 速度环 PI │ └── turn.c/.h 转向环 └── SYSTEM/ ├── delay.c/.h SysTick 延时 └── usart.c/.h 串口重定向 printf读代码的顺序建议是main.c看初始化顺序 →stm32f10x_it.c看中断里调了谁 →attitude.c→balance.c→speed.c。这条链路走通一遍你脑子里就有了一张“数据从传感器流到电机”的图剩下的边角代码都是外围。这里有个容易忽略的点初始化顺序不能乱。I2C、定时器、PWM、编码器全部初始化完之后才能打开定时器中断开始跑控制环。我曾经把TIM_Cmd(TIM4, ENABLE)写在传感器初始化前面结果中断一进来就去读 I2C而 I2C 还没配好直接卡死在 while 等待应答里现象是“上电什么都不动灯也不亮”查了一晚上。2.3 第一次编译、烧录与上电自检工程一般是标准库或 HAL 库的 Keil 工程也有少部分用 Makefile。用命令行的话烧录这一段最省事# 编译Keil 工程用 UV4 命令行或者直接用 arm-none-eabi 工具链 make clean make -j4 # 烧录st-flash 适合 ST-Linkopenocd 也行 st-flash write build/car.bin 0x08000000 # 串口看输出波特率跟 usart.c 里保持一致 minicom -D /dev/ttyUSB0 -b 115200烧完不要急着装车。上电前先做三步自检能省掉后面一半的排查时间第一步串口打印传感器 ID正常应返回固定值六轴芯片一般是 0x68。返回 0xFF 说明 I2C 没通先查上拉电阻和接线别往下走。第二步单独测电机。写个十行的测试函数让左轮正转 1 秒、反转 1 秒确认方向线逻辑和 PWM 都通。这时候如果轮子只嗡不转基本是 STBY 没拉高或者电池电压不够。第三步手动转轮子看串口打印的编码器计数是不是一个加一个减。同向装的两个轮子往前推车时一个计数为正一个为负这是正常的后面速度环里要把其中一个取反。这一步不做速度环必然发散。3. 姿态解算链路六轴原始数据到可用倾角控制环拿到手的不能是“传感器读数”得是“车体相对地面的角度”和“角速度”。这两个量决定了直立环稳不稳。这一段链路是整个项目里最容易出“玄学问题”的地方——角度看着挺正常车就是慢慢往一边倒往往就是解算里的某个细节没处理干净。3.1 传感器初始化与量程选择初始化要配四个东西电源管理、采样率分频、数字低通滤波、量程。量程选大了分辨率不够选小了一抖就饱和我一般用加速度 ±2g、陀螺 ±500°/s对平衡小车够用。/* mpu6050.c —— 上电初始化返回芯片 ID 用于自检 */ #define MPU_ADDR 0x68 #define REG_SMPLRT_DIV 0x19 #define REG_CONFIG 0x1A #define REG_GYRO_CFG 0x1B #define REG_ACCEL_CFG 0x1C #define REG_PWR_MGMT_1 0x6B #define REG_WHO_AM_I 0x75 uint8_t MPU_Init(void) { I2C_WriteByte(MPU_ADDR, REG_PWR_MGMT_1, 0x00); /* 解除休眠用内部时钟 */ I2C_WriteByte(MPU_ADDR, REG_SMPLRT_DIV, 0x04); /* 1k/(14) 200Hz 输出 */ I2C_WriteByte(MPU_ADDR, REG_CONFIG, 0x03); /* 低通 44Hz压电机振动 */ I2C_WriteByte(MPU_ADDR, REG_GYRO_CFG, 0x08); /* ±500dps65.5 LSB/(°/s) */ I2C_WriteByte(MPU_ADDR, REG_ACCEL_CFG, 0x00); /* ±2g16384 LSB/g */ return I2C_ReadByte(MPU_ADDR, REG_WHO_AM_I); /* 正常返回 0x68 */ }REG_CONFIG这个低通滤波值是新手最容易忽略的参数。电机一转车架上全是几十到几百赫兹的机械振动这些振动会原封不动进到陀螺仪数据里再被 D 项放大成 PWM 抖动。低通设到 44Hz 意味着 44Hz 以上的振动被压掉而车身真正的晃动频率在 15Hz完全不受影响。有人把低通设到 5Hz 想“更平滑”结果车身响应的相位滞后变大一推就振荡这是典型的用力过猛。REG_SMPLRT_DIV决定传感器内部输出率设成 200Hz 是给后面 5ms 一次的控制节拍留了余量——控制环 200Hz 读一次传感器 200Hz 出一次节奏刚好对上不会读到重复值。3.2 互补滤波陀螺积分与加速度观测的加权陀螺仪短期准、长期漂加速度计长期准、短期被振动干扰得厉害。互补滤波就是拿陀螺积分做主干用加速度算出的角度做慢速修正一行公式搞定算力开销几乎为零比卡尔曼好调太多。/* attitude.c —— 每 5ms 调用一次dt 固定 0.005s */ #define ALPHA 0.98f #define GYRO_LSB 65.5f /* ±500dps 时的灵敏度 */ #define RAD2DEG 57.29578f static float angle; /* 融合后的倾角单位度 */ static float gyro_bias; /* 上电静态标定出的零偏 */ void Attitude_Update(float ax, float az, float gyro_raw) { /* 加速度算观测角用 atan2 而不是 asin全角度范围内都不会退化 */ float acc_angle atan2f(ax, az) * RAD2DEG; /* 原始值先去零偏再换算成 °/s */ float gyro_rate (gyro_raw - gyro_bias) / GYRO_LSB; /* 陀螺积分做预测加速度观测做修正 */ angle ALPHA * (angle gyro_rate * 0.005f) (1.0f - ALPHA) * acc_angle; }ALPHA是这套滤波唯一的旋钮物理含义是“多大程度相信陀螺”。取 0.98 对应的时间常数大约 0.25 秒意味着加速度计的修正要花这么久才能把陀螺的漂移拉回来。调大到 0.995车对角度变化的响应更“跟手”但静止时会缓慢漂走调小到 0.9抗漂能力变强但加速度计的振动噪声会渗进来车身开始高频抖。我一般从 0.98 起步车能站住之后再微调。用atan2f而不是asin是个细节但很关键asin在接近 ±90° 时对输入误差极度敏感稍微超范围就返回 NaN车会瞬间输出乱值扑倒。atan2f在整个圆周上都稳定代价是多几个时钟周期完全值得。3.3 上电零偏标定与温漂处理陀螺仪静止时输出不是 0这是硬件特性不标定就等着车慢慢往一边走吧。标定要在上电后、开中断前做让车完全静止放在桌面上别有人碰。/* attitude.c —— 上电标定1 秒内采 500 次求均值 */ void Attitude_Calibrate(void) { int32_t sum 0; for (uint16_t i 0; i 500; i) { sum MPU_ReadGyroRaw(); delay_ms(2); /* 采 1 秒期间车必须静止 */ } gyro_bias (float)sum / 500.0f; }采 500 次而不是 10 次是因为单次读数的噪声在 ±3 LSB 左右平均 500 次能把噪声压到 0.15 LSB换算成角速度大约 0.002°/s这个精度足够支撑几分钟的漂移。采太多次也没意义1 秒已经够覆盖大部分噪声周期。温漂是另一回事。传感器跑十几分钟会发热零偏随温度缓慢变化表现是“刚上电车是稳的跑了一会儿开始慢慢往前跑”。正规做法是加温度补偿表但平衡小车这个场景没必要——速度环本身就在不断修正缓慢的位移偏差只要直立环调得够硬温漂会被速度环吃掉。真正要担心的是别把传感器贴在电机驱动芯片旁边那里是整个板子上最热的地方。4. 串级 PID 控制核心直立环、速度环、转向环怎么叠加三环结构是平衡小车的标准答案但“标准答案”不等于“照着写就能站”。三环之间的符号关系、输出限幅、调用频率每一个都是能让车原地画圈或者高频抖动的开关。这一章把三个环单独拆开讲最后讲怎么叠。4.1 直立环只有 PD没有 I直立环的任务是让角度误差归零本质是一个倒立摆的镇定问题。倒立摆需要的是“回复力 阻尼”对应 PD加 I 反而会引入慢速振荡所以这个环里坚决不要积分项。/* balance.c —— 5ms 调用一次 */ #define KP_BALANCE 380.0f #define KD_BALANCE 1.2f #define ANGLE_MID 0.5f /* 机械中值装配后必须实测重标 */ float Balance_Loop(float angle, float gyro_rate) { float err angle - ANGLE_MID; /* 相对机械中值的偏差 */ /* P 项给回复力D 项用角速度直接做阻尼比角度差分更干净 */ return KP_BALANCE * err KD_BALANCE * gyro_rate; }ANGLE_MID是这台车“理论上完全竖直”时传感器读到的角度因为传感器焊歪了一点、电池装偏了一点这个值几乎永远不是 0。标定方法很土但很准车能勉强站住之后让它静止串口读角度值读几十次取平均填进来。KD_BALANCE这里用的不是“角度误差的差分”而是直接拿陀螺仪角速度。这两种写法数学上等价但角速度是传感器的原始输出噪声特性明确、没有差分放大比(angle - last_angle) / dt干净一个数量级。这也是为什么很多人 D 项一调就抖——差分把量化噪声放大了。符号问题必须在这里说清楚陀螺仪读数方向要跟倾角变化方向一致。如果车往前倾时角度变大、但陀螺读数也是正那 PD 两项同号直接发散。验符号的办法是倾斜车体看串口打印的角度和角速度是不是同增同减不是就把陀螺读数取反。4.2 速度环PI 加输出限幅速度环负责“车该不该往前走”输出的是一个角度偏移量叠加到直立环的目标上——也就是说速度环不是直接给 PWM而是通过“让车稍微前倾一点”来产生前进的加速度。这个层级关系理解了速度环的参数就好调了。/* speed.c —— 100ms 调用一次 */ #define KP_SPEED 120.0f #define KI_SPEED 0.8f #define SPEED_INT_LIMIT 3000.0f static float speed_int; float Speed_Loop(int16_t enc_l, int16_t enc_r, float target_mmps) { /* 左右轮取平均做线速度注意其中一个轮子要取反镜像安装 */ float v (enc_l - enc_r) * 0.5f * MM_PER_CNT / 0.1f; /* mm/s周期 0.1s */ float err target_mmps - v; speed_int err; if (speed_int SPEED_INT_LIMIT) speed_int SPEED_INT_LIMIT; if (speed_int -SPEED_INT_LIMIT) speed_int -SPEED_INT_LIMIT; return KP_SPEED * err KI_SPEED * speed_int; }速度环必须加积分限幅这一点是血泪经验。车被手挡住的时候误差持续存在积分项一路累加等你松手积分已经堆到几千车会猛冲出去。限幅值取 PWM 满量程的一半左右比较合适具体看你的电机功率。速度环的频率比直立环低一个数量级5ms 和 100ms 的搭配是常见做法。为什么不能同频同频的话速度环的响应和直立环打架两个环互相激励表现是车在原地前后小幅高频晃动。10 倍频差让两个环解耦直立环管“这一瞬间站不站得住”速度环管“这一秒钟往哪走”。enc_l - enc_r还是enc_l enc_r取决于轮子是不是镜像装的。同向装的轮子用加镜像装的用减。这个符号搞错车会一边转圈一边前进看起来像“抽风”。4.3 三环叠加与输出限幅三个环的输出怎么合成两路 PWM是整个控制里最需要想清楚的一步。/* control.c —— 三环叠加输出给电机 */ #define PWM_MAX 7200 /* 定时器 ARR 的 90%留死区 */ static float speed_target, turn_target; void Control_Update(float angle, float gyro_y, int16_t enc_l, int16_t enc_r) { float out_balance Balance_Loop(angle, gyro_y); float out_speed Speed_Loop(enc_l, enc_r, speed_target); float out_turn Turn_Loop(turn_target); /* 直立 速度同向叠加转向左右反向叠加 */ float pwm_l out_balance out_speed out_turn; float pwm_r out_balance out_speed - out_turn; Motor_Set(LIMIT(pwm_l, PWM_MAX), LIMIT(pwm_r, PWM_MAX)); }叠加关系记住一句话直立环和速度环的输出加在轮子上是“共同前进/后退”的所以两轮同号转向环是“一正一反”所以两轮异号。这个结构决定了你可以单独调试——把turn_target设 0、speed_target设 0车只要站住两个环就都对了。PWM_MAX留 10% 余量是给转向环留空间。如果直立环已经把 PWM 打满转向环再叠加就被削平表现出来就是“直行能走一转弯就卡顿”。留余量之后转向才有调节余地。限幅函数本身也要注意不要用简单的if max return max那样在接近限幅时会有一小段非线性。正确做法是上下都限且限幅值取正负对称。4.4 控制节拍5ms 中断里的执行顺序所有控制都在一个定时器中断里跑dt 的稳定性直接决定积分和微分的准确性。中断里的执行顺序不是随便排的传感器读取得排最前面。/* stm32f10x_it.c —— TIM4 更新中断5ms 一次 */ void TIM4_IRQHandler(void) { static uint8_t div_cnt 0; if (TIM_GetITStatus(TIM4, TIM_IT_Update) ! RESET) { TIM_ClearITPendingBit(TIM4, TIM_IT_Update); MPU_ReadRaw(); /* I2C 读约 1ms先做 */ Attitude_Update(ax, az, gyro_raw); /* 姿态解算 */ Control_Update(angle, gyro_y, Enc_Read(TIM3), Enc_Read(TIM4)); /* 20 次中断 100ms跑一次速度环相关的外围逻辑 */ if (div_cnt 20) { div_cnt 0; Speed_Reset_If_Stopped(); /* 停车时清积分 */ OLED_Refresh(); /* 刷屏只在这里做 */ } } }执行顺序的理由传感器读最慢I2C 一次要 1ms 左右放最后会挤压控制计算的时间窗口姿态解算必须在控制之前因为控制要的是这一拍的角度OLED 刷屏一定要放到低频分支里I2C 刷一屏 128×64 要十几毫秒放 5ms 中断里直接把控制节拍撑爆。中断服务函数本身要短。所有printf、delay、浮点除法能挪就挪到主循环。我见过有人在中断里调printf打调试信息串口一堵中断出不来车直接失控——这种问题排查起来非常费劲因为现象是“偶尔抽一下”没有规律。5. 常见问题排查调了三天还是原地画圈的五种原因平衡小车的调试过程本质上是“用现象反推哪一环错了”。下面五条是我自己踩过、也帮别人定位过的典型问题每条都按“现象 → 原因 → 解决”写对照着看能省不少时间。5.1 现象一上电就朝固定方向猛冲这是新手最常见的第一个坎。车通电瞬间不看角度、直接满 PWM 朝一个方向冲出去撞墙为止。原因基本是符号错了。三种可能一是直立环的 P 项符号反了误差为正时输出也为正车一倾就被推得更倾正反馈二是陀螺仪方向反了D 项变成“加速倾斜”三是电机方向线接反了代码认为在往前推实际在往后拉。解决办法是分步验符号先把 Kp 设成一个很小的值比如 20Kd 设 0手扶着车倾斜看轮子转向是不是朝着“扶正”的方向。不对就把KP_BALANCE取负。P 对了之后再加 Kd同样手扶测试阻尼感应该是“推得快阻力大”。两步都对再把车放地上。5.2 现象能站住但一直缓慢往一个方向跑车立住了姿态也稳就是十几秒之后开始匀速往一边走越走越快最后撞东西。原因有两个层面。表层是速度环没生效或者积分太小车的位置偏差没有被修正。深层原因是姿态的机械中值ANGLE_MID标偏了——车以为自己“竖直”的时候其实是前倾两度于是持续输出一个小的前进分量。解决办法是先标中值再调速度环。让车勉强站住串口连续读角度手动把车调到“轮子完全不动”的位置此时的角度值就是真实中值填进ANGLE_MID。然后再开速度环Ki 从 0.1 开始往上加加到车被推动后能回到原位为止。速度环的 P 太大会让车前后点头I 太大会低频摆动。5.3 现象车身高频抖动、电机嗡嗡响车能站住但一直在高频震颤声音像蜜蜂手放上去能感到明显震动。原因通常是 D 项过大或者传感器低通没配。D 项放大的是角速度的高频成分电机振动、车架共振全都从这里进来。另一个可能是控制周期不稳中断被别的高优先级任务打断dt 忽长忽短微分项算出来的值乱跳。解决办法先把KD_BALANCE砍一半看抖动是否消失消失说明就是 D 太大。如果砍完站不住说明传感器数据本身脏把REG_CONFIG的低通从 0x03 再往下调比如 0x04 对应 21Hz。还不行就查中断里有没有耗时操作把 OLED 刷新和串口打印全挪出去。5.4 现象接上蓝牙模块就复位或者屏幕花屏不接蓝牙一切正常接上就时不时重启或者 OLED 显示乱码。原因是供电和总线冲突。蓝牙模块发射瞬间电流能到几十毫安从主控的 3.3V 稳压上取电会把电压拉低触发欠压复位。另一个可能是模块和 OLED 抢同一个 I2C 地址或者共用了同一路串口。解决办法是给蓝牙单独一路 LDO或者在电源脚并一个 100μF 的电解电容吸收瞬时电流。I2C 地址冲突就换一路 I2C或者改成软件模拟 I2C 挂在其他引脚上。串口冲突的话把蓝牙挂到 USART2调试打印留在 USART1。5.5 现象换了块电池参数全变了同一台车一块电池满电时站得稳稳的换一块电压低一点的就站不住或者抖动明显。原因是 PWM 的有效增益跟电池电压正相关。同样的占空比8.4V 时电机得到的平均电压比 7.0V 时高 20%等于直立环的等效 P 变大了 20%本来调好的参数就越界了。解决办法有两个方向讲究一点的做法是在控制输出上做电压前馈补偿实时采电池电压把 PWM 值按V_ref / V_bat缩放省事的做法是把电池用到 7.4V 就换别等掉到 7.0V 以下并且调参时用一块中等电压的电池做基准。我一般两个都做前馈补偿也就十来行代码能省掉很多“昨天还好好的今天怎么不行”的困惑。6. 二次开发改造把平衡车当底盘往上叠功能车能稳稳站住之后这个工程的价值才刚开始体现——它本质上是一个“能精确控制位置和朝向的两轮底盘”往上加东西的空间很大。下面几个方向是我实际做过或者见人做成的按改动量从小到大排。改造方向主要改动点风险与注意上位机波形调参串口按固定帧格式发角度/PWM/速度电脑端画曲线帧率别超过 50Hz否则串口带宽不够蓝牙遥控解析手机发来的速度/转向指令替换speed_target指令要做超时保护断连自动停车里程计与航向估计编码器积分算位移陀螺积分算航向角航向角会漂长距离要配地磁或视觉避障加超声波/红外检测到障碍时把speed_target置 0避障逻辑要放在速度环外层别塞进中断视觉跟随摄像头模块串口输出目标框中心映射成转向量帧率低转向环要做低通最值得先做的是串口波形。调试三环的时候光看数字根本判断不出是振荡还是漂移把角度、目标角度、PWM 三路数据打成 CSV 发到电脑上画出来一眼就能看出相位关系和超调量。帧格式我一般用最简单的二进制帧帧头 0xAA 0x55 三个 int16 校验和解析代码二十行搞定。/* debug.c —— 每 20ms 发一帧主循环里调用不要放中断 */ void Debug_Send(float angle, float target, int16_t pwm) { uint8_t buf[10]; buf[0] 0xAA; buf[1] 0x55; int16_t a (int16_t)(angle * 100); /* 放大 100 倍保两位小数 */ int16_t t (int16_t)(target * 100); buf[2] a 8; buf[3] a 0xFF; buf[4] t 8; buf[5] t 0xFF; buf[6] pwm 8; buf[7] pwm 0xFF; buf[8] buf[2] ^ buf[3] ^ buf[4] ^ buf[5] ^ buf[6] ^ buf[7]; /* 异或校验 */ buf[9] 0x0D; USART_SendArray(USART1, buf, 10); }这段代码的要点是“放大 100 倍转整型”浮点直接按字节拆会有精度和对齐问题转成int16覆盖 ±327 度、±327 的 PWM够用且不会溢出。校验用异或足够平衡小车的串口环境很干净不需要 CRC。发送放在主循环里按 20ms 调度绝不放进 5ms 中断——串口发送是阻塞的一卡就把控制节拍拖垮。转向环的参数整定有个小技巧先把turn_target设成一个阶跃值看车原地转 90 度需要多久、有没有超调。理想状态是 1 秒左右转完、不来回摆。转向环本质也是个 PD用的是陀螺仪的 Z 轴角速度做阻尼和直立环是同一套思路参数整定顺序也一样先 P 后 D。最后说个我自己的教训。刚做这个项目的时候我习惯“改一个参数、烧一次、看一次”一次调参要烧十几次固件一晚上过去车还在原地抖。后来强制自己先把所有参数做成可以从串口在线改的变量加了set kp 380这样的命令解析调参效率直接翻几倍。从那以后我每次做控制类项目第一件事都是把参数通道和波形通道搭好再动手调——这个习惯比任何调参口诀都管用。希望帮到你。本文还有配套的精品资源点击获取

读完文章,也想定制专属网站?

尧图设计师 24 小时内与您沟通定制方案

免费获取报价 →
↑