Project tutorial
UltraV: A Portable UV-Index Meter

UltraV: A Portable UV-Index Meter © CC BY-NC

A portable small device to verify anywhere the UV index in the sun rays.

  • 20 respects

Components and supplies

About this project

Being unable to expose myself to the sun due to a dermatological problem, I used the time I would have spent on the beach to build an ultraviolet rays meter. UltraV.

It is built on an Arduino Nano rev3, with an UV sensor, a DC/DC converter to raise the 3v battery voltage, and a small OLED display. My main target was to keep it portable, so that I could easily know the UV-index in any moment and in any place.

Step 1: Parts and Components

  • Microcontroller Arduino Nano rev.3
  • ML8511 UV sensor
  • 128×64 OLED diplay (SSD1306)
  • MT3608 DC-DC step-up
  • CR2 battery
  • CR2 battery holder
  • Switch
  • Enclosure case

Step 2: The Sensor

The ML8511 (Lapis Semiconductors) is an UV sensor, which is suitable for acquiring UV intensity indoors or outdoors. The ML8511 is equipped with an internal amplifier, which converts photo-current to voltage depending on the UV intensity. This unique feature offers an easy interface to external circuits such as ADC. In the power down mode, typical standby current is 0.1µA, thus enabling a longer battery life.


  • Photodiode sensitive to UV-A and UV-B
  • Embedded operational amplifier
  • Analog voltage output
  • Low supply current (300µA typ.) and low standby current (0.1µA typ.)
  • Small and thin surface mount package (4.0mm x 3.7mm x 0.73mm, 12-pin ceramic QFN)

Unfortunately, I didn’t have the chance to find any UV-transparent material to protect the sensor. Any kind of transparent cover I tested (plastic, glass, etc.) was attenuating the UV measurement. The better choice seems to be quartz fused silica glass, but I haven’t find any at a reasonable price, so I decided to leave the sensor outside the box, in open air.

Step 3: Operations

To take a measure, just switch on the device and point it to the sun for several seconds, keeping it aligned with the direction of the sun rays. Then watch at the display: the index on the left always shows the instant measure (one each 200 ms), while the reading on the right is the maximum reading taken during this session: that’s the one you need.

In the lower left part of the display it is reported also the WHO equivalent nomenclature (LOW, MODERATE, HIGH, VERY HIGH, EXTREME) for the measured UV-index.

Step 4: Battery Voltage and Reading

I choose a CR2 battery, for its size and capacity (800 mAh). I used UltraV throughout the summer and the battery still reads 2.8 v, so I am quite satisfied of the choice. When operates, the circuit drains about 100 mA, but a reading measurement doesn’t take more than few seconds. As the battery nominal voltage is 3v, I added a DC-DC step up converter to bring the voltage up to 9 volts and connected it to the Vin pin.

In order to have the battery voltage indication on the display, I used an analog input (A2). Arduino analog inputs can be used to measure DC voltage between 0 and 5V, but this technique requires a calibration. To perform the calibration, you will need a multimeter. First power the circuit with your final battery (the CR2) and don’t use the USB power from the computer; measure the 5V on the Arduino from the regulator (found on the Arduino 5V pin): this voltage is used for the Arduino ADC reference voltage by default. Now put the measured value into the sketch as follows (suppose I read 5.023):

voltage = ((long)sum / (long)NUM_SAMPLES * 5023) / 1024.0;

In the sketch, I am taking the voltage measurement as an average over 10 samples.

Step 5: Schematic and Connections

Step 6: Software

For the display, I used the U8g2lib which is very flexible and powerful for this kind of OLED displays, allowing a wide choice of fonts and good positioning functions.

Concerning the voltage reading from the ML8511, I used the 3.3v Arduino reference pin (accurate within 1%) as a base for the ADC converter. So, by doing an analog to digital conversion on the 3.3V pin (by connecting it to A1) and then comparing this reading against the reading from the sensor, we can extrapolate a true-to-life reading, no matter what VIN is (as long as it’s above 3.4V).

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

Download the full code from te following link.

Step 7: Enclosure Case

After several (bad) tests on manually cutting the rectangular display window on a commercial plastic box, I decided to design my own for it. So, with a CAD application I designed a box and to keep it as small as possible, I mounted the CR2 battery externally on the back side (with a battery holder glued on the box itself).

Download the STL file for the enclosure case, from the following link.

Step 8: Possible Future Improvements

  • Utilize an UV spectrometer to measure actual real-time UV-Index values under various conditions (UV spectrometers are very expensive);
  • Simultaneously record output from the ML8511 with the Arduino microcontroller;
  • Write algorithm to relate ML8511 output to actual UVI value in real-time under a wide range of atmospheric conditions.

