ARTICLE DETAIL

资讯详情

深耕网站视觉设计与运营推广的一线实战洞察。

基于STM32的平衡车串级PID控制:从MPU6050姿态解调到三环调参

基于STM32的平衡车串级PID控制:从MPU6050姿态解调到三环调参 简介这是一份基于STM32单片机的两轮自平衡车项目面向嵌入式开发者和控制算法学习者解决如何通过串级PID算法实现车体平衡与多模式运动控制的问题。压缩包共714个文件容量约25.25MB以C源文件398个、头文件183个为主辅以汇编启动文件、ICF链接脚本、lib/a库文件、Keil工程文件及STM32CubeMX配置便于在MDK环境下直接编译与二次开发。项目采用串级PID控制框架分为直立环、速度环和转向环支持蓝牙遥控、巡线和避障模式遥控下可实现前进、后退、转弯巡线时自动循迹避障时实时感知绕行同时利用ADC实时监测电池电压低电量时预警或自动保护提升安全性与续航可靠性。资源内含完整工程源码、CubeMX初始化配置、构建脚本和说明文档适合自动化、电子信息等专业学生和嵌入式爱好者学习电机驱动、姿态反馈控制与PID参数整定并在此基础上扩展自己的平衡车应用。已有551人学习浏览能对上手难度和实用价值提供一定参考。1. 两轮平衡车为什么绕不开串级 PID把两轮平衡车从桌面拿下来放到地上第一次上电大多数人会盯着它思考同一个问题电机已经通电车为什么还是瞬间趴下答案很简单——两轮车是一个天然不稳定的倒立摆系统它的重心在轮轴上方没有任何外力干预时倾角只会按指数规律发散。想让车立住就必须在倾角还没发散到不可挽救之前用电机把车轮送到底座下方去。问题在于角度偏差和车轮速度之间并不是一个简单的比例关系单环 PID 只处理角度误差速度一旦被扰动角度误差会持续累积最终仍然是倒。串级 PID 的思路是把控制拆成内外两层内环以毫秒级周期压制角度发散外环用稍慢的速度把车速拉回目标值再由目标角度去修正内环。这个工程基于 STM32F4 的 HAL 库姿态数据来自 MPU6050 的 DMP 输出三环分别是直立环、速度环、转向环外加蓝牙遥控、巡线、避障三种模式和 ADC 电池电压监测。适合正准备做平衡车毕业设计、电赛题或者已经把单环 PID 调过一遍但速度一加就倒的人。2. 姿态数据链路MPU6050 DMP 读出与互补滤波兜底2.1 硬件接口与资源分配MPU6050 是本项目的姿态传感器内部集成了三轴陀螺仪和三轴加速度计。陀螺仪积分得到角度但积分会漂移加速度计能直接测出重力方向但动态振动时噪声大。DMPDigital Motion Processor是 MPU6050 内部的一个硬件解算单元它把四元数计算、姿态融合这些从主控 MCU 里拿走了STM32 只需要通过 I2C 读取解算结果省下的算力足够跑 PID 和模式逻辑。常见外设分配方式如下表。注意这只是普遍做法具体引脚要对照你自己板子的原理图。外设常用引脚/资源用途说明I2C1PB8(SCL) / PB9(SDA)与 MPU6050 通信400kHz 快速模式EXTI0PA0MPU6050 INT 引脚姿态数据就绪时触发中断TIM2编码器模式左轮编码器测速计数模式需配置为 Encoder ModeTIM3编码器模式右轮编码器测速左右轮测速通道要分开TIM1CH1/CH2左电机 PWM 输出高级定时器带刹车输入更安全TIM8CH1/CH2右电机 PWM 输出互补输出可驱动 H 桥I2C 总线上的上拉电阻一般用 4.7kΩ。MPU6050 的 AD0 引脚接地器件地址是 0x68如果接高电平地址变成 0x69。这块在初始化时很容易踩坑I2C 扫描不到设备多半是 AD0 电平没对上。INT 引脚用边沿触发数据就绪后主控再去读 FIFO比轮询寄存器状态省时间也给 1ms 直立环控制留出裕量。2.2 DMP 初始化和 FIFO 数据读取DMP 初始化流程在工程里一般是独立函数HAL 库工程在MX_I2C1_Init()之后调用。核心步骤如下// mpu_dmp_init() 精简说明加粗位置是决定数据能否出来的关键 uint8_t mpu_dmp_init(void) { uint8_t ret; mpu_init(); // 复位、唤醒 MPU6050默认 I2C 地址 0x68 ret mpu_set_sensors(INV_XYZ_GYRO | INV_XYZ_ACCEL); if (ret) return 1; // 传感器使能失败 ret mpu_configure_fifo(INV_XYZ_GYRO | INV_XYZ_ACCEL); if (ret) return 2; // DMP 输出通过 FIFO 传递不配就取不到数据 ret dmp_load_motion_driver_firmware(); if (ret) return 3; // 把 DMP 固件下载到 MPU6050 内部 RAM ret dmp_set_fifo_rate(100); // 100Hz 姿态输出对应 10ms 周期 if (ret) return 4; ret dmp_enable_feature(DMP_FEATURE_6X_LP_QUAT | DMP_FEATURE_TAP | DMP_FEATURE_ANDROID_ORIENT | DMP_FEATURE_SEND_RAW_ACCEL | DMP_FEATURE_GYRO_CAL); if (ret) return 5; // 设置 DMP 中断常配合 EXTI 使用 mpu_set_int_level(0); // 低电平有效 mpu_set_int_enable(1); // INT 引脚使能 return 0; }配置完成之后在手柄控制函数里读取 FIFO// 中断中置位标志位主循环或定时器回调里去取数 short gyro[3], accel[3]; long quat[4]; unsigned long timestamp; short sensors; unsigned char more; ret dmp_read_fifo(gyro, accel, quat, timestamp, sensors, more); if (ret ! 0) return; // FIFO 读取出错直接丢弃本次数据 // 四元数转欧拉角侧倾角 roll 用于直立环 pitch atan2(-2 * (quat[1]*quat[3] - quat[0]*quat[2]), 2 * (quat[0]*quat[0] quat[3]*quat[3]) - 1) * 57.29578f;dmp_read_fifo一次返回陀螺仪原始值、加速度原始值和四元数。四元数是long型需要除以1 30才能得到浮点四元数但上面角度换算里已经包含这个缩放直接用即可。more参数表示 FIFO 里还有数据如果一直为 1说明主控读取速度跟不上 100Hz 输出要检查中断优先级或 I2C 速度。2.3 DMP 失效时的互补滤波方案DMP 并不是永远可用某些国产 MPU6050 模块的固件版本和官方 DMP 库不匹配或者你移植时 Flash 空间紧张没有加入 DMP 驱动此时可以用互补滤波做姿态解算。互补滤波的核心思想加速度计得到的角度在低频段可信陀螺仪积分得到的角度在高频段更平滑两者用权重系数融合。// 滚转角 atan2(加速度Z, 加速度X)y 轴朝前进方向时适用 float acc_angle atan2f(accel[0], accel[2]) * 57.29578f; // 陀螺仪积分,gyro[1] 是绕 y 轴的角速度 gyro_angle gyro[1] * dt; // dt 是两次采样间隔单位秒 // 互补融合alpha 一般取 0.95 到 0.98 float angle alpha * (angle gyro[1] * dt) (1.0f - alpha) * acc_angle;alpha越接近 1陀螺仪权重越大动态性能好但会引入积分漂移alpha越小加速度计权重越大姿态越不容易漂但噪声会传进直立环。平衡车应用里我一般先用 0.97 起调车体出现高频抖动再降一点。互补滤波的相位滞后比卡尔曼小整定也更直观对 F1 或 C51 这类算力紧张的 MCU 特别合适。注意互补滤波输出前要加一个低通滤波把 H 桥 PWM 引入的机械振动滤掉否则直立环会放大振动。3. 串级 PID 三环拆解直立、速度、转向的环间时间间隔3.1 内外环的作用与时间间隔串级 PID 在平衡车上最容易被误解的是三个 PID 串在一起跑。实际上三个环的执行频率完全不同地位也不对等。直立环是内环任务是让车体倾角尽快归零必须跑得最快速度环是外环输出的是目标倾角偏置让车体稍微前倾或后仰来获得前进动力转向环与前两个环解耦只负责左右轮差速。这三者的理想时间间隔是 10 倍关系。我在工程里使用的是直立环 2ms500Hz速度环 20ms50Hz转向环 20ms。这个节奏可以从两个角度理解。从采样定理角度直立环的频率响应需要在 10Hz 以上才能跟上人体晃动和地面扰动从串级控制角度外环的采样周期应该是内环的 5 到 10 倍否则外环回来的信号在内环眼里已经严重滞后会出现车体还在纠正角度、速度指令又把它推回去这种互相打架的现象。无人机上常见 4kHz 内环与 400Hz 外环的组合也是在维持这个比例关系。时间间隔不需要精确到微秒但比例不要打破这是整个串级结构稳定的前提。3.2 三环 PID 的代码实现下面是一段精简后的control.c展示三环如何关联。实际工程里这段代码放在 2ms 定时器中断回调中执行。// control.c —— 串级 PID 三环核心逻辑2ms 周期 void balance_control(void) { // 1. 直立环内环 // 目标角度由速度环输出决定初始为 0 float angle_target 0.0f; // 2. 速度环外环20ms 周期执行一次 // encoder_speed 单位 cm/starget_speed 由遥控或巡线逻辑给定 if (speed_tick 10) // 每 10 次直立环回调 20ms { float speed_err target_speed - encoder_speed; speed_tick 0; // 增量式 PI积分项做限幅防止长时间停车时积分饱和 pid_speed.integral pid_speed.Ki * speed_err; pid_speed.integral constrain(pid_speed.integral, -3.0f, 3.0f); // 单位改为角度 angle_target pid_speed.Kp * speed_err pid_speed.integral; angle_target constrain(angle_target, -5.0f, 5.0f); } // 3. 直立环误差目标角度减去实际俯仰角 pitch float angle_err angle_target - pitch; // pitch 前倾为正 // P 作用在角度误差上D 作用在陀螺仪角速度上 float upright_pwm pid_upright.Kp * angle_err - pid_upright.Kd * gyro_y; // 4. 转向环20ms 周期执行 // yaw_err 为目标转向率与左右轮速度差之差 float yaw_err target_turn_rate - (speed_left - speed_right); float turn_pwm pid_turn.Kp * yaw_err; turn_pwm constrain(turn_pwm, -50.0f, 50.0f); // 5. 合成 PWM直立环输出给两轮公共量转向环输出做差动量 int pwm_left (int)upright_pwm (int)turn_pwm; int pwm_right (int)upright_pwm - (int)turn_pwm; // 电机驱动占空比限幅通常 MOTOR_PWM_MAX 取 900对应 12 位定时器 pwm_left constrain(pwm_left, -MOTOR_PWM_MAX, MOTOR_PWM_MAX); pwm_right constrain(pwm_right, -MOTOR_PWM_MAX, MOTOR_PWM_MAX); set_motor_duty(pwm_left, pwm_right); }这段代码的关键在串级结构直立环的目标角度不是常量 0而是被速度环输出修改的angle_target。速度环检测到车速低于目标值就让angle_target增大车身前倾加速超过目标值就后仰减速。直立环始终在为一个动态变化的目标角度服务这才是串级而不是三个 PID 各干各的。转向环的极性最容易出错如果电机安装方向与编码器计数方向相反你会发现遥控车辆时打转而且车越转越快。解决方法是先断开直立环只给转向指令观察实际转动方向反了就把turn_pwm的正负号反过来。速度环的Ki不要一开始就加上去先只调Kp能把车体控制在一个前倾角度让车缓慢前进再加积分消除稳态误差。3.3 输出限幅与实际选参三个环的输出限幅是串级结构稳定性的第二道保险。直立环输出直接驱动电机限幅不能太小否则遇到台阶或撞击时输出饱和车根本拉不回来速度环输出是角度偏置限幅一般控制在正负 5 度以内超过这个范围车体倾角过大直立环即使满输出也救不回来。转向环输出是差动量限幅 50 左右足够因为转向只需要微小的左右轮速差。下表是一组可用的初值参考注意这是给你起调的起点不是最终参数。电机不同、车身重心不同参数会差一倍以上。控制环KpKiKd输出限幅执行周期直立环250 ~ 40005 ~ 15±9002ms速度环40 ~ 600.2 ~ 0.50±5°20ms转向环30 ~ 5000±5020ms直立环的 D 项直接作用在陀螺仪角速度上而不是对角度求导。这样做的好处是角速度信号本身比角度微分更平滑不会放大 DMP 输出的量化噪声。F4 带 FPU浮点运算在 2ms 周期里开销可忽略这个工程没有依赖 DSP 数学库也能跑得动DSP 库在这里主要给巡线传感器滤波和后续扩展预留。4. 蓝牙遥控、巡线与避障用状态机管理三种运行模式4.1 模式切换状态机三种模式本质上只做一件事决定target_speed和target_turn_rate。底层串级 PID 不需要知道当前是哪种模式它只消费这两个目标值。工程里用枚举定义模式模式选择来自蓝牙串口或者按键。这个设计思路和常见项目里每模式写一套控制逻辑的区别在于控制核心只有一套换模式只是换目标值来源代码可维护性高很多。// mode_switch.h —— 模式枚举与切换 typedef enum { MODE_BT, // 蓝牙遥控 MODE_LINE, // 巡线 MODE_AVOID // 避障 } run_mode_t; run_mode_t g_mode MODE_BT; // 串口中断接收蓝牙数据单字节指令避免粘包 void on_uart_byte(uint8_t cmd) { switch (cmd) { case A: g_mode MODE_BT; break; case B: g_mode MODE_LINE; break; case C: g_mode MODE_AVOID; break; // 蓝牙遥控指令 case F: g_mode MODE_BT; target_speed 30; break; case S: g_mode MODE_BT; target_speed 0; break; case L: target_turn_rate 20; break; case R: target_turn_rate -20; break; default: break; } }蓝牙模块常见选 HC-05、HC-06 或 JDY-31工作在透传模式波特率 115200。串口中断收到单字节命令后直接执行不需要缓存字符串。每次收到前进或停止指令时要把target_speed和target_turn_rate同时更新否则会出现前进中按下转向松手后车还在转的现象。目标值的更新和 PID 周期是异步的但速度环输出有限幅即使目标值突变也不会导致车身猛冲这是串级结构天然的安全优势。蓝牙遥控模式还有一个容易被忽略的问题手机蓝牙发送的是字符串指令而atoi解析和字符串比较在中断里做会拖长中断时间。用单字符协议最稳一个字节对应一个动作中断处理完成时间可以控制在几微秒内。4.2 巡线灰度传感器权重法巡线用 5 路灰度传感器比较常见常见模块是 TCRT5000 红外对管。5 路传感器沿车头横向排开输出的是数字信号黑线为低或高取决于模块设计。位置计算采用权重法中间传感器权重为 0左右分别为正负 1 和正负 2把点亮位权重求和得到偏差值pos。// line_follow.c —— 五路灰度传感器权重法 static const int8_t line_weight[5] {-2, -1, 0, 1, 2}; void line_follow(void) { uint8_t sensor_bits read_line_sensors(); // 位0到位4对应五路输出 int8_t pos 0; for (int i 0; i 5; i) { if (sensor_bits (1 i)) { pos line_weight[i]; } } if (pos 0) { // 没检测到黑线按上次方向回扫防止冲出赛道 if (last_direction 0) target_turn_rate -30; else target_turn_rate 30; } else { target_turn_rate pid_line.Kp * pos; target_speed 25; // cm/s过弯时可以让速度与偏差联动 last_direction pos; } }权重法的好处是输出连续可以直接喂给转向环。转弯时如果发现车体往外漂不是去调pid_line.Kp而是要么降低target_speed要么在弯道入口做速度前馈。许多巡线车越调越不稳原因是只在直道上调参数弯道上车速太高导致传感器丢线。传感器安装高度也有讲究离地 1cm 左右灵敏度最稳定太高反射光太弱、太低模块容易刮到地面。4.3 避障超声波测距与找宽路策略避障模式使用超声波模块常用 HC-SR04测量车头方向障碍物距离。控制策略不复杂如果前方 25cm 内有障碍降低目标速度到 0然后扫描左右两侧距离选择空旷的一侧转向。// hcsr04.c —— 超声波距离测量 uint32_t hcsr04_read_cm(void) { // 触发信号至少 10us 高电平 HAL_GPIO_WritePin(TRIG_GPIO_Port, TRIG_Pin, GPIO_PIN_SET); delay_us(12); HAL_GPIO_WritePin(TRIG_GPIO_Port, TRIG_Pin, GPIO_PIN_RESET); // 等待 ECHO 引脚拉高并计时 while (HAL_GPIO_ReadPin(ECHO_GPIO_Port, ECHO_Pin) GPIO_PIN_RESET); uint32_t t_start DWT-CYCCNT; // 使用 DWT 时钟计数器精度 1us 以内 while (HAL_GPIO_ReadPin(ECHO_GPIO_Port, ECHO_Pin) GPIO_PIN_SET); uint32_t t_end DWT-CYCCNT; uint32_t dt (t_end - t_start) / (SystemCoreClock / 1000000); return dt / 58; // 声速 340m/s来回距离折算成 cm }DWT-CYCCNT 是 Cortex-M4 内核提供的周期计数器通过CoreDebug-DEMCR使能后可以使用精度远高于软件延时测量。避障的动作应该是先停判定、再转向直接带速度转向会让超声波模块跟着车体一起转测得的距离就是歪的。我一般会让车先停车 100ms车体稳定后再做左右扫描。扫描通过让转向环输出一个固定转向率持续 300ms 来实现扫描结束后把目标转向率设为对应方向。避障模式里还有一个隐含问题超声波模块安装在车头时车体在直立状态下的俯仰角变化会导致测距指向变化。车体前倾加速时超声波的测量方向是朝下的可能把地面误判成障碍物。处理办法是避障模式下速度环的angle_target限幅收紧到正负 3 度尽量减少车体俯仰动作带来的测量角度偏移。5. 电池电压监测ADC 分压采样与低电量停保5.1 分压网络设计平衡车用 2S 锂电池满电 8.4V截止放电电压一般 7.4V 左右。STM32 的 ADC 输入范围是 0 到 Vref常用 3.3V8.4V 不能直接进引脚必须用电阻分压。分压原则满电时分压后的电压不超过 3.3V留出至少 10% 余量同时分压电阻阻值不能太大否则 ADC 采样时输入阻抗会拉低电压造成读数偏低。我用 R147kΩ、R220kΩ 的组合分压比是(R1R2)/R2 3.358.4V 时分压输出约 2.51V3.3V 参考下接近 76% 满量程。分压点并联一个 100nF 电容和电阻一起形成低通滤波滤掉电机换向产生的电压尖峰。下表是该配置下几个关键电压点对应的 ADC 读数12 位分辨率。电池电压分压点电压ADC 读数系统行为8.40V2.51V3117满电正常7.80V2.33V2892提示电量低7.40V2.21V2747进入保护限制速度7.20V2.15V2670停车并锁定5.2 ADC 采样与滤波工程配置 ADC1 的 IN1 通道PA1 引脚采样时间选最大的 480 周期这是为了给 ADC 内部采样电容充分的充电时间。多通道采样时还要注意通道注入顺序但本项目只采一路不需要考虑。// battery.c —— 电压采样与计算 #define R1_RATIO (47.0f 20.0f) / 20.0f // 分压比 3.35 #define VREF 3.3f #define ADC_RES 4095.0f float battery_voltage(void) { uint32_t sum 0; // 连续采 8 次取平均等效一个移动平均滤波器 for (int i 0; i 8; i) { HAL_ADC_Start(hadc1); HAL_ADC_PollForConversion(hadc1, 10); sum HAL_ADC_GetValue(hadc1); HAL_ADC_Stop(hadc1); } float adc_v (sum 3) / ADC_RES * VREF; return adc_v * R1_RATIO; }sum 3是除以 8 的移位写法等效平均。这里每采一次就HAL_ADC_Stop是为了防止连续转换模式下 DMA 缓冲溢出后读数错位。实际电压计算时要考虑 Vref 的精度板载 LDO 输出的 3.3V 通常有正负 2% 的误差用万用表实测一次 Vref 电压把VREF宏改成实测值比在代码里做软件校准更直接。低电量保护不能只在主循环里查询因为车体控制是中断驱动的。我在 2ms 控制中断里每 500ms 读一次电池电压使用时间片计数而非阻塞延时。// 在控制中断里做低电量保护 if (battery_tick 250) { // 2ms × 250 500ms battery_tick 0; float vbat battery_voltage(); if (vbat 7.2f) { target_speed 0; target_turn_rate 0; motor_lock 1; // 停车后低电平锁定电机驱动 } else if (vbat 7.4f) { target_speed 15; // 降速运行只保留基本平衡能力 } }三个阈值的动作是有层次的7.4V 只是限制最高速度7.2V 才停车。不要在 7.8V 就强制停车锂电池从 7.8V 掉到 7.2V 很快但如果车正在过马路或下坡立即锁电机比电量耗尽更危险。停车保护动作前要先让速度环把车体姿态回正也就是让target_speed慢慢减到 0而不是直接把 PWM 置 0否则车会因为没有直立输出而瞬间倒地。5.3 电源噪声对 ADC 的影响电机是大电流负载启动瞬间电流可达 1A 以上电池电压会被拉低这个跌落会直接反映在 ADC 读数上。因此低电量判断要在电机占空比接近 0 的时刻采样或者对连续两次读数做取小值处理如果在两个 500ms 周期里电压都低于阈值才触发保护。单纯取平均值会把一次电压跌落抹平导致保护滞后。另外STM32 的 Vref 引脚某些封装是 VDDA必须接干净的 3.3V。如果板子上 VDDA 和 VDD 共用一颗 LDO电机会从同一路取电那 ADC 参考电压本身就会波动再好的滤波算法也救不回来。这是硬件设计阶段就要定下来的事情电机电源和模拟电源要分开走线至少在电池端共地而不是在芯片引脚附近共地。6. 调参实战让串级 PID 先站稳再谈性能6.1 调参现象对照表串级 PID 的调参顺序不能反过来先调直立环再调速度环最后调转向环。直立环调不稳之前速度环和转向环任何参数都没有意义。下表是一线调车时最常见的现象与对策。现象可能原因调整动作车体高频抖动电机嗡嗡响直立环 P 偏大或 Kd 偏大P 和 Kd 同时减 20%再观察低频左右摇摆周期约 1 到 2 秒直立环 P 偏小或角速度信号延时加大 P检查 I2C 读取周期车体朝一个方向加速冲出去速度环极性反或速度环比例过大先断开速度环单独测试极性起步猛减速时点头速度环 Ki 过大Ki 减半同时检查积分限幅前进时自动偏航转向环零点漂移停车时读一次左右轮编码器差值做零点校准直立环调参耗时最长我的做法是先固定Kp 200、Kd 0如果车体绕平衡点抖动但能回正就保持Kp每次加 5 到 10 的Kd让抖动衰减变快。Kd加过头会出现新的高频尖叫这时把Kd回调 30%。跑完一轮 抖 → 回正 → 尖叫 之后在 Keil 的 Watch 窗口里记录这组能站稳的 P/D 值后续速度环和转向环都以这组参数为锚点。6.2 用 Keil Debug 观察环周期调参时不要靠肉眼判断抖不抖要看变量。工程用 Keil MDK 调试时把断点设在balance_control()入口然后在 Watch 窗口添加pitch、angle_err、upright_pwm。如果pitch波形在正负 1 度以内震荡但upright_pwm已经接近限幅说明直立环 Kp/Kd 比值不对输出大部分用来对抗自己的振荡而不是真实的倾角修正。这时要降低 Kp、加大 Kd。双目看波形可以用逻辑分析仪但更简单的做法是通过串口把pitch和upright_pwm打出来200ms 一条用 VOFA 上位机自带的波形图功能画曲线。串口发送函数要放在中断外避免在控制中断里阻塞。// debug_print.c —— 串口输出关键变量 void debug_send_state(void) { char buf[64]; snprintf(buf, sizeof(buf), %.2f %.2f %.2f\r\n, pitch, angle_err, upright_pwm); HAL_UART_Transmit(huart1, (uint8_t *)buf, strlen(buf), 100); }发送频率不要跟控制周期一样高不然串口会占掉大量时间。50Hz 足够看到振荡趋势。注意 Keil 调试时如果开了-O2优化Watch 窗口看到的变量值可能不是你写入的值因为部分变量被优化成了寄存器中间量。调参期间把优化等级固定为-O0参数确认后再改回优化并做一次回归测试。6.3 参数落 Flash 与工程归档调好的参数要固化不能每次上电都重新输入。用内部 Flash 的最后一个扇区例如 0x0800F800STM32F407 的 11 扇区起始地址来存设置项。写入前先读标志位没有标志说明是首次上电使用默认参数有标志就加载配置。// pid_param.c —— 参数存储核心 #define PARAM_FLASH_ADDR 0x0800F800 #define PARAM_MAGIC 0xA5A5A5A5 typedef struct { uint32_t magic; float upright_kp; float upright_kd; float speed_kp; float speed_ki; float turn_kp; float turn_kd; } pid_param_t; void pid_param_save(pid_param_t *param) { FLASH_Unlock(); FLASH_EraseSector(FLASH_Sector_11, VoltageRange_3); param-magic PARAM_MAGIC; // 按 4 字节对齐写入注意先写后校验 for (uint32_t addr 0; addr sizeof(pid_param_t); addr 4) { FLASH_ProgramWord(PARAM_FLASH_ADDR addr, *(uint32_t *)((uint8_t *)param addr)); } FLASH_Lock(); }Flash 写入前必须擦除整个扇区F4 的扇区擦除粒度是 16KB 或 32KB所以参数结构体尽量设计在 16KB 以内即可。写入函数最后要再读回来做一次校验防止写入中断导致参数损坏。调参成功的最后一步把keilkill.bat先跑一遍清理工程中间文件再把.uvprojx、Core、Drivers和param.h一起归档这样换电脑重开工程时不会因为绝对路径问题报一堆头文件找不到的错。最后一个经验直立环参数调到车身轻微发抖但能自己回正这个临界点就是你第一个稳定的调参锚点。大多数人会忍不住继续加大 Kp 追求更硬结果陷入高频振荡车突然摔倒的循环。记住那个临界点的 P/D 值后续所有速度环和转向环的调试都放在这个稳定底板上进行。本文还有配套的精品资源点击获取
返回列表