news 2026/8/2 22:58:38

红外接近传感器实战指南:从三角测量原理到Arduino/树莓派应用

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
红外接近传感器实战指南:从三角测量原理到Arduino/树莓派应用

1. 项目概述:从“Grove - 80cm 红外接近传感器”说起

最近在整理工作室的传感器库存,翻出了几片Seeed Studio的Grove - 80cm红外接近传感器。这玩意儿在创客圈子里算是个经典老伙计了,但每次用它做项目,总感觉它的潜力被低估了。很多人拿到手,接上Arduino或者树莓派,跑个官方示例代码,看到串口打印出距离值,就觉得“哦,会用了”,然后可能就把它丢在一边,去玩更酷的摄像头或者激光雷达了。这其实挺可惜的。

这个传感器本质上是一个主动式红外(IR)接近传感器,核心原理是三角测量法。它内部有一个红外LED发射特定波长的红外光,当光线遇到前方物体反射回来,会被一个位置敏感探测器(PSD)接收。物体距离不同,反射光在PSD上成像的位置就不同,通过计算这个位置的变化,就能换算出距离。它的标称探测范围是10cm到80cm,这个距离对于很多非接触式检测、避障、物体存在感知的应用来说,其实刚刚好。比超声波传感器响应快、不受声音干扰,比激光雷达成本低、电路简单,比简单的红外对管能提供连续的距离值而非简单的开关量。

我之所以想专门聊聊它,是因为在实际项目中,我发现用好这个传感器,远不止“读取一个数值”那么简单。它的输出特性、环境干扰、安装方式,乃至供电的稳定性,都会直接影响最终项目的可靠性和精度。比如,用它做一个小型AGV(自动导引车)的前向避障,或者做一个智能垃圾桶的自动开盖感应,甚至是做一个展览馆的互动展项——当观众靠近某个区域时触发灯光或音效。在这些场景里,你需要的不是一个“大概”的距离,而是一个稳定、可靠、可重复的检测信号。

所以,这篇内容,我想从一个资深折腾者的角度,带你重新认识这个看似简单的Grove模块。我们会拆开它的数据手册,看看输出曲线到底长什么样;会聊聊怎么用代码去校准和补偿,让数据更“听话”;会分享几种不同的滤波算法在实际单片机上的实现和取舍;当然,也少不了那些我踩过的坑,比如为什么同样的代码,晚上测和白天测数据会飘,以及怎么解决。无论你是刚接触Arduino的新手,还是正在为某个产品原型寻找合适传感器的工程师,希望这些从实战中抠出来的细节,能帮你把这个80cm的小家伙用出“花”来。

2. 核心原理与硬件接口深度解析

要玩转一个传感器,第一步永远是读懂它。对于Grove - 80cm红外接近传感器(以GP2Y0A21YK0F或其兼容型号为核心),我们不能满足于“它能量距离”这个结论,得搞清楚它是怎么量的,以及这个“量”的过程带来了哪些特性与限制。

2.1 三角测量法:光学的尺子

这个传感器的核心是光学三角测量原理。你可以把它想象成一个微缩版的测距仪。模块上的那个小窗口,里面并排“坐”着两个核心部件:一个是红外发射管(IR LED),另一个是位置敏感探测器(PSD),它们之间有一个固定的、微小的基线距离。

工作流程是这样的:

  1. 发射:红外LED发出一束经过调制的红外光(通常频率在几kHz到几十kHz),调制是为了减少环境光(尤其是日光灯)的干扰。
  2. 反射:这束光打在前方的物体上,发生漫反射。
  3. 接收:一部分反射光会返回到传感器窗口,被那个PSD接收。PSD不是一个简单的光敏二极管,它是一个可以检测光点在其感光面上精确位置的器件。
  4. 计算:物体距离不同,反射光返回的角度就不同,导致光点在PSD上成像的位置也不同。距离近,光点位置靠一侧;距离远,光点位置靠另一侧。传感器内部的专用集成电路(ASIC)就是通过测量PSD上光电流的分布,计算出光点的位置,进而通过内置的几何光学模型,换算出物体到传感器的直线距离。

