Skip to content

Commit 9a7cb1a

Browse files
authored
DHTXX driver (#235)
dhtXX: add new driver for dht thermometer
1 parent d170ec8 commit 9a7cb1a

File tree

9 files changed

+561
-1
lines changed

9 files changed

+561
-1
lines changed

Makefile

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -165,11 +165,13 @@ endif
165165
@md5sum ./build/test.hex
166166
tinygo build -size short -o ./build/test.hex -target=circuitplay-express ./examples/lis2mdl/main.go
167167
@md5sum ./build/test.hex
168+
tinygo build -size short -o ./build/test.hex -target=feather-m0 ./examples/dht/main.go
169+
@md5sum ./build/test.hex
168170

169171
DRIVERS = $(wildcard */)
170172
NOTESTS = build examples flash semihosting pcd8544 shiftregister st7789 microphone mcp3008 gps microbitmatrix \
171173
hcsr04 ssd1331 ws2812 thermistor apa102 easystepper ssd1351 ili9341 wifinina shifter hub75 \
172-
hd44780 buzzer ssd1306 espat l9110x st7735 bmi160 l293x
174+
hd44780 buzzer ssd1306 espat l9110x st7735 bmi160 l293x dht
173175
TESTS = $(filter-out $(addsuffix /%,$(NOTESTS)),$(DRIVERS))
174176

175177
unit-test:

README.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -70,6 +70,7 @@ The following 55 devices are supported.
7070
| [BMP280 temperature/barometer](https://www.bosch-sensortec.com/media/boschsensortec/downloads/datasheets/bst-bmp280-ds001.pdf) | I2C |
7171
| [BMP388 pressure sensor](https://www.bosch-sensortec.com/media/boschsensortec/downloads/datasheets/bst-bmp388-ds001.pdf) | I2C |
7272
| [Buzzer](https://en.wikipedia.org/wiki/Buzzer#Piezoelectric) | GPIO |
73+
| [DHTXX thermometer and humidity sensor](https://cdn-shop.adafruit.com/datasheets/Digital+humidity+and+temperature+sensor+AM2302.pdf) | GPIO |
7374
| [DS1307 real time clock](https://datasheets.maximintegrated.com/en/ds/DS1307.pdf) | I2C |
7475
| [DS3231 real time clock](https://datasheets.maximintegrated.com/en/ds/DS3231.pdf) | I2C |
7576
| [ESP32 as WiFi Coprocessor with Arduino nina-fw](https://github.com/arduino/nina-fw) | SPI |

dht/constants.go

Lines changed: 116 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,116 @@
1+
// Package dht provides a driver for DHTXX family temperature and humidity sensors.
2+
//
3+
// [1] Datasheet DHT11: https://www.mouser.com/datasheet/2/758/DHT11-Technical-Data-Sheet-Translated-Version-1143054.pdf
4+
// [2] Datasheet DHT22: https://cdn-shop.adafruit.com/datasheets/Digital+humidity+and+temperature+sensor+AM2302.pdf
5+
// Adafruit C++ driver: https://github.com/adafruit/DHT-sensor-library
6+
7+
package dht // import "tinygo.org/x/drivers/dht"
8+
9+
import (
10+
"encoding/binary"
11+
"machine"
12+
"time"
13+
)
14+
15+
// enum type for device type
16+
type DeviceType uint8
17+
18+
// DeviceType specific parsing of information received from the sensor
19+
func (d DeviceType) extractData(buf []byte) (temp int16, hum uint16) {
20+
if d == DHT11 {
21+
temp = int16(buf[2])
22+
if buf[3]&0x80 > 0 {
23+
temp = -1 - temp
24+
}
25+
temp *= 10
26+
temp += int16(buf[3] & 0x0f)
27+
hum = 10*uint16(buf[0]) + uint16(buf[1])
28+
} else {
29+
hum = binary.LittleEndian.Uint16(buf[0:2])
30+
temp = int16(buf[3])<<8 + int16(buf[2]&0x7f)
31+
if buf[2]&0x80 > 0 {
32+
temp = -temp
33+
}
34+
}
35+
return
36+
}
37+
38+
// Celsius and Fahrenheit temperature scales
39+
type TemperatureScale uint8
40+
41+
func (t TemperatureScale) convertToFloat(temp int16) float32 {
42+
if t == C {
43+
return float32(temp) / 10
44+
} else {
45+
// Fahrenheit
46+
return float32(temp)*(9.0/50.) + 32.
47+
}
48+
}
49+
50+
// All functions return ErrorCode instance as error. This class can be used for more efficient error processing
51+
type ErrorCode uint8
52+
53+
const (
54+
startTimeout = time.Millisecond * 200
55+
startingLow = time.Millisecond * 20
56+
57+
DHT11 DeviceType = iota
58+
DHT22
59+
60+
C TemperatureScale = iota
61+
F
62+
63+
ChecksumError ErrorCode = iota
64+
NoSignalError
65+
NoDataError
66+
UpdateError
67+
UninitializedDataError
68+
)
69+
70+
// error interface implementation for ErrorCode
71+
func (e ErrorCode) Error() string {
72+
switch e {
73+
case ChecksumError:
74+
// DHT returns ChecksumError if all the data from the sensor was received, but the checksum does not match.
75+
return "checksum mismatch"
76+
case NoSignalError:
77+
// DHT returns NoSignalError if there was no reply from the sensor. Check sensor connection or the correct pin
78+
// sis chosen,
79+
return "no signal"
80+
case NoDataError:
81+
// DHT returns NoDataError if the connection was successfully initialized, but not all 40 bits from
82+
// the sensor is received
83+
return "no data"
84+
case UpdateError:
85+
// DHT returns UpdateError if ReadMeasurements function is called before time specified in UpdatePolicy or
86+
// less than 2 seconds after past measurement
87+
return "cannot update now"
88+
case UninitializedDataError:
89+
// DHT returns UninitializedDataError if user attempts to access data before first measurement
90+
return "no measurements done"
91+
}
92+
// should never be reached
93+
return "unknown error"
94+
}
95+
96+
// Update policy of the DHT device. UpdateTime cannot be shorter than 2 seconds. According to dht specification sensor
97+
// will return undefined data if update requested less than 2 seconds before last usage
98+
type UpdatePolicy struct {
99+
UpdateTime time.Duration
100+
UpdateAutomatically bool
101+
}
102+
103+
var (
104+
// timeout counter equal to number of ticks per 1 millisecond
105+
timeout counter
106+
)
107+
108+
func init() {
109+
timeout = cyclesPerMillisecond()
110+
}
111+
112+
func cyclesPerMillisecond() counter {
113+
freq := machine.CPUFrequency()
114+
freq /= 1000
115+
return counter(freq)
116+
}

dht/highfreq.go

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
// +build mimxrt1062 stm32f405 atsamd51 stm32f103xx k210 stm32f407
2+
3+
package dht // import "tinygo.org/x/drivers/dht"
4+
5+
// This file provides a definition of the counter for boards with frequency higher than 2^8 ticks per millisecond (>64MHz)
6+
type counter uint32

dht/lowfreq.go

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
// +build arduino atmega1284p nrf52840 digispark nrf52 arduino_nano nrf51 atsamd21 fe310 arduino_nano33 circuitplay_express arduino_mega2560
2+
3+
package dht // import "tinygo.org/x/drivers/dht"
4+
5+
// This file provides a definition of the counter for boards with frequency lower than 2^8 ticks per millisecond (<64MHz)
6+
type counter uint16

dht/thermometer.go

Lines changed: 218 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,218 @@
1+
// Package dht provides a driver for DHTXX family temperature and humidity sensors.
2+
//
3+
// [1] Datasheet DHT11: https://www.mouser.com/datasheet/2/758/DHT11-Technical-Data-Sheet-Translated-Version-1143054.pdf
4+
// [2] Datasheet DHT22: https://cdn-shop.adafruit.com/datasheets/Digital+humidity+and+temperature+sensor+AM2302.pdf
5+
// Adafruit C++ driver: https://github.com/adafruit/DHT-sensor-library
6+
7+
package dht // import "tinygo.org/x/drivers/dht"
8+
9+
import (
10+
"machine"
11+
"time"
12+
)
13+
14+
// DummyDevice provides a basic interface for DHT devices.
15+
type DummyDevice interface {
16+
ReadMeasurements() error
17+
Measurements() (temperature int16, humidity uint16, err error)
18+
Temperature() (int16, error)
19+
TemperatureFloat(scale TemperatureScale) (float32, error)
20+
Humidity() (uint16, error)
21+
HumidityFloat() (float32, error)
22+
}
23+
24+
// Basic implementation of the DummyDevice
25+
// This implementation takes measurements from sensor only with ReadMeasurements function
26+
// and does not provide a protection from too frequent calls for measurements.
27+
// Since taking measurements from the sensor is time consuming procedure and blocks interrupts,
28+
// user can avoid any hidden calls to the sensor.
29+
type device struct {
30+
pin machine.Pin
31+
32+
measurements DeviceType
33+
initialized bool
34+
35+
temperature int16
36+
humidity uint16
37+
}
38+
39+
// ReadMeasurements reads data from the sensor.
40+
// According to documentation pin should be always, but the t *device restores pin to the state before call.
41+
func (t *device) ReadMeasurements() error {
42+
// initial waiting
43+
state := powerUp(t.pin)
44+
defer t.pin.Set(state)
45+
err := t.read()
46+
if err == nil {
47+
t.initialized = true
48+
}
49+
return err
50+
}
51+
52+
// Getter for temperature. Temperature method returns temperature as it is sent by device.
53+
// The temperature is measured temperature in Celsius multiplied by 10.
54+
// If no successful measurements for this device was performed, returns UninitializedDataError.
55+
func (t *device) Temperature() (int16, error) {
56+
if !t.initialized {
57+
return 0, UninitializedDataError
58+
}
59+
return t.temperature, nil
60+
}
61+
62+
// Getter for temperature. TemperatureFloat returns temperature in a given scale.
63+
// If no successful measurements for this device was performed, returns UninitializedDataError.
64+
func (t *device) TemperatureFloat(scale TemperatureScale) (float32, error) {
65+
if !t.initialized {
66+
return 0, UninitializedDataError
67+
}
68+
return scale.convertToFloat(t.temperature), nil
69+
}
70+
71+
// Getter for humidity. Humidity returns humidity as it is sent by device.
72+
// The humidity is measured in percentages multiplied by 10.
73+
// If no successful measurements for this device was performed, returns UninitializedDataError.
74+
func (t *device) Humidity() (uint16, error) {
75+
if !t.initialized {
76+
return 0, UninitializedDataError
77+
}
78+
return t.humidity, nil
79+
}
80+
81+
// Getter for humidity. HumidityFloat returns humidity in percentages.
82+
// If no successful measurements for this device was performed, returns UninitializedDataError.
83+
func (t *device) HumidityFloat() (float32, error) {
84+
if !t.initialized {
85+
return 0, UninitializedDataError
86+
}
87+
return float32(t.humidity) / 10., nil
88+
}
89+
90+
// Perform initialization of the communication protocol.
91+
// Device lowers the voltage on pin for startingLow=20ms and starts listening for response
92+
// Section 5.2 in [1]
93+
func initiateCommunication(p machine.Pin) {
94+
// Send low signal to the device
95+
p.Configure(machine.PinConfig{Mode: machine.PinOutput})
96+
p.Low()
97+
time.Sleep(startingLow)
98+
// Set pin to high and wait for reply
99+
p.High()
100+
p.Configure(machine.PinConfig{Mode: machine.PinInput})
101+
}
102+
103+
// Measurements returns both measurements: temperature and humidity as they sent by the device.
104+
// If no successful measurements for this device was performed, returns UninitializedDataError.
105+
func (t *device) Measurements() (temperature int16, humidity uint16, err error) {
106+
if !t.initialized {
107+
return 0, 0, UninitializedDataError
108+
}
109+
temperature = t.temperature
110+
humidity = t.humidity
111+
err = nil
112+
return
113+
}
114+
115+
// Main routine that performs communication with the sensor
116+
func (t *device) read() error {
117+
// initialize loop variables
118+
119+
// buffer for the data sent by the sensor. Sensor sends 40 bits = 5 bytes
120+
bufferData := [5]byte{}
121+
buf := bufferData[:]
122+
123+
// We perform measurements of the signal from the sensor by counting low and high cycles.
124+
// The bit is determined by the relative length of the high signal to low signal.
125+
// For 1, high signal will be longer than low, for 0---low is longer.
126+
// See section 5.3 [1]
127+
signalsData := [80]counter{}
128+
signals := signalsData[:]
129+
130+
// Start communication protocol with sensor
131+
initiateCommunication(t.pin)
132+
// Wait for sensor's response and abort if sensor does not reply
133+
err := waitForDataTransmission(t.pin)
134+
if err != nil {
135+
return err
136+
}
137+
// count low and high cycles for sensor's reply
138+
receiveSignals(t.pin, signals)
139+
140+
// process received signals and store the result in the buffer. Abort if data transmission was interrupted and not
141+
// all 40 bits were received
142+
err = t.extractData(signals[:], buf)
143+
if err != nil {
144+
return err
145+
}
146+
// Compute checksum and compare it to the one in data. Abort if checksum is incorrect
147+
if !isValid(buf[:]) {
148+
return ChecksumError
149+
}
150+
151+
// Extract temperature and humidity data from buffer
152+
t.temperature, t.humidity = t.measurements.extractData(buf)
153+
return nil
154+
}
155+
156+
// receiveSignals counts number of low and high cycles. The execution is time critical, so the function disables
157+
// interrupts
158+
func receiveSignals(pin machine.Pin, result []counter) {
159+
i := uint8(0)
160+
machine.UART1.Interrupt.Disable()
161+
defer machine.UART1.Interrupt.Enable()
162+
for ; i < 40; i++ {
163+
result[i*2] = expectChange(pin, false)
164+
result[i*2+1] = expectChange(pin, true)
165+
}
166+
}
167+
168+
// extractData process signal counters and transforms them into bits.
169+
// if any of the bits were not received (timed-out), returns NoDataError
170+
func (t *device) extractData(signals []counter, buf []uint8) error {
171+
for i := uint8(0); i < 40; i++ {
172+
lowCycle := signals[i*2]
173+
highCycle := signals[i*2+1]
174+
if lowCycle == timeout || highCycle == timeout {
175+
return NoDataError
176+
}
177+
byteN := i >> 3
178+
buf[byteN] <<= 1
179+
if highCycle > lowCycle {
180+
buf[byteN] |= 1
181+
}
182+
}
183+
return nil
184+
}
185+
186+
// waitForDataTransmission waits for reply from the sensor.
187+
// If no reply received, returns NoSignalError.
188+
// For more details, see section 5.2 in [1]
189+
func waitForDataTransmission(p machine.Pin) error {
190+
// wait for thermometer to pull down
191+
if expectChange(p, true) == timeout {
192+
return NoSignalError
193+
}
194+
//wait for thermometer to pull up
195+
if expectChange(p, false) == timeout {
196+
return NoSignalError
197+
}
198+
// wait for thermometer to pull down and start sending the data
199+
if expectChange(p, true) == timeout {
200+
return NoSignalError
201+
}
202+
return nil
203+
}
204+
205+
// Constructor function for a DummyDevice implementation.
206+
// This device provides full control to the user.
207+
// It does not do any hidden measurements calls and does not check
208+
// for 2 seconds delay between measurements.
209+
func NewDummyDevice(pin machine.Pin, deviceType DeviceType) DummyDevice {
210+
pin.High()
211+
return &device{
212+
pin: pin,
213+
measurements: deviceType,
214+
initialized: false,
215+
temperature: 0,
216+
humidity: 0,
217+
}
218+
}

0 commit comments

Comments
 (0)