|
1 /* |
|
2 * MIT License |
|
3 * |
|
4 * Copyright (c) 2017 David Antliff |
|
5 * |
|
6 * Permission is hereby granted, free of charge, to any person obtaining a copy |
|
7 * of this software and associated documentation files (the "Software"), to deal |
|
8 * in the Software without restriction, including without limitation the rights |
|
9 * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell |
|
10 * copies of the Software, and to permit persons to whom the Software is |
|
11 * furnished to do so, subject to the following conditions: |
|
12 * |
|
13 * The above copyright notice and this permission notice shall be included in all |
|
14 * copies or substantial portions of the Software. |
|
15 * |
|
16 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR |
|
17 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, |
|
18 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE |
|
19 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER |
|
20 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, |
|
21 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE |
|
22 * SOFTWARE. |
|
23 */ |
|
24 |
|
25 /** |
|
26 * @file ds18b20.c |
|
27 * |
|
28 * Resolution is cached in the DS18B20_Info object to avoid querying the hardware |
|
29 * every time a temperature conversion is required. However this can result in the |
|
30 * cached value becoming inconsistent with the hardware value, so care must be taken. |
|
31 * |
|
32 */ |
|
33 |
|
34 #include <stddef.h> |
|
35 #include <string.h> |
|
36 #include <stdlib.h> |
|
37 #include <stdbool.h> |
|
38 #include <stdint.h> |
|
39 #include <math.h> |
|
40 |
|
41 #include "freertos/FreeRTOS.h" |
|
42 #include "freertos/task.h" |
|
43 #include "driver/gpio.h" |
|
44 #include "esp_system.h" |
|
45 #include "esp_log.h" |
|
46 |
|
47 #include "ds18b20.h" |
|
48 #include "owb.h" |
|
49 |
|
50 static const char * TAG = "ds18b20"; |
|
51 static const int T_CONV = 750; // maximum conversion time at 12-bit resolution in milliseconds |
|
52 |
|
53 // Function commands |
|
54 #define DS18B20_FUNCTION_TEMP_CONVERT 0x44 ///< Start temperature conversion |
|
55 #define DS18B20_FUNCTION_SCRATCHPAD_WRITE 0x4E ///< Write scratchpad |
|
56 #define DS18B20_FUNCTION_SCRATCHPAD_READ 0xBE ///< Read scratchpad |
|
57 #define DS18B20_FUNCTION_SCRATCHPAD_COPY 0x48 ///< Copy scratchpad |
|
58 #define DS18B20_FUNCTION_EEPROM_RECALL 0xB8 ///< EEPROM recall |
|
59 #define DS18B20_FUNCTION_POWER_SUPPLY_READ 0xB4 ///< Read powersupply mode |
|
60 |
|
61 /// @cond ignore |
|
62 typedef struct |
|
63 { |
|
64 uint8_t temperature[2]; // [0] is LSB, [1] is MSB |
|
65 uint8_t trigger_high; |
|
66 uint8_t trigger_low; |
|
67 uint8_t configuration; |
|
68 uint8_t reserved[3]; |
|
69 uint8_t crc; |
|
70 } Scratchpad; |
|
71 /// @endcond ignore |
|
72 |
|
73 static void _init(DS18B20_Info * ds18b20_info, const OneWireBus * bus) |
|
74 { |
|
75 if (ds18b20_info != NULL) |
|
76 { |
|
77 ds18b20_info->bus = bus; |
|
78 memset(&ds18b20_info->rom_code, 0, sizeof(ds18b20_info->rom_code)); |
|
79 ds18b20_info->use_crc = false; |
|
80 ds18b20_info->resolution = DS18B20_RESOLUTION_INVALID; |
|
81 ds18b20_info->solo = false; // assume multiple devices unless told otherwise |
|
82 ds18b20_info->init = true; |
|
83 } |
|
84 else |
|
85 { |
|
86 ESP_LOGE(TAG, "ds18b20_info is NULL"); |
|
87 } |
|
88 } |
|
89 |
|
90 static bool _is_init(const DS18B20_Info * ds18b20_info) |
|
91 { |
|
92 bool ok = false; |
|
93 if (ds18b20_info != NULL) |
|
94 { |
|
95 if (ds18b20_info->init) |
|
96 { |
|
97 // OK |
|
98 ok = true; |
|
99 } |
|
100 else |
|
101 { |
|
102 ESP_LOGE(TAG, "ds18b20_info is not initialised"); |
|
103 } |
|
104 } |
|
105 else |
|
106 { |
|
107 ESP_LOGE(TAG, "ds18b20_info is NULL"); |
|
108 } |
|
109 return ok; |
|
110 } |
|
111 |
|
112 static bool _address_device(const DS18B20_Info * ds18b20_info) |
|
113 { |
|
114 bool present = false; |
|
115 if (_is_init(ds18b20_info)) |
|
116 { |
|
117 owb_reset(ds18b20_info->bus, &present); |
|
118 if (present) |
|
119 { |
|
120 if (ds18b20_info->solo) |
|
121 { |
|
122 // if there's only one device on the bus, we can skip |
|
123 // sending the ROM code and instruct it directly |
|
124 owb_write_byte(ds18b20_info->bus, OWB_ROM_SKIP); |
|
125 } |
|
126 else |
|
127 { |
|
128 // if there are multiple devices on the bus, a Match ROM command |
|
129 // must be issued to address a specific slave |
|
130 owb_write_byte(ds18b20_info->bus, OWB_ROM_MATCH); |
|
131 owb_write_rom_code(ds18b20_info->bus, ds18b20_info->rom_code); |
|
132 } |
|
133 } |
|
134 else |
|
135 { |
|
136 ESP_LOGE(TAG, "ds18b20 device not responding"); |
|
137 } |
|
138 } |
|
139 return present; |
|
140 } |
|
141 |
|
142 static bool _check_resolution(DS18B20_RESOLUTION resolution) |
|
143 { |
|
144 return (resolution >= DS18B20_RESOLUTION_9_BIT) && (resolution <= DS18B20_RESOLUTION_12_BIT); |
|
145 } |
|
146 |
|
147 static float _wait_for_conversion(DS18B20_RESOLUTION resolution) |
|
148 { |
|
149 float elapsed_time = 0.0f; |
|
150 if (_check_resolution(resolution)) |
|
151 { |
|
152 int divisor = 1 << (DS18B20_RESOLUTION_12_BIT - resolution); |
|
153 ESP_LOGD(TAG, "divisor %d", divisor); |
|
154 float max_conversion_time = (float)T_CONV / (float)divisor; |
|
155 int ticks = ceil(max_conversion_time / portTICK_PERIOD_MS); |
|
156 ESP_LOGD(TAG, "wait for conversion: %.3f ms, %d ticks", max_conversion_time, ticks); |
|
157 |
|
158 // wait at least this maximum conversion time |
|
159 vTaskDelay(ticks); |
|
160 |
|
161 // TODO: measure elapsed time more accurately |
|
162 elapsed_time = ticks * portTICK_PERIOD_MS; |
|
163 } |
|
164 return elapsed_time; |
|
165 } |
|
166 |
|
167 static float _decode_temp(uint8_t lsb, uint8_t msb, DS18B20_RESOLUTION resolution) |
|
168 { |
|
169 float result = 0.0f; |
|
170 if (_check_resolution(resolution)) |
|
171 { |
|
172 // masks to remove undefined bits from result |
|
173 static const uint8_t lsb_mask[4] = { ~0x03, ~0x02, ~0x01, ~0x00 }; |
|
174 uint8_t lsb_masked = lsb_mask[resolution - DS18B20_RESOLUTION_9_BIT] & lsb; |
|
175 int16_t raw = (msb << 8) | lsb_masked; |
|
176 result = raw / 16.0f; |
|
177 } |
|
178 else |
|
179 { |
|
180 ESP_LOGE(TAG, "Unsupported resolution %d", resolution); |
|
181 } |
|
182 return result; |
|
183 } |
|
184 |
|
185 static size_t _min(size_t x, size_t y) |
|
186 { |
|
187 return x > y ? y : x; |
|
188 } |
|
189 |
|
190 static Scratchpad _read_scratchpad(const DS18B20_Info * ds18b20_info, size_t count) |
|
191 { |
|
192 count = _min(sizeof(Scratchpad), count); // avoid overflow |
|
193 Scratchpad scratchpad = {0}; |
|
194 ESP_LOGD(TAG, "scratchpad read %d bytes: ", count); |
|
195 if (_address_device(ds18b20_info)) |
|
196 { |
|
197 owb_write_byte(ds18b20_info->bus, DS18B20_FUNCTION_SCRATCHPAD_READ); |
|
198 owb_read_bytes(ds18b20_info->bus, (uint8_t *)&scratchpad, count); |
|
199 //esp_log_buffer_hex(TAG, &scratchpad, count); |
|
200 } |
|
201 return scratchpad; |
|
202 } |
|
203 |
|
204 static bool _write_scratchpad(const DS18B20_Info * ds18b20_info, const Scratchpad * scratchpad, bool verify) |
|
205 { |
|
206 bool result = false; |
|
207 // Only bytes 2, 3 and 4 (trigger and configuration) can be written. |
|
208 // All three bytes MUST be written before the next reset to avoid corruption. |
|
209 if (_is_init(ds18b20_info)) |
|
210 { |
|
211 if (_address_device(ds18b20_info)) |
|
212 { |
|
213 owb_write_byte(ds18b20_info->bus, DS18B20_FUNCTION_SCRATCHPAD_WRITE); |
|
214 owb_write_bytes(ds18b20_info->bus, (uint8_t *)&scratchpad->trigger_high, 3); |
|
215 ESP_LOGD(TAG, "scratchpad write 3 bytes:"); |
|
216 //esp_log_buffer_hex(TAG, &scratchpad->trigger_high, 3); |
|
217 result = true; |
|
218 |
|
219 if (verify) |
|
220 { |
|
221 Scratchpad read = _read_scratchpad(ds18b20_info, offsetof(Scratchpad, configuration) + 1); |
|
222 if (memcmp(&scratchpad->trigger_high, &read.trigger_high, 3) != 0) |
|
223 { |
|
224 ESP_LOGE(TAG, "scratchpad verify failed: " |
|
225 "wrote {0x%02x, 0x%02x, 0x%02x}, " |
|
226 "read {0x%02x, 0x%02x, 0x%02x}", |
|
227 scratchpad->trigger_high, scratchpad->trigger_low, scratchpad->configuration, |
|
228 read.trigger_high, read.trigger_low, read.configuration); |
|
229 result = false; |
|
230 } |
|
231 } |
|
232 } |
|
233 } |
|
234 return result; |
|
235 } |
|
236 |
|
237 |
|
238 // Public API |
|
239 |
|
240 DS18B20_Info * ds18b20_malloc(void) |
|
241 { |
|
242 DS18B20_Info * ds18b20_info = malloc(sizeof(*ds18b20_info)); |
|
243 if (ds18b20_info != NULL) |
|
244 { |
|
245 memset(ds18b20_info, 0, sizeof(*ds18b20_info)); |
|
246 ESP_LOGD(TAG, "malloc %p", ds18b20_info); |
|
247 } |
|
248 else |
|
249 { |
|
250 ESP_LOGE(TAG, "malloc failed"); |
|
251 } |
|
252 |
|
253 return ds18b20_info; |
|
254 } |
|
255 |
|
256 void ds18b20_free(DS18B20_Info ** ds18b20_info) |
|
257 { |
|
258 if (ds18b20_info != NULL && (*ds18b20_info != NULL)) |
|
259 { |
|
260 ESP_LOGD(TAG, "free %p", *ds18b20_info); |
|
261 free(*ds18b20_info); |
|
262 *ds18b20_info = NULL; |
|
263 } |
|
264 } |
|
265 |
|
266 void ds18b20_init(DS18B20_Info * ds18b20_info, const OneWireBus * bus, OneWireBus_ROMCode rom_code) |
|
267 { |
|
268 if (ds18b20_info != NULL) |
|
269 { |
|
270 _init(ds18b20_info, bus); |
|
271 ds18b20_info->rom_code = rom_code; |
|
272 |
|
273 // read current resolution from device as it may not be power-on or factory default |
|
274 ds18b20_info->resolution = ds18b20_read_resolution(ds18b20_info); |
|
275 } |
|
276 else |
|
277 { |
|
278 ESP_LOGE(TAG, "ds18b20_info is NULL"); |
|
279 } |
|
280 } |
|
281 |
|
282 void ds18b20_init_solo(DS18B20_Info * ds18b20_info, const OneWireBus * bus) |
|
283 { |
|
284 if (ds18b20_info != NULL) |
|
285 { |
|
286 _init(ds18b20_info, bus); |
|
287 ds18b20_info->solo = true; |
|
288 |
|
289 // read current resolution from device as it may not be power-on or factory default |
|
290 ds18b20_info->resolution = ds18b20_read_resolution(ds18b20_info); |
|
291 } |
|
292 else |
|
293 { |
|
294 ESP_LOGE(TAG, "ds18b20_info is NULL"); |
|
295 } |
|
296 } |
|
297 |
|
298 void ds18b20_use_crc(DS18B20_Info * ds18b20_info, bool use_crc) |
|
299 { |
|
300 if (_is_init(ds18b20_info)) |
|
301 { |
|
302 ds18b20_info->use_crc = use_crc; |
|
303 ESP_LOGD(TAG, "use_crc %d", ds18b20_info->use_crc); |
|
304 } |
|
305 } |
|
306 |
|
307 bool ds18b20_set_resolution(DS18B20_Info * ds18b20_info, DS18B20_RESOLUTION resolution) |
|
308 { |
|
309 bool result = false; |
|
310 if (_is_init(ds18b20_info)) |
|
311 { |
|
312 if (_check_resolution(ds18b20_info->resolution)) |
|
313 { |
|
314 // read scratchpad up to and including configuration register |
|
315 Scratchpad scratchpad = _read_scratchpad(ds18b20_info, |
|
316 offsetof(Scratchpad, configuration) - offsetof(Scratchpad, temperature) + 1); |
|
317 |
|
318 // modify configuration register to set resolution |
|
319 uint8_t value = (((resolution - 1) & 0x03) << 5) | 0x1f; |
|
320 scratchpad.configuration = value; |
|
321 ESP_LOGD(TAG, "configuration value 0x%02x", value); |
|
322 |
|
323 // write bytes 2, 3 and 4 of scratchpad |
|
324 result = _write_scratchpad(ds18b20_info, &scratchpad, /* verify */ true); |
|
325 if (result) |
|
326 { |
|
327 ds18b20_info->resolution = resolution; |
|
328 ESP_LOGD(TAG, "Resolution set to %d bits", (int)resolution); |
|
329 } |
|
330 else |
|
331 { |
|
332 // Resolution change failed - update the info resolution with the value read from configuration |
|
333 ds18b20_info->resolution = ds18b20_read_resolution(ds18b20_info); |
|
334 ESP_LOGW(TAG, "Resolution consistency lost - refreshed from device: %d", ds18b20_info->resolution); |
|
335 } |
|
336 } |
|
337 else |
|
338 { |
|
339 ESP_LOGE(TAG, "Unsupported resolution %d", resolution); |
|
340 } |
|
341 } |
|
342 return result; |
|
343 } |
|
344 |
|
345 DS18B20_RESOLUTION ds18b20_read_resolution(DS18B20_Info * ds18b20_info) |
|
346 { |
|
347 DS18B20_RESOLUTION resolution = DS18B20_RESOLUTION_INVALID; |
|
348 if (_is_init(ds18b20_info)) |
|
349 { |
|
350 // read scratchpad up to and including configuration register |
|
351 Scratchpad scratchpad = _read_scratchpad(ds18b20_info, |
|
352 offsetof(Scratchpad, configuration) - offsetof(Scratchpad, temperature) + 1); |
|
353 |
|
354 resolution = ((scratchpad.configuration >> 5) & 0x03) + DS18B20_RESOLUTION_9_BIT; |
|
355 if (!_check_resolution(resolution)) |
|
356 { |
|
357 ESP_LOGE(TAG, "invalid resolution read from device: 0x%02x", scratchpad.configuration); |
|
358 resolution = DS18B20_RESOLUTION_INVALID; |
|
359 } |
|
360 else |
|
361 { |
|
362 ESP_LOGD(TAG, "Resolution read as %d", resolution); |
|
363 } |
|
364 } |
|
365 return resolution; |
|
366 } |
|
367 |
|
368 bool ds18b20_convert(const DS18B20_Info * ds18b20_info) |
|
369 { |
|
370 bool result = false; |
|
371 if (_is_init(ds18b20_info)) |
|
372 { |
|
373 const OneWireBus * bus = ds18b20_info->bus; |
|
374 if (_address_device(ds18b20_info)) |
|
375 { |
|
376 // initiate a temperature measurement |
|
377 owb_write_byte(bus, DS18B20_FUNCTION_TEMP_CONVERT); |
|
378 result = true; |
|
379 } |
|
380 else |
|
381 { |
|
382 ESP_LOGE(TAG, "ds18b20 device not responding"); |
|
383 } |
|
384 } |
|
385 return result; |
|
386 } |
|
387 |
|
388 void ds18b20_convert_all(const OneWireBus * bus) |
|
389 { |
|
390 bool is_present = false; |
|
391 owb_reset(bus, &is_present); |
|
392 owb_write_byte(bus, OWB_ROM_SKIP); |
|
393 owb_write_byte(bus, DS18B20_FUNCTION_TEMP_CONVERT); |
|
394 } |
|
395 |
|
396 float ds18b20_wait_for_conversion(const DS18B20_Info * ds18b20_info) |
|
397 { |
|
398 float elapsed_time = 0.0f; |
|
399 if (_is_init(ds18b20_info)) |
|
400 { |
|
401 elapsed_time = _wait_for_conversion(ds18b20_info->resolution); |
|
402 } |
|
403 return elapsed_time; |
|
404 } |
|
405 |
|
406 DS18B20_ERROR ds18b20_read_temp(const DS18B20_Info * ds18b20_info, float * value) |
|
407 { |
|
408 DS18B20_ERROR err = DS18B20_ERROR_UNKNOWN; |
|
409 if (_is_init(ds18b20_info)) |
|
410 { |
|
411 const OneWireBus * bus = ds18b20_info->bus; |
|
412 if (_address_device(ds18b20_info)) |
|
413 { |
|
414 // read measurement |
|
415 if (owb_write_byte(bus, DS18B20_FUNCTION_SCRATCHPAD_READ) == OWB_STATUS_OK) |
|
416 { |
|
417 err = DS18B20_OK; |
|
418 |
|
419 uint8_t temp_LSB = 0; |
|
420 uint8_t temp_MSB = 0; |
|
421 if (!ds18b20_info->use_crc) |
|
422 { |
|
423 // Without CRC: |
|
424 owb_read_byte(bus, &temp_LSB); |
|
425 owb_read_byte(bus, &temp_MSB); |
|
426 bool is_present = false; |
|
427 owb_reset(bus, &is_present); // terminate early |
|
428 } |
|
429 else |
|
430 { |
|
431 // with CRC: |
|
432 uint8_t buffer[9] = {0}; |
|
433 owb_read_bytes(bus, buffer, 9); |
|
434 |
|
435 temp_LSB = buffer[0]; |
|
436 temp_MSB = buffer[1]; |
|
437 |
|
438 if (owb_crc8_bytes(0, buffer, 9) != 0) |
|
439 { |
|
440 ESP_LOGE(TAG, "CRC failed"); |
|
441 temp_LSB = 0x00; |
|
442 temp_MSB = 0x80; |
|
443 err = DS18B20_ERROR_CRC; |
|
444 } |
|
445 } |
|
446 |
|
447 if (err == DS18B20_OK) |
|
448 { |
|
449 float temp = _decode_temp(temp_LSB, temp_MSB, ds18b20_info->resolution); |
|
450 ESP_LOGD(TAG, "temp_LSB 0x%02x, temp_MSB 0x%02x, temp %f", temp_LSB, temp_MSB, temp); |
|
451 |
|
452 if (value) |
|
453 { |
|
454 *value = temp; |
|
455 } |
|
456 } |
|
457 } |
|
458 else |
|
459 { |
|
460 ESP_LOGE(TAG, "owb_write_byte failed"); |
|
461 err = DS18B20_ERROR_OWB; |
|
462 } |
|
463 } |
|
464 else |
|
465 { |
|
466 ESP_LOGE(TAG, "ds18b20 device not responding"); |
|
467 err = DS18B20_ERROR_DEVICE; |
|
468 } |
|
469 } |
|
470 return err; |
|
471 } |
|
472 |
|
473 DS18B20_ERROR ds18b20_convert_and_read_temp(const DS18B20_Info * ds18b20_info, float * value) |
|
474 { |
|
475 DS18B20_ERROR err = DS18B20_ERROR_UNKNOWN; |
|
476 if (_is_init(ds18b20_info)) |
|
477 { |
|
478 if (ds18b20_convert(ds18b20_info)) |
|
479 { |
|
480 // wait at least maximum conversion time |
|
481 _wait_for_conversion(ds18b20_info->resolution); |
|
482 if (value) |
|
483 { |
|
484 *value = 0.0f; |
|
485 err = ds18b20_read_temp(ds18b20_info, value); |
|
486 } |
|
487 else |
|
488 { |
|
489 err = DS18B20_ERROR_NULL; |
|
490 } |
|
491 } |
|
492 } |
|
493 return err; |
|
494 } |
|
495 |
|
496 |