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);    // 停止
  }
}

实测发现两个优化点:

  1. 增加5ms的消抖延时,避免传感器误触发
  2. 转弯时内外轮速度比保持在1:1.8左右最平滑

4. PID算法调参实战心得

刚开始调PID时,我的小车像醉汉一样左右摇摆。后来摸索出一套分步调试法

  1. 先调P参数:从0.1开始逐步增加,直到出现小幅振荡
  2. 再调D参数:取P值的1/5开始,抑制超调
  3. 最后加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查看实时波形。

更多推荐