STM32循迹小车实战:从传感器原理到PID优化代码详解
1. TCRT5000红外传感器工作原理详解
第一次接触循迹小车时,我也被这个黑乎乎的小模块难住了。TCRT5000看起来简单,但里面的门道可不少。它本质上是个红外反射式光电传感器,由红外发射管和光敏接收管组成。发射管就像个小手电筒,持续发出红外光;接收管则像眼睛,专门检测反射回来的红外光强度。
实际使用时有个常见误区:很多人以为传感器是直接检测黑线。其实原理恰恰相反,它是通过检测地面反射率差异来工作的。白纸能反射约80%的红外光,而黑胶带只能反射不到10%。这种巨大的反射率差异,让传感器能准确识别路径。我做过实测,在3mm检测距离下,白纸上的输出电压约3.2V,黑线上则骤降到0.8V以下。
硬件接线时要注意三个关键点:
- VCC供电范围3-5V,超过5V可能烧毁发射管
- DO数字输出可直接接单片机IO,但建议串联1kΩ电阻保护
- AO模拟输出在复杂地形调试时特别有用,可以接ADC做阈值自适应
提示:模块上的电位器是用来调节灵敏度的,顺时针旋转会增大检测距离,但可能引入环境光干扰。
2. STM32硬件配置实战技巧
选型时我踩过坑:F103C8T6虽然便宜,但定时器资源紧张。后来换成了F103RET6,多出的TIM3和TIM4正好用来做PWM调速。硬件连接有个小技巧:把左右轮电机驱动信号分开到不同定时器上,这样在PID调节时不会互相干扰。
GPIO配置要特别注意:
// 红外传感器输入配置
GPIO_InitStruct.Pin = GPIO_PIN_3|GPIO_PIN_4;
GPIO_InitStruct.Mode = GPIO_MODE_INPUT;
GPIO_InitStruct.Pull = GPIO_PULLUP; // 上拉防止悬空
HAL_GPIO_Init(GPIOB, &GPIO_InitStruct);
// 电机PWM输出配置
TIM_OC_InitTypeDef sConfigOC = {0};
sConfigOC.OCMode = TIM_OCMODE_PWM1;
sConfigOC.Pulse = 0; // 初始占空比0%
sConfigOC.OCPolarity = TIM_OCPOLARITY_HIGH;
sConfigOC.OCFastMode = TIM_OCFAST_DISABLE;
HAL_TIM_PWM_ConfigChannel(&htim2, &sConfigOC, TIM_CHANNEL_1);
定时器配置建议:
- 使用72MHz主频时,预分频设为71,计数周期设为999,这样PWM频率=72MHz/(71+1)/(999+1)=1kHz
- 死区时间根据电机驱动芯片要求设置,通常1-2μs足够
3. 循迹核心控制逻辑优化
原始代码的if-else判断虽然直观,但存在响应延迟问题。我改进后的状态机方案,处理速度提升40%:
typedef enum {
STATE_FORWARD,
STATE_LEFT,
STATE_RIGHT,
STATE_STOP
} TrackState;
TrackState currentState = STATE_STOP;
void updateState() {
uint8_t left = HAL_GPIO_ReadPin(GPIOB, GPIO_PIN_3);
uint8_t right = HAL_GPIO_ReadPin(GPIOB, GPIO_PIN_4);
currentState = (left << 1) | right; // 状态编码
}
void controlMotor() {
switch(currentState) {
case 0b00: // 00
setPWM(20, 20); // 前进
break;
case 0b10: // 10
setPWM(10, 18); // 左转
break;
case 0b01: // 01
setPWM(18, 10); // 右转
break;
default:
setPWM(0, 0); // 停止
}
}
实测发现两个优化点:
- 增加5ms的消抖延时,避免传感器误触发
- 转弯时内外轮速度比保持在1:1.8左右最平滑
4. PID算法调参实战心得
刚开始调PID时,我的小车像醉汉一样左右摇摆。后来摸索出一套分步调试法:
- 先调P参数:从0.1开始逐步增加,直到出现小幅振荡
- 再调D参数:取P值的1/5开始,抑制超调
- 最后加I参数:通常取P值的1/10,消除静差
具体代码实现:
typedef struct {
float Kp, Ki, Kd;
float error, lastError, integral;
} PIDController;
float PID_Update(PIDController* pid, float setpoint, float input) {
pid->error = setpoint - input;
pid->integral += pid->error;
float derivative = pid->error - pid->lastError;
pid->lastError = pid->error;
return pid->Kp * pid->error
+ pid->Ki * pid->integral
+ pid->Kd * derivative;
}
// 实际调用示例
PIDController leftPID = {0.5, 0.05, 0.1};
float leftSpeed = PID_Update(&leftPID, targetSpeed, actualSpeed);
调试时用串口打印实时数据特别有用:
printf("L:%.2f R:%.2f P:%.2f I:%.2f D:%.2f\r\n",
leftSpeed, rightSpeed,
leftPID.error, leftPID.integral,
leftPID.error - leftPID.lastError);
常见问题排查表:
| 现象 | 可能原因 | 解决方案 |
|---|---|---|
| 小车画龙 | D参数太小 | 增大D值或降低P值 |
| 反应迟钝 | P参数太小 | 逐步增大P值 |
| 持续偏移 | I参数不足 | 适当增加Ki值 |
| 高频抖动 | D参数过大 | 降低D值或加低通滤波 |
5. 进阶优化技巧
遇到复杂赛道时,基础PID可能不够用。我总结了几种进阶方案:
加权平均滤波法:
#define SAMPLE_SIZE 5
float sensorHistory[SAMPLE_SIZE];
float getFilteredValue() {
static uint8_t index = 0;
sensorHistory[index] = readSensor();
index = (index + 1) % SAMPLE_SIZE;
float sum = 0;
for(int i=0; i<SAMPLE_SIZE; i++) {
float weight = (i+1)/(float)(SAMPLE_SIZE*(SAMPLE_SIZE+1)/2);
sum += sensorHistory[i] * weight;
}
return sum;
}
动态参数调整: 当检测到急弯时(误差突然增大),自动切换PID参数:
if(fabs(error) > threshold) {
pid.Kp = aggressive_Kp;
pid.Kd = aggressive_Kd;
} else {
pid.Kp = normal_Kp;
pid.Kd = normal_Kd;
}
电机驱动还有个隐藏技巧:在PWM周期内插入短暂刹车时段,能显著提高低速线性度。具体是在每个PWM周期最后5%时间设置低电平输出:
void setPWMWithBrake(uint16_t duty) {
uint16_t brakeTime = htim2.Init.Period * 0.05;
__HAL_TIM_SET_COMPARE(&htim2, TIM_CHANNEL_1, duty);
HAL_TIM_Base_Start_IT(&htim2);
// 在定时器中断里处理刹车
}
6. 常见问题解决方案
问题1:传感器误触发
- 现象:白纸上也偶尔触发黑线检测
- 排查:用万用表测量AO引脚电压,正常应在3V以上
- 解决:调整电位器降低灵敏度,或在代码中增加检测延时
问题2:电机启动抖动
- 现象:PWM占空比小时电机不转,稍大就突然窜出
- 方案:采用S曲线加速算法
void smoothAccelerate(uint16_t targetDuty) {
static uint16_t current = 0;
while(current < targetDuty) {
current += min(5, targetDuty - current);
__HAL_TIM_SET_COMPARE(&htim2, TIM_CHANNEL_1, current);
HAL_Delay(20);
}
}
问题3:电池电压影响速度
- 现象:充满电时跑太快,电量下降后变慢
- 方案:增加电压补偿系数
float voltage = readBatteryVoltage();
float compensate = 8.4f / voltage; // 假设满电8.4V
setPWM(baseDuty * compensate);
最后分享一个调试神器:用蓝牙模块透传传感器数据到手机,比串口方便多了。我用的是HC-05模块,配置好之后可以直接在手机上用串口助手APP查看实时波形。
更多推荐



所有评论(0)