Outdoor UV index sensor

Here are instructions for outdoor UV index and ambient light sensor.

Assembled UV index and ambient light sensor

BoM

The construction is shown in the drawing:

UV index and illumination sensor construction
Position Description Quantity
1 PCB 1
2,3,4 Electronic components (see PCB section)  
5 Watch glass, 60 mm 1
6 O-ring 32×2 mm 1
7 Sensor carrier 1
8 Cable gland carrier 1
9 Glass lock 3
10 M3 Nut 3
11 Screw M3x10 3
12 Screw M2x8 6
13 Epoxy 2g
14 Cable gland PG7 2
15 4 wire shielded cable 1m
16 JST/Molex female crimps for 2,54mm pitch 4
15 Housing for JST/Molex crimps 2×3 1

Required tools and equipment

PCB

Simple PCB with sensors is round, with two sensors and decoupling capacitors.

Schematic (in PDF format)

Top side of the PCB
Bot side of the PCB
Assembly drawing
Assembled PCB

A

3D printed parts

There are three differnet models for 3D printing:

  • #7 – Sensor carrier (STL)
  • #8 – Cable gland carrier (STL)
  • #9 – Glass lock (STL)

Assembling

After printing, the two carrirer parts are glued together in one peace with Epoxy adhesive:

Joinig of the two plastic parts

Cable gland PG7 is attached to the base

Attach gland to the sensor base

Remove the isolation at one end of the cable and solder the wires to the PCB

Pinout is as follows (connect power supply and I2C signals – 4 wires):

Connector pinout.
Pin number Signal Color
1 Supply 3,3V Red
2 IRQ signal Not connected
3 SDA White
4 GND Black
5 SCL Green
6 +5V Not connected

Now insert the PCB with cable from the top into the housing and secure with three M2 screws.

PCB attached to the plastic housing
Insert M3 nuts inside the arms of the plastic housing
Add O-ring on the top around the PCB

Clean the glass and place it on the top of the sensor

Fix with three glass locks and M3 screws

Add second gland to the cable and prepare the cable for crimping

Add shrinking tube

Attach and crimp the contacts on wires

And finally insert the crimps inside the housing. Use same piunout as defined above for soldering the wires to the PCB

Now the sensor is prepared to start measuring the UV and ambient light.

Principle of operation – VEML6075

The VEML6075 senses UVA and UVB light and incorporates photodiode, amplifiers, and analog / digital circuits into a single chip using a CMOS process. When the UV sensor is applied, it is able to detect UVA and UVB intensity to provide a measure of the signal strength as well as allowing for UVI measurement. (Source: datasheet)

VEML6075.c – the VEML6975 driver source code for STM32 HAL library

 /*** 
	Description: VEML6075 driver
	License: GNU General Public License
	Maintainer: S54MTB
*/

/******************************************************************************
  * @file    VEML6075.c
  * @author  S54MTB
  * @version V1.0.0
  * @date    14-January-2018
  * @brief   VEML6075 driver
  ******************************************************************************
  * @attention
  *
  * <h2><center>© Copyright (c) 2018 Scidrom 
	* This program is free software: you can redistribute it and/or modify
  * it under the terms of the GNU General Public License as published by
  * the Free Software Foundation, either version 3 of the License, or
  * (at your option) any later version.
	*
  * This program is distributed in the hope that it will be useful,
  * but WITHOUT ANY WARRANTY; without even the implied warranty of
  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
  * GNU General Public License for more details.
  *
  * You should have received a copy of the GNU General Public License
  * along with this program.  If not, see <https://www.gnu.org/licenses/>.  
  ******************************************************************************
  */

#include "veml6075.h"

/*
 * The VEML6075 has 16-bit registers used to set up the measurements as 
 * well as pick up the measurement results.
 */

/*
 * VEML6075_ReadRegister() - Read register from VEML6075
 * @hi2c:  handle to I2C interface
 * @adr: I2C device address
 * @reg: Register address
 * @val: 16-bit register value from the VEML6075
 * Returns HAL status or HAL_ERROR for invalid parameters.
 */
