mgr's weblog

Archives for March 2021

uLisp on M5Stack (ESP32):
temperature sensors via one wire

March 27, 2021, Lisp
Last edited on July 26, 2024

I added support for Dallas temperature sensors to ulisp-esp-m5stack. Activate #define enable_dallastemp in order to use it. It bases on the Arduino libraries OneWire.h DallasTemperature.h.

I used pin 16 to connect my sensors but you can change ONE_WIRE_BUS to use a different pin. As the OneWire library uses simple bit bagging and no hardware support, e. g. UART, any general-purpose input/output (GPIO) pin will work.

The interface consists of four uLisp functions: INIT-TEMP, GET-TEMP, SET-TEMP-RESOLUTION, and GET-TEMP-DEVICES-COUNT. Here is their documentation:

Function init-temp
 
Syntax:
   init-temp
     => result-list

Arguments and values:
   result-list---a list of device addresses; each address being a list of 8 integer values specifying a device address.

Description:
   Detects all supported temperature sensors connected via one wire bus to the pin ONE_WIRE_BUS and returns the list of the sensors' device addresses.

   All sensors are configured to use the resolution specified by default DEFAULT_TEMPERATURE_PRECISION via a broadcast. Note that a sensor might choose a different resolution if the desired resolution is not supported. See also: set-temp-resolution.

Function get-temp
 
Syntax:
   get-temp address
     => temperature

Arguments and values:
   address---a list of 8 integer values specifying a device address.

   temperature---an integer value; the measured temperature in Celsius.

Description:
   Requests the sensor specified by address to measure and compute a new temperature reading, retrieves the value from the sensor device and returns the temperature in Celsius.

Function set-temp-resolution
 
Syntax:
   set-temp-resolution address [resolution]
     => actual-resolution

Arguments and values:
   address---a list of 8 integer values specifying a device address.

   resolution---an integer value.

   actual-resolution---an integer value.

Description:
   Tries to configure the sensor specified by address to use the given resolution and returns the actual resolution that the devices is set to after the attempt.

   Note that a sensor might choose a different resolution if the desired resolution is not supported. In this case, the returned actual-resolution differs from the argument resolution.

If the argument resolution is missing, instead the default given by DEFAULT_TEMPERATURE_PRECISION is used.

Function get-temp-devices-count
 
Syntax:
   get-temp-devices-count
     => count

Arguments and values:
   count---an integer value; the number of detected supported temperature sensors.

Description:
   Returns the number of temperature sensors supported by this interface that were detected by the last call to INIT-TEMP. Note that this might not be the correct current count if sensors were removed or added since the last call to INIT-TEMP.

Findings from reading DallasTemperature.h and DallasTemperature.cpp

These are the notes I wrote down when reading the source code of the Dallas temperature sensor library and my conclusion how to best use it which lead to my implementation for uLisp.

1. The process of counting the number of devices is efficiently done in parallel by a binary tree algorithm.

2. The result of the search is the number of devices with their addresses.

3. The DallasTemperature library keeps only a count of devices and a count of supported temperature sensors (ds18Count) in memory, not an indexed list of addresses. This is done in DallasTemperature::begin() by doing a search but only the counts are kept, no addresses are stored. Sadly, it also does not return anything.

4. getAddress() does a search again to determine the address for an device index. So it is faster to just get a sensor reading by using the address not the index, it safes one search.

5. Sadly, there is not command to get a list of addresses in a row. So at least once you have to do getAddress() to actually get the addresses of all devices.

5. requestTemperature() can be applied to a single device only or to all devices in parallel. It is as fast to request a temperature from all devices as only one device.

6. Actually getting the temperature reading works only one at a time. getTemp*(deviceAddress) is faster than getTemp*ByIndex(index) as the latter has to do a search first (see 4.).

7. There are these temperature resolutions: 9, 10, 11, and 12 bits. The conversion (=reading) times are:
9 bit – 94 ms
10 bit – 188 ms
11 bit – 375 ms
12 bit – 750 ms

