UV Monitor

Andy Clark

Issue 22, May 2019

This article includes additional downloadable resources.
Please log in to access.

Log in

Protect your skin and eyes by monitoring the time and intensity of UV light.

UV light is all around us. The main source is obviously from the sun, but it is also present in man-made operations such as curing paints and glues, welding, PCB making, etc.

It is common in summer time to see the UV rating mentioned on the TV weather reports, and we see that it can vary significantly depending on the weather conditions.

Excessive exposure to UV could lead to sunburn, damage to your eyesight or other health issues.


This portable device monitors UV light and displays the output using an analogue gauge.

The Arduino-driven circuit can record exposure time and help prevent you from being sunburnt.

A battery enables the project to be operated remotely, and a power saving function helps extend the battery life.


The circuit uses the ATmega328P IC, which spends most of its time asleep. It is woken up periodically by a real-time clock chip.

The value of the UV sensor is read and added to the total exposure for that day. If the new sum is different from the previous, the servo is turned on, the pointer moves to the new location, and then the servo is turned off again.

Once a read cycle has completed the Arduino is put back to sleep.


The UV sensor is a key component to the project. After considerable online research, we found a sensor that outputted a linear value dependent on the level of UV light received.

Initially, we intended to use an operational amplifier configured as an integrator. A simple switch would reset the circuit by discharging a capacitor.


We examined this circuit by looking at the mathematical model. The formula for this circuit is defined as follows:


If we had a fixed value for Vin then we could express this more simply as:

Vout = ( Vin × t ) / RC


RC = ( Vin / Vout ) × t

So, if the sensor puts out 1V for 1 hour, and that’s our maximum exposure represented by 2.5V.

RC = (1/2.5) × 60 × 60 × 60 = 86,400

This would mean either a very high value for R or a very high value for C. The resistor and capacitor would also need to be very stable over a wide temperature range as it is likely we would leave our sensor in the sun. We would also need the capacitor 'C' to have low-leakage properties.


Given the above constraints, an analogue solution did not seem practical. We decided that the design needed to use a microcontroller.

The selection of the microcontroller was based on its capability to be run from a battery, have low power consumption, support I2C, available in a through hole form factor, readily available and be easy to program. For that reason, the ATmega328P was selected. To save a little time we picked one that had been preloaded with a bootloader so it could be used the Arduino software. To get reliable time measurements we added a real-time clock chip.


One of the key requirements of the design is that it needs to be portable and to run for extended periods of time. For this reason, it was designed to run from batteries and to use the low power modes of the microcontroller.

Initially, we thought this could be run straight from three alkaline batteries as the ATmega328P chip can run on a wide range of voltages. However, if we look at a typical battery discharge curve, the batteries start at 4.5V but rapidly run down to 4V and below.

The real-time clock chip requires a more controlled supply voltage of between 4.5V and 5.5V, so we can’t use that with this battery arrangement. There are other chips that do support a wider range but these are only available in surface mount packages, which we wanted to avoid.


To avoid re-inventing the wheel, we swapped to a USB battery pack that gives out a regulated 5V output. Some of these battery packs will shut down if too little current is drawn, so we ensured the one selected did not have that issue.

The ATmega328P chip has many sleep modes, which can drop the consumption as low as 1.7µA. The trade-off for this, however, is the time it will take to restart when it comes out of sleep. These times are minuscule though, compared to things like a PC or Tablet restarting.


The Fundamental Build:

Breadboard Prototype

Parts Required:JaycarAltronicsCore Electronics
1 × ATmega328PZZ8727Z5126CE05190
1 × 16MHz CrystalIncluded with ZZ8727V1289ACOM-00536
1 × 32.768KHz CrystalRQ5297V1902002-815-AB38T-32.768KHZ
1 × 100nF MKT CapacitorRM7125R3025BPOLOLU-1166
1 × DS1307+ Real Time Clock IC--002-700-DS1307
1 × Programming Header-Z6225DEV-09718
3 × 10K 1/4W Resistors*RR0596R7582COM-10969
1 × 220Ω 1/4W Resistor*RR0556R7542COM-10969
1 × 5mm Red LED*ZD0154Z0863COM-12903

