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  }