From d1ace447544bf049afa73f161b49d9467f70b936 Mon Sep 17 00:00:00 2001 From: Kenneth Bell Date: Tue, 11 May 2021 22:19:27 -0700 Subject: [PATCH] aht20: add device --- Makefile | 2 + README.md | 1 + aht20/aht20.go | 108 +++++++++++++++++++++++++++++++++++++++++ aht20/aht20_test.go | 74 ++++++++++++++++++++++++++++ aht20/registers.go | 20 ++++++++ examples/aht20/main.go | 55 +++++++++++++++++++++ 6 files changed, 260 insertions(+) create mode 100644 aht20/aht20.go create mode 100644 aht20/aht20_test.go create mode 100644 aht20/registers.go create mode 100644 examples/aht20/main.go diff --git a/Makefile b/Makefile index 872ccfa..57ffa2f 100644 --- a/Makefile +++ b/Makefile @@ -187,6 +187,8 @@ endif @md5sum ./build/test.hex tinygo build -size short -o ./build/test.hex -target=feather-m0 ./examples/ina260/main.go @md5sum ./build/test.hex + tinygo build -size short -o ./build/test.hex -target=nucleo-l432kc ./examples/aht20/main.go + @md5sum ./build/test.hex DRIVERS = $(wildcard */) NOTESTS = build examples flash semihosting pcd8544 shiftregister st7789 microphone mcp3008 gps microbitmatrix \ diff --git a/README.md b/README.md index 570b662..eb60f46 100644 --- a/README.md +++ b/README.md @@ -58,6 +58,7 @@ The following 62 devices are supported. |----------|-------------| | [ADT7410 I2C Temperature Sensor](https://www.analog.com/media/en/technical-documentation/data-sheets/ADT7410.pdf) | I2C | | [ADXL345 accelerometer](http://www.analog.com/media/en/technical-documentation/data-sheets/ADXL345.pdf) | I2C | +| [AHT20 I2C Temperature and Humidity Sensor](http://www.aosong.com/userfiles/files/media/AHT20%20%E8%8B%B1%E6%96%87%E7%89%88%E8%AF%B4%E6%98%8E%E4%B9%A6%20A0%2020201222.pdf) | I2C | | [AMG88xx 8x8 Thermal camera sensor](https://cdn-learn.adafruit.com/assets/assets/000/043/261/original/Grid-EYE_SPECIFICATIONS%28Reference%29.pdf) | I2C | | [APA102 RGB LED](https://cdn-shop.adafruit.com/product-files/2343/APA102C.pdf) | SPI | | [AT24CX 2-wire serial EEPROM](https://www.openimpulse.com/blog/wp-content/uploads/wpsc/downloadables/24C32-Datasheet.pdf) | I2C | diff --git a/aht20/aht20.go b/aht20/aht20.go new file mode 100644 index 0000000..608f5ef --- /dev/null +++ b/aht20/aht20.go @@ -0,0 +1,108 @@ +package aht20 + +import ( + "time" + + "tinygo.org/x/drivers" +) + +// Device wraps an I2C connection to an AHT20 device. +type Device struct { + bus drivers.I2C + Address uint16 + humidity uint32 + temp uint32 +} + +// New creates a new AHT20 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, + } +} + +// Configure the device +func (d *Device) Configure() { + // Check initialization state + status := d.Status() + if status&0x08 == 1 { + // Device is initialized + return + } + + // Force initialization + d.bus.Tx(d.Address, []byte{CMD_INITIALIZE, 0x08, 0x00}, nil) + time.Sleep(10 * time.Millisecond) +} + +// Reset the device +func (d *Device) Reset() { + d.bus.Tx(d.Address, []byte{CMD_SOFTRESET}, nil) +} + +// Status of the device +func (d *Device) Status() byte { + data := []byte{0} + + d.bus.Tx(d.Address, []byte{CMD_STATUS}, data) + + return data[0] +} + +// Read the temperature and humidity +// +// The actual temperature and humidity are stored +// and can be accessed using `Temp` and `Humidity`. +func (d *Device) Read() error { + d.bus.Tx(d.Address, []byte{CMD_TRIGGER, 0x33, 0x00}, nil) + + data := []byte{0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00} + for retry := 0; retry < 3; retry++ { + time.Sleep(80 * time.Millisecond) + err := d.bus.Tx(d.Address, nil, data) + if err != nil { + return err + } + + // If measurement complete, store values + if data[0]&0x04 != 0 && data[0]&0x80 == 0 { + d.humidity = uint32(data[1])<<12 | uint32(data[2])<<4 | uint32(data[3])>>4 + d.temp = (uint32(data[3])&0xF)<<16 | uint32(data[4])<<8 | uint32(data[5]) + return nil + } + } + + return ErrTimeout +} + +func (d *Device) RawHumidity() uint32 { + return d.humidity +} + +func (d *Device) RawTemp() uint32 { + return d.temp +} + +func (d *Device) RelHumidity() float32 { + return (float32(d.humidity) * 100) / 0x100000 +} + +func (d *Device) DeciRelHumidity() int32 { + return (int32(d.humidity) * 1000) / 0x100000 +} + +// Temperature in degrees celsius +func (d *Device) Celsius() float32 { + return (float32(d.temp*200.0) / 0x100000) - 50 +} + +// Temperature in mutiples of one tenth of a degree celsius +// +// Using this method avoids floating point calculations. +func (d *Device) DeciCelsius() int32 { + return ((int32(d.temp) * 2000) / 0x100000) - 500 +} diff --git a/aht20/aht20_test.go b/aht20/aht20_test.go new file mode 100644 index 0000000..3807ecf --- /dev/null +++ b/aht20/aht20_test.go @@ -0,0 +1,74 @@ +package aht20 + +import ( + "testing" + + qt "github.com/frankban/quicktest" + "tinygo.org/x/drivers/tester" +) + +func TestDefaultI2CAddress(t *testing.T) { + c := qt.New(t) + bus := tester.NewI2CBus(c) + dev := New(bus) + c.Assert(uint8(dev.Address), qt.Equals, uint8(Address)) +} + +func TestInitialization(t *testing.T) { + c := qt.New(t) + bus := tester.NewI2CBus(c) + fdev := tester.NewI2CDeviceCmd(c, Address) + fdev.Commands = defaultCommands() + bus.AddDevice(fdev) + + // Set status to uninitialized to force initialization + fdev.Commands[CMD_STATUS].Response[0] = 0x0C + + dev := New(bus) + dev.Configure() + + // Check initialization command invoked + c.Assert(fdev.Commands[CMD_INITIALIZE].Invocations > 0, qt.Equals, true) +} + +func TestRead(t *testing.T) { + c := qt.New(t) + bus := tester.NewI2CBus(c) + fdev := tester.NewI2CDeviceCmd(c, Address) + fdev.Commands = defaultCommands() + bus.AddDevice(fdev) + + dev := New(bus) + dev.Read() + + // Should be 25deg (250 decidegrees) + c.Assert(dev.DeciCelsius(), qt.Equals, int32(250)) + + // Should be 36.3% (363 decipercent) + c.Assert(dev.DeciRelHumidity(), qt.Equals, int32(363)) +} + +func defaultCommands() map[uint8]*tester.Cmd { + return map[uint8]*tester.Cmd{ + CMD_INITIALIZE: { + Command: []byte{0xBE}, + Mask: []byte{0xFF}, + Response: []byte{}, + }, + CMD_TRIGGER: { + Command: []byte{0xAC, 0x33, 0x00}, + Mask: []byte{0xFF, 0xFF, 0xFF}, + Response: []byte{0x1C, 0x5D, 0x10, 0x66, 0x01, 0xD2, 0x93}, + }, + CMD_SOFTRESET: { + Command: []byte{0xBA}, + Mask: []byte{0xFF}, + Response: []byte{}, + }, + CMD_STATUS: { + Command: []byte{0x71}, + Mask: []byte{0xFF}, + Response: []byte{0x1C}, + }, + } +} diff --git a/aht20/registers.go b/aht20/registers.go new file mode 100644 index 0000000..38ba27e --- /dev/null +++ b/aht20/registers.go @@ -0,0 +1,20 @@ +package aht20 + +import "errors" + +const ( + Address = 0x38 + + CMD_INITIALIZE = 0xBE + CMD_STATUS = 0x71 + CMD_TRIGGER = 0xAC + CMD_SOFTRESET = 0xBA + + STATUS_BUSY = 0x80 + STATUS_CALIBRATED = 0x08 +) + +var ( + ErrBusy = errors.New("device busy") + ErrTimeout = errors.New("timeout") +) diff --git a/examples/aht20/main.go b/examples/aht20/main.go new file mode 100644 index 0000000..e1db3b0 --- /dev/null +++ b/examples/aht20/main.go @@ -0,0 +1,55 @@ +package main + +import ( + "machine" + "time" + + "tinygo.org/x/drivers/aht20" +) + +func main() { + machine.I2C0.Configure(machine.I2CConfig{}) + + dev := aht20.New(machine.I2C0) + dev.Configure() + + dev.Reset() + for { + time.Sleep(500 * time.Millisecond) + + err := dev.Read() + if err != nil { + println("Error", err) + continue + } + + println("temp ", fmtD(dev.DeciCelsius(), 3, 1), "C") + println("humidity", fmtD(dev.DeciRelHumidity(), 3, 1), "%") + } +} + +func fmtD(val int32, i int, f int) string { + result := make([]byte, i+f+1) + neg := false + + if val < 0 { + val = -val + neg = true + } + + for p := len(result) - 1; p >= 0; p-- { + result[p] = byte(int32('0') + (val % 10)) + val = val / 10 + + if p == i+1 && p > 0 { + p-- + result[p] = '.' + } + } + + if neg { + result[0] = '-' + } + + return string(result) +}