Parts Required:

* Quantity shown, may only be available in packs. Breadboard and prototyping hardware is also required.


To check that we could program the ATmega328P, we breadboarded a basic circuit with an LED and the blink sketch. The basic circuit is just the ATmega and a crystal, with the LED and resistor for output. A 10K resistor forms a pull-up to keep the reset pin high.

When assembling the circuit try to get the crystal as close as possible to the ATmega328P. Also, note that when programming the ATmega328P that the “Pin 13”, which is used in the blink sketch, refers to the digital pin on the Arduino UNO. This equates to Port B #5 on the ATmega, which is pin 19 on the chip. See for pin mappings.


To program our Arduino, we can either plug it into an UNO and program it that way or use a USB to TTL adaptor. This adaptor converts the complex USB signals from your computer into a simpler protocol that the ATmega328P chip can understand. Look for an adaptor that includes a reset connection, typically labelled RST or DTS, as well as the usual RX and TX pins.


Once the circuit is built you can use the adaptor for other projects. We use ours to access the boot console on a Raspberry Pi, which is useful when there is no network or monitor attached.

If your adaptor has a switchable voltage, ensure it is switched to 5V needed for this circuit. Plugging in the adaptor makes it appear as a serial port on your computer.

Once the blink sketch was tested, the prototype circuit was expanded by the addition of the DS1307 RTC Chip. This chip is designed to be run with a coin cell battery, so to disable this feature for testing the battery + pin is linked to ground. It is essential to remove that link if you do attach a battery.

We used the example sketches for the RTC to set and test the real time clock. The sensor was tested using the AnalogInOutSerial example.


The Main Build:

UV Monitor

The schematic shown here is for the main build.

Parts Required:JaycarAltronicsCore Electronics
1 × ATmega328P (IC1) ZZ8727Z5126CE05190
1 × 28 Pin IC Socket PI6510P0571-
2 × 22pf Ceramic Capacitors* (C1 & C2) Included with ZZ8727R2814CE05206
1 × 16MHz Crystal (Y1) Included with ZZ8727V1289ACOM-00536
1 × 32.768kHz Crystal (Y2) RQ5297V1902002-815-AB38T-32.768KHZ
3 × 100nF MKT Capacitors (C3, C4 & C5) RM7125R3025BPOLOLU-1166
1 × 1N4004 Diode* (D1) ZR1004Z0109COM-14884
1 × DS1307+ Real Time Clock IC (IC2) --002-700-DS1307
1 × 8 Pin IC Socket PI6500 P0550 -PH9238-PRT-00783
1 × 20mm Button Cell Battery Holder (BT1) HM3211P5430FIT0084
1 × Header Terminal Strip (J1, J2 & J3) -Z6225DEV-09718
1 × Programming Header (J4) ZT2468Z1545COM-10213
1 × IRF1540 N-Channel MOSFET (Q1) RR0644R7630COM-10969
1 × 1M 1/4W Resistor* (R1) RR0596R7582COM-10969
3 × 10K 1/4W Resistors* (R2, R3 & R4) RR0556R7542COM-10969
1 × 220Ω 1/4W Resistor* (R5) YM2760Z6392-
1 × Servo (SV1) XC4518Z6397-
1 × UV Sensor (UV1)

Parts Required:

1 × ATmega328P (IC1) ZZ8727
1 × 28 Pin IC Socket PI6510
2 × 22pf Ceramic Capacitors* (C1 & C2) Included with ZZ8727
1 × 16MHz Crystal (Y1) Included with ZZ8727
1 × 32.768kHz Crystal (Y2) RQ5297
3 × 100nF MKT Capacitors (C3, C4 & C5) RM7125
1 × 1N4004 Diode* (D1) ZR1004
1 × DS1307+ Real Time Clock IC (IC2) -
1 × 8 Pin IC Socket PI6500 P0550 -PH9238
1 × 20mm Button Cell Battery Holder (BT1) HM3211
1 × Header Terminal Strip (J1, J2 & J3) -
1 × Programming Header (J4) ZT2468
1 × IRF1540 N-Channel MOSFET (Q1) RR0644
1 × 1M 1/4W Resistor* (R1) RR0596
3 × 10K 1/4W Resistors* (R2, R3 & R4) RR0556
1 × 220Ω 1/4W Resistor* (R5) YM2760
1 × Servo (SV1) XC4518
OPTIONAL:JaycarAltronicsCore Electronics
1 × Plastic CaseHB6248H0324ADA905


