mirror of https://github.com/periph/devices
add dht22 support
parent
55d75a38ed
commit
b86c344570
@ -0,0 +1,161 @@
|
||||
// Package dht22 reads temperature and humidity from DHT22/AM2302 sensors.
|
||||
//
|
||||
// Datasheet: https://www.adafruit.com/datasheets/DHT22.pdf
|
||||
package dht22
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"periph.io/x/conn/v3"
|
||||
"periph.io/x/conn/v3/gpio"
|
||||
"periph.io/x/conn/v3/physic"
|
||||
"periph.io/x/conn/v3/pin"
|
||||
)
|
||||
|
||||
// Dev represents a DHT22/AM2302 sensor.
|
||||
type Dev struct {
|
||||
pin gpio.PinIO
|
||||
mu sync.Mutex
|
||||
}
|
||||
|
||||
// New opens a connection to a DHT22 sensor on the given GPIO pin.
|
||||
func New(p gpio.PinIO) (*Dev, error) {
|
||||
if err := p.In(gpio.PullUp, gpio.FallingEdge); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &Dev{pin: p}, nil
|
||||
}
|
||||
|
||||
// SenseContinuous returns a channel that will receive measurements at the
|
||||
// specified interval. The channel is closed when stop is closed.
|
||||
func (d *Dev) SenseContinuous(interval time.Duration, stop <-chan struct{}) (<-chan physic.Env, error) {
|
||||
ch := make(chan physic.Env)
|
||||
go func() {
|
||||
defer close(ch)
|
||||
ticker := time.NewTicker(interval)
|
||||
defer ticker.Stop()
|
||||
for {
|
||||
select {
|
||||
case <-stop:
|
||||
return
|
||||
case <-ticker.C:
|
||||
if env, err := d.Read(); err == nil {
|
||||
select {
|
||||
case ch <- env:
|
||||
case <-stop:
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}()
|
||||
return ch, nil
|
||||
}
|
||||
|
||||
// Read reads temperature and humidity from the sensor.
|
||||
// Reading more frequently than once per 2 seconds may cause errors.
|
||||
func (d *Dev) Read() (physic.Env, error) {
|
||||
d.mu.Lock()
|
||||
defer d.mu.Unlock()
|
||||
|
||||
// Prepare pin for output
|
||||
if err := d.pin.Out(gpio.Low); err != nil {
|
||||
return physic.Env{}, err
|
||||
}
|
||||
|
||||
// Send start signal: pull low for 1-10ms, then pull high for 20-40µs
|
||||
d.pin.Out(gpio.Low)
|
||||
time.Sleep(2 * time.Millisecond)
|
||||
d.pin.Out(gpio.High)
|
||||
time.Sleep(40 * time.Microsecond)
|
||||
|
||||
// Switch to input with pull-up
|
||||
if err := d.pin.In(gpio.PullUp, gpio.NoEdge); err != nil {
|
||||
return physic.Env{}, err
|
||||
}
|
||||
|
||||
// Wait for sensor response
|
||||
if !waitForState(d.pin, false, 80*time.Microsecond) {
|
||||
return physic.Env{}, errors.New("dht22: no response from sensor")
|
||||
}
|
||||
if !waitForState(d.pin, true, 80*time.Microsecond) {
|
||||
return physic.Env{}, errors.New("dht22: response timeout")
|
||||
}
|
||||
|
||||
// Read 40 bits of data (humidity, temperature, checksum)
|
||||
var data [5]byte
|
||||
for i := 0; i < 40; i++ {
|
||||
if !waitForState(d.pin, false, 50*time.Microsecond) {
|
||||
return physic.Env{}, errors.New("dht22: data sync error")
|
||||
}
|
||||
|
||||
// High pulse length determines bit value
|
||||
start := time.Now()
|
||||
waitForState(d.pin, true, 70*time.Microsecond)
|
||||
duration := time.Since(start)
|
||||
|
||||
bit := byte(i / 8)
|
||||
shift := 7 - (i % 8)
|
||||
if duration > 50*time.Microsecond {
|
||||
data[bit] |= 1 << shift
|
||||
}
|
||||
}
|
||||
|
||||
// Verify checksum
|
||||
checksum := data[0] + data[1] + data[2] + data[3]
|
||||
if data[4] != checksum {
|
||||
return physic.Env{}, errors.New("dht22: checksum error")
|
||||
}
|
||||
|
||||
// Parse data (big-endian)
|
||||
humidityInt := uint16(data[0])<<8 | uint16(data[1])
|
||||
tempRaw := int16(uint16(data[2])<<8 | uint16(data[3]))
|
||||
|
||||
// Convert to physic units
|
||||
// humidity is 0.1% RH units, so divide by 10 to get percent
|
||||
humidity := physic.RelativeHumidity(float64(humidityInt)/10.0) * physic.PercentRH
|
||||
|
||||
// temperature is 0.1°C units, with bit 15 for sign
|
||||
// Convert to nano Kelvin (base unit for physic.Temperature)
|
||||
// 1°C = 1K in delta, but physic uses nanoKelvin
|
||||
temperature := physic.Temperature(float64(tempRaw)/10.0) * physic.Celsius
|
||||
|
||||
env := physic.Env{
|
||||
Humidity: humidity,
|
||||
Temperature: temperature,
|
||||
}
|
||||
|
||||
return env, nil
|
||||
}
|
||||
|
||||
// String implements conn.Resource.
|
||||
func (d *Dev) String() string {
|
||||
return "DHT22{" + d.pin.String() + "}"
|
||||
}
|
||||
|
||||
// Halt implements conn.Resource.
|
||||
func (d *Dev) Halt() error {
|
||||
return d.pin.Halt()
|
||||
}
|
||||
|
||||
// Pin returns the GPIO pin used by the sensor.
|
||||
func (d *Dev) Pin() pin.Pin {
|
||||
return d.pin
|
||||
}
|
||||
|
||||
// waitForState waits for pin to reach desired state within timeout.
|
||||
func waitForState(p gpio.PinIO, state bool, timeout time.Duration) bool {
|
||||
deadline := time.Now().Add(timeout)
|
||||
for time.Now().Before(deadline) {
|
||||
if p.Read() == gpio.Level(state) {
|
||||
return true
|
||||
}
|
||||
time.Sleep(1 * time.Microsecond)
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
// Ensure Dev implements conn.Resource.
|
||||
var _ conn.Resource = &Dev{}
|
||||
Loading…
Reference in New Issue