From 315dbef6949c8ba79bba97f3901b16806e117e85 Mon Sep 17 00:00:00 2001 From: sago35 Date: Sat, 13 Jul 2024 10:12:57 +0900 Subject: [PATCH 1/4] dht20: add I2C driver for DHT20 temperature and humidity sensor --- dht20/dht20.go | 103 +++++++++++++++++++++++++++++++++++++++++ dht20/registers.go | 6 +++ examples/dht20/main.go | 36 ++++++++++++++ smoketest.sh | 1 + 4 files changed, 146 insertions(+) create mode 100644 dht20/dht20.go create mode 100644 dht20/registers.go create mode 100644 examples/dht20/main.go diff --git a/dht20/dht20.go b/dht20/dht20.go new file mode 100644 index 000000000..c692ea748 --- /dev/null +++ b/dht20/dht20.go @@ -0,0 +1,103 @@ +// Package dht20 implements a driver for the DHT20 temperature and humidity sensor. +// +// Datasheet: https://cdn-shop.adafruit.com/product-files/5183/5193_DHT20.pdf + +package dht20 + +import ( + "time" + + "tinygo.org/x/drivers" +) + +// Device wraps an I2C connection to a DHT20 device. +type Device struct { + bus drivers.I2C + Address uint16 + data [8]uint8 + temperature float32 + humidity float32 + prevAccessTime time.Time +} + +// New creates a new DHT20 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: defaultAddress, // Using the address defined in registers.go + } +} + +// Configure sets up the device for communication and initializes the registers if needed. +func (d *Device) Configure() { + // Get the status word + d.data[0] = 0x71 + d.bus.Tx(d.Address, d.data[:1], d.data[:1]) + if d.data[0] != 0x18 { + // Initialize registers + d.initRegisters() + } + // Set the previous access time to the current time + d.prevAccessTime = time.Now() +} + +// initRegisters initializes the registers 0x1B, 0x1C, and 0x1E to 0x00. +func (d *Device) initRegisters() { + // Initialize register 0x1B + d.data[0] = 0x1B + d.data[1] = 0x00 + d.bus.Tx(d.Address, d.data[:2], nil) + + // Initialize register 0x1C + d.data[0] = 0x1C + d.data[1] = 0x00 + d.bus.Tx(d.Address, d.data[:2], nil) + + // Initialize register 0x1E + d.data[0] = 0x1E + d.data[1] = 0x00 + d.bus.Tx(d.Address, d.data[:2], nil) +} + +// Update reads data from the sensor and updates the temperature and humidity values. +func (d *Device) Update(which drivers.Measurement) error { + // Check if 80ms have passed since the last access + if d.prevAccessTime.Add(80 * time.Millisecond).Before(time.Now()) { + // Check the status word Bit[7] + d.data[0] = 0x71 + d.bus.Tx(d.Address, d.data[:1], d.data[:1]) + if (d.data[0] & 0x80) == 0 { + // Read 7 bytes of data from the sensor + d.bus.Tx(d.Address, nil, d.data[:7]) + rawHumidity := uint32(d.data[1])<<12 | uint32(d.data[2])<<4 | uint32(d.data[3])>>4 + rawTemperature := uint32(d.data[3]&0x0F)<<16 | uint32(d.data[4])<<8 | uint32(d.data[5]) + + // Convert raw values to human-readable values + d.humidity = float32(rawHumidity) / 1048576.0 * 100 + d.temperature = float32(rawTemperature)/1048576.0*200 - 50 + + // Trigger the next measurement + d.data[0] = 0xAC + d.data[1] = 0x33 + d.data[2] = 0x00 + d.bus.Tx(d.Address, d.data[:3], nil) + + // Update the previous access time to the current time + d.prevAccessTime = time.Now() + } + } + return nil +} + +// Temperature returns the last measured temperature. +func (d *Device) Temperature() float32 { + return d.temperature +} + +// Humidity returns the last measured humidity. +func (d *Device) Humidity() float32 { + return d.humidity +} diff --git a/dht20/registers.go b/dht20/registers.go new file mode 100644 index 000000000..c6e011b4a --- /dev/null +++ b/dht20/registers.go @@ -0,0 +1,6 @@ +package dht20 + +// Constants/addresses used for I2C. + +// The I2C address which this device listens to. +const defaultAddress = 0x38 diff --git a/examples/dht20/main.go b/examples/dht20/main.go new file mode 100644 index 000000000..aa9139aa8 --- /dev/null +++ b/examples/dht20/main.go @@ -0,0 +1,36 @@ +package main + +import ( + "machine" + "strconv" + "time" + + "tinygo.org/x/drivers" + "tinygo.org/x/drivers/dht20" +) + +var ( + i2c = machine.I2C0 +) + +func main() { + i2c.Configure(machine.I2CConfig{}) + sensor := dht20.New(i2c) + sensor.Configure() + + // Trigger the first measurement + sensor.Update(drivers.AllMeasurements) + + for { + time.Sleep(1 * time.Second) + + // Update sensor dasta + sensor.Update(drivers.AllMeasurements) + temp := sensor.Temperature() + hum := sensor.Humidity() + + // Note: The sensor values are from the previous measurement (1 second ago) + println("Temperature:", strconv.FormatFloat(float64(temp), 'f', 2, 64), "°C") + println("Humidity:", strconv.FormatFloat(float64(hum), 'f', 2, 64), "%") + } +} diff --git a/smoketest.sh b/smoketest.sh index 1237d8501..826e959cf 100755 --- a/smoketest.sh +++ b/smoketest.sh @@ -99,6 +99,7 @@ tinygo build -size short -o ./build/test.hex -target=hifive1b ./examples/ssd1351 tinygo build -size short -o ./build/test.hex -target=circuitplay-express ./examples/lis2mdl/main.go tinygo build -size short -o ./build/test.hex -target=arduino-nano33 ./examples/max72xx/main.go tinygo build -size short -o ./build/test.hex -target=feather-m0 ./examples/dht/main.go +tinygo build -size short -o ./build/test.hex -target=feather-m0 ./examples/dht20/main.go # tinygo build -size short -o ./build/test.hex -target=arduino ./examples/keypad4x4/main.go tinygo build -size short -o ./build/test.hex -target=feather-rp2040 ./examples/pcf8523/ tinygo build -size short -o ./build/test.hex -target=xiao ./examples/pcf8563/alarm/ From 19c9fe6db379bd0a4e2ba34ddc957f93abd7385c Mon Sep 17 00:00:00 2001 From: sago35 Date: Sun, 14 Jul 2024 13:37:13 +0900 Subject: [PATCH 2/4] Add error handling --- dht20/dht20.go | 55 +++++++++++++++++++++++++++++--------------------- 1 file changed, 32 insertions(+), 23 deletions(-) diff --git a/dht20/dht20.go b/dht20/dht20.go index c692ea748..a34c0b919 100644 --- a/dht20/dht20.go +++ b/dht20/dht20.go @@ -5,11 +5,16 @@ package dht20 import ( + "errors" "time" "tinygo.org/x/drivers" ) +var ( + errUpdateCalledTooSoon = errors.New("Update() called within 80ms is invalid") +) + // Device wraps an I2C connection to a DHT20 device. type Device struct { bus drivers.I2C @@ -63,31 +68,35 @@ func (d *Device) initRegisters() { } // Update reads data from the sensor and updates the temperature and humidity values. +// Note that the values obtained by this function are from the previous call to Update. +// If you want to use the most recent values, shorten the interval at which Update is called. func (d *Device) Update(which drivers.Measurement) error { // Check if 80ms have passed since the last access - if d.prevAccessTime.Add(80 * time.Millisecond).Before(time.Now()) { - // Check the status word Bit[7] - d.data[0] = 0x71 - d.bus.Tx(d.Address, d.data[:1], d.data[:1]) - if (d.data[0] & 0x80) == 0 { - // Read 7 bytes of data from the sensor - d.bus.Tx(d.Address, nil, d.data[:7]) - rawHumidity := uint32(d.data[1])<<12 | uint32(d.data[2])<<4 | uint32(d.data[3])>>4 - rawTemperature := uint32(d.data[3]&0x0F)<<16 | uint32(d.data[4])<<8 | uint32(d.data[5]) - - // Convert raw values to human-readable values - d.humidity = float32(rawHumidity) / 1048576.0 * 100 - d.temperature = float32(rawTemperature)/1048576.0*200 - 50 - - // Trigger the next measurement - d.data[0] = 0xAC - d.data[1] = 0x33 - d.data[2] = 0x00 - d.bus.Tx(d.Address, d.data[:3], nil) - - // Update the previous access time to the current time - d.prevAccessTime = time.Now() - } + if time.Since(d.prevAccessTime) < 80*time.Millisecond { + return errUpdateCalledTooSoon + } + + // Check the status word Bit[7] + d.data[0] = 0x71 + d.bus.Tx(d.Address, d.data[:1], d.data[:1]) + if (d.data[0] & 0x80) == 0 { + // Read 7 bytes of data from the sensor + d.bus.Tx(d.Address, nil, d.data[:7]) + rawHumidity := uint32(d.data[1])<<12 | uint32(d.data[2])<<4 | uint32(d.data[3])>>4 + rawTemperature := uint32(d.data[3]&0x0F)<<16 | uint32(d.data[4])<<8 | uint32(d.data[5]) + + // Convert raw values to human-readable values + d.humidity = float32(rawHumidity) / 1048576.0 * 100 + d.temperature = float32(rawTemperature)/1048576.0*200 - 50 + + // Trigger the next measurement + d.data[0] = 0xAC + d.data[1] = 0x33 + d.data[2] = 0x00 + d.bus.Tx(d.Address, d.data[:3], nil) + + // Update the previous access time to the current time + d.prevAccessTime = time.Now() } return nil } From 3f4a9d82f7bf37d59a982aeb7f0cf6e56a1b73b9 Mon Sep 17 00:00:00 2001 From: sago35 Date: Tue, 16 Jul 2024 09:00:34 +0900 Subject: [PATCH 3/4] Add check for which --- dht20/dht20.go | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/dht20/dht20.go b/dht20/dht20.go index a34c0b919..769be7382 100644 --- a/dht20/dht20.go +++ b/dht20/dht20.go @@ -71,6 +71,10 @@ func (d *Device) initRegisters() { // Note that the values obtained by this function are from the previous call to Update. // If you want to use the most recent values, shorten the interval at which Update is called. func (d *Device) Update(which drivers.Measurement) error { + if which&drivers.Temperature == 0 && which&drivers.Humidity == 0 { + return nil + } + // Check if 80ms have passed since the last access if time.Since(d.prevAccessTime) < 80*time.Millisecond { return errUpdateCalledTooSoon From 3c1aaa9a9f2f54c915df3c8e435d6e72d99402da Mon Sep 17 00:00:00 2001 From: sago35 Date: Thu, 18 Jul 2024 08:35:57 +0900 Subject: [PATCH 4/4] Add error check for d.bus.Tx() --- dht20/dht20.go | 48 ++++++++++++++++++++++++++++++++++++++---------- 1 file changed, 38 insertions(+), 10 deletions(-) diff --git a/dht20/dht20.go b/dht20/dht20.go index 769be7382..65e76d457 100644 --- a/dht20/dht20.go +++ b/dht20/dht20.go @@ -37,34 +37,53 @@ func New(bus drivers.I2C) Device { } // Configure sets up the device for communication and initializes the registers if needed. -func (d *Device) Configure() { +func (d *Device) Configure() error { // Get the status word d.data[0] = 0x71 - d.bus.Tx(d.Address, d.data[:1], d.data[:1]) + err := d.bus.Tx(d.Address, d.data[:1], d.data[:1]) + if err != nil { + return err + } + if d.data[0] != 0x18 { // Initialize registers - d.initRegisters() + err := d.initRegisters() + if err != nil { + return err + } } // Set the previous access time to the current time d.prevAccessTime = time.Now() + return nil } // initRegisters initializes the registers 0x1B, 0x1C, and 0x1E to 0x00. -func (d *Device) initRegisters() { +func (d *Device) initRegisters() error { // Initialize register 0x1B d.data[0] = 0x1B d.data[1] = 0x00 - d.bus.Tx(d.Address, d.data[:2], nil) + err := d.bus.Tx(d.Address, d.data[:2], nil) + if err != nil { + return err + } // Initialize register 0x1C d.data[0] = 0x1C d.data[1] = 0x00 - d.bus.Tx(d.Address, d.data[:2], nil) + err = d.bus.Tx(d.Address, d.data[:2], nil) + if err != nil { + return err + } // Initialize register 0x1E d.data[0] = 0x1E d.data[1] = 0x00 - d.bus.Tx(d.Address, d.data[:2], nil) + err = d.bus.Tx(d.Address, d.data[:2], nil) + if err != nil { + return err + } + + return nil } // Update reads data from the sensor and updates the temperature and humidity values. @@ -82,10 +101,16 @@ func (d *Device) Update(which drivers.Measurement) error { // Check the status word Bit[7] d.data[0] = 0x71 - d.bus.Tx(d.Address, d.data[:1], d.data[:1]) + err := d.bus.Tx(d.Address, d.data[:1], d.data[:1]) + if err != nil { + return err + } if (d.data[0] & 0x80) == 0 { // Read 7 bytes of data from the sensor - d.bus.Tx(d.Address, nil, d.data[:7]) + err := d.bus.Tx(d.Address, nil, d.data[:7]) + if err != nil { + return err + } rawHumidity := uint32(d.data[1])<<12 | uint32(d.data[2])<<4 | uint32(d.data[3])>>4 rawTemperature := uint32(d.data[3]&0x0F)<<16 | uint32(d.data[4])<<8 | uint32(d.data[5]) @@ -97,7 +122,10 @@ func (d *Device) Update(which drivers.Measurement) error { d.data[0] = 0xAC d.data[1] = 0x33 d.data[2] = 0x00 - d.bus.Tx(d.Address, d.data[:3], nil) + err = d.bus.Tx(d.Address, d.data[:3], nil) + if err != nil { + return err + } // Update the previous access time to the current time d.prevAccessTime = time.Now()