static HAL_StatusTypeDef VEML6075_ReadRegister(I2C_HandleTypeDef *hi2c, 
						uint8_t adr, uint8_t reg, uint16_t *regval)
{
	uint8_t val[2];
	HAL_StatusTypeDef status;
	
	status = HAL_I2C_Mem_Read(hi2c, adr<<1, reg , I2C_MEMADD_SIZE_8BIT, val, 2, 100);

	if (status == HAL_OK)
	{
		*regval = val[0] | val[1] << 8;
	}
	return status;
}

/*
 * VEML6075_WriteRegister() - Write VEML6075 register
 * @hi2c:  handle to I2C interface
 * @adr: I2C device address
 * @reg: Register address
 * @val: 8-bit register value from the VEML6075
 * Returns HAL status or HAL_ERROR for invalid parameters.
 */
static HAL_StatusTypeDef VEML6075_WriteRegister(I2C_HandleTypeDef *hi2c, 
				uint8_t adr, uint8_t reg, uint16_t regval)
{	
	uint8_t val[2];
	val[1] = (regval >> 8) & 0xff;
	val[0] = regval & 0xff;
	HAL_StatusTypeDef status = HAL_I2C_Mem_Write(hi2c, adr<<1, reg, 
			I2C_MEMADD_SIZE_8BIT, val, 2, 100);
	return status;
}

HAL_StatusTypeDef VEML6075_WhoAmI( I2C_HandleTypeDef *hi2c, uint16_t *idval )
{
	return VEML6075_ReadRegister(hi2c, VEML6075ADDRESS, VEML6075_ID, idval); 
}


HAL_StatusTypeDef VEML6075_UVVAL( I2C_HandleTypeDef *hi2c, VEML6075_PARAM_t param, uint16_t *val )
{
	switch (param)
	{
		case VEML6075_PARAM_UVA:
			return VEML6075_ReadRegister(hi2c, VEML6075ADDRESS, VEML6075_UVA_DATA, val); 
		//break;

		case VEML6075_PARAM_UVB:
			return VEML6075_ReadRegister(hi2c, VEML6075ADDRESS, VEML6075_UVB_DATA, val); 
		//break;

		case VEML6075_PARAM_UVCOMP1:
			return VEML6075_ReadRegister(hi2c, VEML6075ADDRESS, VEML6075_UVCOMP1_DATA, val); 
		//break;

		case VEML6075_PARAM_UVCOMP2:
			return VEML6075_ReadRegister(hi2c, VEML6075ADDRESS, VEML6075_UVCOMP2_DATA, val); 
		//break;

		case VEML6075_PARAM_DARK:
			return VEML6075_ReadRegister(hi2c, VEML6075ADDRESS, VEML6075_DARK_DATA, val); 
		//break;
	}
	
	// invalid parameter if we reached here
	return HAL_ERROR;
}


HAL_StatusTypeDef VEML6075_ShutDown( I2C_HandleTypeDef *hi2c, bool sd )
{
	uint16_t regval = VEML6075_CONF_DEFAULT | (sd ? VEML6075_CONF_SD : 0);
	
	return VEML6075_WriteRegister(hi2c, VEML6075ADDRESS, VEML6075_UV_CONF, regval); 
}

/*
Readouts indexes and identifiers...
First column are subscriptors from 
http://www.vishay.com/docs/84339/designingveml6075.pdf

vis ---  0a  -- VEML6075_UVCOMP1_DATA  VEML6075_PARAM_UVCOMP1
dark --  08  -- VEML6075_DARK_DATA     VEML6075_PARAM_DARK
uva ---  07  -- VEML6075_UVA_DATA      VEML6075_PARAM_UVA
uvb ---  09  -- VEML6075_UVB_DATA      VEML6075_PARAM_UVB
ir ----  0b  -- VEML6075_UVCOMP2_DATA  VEML6075_PARAM_UVCOMP2
*/


/*!
 * \brief Get UVA reading from VEML6075 Sensor
 * returns: HAL Status
 */	