这种方法的优点是成本相对较低,且能输出模拟电压值,与距离成一定的反比关系。但它也有明显的物理限制:探测角度很小(通常只有几度),因此对物体的对齐要求较高;对于黑色或吸光材料,反射信号弱,探测距离会急剧缩短甚至失效;强环境光(特别是含有红外成分的阳光)会直接淹没微弱的调制信号,导致传感器“失明”或数据跳动。

2.2 接口与电气特性:不仅仅是Grove四针

这个模块采用了标准的Grove 4针接口(VCC, GND, NC, SIG)。但请注意,那个标着“NC”(Not Connected)的引脚,在某些版本的模块或不同的传感器芯片上,可能并非真正悬空。对于GP2Y0A21,它可能连接着芯片的开关控制脚。但在标准的Grove封装里,我们通常只使用VCC(5V)、GND和SIG(信号线)这三根线。

关键电气参数:

  • 工作电压:典型值为4.5V至5.5V。这里有个大坑:虽然很多开发板(如Arduino Uno)的“5V”引脚输出接近5V,但如果你使用一些廉价的USB电源或者电量不足的移动电源,这个电压可能掉到4.7V甚至更低。红外LED的发射功率和内部运放的参考电压都会随供电电压波动,导致输出距离值发生系统性偏移。我的经验是,务必确保供电稳定在5.0V±0.1V以内,这是获得可重复数据的基础。
  • 输出信号:模拟电压输出。这是重点中的重点。它的输出不是与距离成线性正比或反比,而是一条复杂的曲线。大致规律是:距离越近,电压越高;距离越远,电压越低。但在整个10cm-80cm量程内,电压变化并非均匀。
  • 输出曲线与数据处理:官方数据手册会提供一张“距离-输出电压”的特性曲线图。这张图告诉我们,在中间段(比如20cm-60cm),电压变化相对线性,斜率较陡,分辨率较高。而在非常近(<10cm)和非常远(>70cm)的区域,曲线变得平缓,电压变化很小,这意味着在这些区域传感器的分辨率和精度会下降。直接使用电压 = k / 距离这种简单公式是极不准确的。正确的做法是:要么根据曲线图进行分段线性拟合,要么使用查表法,要么用多项式来拟合整个曲线。我会在下一章给出具体的代码实现。

2.3 与常见传感器的对比

为什么在很多场景下选它,而不是别的?我们快速对比一下:

传感器类型原理测距范围/方式优点缺点适用场景
红外接近传感器 (如GP2Y0A21)三角测量10cm - 80cm (模拟量)成本低,体积小,响应快,不受声波干扰受物体颜色、材质、环境光影响大,非线性输出室内避障、物体存在检测、互动装置
超声波传感器 (如HC-SR04)声波飞行时间2cm - 400cm (数字脉冲)不受光线、颜色影响,价格低廉受温度、湿度影响,对柔软/斜面物体检测不佳,响应慢通用测距、液位检测
激光测距模块 (如VL53L0X)飞行时间几厘米到数米 (I2C数字)精度高,响应快,几乎不受环境光影响成本高,对透明玻璃等物体可能失效高精度定位、手势识别、机器人导航
红外对管/光电开关反射光强度开关量 (有/无)电路最简单,成本极低无法测距,检测距离短且不稳定计数、限位、黑白线循迹

选择Grove - 80cm红外传感器的理由很明确:当你需要一段中短距离的、连续的距离信息,且项目预算有限、对绝对精度要求不是极端苛刻(厘米级即可),同时工作环境是室内光线可控时,它是一个非常平衡和实用的选择。

3. 从基础读取到高级滤波:Arduino实战代码全解

理论懂了,接下来就是动手。我们以最普及的Arduino平台为例,从最简单的读数开始,一步步构建一个稳健可靠的红外测距系统。

3.1 基础连接与原始数据读取

硬件连接简单到不能再简单:用一根Grove连接线,将传感器模块连接到Arduino的任意一个模拟输入端口(例如A0)。如果没有Grove扩展板,你也可以用杜邦线直接连接:红线接5V,黑线接GND,黄线(或白线,即SIG)接A0。

第一段代码,我们的目标是看到最原始的数据:

