STM32F103C8T6 寻迹小车 PID 调参实战:5个传感器实现 0.5cm 循迹精度
STM32F103C8T6 寻迹小车 PID 调参实战5个传感器实现 0.5cm 循迹精度在嵌入式开发领域寻迹小车一直是检验硬件设计与算法实现的经典项目。当基础循迹功能已经实现后如何进一步提升小车的循迹精度和稳定性成为开发者面临的下一个挑战。本文将聚焦于使用PID控制算法优化STM32F103C8T6寻迹小车的性能通过5个红外传感器实现0.5cm的高精度循迹。1. 硬件配置与系统架构1.1 核心硬件选型我们的寻迹小车采用以下核心组件构建主控芯片STM32F103C8T672MHz主频64KB Flash20KB SRAM传感器阵列5路TCRT5000红外反射传感器间距1.5cm电机驱动L298N双H桥驱动模块电源系统18650锂电池组7.4V配合AMS1117-3.3V稳压传感器布局采用左2-中1-右2的对称分布这种配置能够精确检测小车相对于黑线的偏移量[左外] [左内] [中央] [右内] [右外] | | | | | 1.5cm 1.5cm 1.5cm 1.5cm1.2 电路设计要点电机驱动电路需要特别注意PWM信号的隔离与保护// PWM初始化配置示例TIM3通道1和2 void PWM_Init(void) { TIM_TimeBaseInitTypeDef TIM_TimeBaseStructure; TIM_OCInitTypeDef TIM_OCInitStructure; RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM3, ENABLE); // 时基配置10kHz PWM频率 TIM_TimeBaseStructure.TIM_Period 999; TIM_TimeBaseStructure.TIM_Prescaler 71; TIM_TimeBaseStructure.TIM_ClockDivision 0; TIM_TimeBaseStructure.TIM_CounterMode TIM_CounterMode_Up; TIM_TimeBaseInit(TIM3, TIM_TimeBaseStructure); // PWM通道配置 TIM_OCInitStructure.TIM_OCMode TIM_OCMode_PWM1; TIM_OCInitStructure.TIM_OutputState TIM_OutputState_Enable; TIM_OCInitStructure.TIM_Pulse 0; TIM_OCInitStructure.TIM_OCPolarity TIM_OCPolarity_High; TIM_OC1Init(TIM3, TIM_OCInitStructure); // 左电机 TIM_OC2Init(TIM3, TIM_OCInitStructure); // 右电机 TIM_Cmd(TIM3, ENABLE); }2. PID控制算法实现2.1 位置式PID控制器设计我们采用增量式PID算法实现循迹控制其离散化公式为Δu(k) Kp[e(k)-e(k-1)] Ki*e(k) Kd[e(k)-2e(k-1)e(k-2)]对应的C语言实现如下typedef struct { float Kp, Ki, Kd; float error[3]; // 当前、前一次、前两次误差 float output; float output_max; } PID_Controller; void PID_Init(PID_Controller *pid, float Kp, float Ki, float Kd, float max) { pid-Kp Kp; pid-Ki Ki; pid-Kd Kd; pid-output_max max; memset(pid-error, 0, sizeof(pid-error)); pid-output 0; } float PID_Calculate(PID_Controller *pid, float setpoint, float feedback) { // 计算当前误差 pid-error[0] setpoint - feedback; // 计算增量 float delta pid-Kp * (pid-error[0] - pid-error[1]) pid-Ki * pid-error[0] pid-Kd * (pid-error[0] - 2*pid-error[1] pid-error[2]); // 更新历史误差 pid-error[2] pid-error[1]; pid-error[1] pid-error[0]; // 输出限幅 pid-output delta; if(pid-output pid-output_max) pid-output pid-output_max; if(pid-output -pid-output_max) pid-output -pid-output_max; return pid-output; }2.2 误差计算策略使用5路传感器数据计算位置误差时我们采用加权平均法传感器位置权重系数检测到黑线时的值左外-2.01左内-1.01中央0.01右内1.01右外2.01误差计算公式error Σ(传感器值 × 权重系数) / Σ(传感器值)实现代码float Calculate_Error(uint8_t *sensors) { float numerator 0; float denominator 0; const float weights[5] {-2.0, -1.0, 0.0, 1.0, 2.0}; for(int i0; i5; i) { numerator sensors[i] * weights[i]; denominator sensors[i]; } return (denominator ! 0) ? (numerator / denominator) : 0; }3. PID参数整定实战3.1 分阶段调参法我们采用先比例后微分最后积分的调参顺序纯P控制阶段设置Ki0Kd0逐步增大Kp直到小车出现轻微振荡记录临界增益Ku和振荡周期Tu加入微分控制根据Ziegler-Nichols公式Kp0.6KuKdKp*Tu/8微调Kd消除超调加入积分控制KiKp*2/Tu调整Ki消除稳态误差3.2 不同路径的调参对比我们测试了三类典型路径的优化参数路径类型KpKiKd平均误差(cm)最大超调量直线1.20.010.80.35%S弯0.80.051.20.515%直角弯1.50.020.50.720%提示实际应用中可采用参数自适应算法根据路径曲率动态调整PID参数4. 系统优化技巧4.1 软件滤波处理为消除传感器噪声我们采用移动平均滤波结合中值滤波#define FILTER_WINDOW 5 float Sensor_Filter(float new_value) { static float buffer[FILTER_WINDOW] {0}; static uint8_t index 0; float temp[FILTER_WINDOW]; // 更新缓冲区 buffer[index] new_value; index (index 1) % FILTER_WINDOW; // 复制到临时数组进行排序 memcpy(temp, buffer, sizeof(buffer)); for(int i0; iFILTER_WINDOW-1; i) { for(int ji1; jFILTER_WINDOW; j) { if(temp[i] temp[j]) { float swap temp[i]; temp[i] temp[j]; temp[j] swap; } } } // 取中值 return temp[FILTER_WINDOW/2]; }4.2 电机死区补偿由于电机存在启动死区我们需要在PWM输出时添加补偿void Set_Motor_Speed(TIM_TypeDef* TIMx, uint32_t Channel, int16_t speed) { uint16_t pwm abs(speed); // 死区补偿 if(pwm 0 pwm MOTOR_DEADZONE) { pwm MOTOR_DEADZONE; } if(Channel TIM_Channel_1) { TIMx-CCR1 pwm; GPIO_WriteBit(MOTOR_L_PORT, MOTOR_L_PIN, (speed 0) ? Bit_SET : Bit_RESET); } else { TIMx-CCR2 pwm; GPIO_WriteBit(MOTOR_R_PORT, MOTOR_R_PIN, (speed 0) ? Bit_SET : Bit_RESET); } }5. 性能测试与结果分析我们设计了三种测试场景来验证PID控制的效果直线循迹测试路径长度2米平均偏差0.35cm最大偏差0.8cmS弯道测试曲率半径15cm平均偏差0.52cm最大偏差1.2cm直角转弯测试转弯时间0.8秒超调量18%稳定时间1.2秒测试数据表明采用PID控制后循迹精度相比简单的阈值控制提升了约60%。特别是在弯道场景下微分项的引入有效抑制了超调现象。