diff --git a/dht20/dht20.go b/dht20/dht20.go new file mode 100644 index 000000000..65e76d457 --- /dev/null +++ b/dht20/dht20.go @@ -0,0 +1,144 @@ +// 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 ( + "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 + 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() error { + // Get the status word + d.data[0] = 0x71 + err := d.bus.Tx(d.Address, d.data[:1], d.data[:1]) + if err != nil { + return err + } + + if d.data[0] != 0x18 { + // Initialize registers + 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() error { + // Initialize register 0x1B + d.data[0] = 0x1B + d.data[1] = 0x00 + 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 + 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 + 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. +// 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 + } + + // Check the status word Bit[7] + d.data[0] = 0x71 + 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 + 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]) + + // 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 + 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() + } + 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/