1. 项目概述与设计初衷夏天一到海滩和户外就成了热门去处。对于我那些皮肤比较敏感、容易晒伤的朋友来说享受阳光的同时心里总得绷着一根弦担心紫外线强度超标。市面上虽然有一些便携的紫外线指数计但要么是耗电大户用一次性电池要么设计得不太合理——想看屏幕读数就得把传感器从太阳底下挪开用起来挺别扭。于是我就琢磨着自己动手做一个它得是充电的续航要久屏幕和传感器得朝向同一个方向方便单手操作和读数最好还能直观地显示风险等级而不仅仅是冷冰冰的数字。这就是“B.U.R.N. Meter”的由来——一个基于Arduino的可充电紫外线指数监测仪。这个项目本质上是一个集成了传感、数据处理、显示和电源管理的嵌入式系统。它的核心价值在于将环境监测紫外线强度这个专业领域的需求通过开源硬件和软件变成一个普通电子爱好者也能亲手实现、并真正在日常生活中使用的工具。无论你是想学习如何将传感器数据可视化还是想打造一个个性化的便携设备这个项目都能提供一个从电路设计、3D建模到嵌入式编程的完整实践路径。接下来我会详细拆解整个项目的设计思路、硬件选型、软件实现以及组装调试中的每一个细节和踩过的坑。2. 核心硬件选型与电路设计解析硬件是整个项目的骨架选型直接决定了设备的性能、功耗和最终的用户体验。我的核心思路是在满足功能、保证精度的前提下优先选择我手头已有的、或容易获取且性价比高的模块同时充分考虑便携性和可制造性。2.1 主控与显示模块性能与视觉的平衡主控芯片Arduino Teensy 4.0我选择Teensy 4.0首要原因是我手头正好有一个闲置的。但抛开这个偶然因素它本身也是一个非常优秀的选择。其核心是一颗600MHz的ARM Cortex-M7处理器性能远超普通的AVR芯片如Arduino Uno用的ATmega328P。这意味着它有充足的计算能力来处理图形显示和复杂的数值运算而不会出现卡顿。其次它拥有多达14个模拟输入引脚这为未来可能的传感器扩展例如增加温度、湿度传感器预留了充足的空间。虽然对于本项目来说有些“性能过剩”但其极低的功耗在低频率运行时和通过Teensyduino插件在Arduino IDE中近乎完美的兼容性使得“杀鸡用牛刀”也成了一种愉快的体验——开发阻力小运行流畅。注意如果你没有Teensy 4.0完全可以使用其他Arduino兼容板如ESP32、Arduino Nano 33 BLE等。关键是要确保板子有足够的GPIO来驱动SPI显示屏和读取模拟传感器并且工作电压是3.3V以匹配我们的传感器和显示屏。显示屏ST7789驱动的1.3英寸IPS液晶屏放弃常见的单色OLED选择这块彩色LCD屏是为了实现更丰富、更直观的信息呈现。OLED虽然省电、对比度高但通常尺寸较小且为单色。ST7789屏能显示全彩图像这让我萌生了一个想法用一个会“变脸”的卡通人物来直观反映紫外线强度——强度越高脸越红。这种视觉隐喻比单纯的数字或进度条更能引起使用者的警觉。这块屏通过4线SPISCK, MOSI, DC, RESET与主控通信驱动库成熟在Teensy上运行效率很高。2.2 传感核心GUVA-S12SD紫外线传感器这是项目的“眼睛”。我选择了Adafruit的GUVA-S12SD模块。它内部是一颗对紫外线特别是UV-A和UV-B波段敏感的“真”紫外光电二极管而非那种对可见光也有反应的光敏电阻。其输出是一个与二极管电流成线性关系的电压信号比例系数约为4.3V/μA。这意味着当紫外线强度使得二极管产生1μA电流时对应约9 mW/cm²的辐照度模块会输出4.3V电压。这个特性使得数据转换变得极其简单。在代码中我们只需要读取模拟引脚上的电压值0-3.3V经过ADC转换然后除以一个系数例如0.1就能近似得到紫外线指数UVI。世界卫生组织定义的紫外线指数每增加1大致对应25 mW/m²的 erythemally weighted UV irradiance。GUVA-S12SD的灵敏度曲线经过加权后可以较好地匹配这个标准。模块本身工作在3.3V且其输出对供电电压波动不敏感稳定性很好。2.3 电源管理系统可充电与稳压这是实现“便携可充电”目标的关键也是电路设计中需要仔细处理的部分。1. 储能单元3.7V锂聚合物电池选择标准尺寸的3.7V LiPo电池容量根据盒子大小决定我用了1000mAh左右。它能量密度高、形状扁平适合放入狭小空间。其电压范围通常在3.0V完全放电到4.2V充满电之间。2. 充电管理TP4056模块这是一个极其常见且廉价的单节锂电池充电管理模块集成了Micro-USB输入口。它的核心是TP4056芯片负责以恒定电流/恒定电压方式安全地为电池充电。模块上的两个LED红/蓝直接指示充电状态红灯亮表示正在充电蓝灯亮表示充满或未接电池。这个模块的输出B和B-直接连接电池所以输出电压就是电池电压是未稳压的。3. 电压稳压S7V8F3降压模块由于Teensy 4.0、ST7789屏和GUVA-S12SD传感器都需要稳定的3.3V电压工作而电池电压在3.0V-4.2V之间波动因此必须加入稳压电路。我选择了S7V8F3这款开关降压稳压器模块。它效率高通常90%发热小能将高于3.3V的输入电压稳定输出到3.3V。当电池电压低于3.3V时它无法再维持输出设备会关机这恰好也保护了电池不会过放。4. 通断控制船型开关在TP4056的输出和S7V8F3的输入之间我串联了一个双路船型开关。这样关闭开关可以同时切断电池对后续电路的正极和负极供电实现完全断电消除待机功耗。如果你手头的开关是单路的只切断正极VCC也完全没问题。5. 电池电压监测分压电路为了在屏幕上显示剩余电量需要监测电池电压。但Teensy的模拟输入引脚只能承受最大3.3V电压而电池电压最高可达4.2V。因此我使用两个10kΩ电阻串联构成一个分压器将电池电压减半后再送入Teensy的模拟引脚A3。这样A3引脚接收到的最高电压约为2.1V处于安全范围内。在软件中需要将读取的电压值乘以2来还原真实的电池电压。2.4 整体电路连接思路将所有模块连接起来的核心原则是电源路径清晰信号线避免干扰。电源主干电池正负极接TP4056的B/B-。TP4056的OUT/OUT-接船型开关开关输出接S7V8F3的VIN/GND。S7V8F3的VOUT3.3V和GND作为系统总电源为Teensy的3.3V引脚、显示屏的VCC、传感器的VCC供电。信号连接显示屏Teensy的硬件SPI引脚如SCK-13, MOSI-11接显示屏的SCL和SDA再任意指定两个数字引脚接DC和RESET。传感器GUVA-S12SD的VOUT引脚接Teensy的某个模拟输入引脚如A0GND接系统地。电池监测分压器中间点接Teensy的A3引脚。接地所有模块的GND必须最终连接到一起即“共地”这是电路正常工作的基础。3. 嵌入式软件设计与代码深度剖析软件是项目的“大脑”负责数据采集、处理、计算和显示。我的代码结构力求清晰并针对用户体验做了优化。3.1 主程序架构高效的事件驱动更新主程序文件Burn_Meter.ino的核心逻辑非常简洁采用了“按需更新”的策略以提升显示流畅度和降低功耗。#include TFT_display.h #include battery.h #include UV_sensor.h float last_percent 100; float last_index 100; float percent_now; float index_now; void setup(void) { Serial.begin(9600); // 初始化串口用于调试 init_screen(); // 初始化显示屏 } void loop() { percent_now bat_percentage(); // 读取当前电量百分比 index_now UV_index(); // 读取当前紫外线指数 // 仅当电量百分比发生变化时更新屏幕上的电量显示部分 if (percent_now ! last_percent){ display_battery(percent_now); last_percent percent_now; } // 仅当紫外线指数发生变化时更新屏幕上的指数文本和表情图 if (index_now ! last_index){ display_texts(index_now); display_pic(index_now); last_index index_now; } delay(1000); // 每秒循环一次 }设计要点解析变量缓存last_percent和last_index用于保存上一次的读数。只有当新的读数与旧值不同时才调用相应的显示函数。这避免了屏幕每一秒都被全部刷新从而消除了因全局刷新导致的轻微闪烁和延迟操作感受更顺滑。更新频率delay(1000)将主循环定为1秒一次。对于紫外线监测这个应用场景1秒的更新间隔完全足够因为环境紫外线强度不会在毫秒级别剧烈变化。降低更新频率如改为2秒可以进一步省电但会牺牲实时性。提高频率则会增加功耗意义不大。模块化将显示屏驱动、电池电量计算、紫外线传感器读数分别封装在独立的.cpp/.h文件对中使得主程序逻辑清晰也便于后期维护和功能扩展。3.2 紫外线指数计算与传感器驱动在UV_sensor.cpp中核心函数是UV_index()。float UV_index() { int sensorValue analogRead(UV_SENSOR_PIN); // 假设UV_SENSOR_PIN定义为A0 float voltage sensorValue * (3.3 / 1023.0); // 将ADC值转换为电压Teensy 4.0 ADC为10位参考电压3.3V float uvi voltage / 0.1; // 根据GUVA-S12SD特性进行换算 // 可在此处添加校准或滤波代码例如滑动平均滤波 return uvi; }关键细节与校准系数0.1的由来这是一个基于传感器数据手册和实验得出的经验系数。GUVA-S12SD的输出电压与紫外线强度并非严格的线性关系且其光谱响应曲线与标准的红斑作用光谱存在差异。voltage / 0.1是一个简化的近似公式能给出一个相对合理的UVI值。为了获得更精确的读数你需要进行现场校准。如何进行校准在一个晴朗的中午用你的设备和一款经过认证的商业紫外线指数计或使用权威气象App的数据进行对比测量。记录下你的设备读取的电压值和实际的UVI。通过多次测量你可以拟合出一个更准确的公式例如UVI a * voltage b或者使用查找表。没有经过校准的DIY传感器其读数应视为相对参考值而非绝对精确值。软件滤波模拟读数容易受到噪声干扰。可以在UV_index()函数中加入软件滤波例如连续读取10次然后取平均值或者使用更复杂的滑动平均滤波、卡尔曼滤波等以获得更稳定的读数。3.3 动态图形显示让数据“活”起来这是本项目在用户体验上的一大亮点。TFT_display.cpp中的display_pic()和adjust_redness()函数共同实现了表情随紫外线强度变红的效果。1. 位图显示基础首先你需要将一张图片例如一个卡通脸转换为Arduino GFX库可以识别的位图数组。这通常通过在线转换工具完成生成一个const uint16_t face[] PROGMEM {...}的数组。display_pic()函数的核心是一个双重循环遍历这个数组的每一个像素颜色值并使用tft.drawPixel()函数将其绘制到屏幕的指定位置。2. 动态调色算法adjust_redness()函数接收一个原始像素颜色值16位RGB565格式和当前的紫外线指数然后输出一个调整后的颜色值。RGB565格式一个16位的颜色值前5位是红色R0-31中间6位是绿色G0-63后5位是蓝色B0-31。算法步骤int r color 11;将颜色值右移11位提取出红色的5位值。int new_r r (r * index / 5);根据紫外线指数index按比例增加红色分量。这里的除数5是一个可调节的敏感度系数。指数越大new_r增加值越大。你可以通过修改这个值来调整“变红”的速度。if (new_r 0b11111) { new_r 0b11111; }确保红色分量不超过5位所能表示的最大值31防止溢出导致颜色异常。uint16_t new_color (color 0b0000011111111111) | (new_r 11);这是一个位操作组合。(color 0b0000011111111111)将原始颜色的高5位红色清零保留低11位绿色和蓝色。(new_r 11)将新的红色值移到高5位。最后用|或操作合并生成新的颜色。实操心得图片选择务必选择背景为纯黑色RGB565值为0x0000的图片。因为黑色像素的红色分量r0代入公式new_r 0 (0 * index / 5)结果始终为0所以背景颜色不会改变。如果背景不是黑色整个背景也会跟着变红效果会很奇怪。3.4 电池电量估计算法电压法的局限与应对准确估算锂电池剩余电量SoC是一个复杂的课题通常需要库仑计。在本项目中我采用了一种简单实用的方法基于电压的查表法。代码battery.cpp中的bat_percentage()函数体现的就是这种方法。原理与实现读取电压通过分压电路和ADC读取电池电压并乘以2还原真实电压v。分段线性插值我参考了一个公开的3.7V LiPo电池放电曲线电压-容量对应表。代码中使用了一系列的if-else if语句将电压范围划分为多个小区间例如4.2V-4.15V对应100%-95%。在每个区间内假设电压与容量是线性关系利用两点式直线方程进行插值计算。边界处理确保计算结果不会超过0-100%的范围。这种方法的局限性不精确电池电压受负载电流、温度、电池老化程度影响很大。同一电压值在刚停止大电流放电和静置一段时间后对应的实际容量可能不同。平台区不明显锂电池在放电中期有很长的电压平台期约3.7V-3.9V在这段区间内电压变化很小但容量却在持续下降导致估算误差较大。无法校准每次充电后由于没有库仑计复位估算的“100%”点可能漂移。给开发者的建议仅供参考务必在屏幕上用文字提示用户电量显示为“估算值”仅供参考。可以用图形如电池图标代替精确数字降低用户对精度的预期。低电量预警这个方法在电池电压接近放电截止电压如3.3V-3.5V时相对准确。因此重点确保低电量预警例如电量低于20%时闪烁提示的可靠性这比显示精确的80%还是85%更有实际意义。温度补偿如果追求更高精度可以增加温度传感器根据温度对电压-容量关系进行修正。4. 机械结构设计与组装工艺一个好的电子项目需要一个结实、美观且人性化的外壳。我的设计目标是保护内部元件、方便观看和操作、易于加工组装。4.1 3D打印外壳设计要点我使用Onshape一款在线CAD软件进行建模你也可以用Fusion 360或SolidWorks。倾斜面板外壳顶部设计成约42度倾斜。这是经过多次手持模拟后确定的角度使得用户以自然握持姿势举起设备时屏幕正好面向眼睛同时顶部的传感器开口垂直朝向天空避免了外壳边缘对传感器造成遮挡影响读数准确性。你可以根据自己手掌大小和观看习惯在30-50度之间调整这个角度。开孔与固定屏幕开口尺寸略小于ST7789屏幕的显示区域用于后续安装亚克力保护板。传感器开口位于顶部正对GUVA-S12SD传感器。开关孔位于侧面用于安装船型开关。我将开关孔设计为沉入式即开关面板略低于外壳表面防止在背包中误触。充电口开口位于底部或背面尺寸需能容纳Micro-USB充电头的插入。螺丝柱在外壳内部设计多个带通孔的圆柱用于固定主控板、电池等。使用M2自攻螺丝无需在打印件上预攻螺纹对打印机精度要求低组装方便。可拆卸背板设计一个单独的背板用4颗M2x10mm螺丝与主体固定。这样便于后期维修或更换电池。防丢充电口塞单独打印一个带卡扣的小塞子用于不用时堵住充电口防尘。关键技巧在塞子上设计一个小孔并用一小段细绳或尼龙扎带将其与外壳背板连接这样塞子就永远不会丢了。4.2 材料选择与加工外壳材料PLA聚乳酸是最常见、最易打印的3D打印材料强度足够且环保。打印参数建议层高0.15mm-0.2mm填充率20%-25%。主壳体和背板打印时应将有开口的一面朝下放置于打印平台这样可以获得最光滑的顶面且无需支撑。屏幕保护使用1/8英寸约3mm厚的透明亚克力板用激光切割或手工裁切成比屏幕开口稍大的尺寸。用少量热熔胶沿边缘粘在外壳内侧。务必确保胶水不要污染亚克力板的可视区域。传感器保护这是本项目的一个特色。我使用了Alpha Nanotech的熔融石英玻璃片30x30x1mm。熔融石英在紫外线波段特别是UV-B和UV-C具有极高的透过率90%远高于普通玻璃或亚克力它们会吸收大部分紫外线。这保证了传感器测量的准确性。由于原片太大我戴着防割手套和护目镜用手沿着划痕将其掰成了合适的大小然后用热熔胶固定在外壳的传感器开口内侧。4.3 组装步骤与避坑指南组装顺序至关重要错误的顺序可能导致无法安装或需要返工。先内后外先固定后接线首先将船型开关从外壳内部向外推卡紧在侧面的开口处。务必在焊接开关连线之前完成这一步否则焊好线后开关可能穿不过小孔。然后将熔融石英玻璃和亚克力保护板用热熔胶分别粘在传感器和屏幕的开口内侧。等待胶水完全冷却固化。电路板焊接与布局在万用板上规划好各模块位置。我的布局是TP4056充电模块在板子一端S7V8F3稳压模块在另一端Teensy 4.0和电压分压电阻等在中间。确保所有较高的元件如USB口、电解电容不会与外壳或背板干涉。空间紧张时的连线技巧ST7789屏幕和GUVA-S12SD传感器的排针可能没有空间安装杜邦线插座。我的做法是直接将22AWG的实心铜线焊接到模块的焊盘上另一头焊接到万用板。焊接时可以用蓝丁胶或电工胶带将小模块临时固定在桌面上辅助操作。内部安装与绝缘用M2x5mm螺丝将屏幕和传感器模块固定到外壳对应的立柱上。将焊好的主控板用M2x10mm螺丝固定。至关重要的一步绝缘处理用绝缘胶带或海绵胶将锂聚合物电池包裹起来特别是正负极触点。同时检查万用板背面是否有尖锐的焊点或剪短的元件引脚。用绝缘胶带将这些可能戳破电池的点全部覆盖。最后再用胶带将电池平整地固定在主控板背面或预留的空位上。最终合盖理顺所有导线将背板盖上拧紧最后四颗螺丝。在合盖前可以短暂通电测试所有功能是否正常。5. 调试、校准与常见问题排查即使按照图纸一步步组装第一次通电也可能遇到问题。以下是基于我实际经验总结的排查清单。5.1 设备完全无反应屏幕不亮可能原因排查步骤解决方法电池没电连接USB充电线查看TP4056模块上的充电指示灯红灯是否亮起。充电一段时间后再尝试。开关未打开或损坏用万用表通断档测量开关在“ON”位置时两端是否导通。确保开关已打开。如损坏更换开关。电源连接错误1. 检查TP4056的B/B-是否接反电池极性非常危险。2. 检查S7V8F3的VIN是否接到开关后VOUT3.3V是否接到Teensy的3.3V引脚。3. 检查所有GND是否已共地。对照电路图仔细检查所有电源线连接。使用万用表测量关键点电压电池两端应~3.7V-4.2VS7V8F3的VOUT应稳定3.3V。主控板故障断开其他所有模块仅给Teensy供电尝试上传一个简单的Blink程序。如果Blink程序也无法运行检查Teensy是否损坏或USB驱动/Arduino IDE设置是否正确。5.2 屏幕亮但无显示或花屏可能原因排查步骤解决方法SPI接线错误检查SCK、MOSI、DC、RESET、CS如果使用引脚是否与代码中的定义一一对应。仔细核对接线图和代码中的引脚定义。库未安装或版本冲突在Arduino IDE中检查是否已安装Adafruit GFX和Adafruit ST7789库。通过“工具”-“管理库”搜索并安装最新版库。有时需要卸载旧版再安装新版。屏幕初始化代码错误检查init_screen()函数中针对你的屏幕型号如1.3寸IPS的初始化参数如宽度、高度、旋转角度是否正确。参考Adafruit ST7789库的示例程序修改初始化参数。常见的错误是宽度和高度设置反了。电源功率不足彩色LCD屏在点亮背光时瞬时电流较大。确保你的电池电量充足且所有电源连接线接触良好线径足够粗建议22AWG或更粗。5.3 紫外线读数异常始终为0、不变或极大可能原因排查步骤解决方法传感器被遮挡检查熔融石英玻璃是否清洁是否有胶水污渍覆盖了感光区域。清洁石英玻璃确保传感器正对天空时无遮挡。传感器接线错误检查GUVA-S12SD的VCC、GND、VOUT是否分别接3.3V、GND和Teensy的模拟输入引脚。重新焊接连接线。模拟引脚配置错误检查代码中UV_SENSOR_PIN的定义是否与实际接线引脚一致。修改代码中的引脚定义。环境光线极弱在室内或夜晚测试紫外线指数本就接近0。将设备置于阳光下测试。可以用验钞灯的紫外线部分需谨慎避免直射眼睛作为信号源进行快速验证。换算系数不准确读数有变化但数值与天气预报相差甚远。进行现场校准。在晴朗户外同时记录设备输出电压和权威气象来源的UVI计算校准系数。公式可能调整为UVI voltage * scale_factor offset。未进行软件滤波读数跳动剧烈。在UV_index()函数中添加滑动平均滤波。例如sensorValue (analogRead(UV_SENSOR_PIN) last_4_readings) / 5;。5.4 电池电量显示不准这是预期之内的情况如前文所述电压法本身就不精确。如果显示异常如充电后永远达不到100%或掉电过快可以校准ADC参考电压Teensy 4.0的内部电压基准可能略有偏差。在setup()中使用analogReadResolution(12)提高ADC精度如果支持并用电表实测Teensy的3.3V引脚电压替换代码battery.cpp中read_voltage()函数里的3.36这个常数。更新电压-容量对照表找到你所使用的具体型号锂电池的放电曲线图根据其数据更新bat_percentage()函数中的电压分段点和对应容量值。修改显示策略将精确的数字百分比改为5格或10格的图标显示用户对图标的精度容忍度更高。5.5 设备发热或耗电过快S7V8F3稳压芯片发热如果电池电压较高如4.2V压差较大稳压芯片会以发热形式消耗功率。这是正常现象只要不过热烫手即可。确保外壳有通风缝隙。屏幕背光常亮ST7789屏幕的背光是主要的耗电源。可以在代码中增加功能一段时间无操作后自动降低背光亮度或关闭屏幕需保留部分显示或通过按键唤醒。程序循环过快检查loop()中的delay(1000)是否被意外改小导致主控和屏幕频繁刷新增加功耗。完成所有调试后你的B.U.R.N. Meter就可以投入使用了。带上它去海滩、爬山或日常通勤它不仅能提供一个紫外线强度的参考更是你亲手打造的一个充满巧思的实用工具。这个项目涉及了嵌入式系统开发的完整链条从需求分析、硬件选型、电路设计、结构建模、固件编程到调试测试每一步都充满了实践学习的乐趣。希望这份详细的拆解能帮助你成功复现甚至激发灵感做出属于你自己的个性化环境监测设备。