// 示例1: 读取原始模拟值 const int sensorPin = A0; // 传感器连接在A0引脚 void setup() { Serial.begin(9600); // 初始化串口通信 } void loop() { int rawValue = analogRead(sensorPin); // 读取原始值 (0-1023) float voltage = rawValue * (5.0 / 1023.0); // 转换为电压值 (0-5V) Serial.print("Raw: "); Serial.print(rawValue); Serial.print(" | Voltage: "); Serial.print(voltage); Serial.println(" V"); delay(100); // 延时100毫秒,避免串口数据刷屏 }

上传代码,打开串口监视器,你就能看到不断刷新的数值。用手或白纸在传感器前移动,观察数值变化。这时你会发现几个问题:

  1. 数值即使在物体静止时也在小范围跳动(噪声)。
  2. 数值和距离的关系不明显,需要转换。
  3. 靠近传感器最近端(<10cm)时,数值可能变化混乱,因为超出了有效量程。

3.2 电压到距离的转换:拟合才是王道

如前所述,直接套用简单公式误差很大。我们需要根据数据手册的曲线进行拟合。以GP2Y0A21YK0F的典型曲线为例,在20-150cm范围内(注意,Grove模块标称80cm,但芯片潜力不止于此),一个经验公式是:

距离 (cm) = 10650.08 * pow(电压, -0.935) - 10

但这个公式在近端和远端误差较大。更通用的方法是分段线性拟合多项式拟合。这里我提供一个经过实测调整、在10-80cm内相对准确的多项式拟合方法(适用于输出电压在0.4V-2.5V之间):

// 示例2: 使用多项式拟合将电压转换为距离 const int sensorPin = A0; // 多项式拟合系数,根据实际传感器校准微调 const float A = 69.172; const float B = -1.0; // 注意是负号 const float C = 0.005; void setup() { Serial.begin(9600); } void loop() { int raw = analogRead(sensorPin); float voltage = raw * (5.0 / 1023.0); // 假设供电为精确5.0V // 确保电压在有效范围内,避免计算异常 if (voltage < 0.4 || voltage > 2.5) { Serial.println("Out of range!"); delay(500); return; } // 使用公式: Distance = A * voltage^B + C // 注意:这是其中一种拟合形式,B为负指数表示距离与电压成反比 // 更常见的是一次或二次多项式拟合,例如: // float distance_cm = 60.0 * pow(voltage, -1.0); // 简单反比 // 下面是一个二次多项式拟合示例(系数需自行校准): // float distance_cm = p2 * voltage * voltage + p1 * voltage + p0; // 这里提供一个我校准过的、在15-70cm效果较好的公式: // 距离 = 26.549 * V^(-1.2091) (其中V是电压) float distance_cm = 26.549 * pow(voltage, -1.2091); Serial.print("Voltage: "); Serial.print(voltage, 3); // 显示3位小数 Serial.print(" V | Distance: "); Serial.print(distance_cm, 1); // 显示1位小数 Serial.println(" cm"); delay(200); }

重要提示:没有任何一个公式是普适的。不同批次的传感器、不同的供电电压、甚至不同的环境温度,都会导致特性曲线发生微小偏移。因此,校准是必须的。最靠谱的方法是:在目标工作环境下,用尺子实际测量几个已知距离点(如20cm, 40cm, 60cm),记录下对应的电压值,然后用Excel或在线工具进行曲线拟合,得到属于你自己这个传感器的拟合系数。这才是工程实践。

3.3 软件滤波:让数据“沉稳”下来

原始数据的跳动会严重影响判断。我们需要滤波。对于嵌入式系统,以下几种滤波算法实用且高效:

1. 滑动平均滤波:最简单,能有效抑制随机噪声,但会引入滞后(响应变慢)。

// 示例3: 滑动平均滤波 const int sensorPin = A0; const int numReadings = 10; // 平均窗口大小 float readings[numReadings]; // 存储读数的数组 int readIndex = 0; float total = 0; float average = 0; void setup() { Serial.begin(9600); for (int i = 0; i < numReadings; i++) { readings[i] = 0; // 初始化数组 } } void loop() { // 减去最早的读数,加上最新的读数 total = total - readings[readIndex]; int raw = analogRead(sensorPin); readings[readIndex] = raw * (5.0 / 1023.0); // 存储电压值 total = total + readings[readIndex]; readIndex = (readIndex + 1) % numReadings; // 循环索引 average = total / numReadings; // 使用average进行距离换算... Serial.println(average, 3); delay(50); // 采样间隔 }

