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:
-
Call getDS18Count() once to determine that there are any supported temperature
sensors at all.
-
Iterate over all devices, that is, from index "0" up to "getDeviceCount() - 1".
-
Call getAddress() for each index (this will also check validAddress())
-
and then call validFamily() for the address.
-
If validFamily() returns true, store the address for later temperature readings.
-
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:
-
Call requestTemperature() to request all sensors to do new readings in parallel,
-
then iterate over the stored list of DS18 addresses and
-
call getTempC(address), getTempF(address), or getTemp(address) for each address and
-
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).