Arduino结合UV/IR传感器实现钞票真伪检测:从原理到实践
1. 项目概述用Arduino打造你的钞票验真仪最近在捣鼓一些传感器应用发现用Arduino结合紫外UV和红外IR传感器来识别钞票是个既有趣又实用的项目。这玩意儿本质上是一个小型的、低成本的钞票真伪检测装置原型。它的核心逻辑很简单真钞在特定波长的紫外光和红外光下会呈现出与普通纸张或假钞截然不同的特征。比如很多国家的真钞在紫外光照射下特定区域如安全线、荧光纤维会发出特定颜色的荧光而在红外光下某些油墨会吸收红外线使得图案“消失”或发生变化。我们通过传感器捕捉这些光学特征再让Arduino做个简单的“是或否”的判断就能实现基础的验真功能。这个项目非常适合电子爱好者、创客入门或者作为嵌入式系统、传感器原理的教学案例。你不需要高深的金融知识重点在于理解传感器如何与物理世界交互以及如何用微控制器处理这些模拟或数字信号。整个过程从硬件连线、传感器调试到逻辑代码编写再到最后的阈值校准每一步都能让你对“如何让机器感知世界”有更直观的认识。我最初做这个也是为了验证手头一堆传感器的特性结果发现它比想象中更有挑战性尤其是在如何排除环境光干扰、如何设定一个可靠的判断阈值上踩了不少坑也积累了一些心得。2. 核心硬件选型与电路设计思路2.1 传感器的工作原理与选型考量这个项目的成败一半取决于你是否选对了传感器并且真正理解了它们的工作原理。紫外UV传感器市面上常见的用于检测紫外光的模块比如GUVA-S12SD其核心是一个对紫外线敏感的半导体光电二极管。它并不是直接“看到”荧光而是检测钞票被紫外LED照射后其表面反射或激发的紫外光强度。真钞的防伪荧光材料在紫外光激发下会发射出特定波长的可见光荧光但同时纸张本身对紫外光也有反射。传感器测量的是这个综合的紫外光反射强度。通常含有荧光防伪的真钞其紫外反射/荧光特性与普通A4纸差异显著。选择时要注意传感器的光谱响应范围最好能匹配你使用的紫外LED的峰值波长常见是365nm或395nm。我推荐使用集成好的模拟量输出模块它已经包含了必要的信号放大电路输出一个0-Vcc例如5V的电压信号Arduino的模拟输入引脚可以直接读取非常方便。红外IR传感器这里通常指红外反射传感器或红外接收管。常见的有TCRT5000这类模块它集成了一个红外发射管和一个红外接收管。发射管发出红外光波长约940nm照射到钞票表面后反射回来接收管检测反射光的强度。真钞上某些区域使用了红外吸收油墨在红外光下几乎不反光呈现“黑色”而其他区域或假钞则可能反射强烈。因此传感器输出值会因照射区域不同而发生剧烈变化。选择时注意其探测距离和抗环境光干扰能力。同样模拟输出的模块会简化你的电路设计。为什么选择Arduino Uno对于这个项目Arduino Uno的10位ADC模拟数字转换器精度0-1023完全够用其数字IO口也足以驱动紫外LED和传感器模块的使能端。更重要的是Uno庞大的社区支持和丰富的库能让你的开发过程顺畅很多。当然如果你希望集成显示、无线传输等功能可以考虑Mega或ESP32但对于核心的验真功能Uno是性价比最高的选择。2.2 电路连接与供电设计详解电路连接并不复杂但一些细节决定了系统的稳定性。下图是一个基础的连接示意图Arduino Uno │ ├── 5V ────────────────────────┬── UV传感器模块 VCC │ ├── IR传感器模块 VCC │ └── 紫外LED阳极通过限流电阻 │ ├── GND ───────────────────────┬── UV传感器模块 GND │ ├── IR传感器模块 GND │ └── 紫外LED阴极 │ ├── 模拟引脚 A0 ──────────────── UV传感器模块 OUT信号线 │ ├── 模拟引脚 A1 ──────────────── IR传感器模块 OUT信号线 │ └── 数字引脚 D2或其他─────── 紫外LED控制可选用于节能或同步检测关键细节与避坑指南紫外LED的驱动紫外LED工作电压通常在3.0-3.6V电流20-30mA。绝对不能直接接到Arduino的5V引脚上否则会瞬间烧毁。必须串联一个限流电阻。计算电阻值R (电源电压 - LED正向电压) / 期望电流。例如用5V驱动一颗3.2V/20mA的LEDR (5-3.2)V / 0.02A 90欧姆。选择最接近的标准值如100欧姆。为了精确控制可以将LED的阴极接地阳极通过这个电阻接到一个数字引脚如D2这样你就可以用代码digitalWrite(2, HIGH/LOW)来开关它便于进行同步测量和节省功耗。传感器模块的干扰UV和IR传感器模块最好分开供电或者至少在电源入口处并联一个100uF的电解电容和一个0.1uF的陶瓷电容以滤除电源噪声防止电机或其他大电流设备工作时导致传感器读数跳动。这是很多初学者忽略但至关重要的一点。环境光屏蔽环境光尤其是日光灯和太阳光含有紫外和红外成分会严重干扰读数。你必须为检测区域制作一个暗箱。可以用黑色亚克力板或甚至一个涂黑的内壁的纸盒来包裹传感器和检测区域只留出钞票插入的缝隙。这是获得稳定、可重复数据的前提。3. 核心代码逻辑与传感器数据处理3.1 基础数据采集与串口调试一切从读取数据开始。我们先写一个最简单的程序把两个传感器的原始模拟值打印到串口监视器上这是后续所有工作的基础。// 定义引脚 const int uvSensorPin A0; // UV传感器连接至A0 const int irSensorPin A1; // IR传感器连接至A1 const int uvLedPin 2; // 紫外LED控制引脚 void setup() { Serial.begin(9600); // 初始化串口通信 pinMode(uvLedPin, OUTPUT); digitalWrite(uvLedPin, LOW); // 初始关闭紫外LED } void loop() { // 打开紫外LED进行测量 digitalWrite(uvLedPin, HIGH); delay(50); // 等待LED稳定和传感器响应非常重要 int uvValue analogRead(uvSensorPin); // 读取UV传感器值 (0-1023) int irValue analogRead(irSensorPin); // 读取IR传感器值 (0-1023) // 关闭紫外LED以节省功耗和减少发热 digitalWrite(uvLedPin, LOW); // 打印到串口监视器 Serial.print(UV: ); Serial.print(uvValue); Serial.print( | IR: ); Serial.println(irValue); delay(500); // 延时半秒避免串口数据刷屏过快 }实操心得delay(50)的重要性在打开LED后立即读取传感器得到的值可能是不稳定的。给传感器几十毫秒的响应时间读数会稳定得多。这个值需要根据你的具体传感器模块调整。串口绘图器Arduino IDE自带的“串口绘图器”工具比监视器更直观。它能实时绘制uvValue和irValue的曲线。你拿着真钞和假钞或白纸在传感器前来回移动可以清晰地看到信号的变化范围和差异这对后续设定阈值有巨大帮助。3.2 阈值判定算法的设计与优化拿到数据后如何判断最直接的方法是设定阈值。// 定义阈值这些值需要你通过实验校准 const int uvThreshold 500; // UV信号阈值高于此值可能为真钞有荧光 const int irThreshold 300; // IR信号阈值低于此值可能为真钞红外吸收 void loop() { // ... (数据采集部分同上) ... // 简单的双条件阈值判定 bool isUVValid (uvValue uvThreshold); bool isIRValid (irValue irThreshold); // 注意这里是小于因为真钞反射弱 if (isUVValid isIRValid) { Serial.println(Result: GENUINE NOTE DETECTED); } else { Serial.println(Result: SUSPICIOUS OR FAKE); // 可以进一步细化提示 if (!isUVValid) Serial.println( - UV feature mismatch.); if (!isIRValid) Serial.println( - IR feature mismatch.); } }为什么不能只用固定阈值环境温度、传感器老化、不同批次钞票的微小差异都会导致基准值漂移。固定阈值在今天校准好后下周可能就不准了。更健壮的算法动态基准与区间判断一个更好的实践是引入“动态基准”的概念。在设备启动时或者每次检测前先测量一下“空白”即无钞票或一张已知的白纸时的传感器值作为环境基准。int uvBaseline 0; int irBaseline 0; void calibrateBaseline() { digitalWrite(uvLedPin, HIGH); delay(100); long uvSum 0; long irSum 0; for (int i 0; i 10; i) { // 采样10次取平均 uvSum analogRead(uvSensorPin); irSum analogRead(irSensorPin); delay(10); } digitalWrite(uvLedPin, LOW); uvBaseline uvSum / 10; irBaseline irSum / 10; Serial.print(Calibrated Baseline - UV: ); Serial.print(uvBaseline); Serial.print( | IR: ); Serial.println(irBaseline); } void setup() { // ... 初始化 ... calibrateBaseline(); // 上电校准 }在判定时我们不再使用固定阈值而是判断读数相对于基线的变化量是否超过某个范围。const int uvChangeThreshold 150; // UV变化量阈值 const int irChangeThreshold 100; // IR变化量阈值 void loop() { // ... 采集 uvValue, irValue ... int uvChange uvValue - uvBaseline; int irChange irBaseline - irValue; // 注意IR是反向变化 bool isUVValid (uvChange uvChangeThreshold); // 真钞导致UV读数显著增加 bool isIRValid (irChange irChangeThreshold); // 真钞导致IR读数显著减少 // ... 后续判定逻辑 ... }这种方法抗环境缓慢变化的能力强得多。你可以通过一个按钮来触发calibrateBaseline()函数方便随时重新校准。4. 使用Visuino进行图形化编程与快速原型开发对于不熟悉C/C语法或者想快速验证逻辑的朋友Visuino是一个强大的图形化Arduino编程工具。你可以通过拖放组件来搭建整个程序流程。4.1 Visuino项目搭建步骤安装与设置下载安装Visuino并配置好Arduino开发板的路径。添加组件从工具箱拖入一个Digital[PWM]通道连接到Digital 2引脚作为紫外LED开关。拖入两个Analog Input组件分别连接到Analog 0和Analog 1对应UV和IR传感器。为了处理数据你需要Compare组件用于阈值比较、Logic组件如AND门用于综合判断。拖入Serial组件和Text组件用于输出结果到串口。连线与配置将Analog Input的输出连接到Compare组件的输入。双击Compare组件设置比较类型大于、小于和阈值。将两个Compare组件的输出连接到一个AND逻辑门的输入。将AND门的输出连接到一个Clock发生器再连接到Serial输出这样当检测为真时会触发一次串口信息发送。配置Digital输出组件使其周期性地开关例如用另一个时钟组件控制模拟我们代码中的digitalWrite和delay。生成与上传代码点击Visuino的“生成代码”按钮它会自动生成完整的Arduino代码然后点击“上传”到板子。Visuino的优势与局限优势直观逻辑流清晰特别适合教育、演示和快速原型验证。你可以实时看到数据流调试逻辑错误非常方便。局限对于复杂的算法如我们上面提到的动态基线校准、滤波算法实现起来比较繁琐生成的代码可能不够优化占用空间较大。对于追求性能和精细控制的最终项目还是手写代码更灵活。4.2 在Visuino中实现动态阈值逻辑在Visuino中实现动态基准有一定技巧。你可以利用Sample Hold组件。在按下校准按钮时用一个Clock触发Sample Hold去捕获当前传感器的模拟值并将其输出作为一个Subtract组件的减数。然后将实时传感器值作为被减数Subtract的输出就是变化量再将此变化量送给Compare组件与变化量阈值比较。这个过程在图形化界面中需要精心布局连线但一旦搭建成功同样能实现健壮的检测逻辑。5. 系统集成、校准与提升可靠性的实战技巧5.1 机械结构与校准流程一个稳定的机械结构至关重要。你需要制作或设计一个支架确保传感器距离固定UV和IR传感器到钞票检测面的距离必须每次一致。距离变化会极大影响反射光强度。可以用一个带滑槽的卡片引导钞票插入到固定位置。传感器正对确保传感器发射/接收面与钞票平面垂直避免倾斜导致读数不准。暗室密封你的暗箱要尽可能密封防止外部光泄露。可以在插入口使用黑色海绵制作一个遮光缝。标准校准流程准备样本至少一张真钞用作正样本一张高质量白纸或普通打印纸用作负样本。上电预热让系统通电运行2-3分钟使传感器和LED温度稳定。采集基准在无钞状态下运行calibrateBaseline()函数记录空白值。采集样本值插入白纸记录稳定的UV和IR读数uvPaper,irPaper。插入真钞并确保对准你要检测的防伪区域如安全线附近记录稳定的读数uvMoney,irMoney。计算阈值uvChangeThreshold (uvMoney - uvBaseline) * 0.7;// 取真钞变化量的70%作为阈值留有余量。irChangeThreshold (irBaseline - irMoney) * 0.7;// 同理。验证用真钞和白纸反复测试多次调整系数0.7直到误判率最低。如果可能用不同新旧程度的同面额真钞测试。5.2 软件滤波与抗干扰策略原始模拟读数难免有毛刺。除了硬件滤波电容软件上必须加入滤波算法。移动平均滤波这是最简单有效的方法。它用一个数据窗口内 readings 的平均值来平滑数据。const int numReadings 10; // 滤波窗口大小 int uvReadings[numReadings]; int irReadings[numReadings]; int readIndex 0; long uvTotal 0, irTotal 0; void setup() { // ... 其他初始化 ... for (int i 0; i numReadings; i) { uvReadings[i] 0; irReadings[i] 0; } } int getFilteredUV() { uvTotal uvTotal - uvReadings[readIndex]; // 减去最旧的值 uvReadings[readIndex] analogRead(uvSensorPin); // 读取新值 uvTotal uvTotal uvReadings[readIndex]; // 加上新值 readIndex (readIndex 1) % numReadings; // 移动索引 return uvTotal / numReadings; // 返回平均值 } // 对IR传感器实现同样的函数 getFilteredIR()在loop()中使用getFilteredUV()和getFilteredIR()代替直接的analogRead你会发现读数瞬间变得平稳阈值判断的可靠性大幅提升。窗口大小numReadings需要权衡越大越平滑但响应越慢通常5-20之间是合理的。5.3 扩展功能与高级思路基础功能实现后可以考虑以下扩展让项目更完整结果指示增加一个绿色LED和一个红色LED分别指示“真”和“假”或者用一个小型蜂鸣器发出不同声音。LCD/OLED显示连接一个I2C的OLED屏幕实时显示传感器数值、阈值和判定结果更加直观。多区域检测通过舵机或步进电机移动传感器或者布置多组传感器对钞票上不同位置的防伪点如左上角荧光数字、中间安全线进行逐一检测综合评分极大提高识别准确率。机器学习边缘部署进阶如果你使用ESP32或Raspberry Pi Pico可以尝试用TensorFlow Lite Micro。采集大量真钞和假钞或模拟物在不同条件下的传感器数据UV, IR值对训练一个简单的二元分类模型如决策树、小神经网络然后部署到MCU上。这样系统就能学习更复杂的特征模式而不仅仅是依赖手动设定的阈值。6. 常见问题排查与调试心得实录在实际制作过程中你几乎一定会遇到下面这些问题。这里是我的排查清单和解决方法。问题现象可能原因排查步骤与解决方案传感器读数始终为0或10231. 接线错误电源/地接反。2. 传感器模块损坏。3. 模拟引脚配置错误极少见。1. 用万用表检查传感器VCC和GND间电压是否为5V。2. 将信号线OUT暂时接到稳定的3.3V或5V上看读数是否跳变到1023接到GND看是否变为0。若无变化引脚或板子可能有问题。3. 换一个模拟引脚试试。读数不稳定跳动剧烈1. 电源噪声。2. 环境光干扰。3. 传感器离检测面太远或角度不对。4. 未使用软件滤波。1. 在传感器电源引脚就近并联一个100uF电解电容和一个0.1uF陶瓷电容。2.确保在完全暗室环境下测试这是最常见原因。3. 调整传感器距离通常2-10mm为宜并确保正对。4. 务必实现移动平均滤波。真钞和假钞白纸读数差异很小1. 紫外LED波长或功率不对。2. 传感器光谱响应不匹配。3. 检测的不是正确的防伪区域。4. 阈值设置不合理。1. 确认UV LED是365nm或395nm。用手机摄像头通常能感应部分紫外粗略检查LED是否亮起。2. 尝试更换传感器模块。不同型号的UV传感器对365nm和395nm的灵敏度不同。3.仔细研究真钞的防伪特征。用验钞笔找到荧光位置确保传感器正对该区域。4. 使用串口绘图器反复对比真钞和假钞的波形重新校准阈值。系统偶尔误判1. 环境基准漂移如环境温度变化。2. 钞票插入位置不重复。3. 传感器或LED老化。1. 实现上文所述的“动态基准校准”功能并增加定期自动校准如每10分钟一次。2. 改进机械导槽确保每次插入位置一致。3. 长期使用的设备需要定期如每月用标准样本重新校准阈值。Visuino生成的代码上传失败1. Arduino板卡类型选择错误。2. 串口被占用或驱动问题。3. 生成的代码有语法错误罕见。1. 在Visuino和Arduino IDE中双重确认板卡型号和端口选择正确。2. 关闭Arduino IDE和其他可能占用串口的软件。3. 尝试用Visuino生成代码后用Arduino IDE直接打开生成的.ino文件进行编译上传看是否有更具体的错误提示。最重要的心得数据不会说谎。当你遇到任何奇怪的现象时第一反应应该是打开串口监视器或绘图器看看原始数据到底是什么样的。是根本没变化还是变化杂乱无章或者是变化趋势和预期相反这些数据是你调试的唯一依据。凭空猜测硬件或代码问题往往会浪费大量时间。这个项目从原理到实现每一步都紧扣着“感知-处理-判断”的经典嵌入式系统逻辑希望你在动手的过程中不仅能做出一个能用的验钞器更能透彻理解这些传感器和微控制器是如何协同工作的。