2. 中值滤波:脉冲噪声(偶尔出现的跳变极大/极小值)有奇效。原理是连续采样N次,排序后取中间值。

// 示例4: 中值滤波(简易版,取3次采样) float medianFilter(float newVal) { static float buf[3] = {0}; static byte index = 0; float temp[3]; buf[index] = newVal; index = (index + 1) % 3; // 复制到临时数组排序 temp[0] = buf[0]; temp[1] = buf[1]; temp[2] = buf[2]; // 简单的冒泡排序找中值 if (temp[0] > temp[1]) swap(temp[0], temp[1]); if (temp[1] > temp[2]) swap(temp[1], temp[2]); if (temp[0] > temp[1]) swap(temp[0], temp[1]); return temp[1]; // 返回中值 }

3. 一阶低通滤波(指数加权平均):在响应速度和平滑度之间取得很好的平衡,计算量小,非常适合单片机。

// 示例5: 一阶低通滤波 float lowPassFilter(float newVal) { static float filteredVal = 0; const float alpha = 0.2; // 滤波系数 (0~1),越小越平滑,滞后越大 filteredVal = alpha * newVal + (1 - alpha) * filteredVal; return filteredVal; }

我的实战建议:对于红外接近传感器,我通常采用“中值滤波” + “一阶低通滤波”的组合拳。先用中值滤波(窗口为3或5)干掉那些偶尔出现的尖峰毛刺,再将结果送入一阶低通滤波(alpha取0.3~0.5),这样得到的数据既平滑又及时。滑动平均在需要非常平稳的数据且对实时性要求不高的场合可以用。

4. 树莓派上的Python驱动与系统集成

在树莓派上使用这个传感器,逻辑是相通的,但环境变成了Linux和Python。我们不再读取模拟电压(树莓派GPIO没有原生ADC),而是需要借助一个模数转换器(ADC),比如最常见的PCF8591(I2C接口)或ADS1115(精度更高,也是I2C)。这里以PCF8591为例,因为它价格低廉且足够用于此类应用。

4.1 硬件连接与I2C启用

首先,将Grove传感器连接到PCF8591的模拟输入通道(例如AIN0)。PCF8591的VCC接树莓派3.3V或5V(注意电平兼容,PCF8591是5V器件,但其I2C总线是3.3V电平,需确认模块是否有电平转换,通常Grove模块已处理),SDA接GPIO2, SCL接GPIO3。

在树莓派上,需要先启用I2C接口:

sudo raspi-config # 选择 Interfacing Options -> I2C -> Yes

重启后,安装必要的工具和库:

sudo apt update sudo apt install i2c-tools python3-smbus sudo i2cdetect -y 1 # 查看I2C设备地址,PCF8591通常是0x48

4.2 Python驱动代码实现

下面是一个完整的Python示例,包含读取、滤波和距离转换:

# 示例6: 树莓派通过PCF8591读取红外传感器 (Python) import smbus2 import time import math # 初始化I2C总线 bus = smbus2.SMBus(1) # 树莓派版本2及以后使用总线1 PCF8591_ADDR = 0x48 # PCF8591的默认地址 AIN_CHANNEL = 0 # 模拟输入通道0 # 滤波参数 ALPHA = 0.3 filtered_voltage = 0.0 def read_analog(channel): """从PCF8591指定通道读取模拟值 (0-255)""" # PCF8591控制字:启用模拟输出,选择输入通道 control_byte = 0x40 | (channel & 0x03) bus.write_byte(PCF8591_ADDR, control_byte) # 第一次读是上一次的转换结果,丢弃 bus.read_byte(PCF8591_ADDR) # 第二次读才是当前通道的值 value = bus.read_byte(PCF8591_ADDR) return value def voltage_to_distance(voltage): """将电压值转换为距离 (cm),使用校准后的公式""" if voltage < 0.4 or voltage > 2.5: return None # 超出有效范围 # 使用一个经验公式,需要根据实际校准 # distance = k / (voltage - b) 或多项式 # 这里使用一个近似公式,假设供电5V,ADC参考电压5V distance_cm = 26.549 * (voltage ** -1.2091) return distance_cm try: while True: # 1. 读取原始值 raw = read_analog(AIN_CHANNEL) # 0-255 # 2. 转换为电压 (假设PCF8591参考电压为5V) voltage = raw * (5.0 / 255.0) # 3. 应用一阶低通滤波 filtered_voltage = ALPHA * voltage + (1 - ALPHA) * filtered_voltage # 4. 转换为距离 distance = voltage_to_distance(filtered_voltage) if distance: print(f"Raw: {raw:3d} | Voltage: {filtered_voltage:.3f} V | Distance: {distance:.1f} cm") else: print("Out of valid range") time.sleep(0.1) # 采样间隔 except KeyboardInterrupt: print("\nProgram stopped.") finally: bus.close()