* Quantity shown, may only be available in packs. Mounting hardware is also required depending on the enclosure you choose.

The prototype has a very simple setup for the timing crystal. For the main build, we added a few extra components. Two load capacitors are added and connect the ends of the crystal to ground. These help the oscillator keep at a stable frequency. The parallel resistor allows the clock to stabilise more quickly after the device is turned on.

More information for this can be found at:

The other additional components are also to improve reliability and stability. These are two 100nF capacitors. The first from VCC to ground removes noise from the power supply rails. The second from AREF to ground ensures that the analogue to digital converter also has a clean supply of power.

To control the power to the servo, an N-Channel MOSFET was added. The gate of the MOSFET acts like a capacitor. When the microcontroller turns on the transistor it has to charge the capacitor. The resistor limits this momentary current flow to the gate. We also added a rectifier diode in reverse bias to protect the circuitry from back EMF when the servo is turned off.


The first step for moving the circuit to a printed circuit board is to draw up the circuit in a suitable design package. We used KiCad for this project.

Like most design packages, this has an “electrical rules check”, which does simple tests to ensure you have not connected a short across the supply and other simple tests. However, it does not check that you have entered all the wires you wanted, and on our first attempt, we missed the wire from the RTC square wave output to the ATmega.

Once the schematic is complete, the next step is to assign footprints to your components. These define the copper pads used by the component and any text or outline to put on the board using silkscreen printing. In the case of KiCad, this also includes the 3D models of the components. Watch out for similar sounding parts as these may have different footprints.


The last step is to lay down the components and tracks (routing). It is good to get a rough position of the components first and rotate these as necessary to fit. Place connectors and components with heat sinks around the edge of the board. Draw a rough outline and allow space for mounting rails or holes.

When connecting the pads together we like to do the power first and use bigger tracks. Use a track width calculator if you are not sure of the width to use. Next, route any components such as the crystals which need to be placed near to their respective ICs.

Finally, route the remaining tracks. Once you have a board designed it can be tested using the Design Rules Check. The DRC looks for tracks that are too close together, unconnected pins and overlapping footprints. If you are getting your board made at a fabrication house then check their recommendations and adjust the DRC parameters to match.

To check your actual components fit, you can print out a 1:1 image of the board and physically compare your components to it.

On our first attempt, we routed the board using the default settings designed for fabrication houses. So that it would be easier to make a board using a mill, we needed to increase the separation of the tracks. At the same time, we increased the track widths to make it better for home etching.



Fit the lower-profile components first, such as the the resistors, diode and crystals, followed by the IC and battery sockets, capacitors, pin headers and finally the MOSFET. Make sure you have soldered the IC socket, diode and MOSFET in the correct way.



For the power, a Type A USB cable was cut up and a header soldered to the end to allow it to be plugged into the board. Note that the cable colours are usually black for ground, red for 5V and green and white for the data connections. However, it is always worth checking these with a multimeter as some are wired differently.


To simplify the code, libraries were used to handle the low power functions and to interface to the RTC.

To install the DS1307 library from the IDE, open the Library Manager and search for “Rtc by Makuna” and install. The sleep library is installed manually by downloading the zip file from github then choosing the menu Sketch › Include Library › Add .ZIP Library.



The Servo library and the wire library, which is used to communicate on the I2C bus to the RTC chip, are installed by default.

#include <LowPower.h>
#include <Wire.h>
#include <RtcDS1307.h>
#include <Servo.h>
RtcDS1307<TwoWire> Rtc(Wire);
Servo servoDisplay; 


