手把手教你用STM32和ROS实现阿克曼小车PID控制

张开发
2026/4/15 2:19:57 15 分钟阅读

分享文章

手把手教你用STM32和ROS实现阿克曼小车PID控制
1. 阿克曼小车与PID控制基础阿克曼转向机构最早出现在马车时代后来被汽车工业广泛采用。这种转向方式的特点是车辆转弯时内侧轮比外侧轮转动更大的角度使得所有轮胎的延长线都交于同一点从而减少轮胎磨损。在机器人领域阿克曼结构常用于需要高速稳定运行的轮式机器人。PID控制是工业控制中最经典的算法之一由比例P、积分I、微分D三个环节组成。P项负责快速响应误差I项消除稳态误差D项抑制系统震荡。在阿克曼小车中我们需要同时控制两个关键部分前轮转向舵机的角度位置式PID和后轮电机的差速速度式PID。我去年帮学校机器人社团调试阿克曼赛车时发现很多新手容易犯一个错误把舵机控制和电机控制分开调试。实际上这两个系统是强耦合的舵机角度会影响后轮差速需求而后轮速度变化又会影响转向稳定性。建议大家在开始编程前先用纸笔画出小车在不同转向角度时左右轮的理论速度关系。2. 硬件搭建与STM32底层开发2.1 关键硬件选型建议舵机选择上我强烈推荐金属齿轮的数码舵机。虽然价格比模拟舵机贵30%左右但在反复转向时定位更精准。实测某品牌MG996R在2kg·cm扭矩下转向抖动比模拟舵机小60%。电机方面带霍尔编码器的直流减速电机是性价比之选编码器分辨率建议不低于13PPR每转脉冲数。STM32型号选择有个小技巧F1系列的TIM1/TIM8高级定时器可以同时输出多路互补PWM非常适合同时控制电机和舵机。我用STM32F103C8T6最小系统板做过测试完全能满足需求成本不到20元。如果预算充足可以考虑F4系列其硬件FPU能加速PID运算。2.2 PWM信号生成实战舵机控制最关键的时基脉冲要求20ms周期50Hz高电平宽度0.5-2.5ms对应0-180度。在STM32中配置时假设使用72MHz主频预分频设置为24-1自动重装载值设为60000-1这样得到的PWM频率正好是50Hz// 舵机PWM初始化代码TIM1通道1 void Servo_PWM_Init(void) { GPIO_InitTypeDef GPIO_InitStruct; TIM_TimeBaseInitTypeDef TIM_TimeBaseStruct; TIM_OCInitTypeDef TIM_OCInitStruct; RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA|RCC_APB2Periph_TIM1, ENABLE); // PA8作为TIM1_CH1输出 GPIO_InitStruct.GPIO_Pin GPIO_Pin_8; GPIO_InitStruct.GPIO_Mode GPIO_Mode_AF_PP; GPIO_InitStruct.GPIO_Speed GPIO_Speed_50MHz; GPIO_Init(GPIOA, GPIO_InitStruct); TIM_TimeBaseStruct.TIM_Period 60000-1; TIM_TimeBaseStruct.TIM_Prescaler 24-1; TIM_TimeBaseStruct.TIM_ClockDivision TIM_CKD_DIV1; TIM_TimeBaseStruct.TIM_CounterMode TIM_CounterMode_Up; TIM_TimeBaseInit(TIM1, TIM_TimeBaseStruct); TIM_OCInitStruct.TIM_OCMode TIM_OCMode_PWM2; TIM_OCInitStruct.TIM_OutputState TIM_OutputState_Enable; TIM_OCInitStruct.TIM_Pulse 4500; // 初始1.5ms(90度) TIM_OCInitStruct.TIM_OCPolarity TIM_OCPolarity_Low; TIM_OC1Init(TIM1, TIM_OCInitStruct); TIM_OC1PreloadConfig(TIM1, TIM_OCPreload_Enable); TIM_ARRPreloadConfig(TIM1, ENABLE); TIM_CtrlPWMOutputs(TIM1, ENABLE); TIM_Cmd(TIM1, ENABLE); }调试时有个实用技巧先用示波器检查PWM波形确保周期和脉宽准确。我遇到过因为硬件滤波电容导致舵机响应迟钝的情况解决方法是在舵机信号线串联一个200Ω电阻。3. ROS系统集成与通信协议3.1 ROS Kinetic/Melodic环境配置建议使用Ubuntu 18.04ROS Melodic的组合对新手最友好。安装完成后需要额外安装serial包sudo apt-get install ros-melodic-serial创建功能包时记得添加依赖项rospy、roscpp、std_msgs、geometry_msgs。我在去年移植到ROS Noetic时发现新版本需要额外修改CMakeLists.txt中的C标准设置add_compile_options(-stdc17)3.2 自定义消息协议设计STM32与ROS之间建议采用紧凑的二进制协议。下面是我优化过的帧结构[0xAA][0x55][长度][命令字][数据...][校验和]校验和用简单的累加和即可。在ROS节点中串口读取线程需要处理粘包问题。分享一个经过实战检验的解析函数void parseSerialData(uint8_t *buf, int len) { static uint8_t rxBuffer[128]; static int rxIndex 0; for(int i0; ilen; i) { if(rxIndex 0 buf[i] ! 0xAA) continue; if(rxIndex 1 buf[i] ! 0x55) { rxIndex 0; continue; } rxBuffer[rxIndex] buf[i]; if(rxIndex 3 rxIndex (rxBuffer[2]3)) { uint8_t checksum 0; for(int j0; jrxIndex-1; j) checksum rxBuffer[j]; if(checksum rxBuffer[rxIndex-1]) { processPacket(rxBuffer); } rxIndex 0; } } }4. PID算法实现与调参技巧4.1 位置式PID实现转向控制舵机角度控制适合用位置式PID。这里分享一个带死区和积分限幅的改进版本typedef struct { float Kp, Ki, Kd; float max_out; // 输出限幅 float max_iout; // 积分限幅 float deadband; // 死区 float set, fdb; float err, last_err; float Pout, Iout, Dout; } PID_TypeDef; void PID_Calc(PID_TypeDef *pid) { pid-err pid-set - pid-fdb; if(fabs(pid-err) pid-deadband) { pid-Pout pid-Kp * pid-err; pid-Iout pid-Ki * pid-err; // 积分限幅 if(pid-Iout pid-max_iout) pid-Iout pid-max_iout; else if(pid-Iout -pid-max_iout) pid-Iout -pid-max_iout; pid-Dout pid-Kd * (pid-err - pid-last_err); pid-last_err pid-err; } else { pid-Iout 0; // 进入死区清零积分 } float output pid-Pout pid-Iout pid-Dout; // 输出限幅 if(output pid-max_out) output pid-max_out; else if(output -pid-max_out) output -pid-max_out; pid-output output; }调参时有个小窍门先用纯P控制从小到大调整Kp直到出现轻微震荡然后取这个值的60%作为基础。Ki一般设为Kp的1/20Kd设为Kp的3-5倍。4.2 速度式PID实现差速控制后轮电机需要速度环PID。由于STM32计算能力有限建议使用简化版的增量式PIDvoid Speed_PID_Update(PID_TypeDef *pid, float current_speed) { static float last_output 0; float err pid-set - current_speed; float delta pid-Kp*(err - pid-last_err) pid-Ki*err pid-Kd*(err - 2*pid-last_err pid-last_last_err); pid-last_last_err pid-last_err; pid-last_err err; float output last_output delta; // 输出限幅 if(output pid-max_out) output pid-max_out; else if(output -pid-max_out) output -pid-max_out; last_output output; pid-output output; }调试差速控制时建议先用示波器观察编码器波形确保计数准确。常见问题包括编码器线序接反、滤波电容过大导致脉冲丢失、电机启动时电流不足等。

更多文章