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