HAL_StatusTypeDef VEML6075_getUVA(I2C_HandleTypeDef *hi2c, float *uva) 
{
  HAL_StatusTypeDef status;
  float comp_vis;
  float comp_ir;
  float comp_uva;
  uint16_t raw_vis, raw_ir, raw_uva, raw_dark;
  
  status = VEML6075_UVVAL( hi2c, VEML6075_PARAM_UVA, &raw_uva);
	if (status == HAL_OK)
	{		
	   // get rest of the readouts  
    status += VEML6075_UVVAL( hi2c, VEML6075_PARAM_UVCOMP1, &raw_vis);
    status += VEML6075_UVVAL( hi2c, VEML6075_PARAM_UVCOMP2, &raw_ir);
    status += VEML6075_UVVAL( hi2c, VEML6075_PARAM_DARK, &raw_dark);
  } else return HAL_ERROR;

  comp_vis = (float)raw_vis - (float)raw_dark;
  comp_ir  = (float)raw_ir - (float)raw_dark;
  comp_uva = (float)raw_uva - (float)raw_dark;

  comp_uva -= (VEML6075_UVI_UVA_VIS_COEFF * comp_vis) - (VEML6075_UVI_UVA_IR_COEFF * comp_ir);

  *uva = comp_uva;
  
  return status;
}



/*!
 * \brief Get UVB reading from VEML6075 Sensor
 * returns: HAL Status
 */	
HAL_StatusTypeDef VEML6075_getUVB(I2C_HandleTypeDef *hi2c, float *uvb) 
{
  HAL_StatusTypeDef status;
  float comp_vis;
  float comp_ir;
  float comp_uvb;
  uint16_t raw_vis, raw_ir, raw_uvb, raw_dark;
  
  status = VEML6075_UVVAL( hi2c, VEML6075_PARAM_UVB, &raw_uvb);
	if (status == HAL_OK)
	{		
	   // get rest of the readouts  
    status += VEML6075_UVVAL( hi2c, VEML6075_PARAM_UVCOMP1, &raw_vis);
    status += VEML6075_UVVAL( hi2c, VEML6075_PARAM_UVCOMP2, &raw_ir);
    status += VEML6075_UVVAL( hi2c, VEML6075_PARAM_DARK, &raw_dark);
  } else return HAL_ERROR;

  comp_vis = (float)raw_vis - (float)raw_dark;
  comp_ir  = (float)raw_ir - (float)raw_dark;
  comp_uvb = (float)raw_uvb - (float)raw_dark;

  comp_uvb -= (VEML6075_UVI_UVB_VIS_COEFF * comp_vis) - (VEML6075_UVI_UVB_IR_COEFF * comp_ir);

  *uvb = comp_uvb;
  
  return status;
}




/*!
 * \brief Calculate UV index from compensated VEML6075 Sensor UV readings
 * returns: HAL Status
 */	
HAL_StatusTypeDef VEML6075_calculateUVIndex(I2C_HandleTypeDef *hi2c, float *uvindex) 
{
  float uva_weighted;
  float uvb_weighted;
  HAL_StatusTypeDef status;

  status = VEML6075_getUVA(hi2c, &uva_weighted);
  uva_weighted *= VEML6075_UVI_UVA_RESPONSE;
  status += VEML6075_getUVB(hi2c, &uvb_weighted);
  uvb_weighted *= VEML6075_UVI_UVB_RESPONSE;
  
  if (status == HAL_OK)
  {
    *uvindex = (uva_weighted + uvb_weighted) / 2.0;
  }  
  return status;
}



/*!
 * \brief Prepare UV, UVB and UV index readings in one single pass
 * returns: HAL Status
 */	
HAL_StatusTypeDef VEML6075_getreadouts(I2C_HandleTypeDef *hi2c, VEML6075_readout_t *reading)
{
  HAL_StatusTypeDef status;
  float comp_vis;
  float comp_ir;
  float comp_uva;
  float comp_uvb;
  float uva_weighted;
  float uvb_weighted;

  uint16_t raw_vis, raw_ir, raw_uva, raw_uvb, raw_dark;
  
  status = VEML6075_UVVAL( hi2c, VEML6075_PARAM_UVA, &raw_uva);
	if (status == HAL_OK)
	{		
	   // get rest of the readouts  
    status += VEML6075_UVVAL( hi2c, VEML6075_PARAM_UVCOMP1, &raw_vis);
    status += VEML6075_UVVAL( hi2c, VEML6075_PARAM_UVCOMP2, &raw_ir);
    status += VEML6075_UVVAL( hi2c, VEML6075_PARAM_DARK, &raw_dark);
    status += VEML6075_UVVAL( hi2c, VEML6075_PARAM_UVB, &raw_uvb);
  } else return HAL_ERROR;

  comp_vis = (float)raw_vis - (float)raw_dark;
  comp_ir  = (float)raw_ir - (float)raw_dark;
  comp_uva = (float)raw_uva - (float)raw_dark;
  comp_uvb = (float)raw_uvb - (float)raw_dark;
  
  comp_uva -= (VEML6075_UVI_UVA_VIS_COEFF * comp_vis) - (VEML6075_UVI_UVA_IR_COEFF * comp_ir);
  comp_uvb -= (VEML6075_UVI_UVB_VIS_COEFF * comp_vis) - (VEML6075_UVI_UVB_IR_COEFF * comp_ir);

  uva_weighted = comp_uva*VEML6075_UVI_UVA_RESPONSE;
  uvb_weighted = comp_uvb*VEML6075_UVI_UVB_RESPONSE;

  reading->uva = comp_uva;
  reading->uvb = comp_uvb;
  reading->uvindex = (uva_weighted + uvb_weighted) / 2.0;
  
  return status;
}





