-
Notifications
You must be signed in to change notification settings - Fork 212
dht20: add I2C driver for DHT20 temperature and humidity sensor #693
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: dev
Are you sure you want to change the base?
Changes from 1 commit
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -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]) | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Why is the returned error ignored here? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Thank you for the good suggestion. I have made the correction. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I don't see related change. Error returned from There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I misread the suggestion. $ errcheck -verbose ./dht20/...
checking tinygo.org/x/drivers/dht20 |
||
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 | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. If the measurement is only triggered here that means the data that was read before is from previous measurement. If the user cod is calling There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. The use of Update() is aligned with the interface in #345. I feel that waiting for 80ms within Update() is problematic. As you mentioned, the current code results in obtaining stale data. We would like to discuss how to proceed in a separate PR or other discussion. For now, I have added a note in the comments. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. The code is marked as "outdated" by github. Is the issue you all were discussing still present? My only gripe with the code is that Update does not actually update the sensor and returns no indication of such. We could have a sentinel error for this case or choose to block until update finishes. Both are not without their pros and cons. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. That said, now come to think of it the sentinel error might be the most idiomatic way to solve this without requiring users to start using goroutines to manage several sensors which have a cooldown period. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. @soypat the issue I noted is that There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. ah, that sounds like it could cause several headaches. I'd document the Sensor interface such that the function returns a nil error only after it starts and ends a new sensor measurment succesfully updating all requested measurements in
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I have added the check for which. |
||
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 | ||
} | ||
Comment on lines
+137
to
+144
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Ah! I'm just noticing this now- you are using floats! This would present a problem on certain devices with no FPU, causing lots of CPU cycles to be spent on processing the data. It was not explicit in the Sensor PR, but I'd expect these methods to return an integer lest the sensor send back a binary floating point representation. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Using integer arithmetic and representing the humidity and temperature as fixed point integers the code could take the following form (have not tested, did some basic arithmetic to get the numbers): 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])
// humidity contains millipercent
d.humidity = int32(rawHumidity / 10)
// temperature contains millicelsius
d.temperature = int32(rawTemperature/5) - 50000 If users need floating point representations (which they do) we could create a type Thermometer interface {
drivers.Sensor
// Temperature returns fixed point representation of temperature in milicelsius [mC].
Temperature() int32
}
func ReadKelvin32(t Thermometer) (float32, error) {
err := t.Update(drivers.Temperature)
if err != nil {
return err
}
v := t.Temperature()
return float32(v)/1000 + 273.15
}
// more advanced ADC configurations...
type sensorConversion struct {
s drivers.Sensor
gets []func() int32
conversions []func(v int32) float32
} There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. See #332 for a previous attempt |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,6 @@ | ||
package dht20 | ||
|
||
// Constants/addresses used for I2C. | ||
|
||
// The I2C address which this device listens to. | ||
const defaultAddress = 0x38 |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -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), "%") | ||
} | ||
} |
Uh oh!
There was an error while loading. Please reload this page.