From 955b3a56e8e116d3d70fee20120e999b719b1e78 Mon Sep 17 00:00:00 2001 From: cn Date: Wed, 18 Sep 2019 19:26:41 +0200 Subject: [PATCH] veml6070: add Vishay UV light sensor --- Makefile | 1 + README.md | 1 + examples/veml6070/main.go | 41 +++++++++++ veml6070/registers.go | 65 ++++++++++++++++++ veml6070/veml6070.go | 140 ++++++++++++++++++++++++++++++++++++++ 5 files changed, 248 insertions(+) create mode 100644 examples/veml6070/main.go create mode 100644 veml6070/registers.go create mode 100644 veml6070/veml6070.go diff --git a/Makefile b/Makefile index dc3dcfa..c6526ce 100644 --- a/Makefile +++ b/Makefile @@ -50,5 +50,6 @@ smoke-test: tinygo build -size short -o ./build/test.elf -target=trinket-m0 ./examples/bme280/main.go tinygo build -size short -o ./build/test.elf -target=circuitplay-express ./examples/microphone/main.go tinygo build -size short -o ./build/test.elf -target=circuitplay-express ./examples/buzzer/main.go + tinygo build -size short -o ./build/test.elf -target=trinket-m0 ./examples/veml6070/main.go test: clean fmt-check smoke-test diff --git a/README.md b/README.md index a0aa8a1..c428dc2 100644 --- a/README.md +++ b/README.md @@ -82,6 +82,7 @@ func main() { | [ST7735 TFT color display](https://www.crystalfontz.com/controllers/Sitronix/ST7735R/319/) | SPI | | [ST7789 TFT color display](https://cdn-shop.adafruit.com/product-files/3787/3787_tft_QT154H2201__________20190228182902.pdf) | SPI | | [Thermistor](https://www.farnell.com/datasheets/33552.pdf) | ADC | +| [VEML6070 UV light sensor](https://www.vishay.com/docs/84277/veml6070.pdf) | I2C | | [VL53L1X time-of-flight distance sensor](https://www.st.com/resource/en/datasheet/vl53l1x.pdf) | I2C | | [Waveshare 2.13" e-paper display](https://www.waveshare.com/w/upload/e/e6/2.13inch_e-Paper_Datasheet.pdf) | SPI | | [Waveshare 2.13" (B & C) e-paper display](https://www.waveshare.com/w/upload/d/d3/2.13inch-e-paper-b-Specification.pdf) | SPI | diff --git a/examples/veml6070/main.go b/examples/veml6070/main.go new file mode 100644 index 0000000..27fbf0d --- /dev/null +++ b/examples/veml6070/main.go @@ -0,0 +1,41 @@ +package main + +import ( + "time" + + "machine" + + "tinygo.org/x/drivers/veml6070" +) + +func main() { + machine.I2C0.Configure(machine.I2CConfig{}) + sensor := veml6070.New(machine.I2C0) + + if !sensor.Configure() { + println("VEML6070 could not be configured") + return + } + + println("VEML6070 configured") + + for { + intensity, _ := sensor.ReadUVALightIntensity() + println("UVA light intensity:", float32(intensity)/1000.0, "W/(m*m)") + + switch sensor.GetEstimatedRiskLevel(intensity) { + case veml6070.UVI_RISK_LOW: + println("UV risk level: low") + case veml6070.UVI_RISK_MODERATE: + println("UV risk level: moderate") + case veml6070.UVI_RISK_HIGH: + println("UV risk level: high") + case veml6070.UVI_RISK_VERY_HIGH: + println("UV risk level: very high") + case veml6070.UVI_RISK_EXTREME: + println("UV risk level: extreme") + } + + time.Sleep(2 * time.Second) + } +} diff --git a/veml6070/registers.go b/veml6070/registers.go new file mode 100644 index 0000000..75fb87e --- /dev/null +++ b/veml6070/registers.go @@ -0,0 +1,65 @@ +package veml6070 + +// I2C addresses and other constants + +const ( + ADDR_L = 0x38 // 7bit address of the VEML6070 (write, read) + ADDR_H = 0x39 // 7bit address of the VEML6070 (read) +) + +// Some possible values for resistance value (in ohm) of VEML6070 calibration resistor +const ( + RSET_240K = 240000 + RSET_270K = 270000 + RSET_300K = 300000 + RSET_600K = 600000 +) + +// Possible values for integration time of VEML6070 +// (internally represents the config register bit mask) +const ( + IT_HALF = 0x00 + IT_1 = 0x04 + IT_2 = 0x08 + IT_4 = 0x0C +) + +// Possible values for UVI (UV index) risk level estimations - the VEML6070 can +// only estimate UVI risk levels since it can only sense UVA rays but the vendor +// tried to come up with some coarse thresholds, from application notes +const ( + UVI_RISK_LOW = iota + UVI_RISK_MODERATE + UVI_RISK_HIGH + UVI_RISK_VERY_HIGH + UVI_RISK_EXTREME +) + +// Scale factor in milliseconds / ohm to determine refresh time +// (aka sampling time) without IT_FACTOR for any given RSET, from datasheet. +// Note: 100.0 milliseconds are applicable for RSET=240 kOhm and IT_FACTOR=1 +const RSET_TO_REFRESHTIME_SCALE = 100.0 / RSET_240K + +// The refresh time in milliseconds for which NORMALIZED_UVA_SENSITIVITY +// is applicable to a step count +const NORMALIZED_REFRESHTIME = 100.0 + +// The UVA sensitivity in mW/(m*m)/step which is applicable to a step count +// normalized to the NORMALIZED_REFRESHTIME, from datasheet for RSET=240 kOhm +// and IT_FACTOR=1 +const NORMALIZED_UVA_SENSITIVITY = 50.0 + +// Config register + +// Possible values for shutdown +const ( + CONFIG_SD_DISABLE = 0x00 + CONFIG_SD_ENABLE = 0x01 +) + +// Enable / disable +const ( + CONFIG_DEFAULTS = 0x02 + CONFIG_ENABLE = CONFIG_SD_DISABLE | CONFIG_DEFAULTS + CONFIG_DISABLE = CONFIG_SD_ENABLE | CONFIG_DEFAULTS +) diff --git a/veml6070/veml6070.go b/veml6070/veml6070.go new file mode 100644 index 0000000..ccccb14 --- /dev/null +++ b/veml6070/veml6070.go @@ -0,0 +1,140 @@ +// Package veml6070 provides a driver for the VEML6070 digital UV light sensor +// by Vishay. +// +// Datasheet: +// https://www.vishay.com/docs/84277/veml6070.pdf +// Application Notes: +// https://www.vishay.com/docs/84310/designingveml6070.pdf +// +package veml6070 // import "tinygo.org/x/drivers/veml6070" + +import ( + "time" + + "machine" +) + +// Device wraps an I2C connection to a VEML6070 device. +type Device struct { + bus machine.I2C + AddressLow uint16 + AddressHigh uint16 + RSET uint32 + IT uint8 +} + +// New creates a new VEML6070 connection. The I2C bus must already be +// configured. +// +// This function only creates the Device object, it does not initialize the device. +// You must call Configure() first in order to use the device itself. +func New(bus machine.I2C) Device { + return Device{ + bus: bus, + AddressLow: ADDR_L, + AddressHigh: ADDR_H, + RSET: RSET_240K, + // Note: default to maximum to get as much precision as possible since + // raw data values larger than 16 bit can hardly occur with RSET below + // 300 kOhm in real world applications. Power saving due to shorter + // sampling time might be a reason to reduce this. + IT: IT_4, + } +} + +// Configure sets up the device for communication +func (d *Device) Configure() bool { + // save power by shutdown as early as possible, also serves as presence test + if err := d.disable(); err != nil { + return false + } + + return true +} + +// ReadUVALightIntensity returns the UVA light intensity (irradiance) +// in milli Watt per square meter (mW/(m*m)) +func (d *Device) ReadUVALightIntensity() (uint32, error) { + var err2 error + + if err := d.enable(); err != nil { + return 0, err + } + + // wait two times the refresh time to allow completion of a previous cycle + // with old settings (worst case) + time.Sleep(time.Duration(d.getRefreshTime()) * 2 * time.Millisecond) + + msb, err2 := d.readData(d.AddressHigh) + if err2 != nil { + return 0, err2 + } + + lsb, err2 := d.readData(d.AddressLow) + if err2 != nil { + return 0, err2 + } + + if err := d.disable(); err != nil { + return 0, err + } + + rawData := (uint32(msb) << 8) | uint32(lsb) + + // normalize raw data (step count sampled in d.getRefreshTime()) into the + // linearly scaled normalized data (step count sampled in 100ms) for which + // we know the UVA sensitivity + normalizedData := float32(rawData) * NORMALIZED_REFRESHTIME / d.getRefreshTime() + + // now we can calculate the absolute UVA power detected combining normalized + // data with known UVA sensitivity for this data, from datasheet + intensity := normalizedData * NORMALIZED_UVA_SENSITIVITY // mW/(m*m) + + return uint32(intensity + 0.5), nil +} + +// GetEstimatedRiskLevel returns estimated risk level from comparing UVA light +// intensity values in mW/(m*m) with thresholds calculated from application notes +func (d *Device) GetEstimatedRiskLevel(intensity uint32) uint8 { + if intensity <= 24888 { + return UVI_RISK_LOW + } else if intensity <= 49800 { + return UVI_RISK_MODERATE + } else if intensity <= 66400 { + return UVI_RISK_HIGH + } else if intensity <= 91288 { + return UVI_RISK_VERY_HIGH + } else { + return UVI_RISK_EXTREME + } +} + +func (d *Device) disable() error { + return d.bus.Tx(uint16(d.AddressLow), []byte{CONFIG_DISABLE}, nil) +} + +func (d *Device) enable() error { + return d.bus.Tx(uint16(d.AddressLow), []byte{CONFIG_ENABLE | d.IT}, nil) +} + +func (d *Device) readData(address uint16) (byte, error) { + data := []byte{0} + err := machine.I2C0.Tx(address, []byte{}, data) + return data[0], err +} + +// getRefreshTime returns the refresh time (aka sample time) in milliseconds +func (d *Device) getRefreshTime() float32 { + var it float32 + switch d.IT { + case IT_HALF: + it = 0.5 + case IT_1: + it = 1 + case IT_2: + it = 2 + case IT_4: + it = 4 + } + return float32(d.RSET) * RSET_TO_REFRESHTIME_SCALE * it +}