Outdoor UV index sensor
Here are instructions for outdoor UV index and ambient light sensor.
BoM
The construction is shown in the drawing:
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
- Soldering iron
- cutting pliers
- isolation stripping pliers
- connector crimping pliers PA-09
- epoxy mixing tools
- 3D printer
PCB
Simple PCB with sensors is round, with two sensors and decoupling capacitors.
A
3D printed parts
There are three differnet models for 3D printing:
Assembling
After printing, the two carrirer parts are glued together in one peace with Epoxy adhesive:
Cable gland PG7 is attached to the 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):
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.
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…
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.
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.
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!ž.
I use PLEXIGLAS® XT 0A770 for these applications.
It´s robust and durable!
http://www.matweb.com/search/datasheettext.aspx?matguid=9b5fe1c24dab4ac489b2911754b5d802
Thank you for the reference.
Marko
Hello,
Do you have any hints on where could I find a small piece of PLEXIGLAS® XT 0A770?
Thanks !
Sorry, not.