
简介基于51单片机的红外循迹与避障双功能小车资料服务于嵌入式入门的实践教学也适合电子设计竞赛选手参考。项目围绕红外传感器与电机控制展开完整展示了循迹和避障的算法逻辑以及从原理图到代码烧录的实现路径。压缩包共14个文件大小约418KB其中包含Keil工程文件、C语言源文件、可下载的HEX固件、程序编译生成的调试信息以及实验场景图等足以支撑从源码阅读到硬件验证的全流程。当前已有二千五百零二人学习下载人气可观说明其受到广泛关注。学习者可从中获得可复用的工程框架和具体的编程思路尤其能练习中断、定时器、PWM调速和IO口控制等常用技术的综合应用帮助快速上手小型智能车开发少走弯路。1. 51单片机双功能小车的定义与适用人群做电子设计课设的人拿到“51单片机红外循迹红外避障小车”多半会以为要写两套代码一套让车沿着黑线走一套让车躲开障碍物。实际把硬件摆上桌就会发现这辆所谓的双功能小车核心传感元件只有一种——红外对管。循迹时对管朝下照地面黑线吸光、白纸反光避障时同一只对管水平朝前照障碍物把红外光反射回来。双功能的“功能”不在传感器数量而在模式切换和差速转向的调度逻辑这恰恰是课设答辩时最容易被追问的地方。适合读这篇的人分两类第一类是正在做51单片机课程设计或电子竞赛入门车的学生需要一套能复现的接线表和C代码第二类是写过几年嵌入式、想快速确认传感器阈值标定和PWM不对称这类细节的工程师。下面的内容按硬件搭建、传感原理、Keil5下的C代码实现、现场调参的顺序推进把常见的丢线、误触、电机抖动问题一次讲清楚。2. 红外循迹与红外避障的本质从反射光路到数字电平2.1 同一只红外对管完成两种感知红外对管由发射管和接收管组成发射管持续发出940nm左右的红外光接收管根据反射回来的光强改变导通程度。循迹时对管垂直向下安装白色或浅色地面反射率高接收管导通强黑色电工胶带几乎不反射红外光接收管截止。模块上的LM393比较器把这个模拟差异转换成数字电平单片机读到的就是干净的0或1不需要再经过ADC。避障时对管水平向前安装前方没有障碍物时红外光发散后无法回到接收管输出一种电平有障碍物时反射光增强输出翻转。这里有一个从原理上就能推导出的坑避障模块的安装高度和仰角会直接影响检测距离装得太低会一直收到地面反射导致误触发装得太高又测不到矮障碍物。我一般把避障传感器装在车头前方4到6厘米高处朝前微微上仰5度左右让光轴避开地面。2.2 传感器选型TCRT5000和E18-D80NK的组合怎么定循序迹和避障虽然原理相同但检测距离要求差了一个数量级所以市面上常见的方案是传感件混搭循迹用TCRT5000模块避障用带调制功能的E18-D80NK。参数TCRT5000循迹模块E18-D80NK避障模块工作电压DC 5V部分模块支持3.3VDC 5V检测距离1mm 到 25mm实际建议离地10~20mm3cm 到 80cm用电位器可调输出形式数字电平TTL兼容数字电平NPN开漏输出抗环境光较弱需要屏蔽罩或在暗光环境调试强内部有调制解调电路适用场景黑线灰度识别障碍物有无判断不要指望用E18-D80NK去做循迹它的检测距离是以厘米为单位的面对大面积的地面颜色差异很难给出稳定输出反过来TCRT5000做避障也只有2.5cm左右的有效距离车速稍快就来不及刹车。如果预算有限非要统一用TCRT5000做避障那小车速度必须压到极低且要接受频繁误报。2.3 阈值比较和软件标定先调硬件再写代码每个红外模块上都有一颗电位器用来调节LM393比较器的参考电压。这个电位器是现场调试的第一个关口。以循迹模块为例先把小车放在白色地面上用螺丝刀缓慢旋转电位器找到模块上指示灯刚好翻转的临界点然后往回调1/8圈让白地面的输出更稳定再把车移到黑色胶带上确认指示灯状态确实翻转。这个标定动作解决的是“什么是黑”的问题调完之后模块输出的数字电平就固定了。代码里要做的只是读引脚不需要ADC采样。读取部分在51单片机上加一句引脚定义即可sbit TRACK_LEFT P0^0; // 左循迹传感器输出 sbit TRACK_MID P0^1; // 中循迹传感器输出 sbit TRACK_RIGHT P0^2; // 右循迹传感器输出 sbit AVOID_LEFT P0^3; // 左避障传感器输出 sbit AVOID_RIGHT P0^4; // 右避障传感器输出注意P0口传统8051默认是开漏结构很多51开发板上已经有上拉排阻直接用没问题如果是自制最小系统板务必给P0口加上4.7k到10k的上拉电阻否则读回来的数据会随机跳动。3. 整车搭建电机驱动、电源分配与双功能接线3.1 L298N驱动与电池供电的注意事项电机驱动最常见的做法是L298N模块它可以同时驱动左右两个直流减速电机。L298N上有两个使能端ENA和ENB分别给左右电机提供PWM调速每侧还有两个方向控制引脚IN1~IN4组合成正转、反转和刹车。电流路径上动力电池正极接L298N的12V输入L298N板载5V稳压输出再供给单片机。这里有一个新手几乎必踩的坑电机堵转启动瞬间电流可达2A如果单片机也从这个5V输出取电电机会把5V拉低造成单片机复位或传感器阈值漂移。我一般会做两级处理一是在L298N的12V输入侧并联一个470uF电解电容吸收启动冲击二是把L298N的5V输出和单片机电源之间加一个100uF电容同时把L298N的GND与单片机GND用粗导线短接。共地线做不到位PWM波形上会叠加大量毛刺传感器误判会频繁出现。3.2 双功能小车接线对照表双功能并不是把两套传感器全接到P2口那么简单引脚规划的原则是传感器占用P0口统一读取电机方向控制占用P1口PWM信号也从P1口输出模式切换占用P3口的一个独立引脚。下面这套接线适用于STC89C52或AT89S52的最小系统板模块模块引脚单片机引脚说明左循迹OUTP0.01表示压黑线中循迹OUTP0.11表示压黑线右循迹OUTP0.21表示压黑线左避障OUTP0.3检测到障碍为低电平右避障OUTP0.4检测到障碍为低电平L298NIN1P1.0左电机方向AL298NIN2P1.1左电机方向BL298NIN3P1.2右电机方向AL298NIN4P1.3右电机方向BL298NENAP1.4左电机PWM使能L298NENBP1.5右电机PWM使能模式开关拨码一端P3.7高电平循迹模式低电平避障优先为什么PWM不接在L298N的固定PWM引脚上因为51单片机不像STM32那样有硬件PWM输出常用的做法是让定时器中断去翻转任意IO所以P1.4和P1.5这样的普通引脚就能承担软件PWM功能。以后换到STM32F103ZET6做智能小车时把这个软件PWM换成定时器通道输出即可接线思路不变。3.3 双功能模式的硬件切换方式模式切换一般用两种硬件方案。最简单的是拨码开关一端接P3.7另一端接地通电时读取一次引脚状态程序里用一个mode变量记录下来另一种是用独立按键但按键需要处理消抖和长按对课设来说性价比不高。拨码开关的优势在于复位瞬间电平就稳定不需要在代码里加入防抖等待。双功能的具体定义也需要在硬件层面说清楚。常见设计是三种模式模式一纯循迹模式二纯避障模式三避障优先的循迹——也就是正常沿黑线走遇到障碍物临时切换避障动作绕开后再回到线上。第三种模式最能体现“双功能”的价值把它作为默认屏蔽档位即可。4. 双功能控制逻辑Keil5下的C代码与策略切换4.1 用51单片机的定时器生成双路软件PWMSTC89C52没有硬件PWM模块最可靠的方式是用定时器0产生一个固定频率的时基中断在中断里维护一个递增计数器把这个计数器和目标占空比值比较从而输出PWM。下面这段代码把PWM周期设为2ms对应500Hz这个频率下电机没有明显啸叫L298N也能正常响应。unsigned char pwm_count 0; unsigned char speed_left 70; // 左轮占空比 0~100 unsigned char speed_right 70; // 右轮占空比 0~100 sbit PWM_L P1^4; // L298N ENA sbit PWM_R P1^5; // L298N ENB void Timer0_Init(void) { TMOD 0xF0; TMOD | 0x01; // 定时器0模式116位 TH0 0xFF; TL0 0xA4; // 100us 11.0592MHz ET0 1; TR0 1; EA 1; } void Timer0_ISR(void) interrupt 1 { TH0 0xFF; TL0 0xA4; // 重装初值 pwm_count; if (pwm_count 20) { pwm_count 0; } PWM_L (pwm_count speed_left) ? 1 : 0; PWM_R (pwm_count speed_right) ? 1 : 0; }初值0xFFA4的计算过程11.0592MHz晶振下机器周期为12/11.0592MHz约等于1.085us100us需要计数92个机器周期65536减92等于65444也就是0xFFA4。计数器每溢出一次进入中断pwm_count在0到19之间循环speed值就是占空比的百分比。调整speed_left和speed_right就能让小车直行、加速或转圈而不需要额外修改寄存器配置。4.2 三路循迹的状态表与差速转向循迹的转向逻辑是一个典型的两轮差速模型想让车头往左偏就降低左轮占空比、提高右轮占空比车体围绕重心旋转。三路传感器的读数是三个bit组合起来有8种状态对应不同的转向强度。#define STATE_STRAIGHT 0x02 // 010中间压线直行 #define STATE_LEFT_SLOW 0x06 // 110左和中压线快速左转 #define STATE_LEFT_TURN 0x04 // 100仅左压线中速左转 #define STATE_RIGHT_SLOW 0x03 // 011右和中压线快速右转 #define STATE_RIGHT_TURN 0x01 // 001仅右压线中速右转 #define STATE_LOST 0x00 // 000全部丢线 void track_control(void) { unsigned char state 0; state | TRACK_LEFT ? 0x04 : 0; state | TRACK_MID ? 0x02 : 0; state | TRACK_RIGHT ? 0x01 : 0; switch (state) { case STATE_STRAIGHT: speed_left 70; speed_right 70; break; case STATE_LEFT_SLOW: speed_left 30; speed_right 90; last_dir 1; // 记录最后一次转向方向 break; case STATE_RIGHT_SLOW: speed_left 90; speed_right 30; last_dir 0; break; case STATE_LOST: recover_track(); // 丢线调头找回 break; default: speed_left 55; speed_right 85; break; } }这段代码的默认分支覆盖了只有单侧压线的中间态。注意state拼装时把P0.0赋给最高位这样读到的状态码和上面的宏定义一一对应。逻辑上“黑线在左就往左修正”这条规则适用于大多数循迹场景但如果线圈铺设方向相反只需要交换左右传感器接线或改变配套的分支数值。4.3 循迹丢线的调头找回“红外循迹调头”是循迹小车最难的环节因为传感器全丢线时小车其实是“盲”的状态。常见做法是利用last_dir这个记忆变量最后一次是左转时丢线说明黑线往左甩出小车应该继续原地左转最后一次是右转时丢线则反向操作。这样能在多数弯道场景中恢复路线。void recover_track(void) { if (last_dir 1) { // 左轮反转、右轮正转原地左转 IN1 0; IN2 1; IN3 1; IN4 0; speed_left 45; speed_right 45; delay_ms(300); } else { IN1 1; IN2 0; IN3 0; IN4 1; speed_left 45; speed_right 45; delay_ms(300); } // 恢复直行低速试探 speed_left 50; speed_right 50; }调头动作的核心不是转多久而是每次只增加一个固定的旋转时长让控制器重新进入循迹循环。如果在recover_track返回后依然丢线下一次进入时应该适当延长旋转时间我一般会在丢线计数的累积上做线性递增第一次300ms第二次400ms直到传感器重新读到黑线为止。4.4 避障传感器的防抖处理与三档避障逻辑E18-D80NK这类数字量避障传感器的输出在障碍物边缘会抖动直接读引脚会导致小车左右抖动。我一般会在避障检测函数里加入连续多次采样确认。下面以左避障为例unsigned char avoid_left_check(void) { static unsigned char cnt 0; if (AVOID_LEFT 0) // 模块检测到障碍时输出低电平 { if (cnt 50) cnt; } else { cnt 0; } return (cnt 50); // 连续50次低电平确认有障碍 }这个函数每次被调用时都会在定时器中断的间隙执行50次连续采样累计大约50ms时间足以滤除毛刺而又不会让避障反应迟钝。避障控制策略用一个简单的三态判断即可void avoid_control(void) { unsigned char l avoid_left_check(); unsigned char r avoid_right_check(); if (l r) { // 两侧都有障碍先后退再右转 IN1 0; IN2 1; IN3 0; IN4 1; delay_ms(200); IN1 1; IN2 0; IN3 0; IN4 1; delay_ms(200); } else if (l) { // 左侧有障碍右转 speed_left 80; speed_right 30; } else if (r) { // 右侧有障碍左转 speed_left 30; speed_right 80; } else { speed_left 70; speed_right 70; } }值得说明的是这种局部避障不做路径规划只做“遇障绕行”适合51单片机这类资源有限的平台。在ROS小车或STM32小车上同样的逻辑会被行为树或状态机取代但“先判定空间、再分配差速”的思路是相通的。4.5 双功能融合避障优先还是循迹优先双功能控制的核心是确定优先级我认为合理的策略是避障优先。在障碍物密集区域循迹功能的意义不大小车首先要保证不撞离开障碍区后再回到黑线上继续循迹。主循环的结构很简洁while (1) { if (mode 0) { track_control(); } else if (mode 1) { avoid_control(); } else { if (avoid_left_check() || avoid_right_check()) avoid_control(); else track_control(); } }mode的值由P3.7在初始化时读取建议在init函数里用一句mode SWITCH_PIN;读取并且不要在运行中频繁切换因为拨码开关在车辆震动时可能跳动如果确实需要动态切换务必加上50ms的软件防抖。这个结构与动态避障小车路径规划中常见的“全局规划器局部避障器”分层一致只不过在51单片机上全局规划被简化为一条固定的黑线局部避障被简化为一对红外传感器。5. 调头、地线与阈值三个必须现场调试的细节5.1 先调阈值再查代码代码写得再干净传感器阈值不对整台车就是个随机运动器。现场标定的标准动作是把小车分别放在白色地面和黑线上各测一次传感器输出的电平状态确认两者的逻辑差异是1和0而不是都是1或都是0。如果循迹模块的输出逻辑和预期相反最简单的办法是在代码里给每个引脚定义一个反相宏比如#define TRACK_LEFT_ACTIVE (TRACK_LEFT 0)而不是去改硬件。电位器调好后用螺丝刀在电位器上点一点热熔胶固定防止车辆震动导致漂移。这一步看起来土但在课设答辩现场能避免大量尴尬。5.2 线宽与传感器间距的匹配循迹稳定性与黑线宽度、三个传感器的间距强相关。用标准1.8cm宽的电工胶带铺线时传感器间距需要满足一个关系三路探头中至少保证中间探头能落在黑线内同时左右探头在线外。间距太宽小车在直线上也会左右摇摆间距太窄过弯时传感器容易同时丢线。黑线宽度传感器间距范围铺线建议1.5cm ~ 2.0cm1.0cm ~ 1.5cm使用标准电工胶带2.0cm ~ 3.0cm1.5cm ~ 2.2cm转弯半径需大于15cm3.0cm以上2.5cm以下即可允许更激进的速度用记号笔画的线反光特性不稳定最好统一用哑光黑色电工胶带铺设否则白纸和黑线的反射差异会让阈值标定失效。5.3 验证调头动作与PWM不对称调头动作看起来是左右轮反转但因为两个电机的机械阻尼和减速比不会完全一致同一个占空比下实际转速可能差20%。验证方法很简单在代码里固定speed_left和speed_right为相同值让小车沿直线跑1米如果跑偏就往慢的那一侧增加占空比偏移量。把这个偏移量定义成一个全局变量pwm_offset在左右轮赋值处分别应用void set_speed(unsigned char left, unsigned char right) { speed_left left pwm_offset; // 补偿慢轮 speed_right right - pwm_offset; }同样地原地调头时左右轮目标方向相反转向半径更小但补偿逻辑依然适用。现场用慢动作视频记录小车原地转一圈的时间微调补偿量直到车头转角与设定值一致。最后把补偿值写死到初始化里调头逻辑才算真正可靠。本文还有配套的精品资源点击获取