VEML6075.h – the VEML6975 driver header file

 /***
 
	Description: VEML6075 driver
	License: GNU General Public License
	Maintainer: S54MTB
*/

/******************************************************************************
  * @file    veml6075.h
  * @author  S54MTB
  * @version V1.0.0
  * @date    12-January-2018
  * @brief   veml6075 driver header file
  ******************************************************************************
  * @attention
  *
  * <h2><center>© Copyright (c) 2018 Scidrom 
	* This program is free software: you can redistribute it and/or modify
  * it under the terms of the GNU General Public License as published by
  * the Free Software Foundation, either version 3 of the License, or
  * (at your option) any later version.
	*
  * This program is distributed in the hope that it will be useful,
  * but WITHOUT ANY WARRANTY; without even the implied warranty of
  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
  * GNU General Public License for more details.
  *
  * You should have received a copy of the GNU General Public License
  * along with this program.  If not, see <https://www.gnu.org/licenses/>.  *
  ******************************************************************************
  */


#ifndef __VEML6075__
#define __VEML6075__

#include "stm32l0xx_hal.h"
#include "stdint.h"
#include "stdbool.h"

/**!
	* VEML6075 I2C Slave address
	*/
#define VEML6075ADDRESS				0x10

/**!
	* VEML6075 data registers
	*/
#define VEML6075_UV_CONF			0x00
#define VEML6075_UVA_DATA			0x07
#define VEML6075_DARK_DATA    0x08
#define VEML6075_UVB_DATA			0x09
#define VEML6075_UVCOMP1_DATA	0x0a
#define VEML6075_UVCOMP2_DATA	0x0b
#define VEML6075_ID						0x0c

#define VEML6075_IDRESPONSE		0x0026

/**!
	*	Register values define : CONF */
#define VEML6075_CONF_SD 0x01
#define VEML6075_CONF_UV_AF_AUTO 0x00
#define VEML6075_CONF_UV_AF_FORCE 0x02
#define VEML6075_CONF_UV_TRIG_NO 0x00
#define VEML6075_CONF_UV_TRIG_ONCE 0x04
#define VEML6075_CONF_HD 0x08
#define VEML6075_CONF_UV_IT_MASK 0x70
#define VEML6075_CONF_UV_IT_50MS 0x00
#define VEML6075_CONF_UV_IT_100MS 0x10
#define VEML6075_CONF_UV_IT_200MS 0x20
#define VEML6075_CONF_UV_IT_400MS 0x30
#define VEML6075_CONF_UV_IT_800MS 0x40
#define VEML6075_CONF_DEFAULT (VEML6075_CONF_UV_AF_AUTO | VEML6075_CONF_UV_TRIG_NO | VEML6075_CONF_UV_IT_100MS)


// Taken from application note:
// http://www.vishay.com/docs/84339/designingveml6075.pdf

#define VEML6075_UVI_UVA_VIS_COEFF    (2.22)
#define VEML6075_UVI_UVA_IR_COEFF     (1.33)
#define VEML6075_UVI_UVB_VIS_COEFF    (2.95)
#define VEML6075_UVI_UVB_IR_COEFF     (1.74)
#define VEML6075_UVI_UVA_RESPONSE     (1.0 / 909.0)
#define VEML6075_UVI_UVB_RESPONSE (1.0 / 800.0)


