From 6f05615f3ea64172a330b452d32dc5da1b2d7062 Mon Sep 17 00:00:00 2001 From: Daniel Esteban Date: Tue, 30 Aug 2022 15:56:58 +0200 Subject: [PATCH] Initial support for VL6180x sensor required changes --- Makefile | 2 + README.md | 13 +- examples/vl6180x/main.go | 34 +++++ vl6180x/registers.go | 50 ++++++++ vl6180x/vl6180x.go | 270 +++++++++++++++++++++++++++++++++++++++ 5 files changed, 363 insertions(+), 6 deletions(-) create mode 100644 examples/vl6180x/main.go create mode 100644 vl6180x/registers.go create mode 100644 vl6180x/vl6180x.go diff --git a/Makefile b/Makefile index aeb59b6..681881f 100644 --- a/Makefile +++ b/Makefile @@ -143,6 +143,8 @@ smoke-test: @md5sum ./build/test.hex tinygo build -size short -o ./build/test.hex -target=itsybitsy-m0 ./examples/vl53l1x/main.go @md5sum ./build/test.hex + tinygo build -size short -o ./build/test.hex -target=itsybitsy-m0 ./examples/vl6180x/main.go + @md5sum ./build/test.hex tinygo build -size short -o ./build/test.hex -target=microbit ./examples/waveshare-epd/epd2in13/main.go @md5sum ./build/test.hex tinygo build -size short -o ./build/test.hex -target=microbit ./examples/waveshare-epd/epd2in13x/main.go diff --git a/README.md b/README.md index 245c84a..84056af 100644 --- a/README.md +++ b/README.md @@ -52,7 +52,7 @@ func main() { ## Currently supported devices -The following 82 devices are supported. +The following 83 devices are supported. | Device Name | Interface Type | |-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|-------------| @@ -97,10 +97,10 @@ The following 82 devices are supported. | [LPS22HB MEMS nano pressure sensor](https://www.st.com/resource/en/datasheet/dm00140895.pdf) | I2C | | [LSM6DS3 accelerometer](https://www.st.com/resource/en/datasheet/lsm6ds3.pdf) | I2C | | [LSM6DSOX accelerometer](https://www.st.com/resource/en/datasheet/lsm6dsox.pdf) | I2C | -| [LSM6DS3TR accelerometer](https://www.st.com/resource/en/datasheet/lsm6ds3tr.pdf) | I2C | +| [LSM6DS3TR accelerometer](https://www.st.com/resource/en/datasheet/lsm6ds3tr.pdf) | I2C | | [LSM303AGR accelerometer](https://www.st.com/resource/en/datasheet/lsm303agr.pdf) | I2C | | [LSM9DS1 accelerometer](https://www.st.com/resource/en/datasheet/lsm9ds1.pdf) | I2C | -| [Makey Button](https://makeymakey.com/) | GPIO | +| [Makey Button](https://makeymakey.com/) | GPIO | | [MAG3110 magnetometer](https://www.nxp.com/docs/en/data-sheet/MAG3110.pdf) | I2C | | [MAX7219 & MAX7221 display driver](https://datasheets.maximintegrated.com/en/ds/MAX7219-MAX7221.pdf) | SPI | | [MCP2515 Stand-Alone CAN Controller with SPI Interface](https://ww1.microchip.com/downloads/en/DeviceDoc/MCP2515-Family-Data-Sheet-DS20001801K.pdf) | SPI | @@ -114,7 +114,7 @@ The following 82 devices are supported. | [PCF8563 real time clock](https://www.nxp.com/docs/en/data-sheet/PCF8563.pdf) | I2C | | [Resistive Touchscreen (4-wire)](http://ww1.microchip.com/downloads/en/Appnotes/doc8091.pdf) | GPIO | | [RTL8720DN 2.4G/5G Dual Bands Wireless and BLE5.0](https://www.seeedstudio.com/Realtek8720DN-2-4G-5G-Dual-Bands-Wireless-and-BLE5-0-Combo-Module-p-4442.html) | UART | -| [SCD4x CO2 Sensor](https://sensirion.com/media/documents/C4B87CE6/627C2DCD/CD_DS_SCD40_SCD41_Datasheet_D1.pdf) | I2C | +| [SCD4x CO2 Sensor](https://sensirion.com/media/documents/C4B87CE6/627C2DCD/CD_DS_SCD40_SCD41_Datasheet_D1.pdf) | I2C | | [Semihosting](https://wiki.segger.com/Semihosting) | Debug | | [Servo](https://learn.sparkfun.com/tutorials/hobby-servo-tutorial/all) | PWM | | [Shift register (PISO)](https://en.wikipedia.org/wiki/Shift_register#Parallel-in_serial-out_\(PISO\)) | GPIO | @@ -132,12 +132,13 @@ The following 82 devices are supported. | [Thermistor](https://www.farnell.com/datasheets/33552.pdf) | ADC | | [TM1637 7-segment LED display](https://www.mcielectronics.cl/website_MCI/static/documents/Datasheet_TM1637.pdf) | I2C | | [TMP102 I2C Temperature Sensor](https://download.mikroe.com/documents/datasheets/tmp102-data-sheet.pdf) | I2C | -| [UC8151 All-in-one driver IC for ESL](https://www.buydisplay.com/download/ic/UC8151C.pdf) | I2C | +| [UC8151 All-in-one driver IC for ESL](https://www.buydisplay.com/download/ic/UC8151C.pdf) | I2C | | [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 | +| [VL6180X time-of-flight distance sensor](https://www.st.com/resource/en/datasheet/vl6180x.pdf) | I2C | | [Waveshare 2.13" (B & C) e-paper display](https://www.waveshare.com/w/upload/d/d3/2.13inch-e-paper-b-Specification.pdf) | SPI | | [Waveshare 2.13" e-paper display](https://www.waveshare.com/w/upload/e/e6/2.13inch_e-Paper_Datasheet.pdf) | SPI | -| [Waveshare 2.9" e-paper display (V1)](https://www.waveshare.com/w/upload/e/e6/2.9inch_e-Paper_Datasheet.pdf) | SPI | +| [Waveshare 2.9" e-paper display (V1)](https://www.waveshare.com/w/upload/e/e6/2.9inch_e-Paper_Datasheet.pdf) | SPI | | [Waveshare 4.2" e-paper B/W display](https://www.waveshare.com/w/upload/6/6a/4.2inch-e-paper-specification.pdf) | SPI | | [WS2812 RGB LED](https://cdn-shop.adafruit.com/datasheets/WS2812.pdf) | GPIO | | [XPT2046 touch controller](http://grobotronics.com/images/datasheets/xpt2046-datasheet.pdf) | GPIO | diff --git a/examples/vl6180x/main.go b/examples/vl6180x/main.go new file mode 100644 index 0000000..21eb61f --- /dev/null +++ b/examples/vl6180x/main.go @@ -0,0 +1,34 @@ +package main + +import ( + "machine" + + "tinygo.org/x/drivers/vl6180x" + + "time" +) + +func main() { + time.Sleep(3 * time.Second) + machine.I2C0.Configure(machine.I2CConfig{ + Frequency: 400000, + }) + sensor := vl6180x.New(machine.I2C0) + connected := sensor.Connected() + if !connected { + println("VL6180X device not found") + return + } + println("VL6180X device found") + sensor.Configure(true) + var value uint16 + var status uint8 + for { + value = sensor.Read() + status = sensor.ReadStatus() + println("Distance (mm):", value) + println("Status:", status) + println("---") + time.Sleep(100 * time.Millisecond) + } +} diff --git a/vl6180x/registers.go b/vl6180x/registers.go new file mode 100644 index 0000000..30f40cc --- /dev/null +++ b/vl6180x/registers.go @@ -0,0 +1,50 @@ +package vl6180x + +// The I2C address which this device listens to. +const Address = 0x29 + +// Registers +const ( + CHIP_ID = 0xB4 + WHO_AM_I = 0x0000 + SYSTEM_INTERRUPT_CONFIG = 0x0014 + SYSTEM_INTERRUPT_CLEAR = 0x0015 + SYSTEM_FRESH_OUT_OF_RESET = 0x0016 + SYSRANGE_START = 0x0018 + SYSRANGE_PART_TO_PART_RANGE_OFFSET = 0x0024 + SYSALS_START = 0x0038 + SYSALS_ANALOGUE_GAIN = 0x003F + SYSALS_INTEGRATION_PERIOD_HI = 0x0040 + SYSALS_INTEGRATION_PERIOD_LO = 0x0041 + RESULT_RANGE_STATUS = 0x004d + RESULT_INTERRUPT_STATUS_GPIO = 0x004f + RESULT_ALS_VAL = 0x0050 + RESULT_RANGE_VAL = 0x0062 + I2C_SLAVE_DEVICE_ADDRESS = 0x0212 + RANGING_INTERMEASUREMENT_PERIOD = 0x001b + ALS_INTERMEASUREMENT_PERIOD = 0x003e + + ALS_GAIN_1 = 0x06 ///< 1x gain + ALS_GAIN_1_25 = 0x05 ///< 1.25x gain + ALS_GAIN_1_67 = 0x04 ///< 1.67x gain + ALS_GAIN_2_5 = 0x03 ///< 2.5x gain + ALS_GAIN_5 = 0x02 ///< 5x gain + ALS_GAIN_10 = 0x01 ///< 10x gain + ALS_GAIN_20 = 0x00 ///< 20x gain + ALS_GAIN_40 = 0x07 ///< 40x gain + +) + +const ( + NONE VL6180XError = iota + SYSERR_1 + SYSERR_5 + ECEFAIL + NOCONVERGE + RANGEIGNORE + SNR + RAWUFLOW + RAWOFLOW + RANGEUFLOW + RANGEOFLOW +) diff --git a/vl6180x/vl6180x.go b/vl6180x/vl6180x.go new file mode 100644 index 0000000..14db261 --- /dev/null +++ b/vl6180x/vl6180x.go @@ -0,0 +1,270 @@ +// Package vl6180x provides a driver for the VL6180X time-of-flight distance sensor +// +// Datasheet: +// https://www.st.com/resource/en/datasheet/vl6180x.pdf +// This driver was based on the library https://github.com/adafruit/Adafruit_VL6180X +// and document 'AN4545 VL6180X basic ranging application note': +// https://www.st.com/resource/en/application_note/an4545-vl6180x-basic-ranging-application-note-stmicroelectronics.pdf +// +package vl6180x // import "tinygo.org/x/drivers/vl6180x" + +import ( + "time" + + "tinygo.org/x/drivers" +) + +type VL6180XError uint8 + +// Device wraps an I2C connection to a VL6180X device. +type Device struct { + bus drivers.I2C + Address uint16 + timeout uint32 +} + +// New creates a new VL6180X connection. The I2C bus must already be +// configured. +// +// This function only creates the Device object, it does not touch the device. +func New(bus drivers.I2C) Device { + return Device{ + bus: bus, + Address: Address, + timeout: 500, + } +} + +// Connected returns whether a VL6180X has been found. +// It does a "who am I" request and checks the response. +func (d *Device) Connected() bool { + return d.readReg(WHO_AM_I) == CHIP_ID +} + +// Configure sets up the device for communication +func (d *Device) Configure(use2v8Mode bool) bool { + if !d.Connected() { + return false + } + + if (d.readReg(SYSTEM_FRESH_OUT_OF_RESET) & 0x01) == 0x01 { + + // mandatory settings from page 24 of AN4545 + d.writeReg(0x0207, 0x01) + d.writeReg(0x0208, 0x01) + d.writeReg(0x0096, 0x00) + d.writeReg(0x0097, 0xfd) + d.writeReg(0x00e3, 0x00) + d.writeReg(0x00e4, 0x04) + d.writeReg(0x00e5, 0x02) + d.writeReg(0x00e6, 0x01) + d.writeReg(0x00e7, 0x03) + d.writeReg(0x00f5, 0x02) + d.writeReg(0x00d9, 0x05) + d.writeReg(0x00db, 0xce) + d.writeReg(0x00dc, 0x03) + d.writeReg(0x00dd, 0xf8) + d.writeReg(0x009f, 0x00) + d.writeReg(0x00a3, 0x3c) + d.writeReg(0x00b7, 0x00) + d.writeReg(0x00bb, 0x3c) + d.writeReg(0x00b2, 0x09) + d.writeReg(0x00ca, 0x09) + d.writeReg(0x0198, 0x01) + d.writeReg(0x01b0, 0x17) + d.writeReg(0x01ad, 0x00) + d.writeReg(0x00ff, 0x05) + d.writeReg(0x0100, 0x05) + d.writeReg(0x0199, 0x05) + d.writeReg(0x01a6, 0x1b) + d.writeReg(0x01ac, 0x3e) + d.writeReg(0x01a7, 0x1f) + d.writeReg(0x0030, 0x00) + + // recommended settings + d.writeReg(0x0011, 0x10) // enables polling when measurement completes + d.writeReg(0x010a, 0x30) // sets averaging sample period + d.writeReg(0x003f, 0x46) // sets light and dark gain + d.writeReg(0x0031, 0xFF) // sets the # of range measurements for auto calibration + d.writeReg(0x0041, 0x63) // sets ALS integration time to 100ms + d.writeReg(0x002e, 0x01) // performs a single temperature calibration + + // optional settings + d.writeReg(RANGING_INTERMEASUREMENT_PERIOD, 0x09) // sets ranging inter-measurement period to 100ms + d.writeReg(ALS_INTERMEASUREMENT_PERIOD, 0x31) // sets default ALS inter-measurement period to 500ms + d.writeReg(SYSTEM_INTERRUPT_CONFIG, 0x24) // configures interrupt + + d.writeReg(SYSTEM_FRESH_OUT_OF_RESET, 0x00) + time.Sleep(100 * time.Microsecond) + } + + return true +} + +// Read returns the proximity of the sensor in mm +func (d *Device) Read() uint16 { + start := time.Now() + + for d.dataReady() { + elapsed := time.Since(start) + if d.timeout > 0 && uint32(elapsed.Seconds()*1000) > d.timeout { + return 0 + } + } + + d.writeReg(SYSRANGE_START, 0x01) + for (d.readReg(RESULT_INTERRUPT_STATUS_GPIO) & 0x04) == 0 { + } + + return uint16(d.readRangeResult()) +} + +// dataReady returns true when the data is ready to be read +func (d *Device) dataReady() bool { + return (d.readReg(RESULT_RANGE_STATUS) & 0x01) == 0 +} + +// startRange starts the readings +func (d *Device) startRange() { + for d.dataReady() { + } + d.writeReg(SYSRANGE_START, 0x01) +} + +// IsRangeComplete return true when the reading is complete +func (d *Device) IsRangeComplete() bool { + if (d.readReg(RESULT_INTERRUPT_STATUS_GPIO) & 0x04) != 0 { + return true + } + return false +} + +// readRangeResults returns the sensor value from the register +func (d *Device) readRangeResult() uint8 { + value := d.readReg(RESULT_RANGE_VAL) + + d.writeReg(SYSTEM_INTERRUPT_CLEAR, 0x07) + return value +} + +// StartRangeContinuous starts the continuous reading mode +func (d *Device) StartRangeContinuous(periodInMs uint16) { + var periodReg uint8 + if periodInMs > 10 { + if periodInMs < 2550 { + periodReg = uint8(periodInMs/10) - 1 + } else { + periodReg = 254 + } + } + d.writeReg(RANGING_INTERMEASUREMENT_PERIOD, periodReg) + d.writeReg(SYSRANGE_START, 0x03) +} + +// StopRangeContinuous stops the continuous reading mode +func (d *Device) StopRangeContinuous() { + d.writeReg(SYSRANGE_START, 0x01) +} + +// ReadStatus returns the current status of the sensor +func (d *Device) ReadStatus() uint8 { + return d.readReg(RESULT_RANGE_STATUS) >> 4 +} + +// ReadLux returns the lux of the sensor +func (d *Device) ReadLux(gain uint8) (lux uint32) { + reg := d.readReg(SYSTEM_INTERRUPT_CONFIG) + reg &= ^uint8(0x38) + reg |= 0x4 << 3 + d.writeReg(SYSTEM_INTERRUPT_CONFIG, reg) + + d.writeReg(SYSALS_INTEGRATION_PERIOD_HI, 0) + d.writeReg(SYSALS_INTEGRATION_PERIOD_HI, 100) + + if gain > ALS_GAIN_40 { + gain = ALS_GAIN_40 + } + d.writeReg(SYSALS_ANALOGUE_GAIN, 0x40|gain) + + d.writeReg(SYSALS_START, 0x1) + for 4 != ((d.readReg(RESULT_INTERRUPT_STATUS_GPIO) >> 3) & 0x7) { + } + + lux = uint32(d.readReg16Bit(RESULT_ALS_VAL)) * 320 + d.writeReg(SYSTEM_INTERRUPT_CLEAR, 0x07) + + switch gain { + case ALS_GAIN_1: + break + case ALS_GAIN_1_25: + lux = (lux * 100) / 125 + break + case ALS_GAIN_1_67: + lux = (lux * 100) / 167 + break + case ALS_GAIN_2_5: + lux = (lux * 10) / 25 + break + case ALS_GAIN_5: + lux /= 5 + break + case ALS_GAIN_10: + lux /= 10 + break + case ALS_GAIN_20: + lux /= 20 + break + case ALS_GAIN_40: + lux /= 40 + break + } + + return lux +} + +// SetOffset sets the offset +func (d *Device) SetOffset(offset uint8) { + d.writeReg(SYSRANGE_PART_TO_PART_RANGE_OFFSET, offset) +} + +// SetAddress sets the I2C address which this device listens to. +func (d *Device) SetAddress(address uint8) { + d.writeReg(I2C_SLAVE_DEVICE_ADDRESS, address) + d.Address = uint16(address) +} + +// GetAddress returns the I2C address which this device listens to. +func (d *Device) GetAddress() uint8 { + return uint8(d.Address) +} + +// writeReg sends a single byte to the specified register address +func (d *Device) writeReg(reg uint16, value uint8) { + msb := byte((reg >> 8) & 0xFF) + lsb := byte(reg & 0xFF) + d.bus.Tx(d.Address, []byte{msb, lsb, value}, nil) +} + +// readReg reads a single byte from the specified address +func (d *Device) readReg(reg uint16) uint8 { + data := []byte{0} + msb := byte((reg >> 8) & 0xFF) + lsb := byte(reg & 0xFF) + d.bus.Tx(d.Address, []byte{msb, lsb}, data) + return data[0] +} + +// readReg16Bit reads two bytes from the specified address +// and returns it as a uint16 +func (d *Device) readReg16Bit(reg uint16) uint16 { + data := []byte{0, 0} + msb := byte((reg >> 8) & 0xFF) + lsb := byte(reg & 0xFF) + d.bus.Tx(d.Address, []byte{msb, lsb}, data) + return readUint(data[0], data[1]) +} + +// readUint converts two bytes to uint16 +func readUint(msb byte, lsb byte) uint16 { + return (uint16(msb) << 8) | uint16(lsb) +}