diff --git a/VEML6075_stm32.cpp b/VEML6075_stm32.cpp new file mode 100644 index 0000000..e83a50b --- /dev/null +++ b/VEML6075_stm32.cpp @@ -0,0 +1,152 @@ +/* + * VEML6075.cpp + * + * Arduino library for the Vishay VEML6075 UVA/UVB i2c sensor. + * + * Author: Sean Caulfield + * + * License: GPLv2.0 + * + */ + +#include "VEML6075.h" + +VEML6075::VEML6075(I2C_HandleTypeDef &hi2c) : i2c(hi2c){ + + // Despite the datasheet saying this isn't the default on startup, it appears + // like it is. So tell the thing to actually start gathering data. + + + config = 0; + config |= VEML6075_CONF_SD_OFF; + + // App note only provided math for this one... + config |= VEML6075_CONF_IT_100MS; +} + +bool VEML6075::begin() { + + + if (getDevID() != VEML6075_DEVID) { + return false; + } + + // Write config to make sure device is enabled + write16(VEML6075_REG_CONF, config); + + return true; +} + +// Poll sensor for latest values and cache them +void VEML6075::poll() { + raw_uva = read16(VEML6075_REG_UVA); + raw_uvb = read16(VEML6075_REG_UVB); + raw_dark = read16(VEML6075_REG_DUMMY); + raw_vis = read16(VEML6075_REG_UVCOMP1); + raw_ir = read16(VEML6075_REG_UVCOMP2); +} + +uint16_t VEML6075::getRawUVA() { + return raw_uva; +} + +uint16_t VEML6075::getRawUVB() { + return raw_uvb; +} + +uint16_t VEML6075::getRawDark() { + return raw_dark; +} + +uint16_t VEML6075::getRawVisComp() { + return raw_vis; +} + +uint16_t VEML6075::getRawIRComp() { + return raw_ir; +} + + +uint16_t VEML6075::getDevID() { + return read16(VEML6075_REG_DEVID); +} + +float VEML6075::getUVA() { + //float comp_vis = raw_vis - raw_dark; + //float comp_ir = raw_ir - raw_dark; + //float comp_uva = raw_uva - raw_dark; + float comp_vis; + float comp_ir; + float comp_uva; + + // Constrain compensation channels to positive values + comp_ir = max(raw_ir - raw_dark, 0.0); + comp_vis = max(raw_vis - raw_dark, 0.0); + comp_uva = max(raw_uva - raw_dark, 0.0); + + // Scale by coeffs from datasheet + comp_vis *= VEML6075_UVI_UVA_VIS_COEFF; + comp_ir *= VEML6075_UVI_UVA_IR_COEFF; + + // Subtract out visible and IR components + comp_uva = max(comp_uva - comp_ir, 0.0); + comp_uva = max(comp_uva - comp_vis, 0.0); + + return comp_uva; +} + +float VEML6075::getUVB() { + //float comp_vis = raw_vis - raw_dark; + //float comp_ir = raw_ir - raw_dark; + //float comp_uvb = raw_uvb - raw_dark; + float comp_vis; + float comp_ir; + float comp_uvb; + + // Constrain compensation channels to positive values + comp_ir = max(raw_ir - raw_dark, 0.0); + comp_vis = max(raw_vis - raw_dark, 0.0); + comp_uvb = max(raw_uvb - raw_dark, 0.0); + + // Scale by coeffs from datasheet + comp_vis *= VEML6075_UVI_UVB_VIS_COEFF; + comp_ir *= VEML6075_UVI_UVB_IR_COEFF; + + // Subtract out visible and IR components + comp_uvb = max(comp_uvb - comp_ir, 0.0); + comp_uvb = max(comp_uvb - comp_vis, 0.0); + + return comp_uvb; +} + +float VEML6075::getUVIndex() { + float uva_weighted = getUVA() * VEML6075_UVI_UVA_RESPONSE; + float uvb_weighted = getUVB() * VEML6075_UVI_UVB_RESPONSE; + return (uva_weighted + uvb_weighted) / 2.0; +} + +uint16_t VEML6075::read16(uint8_t reg) { + uint8_t data[2]; + + HAL_I2C_Mem_Read(&i2c, VEML6075_ADDR, reg, I2C_MEMADD_SIZE_8BIT, data , 2, 100); + + return (data[1] << 8) | data[0]; +} + +void VEML6075::write16(uint8_t reg, uint16_t data) { + + uint8_t lsb = (uint8_t)(0xFF & (data >> 0)); + uint8_t msb = (uint8_t)(0xFF & (data >> 8)); + + uint8_t buffer[2] = {lsb,msb}; + + HAL_I2C_Mem_Write(&i2c, VEML6075_ADDR, reg, I2C_MEMADD_SIZE_8BIT, buffer, 2, 100); +} + +uint16_t VEML6075::max(uint16_t a, uint16_t b){ + if(a <= b){ + return b; + }else{ + return a; + } +} diff --git a/VEML6075_stm32.h b/VEML6075_stm32.h new file mode 100644 index 0000000..2f2fa2e --- /dev/null +++ b/VEML6075_stm32.h @@ -0,0 +1,121 @@ +/* + * VEML6075.h + * + * Arduino library for the Vishay VEML6075 UVA/UVB i2c sensor. + * + * Author: Sean Caulfield + * License: GPLv2.0 + * + */ + +#ifndef _VEML6075_H +#define _VEML6075_H + +#include "main.h" +#include "i2c.h" + +#define VEML6075_ADDR 0x20 +#define VEML6075_DEVID 0x26 + +// Reading the application note on calculation of UV index, the "dummy" channel +// value is actually not a dummy value at all, but the dark current count. +// NAMES ARE IMPORTANT PEOPLE. + +#define VEML6075_REG_CONF (0x00) // Configuration register (options below) +#define VEML6075_REG_UVA (0x07) // UVA register +#define VEML6075_REG_DUMMY (0x08) // Dark current register (NOT DUMMY) +#define VEML6075_REG_UVB (0x09) // UVB register +#define VEML6075_REG_UVCOMP1 (0x0A) // Visible compensation register +#define VEML6075_REG_UVCOMP2 (0x0B) // IR compensation register +#define VEML6075_REG_DEVID (0x0C) // Device ID register + +#define VEML6075_CONF_IT_50MS (0x00) // Integration time = 50ms (default) +#define VEML6075_CONF_IT_100MS (0x10) // Integration time = 100ms +#define VEML6075_CONF_IT_200MS (0x20) // Integration time = 200ms +#define VEML6075_CONF_IT_400MS (0x30) // Integration time = 400ms +#define VEML6075_CONF_IT_800MS (0x40) // Integration time = 800ms +#define VEML6075_CONF_IT_MASK (0x8F) // Mask off other config bits + +#define VEML6075_CONF_HD_NORM (0x00) // Normal dynamic seetting (default) +#define VEML6075_CONF_HD_HIGH (0x08) // High dynamic seetting + +#define VEML6075_CONF_TRIG (0x04) // Trigger measurement, clears by itself + +#define VEML6075_CONF_AF_OFF (0x00) // Active force mode disabled (default) +#define VEML6075_CONF_AF_ON (0x02) // Active force mode enabled (?) + +#define VEML6075_CONF_SD_OFF (0x00) // Power up +#define VEML6075_CONF_SD_ON (0x01) // Power down + +// To calculate the UV Index, a bunch of empirical/magical coefficients need to +// be applied to UVA and UVB readings to get a proper composite index value. +// Seems pretty hand wavey, though not nearly as annoying as the dark current +// not being subtracted out by default. + +#define VEML6075_UVI_UVA_VIS_COEFF (2.22) // aka coeff "A" +#define VEML6075_UVI_UVA_IR_COEFF (1.33) // aka coeff "B" +#define VEML6075_UVI_UVB_VIS_COEFF (2.95) // aka coeff "C" +#define VEML6075_UVI_UVB_IR_COEFF (1.74) // aka coeff "D" + +// Once the above offsets and crunching is done, there's a last weighting +// function to convert the ADC counts into the UV index values. This handles +// both the conversion into irradiance (W/m^2) and the skin erythema weighting +// by wavelength--UVB is way more dangerous than UVA, due to shorter +// wavelengths and thus more energy per photon. These values convert the +// compensated values. +// +// NB These are the "open air" values given in the application note for the +// VEML6075. + +#define VEML6075_UVI_UVA_RESPONSE (0.001461) +#define VEML6075_UVI_UVB_RESPONSE (0.002591) + +enum veml6075_int_time { + VEML6075_IT_50MS, + VEML6075_IT_100MS, + VEML6075_IT_200MS, + VEML6075_IT_400MS, + VEML6075_IT_800MS +}; +typedef enum veml6075_int_time veml6075_int_time_t; + +class VEML6075 { + + public: + + VEML6075(I2C_HandleTypeDef &hi2c); + bool begin(); + I2C_HandleTypeDef &i2c; + + void poll(); + float getUVA(); + float getUVB(); + float getUVIndex(); + uint16_t getDevID(); + + uint16_t getRawUVA(); + uint16_t getRawUVB(); + uint16_t getRawDark(); + uint16_t getRawVisComp(); + uint16_t getRawIRComp(); + + void setIntegrationTime(veml6075_int_time_t it); + + private: + + uint16_t max(uint16_t,uint16_t b); + + uint8_t config; + + uint16_t raw_uva; + uint16_t raw_uvb; + uint16_t raw_dark; + uint16_t raw_vis; + uint16_t raw_ir; + + uint16_t read16(uint8_t reg); + void write16(uint8_t reg, uint16_t data); + +}; + +#endif