typedef enum
{
	VEML6075_PARAM_UVA,
	VEML6075_PARAM_UVB,
	VEML6075_PARAM_UVCOMP1,
	VEML6075_PARAM_UVCOMP2,
	VEML6075_PARAM_DARK
} VEML6075_PARAM_t;

typedef struct 
{
  float uva;
  float uvb;
  float uvindex;
}  VEML6075_readout_t;

HAL_StatusTypeDef VEML6075_WhoAmI( I2C_HandleTypeDef *hi2c, uint16_t *idval );
HAL_StatusTypeDef VEML6075_ShutDown( I2C_HandleTypeDef *hi2c, bool sd ) ;
//HAL_StatusTypeDef VEML6075_UVVAL( I2C_HandleTypeDef *hi2c, VEML6075_PARAM_t param, uint16_t *val );

HAL_StatusTypeDef VEML6075_getUVA(I2C_HandleTypeDef *hi2c, float *uva);
HAL_StatusTypeDef VEML6075_getUVB(I2C_HandleTypeDef *hi2c, float *uvb);
HAL_StatusTypeDef VEML6075_calculateUVIndex(I2C_HandleTypeDef *hi2c, float *uvindex);

HAL_StatusTypeDef VEML6075_getreadouts(I2C_HandleTypeDef *hi2c, VEML6075_readout_t *reading);

#endif

Initialization example code

/*!
 * \brief Init VEML6075 Sensor - init
 * returns: HAL Status
 */	
HAL_StatusTypeDef VEML6075_Init(void)
{
  uint16_t id;
  
  VEML6075_ShutDown( &SEN_hi2c1, true );
  HAL_Delay(50);
  VEML6075_ShutDown( &SEN_hi2c1, false );
  HAL_Delay(500);
	VEML6075_WhoAmI( &SEN_hi2c1, &id );

	return (id == VEML6075_IDRESPONSE) ? HAL_OK : HAL_ERROR;
}

Sensor presence example code

/*!
 * \brief Check if sensor is present
 * returns: 0 = not present, > 0 = sensor present
 */	
uint8_t VEML6075_Present(void)
{	
  uint16_t id;

  VEML6075_WhoAmI( &SEN_hi2c1, &id ); 
	
	return (id == VEML6075_IDRESPONSE) ? 1 : 0;	
}

Readout example

After I2C initialiyation (use CubeMX or do it with your own code), the VEML6075 can be read:

VEML6075_readout_t VEML6075_readouts[VEML6075_READOUTSNUM];
static uint16_t VEML6075_buffercounter;

/**
  * @brief  This function stores new readout to buffer
  * @retval number of places left in the buffer
  */  
uint16_t ALS_StoreReadout(void)
{
  HAL_StatusTypeDef status ;
  uint16_t white, als;

  status = VEML6030_ReadWhite(&SEN_hi2c1, VEML6030_ADDR, &white);
  status += VEML6030_ReadALS(&SEN_hi2c1, VEML6030_ADDR, &als);
	if (status == HAL_OK)
	{		
    ALS_readouts[ALS_buffercounter].als = als;
    ALS_readouts[ALS_buffercounter].white = white;
    ALS_buffercounter++;
  }
  return VEML6030_READOUTSNUM-ALS_buffercounter;
}

VEML6030 Ambient light sensor code

Not implemented yet…

7 Comments

  1. Lisa says:

    Are you compensating for the UV absorbed by the watch glass? I used a quartz slip with this sensor instead of glass, because it’s fairly transparent to UV and a consistent precise thickness.

    • Mare says:

      Hi,

      Thanks for asking. I started with slip glass but I had issues due to fragile nature of such thin glass. The sensor was developed for group of students and I didn’t want to cause blood “leakage” 🙂 There is another design based on the cover glass and I will post it in future. There are also measurements planed with the watch glass under monochromator in local university lab.

  2. Mare says:

    There is slave address conflict on the circuit: both devices have 0x10 slave address. Workaround is to hardwire the VEML6030 ADR pin to Vcc and to change te slave address define from 0x10 to 0x48!ž.

  3. Andre Bryx says:

    I use PLEXIGLAS® XT 0A770 for these applications.
    It´s robust and durable!
    http://www.matweb.com/search/datasheettext.aspx?matguid=9b5fe1c24dab4ac489b2911754b5d802

Leave a Reply to Lisa