基于Arduino与光敏电阻的非接触自动消毒液分发器DIY全解析

发布时间:2026/6/4 14:16:03

基于Arduino与光敏电阻的非接触自动消毒液分发器DIY全解析 1. 项目概述与设计思路最近几年公共卫生安全成了大家日常生活中的头等大事尤其是在一些公共场所如何安全、便捷地获取手部消毒液减少不必要的接触成了一个很实际的需求。你可能在很多商场、医院门口见过那种需要用手按压的消毒液瓶每次使用都得碰一下心里总有点嘀咕。今天我想分享一个我自己动手做的、完全非接触的自动消毒液分发器。它的核心原理非常简单就是利用一个几块钱的光敏电阻LDR当你的手伸过去遮挡光线时机器就自动“吐”出定量的消毒液。整个项目基于开源的Arduino平台硬件成本极低代码也就二十来行非常适合电子爱好者、创客或者有相关需求的社区、小店来自制。这个项目的核心价值在于“非接触”和“自动化”。在传染病防控背景下减少高频接触表面的触碰能有效切断一种潜在的传播途径。市面上当然有成熟的商用自动感应消毒机但价格不菲。我们这个DIY方案总成本可以控制在百元以内而且所有部件透明可控你可以根据自己手头消毒瓶的型号灵活调整机械结构和出液时间。它不仅仅是一个简单的“玩具”更是一个将基础传感器技术应用于解决真实世界问题的典型案例。无论你是想学习Arduino和传感器入门还是真的需要为某个空间添加一个低成本自动化卫生设备这个项目都能给你带来清晰的实现路径和满满的成就感。2. 核心硬件选型与电路设计解析2.1 传感器选型为什么是LDR在实现非接触感应上常见的选择有红外对管、超声波传感器、电容式接近传感器以及我们这里用的光敏电阻LDR。每种方案都有其优缺点。红外对管如 TCRT5000非常普及感应距离和抗环境光干扰能力可以通过电路调节但需要精确对准发射和接收管安装调试稍麻烦。超声波传感器如 HC-SR04测量距离精确不受光线影响但成本相对较高且在非常近的距离如2-3厘米可能存在盲区。电容式传感器感应灵敏甚至可以隔空检测但电路设计和调试最为复杂容易误触发。我最终选择LDR首要原因是极致的简单和低成本。一个LDR加一个10K的电阻成本不到2元钱就构成了一个基本的光线检测电路。其工作原理是LDR的电阻值随光照强度增加而减小。在电路中我们将LDR与一个固定电阻10KΩ串联组成分压电路。微控制器Arduino的模拟输入引脚A0测量它们中间连接点的电压。当光线明亮时LDR电阻小分得的电压低A0读到的模拟值就小当手遮挡导致光线变暗时LDR电阻急剧增大分得的电压升高A0读到的模拟值就变大。注意LDR的响应速度相对较慢毫秒级但对于人手遮挡这种动作来说完全足够。它的主要弱点是对环境光变化敏感。比如白天室内和夜晚环境光照度差异巨大这会导致触发阈值需要调整。不过在我们的应用场景中消毒机通常放置在室内光线相对稳定的位置如门口、走廊这个问题可以通过软件校准或选择触发“光线变暗”的相对变化量来缓解后续在代码部分会详细说明。2.2 主控与执行机构选型主控单元任何一款Arduino板子都可以胜任从最基础的Arduino Uno到更小巧的Nano、Pro Mini。它们都提供了易于编程的环境和足够的数字/模拟IO口。对于这个项目我们只需要一个数字输出口控制继电器一个模拟输入口读取LDR值所以即使是最小系统也绰绰有余。执行机构这里有两个主流选择微型直流电机或舵机Servo。它们的选择取决于你希望如何驱动消毒液瓶。直流电机方案通常需要配合一个小型泵头如微型隔膜泵或者通过电机转动来挤压一个软管。优点是出液量可以通过控制电机转动时间来比较精确地控制。但需要额外的驱动电路如L298N电机驱动模块或晶体管因为Arduino的IO口无法直接驱动电机。舵机方案这是更简单、更直观的选择。舵机可以精确控制旋转角度。你可以将舵机的摇臂改装成一个“按压杠杆”直接对准消毒液瓶的按压头。当舵机旋转一定角度如60度时杠杆下压挤出消毒液然后舵机回位。这种方式机械结构简单无需泵和管路直接利用原瓶。本项目原始描述中提到了“MOTOR OR SERVO”为了最大化简化我强烈推荐使用舵机如SG90它只需要一个数字IO口PWM控制且Arduino的Servo库使得控制异常简单。控制开关无论是驱动电机还是舵机我们都不应该直接用Arduino的引脚来供电因为它们的电流可能超过引脚负载能力通常40mA。因此我们需要一个“开关”。这里使用继电器模块是最佳选择。继电器模块如单路5V继电器模块相当于一个由小电流来自Arduino控制大电流驱动电机/舵机的电子开关。当Arduino给继电器模块的控制引脚IN一个高电平信号时继电器的常开触点闭合外部电源如5V或12V适配器就可以为电机或舵机供电了。2.3 完整电路连接详解让我们把上述所有部件连接起来。假设我们使用Arduino Uno、LDR、10K电阻、SG90舵机和5V单路继电器模块。LDR检测电路将LDR的一个引脚连接到Arduino的5V引脚。将LDR的另一个引脚连接到Arduino的模拟引脚A0。将一个10KΩ电阻的一端连接到A0引脚另一端连接到GND地。这样A0引脚就测量到了LDR和10K电阻的分压点电压。继电器控制电路继电器模块通常有三个控制引脚VCC、GND和IN或SIG。将模块的VCC连接到Arduino的5V引脚。将模块的GND连接到Arduino的GND引脚。将模块的IN引脚连接到Arduino的数字引脚13或其他任意数字引脚代码中需对应修改。舵机驱动电路重要舵机不直接连接到Arduino而是通过继电器控制的外部电源供电以保护Arduino板。继电器模块上有三个被控端子常开NO、公共端COM、常闭NC。我们使用常开和公共端。准备一个外部5V电源可以是手机充电器或电池组。将外部电源的正极连接到继电器模块的COM端子。将外部电源的负极-连接到舵机的棕色或黑色线GND。将舵机的红色线VCC连接到继电器模块的NO端子。最后将舵机的信号线橙色或黄色连接到Arduino的一个PWM引脚例如数字引脚9。电源Arduino Uno可以通过USB供电也可以通过其桶形插座接入7-12V直流电源。舵机所需的外部5V电源需要能提供足够的电流SG90工作电流约100-250mA确保其能正常转动。这样整个电路的工作流程是手遮挡LDR → Arduino A0读取到电压升高 → 程序判断达到触发阈值 → Arduino 数字引脚13输出高电平 → 继电器吸合NO与COM接通→ 外部5V电源为舵机供电 → 同时Arduino 引脚9发出PWM信号控制舵机转动 → 舵机压下消毒液瓶 → 延时后引脚13输出低电平继电器断开舵机断电同时引脚9控制舵机回位。3. 软件逻辑与代码深度剖析代码是项目的大脑虽然只有短短几十行但每一行都至关重要且包含了许多可以优化和调整的点。让我们逐部分拆解。3.1 基础代码实现与常量定义首先我们引入舵机库并定义引脚和关键阈值。#include Servo.h // 引入舵机控制库 // 引脚定义 const int relayPin 13; // 控制继电器的数字引脚 const int ldrPin A0; // 读取LDR的模拟引脚 const int servoPin 9; // 控制舵机信号的PWM引脚 // 阈值与参数定义 const int LDR_DARK_THRESHOLD 80; // LDR触发阈值需根据实际环境校准 const int SERVO_PRESS_ANGLE 60; // 舵机按压角度 const int SERVO_HOME_ANGLE 0; // 舵机初始/回位角度 const int DISPENSE_TIME_MS 1000; // 继电器吸合/出液时间毫秒 const int COOLDOWN_TIME_MS 3000; // 两次触发之间的冷却时间防止连续误触发 Servo myServo; // 创建舵机对象 bool systemEnabled true; // 系统总开关可用于防止重复触发 unsigned long lastTriggerTime 0; // 记录上次触发的时间代码解析#include Servo.h这是必须的它提供了控制舵机的简单函数。阈值LDR_DARK_THRESHOLD这是整个感应逻辑的核心。analogRead(ldrPin)的返回值范围是0-1023。在明亮环境下LDR电阻小A0电压低读数小可能小于50。当手遮挡时读数会变大。这个阈值就是你设定的“多暗才算有手”的标准。原始代码中设为80这只是一个起点你必须根据安装位置的实际光照情况进行校准。冷却时间COOLDOWN_TIME_MS这是一个非常重要的防误触设计。如果没有它当手停留在感应区时Arduino会以极快的循环速度每秒上百次不断地开、关继电器和舵机导致设备抽搐并可能很快损坏。设置一个3秒的冷却时间意味着一次触发后至少3秒内系统不会再次响应无论LDR值如何变化。3.2 核心控制逻辑与状态管理接下来是setup()和loop()函数其中包含了完整的控制逻辑。void setup() { Serial.begin(9600); // 初始化串口用于调试输出LDR值 pinMode(relayPin, OUTPUT); pinMode(ldrPin, INPUT); digitalWrite(relayPin, LOW); // 确保继电器初始为断开状态 myServo.attach(servoPin); // 将舵机对象绑定到控制引脚 myServo.write(SERVO_HOME_ANGLE); // 初始化舵机到归位角度 delay(500); // 给舵机一点时间归位 } void loop() { // 1. 读取当前环境传感器值 int ldrValue analogRead(ldrPin); // 串口打印当前值用于调试和校准阈值 Serial.print(LDR Value: ); Serial.println(ldrValue); // 2. 检查系统是否处于冷却期 unsigned long currentTime millis(); // 获取当前运行时间 if (!systemEnabled) { if (currentTime - lastTriggerTime COOLDOWN_TIME_MS) { systemEnabled true; // 冷却时间结束重新启用系统 Serial.println(System re-enabled.); } } // 3. 主触发判断逻辑 if (systemEnabled ldrValue LDR_DARK_THRESHOLD) { // 条件满足系统已启用且检测到“暗” Serial.println(Triggered! Dispensing...); // 禁用系统进入冷却 systemEnabled false; lastTriggerTime currentTime; // 启动执行机构 digitalWrite(relayPin, HIGH); // 继电器吸合舵机通电 delay(100); // 短暂延时确保舵机电源稳定 myServo.write(SERVO_PRESS_ANGLE); // 舵机转动到按压角度 delay(DISPENSE_TIME_MS); // 保持按压状态确保足量出液 myServo.write(SERVO_HOME_ANGLE); // 舵机回位 delay(300); // 等待舵机回位动作完成 digitalWrite(relayPin, LOW); // 继电器断开舵机断电 Serial.println(Dispensing complete.); } // 4. 短暂延时降低CPU占用非必须但是个好习惯 delay(50); }逻辑流程详解读取与监控每次循环都读取LDR的当前值并通过串口打印出来。这是调试阶段最关键的一步。打开Arduino IDE的串口监视器你就能看到实时数据从而确定明亮和遮挡时的典型数值科学地设置LDR_DARK_THRESHOLD。冷却判断通过millis()函数管理时间实现非阻塞的延时。相比用delay()进行长延时这种方式不会冻结整个程序允许系统在冷却期间依然能监控串口等。触发与执行当且仅当系统启用(systemEnabled true)且LDR读数超过阈值时触发动作序列。触发后立即禁用系统记录时间。继电器先吸合务必先给舵机供电再发送控制信号。如果顺序反了舵机可能在未通电时收到信号导致行为异常。舵机动作myServo.write(angle)命令舵机转到指定角度。SERVO_PRESS_ANGLE需要你根据实际机械结构调试确保能压到底。保持与回位DISPENSE_TIME_MS是出液时间通常0.5-1.5秒足够。回位后再断开继电器电源。循环延时主循环末尾的小延时可以稍微降低Arduino的CPU使用率对简单项目影响不大但能避免串口输出过快。3.3 高级优化与可靠性增强基础代码能工作但要在实际环境中稳定运行还需要考虑更多。1. 阈值动态校准与环境光自适应固定阈值在环境光变化时如昼夜交替、阴晴变化会失效。我们可以让系统在启动时自动学习当前环境光并设定一个相对阈值。const int CALIBRATION_TIME 3000; // 校准时间3秒 int ambientLightLevel 0; // 环境光基准值 const int TRIGGER_OFFSET 150; // 触发偏移量当光线比基准暗这么多时触发 void calibrateLDR() { long sum 0; Serial.println(Calibrating LDR, please ensure sensor is uncovered...); for (int i 0; i 100; i) { // 采样100次 sum analogRead(ldrPin); delay(CALIBRATION_TIME / 100); } ambientLightLevel sum / 100; // 计算平均环境光值 Serial.print(Ambient light level calibrated to: ); Serial.println(ambientLightLevel); }在setup()中调用calibrateLDR()函数。然后在主循环的判断条件中将ldrValue LDR_DARK_THRESHOLD改为ldrValue (ambientLightLevel - TRIGGER_OFFSET)。这样系统会自动适应不同环境触发条件是“当前光线比环境基准暗一定数值”更加鲁棒。2. 消抖与信号滤波LDR的模拟值可能会有微小波动。我们可以通过软件滤波来平滑读数避免因噪声引起的误触发。最简单的是移动平均滤波。const int NUM_READINGS 10; int readings[NUM_READINGS]; int readIndex 0; long total 0; int average 0; void setup() { // ... 其他初始化 for (int i 0; i NUM_READINGS; i) { readings[i] 0; } } int getFilteredLDRValue() { total total - readings[readIndex]; // 减去最旧的读数 readings[readIndex] analogRead(ldrPin); // 读取新值 total total readings[readIndex]; // 加上新值 readIndex (readIndex 1) % NUM_READINGS; // 循环索引 return total / NUM_READINGS; // 返回平均值 }然后在loop()中使用int ldrValue getFilteredLDRValue();代替直接的analogRead。这能有效平滑数据。3. 低功耗考虑电池供电场景如果希望用电池供电需要最大限度降低功耗。可以将主循环中的delay(50)改为使用Arduino的低功耗休眠库如LowPower库让单片机在大部分时间休眠仅定时唤醒检查传感器。同时继电器模块和舵机只在动作时通电也能节省大量电能。4. 机械结构设计与组装要点电路和代码是灵魂机械结构则是身体。一个可靠的机械设计决定了出液的稳定性和产品的美观度。4.1 外壳设计与材料选择外壳需要容纳Arduino板、继电器模块、电源、以及最重要的——消毒液瓶和舵机按压机构。材料选择上3D打印这是最灵活的方式。你可以使用Fusion 360或Tinkercad等软件自行设计或者去Thingiverse等网站搜索“hand sanitizer dispenser”寻找开源模型进行修改。设计时要留出传感器窗口、出液口、USB电源线孔以及散热孔。亚克力板激光切割结构规整外观通透现代。设计好图纸后用激光切割机切割出各面板再用螺丝或胶水组装。改造现有容器利用旧的纸巾盒、塑料收纳盒进行改造。用美工刀开孔热熔胶固定内部组件。这是最经济快捷的方式。无论哪种方式设计时需注意LDR开孔位置必须正对用户手部伸入的方向且避免内部电路板灯光直射干扰。可以在开孔处加一段黑色的小管如用热缩管形成“遮光筒”让LDR只感应特定方向的光线变化提高抗干扰能力。消毒液瓶固定瓶身必须被牢牢固定确保每次按压的位置一致。可以使用扎带、3D打印的瓶夹或者用泡沫棉填充空隙。舵机杠杆设计舵机摇臂需要延长可以用硬铁丝、冰棍棒或3D打印一个延长臂末端需要做一个“压头”用来对准消毒液瓶的按压泵。压头最好粘贴一小块橡胶或海绵增加摩擦力并缓冲冲击。4.2 舵机杠杆传动机构详解这是机械部分的核心。舵机的标准摇臂很短力矩小直接按压可能力量不足。我们需要制作一个省力杠杆。方案一直接延长摇臂将一根硬质钢丝或铝条牢固地固定在舵机摇臂上。根据消毒液瓶按压所需的行程和力度确定杠杆的长度和压头的位置。原理根据杠杆原理F1 * L1 F2 * L2加长力臂L1可以在输出端F2获得更大的力但代价是按压的行程L2会变小。你需要测试消毒液瓶按到底需要多长的行程然后反推舵机摇臂需要转动的角度和杠杆长度。方案二二级杠杆推荐如果消毒液瓶需要较大行程和力度可以使用二级杠杆。第一级由舵机驱动第二级是一个铰接的按压板。这样可以用较小的舵机转角通过两级杠杆放大行程和力。设计稍复杂但动作更平顺可靠。组装与调试步骤先将所有电子部件除LDR固定在外壳底板上。将消毒液瓶就位手动模拟按压标记出按压泵头的准确位置和所需下压的行程。安装舵机调整其位置使延长臂末端的压头在舵机转动到SERVO_PRESS_ANGLE时能刚好将泵头压到底。反复调整SERVO_PRESS_ANGLE的角度值在代码中并配合微调舵机物理安装位置直到按压动作干脆利落出液量稳定。关键测试连续触发几十次观察出液量是否一致机械结构有无松动或变形。在压头和泵头之间加一点润滑脂如凡士林可以减少磨损和噪音。5. 系统调试、问题排查与优化实录即使按照教程一步步做第一次也难免遇到问题。下面是我在多次制作和教学中总结的常见问题及解决方法。5.1 传感器不触发或误触发这是最常见的问题几乎都出在LDR阈值和环境光上。症状手放过去没反应。排查打开串口监视器观察LDR数值。解决确保LDR的感光面正对用户方向且没有被内部元件遮挡或直射。查看手未遮挡时的数值环境光值A_light和完全遮挡时的数值A_dark。你的触发阈值LDR_DARK_THRESHOLD应设置在(A_light A_dark) / 2附近并略偏向A_dark。例如明亮时读数为30遮挡后读数为600阈值可以设为100-150。如果A_light和A_dark差值很小比如小于100说明环境光太强或遮挡不严。需要加强遮光或者将LDR和电阻的位置对调即LDR接GND10K电阻接5V这样遮挡时读数会变小触发条件改为ldrValue THRESHOLD有时在强光环境下更有效。症状没人靠近机器自己偶尔会触发。排查同样观察串口数值看是否在无人时有剧烈波动。解决启用软件滤波如前所述使用移动平均滤波平滑信号。增加“触发确认”时间不要一检测到阈值就动作改为“连续N次循环读数都超过阈值才触发”。这能滤除瞬间的干扰。int triggerCount 0; if (ldrValue THRESHOLD) { triggerCount; if (triggerCount 5) { // 连续5次都超阈值才认为有效 // 执行触发 triggerCount 0; } } else { triggerCount 0; // 一旦读数正常计数清零 }检查电源干扰电机/舵机工作时会产生电源噪声可能影响敏感的模拟电路。尝试给Arduino的模拟电源部分Aref引脚附近加一个0.1uF的瓷片电容到地或者使用独立的稳压模块为Arduino和传感器供电。5.2 执行机构工作异常症状舵机抖动、不转或转到错误角度。排查电源不足这是首要原因。舵机在堵转被卡住时电流很大。确保你的外部5V电源能提供至少1A的电流。使用万用表测量舵机动作时其供电电压是否被拉低到4.5V以下。信号干扰确保舵机信号线连接牢固且远离电源线。如果线较长可以尝试在舵机电源引脚就近并联一个100uF以上的电解电容以稳定电压。机械卡死检查杠杆是否被外壳或其他部件卡住。舵机有最大扭矩不要让它硬扛。症状继电器有响声但舵机不动作。排查用万用表通断档测量继电器吸合时其COM和NO端子是否真的接通。检查外部电源到舵机的线路是否连通。特别注意继电器模块的触发电平有的模块是低电平触发IN脚给低电平吸合有的是高电平触发。务必根据你的模块说明书修改代码中digitalWrite(relayPin, HIGH/LOW)的逻辑。5.3 出液量不稳定或不出液症状有时出液多有时出液少有时不出。排查按压行程不足舵机角度SERVO_PRESS_ANGLE设置太小没有将泵头完全压到底。增大角度值。按压时间不足DISPENSE_TIME_MS时间太短消毒液还未流下。对于粘稠的凝胶需要适当加长保持时间如1200ms。机械结构松动反复按压后固定舵机或瓶子的螺丝、胶水松脱导致每次按压位置变化。加强机械固定。消毒液瓶问题瓶内液位过低导致吸不上来或者泵头本身有质量问题。更换一瓶新的试试。5.4 系统稳定性与维护建议一个产品化的设备还需要考虑长期使用的稳定性。电源管理如果使用适配器确保其输出电压电流稳定。如果使用电池建议选择18650锂电池组配合充电管理模块并加入电压检测在电量低时通过LED闪烁提示。防酒精腐蚀消毒液主要成分是酒精对某些塑料和电子元件有腐蚀性。确保外壳内部有隔离避免液体溅入。所有电线接头最好用热缩管或绝缘胶带包好。定期功能测试建议每周或每半个月用一张纸测试一下感应和出液是否正常。检查消毒液余量。软件看门狗对于需要长期不间断运行的Arduino可以在代码中启用硬件看门狗#include avr/wdt.h防止程序跑飞后设备死机。从一堆散件到一个能稳定工作的非接触消毒液分发器这个过程充满了动手的乐趣和解决问题的成就感。这个项目的魅力在于它清晰地展示了一个完整嵌入式系统的闭环感知LDR→ 决策Arduino程序→ 执行继电器/舵机。每一个环节你都可以去深挖、优化和替换。比如你可以把LDR换成更稳定的红外传感器把舵机换成更安静的步进电机甚至加入Wi-Fi模块实现出液次数统计和缺液远程报警。我个人在制作多个版本后最深的体会是可靠性源于细节。无论是LDR的那一小段遮光管还是舵机电源上的那个滤波电容抑或是代码里那3秒的冷却时间这些看似不起眼的细节共同决定了设备在无人值守时能否经年累月地稳定工作。希望这份超详细的拆解能帮你不仅做出一个能动的作品更能做出一个真正好用、耐用的设备。

相关新闻