4.3 集成到更复杂的系统中

在树莓派上,你可以轻松地将这个测距功能集成到更大的应用中。例如,结合Flask或FastAPI创建一个简单的Web API,实时返回距离数据;或者使用PyGame创建一个视觉化的距离监控界面;再或者,当检测到距离小于某个阈值时,触发一个系统命令(如播放声音、发送网络请求、控制GPIO引脚点亮LED或驱动继电器)。

# 示例7: 简单的阈值触发示例 import RPi.GPIO as GPIO LED_PIN = 17 THRESHOLD_CM = 30.0 GPIO.setmode(GPIO.BCM) GPIO.setup(LED_PIN, GPIO.OUT) # ... 在之前的循环中加入 ... current_distance = voltage_to_distance(filtered_voltage) if current_distance and current_distance < THRESHOLD_CM: GPIO.output(LED_PIN, GPIO.HIGH) # 物体靠近,点亮LED else: GPIO.output(LED_PIN, GPIO.LOW) # 物体远离,熄灭LED

树莓派的强大之处在于其丰富的软件生态,你可以用几行代码就将一个硬件传感器数据接入到数据库(如InfluxDB)、消息队列(如MQTT)或图形化仪表盘(如Grafana)中,构建出真正可用的物联网原型。

5. 避坑指南与性能优化实战经验

纸上得来终觉浅,绝知此事要踩坑。下面这些经验,都是我在多个项目里真金白银(和时间)换来的。

5.1 环境光干扰:最大的敌人

红外传感器最怕强环境光,尤其是含有红外成分的光源(太阳光、白炽灯)。对策:

  • 物理遮挡:给传感器做一个遮光罩,就像相机镜头遮光罩一样,只允许正前方的光进入。可以用热缩管、黑色电工胶带或者3D打印一个小筒。
  • 软件判断:在代码中加入“环境光基准”测量。先关闭传感器的红外发射(如果硬件支持),或者在一个已知无物体的距离下读取一个“环境光本底”电压值。在实际测量时,如果读数非常接近甚至低于这个本底值,说明环境光太强,当前数据不可信,应丢弃或标记为无效。
  • 选择调制频率:有些高级的红外测距芯片允许选择调制频率,以避开环境中常见的干扰源(如50/60Hz的工频干扰)。但Grove模块通常固定了频率。
  • 终极方案:如果必须在阳光直射下使用,建议换用超声波或激光方案。红外方案更适合室内或光线稳定的环境。

5.2 被测物体特性:不是所有东西都能测

  • 颜色与材质:黑色物体吸收红外光,反射极少,探测距离会大幅缩短甚至为零。光滑的镜面可能会将光线反射到其他方向,导致测距失败。解决方法是在项目设计阶段就明确检测对象,并进行实测。如果必须检测黑色物体,可以尝试在物体上贴一小块白色漫反射贴纸。
  • 大小与形状:传感器的小探测角意味着它需要一个“足够大”的物体来反射回足够的光线。检测细线或非常小的物体时,信号会很弱。对于不规则物体,距离读数可能会因为反射面角度不同而波动。

5.3 供电与接地:稳定是一切的基础

  • 电源噪声:电机、舵机、继电器等感性负载在开关时会产生巨大的电压尖峰,通过电源线干扰传感器的模拟输出。务必为传感器模块使用独立的、经过滤波的5V供电,或者至少在传感器电源引脚就近并联一个10uF和0.1uF的电容进行去耦。
  • 共地问题:确保单片机(Arduino/树莓派)和传感器模块的GND是连接在一起的,并且连接线要粗短,避免地线噪声。

