用STM32F411和CLion从零搭建三轮全向小车:PID调参、VOFA+上位机调试全记录
用STM32F411和CLion从零搭建三轮全向小车:PID调参、VOFA+上位机调试全记录
第一次接触全向轮机器人时,我被它灵活的运动方式深深吸引——不同于传统轮式机器人,它能实现任意方向的平移和旋转。这种独特的移动能力在狭小空间作业、仓储物流等领域有着巨大潜力。本文将详细记录我使用STM32F411CEU6开发板、CLion开发环境和VOFA+调试工具,从零构建三轮全向小车的完整过程。
1. 硬件选型与基础环境搭建
选择STM32F411CEU6作为主控芯片主要基于三点考虑:首先,它具备足够的定时器资源(共11个定时器)来同时控制三个电机;其次,72MHz的主频完全能满足实时控制需求;最重要的是,它支持硬件浮点运算单元(FPU),这对PID算法的实时计算至关重要。
核心硬件清单:
- 电机:带编码器的直流减速电机(360线编码器,减速比1:74.8)
- 驱动模块:TB6612四路电机驱动
- 全向轮:直径70mm的塑料材质麦克纳姆轮
- 姿态传感器:IM600(用于获取小车航向角)
- 电源:3S锂电池(11.1V,2200mAh)
开发环境采用CLion+STM32CubeMX的组合。CLion提供了优秀的代码导航和自动补全功能,而CubeMX则极大简化了外设配置过程。特别提醒:在CubeMX中配置定时器时,务必注意以下几点:
// PWM输出配置示例(TIM2 Channel2) htim2.Instance = TIM2; htim2.Init.Prescaler = 71; // 72MHz/(71+1)=1MHz htim2.Init.CounterMode = TIM_COUNTERMODE_UP; htim2.Init.Period = 9; // 1MHz/(9+1)=100kHz htim2.Init.ClockDivision = TIM_CLOCKDIVISION_DIV1;编码器接口配置更为关键,需要设置为编码器模式:
// 编码器模式配置(TIM1) sEncoderConfig.EncoderMode = TIM_ENCODERMODE_TI12; sEncoderConfig.IC1Polarity = TIM_ICPOLARITY_RISING; sEncoderConfig.IC1Selection = TIM_ICSELECTION_DIRECTTI; sEncoderConfig.IC1Prescaler = TIM_ICPSC_DIV1; sEncoderConfig.IC1Filter = 0; // Channel2配置同理2. 电机速度环PID控制实战
速度控制是底盘运动的基础。我采用增量式PID算法,主要考虑到它对计算资源的占用较少,且不易产生积分饱和问题。实际调试中发现几个关键点:
采样周期选择:通过实验对比,1ms的速度采样周期(编码器读数)配合10ms的控制周期取得了最佳效果。过短的采样周期会导致噪声放大,而过长则会影响响应速度。
死区处理:当误差小于0.1rpm时直接归零,避免电机"抖动":
if(pid->err < 0.1 && pid->err > -0.1) pid->err = 0;- 积分限幅:这是防止"windup"现象的关键:
if(pid->ki * pid->integral < -pid->maxIntegral) pid->integral = -pid->maxIntegral / pid->ki; else if(pid->ki * pid->integral > pid->maxIntegral) pid->integral = pid->maxIntegral / pid->ki;使用VOFA+进行实时波形监控极大提升了调试效率。通过串口以FireWater协议发送数据:
printf("MOTOR:%.2f,%.2f,%d,%d\n", Target_Speed, motorA.speed, (short)(__HAL_TIM_GET_COUNTER(ENCODER1)), motorA.totalCount);PID参数调试经验:
- 先单独调P值,直到出现明显振荡
- 加入D项抑制振荡
- 最后尝试加入少量I项消除静差
- 实际测试发现本系统中I项会引入超调,最终采用了PD控制
3. 位置环与角度环的串级控制
位置环作为外环,输出作为速度环的目标值。这里采用全量式PID,因为它能更好地处理阶跃响应。位置计算的关键是将编码器脉冲转换为角度:
angle_now = motorA.totalCount / (4.0 * 74.8 * 360) * 360.0;角度环的实现有几个特殊处理:
- 角度归一化:处理-180°到180°的跳变
if(target-feedback > 180){ feedback += 360; }else if(target-feedback < -180){ feedback -= 360; }- 串级控制结构:
角度环输出 → 速度环目标值 → PWM输出- 航向角修正:在小车移动过程中,角度环的输出作为底盘解算的旋转分量输入,实现运动过程中的航向保持。
4. 三轮全向底盘运动学解算
三轮全向底盘的运动学模型是项目中最精妙的部分。三个电机呈120°分布,通过速度矢量合成实现全向移动。运动解算的核心公式:
void Kinematic_Analysis(float Vx, float Vy, float V_angle) { Target_Speed_C = Vx + L_PARAMETER*V_angle/(2*PI)*60; Target_Speed_A = -0.5f*Vx + 0.866f*Vy + L_PARAMETER*V_angle/(2*PI)*60; Target_Speed_B = -0.5f*Vx - 0.866f*Vy + L_PARAMETER*V_angle/(2*PI)*60; }其中L_PARAMETER是轮子中心到机器人中心的距离(0.117m)。通过PS2手柄控制时,将摇杆输入分解为X/Y方向速度:
case FORWARD: Kinematic_Analysis(0, Target_Speed, pid_angle.output); break; case LEFT: Kinematic_Analysis(-Target_Speed, 0, pid_angle.output); break;实际测试中发现,当三个电机特性不一致时会出现运动偏差。解决方法是在代码中加入电机输出补偿系数:
// 在PWM输出函数中加入补偿 void MotorA_Run(float output) { __HAL_TIM_SET_COMPARE(&htim2, TIM_CHANNEL_2, fabs(output) * 0.97); // A电机补偿系数 }5. 系统集成与调试技巧
整个系统的定时器中断处理流程如下:
- 1ms读取三个编码器值并清零计数器
- 计算各电机转速(RPM)
- 每10ms执行一次PID计算
- 根据控制模式(旋转/移动)选择控制策略
- 输出PWM到电机驱动
VOFA+的高级用法:
- 使用控件发送PID参数进行实时调整
- 创建多个波形窗口同时监控速度、位置、角度
- 保存数据日志供后期分析
一个特别实用的调试技巧:当出现异常振动时,可以先固定两个电机,单独调试第三个电机的PID参数,然后再进行整体调试。
电源管理也是实战中的重要课题。发现当电池电压低于10V时,电机控制会出现异常。最终解决方案是:
- 添加电压检测电路
- 在代码中实现低压保护:
if(ADC_Value < 2500) { // 约10V MotorA_Run(0); MotorB_Run(0); MotorC_Run(0); }6. 性能优化与扩展思考
经过两周的调试,小车最终实现了以下性能指标:
- 直线运动误差:<2cm/m
- 旋转定位精度:<3°
- 最大运动速度:0.8m/s
几个可能的改进方向:
- 加入运动轨迹规划,实现平滑加减速
- 添加红外或超声波传感器实现避障
- 移植到ROS系统实现SLAM功能
在底盘机械结构方面,发现全向轮在粗糙地面表现不佳。后续考虑使用更大直径的轮子(90mm)并采用金属轮毂提高耐用性。
