51单片机智能小车:红外循迹与超声波避障完整实现与调试指南
2026/9/8 12:54:19 网站建设 项目流程

简介:一套基于51单片机(STC15W4K56S4)的智能小车红外循迹与超声波避障完整程序源码,适合正在学习单片机、传感器应用或准备电子设计竞赛的读者。程序使用KEIL开发,包含main.c、电机驱动、循迹、避障、超声波测距、1602液晶显示等模块化C语言文件,并配有必要的头文件与工程配置。其核心逻辑是:小车正常沿黑线循迹,当超声波检测到前方障碍时立即停车,待障碍移开后自动恢复循迹,逻辑清晰、便于理解与修改。

压缩包内共62个文件,主要包含.c源码、.h头文件、.obj编译中间文件、.lst列表文件以及可直接烧录的.hex文件,另附一份“程序说明(必看).txt”,整体体积仅150KB,结构紧凑、定位明确。目前已有3431人学习该资源,适合需要快速搭建智能小车实验平台或借鉴循迹避障控制思路的开发者参考。代码已在作者自制的51智能小车上实测通过,具备良好的参考价值和可直接移植到同系列单片机项目中的实用性。 当初做这个51单片机智能小车的时候,我其实就一个想法:把红外循迹和超声波避障这两个功能老老实实跑通,代码不搞花里胡哨,逻辑清晰、注释详细,最好能直接改改就用。结果真正从画原理图到调完最后一行代码,前前后后折腾了好几个晚上,踩了不少坑。网上关于51单片机智能小车的资料很多,但大多零散,要么只讲循迹,要么只给个避障模块代码,两个功能怎么在一个程序里协同工作、引脚怎么分配、电源怎么处理,往往一笔带过。

这份源代码压缩包就是我当时调试完成的完整工程,包含红外循迹模块、超声波避障模块、电机驱动PWM调速、舵机云台扫描等完整逻辑。今天我不光把程序结构拆开讲清楚,还会把调试过程中遇到的典型问题、排查思路一并整理出来。不管你是正在做课程设计,还是电子设计竞赛入门选题,又或者是想搞一台能自己跑的小车练手,这篇内容都能让你少走不少弯路。

1. 项目整体设计与思路拆解

1.1 这辆小车到底要实现什么功能

先明确目标。我做的小车是三轮结构,前面一个万向轮,后面两个直流减速电机驱动轮,主控板用的是STC89C52RC单片机。功能上分两个模式:红外循迹模式和超声波避障模式,通过一个拨码开关切换,也可以同时开启,让避障优先级高于循迹。

红外循迹的核心逻辑是让小车沿着地面上的黑线行驶。常见的做法是在车头底部安装2到5路红外反射式传感器,利用黑线与地面其他颜色的反射率差异,输出不同的电平信号。单片机根据这些信号判断小车偏离黑线的方向,再控制左右两个电机的转速差异,实现转向修正。这个原理听起来简单,但实际调起来,传感器的安装高度、灵敏度阈值、线宽、环境光线,都会影响循迹的稳定性。

超声波避障则是利用HC-SR04超声波模块发射超声波脉冲,测量回波时间来计算障碍物距离。当检测到前方障碍物距离小于设定的安全阈值时,小车停车,然后通过舵机控制超声波模块左右扫描,判断哪边空间更大,就往哪边转向绕过障碍物。

1.2 为什么选51单片机而不是STM32或Arduino

这是很多人纠结的问题。我自己手头正好有STC89C52RC,而且51单片机的资料实在太全了,任何一个引脚功能、任何一个模块的驱动示例,网上都能找到。对于智能小车这个量级的控制任务,51的性能完全够用。

有人可能觉得51单片机主频低、资源少,跑不了复杂的算法。但智能小车说白了就是传感器输入加电机输出,逻辑并不复杂。51的两个定时器刚好够用:一个做PWM调速,一个做超声波测距计时。外部中断也能应对一些突发情况。相比之下,STM32性能强,但入门门槛高,开发环境配置、寄存器操作复杂得多,对一个以学习为目的的项目来说,反而是个负担。Arduino上手快,但封装太狠,很多底层的东西被隐藏了,学完容易"知其然而不知其所以然"。

如果你是为了学习单片机原理、理解硬件和软件怎么配合,51单片机是很好的起点。这是我自己走过一遍之后最深的体会。

1.3 循迹和避障怎么在一个程序里协同

这是整个项目最核心的设计难点。两个功能单独写都不难,难的是让它们在同一个while循环里和谐工作。

我的方案是建立一个简单的状态机。主循环里先做超声波避障检测,如果检测到障碍物距离小于设定阈值,就进入避障状态,暂停循迹逻辑,执行停车、扫描、转向等动作。如果前方没有障碍物,就恢复循迹模式,小车沿着黑线继续走。

状态机的好处是逻辑清晰,不会出现"既在循迹又在避障"的冲突。实际编写时,我用一个标志位flag_avoid来标记当前是否处于避障状态,避障动作完成后自动清零,再回到循迹。这里有个关键细节:避障转向完成后,小车不一定正好回到黑线上,所以避障结束后需要让小车向前行驶一小段距离,再开始循迹校正。如果直接就切回循迹,很容易因为传感器没检测到黑线而原地打转。这个小细节是我调试时反复观察后总结出来的。

2. 硬件选型与电路连接

2.1 核心器件清单与作用

把这个项目用到的硬件列出来,每样东西都有它不可替代的作用:

器件型号/规格作用
主控单片机STC89C52RC处理传感器信号、控制电机转向和速度
电机驱动模块L298N驱动两个直流减速电机,提供大电流输出
直流减速电机带霍尔编码器或普通减速电机驱动车轮,转速与PWM占空比有关
红外循迹模块TCRT5000(3路)检测地面黑线,输出TTL电平信号
超声波模块HC-SR04测量前方障碍物距离
舵机SG90控制超声波模块左右扫描
电源7.4V锂电池组或4节18650给电机和逻辑电路供电
电压稳压LM2596降压模块或L7805将电池电压稳定到5V给单片机供电
车架3轮或4轮小车底盘承载所有硬件

这里最容易被忽略的是电源部分。L298N电机驱动模块的逻辑供电和电机供电是分开的,电机的瞬间启动电流很大,可能达到1A以上,如果和单片机共用同一路5V电源,会导致电压跌落,单片机复位甚至烧毁。我的做法是:电池组直接给L298N的电机供电端和稳压模块输入端供电,稳压模块输出5V单独给单片机、传感器、舵机供电。

2.2 红外循迹和超声波模块的工作原理

红外循迹模块的核心是TCRT5000,这是一个反射式光电传感器,内部集成了红外发射管和光电晶体管。发射管持续发射红外光,红外光照射到物体表面后反射回来,被光电晶体管接收。物体颜色不同,反射率不同。黑色物体吸收红外光,反射率很低,光电晶体管接收到的光强弱,输出高电平(或低电平,取决于模块电路设计);白色或其他亮色反射率高,输出相反的电平。

循迹模块输出的电平信号直接进单片机IO口,单片机通过读取IO口电平判断当前传感器是压在白线上还是黑线上。3路循迹模块的布局一般是"左、中、右"三个探头,中间探头对准黑线,左右探头分列两侧。当小车跑偏时,某一侧的探头就会压到黑线,输出电平变化,单片机据此调整转向。

超声波模块HC-SR04的工作原理是:Trig引脚收到一个至少10us的高电平脉冲后,模块内部发射8个40kHz的超声波脉冲,同时Echo引脚输出高电平。高电平持续的时间就是超声波从发射到接收回波的时间。距离的计算公式是:距离 = 高电平时间 × 声速340m/s ÷ 2。之所以除以2,是因为时间包含了超声波去和回两个方向的传播距离。

2.3 电路连接与引脚分配

我的引脚分配如下,你可以直接照着接线:

外设信号引脚单片机引脚
左电机PWMENAP1.0
右电机PWMENBP1.1
电机正反转控制IN1/IN2/IN3/IN4P1.2/P1.3/P1.4/P1.5
循迹模块左探头OUT1P2.0
循迹模块中探头OUT2P2.1
循迹模块右探头OUT3P2.2
超声波TrigTrigP2.3
超声波EchoEchoP2.4
舵机信号线PWM信号P2.5
模式切换开关接地或悬空P3.2

有一点要特别提醒:HC-SR04的Echo引脚输出的是5V高电平,而STC89C52RC的IO口是5V兼容的,可以直接连接。如果你用的是3.3V的单片机,那就必须加电阻分压或者电平转换电路了,不然会把单片机烧掉。

舵机的控制信号是50Hz的PWM波,即周期20ms,脉宽0.5ms到2.5ms对应0度到180度。STC89C52RC没有硬件PWM,需要用定时器模拟,后面讲代码的时候会详细说。

3. 核心程序源码解析

3.1 主程序逻辑框架

这份工程文件的代码结构比较清晰,我把主程序的逻辑框架简化描述一下:

void main() { // 初始化定时器、GPIO、串口 Timer0_Init(); // 用于PWM模拟 Timer1_Init(); // 用于超声波计时 GPIO_Init(); while(1) { // 超声波避障检测 float distance = Get_Distance(); if(distance < 20) { // 如果前方20cm内有障碍物 Avoid_Action(); // 进入避障动作 } else { Tracking_Action(); // 正常循迹行驶 } } }

主循环非常简短,核心都在两个函数里。这个设计思路就是我在前面说的状态机逻辑,先用超声波做一个前置判断,再决定走循迹还是避障分支。这种写法虽然简单,但是很有效,尤其适合刚入门的开发者理解程序的整体结构。

有人可能会问,为什么不把两个传感器的数据同时读取然后做加权决策?对于竞赛或者更复杂的场景,确实可以这么做,比如根据障碍物距离动态调整循迹速度,或者用模糊控制算法平滑转向。但对于入门级的51小车来说,先保证功能可靠,再谈优化,这才是务实的路线。

3.2 红外循迹核心代码分析

循迹的核心逻辑就是读取3路传感器的电平状态,然后做出对应的转向控制。3路传感器一共只有8种组合,但实际有效的组合只有几种:

void Tracking_Action() { unsigned char left = P2_0; unsigned char mid = P2_1; unsigned char right = P2_2; // 注意:我的模块是黑线输出低电平,白底输出高电平 if(mid == 0) { // 中间探头压线,小车直行 Motor_Control(SPEED_NORMAL, SPEED_NORMAL); } else if(left == 0) { // 左边探头压线,说明小车向右偏了,需要左转 Motor_Control(SPEED_TURN, SPEED_SLOW); } else if(right == 0) { // 右边探头压线,说明小车向左偏了,需要右转 Motor_Control(SPEED_SLOW, SPEED_TURN); } else { // 都没压线,保持直行(可能刚过弯或者线断了) Motor_Control(SPEED_NORMAL, SPEED_NORMAL); } }

这里面最关键的参数是SPEED_TURNSPEED_SLOW的取值。如果差值太小,转向不够灵敏,小车冲出黑线;如果差值太大,转向过猛,小车会左右剧烈摆动,走蛇形路线。我实验下来,基准速度设为120(PWM占空比约47%),转向时外侧轮120、内侧轮50,效果比较平稳。

还有一个细节,传感器的排列间距要略小于黑线宽度。我用的赛道黑线宽2cm,传感器中心间距1.5cm,这样中间探头始终能覆盖黑线,不会出现在两条线之间来回跳的情况。

3.3 超声波避障核心代码分析

超声波测距的时序很关键,照着数据手册来就不会错:

float Get_Distance() { int time_us = 0; // 发送Trig脉冲 Trig = 1; delay_us(10); Trig = 0; // 等待Echo变高,开始计时 while(Echo == 0); TR1 = 1; // 启动定时器1 // 等待Echo变低,停止计时 while(Echo == 1); TR1 = 0; // 停止定时器1 time_us = TH1 * 256 + TL1; // 定时器每us计一个数(12MHz晶振,单周期指令) distance = time_us * 0.017; // time_us * 340um/2 的简化计算 return distance; }

这里time_us * 0.017是简化后的公式。推导一下:距离(cm) = 时间(us) × 0.034cm/us ÷ 2 = 时间 × 0.017cm/us。也就是说,测得的时间如果是2000us,对应的距离是34cm。

while(Echo == 0);这条语句在实际运行中有一个隐患:如果超声波模块损坏或者没有接好,Echo引脚一直保持低电平,程序就会死等在这里,整辆车失去控制。我在正式代码里加了一个超时跳出的判断:如果定时器溢出标志位被置位,说明超过了65ms还没等到回波,就强制退出并返回一个很大的距离值。这样即使前方没有障碍物或者模块故障,小车也不会卡死。

避障动作函数Avoid_Action的逻辑是:先停车,然后舵机转到左、前、右三个方向分别测距,比较三个方向的障碍物距离:

void Avoid_Action() { // 停车,左右电机停止 Motor_Control(0, 0); // 舵机转到左边测距 Servo_SetAngle(30); delay_ms(200); // 等待舵机稳定 float left_dist = Get_Distance(); // 舵机转到右边测距 Servo_SetAngle(150); delay_ms(200); float right_dist = Get_Distance(); // 舵机回正 Servo_SetAngle(90); delay_ms(200); // 比较距离,转向较空旷的一侧 if(left_dist > right_dist) { // 向左转弯并前进 Motor_Control(SPEED_TURN, SPEED_SLOW); delay_ms(600); } else { // 向右转弯并前进 Motor_Control(SPEED_SLOW, SPEED_TURN); delay_ms(600); } }

这里舵机稳定等待时间不能太短。SG90舵机的响应速度不快,我最初只延时50ms,测出来的距离抖动很大,后来改成200ms才稳定下来。但延时也不能太长,否则小车在路上停太久,不符合实际使用场景。

3.4 PWM调速与舵机转向控制

STC89C52RC没有硬件PWM模块,所有PWM都要靠定时器模拟。我的做法是用定时器0做20ms周期中断,在中断里用一个计数器累加,根据设定的占空比阈值控制输出电平:

void Timer0_ISR() interrupt 1 { TH0 = 0xB8; // 20ms定时初值 TL0 = 0x00; cnt_pwm++; if(cnt_pwm >= 100) cnt_pwm = 0; // 左电机PWM if(cnt_pwm < speed_left) { ENA = 1; } else { ENA = 0; } // 右电机PWM if(cnt_pwm < speed_right) { ENB = 1; } else { ENB = 0; } // 舵机PWM,周期20ms,脉宽0.5ms~2.5ms servoTimer++; if(servoTimer == 1) { ServoPin = 1; ServoCount = servo_pulse_width; // 0~200对应0.5ms~2.5ms } if(servoTimer == ServoCount) { ServoPin = 0; } if(servoTimer >= 200) { servoTimer = 0; } }

定时器0返回的频率是50Hz(周期20ms),我把它分成100份作为电机的PWM精度,也就是说速度可以从0调到100。舵机的脉宽调整范围更大,用0到200的计数范围,每份相当于0.01ms,正好覆盖0.5ms到2.5ms的脉宽范围。

这里要特别注意的是,PWM周期中断里做的事情越多,中断占用时间就越长,主循环的实时性就越差。我一开始在中断里加了串口打印调试信息,结果小车整个控制变得非常卡顿。后来把所有调试信息移到主循环,使用标志位触发打印,问题就解决了。

4. 调试过程与常见问题排查

4.1 调试环境与烧录流程

开发环境用的是Keil C51,编译器版本C51 V9.53,源码文件可以直接用Keil打开编译生成hex文件。烧录工具用STC-ISP,把hex文件通过串口下载到单片机。

很多新手在烧录这一步就卡住了。STC单片机烧录和AVR、PIC不同,需要先点击下载按钮,然后再给单片机上电(冷启动),芯片的引导程序才会上电运行并接收数据。如果你先上电再点下载,STC-ISP软件会一直提示"正在检测单片机"却连不上。

还有USB转串口模块的选择,建议不要用CH340G兼容性差的杂牌模块,用CP2102或者官方的CH340模块都行。我最初用了一个几块钱的PL2303模块,驱动装了三次才识别,而且下载速度只有很低的波特率才稳定,浪费了不少时间。

4.2 循迹失败的原因与调试技巧

循迹小车跑得不稳,最常见的问题不是代码,而是安装和调节。我把踩过的坑归类列出:

问题一:传感器安装高度不合适。TCRT5000的检测距离一般不超过1.5cm,超过这个距离灵敏度急剧下降。我一开始把传感器用铜柱垫高了2.5cm,结果小车在黑线上直接乱跑,完全检测不到。把高度降到1cm左右之后,问题立刻解决。

问题二:阈值没校准。循迹模块上有个电位器可以调灵敏度,顺时针旋转增大检测距离。校准的办法是:把小车放在地面白纸上,用螺丝刀缓慢旋转电位器,直到模块指示灯刚好熄灭或者点亮,然后再把传感器移到黑线上,确认状态反转。这个过程必须在实际地板上进行,不能放在桌面上调,因为不同材料反射率差异很大。

问题三:环境光干扰。阳光直射或者强光环境下,红外接收管会受到干扰,导致输出电平抖动。处理办法是给传感器加一个遮光罩,用热缩管套在发射管和接收管外部,或者用黑色海绵把传感器周边围起来。在室内日光灯下一般没事,但放在窗边调试时就会有明显问题。

问题四:线宽太窄。2cm的黑线是下限了,如果用的是1cm的黑色电工胶带,3路传感器的间距就太大了,很容易冲出赛道。建议至少使用2cm宽的黑线,或者增加传感器的路数,5路循迹模块的容错性会好得多。

4.3 超声波测距不准的排查思路

超声波模块测距出现异常,按照下面的顺序排查:

第一步,确认供电电压。HC-SR04的工作电压是5V,如果供电电压只有4.5V甚至更低,测距会明显偏大,而且不稳定。用万用表测一下模块的VCC引脚,确保在4.8V以上。

第二步,测试盲区。HC-SR04的盲区大约是2cm,也就是说距离小于2cm的障碍物是测不准的。如果用手挡在模块前方很近的地方,测出来的值反而可能非常大,这是正常现象,不是模块坏了。

第三步,检查Echo引脚信号。用示波器或者逻辑分析仪抓Echo引脚的波形,正常情况是:Trig发出10us脉冲后,Echo引脚会输出一个几十到几百微秒的高电平。如果Echo一直低电平,可能是模块损坏;如果Echo高电平时间异常长,可能是回波被多次反射,测量目标表面不平整导致。

第四步,温度影响。声速在空气中的传播速度随温度变化,公式是声速=331.45+0.607×温度℃。在20℃室温下用340m/s没问题,但如果是在炎热夏天或者冷库里,测量误差会比较明显。低成本的解决方法是测量时加一个温度补偿系数,但入门项目通常不需要考虑这一点。

4.4 常见问题速查表

问题现象可能原因解决方法
烧录时提示无法检测单片机未冷启动先点下载,再给单片机上电
小车循迹时左右剧烈摆动转向速度差过大降低SPEED_TURN/SPEED_SLOW的差值
小车直线跑偏两个电机转速不一致用PWM校准,实测慢的那个电机加大占空比
循迹模块指示灯常亮不灭灵敏度调太高逆时针旋转电位器降低灵敏度
超声波测距值跳动很大供电不足或舵机未稳定检查电源,延长稳定延时到200ms
小车原地打转不回线避障结束后未前进距离避障动作后加一个小幅直行延时
舵机不转动PWM频率不对或电源不足确认是50Hz,单独给舵机供电

排查问题的核心思路是"先硬件后软件、先模块后整机"。不要一上来就改代码,先确认每个模块独立工作正常,再逐步组合排查。

调试完成后的一些心得

这个项目做完之后,我最大的感受是:单片机学习最怕"只看不练"。看教程觉得什么都懂了,一上实物就哪哪都是问题。但正因为踩过那些坑,才真正理解了数据手册上那句"Echo高电平时间是超声波往返的时间"背后的含义,理解了中断里不能做耗时操作,理解了电源纹波对传感器的影响。

如果你打算复刻这个项目,我的建议是:先不要急着把循迹和避障一次性做完,分阶段来。第一周把电机转起来,实现PWM调速和前进后退;第二周单独调试循迹,用一根简单的黑色胶带围一个圈,让小车上路跑;第三周再单独测超声波测距,把距离值通过1602液晶显示出来;最后才把两个功能整合。每一步都确认稳了再往下走,整体进度反而更快。

源码里面我保留了完整的注释,包括每个寄存器配置的含义、每个变量作用、每个函数什么时候调用,你可以边看注释边对照这个逻辑框图理解。遇到问题随时翻速查表,大部分坎都能跨过去。

本文还有配套的精品资源,点击获取

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询