5.4 安装与机械结构

  • 避免振动:传感器本身应牢固安装。任何微小的振动都可能导致其与目标物体的相对距离发生变化,或者内部元件产生微音效应,引入噪声。
  • 镜头清洁:传感器前面的透明窗口要保持清洁,灰尘和污渍会严重影响透光率。
  • 避免相互干扰:如果系统中有多个红外传感器,不要让它们的发射光束直接交叉照射到对方的接收器上,否则会产生串扰。可以错开安装位置,或者分时工作(一个发射时,其他的关闭)。

5.5 校准:没有校准,就没有精度

再次强调校准的重要性。建立一个简单的校准工装:将传感器固定,前方放置一个可精确移动的平板(最好是白色的)。从20cm开始,每5cm或10cm记录一次稳定的电压读数。将“距离-电压”数据录入Excel,用散点图画出,然后添加趋势线,选择“幂函数”或“多项式”,并显示公式。将这个公式的系数直接用到你的代码里。这个过程只需做一次,但能极大提升你整个项目的可靠性。

6. 创意项目应用实例:从想法到实现

掌握了原理、代码和避坑技巧,这个传感器能做什么?它的应用远超简单的“测距”。下面分享两个我做过或指导过的有趣项目,希望能激发你的灵感。

6.1 项目一:非接触式智能手势开关

目标:在传感器前方一段距离内(如15-30cm),用手掌左右挥动来控制开关(如台灯)。

实现思路

  1. 状态机设计:这不是简单的阈值判断。我们需要检测一个“挥手”的动态过程。可以设计一个简单的状态机:
    • 状态0(空闲):距离 > 30cm。
    • 状态1(手进入):距离进入15-30cm区间,记录进入时间。
    • 状态2(挥手判断):持续监测距离变化。如果距离在短时间内(如300ms)先减小后增大(或先增大后减小),形成一个“V”形或“^”形波形,则判定为一次挥手。
    • 状态3(触发动作):根据挥手方向(先近后远,或先远后近),触发不同的动作(开灯/关灯)。
  2. 关键代码片段
    enum GestureState { IDLE, ENTERED, MONITORING }; GestureState state = IDLE; unsigned long enterTime = 0; float prevDistance = 0; const int ENTER_ZONE_MAX = 30; const int ENTER_ZONE_MIN = 15; const unsigned long GESTURE_TIMEOUT = 500; // ms void loop() { float currentDistance = getFilteredDistance(); // 你的滤波后距离函数 switch(state) { case IDLE: if (currentDistance > ENTER_ZONE_MIN && currentDistance < ENTER_ZONE_MAX) { state = ENTERED; enterTime = millis(); prevDistance = currentDistance; } break; case ENTERED: if (millis() - enterTime > 100) { // 短暂稳定后进入监控 state = MONITORING; } break; case MONITORING: if (millis() - enterTime > GESTURE_TIMEOUT) { // 超时,重置 state = IDLE; } else { float delta = currentDistance - prevDistance; // 简单的趋势判断:连续两个采样点变化方向相反? // 这里需要更复杂的逻辑,例如记录一个小的距离数组来判断峰值 if (/* 检测到先近后远的波形 */) { triggerGesture(GESTURE_LEFT_TO_RIGHT); state = IDLE; } else if (/* 检测到先远后近的波形 */) { triggerGesture(GESTURE_RIGHT_TO_LEFT); state = IDLE; } prevDistance = currentDistance; } break; } delay(50); }
  3. 优化:为了更可靠,可以引入一个包含4-5个最近距离值的循环数组,通过分析这个数组的局部极值来判断手势方向,而不是仅仅依赖前后两个点。

6.2 项目二:桌面物品存在检测与自动化

目标:在办公桌上,检测水杯是否放在指定位置,如果离开超过一定时间,则自动锁屏(提醒你离开);或者检测手机是否放在无线充电座上。

实现思路

  1. 安装:将传感器朝上安装在桌面下方或侧面,使其光束垂直或倾斜照射到待检测区域。
  2. 基线学习:系统启动后,先学习“空置”状态的距离值(baselineDistance)。这个值应该是桌面或充电座表面的距离。
  3. 物体检测:当有物体(水杯、手机)放入时,距离会显著缩短。设置一个阈值DETECTION_THRESHOLD(如比基线小3cm)。
  4. 状态保持与超时:一旦检测到物体,开启一个计时器。如果物体被取走(距离恢复到基线附近),计时器停止。如果物体一直存在,计时器持续累加。当计时器超过预设的“超时时间”(如10分钟),则触发动作(通过串口发送锁屏命令到电脑,或点亮一个提醒LED)。
  5. 防误触发:加入去抖逻辑。只有当距离持续低于阈值超过1秒钟,才判定为“物体放入”;同样,持续高于阈值超过1秒,才判定为“物体取走”。
