亿迅智能制造网
工业4.0先进制造技术信息网站!
首页 | 制造技术 | 制造设备 | 工业物联网 | 工业材料 | 设备保养维修 | 工业编程 |
home  MfgRobots >> 亿迅智能制造网 >  >> Manufacturing Technology >> 制造工艺

UltraV:便携式紫外线指数仪,守护阳光下的安全

组件与物料

组件 说明 数量
Arduino Nano R3 微控制器核心 1
Texas Instruments ML-8511 紫外线传感器 1
128x64 OLED 显示屏 SSD1306 驱动 1
MT3608 DC-DC 升压模块 将 3V 电池电压升至 9V 供 Vin 引脚 1
CR2 (3V) 电池 800mAh 容量 1
CR2 电池座 固定电池 1

项目简介

因皮肤问题无法暴晒,我利用本该在海滩的时间制作了这款紫外线强度仪——UltraV。

基于 Arduino Nano rev3,搭配 UV 传感器、DC/DC 升压模块和小型 OLED 显示屏。核心目标是便携,随时随地读取 UV 指数。

步骤 1:零件清单

步骤 2:传感器详解

UltraV:便携式紫外线指数仪,守护阳光下的安全

ML8511 (Lapis Semiconductors) 适合室内外紫外线强度采集。内置运放将光电流转换为电压输出,便于 ADC 采集。掉电模式待机电流仅 0.1µA,大幅延长续航。

特性:

UltraV:便携式紫外线指数仪,守护阳光下的安全

未找到合适的透紫外线保护材料(塑料、玻璃均衰减 UV),石英熔融二氧化硅玻璃虽佳但价格高昂,最终决定让传感器裸露在外。

步骤 3:操作方法

UltraV:便携式紫外线指数仪,守护阳光下的安全

开机对准太阳持续数秒,保持与光线方向一致。屏幕左侧显示即时值(每 200ms 刷新),右侧显示本次会话最大值——即您需要的读数。左下角同步显示 WHO 标准分级:LOW、MODERATE、HIGH、VERY HIGH、EXTREME。

步骤 4:电池电压与读数校准

选用 CR2 电池(800mAh),整个夏季使用后电压仍 2.8V,表现满意。工作电流约 100mA,但单次测量仅几秒。电池标称 3V,通过升压模块升至 9V 接 Vin 引脚。

为在屏幕显示电池电压,使用模拟输入 A2 测量。Arduino 模拟输入测 0-5V 需校准:

  1. 用最终电池供电(勿用 USB),万用表测 Arduino 5V 引脚电压(作为 ADC 参考)
  2. 将实测值填入代码,如实测 5.023V:
voltage = ((long)sum / (long)NUM_SAMPLES * 5023) / 1024.0;

代码对 10 个样本取平均。

步骤 5:原理图与接线

UltraV:便携式紫外线指数仪,守护阳光下的安全

步骤 6:软件设计

显示屏使用 U8g2lib,字体丰富、定位灵活。UV 传感器读数采用 Arduino 3.3V 基准引脚(精度 1%)作为 ADC 参考:将 3.3V 引脚接 A1,对比传感器读数(A0)与基准读数,消除供电电压波动影响(只要高于 3.4V):

int uvLevel = averageAnalogRead(UVOUT);
int refLevel = averageAnalogRead(REF_3V3);
float outputVoltage = 3.3 / refLevel * uvLevel;

完整代码下载: UltraV_OLED.ino.zip

步骤 7:外壳设计

UltraV:便携式紫外线指数仪,守护阳光下的安全

手工开矩形显示窗口效果不佳,改用 CAD 设计外壳。为极致小巧,CR2 电池外置于背部(电池座粘贴固定)。

STL 文件下载: UltraV.stl.zip

步骤 8:未来改进方向

步骤 9:图片集

UltraV:便携式紫外线指数仪,守护阳光下的安全
UltraV:便携式紫外线指数仪,守护阳光下的安全
UltraV:便携式紫外线指数仪,守护阳光下的安全
UltraV:便携式紫外线指数仪,守护阳光下的安全
UltraV:便携式紫外线指数仪,守护阳光下的安全
UltraV:便携式紫外线指数仪,守护阳光下的安全

步骤 10:致谢

完整代码

UltraV (C/C++)

/* Fabio Marzocca @ 2018
   ADC 完全依赖 VCC,USB 供电时可能 4.75V-5.25V,引入误差。
   利用板载精准 3.3V 基准(1% 精度)校正:
   对 3.3V 引脚 (A1) 与传感器 (A0) 同步 ADC,通过比值还原真实读数。 */
#include <Arduino.h>
#include <U8g2lib.h>
#include <Wire.h>

U8G2_SSD1306_128X64_NONAME_F_SW_I2C u8g2(U8G2_R0, SCL, SDA, U8X8_PIN_NONE);

#define FIRST_ROW_Y 16
#define FIRST_ROW_X 16
#define BOX_H 38

const int UVOUT = A0;
const int REF_3V3 = A1;
const int VBATT = A2;
float maxUV = 0;

void setup() {
  pinMode(UVOUT, INPUT);
  pinMode(REF_3V3, INPUT);
  pinMode(VBATT, INPUT);
  u8g2.begin();
}

void loop() {
  u8g2.firstPage();
  do {
    int uvLevel = averageAnalogRead(UVOUT);
    int refLevel = averageAnalogRead(REF_3V3);
    float outputVoltage = 3.3 / refLevel * uvLevel;
    float uvIntensity = mapfloat(outputVoltage, 0.99, 2.6, 0.0, 15.0);
    readBattery();
    if (maxUV < uvIntensity) maxUV = uvIntensity;

    u8g2.drawFrame(0, FIRST_ROW_Y+1, 128, BOX_H);
    u8g2.setFont(u8g2_font_logisoso18_tf);
    u8g2.setCursor(10, 40); u8g2.print(uvIntensity);   // 即时值
    u8g2.setCursor(70, 40); u8g2.print(maxUV);         // 最大值
    u8g2.setFont(u8g2_font_u8glib_4_tf);
    u8g2.setCursor(10, 52); u8g2.print(F("INSTANT"));
    u8g2.setCursor(75, 52); u8g2.print(F("MAXIMUM"));
    showUVCategory();
    u8g2.setCursor(88, 64); u8g2.print(F("F.Marzocca"));
  } while (u8g2.nextPage());
  delay(200);
}

void showUVCategory() {
  char strCat[12];
  byte categ = (byte)(maxUV + 0.5); // 四舍五入
  if (categ >= 0 && categ < 3) strcpy(strCat, "LOW");
  else if (categ >= 3 && categ < 6) strcpy(strCat, "MODERATE");
  else if (categ >= 6 && categ < 8) strcpy(strCat, "HIGH !");
  else if (categ >= 8 && categ < 10) strcpy(strCat, "VERY HIGH!");
  else if (categ >= 11) strcpy(strCat, "EXTREME!");
  u8g2.setCursor(0, 64); u8g2.print(strCat);
}

int averageAnalogRead(int pinToRead) {
  byte numberOfReadings = 16;
  unsigned int runningValue = 0;
  for (int x = 0; x < numberOfReadings; x++) runningValue += analogRead(pinToRead);
  runningValue /= numberOfReadings;
  return runningValue;
}

float mapfloat(float x, float in_min, float in_max, float out_min, float out_max) {
  return (x - in_min) * (out_max - out_min) / (in_max - in_min) + out_min;
}

void readBattery() {
  long battery = readBattVcc();
  long VccMin = 2300;
  byte batteryBar;
  int barStep = 140;
  batteryBar = (battery - VccMin) / barStep;
  
  u8g2.setFont(u8g2_font_open_iconic_embedded_2x_t);
  u8g2.drawGlyph(1, FIRST_ROW_Y, 73); // 电池图标
  u8g2.setFont(u8g2_font_open_iconic_play_1x_t);
  for (byte i = 1; i <= batteryBar; i++) {
    u8g2.drawGlyph(128 - 9*i, FIRST_ROW_Y - 4, 75); // 电量条
  }
  u8g2.setFont(u8g2_font_freedoomr10_tu);
  u8g2.setCursor(25, FIRST_ROW_Y);
  u8g2.print(float(battery)/1000, 3); // 电压显示
}

long readBattVcc() {
  int sum = 0, sample_count = 0;
  long voltage = 0;
  #define NUM_SAMPLES 10
  while (sample_count < NUM_SAMPLES) {
    sum += analogRead(VBATT);
    sample_count++;
    delay(2);
  }
  // 使用校准后的 5020mV 作为参考电压
  voltage = ((long)sum / (long)NUM_SAMPLES * 5020) / 1024.0;
  return voltage; // 返回毫伏
}

制造工艺

  1. 移动厕所:历史、设计与环境影响
  2. 便携式点焊机:原理、操作与核心优势全解析
  3. 固定式与便携式三坐标测量机选型指南:核心决策因素
  4. 汽车蓄电池核心技术:原理、分类、规格与维护全攻略
  5. 发电机(交流发电机)检测全攻略:万用表、螺丝刀、断电法实测指南
  6. 发电机故障的7大信号:别等趴窝才后悔
  7. 分压器实验室:设计、测量与基尔霍夫电压定律(KVL)全验证
  8. 制作马铃薯电池:DIY 电化学电源分步指南
  9. 电表设计解析:从经典检流计到现代数字显示
  10. 万用表全解析:从模拟到数字的进化指南
  11. 电池构造基础:单体、内阻与连接方式
  12. 理解电池容量:安时额定值与状态测试