In the setup we configure pins, setup the variables that control the servos, enable the real-time clock and ensure the square wave output is configured for 1Hz.

const int wakeUpPin = 3;
const int servoPowerPin = 6; 
// MOSFET is attached to pin 12/PD6 Arduino pin 6
const int servoPin = 5; 
// servo is on pin 11/PD5 Arduino pin 5
const int sensorPin = A0;
int wakes = 0;
int servoPos = 0;
unsigned int hours[24];
unsigned int minutes[60];
void setup() {
  pinMode(servoPowerPin, OUTPUT);
  pinMode(wakeUpPin, INPUT);
  // Configure the RTC

The interrupt handler routine is empty as we return to the loop() when it exits.

void wakeUp()
  // Just a handler for the interrupt.

The main loop will get called when the chip wakes up. On the first run and then it will read the sensor each 60 seconds. The value is stored in an array and the sum of the value for the hour and the day is calculated.

The value is mapped to a servo position. If there is a new position for the servo then the power for the servo is enabled, and it moves to the new position.

Finally, the code attaches an interrupt to the wakeup pin and powers down until an external interrupt is received from the RTC, just under 1s later.

void loop() {
  if (wakes == 0) {
    //Read sensor every minute
    int sensorValue = analogRead(sensorPin);
    RtcDateTime dt = Rtc.GetDateTime();
    minutes[dt.Minute()-1] = sensorValue;
    //Calculate the total for the current hour
    unsigned long sum;
    sum = 0;
    for(byte i=0;i <= 59;i++) {
      sum = sum + minutes[i];
    hours[dt.Hour()-1] = sum;
    //Calculate the total for the current day
    sum = 0;
    for(byte i=0;i <= 23;i++) {
      sum = sum + hours[i];
    int newPos = map(sum,0,1473120,0,180);
    // Calibrate meter
    if (servoPos != newPos) {
      servoPos = newPos;
      digitalWrite(servoPowerPin, HIGH);
      delay(15);  //Give the servo time to move
      digitalWrite(servoPowerPin, LOW);
  wakes = ((wakes + 1) % 60);
  // Allow wake up pin to trigger interrupt
(wakeUpPin),wakeUp, RISING);
  //Enter power down state with ADC and BOD
module disabled.
  //Wake up when wake up pin is low.
  LowPower.powerDown(SLEEP_FOREVER, ADC_OFF,
  //Disable external pin interrupt on wake up pin


Before plugging in the ICs, use a continuity tester or multimeter to look for shorts or dry joints around the circuit. When inserting the ICs ensure that all of the pins end up in the socket and none are bent under the chip or outside the socket.

For testing, the board can be powered from the programming header. Note that you should not connect both the power from the computer and the battery at the same time.

As for the prototyping, you can test the subsections of the circuit using the example code. We used a modified sweep sketch to test the servo and MOSFET power switch. See PowerSweep.ino in the resources.

Note: The RTC does not run unless the coin cell is fitted and has charge.


The default code will display the maximum reading on the scale when the sensor has received the highest values all day. To calibrate your sensor use the weather forecast to find a day where the UV index is medium or high. Take note of the maximum number of minutes in the sun recommended for this UV index. Take some readings from your sensor using the AnalogInOutSerial sketch and make a note of the values. Replace the value 1473120 in the map function with your value times the number of minutes exposure.



For an enclosure, we made a simple wooden box with a clear polycarbonate lid. As polycarbonate blocks UV light we made a hole in the lid and glued the sensor below it. A printed scale was added and the pointer was one of the single-ended servo horns provided with the microservo.


An off the shelf box could also be used.


The DS1307 contains non-volatile RAM, which could be used to record exposure over a month.

A Bluetooth or WiFi board could send readings to your phone or computer, or perhaps capture data remotely with a LoRa module?

The same circuit with different code can be used with different sensors. Perhaps you want to measure temperature, sound levels or a strain gauge?

You can improve sensor accuracy by using an external voltage reference.