Step 10: Credits


 Fabio Marzocca @ 2018
 Analog to digital conversions rely completely on VCC. We assume
 this is 5V but if the board is powered from USB this may be as high as 5.25V or as low as 4.75V: Because of this unknown window it makes the ADC fairly inaccurate
 in most cases. To fix this, we use the very accurate onboard 3.3V reference (accurate within 1%). So by doing an
 ADC on the 3.3V pin (A1) and then comparing this against the reading from the sensor we can extrapolate
 a true-to-life reading no matter what VIN is (as long as it's above 3.4V).

v. 2.0.0 - July 2018
   - moved from 16x2 LCD to OLED
v. 2.0.1 - Sept 2018
   - changed read battery function

#include <Arduino.h>
#include <U8g2lib.h>
#include <Wire.h>

U8G2_SSD1306_128X64_NONAME_F_SW_I2C u8g2(U8G2_R0, /* clock=*/ SCL, /* data=*/ SDA, /* reset=*/ U8X8_PIN_NONE);   // All Boards without Reset of the Display

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

//Hardware pin definitions
const int UVOUT = A0; //Output from the sensor
const int REF_3V3 = A1; //3.3V power on the Arduino board
const int VBATT = A2; //Battery voltage

float maxUV = 0; //Max UV index read

void setup()
  pinMode(UVOUT, INPUT);
  pinMode(REF_3V3, INPUT);
  pinMode(VBATT, INPUT);


void loop()
  do {
    int uvLevel = averageAnalogRead(UVOUT);
    int refLevel = averageAnalogRead(REF_3V3);

    //Use the 3.3V power pin as a reference to get a very accurate output value from sensor
    float outputVoltage = 3.3 / refLevel * uvLevel;

    float uvIntensity = mapfloat(outputVoltage, 0.99, 2.6, 0.0, 15.0); //Convert the voltage to a UV intensity level

    if (maxUV < uvIntensity) {
        maxUV = uvIntensity;

    //Instant UV
    //UV Max

    u8g2.setCursor(10,52); u8g2.print(F("INSTANT"));
    u8g2.setCursor(75,52); u8g2.print(F("MAXIMUM"));

    u8g2.setCursor(88,64); u8g2.print(F("F.Marzocca"));
   } while ( u8g2.nextPage() );

// Reads maxUV and prints the UV category
void showUVCategory() {
  char strCat[12];

  byte categ = (byte)(maxUV+0.5);  //round up
  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!");

//Takes an average of readings on a given pin
//Returns the average
int averageAnalogRead(int pinToRead)
  byte numberOfReadings = 16;
  unsigned int runningValue = 0; 

  for(int x = 0 ; x < numberOfReadings ; x++)
    runningValue += analogRead(pinToRead);
  runningValue /= numberOfReadings;


//The Arduino Map function but for floats
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; //Battery minimum voltage read on Vcc
   byte batteryBar; //Battery progress bar
   int barStep = 140; //step for battery progress bar (235)

   batteryBar = (battery - VccMin)/barStep;
       //battery icon
    u8g2.drawGlyph(1, FIRST_ROW_Y, 73 );
    // battery status cursor
    for (byte i=1; i<=batteryBar; i++) {
       u8g2.drawGlyph( 128-9*i,FIRST_ROW_Y-4,75);
    // battery voltage
    u8g2.setCursor(25, FIRST_ROW_Y);
    u8g2.print(float(battery)/1000, 3);

long readBattVcc()
    int sum=0;
    int sample_count=0;
    long voltage= 0;
    #define NUM_SAMPLES 10
    // take a number of analog samples and add them up
    while (sample_count < NUM_SAMPLES) {
        sum += analogRead(VBATT);
        // calculate the voltage
    // use 5000 for a 5.0V ADC reference voltage
    // 5020V is the calibrated reference voltage (in millivolts) for my project
    voltage = ((long)sum / (long)NUM_SAMPLES * 5020) / 1024.0;
    return voltage; //Vbattery in millivolts


Similar projects you might like

Mobile Fine Dust (PM10 & PM2.5) and NO2 Meter

Project showcase by Telina

  • 84 respects

Arduino pH Meter

Project tutorial by Atlas Scientific

  • 40 respects

Wireless Water-Tank Level Meter with Alarm

Project showcase by Manusha_Ramanayake

  • 34 respects

Portable Environment Monitor

Project tutorial by Tharindu Liyanage

  • 9 respects

LCD Screens and the Arduino Uno

Project tutorial by Mahamudul Karim Khondaker

  • 1 comment
  • 3 respects

Ultrasonic occupancy counter

Project tutorial by harrrrrrrrrrrrrrrry

  • 1 comment
  • 3 respects
Add projectSign up / Login