零知派——STM32智能小车系列教程(一):TCRT5000 五路循迹模块原理与调试
引言智能小车要实现的功能很多——避障、跟随、贴边、灭火、直线行驶每一个模块单独调试起来都有自己的难点。如果一开始就把所有模块塞进一个工程里联调出了问题很难分清到底是传感器硬件的问题、电平极性的问题还是上层控制算法的问题所以这一系列教程的思路是先把每个功能模块单独拎出来配一个不依赖其他硬件的最小测试程序把模块本身调通调透再回头看它在完整项目里是怎么被使用的目录一、系列教程前言二、循迹模块硬件原理2.1 红外发射接收2.2 原理图讲解三、硬件接线说明3.1 循迹模块引脚定义3.2 接线方案表3.3 连接示意图四、独立测试程序五、调试流程演示5.1 测试流程5.2 演示视频六、项目循迹功能实现6.1 感知层加权质心算法6.2 极性配置6.3 丢线判断与防抽动消抖6.4 从偏差值到电机控制6.5 PID参数调试方法七、常见问题解答FAQQ1小车过弯时冲出赛道怎么办Q2串口读到的电平与预期相反怎么办Q3检测距离太近或太远怎么调一、系列教程前言这是系列的第一篇专门讲五路循迹模块——也是实际调试过程中踩坑最多、最容易出现“看起来能用但逻辑是错的”这种隐蔽问题的一个模块。整车项目里循迹功能遇到过三次崩溃次数问题根因表现第一次精确匹配表遗漏传感器组合五路传感器有 2⁵ 32 种组合匹配表只列了约 16 种大量组合下小车卡死不动第二次极性参数传反setColors(WHITE, BLACK)把黑线当成了地面小车在黑线边缘反复前后抽动第三次转向方向符号搞反偏差值符号到电机转向的映射写反了感知是对的车却往错误方向转这三个问题分别对应了“算法设计”、“硬件极性适配”、“控制输出方向”三个完全不同的层面非常适合作为一个完整的调试案例后续这个系列拆成以下几篇篇号主题涉及硬件第一篇本文五路循迹模块TCRT5000 LM393第二篇超声波测距与舵机云台HC-SR04 SG90覆盖避障/贴边/魔术手三个模式第三篇红外双目跟随模块红外避障对管第四篇霍尔编码器测速与直线行驶PID霍尔传感器 PID自整定第五篇环境感知模块DHT11温湿度 火焰传感器 风扇第六篇蓝牙/串口双通道通信协议设计HC-05/BT06终篇整车架构总览串联前六篇二、循迹模块硬件原理TCRT5000 接收管输出的是一个连续变化的模拟电压电压高低取决于反射光的强弱2.1 红外发射接收①TCRT5000 反射式红外对管发射管持续发出红外光如果前方是反光能力强的表面比如白纸大部分红外光会被反射回来被接收管捕获如果前方是吸光能力强的表面比如黑色胶带或黑漆红外光被大量吸收反射回来的光线很弱接收管几乎收不到信号不同颜色对红外光的反射率不同表面颜色红外反射接收管收到的光输出电压趋势白色/浅色强反射收到大量红外一种状态黑色/深色几乎吸收收到极少红外另一种状态利用这个特性五个 TCRT5000 在车头一字排开最左-X5、左二-X4、正中-X3、右二-X2、最右-X1同时扫过赛道就能知道黑线相对车身的左右位置②LM393 比较器LM393 的作用是比较两个输入电压的大小当同相输入端 电压高于反相输入端-电压时比较器输出高电平。当同相输入端电压低于反相输入端-电压时比较器输出低电平LM393 在循迹模块中的具体应用在循迹模块中LM393 的一个输入端通常是同相输入端连接 TCRT5000 接收管的输出电压随反射光强度变化另一个输入端连接电位器分压提供的参考电压阈值比较器把这两个电压进行比较条件输出状态反射光强白底接收管导通输入电压低于阈值输出低电平DO0反射光弱黑线接收管截止输入电压高于阈值输出高电平DO1注意不同厂家、不同批次的模块输出电平与颜色的对应关系可能相反有的模块黑线输出低电平白底输出高电平。本项目实测确认的是黑线 HIGH(1)白底 LOW(0)这也是后面代码中使用的极性③LM339 四路电压比较器本项目五路循迹模块实际上同时使用了 LM393 和 LM339 两种比较器——部分模块使用 LM393双路部分使用 LM339四路拼成五路检测通道LM393 与 LM339 对比对比项LM393LM339比较器数量2 路4 路引脚间距2.54mm2.54mm单路功耗~0.6mA~0.325mA每路响应时间~1.3μs~1.3μs输入失调电压±1.0mV±2.0mV实际设计中采用 LM3394 路 LM3932 路 6 路比较器刚好覆盖 5 路传感器成本最优PCB 布局也最紧凑④电位器调节模块上那颗蓝色方形可调3362电位器本质上是可调分压器——旋转旋钮改变分压比例从而改变送入 LM393 比较器的参考电压阈值调节效果调节方向阈值变化效果阈值调低参考电压降低更容易判定为黑线灵敏但容易误判阈值调高参考电压升高更难判定为黑线不灵敏抗干扰强实际调试方法模块正对白纸悬空约 1~2cm慢慢旋转电位器观察模块上那颗状态指示灯DO-LED找到指示灯刚好从亮变暗或从暗变亮的临界点再往灵敏方向回调一点点约 1/8 圈用黑色遮挡物验证压在黑线上时指示灯应熄灭或点亮移开应恢复每一路模块都要单独调节因为五个模块的电位器是独立的灵敏度不会自动一致2.2 原理图讲解①单路 TCRT5000 检测电路典型的 TCRT5000 LM393 循迹模块单路原理图元件作用180Ω限流电阻限制 TCRT5000 发射管电流通常 100Ω ~ 200Ω10KΩ分压电阻与接收管内阻组成分压电路决定接收管输出的电压变化范围10KΩ上拉电阻LM393 集电极开路输出需要上拉到 VCC通常 10kΩVR1电位器调节比较器参考阈值电压通常 10kΩ 3296 型LM393 和 LM339 都采用集电极开路Open-Collector输出输出晶体管导通→ 输出引脚被拉低到 GND低电平输出晶体管截止→ 输出引脚悬空必须通过上拉电阻拉到 VCC高电平三、硬件接线说明3.1 循迹模块引脚定义本项目五路循迹模块的引脚定义与pinsdefine.h一致传感器位置引脚号说明最左TracePin114五路中最左侧左二TracePin213左侧第二路中间TracePin312正中间右二TracePin417右侧第二路最右TracePin518五路中最右侧3.2 接线方案表每个 TCRT5000 循迹模块通常有 4 个引脚信号线模块端引脚零知派迷你板引脚代码常量说明VCCVCC5V—五路模块共用一路5V供电GNDGNDGND—五路模块共用地线信号1DOD14TracePin1最左路信号2DOD13TracePin2左二路信号3DOD12TracePin3正中路信号4DOD17TracePin4右二路信号5DOD18TracePin5最右路3.3 连接示意图四、独立测试程序下面这个程序只依赖五路循迹模块的信号线和串口不需要接电机、舵机或其他任何传感器。引脚号与主项目 pinsdefine.h 完全一致/************************************************************************************** * 文件: LineTracker_Standalone_Test.ino * 作者零知实验室深圳市在芯间科技有限公司 * 功能5路TCRT5000循迹模块独立测试程序 * 引脚定义与主项目 pinsdefine.h 完全一致不修改任何引脚 * 仅依赖串口不需要电机、舵机等其他硬件 * * 使用方法 * 1. 烧录本程序到零知派迷你板STM32F103C8T6 * 2. 打开串口监视器波特率 115200 * 3. 把小车或单独的循迹模块放在白底黑线赛道上移动 * 4. 观察串口输出的原始电平、加权质心偏差值 * 5. 通过模块上的电位器旋钮调节灵敏度同步观察数值变化 **************************************************************************************/ // ── 引脚定义与主项目 pinsdefine.h 完全一致 ────────── #define TracePin1 14 // 最左 #define TracePin2 13 #define TracePin3 12 // 中间 #define TracePin4 17 #define TracePin5 18 // 最右 const uint8_t pins[5] {TracePin1, TracePin2, TracePin3, TracePin4, TracePin5}; const int8_t weight[5] {-10, -5, 0, 5, 10}; // 与主项目算法保持一致的位置权重 void setup() { Serial.begin(115200); delay(200); for (uint8_t i 0; i 5; i) pinMode(pins[i], INPUT); Serial.println(F()); Serial.println(F( TCRT5000 五路循迹模块 独立测试程序 )); Serial.println(F()); Serial.println(F(请将模块放在白底上确认5路均为0)); Serial.println(F(再放在黑线上确认对应路变为1)); Serial.println(F(如果方向相反请检查模块极性或接线顺序)); Serial.println(F()); } void loop() { byte b[5]; for (uint8_t i 0; i 5; i) b[i] (byte)digitalRead(pins[i]); // ── 原始电平输出 ────────────────────────────────────────── Serial.print(F(RAW: )); for (uint8_t i 0; i 5; i) { Serial.print(b[i]); if (i 4) Serial.print(-); } // ── 加权质心计算与主项目算法一致────────────────────── int32_t weightSum 0; uint8_t lineCount 0; for (uint8_t i 0; i 5; i) { if (b[i] 1) { // 1 实测黑线电平本项目极性 weightSum weight[i]; lineCount; } } Serial.print(F( 压线数)); Serial.print(lineCount); if (lineCount 0) { Serial.println(F( 状态丢线全白)); } else { int dec weightSum / (int)lineCount; Serial.print(F( 偏差值)); Serial.print(dec); if (dec -5) Serial.println(F( 状态大幅左偏/直角左)); else if (dec -2) Serial.println(F( 状态小幅左偏)); else if (dec 5) Serial.println(F( 状态大幅右偏/直角右)); else if (dec 2) Serial.println(F( 状态小幅右偏)); else Serial.println(F( 状态直行)); } delay(200); // 测试阶段放慢刷新速度方便观察 }五、调试流程演示5.1 测试流程①悬空测试模块抬高离开任何表面五路应全部显示0②单路压线测试用黑色胶带依次盖住每一路传感器确认串口输出对应那一位从0变成1其余保持0这一步验证接线顺序——如果压住最左边传感器串口却显示最右边那一位变化说明物理接线顺序和代码定义顺序搞反了③电位器灵敏度调节固定模块在赛道黑线正上方边压线边旋转电位器观察对应位是否稳定为1移到白色区域确认能稳定回到0④整体过线测试让模块缓慢从赛道左侧移动到右侧观察偏差值能否连续地从负值经过0变化到正值过程应平滑无跳变5.2 演示视频零知派迷你板-5路循迹模块组装与测试六、项目循迹功能实现确认模块本身没问题之后再看完整项目里循迹功能是怎么组织的。这部分代码分布在两个文件里文件职责sc_perception.h/.cpp感知层把硬件信号转换成偏差值sc_behaviors.h/.cpp行为层把偏差值转换成电机控制指令6.1 感知层加权质心算法sc_perception.h里LineTracker类的核心是五个固定权重const int8_t _weight[5] {-10, -5, 0, 5, 10};getDecide()的算法逻辑只对压到黑线的传感器把权重加起来再除以压线数量得到加权平均位置作为偏差值int32_t weightSum 0; uint8_t lineCount 0; for (uint8_t i 0; i 5; i) { if (b[i] L) { weightSum _weight[i]; lineCount; } } int dec weightSum / (int)lineCount;这个设计的优势五路传感器无论压线的组合是哪几路、压了几路公式永远能算出一个确定的结果不存在某种组合没考虑到的遗漏问题。场景压线传感器计算过程偏差值直行仅中间 b[2]0/10轻微左偏b[1]b[2](-50)/2-2.5轻微右偏b[2]b[3](05)/22.5中等右偏仅 b[3]5/15最大右偏仅 b[4]10/110十字路口b[0..4]全压(-10-50510)/506.2 极性配置setColors()接口决定了代码怎么理解地面和线对应的实测电平值void setColors(byte groundColor, byte lineColor);本项目实测黑线高电平、白底低电平正确调用是g_tracker.setColors(BLACK, WHITE);常见错误:如果把参数顺序写反成setColors(WHITE, BLACK)黑线会被误判成地面小车压在黑线上反而触发丢线逻辑表现为小车在黑线和白底交界处来回乱动6.3 丢线判断与防抽动消抖五路全部读到地面色才判定丢线且要连续三次才确认避免单次噪点误判触发不必要的后退动作uint8_t _allOffCount 0; static const uint8_t ALL_OFF_CONFIRM 3;丢线状态机分三个阶段enum SearchState : uint8_t { NORMAL, // 正常循迹 SEARCH_BACK, // 后退 300ms SEARCH_SPIN, // 原地旋转找线最多 10 秒 SEARCH_STOP // 超时停车等待人工介入 };6.4 从偏差值到电机控制TrackBehavior::update()拿到偏差值后分两种情况处理①大角度|decide|5不走 PID直接原地旋转if (absD 5) { if (decide 0) { g_motor.motorRun(-TURN_SPEED, TURN_SPEED); } else { g_motor.motorRun(TURN_SPEED, -TURN_SPEED); } return; }②小角度|decide| 5用 PD 控制器_input (double)decide; _pid.SetOutputLimits(-spd, spd); _pid.Compute(); int lSpd constrain(spd - (int)_output, -255, 255); int rSpd constrain(spd (int)_output, -255, 255); g_motor.motorRun(lSpd, rSpd);只用P和D两项没用I因为循迹偏差是瞬时测量值不存在需要积分消除的固定偏置积分项反而容易在连续弯道产生过冲震荡自适应速度偏差越大基础速度越低int spd (absD 2) ? baseSpeed : (absD 3) ? baseSpeed * 85 / 100 : baseSpeed * 70 / 100;偏差越大基础速度越低过弯更稳直道全速6.5 PID参数调试方法TrackBehavior 构造时传入的默认参数是 比例 Kp 6.5微分 Kd 0.5调试时可以通过蓝牙或串口发送命令实时热更新参数命令说明示例TKP数值设置比例项TKP8.0TKD数值设置微分项TKD0.8SPD数值设置基础速度SPD100调试顺序微分项设为 0只调比例项从较小值如 3.0开始逐步增大观察车身太小则跟不住线太大则来回摆动找到能跟住线但有轻微震荡的比例值逐步加大微分项如 0.3 → 0.5 → 0.8压制震荡微分项不能太大否则转弯响应会变迟钝七、常见问题解答FAQQ1小车过弯时冲出赛道怎么办A请尝试降低基础速度SPD60 或更低、增大比例项 Kp让转向更敏感、适当增加微分项 Kd抑制震荡Q2串口读到的电平与预期相反怎么办A不同批次的 TCRT5000 模块输出极性可能相反。解决方法是修改 TrackBehavior::onEnter() 中的 setColors()// 如果黑线0白底1与本文相反 g_tracker.setColors(WHITE, BLACK);Q3检测距离太近或太远怎么调A调节电位器可以改变检测灵敏度。如果电位器调到极限仍不满足可以调整 TCRT5000 的限流电阻R1来改变发射功率资料整合LM393 数据手册TIlm393.pdfLM339 数据手册TIlm339.pdfLM339/LM393 应用电路差分比较器应用电路循迹这类依赖传感器输入的功能调试时最容易出问题的往往不是算法本身的数学正确性而是中间的“翻译层”——电平极性怎么对应到地面和线的语义、偏差值的符号怎么对应到电机的转向