8. setResolution() can either set all devices in parallel or only set one device at a time (only by address, there is no setResultionByIndex()).

9. The temperatures are internally stored in 1/128 degree steps. This is the "raw" readings returned by DallasTemperature::getTemp() as int16_t.

DallasTemperature::getTempC returns "(float) raw * 0.0078125f" and
DallasTemperature::getTempF returns "((float) raw * 0.0140625f) + 32.0f".

In case of an error,
getTempC() will return DEVICE_DISCONNECTED_C which is "(float)-127",
getTempF() will return DEVICE_DISCONNECTED_F which is "(float)-196.6", and
getTemp() will return DEVICE_DISCONNECTED_RAW which is "(int16_t)-7040", respectively.

10. If you don't need the actual temperature but just to monitor that the temperature is in a defined range, it is not necessary to read the temperatures at all (which has to happen one sensor at a time). Instead, you can use the alarm signaling.

For that, you can set a high and a low alarm temperature per device and then you can do an alarm search to determine in parallel if there are sensors with alarms. The range can be half open, that is you can also only define high and low alarm temperatures.

DallasTemperature::alarmSearch() returns one device address with an alarm at a time. It is also possible to install an alarm handler and then call DallasTemperature::processAlarms() which will do repeated alarm searches and call the handler for each device with an alarm.

11. isConnected(deviceAddress) can be used to determine if a certain sensor is still available. It will return quickly when it is not but transfer a full sensor reading in case it is still available. The library currently does not support a case where parallel search is used to determine if known devices are still present.

12. The search is deterministic, it seems, so as long as you don't change sensors, the indices stay the same. If you add and remove a sensor, existing sensor might get new indices. So it seems actually not to be safe to use *ByIndex() functions.

13. getDeviceCount() gives you the number of all devices, getDS18Count() the number of all supported DS18 sensors. But no function gives you the list of indices or addresses of all supported DS19 sensors.

validFamily(deviceAddress) lets you check by address if a device is supported. Supported are DS18S20MODEL (also DS1820), DS18B20MODEL (also MAX31820), DS1822MODEL, DS1825MODEL, and DS28EA00MODEL.

getAddress() just checks if the address is valid (using validAddress(deviceAddress)) but not if the device is actually known. As getAddress() already calls validAddress() for you, there should be no need to ever call validAddress() from user code. If you just request a temperature from all devices till getDeviceCount() you'll also send requests to unsupported devices.

In conclusion, this seems to be the best approach to setup all devices:

  1. Call getDS18Count() once to determine that there are any supported temperature sensors at all.
  2. Iterate over all devices, that is, from index "0" up to "getDeviceCount() - 1".
  3. Call getAddress() for each index (this will also check validAddress())
  4. and then call validFamily() for the address.
  5. If validFamily() returns true, store the address for later temperature readings.
  6. This is also a good time to call setResolution() as per default each device is left at its individual default resolution if you have sensors of different kinds. Either call getResolution(newResolution) to set all devices in parallel, or setResolution(address, newResolution) in the loop right after each call to validFamily() to set up individual resolutions.

To read sensor values:

  1. Call requestTemperature() to request all sensors to do new readings in parallel,
  2. then iterate over the stored list of DS18 addresses and
  3. call getTempC(address), getTempF(address), or getTemp(address) for each address and
  4. check for error return values (see Finding 9.).

Note: getTempC() and getTempF() will call getTemp() internally and that one will also use isConnected(). So there should be no need to call isConnected() from user code if you check for the error return values of the functions (see Finding 8.)



This is the last thing I promised to release in my previous post of February 15, 2021. Documentation takes time! But I programmed new features last Thursday so stay tuned.


See also "Curl/Wget for uLisp", time via NTP, lispstring without escaping and more space, flash support, muting of the speaker and backlight control and uLisp on M5Stack (ESP32).

Select a Theme:

Basilique du Sacré-Cœur de Montmartre (Paris) Parc Floral de Paris Castillo de Santa Barbara (Alicante) About the photos

Entries: