51单片机红外测距仪:从三角测距原理到标定实现
2026/9/19 3:53:14 网站建设 项目流程

简介:这是一份以STC89C51单片机为核心、面向电子信息类课程设计与本科毕业设计的红外测距仪设计论文。全文围绕主控制器选择、项目总体设计、硬件电路与软件流程展开,覆盖红外发射接收模块、液晶显示模块、蜂鸣器报警模块的选型与实现,并给出Keil环境下用C语言编写系统主程序和LCD显示程序的思路。设计测量距离为几十厘米,超出范围会触发蜂鸣器报警。系统调试部分区分硬件测试与软件测试,可帮助读者掌握距离测量异常时的排查方法。文末附有参考文献、源程序清单与总体接线图,便于对照原理图理解代码与电路的关系。资源包仅含1个docx文档,大小约442KB,目前已有222人学习,适合需要参考完整课题设计流程与论文格式的单片机初学者及毕业生。

1. 基于单片机的红外测距仪:从原理到落地

红外测距仪这个名字,听起来像是接一个传感器再读几个数字的小玩意,真正动手做的人基本都卡在同一个地方:标定。同一个GP2Y0A21模块,对着白墙和黑色书包,读出的距离能差出好几厘米;上午窗边的阳光和晚上的LED灯下,电压也在悄悄漂移。这篇文章要写的就是一套以51单片机(STC15系列)为核心的模拟量红外测距仪方案,从三角测距原理、传感器输出特性,到信号调理电路、ADC采样固件,再到标定表和误差修正,全部按可复现的方式展开。做完之后的实测范围大约10~80cm(换近距探头也能做到4~30cm),近距离分辨力能到1cm。课程设计、电子竞赛备赛和想把模拟信号链摸透的工程师,都可以按这套思路自己搭一台。

2. 红外测距仪的核心原理与传感器选型

2.1 三角测距和飞行时间测距,课程设计为什么选前者

红外测距有两条技术路线:三角测距和飞行时间测距(ToF)。

三角测距模块内部有一颗红外发光管和一个位置敏感探测器(PSD)。发光管朝目标发射红外光束,反射回来的光经过透镜会聚到探测器表面,目标距离不同,光斑落在探测器上的位置也不同。PSD把光斑位置线性变换成电信号,模块内部再做一次放大和调制解调,最终从OUT引脚输出一个与距离强相关的模拟电压。

飞行时间测距不靠光斑位置,而是测量光脉冲飞出去再反射回来的时间。这种方法需要纳秒级计时电路,通常用I2C接口直接输出距离数字,最典型的是VL53L0X。它精度高、抗干扰强,但价格和调试复杂度都上去了,而且51单片机读它只能算I2C主机操作,无法体现信号处理这条主线。所以“基于单片机的红外测距仪”这类以学习为目的的项目,主流仍然是三角测距加模拟电压,再用ADC还原距离。

2.2 传感器电压输出特性:非线性是最大门槛

最常见的三角测距模块是GP2Y0A21,标称测距范围10~80cm。它的输出电压和距离近似成反比:距离10cm时输出接近2.3V,20cm时约1.9V,40cm时约1.3V,80cm时只剩0.6V左右。曲线在近端陡、远端平,整个区间找不到一个固定比例系数。

这个特性带来的直接问题是:不能把ADC读数乘一个常数当距离。近距离差1mm电压可能跳好几mV,远距离差10mm电压才动一点。实际工程处理思路是“标定加查表”:在几个已知距离上记录实测电压,建立一张表,再用线性插值补出中间值。为什么不拟合一条数学曲线?因为模块里PSD和透镜的装配公差很大,每片的位置偏移都不一样,函数拟合只能满足一片,换一块传感器就得全部重调。

下面这段Python脚本只是用来观察曲线的变化趋势,真实数值要来自实测:

# 示意:距离d(cm)与输出电压V(V)的简化关系 def voltage(d): # 三个系数只代表趋势,不是任何型号的官方参数 a, b, c = 0.0058, 0.004, 0.05 return a / (d - b) + c for d in range(10, 81, 5): print(f"d={d:3d}cm V={voltage(d):.3f}V")

逻辑说明:脚本按反比例形式打印出每5cm对应的电压,可以看到近端每5cm变化几百毫伏,远端几十毫伏都不到。实际项目中不要直接套这三个系数,它们只是为了让初学者明白为什么“线性换算”这条路走不通。

2.3 常见红外测距传感器对比

做设计前先弄清几款常用传感器各自的定位:

型号测距范围输出接口典型精度适合场景
GP2Y0A2110–80cm模拟电压±3cm左右普通课程设计、避障
GP2Y0A41SK4–30cm模拟电压近距离高近距离防撞
GP2Y0A0220–150cm模拟电压远段一般中长距离测量
VL53L0X2–200cmI2C数字需要稳定数字测距

选型的核心依据有两个:一是量程是否覆盖设计目标,二是接口是否匹配51单片机。GP2Y0A21输出模拟电压,直接进单片机ADC,整体信号链短,能完整展示从传感器到数字量的处理流程,是多数课程设计的选择。GP2Y0A41SK适合近距离高精度应用,但量程只有30cm。如果只想快速出数字结果,选VL53L0X也可以,但它和标题里“红外测距仪”的信号处理关系就不大了。

2.4 最终选型:模拟量模块加51核

这一节把结论定下来:GP2Y0A21模块加STC15系列51单片机。GP2Y0A21自带红外调制、PSD和信号处理电路,单片机只需要读取一个模拟电压,开发难度集中在滤波、标定和显示逻辑上,这才是一个“设计型”项目该有的重点。分体式方案(独立红外发射管加接收管)也不是不行,但要自己做38kHz调制、解调和波形整形,周期长、排错难,更适合作为进阶课题而不是主体路线。

3. 红外测距仪的硬件电路:信号调理与单片机采集

3.1 电源和参考电压,决定ADC准不准的一半

GP2Y0A21工作电压4.5~5.5V,典型电流30mA左右。很多开发板的5V是USB直接供给,电机、继电器一动作,VCC上能出现几百毫伏的跌落。传感模块的输出电压是模拟量,电源纹波会毫无衰减地叠在信号上,ADC最终读到的可能根本不是距离而是电源噪声。

给模块供电时,在模块电源引脚附近并联一个100µF电解电容和一个0.1µF陶瓷电容。前者稳住低频波动,后者滤掉高频干扰。单片机ADC的参考电压不要直接挂VCC,最好用一片稳压芯片输出3.3V,接到STC15系列的ADC_VREF引脚。GP2Y0A21输出电压最高约2.5V,3.3V参考既留出余量,分辨率又比5V参考更高;换算系数从5000mV/1023变成3300mV/1023,每档对应约3.2mV。

3.1.1 一个容易踩的参考电压坑

如果单片机ADC参考电压接的是内部LDO输出的2.5V,而GP2Y0A21远端输出低于0.6V,ADC近端满量程就会浪费掉一半。选参考电压前先确认传感器输出范围,再决定是3.0V还是3.3V。STC15部分型号内置参考电压通道,可以省掉外部稳压,但精度和温漂不如独立基准,对误差敏感的项目仍然建议外接。

3.2 传感器输出接入ADC:跟随器还是直接接

GP2Y0A21输出阻抗不算高,单片机ADC在采样瞬间需要信号源提供充电电流,如果模块和单片机之间的引线在10cm以内,直接接到P1.0也能正常工作。但引线一旦拉长,导线分布电容会拖慢信号建立,最好的做法是先做一级电压跟随器再进ADC。

跟随器用LM358就能满足需求,电路结构是运放同相输入端接传感器输出,输出端直接反馈到反相输入端。LM358在输出接近0V时有饱和问题,可能把远距离的小电压垫高一点,所以在输出端对地并一个10kΩ电阻,给输出级一个下拉电流路径。ADC引脚上再并联一个10nF电容到地,和运放输出电阻组成低通滤波,开关电源的高频噪声会被压掉不少。

运放供电建议和模块共用5V,避免模块输出信号接近运放正电源时出现削顶。如果只做单芯片、引线很短,省略跟随器也不会出大问题,但后续接长线或换高阻抗传感器时必须补上。

注意:省略跟随器时,模块OUT到单片机ADC引脚的连线要控制在10cm以内,并远离晶振、PWM线和继电器驱动线。

3.3 LCD1602与报警引脚分配

显示用LCD1602比较稳妥,4线模式占用6个IO口。蜂鸣器用有源蜂鸣器,一个IO口加三极管驱动即可,不需要PWM。下面是推荐引脚分配:

STC15引脚连接对象说明
P1.0模块OUT(经跟随器或直接接)ADC通道0
VCC/GND模块电源就近接电容
P2.0LCD1602 RS寄存器选择
P2.1LCD1602 E使能信号
P2.2–P2.5LCD1602 D4–D74位数据线
P3.7有源蜂鸣器低电平导通

把ADC放在P1.0,是为了避开P3.0/P3.1这两个串口下载引脚,烧录时不冲突。LCD占用的P2口不占用定时器和外部中断,P3.7和定时器0也没有冲突。所有引脚分配完成后,硬件上按这张表焊接,固件里就能直接对应。

3.4 STC15的ADC初始化要点

STC15系列内置10位逐次逼近型ADC,8个通道,初始化总共三件事:设置P1.0为高阻输入、打开ADC电源、配置ADC时钟分频。