// 示例8: 物品存在检测与超时提醒(简化逻辑) float baselineDist = 0.0; bool objectPresent = false; unsigned long presentStartTime = 0; const unsigned long TIMEOUT_MS = 10 * 60 * 1000; // 10分钟 const float DETECT_THRESHOLD_CM = 3.0; // 检测阈值 void setup() { // ... 初始化 ... learnBaseline(); // 学习基线距离 } void loop() { float dist = getFilteredDistance(); if (!objectPresent) { if (dist < (baselineDist - DETECT_THRESHOLD_CM)) { // 可能检测到物体,启动去抖计时 delay(1000); // 简单去抖,实际应用应用状态机 dist = getFilteredDistance(); // 再次确认 if (dist < (baselineDist - DETECT_THRESHOLD_CM)) { objectPresent = true; presentStartTime = millis(); Serial.println("Object placed."); } } } else { if (dist > (baselineDist - DETECT_THRESHOLD_CM/2)) { // 物体可能被取走 delay(1000); dist = getFilteredDistance(); if (dist > (baselineDist - DETECT_THRESHOLD_CM/2)) { objectPresent = false; Serial.println("Object removed."); } } else { // 物体仍在,检查超时 if (millis() - presentStartTime > TIMEOUT_MS) { triggerTimeoutAction(); // 例如,锁屏 presentStartTime = millis(); // 重置计时器,避免连续触发 } } } delay(500); }

这两个项目展示了如何将简单的距离读数,通过逻辑和状态设计,转化为有实际意义的交互和自动化功能。关键在于理解传感器的特性,并用恰当的软件算法来弥补其物理上的局限性,同时充分发挥其低成本、易集成的优势。

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/8/2 22:56:36

单片机毕设选题推荐:带语音对讲功能的医用病床无线呼叫预警装置设计 基于液位传感的输液监测病床呼叫单片机系统实现(020301)

博主介绍&#xff1a;✌️码农一枚 &#xff0c;专注于大学生项目实战开发、讲解和毕业&#x1f6a2;文撰写修改等。全栈领域优质创作者&#xff0c;博客之星、掘金/华为云/阿里云/InfoQ等平台优质作者、专注于嵌入式单片机&#xff0c;Java、小程序技术领域和毕业项目实战 ✌️…

作者头像 李华
网站建设 2026/8/2 22:54:35

Excel集成REFPROP:热物性计算与工程应用实战指南

1. 项目概述&#xff1a;当Excel遇上专业物性计算如果你在工程热物理、制冷空调、能源动力或者化工流程领域工作&#xff0c;大概率听说过或者用过NIST REFPROP。这个由美国国家标准与技术研究院维护的物性数据库&#xff0c;可以说是行业内的“黄金标准”&#xff0c;涵盖了从…

作者头像 李华
网站建设 2026/8/2 22:54:33

【计算机毕业设计单片机案例】STM32 驱动的多功能桌面健康监测台灯实现 基于多传感器融合的智能护眼提醒设备开发(018401)

博主介绍&#xff1a;✌️码农一枚 &#xff0c;专注于大学生项目实战开发、讲解和毕业&#x1f6a2;文撰写修改等。全栈领域优质创作者&#xff0c;博客之星、掘金/华为云/阿里云/InfoQ等平台优质作者、专注于嵌入式单片机&#xff0c;Java、小程序技术领域和毕业项目实战 ✌️…

作者头像 李华
网站建设 2026/8/2 22:42:34

ESP-NOW设备角色详解:initiator与responder如何协作实现双向通信

ESP-NOW设备角色详解&#xff1a;initiator与responder如何协作实现双向通信 【免费下载链接】esp-now A connectionless Wi-Fi communication protocol 项目地址: https://gitcode.com/gh_mirrors/es/esp-now ESP-NOW是一种无连接的Wi-Fi通信协议&#xff0c;在物联网系…

作者头像 李华