void ADC_Init(void) { P1M1 |= 0x01; // P1.0设为高阻输入 P1M2 &= ~0x01; ADC_CONTR = 0x80; // 打开ADC电源,ADC_FLAG清零 CLK_DIV |= 0x20; // 给ADC时钟做分频 } unsigned int Read_ADC(void) { ADC_CONTR = 0x80 | 0x00 | 0x40; // 通道0,启动转换 while (!(ADC_CONTR & 0x10)); // 等待ADC_FLAG=1 ADC_CONTR &= ~0x10; // 清转换完成标志 return (ADC_RES << 2) | (ADC_RESL & 0x03); }

逻辑说明:STC15的10位结果分成高8位和低2位两段存放。启动转换后,硬件在转换结束时把ADC_FLAG置1;读取完成后要立刻清标志,否则下一次while判断会直接误认为转换已经结束。返回语句把高8位左移2位,再加上低2位,拼成一个0~1023的整数。

参数提示:CLK_DIV置位后ADC时钟来自系统时钟分频,具体分频数随型号略有差异,最简单的是先按这个写法测试,若读数在静止目标上还跳字,就把分频再加大一位。ADC输入引脚不要悬空,未用到的通道保持原始配置,否则悬空脚会通过内部寄生电容把噪声耦合进ADC模块。

4. 51单片机固件:从ADC采样到距离映射

4.1 中值滤波:防止单个尖峰毁掉一次测距

GP2Y0A21输出本身平稳,但室内灯管、充电器开关会产生脉冲干扰。一次采样如果落在尖峰上,距离值就会突然跳几厘米。常见的处理是连续采样16次,排序后取中值,这样单个大尖峰最多影响排序结果的位置,不会直接变成输出值。

unsigned int Median_Filter(void) { unsigned char i, j; unsigned int t; unsigned int tmp[16]; for (i = 0; i < 16; i++) { tmp[i] = Read_ADC(); // 连续取16个原始值 } for (i = 0; i < 15; i++) { // 冒泡排序,升序 for (j = i + 1; j < 16; j++) { if (tmp[i] > tmp[j]) { t = tmp[i]; tmp[i] = tmp[j]; tmp[j] = t; } } } return tmp[8]; // 取中间位置的值 }

逻辑说明:16个元素排序后,tmp[8]落在中间位置,虽然不是数学上严格的中位数,但对消除尖峰来说已经足够。数组占用32字节RAM,STC15系列最少也有256字节内部RAM,不会紧张。如果目标移动很快,16次采样期间距离可能已经变化,可以把次数降到8次,代价是滤波效果弱一些。

注意:每次调用Read_ADC之间建议加1ms左右的间隔,让模块内部AGC有重建时间。这个延时放在主循环里由定时器控制,不要放在滤波函数里阻塞其他逻辑。

4.2 电压-距离查表与线性插值

51单片机的浮点运算效率低,算一个反比例公式要耗掉不少指令周期。设计型项目更常见的做法是标定一张表,再在表上做线性插值。下表是一组GP2Y0A21的实测标定记录示例:

距离(cm)输出电压(mV)
102300
152000
201750
301500
401300
601000
80600

表中电压是典型值,每个独立模块都要重新标定。实际标定时把目标从10cm开始,每5cm固定一次,等显示稳定后记录一组ADC换算电压,存入EEPROM。查表函数先把电压换算成毫伏,再在相邻两个标定点之间做比例缩放:

unsigned char Lookup(unsigned int mv) { static const unsigned int tab_mv[7] = {2300, 2000, 1750, 1500, 1300, 1000, 600}; static const unsigned char tab_cm[7] = {10, 15, 20, 30, 40, 60, 80}; unsigned char i; if (mv >= tab_mv[0]) return tab_cm[0]; // 太近,截到10cm if (mv <= tab_mv[6]) return tab_cm[6]; // 太远,截到80cm for (i = 0; i < 6; i++) { if (mv <= tab_mv[i] && mv > tab_mv[i+1]) { unsigned int rangemv = tab_mv[i] - tab_mv[i+1]; unsigned int rangcm = tab_cm[i+1] - tab_cm[i]; unsigned int delta = tab_mv[i] - mv; unsigned int rate = (unsigned long)delta * rangcm / rangemv; return tab_cm[i] + rate; } } return tab_cm[6]; }

逻辑说明:如果当前电压落在2300mV和2000mV之间,程序先算出这段电压区间rangemv,再算出当前点距上一点的差值delta,按距离差的比例算出插值距离。比如2100mV时,delta=2300-2100=200,rangemv=300,rangcm=5,rate=200*5/300=3,返回10+3=13cm。靠近表两端的极端值直接截断到边界,避免越界。

参数说明:(unsigned long)delta * rangcm是32位中间量,即使表项增加也不会溢出,这是16位单片机做插值时最容易忽略的坑。如果完全不做查表而是现场用浮点算,代码量省但运行时间变长;查表法速度快、逻辑直观,也方便后续换表头。

4.3 LCD1602显示与阈值报警

LCD1602用4位数据模式,每次传一个字节要分两次写高4位和低4位。先写底层数据写入函数:

void Lcd_WriteData(unsigned char dat) { LCD_RS = 1; // 数据模式 LCD_E = 0; Delay10us(); LCD_D4 = (dat >> 4) & 0x01; // 先送高4位 LCD_D5 = (dat >> 5) & 0x01; LCD_D6 = (dat >> 6) & 0x01; LCD_D7 = (dat >> 7) & 0x01; LCD_E = 1; Delay10us(); LCD_E = 0; // E下降沿锁存数据 LCD_D4 = dat & 0x01; // 再送低4位 LCD_D5 = (dat >> 1) & 0x01; LCD_D6 = (dat >> 2) & 0x01; LCD_D7 = (dat >> 3) & 0x01; LCD_E = 1; Delay10us(); LCD_E = 0; }

逻辑说明:4位模式把一个字节拆成两次,第一次D4-D7送高4位,第二次送低4位;每次E引脚产生一个下降沿指示LCD锁存,RS=1表示写数据寄存器。LCD_D4~LCD_D7对应P2.2~P2.5,LCD_RS对应P2.0,LCD_E对应P2.1,在头文件里用sbit定义即可。

距离刷新主体调用这个底层函数:

void Show_Distance(unsigned int d_cm) { unsigned char buf[5]; buf[0] = (d_cm / 100) == 0 ? ' ' : '0' + d_cm / 100; buf[1] = '0' + (d_cm % 100) / 10; buf[2] = '0' + d_cm % 10; buf[3] = 'c'; buf[4] = 'm'; Lcd_WriteData(buf[0]); Lcd_WriteData(buf[1]); Lcd_WriteData(buf[2]); Lcd_WriteData(buf[3]); Lcd_WriteData(buf[4]); }

逻辑说明:d_cm按0~999设计,百位为0时显示空格,避免出现“012cm”这类多余位数。LCD1602内部显示地址每写一个字符自动加1,所以连续写5个字符就能覆盖原来的距离。如果传感器量程到150cm,这里三位数足够,不需要处理四位数。

阈值报警可以复用这帧数据的判断结果:

#define ALARM_CM 30 if (d_cm < ALARM_CM) { BUZZER = 0; // 有源蜂鸣器低电平导通 } else { BUZZER = 1; }

BUZZER接在P3.7,经三极管驱动。有源蜂鸣器内部自带振荡源,高低电平就能控制开关,不需要像无源蜂鸣器那样给PWM波形。阈值30cm只是示例,改成40cm就变成倒车雷达逻辑,改成5cm就是近距离防撞提示。

4.4 主循环节奏与时间基准

测距值每秒刷4~10次比较合适,刷新太快人眼看不出变化,反而显得数字在跳。主循环用一个100ms标志位控制采样节奏:

volatile unsigned char flag_100ms; unsigned int mv; while (1) { if (flag_100ms) { flag_100ms = 0; // 参考电压为3300mV时,把ADC值换算成毫伏 mv = (unsigned long)Median_Filter() * 3300UL / 1023; cm = Lookup(mv); Show_Distance(cm); if (cm < ALARM_CM) { BUZZER = 0; } else { BUZZER = 1; } } }

逻辑说明:3300UL / 1023是3.3V参考电压下的换算系数,若换回5V参考就要改成5000。时间基准用定时器0产生100ms中断并把flag置1,主循环不阻塞等待,显示和报警逻辑可以一直响应。

5. 标定与误差处理:把测距结果拉回厘米以内

5.1 标定姿势与多点插值

拿到新模块后不要急着写显示代码,先把传感器固定在稳定的支架上,对准平整白墙,从10cm开始每5cm记录一次电压。记录时让目标静止2秒再取值,模块内部AGC需要时间稳定。多点标定后把表写进EEPROM,开机自动装载。如果想批量生产或者后续换模块,可以做一个按键标定模式:进入标定后每按一次按键,记录当前距离的一个点,操作员负责移动直尺,单片机负责打点,这样效率会高很多。

5.2 反射率和环境光的修正

红外测距看的是反射光强度,物体颜色和材质直接影响结果。黑色绒布反射率只有个位数,白色纸张接近90%,同一距离输出电压差几百毫伏。模块自带同步检波电路对直流环境光有免疫力,但阳光直射和强红外源仍会造成漂移。解决思路有三:测量面保持平整并接近垂直入射;必要时在传感器窗口前加850nm窄带滤光片;最关键的是标定必须在最终安装位置完成,不要拿厂家数据表上的曲线当自己的标准。

5.3 温度漂移的实用补偿

红外LED发射效率和PSD暗电流都随温度变化,同一距离冬天和夏天可能差出1%~2%。对课程设计来说,最省事的做法是上电先运行5分钟再标定,让模块自身温度稳定,之后连续测量的漂移就很小。如果做长期运行的设备,在传感器附近放一颗DS18B20,按每摄氏度0.3%左右做一个输出修正系数,系数用空调房或恒温箱先测一轮即可。

5.4 一叠A4纸验证法

最后给一个每位读者都能复现的验证方法:用一叠A4纸当反射面,100张约1cm厚。把传感器固定在支架上,窗口朝下对准纸堆,先垫几百张让反射面进入量程,然后每增加100张让反射面靠近1cm。重点看两件事:全程是否单调递减,相邻两点显示差是否接近1cm。如果中间出现回跳,先调大滤波次数;如果整体都偏小或偏大,优先检查参考电压换算系数和实际基准电压是否一致。

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

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

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

立即咨询