Branch data Line data Source code
1 : : #include "ds18b20.h"
2 : : #include "onewire.h"
3 : : #include "ow_port.h"
4 : : #include "ow_stats.h"
5 : :
6 : : /**
7 : : * @defgroup DS18B20_Private_Types DS18B20 Private Types
8 : : * @{
9 : : */
10 : :
11 : : /**
12 : : * @defgroup DS18B20_Private_Constants DS18B20 Private Constants
13 : : * @{
14 : : */
15 : :
16 : : /** @brief Total length of DS18B20 scratchpad in bytes */
17 : : #define DS18B20_SCRATCHPAD_LEN 9
18 : : /** @brief Number of bytes to include in the scratchpad CRC calculation */
19 : : #define DS18B20_CRC8_BYTES 8
20 : : /** @brief Total number of bits in DS18B20 scratchpad */
21 : : #define DS18B20_SCRATCHPAD_BITS (DS18B20_SCRATCHPAD_LEN * DS18B20_BITS_PER_BYTE)
22 : : /** @brief Total slots for Match ROM + 8-byte ROM + command */
23 : : #define DS18B20_MATCH_SLOTS ((DS18B20_ROM_BYTES + 2) * DS18B20_BITS_PER_BYTE)
24 : : /** @brief Slots for the invariant Match ROM + 8-byte ROM prefix (built on select) */
25 : : #define DS18B20_PREFIX_SLOTS ((DS18B20_ROM_BYTES + 1) * DS18B20_BITS_PER_BYTE)
26 : : /** @brief Number of DMA transfers for command transmission (2 bytes × 8 bits) */
27 : : #define DS18B20_DMA_TRANSFERS (2 * DS18B20_BITS_PER_BYTE)
28 : : /** @brief Timer configuration for wait and pause (ARR, RCR) — 62500 ticks @ 1µs = 62.5ms per period */
29 : : #ifndef DS18B20_CYCLE_PAUSE_US
30 : : #define DS18B20_CYCLE_PAUSE_US 5000000 /**< default inter-cycle pause: 5s */
31 : : #endif
32 : : #define _OW_PAUSE_US (DS18B20_CYCLE_PAUSE_US > 0 ? DS18B20_CYCLE_PAUSE_US : 1)
33 : : #if _OW_PAUSE_US <= 62500
34 : : #define PAUSE_ARR (_OW_PAUSE_US)
35 : : #define PAUSE_RCR 0
36 : : #else
37 : : #define PAUSE_ARR 62500
38 : : #define PAUSE_RCR ((_OW_PAUSE_US / 62500) - 1)
39 : : #endif
40 : : #define SCAN_DEVICE_GAP 1000, 0 /**< 1ms scheduling bridge between scan-mode device reads (no bus requirement) */
41 : : /** @brief TH byte written together with the config register by the resolution
42 : : * state machine (Write Scratchpad requires TH + TL + CFG in one go).
43 : : * 0 disables the alarm trigger threshold. */
44 : : #define DS18B20_RES_TH 0x00
45 : : /** @brief TL byte written together with the config register by the resolution
46 : : * state machine. 0 disables the alarm trigger threshold. */
47 : : #define DS18B20_RES_TL 0x00
48 : : /** @brief Bytes in the resolution config write for Skip ROM mode
49 : : * (Skip ROM 0xCC + Write Scratchpad 0x4E + TH + TL + CFG) */
50 : : #define DS18B20_RES_BYTES_MIN (1 + 1 + 3)
51 : : /** @brief Bytes in the resolution config write for Match ROM mode
52 : : * (Match ROM 0x55 + 8-byte ROM + 0x4E + TH + TL + CFG) */
53 : : #define DS18B20_RES_BYTES_MAX (1 + DS18B20_ROM_BYTES + 1 + 3)
54 : : /** @brief Slots for the Skip ROM resolution config write */
55 : : #define DS18B20_RES_SLOTS_MIN (DS18B20_RES_BYTES_MIN * DS18B20_BITS_PER_BYTE)
56 : : /** @brief Slots for the Match ROM resolution config write */
57 : : #define DS18B20_RES_SLOTS_MAX (DS18B20_RES_BYTES_MAX * DS18B20_BITS_PER_BYTE)
58 : : /** @brief Wait for Copy Scratchpad (t_COPY) / Recall EEPROM (t_RECALL)
59 : : * completion in microseconds (DS18B20 datasheet: 10ms max). */
60 : : #define DS18B20_EEPROM_WAIT_US 10000
61 : :
62 : : /**
63 : : * @}
64 : : */
65 : :
66 : : /**
67 : : * @defgroup DS18B20_Private_Types DS18B20 Private Types
68 : : * @{
69 : : */
70 : :
71 : : /**
72 : : * @brief DS18B20 driver context structure using union for memory efficiency
73 : : * @note Different stages of communication use the same memory for different purposes
74 : : */
75 : : typedef struct {
76 : : /**
77 : : * @brief Union overlay for memory efficiency
78 : : * @warning CRITICAL INVARIANT: scratchpad[n] aliases pulse[n] (same byte).
79 : : * decode_scratchpad() MUST read all 8 bits of pulse[byte*8..byte*8+7]
80 : : * BEFORE writing scratchpad[byte]. Reordering loops will corrupt bytes 0-8.
81 : : */
82 : : union {
83 : : volatile uint16_t capture[DS18B20_SCRATCHPAD_BITS / 2]; /**< Captured pulse durations (reset/presence) */
84 : : volatile uint8_t pulse[DS18B20_SCRATCHPAD_BITS]; /**< Pulse durations for data decoding */
85 : : uint8_t scratchpad[DS18B20_SCRATCHPAD_LEN]; /**< Sensor scratchpad data */
86 : : uint64_t fill_union; /**< Utility field for filling the union */
87 : : };
88 : : ds18b20_state_t current_state; /**< Current state of the state machine */
89 : : uint8_t address_mode; /**< 0 = Skip ROM (all devices), non-zero = Match ROM */
90 : : uint8_t scan_mode; /**< 1 = simultaneous multi-device conversion (scan) mode */
91 : : uint8_t scan_index; /**< Index of the device currently read in scan mode */
92 : : uint8_t selected_rom[DS18B20_ROM_BYTES]; /**< ROM of the selected device */
93 : : uint8_t addr_cmd[DS18B20_MATCH_SLOTS + 1]; /**< Pulse buffer for Match ROM command (+ trailing 0 for hardware bus release) */
94 : : uint8_t resolution; /**< Conversion resolution in bits (9..12); drives the conversion wait */
95 : : uint8_t parasite; /**< 1 = parasite-powered bus: engage the strong pull-up during conversion and EEPROM programming windows (see ds18b20_set_parasite) */
96 : : } DS18B20_ctx_t;
97 : :
98 : : /**
99 : : * @brief Non-blocking single-command transaction phases
100 : : */
101 : : typedef enum {
102 : : DS18B20_TXN_RESET, /**< reset scheduled; check presence */
103 : : DS18B20_TXN_WRITE, /**< command (prefix + function + payload) write scheduled */
104 : : DS18B20_TXN_READ, /**< data read scheduled (read_bytes != 0) */
105 : : DS18B20_TXN_WAIT, /**< timed wait scheduled (wait_us != 0) */
106 : : DS18B20_TXN_DONE /**< finished; hand the timer back to the measurement */
107 : : } ds18b20_txn_phase_t;
108 : :
109 : : /**
110 : : * @brief Non-blocking single-command transaction context
111 : : * @note Drives every infrequent DS18B20 command (Read ROM, Write Scratchpad
112 : : * thresholds, Copy/Recall EEPROM, Read Power Supply, raw Read
113 : : * Scratchpad) with the same reset -> write -> (read | wait) discipline
114 : : * as the resolution state machine. The pulse buffer must stay valid
115 : : * across poll calls because the DMA feeds CCR3 from it asynchronously.
116 : : */
117 : : typedef struct {
118 : : ds18b20_txn_phase_t phase; /**< Current phase of the transaction */
119 : : uint8_t command; /**< DS18B20 function command byte (0x33/0x4E/0x48/0xB8/0xB4/0xBE) */
120 : : uint8_t* out; /**< User result buffer (valid until the command finishes) */
121 : : uint8_t payload[3]; /**< Write Scratchpad payload (TH, TL, CFG) */
122 : : uint8_t payload_len; /**< 0..3 (payload bytes written after the command) */
123 : : uint8_t read_bytes; /**< Bytes to read back (0 = no read phase) */
124 : : uint16_t wait_us; /**< Timed wait after the command (0 = none) */
125 : : uint8_t bare; /**< 1 = no addressing prefix (Read ROM: single-device bus only) */
126 : : uint8_t slots; /**< Bit slots in the built pulses (incl. prefix and payload) */
127 : : uint8_t pulses[DS18B20_RES_SLOTS_MAX + 1]; /**< Built command (+ trailing 0 for hardware bus release) */
128 : : uint8_t raw[DS18B20_SCRATCHPAD_LEN]; /**< Decoded read result */
129 : : uint8_t ok; /**< 1 once the transaction completed with a device present / valid read */
130 : : uint8_t finished; /**< 1 once the transaction finished (or aborted) */
131 : : } ds18b20_txn_ctx_t;
132 : :
133 : : /**
134 : : * @}
135 : : */
136 : :
137 : : /**
138 : : * @defgroup DS18B20_Private_Variables DS18B20 Private Variables
139 : : * @{
140 : : */
141 : :
142 : : /** @brief Global driver context instance */
143 : : static DS18B20_ctx_t ctx;
144 : :
145 : : /** @brief ROM table of the discovered devices (filled by the device search). */
146 : : static uint8_t dev_roms[DS18B20_MAX_DEVICES][DS18B20_ROM_BYTES];
147 : : /** @brief Number of devices currently stored in dev_roms. */
148 : : static uint8_t dev_count;
149 : :
150 : : /* B1 guard: the 1-Wire layer reads cmd[slots] as the trailing zero-pulse that
151 : : * the final DMA transfer feeds into CCR3 to release the 1-Wire bus. The
152 : : * addr_cmd buffer must therefore hold DS18B20_MATCH_SLOTS + 1 entries, not
153 : : * DS18B20_MATCH_SLOTS, or that last slot reads one byte past the buffer. */
154 : : _Static_assert(sizeof(ctx.addr_cmd) >= DS18B20_MATCH_SLOTS + 1,
155 : : "addr_cmd must be DS18B20_MATCH_SLOTS + 1 to hold the trailing "
156 : : "bus-release pulse consumed by the 1-Wire layer");
157 : :
158 : : /** @brief Global single-command transaction context instance */
159 : : static ds18b20_txn_ctx_t txn_ctx;
160 : :
161 : : /** @brief Receive buffer for the parasite-mode detection answer byte */
162 : : static uint8_t detect_buf;
163 : :
164 : : /* B1 guard: the trailing zero-pulse consumed by the CCR3-feed DMA's final
165 : : * transfer must always be present at the exact slot index used for the write
166 : : * (see txn_build_pulses); the buffer is sized for the longest (Match ROM)
167 : : * command write. */
168 : : _Static_assert(sizeof(txn_ctx.pulses) >= DS18B20_RES_SLOTS_MAX + 1,
169 : : "txn_ctx.pulses must be DS18B20_RES_SLOTS_MAX + 1 to hold the "
170 : : "trailing bus-release pulse consumed by the 1-Wire layer");
171 : :
172 : : /**
173 : : * @defgroup DS18B20_Resolution_Internal DS18B20 Internal Non-Blocking Resolution Change
174 : : * @brief Change the temperature conversion resolution (9..12 bit) with the same
175 : : * non-blocking discipline as the device search: every state performs
176 : : * exactly one hardware-timed operation via the internal bus primitives,
177 : : * so a poll call never blocks. The config write is sent with Write
178 : : * Scratchpad (0x4E) + TH + TL + CFG; it takes effect immediately and is
179 : : * not persisted to the EEPROM (no Copy Scratchpad, which would need a
180 : : * strong pull-up under parasitic power).
181 : : * @{
182 : : */
183 : :
184 : : /** @brief Resolution state machine phases */
185 : : typedef enum {
186 : : DS18B20_RES_RESET, /**< reset scheduled; check presence */
187 : : DS18B20_RES_WRITE, /**< config write scheduled (skip/match + 0x4E + TH + TL + CFG) */
188 : : DS18B20_RES_DONE /**< operation finished; hand the timer back to the measurement */
189 : : } res_phase_t;
190 : :
191 : : /**
192 : : * @brief Non-blocking resolution change context
193 : : * @note The pulse buffer must stay valid across poll calls because the DMA
194 : : * feeds CCR3 from it asynchronously while the config write is sent.
195 : : */
196 : : typedef struct {
197 : : res_phase_t phase; /**< Current phase of the resolution state machine */
198 : : uint8_t pending_res; /**< Resolution (bits) to apply */
199 : : uint8_t applied; /**< 1 once the config write completed (resolution actually changed) */
200 : : uint8_t finished; /**< 1 once the operation has completed (or aborted) */
201 : : uint8_t slots; /**< Bit slots in the built config write (incl. prefix and payload) */
202 : : uint8_t pulses[DS18B20_RES_SLOTS_MAX + 1]; /**< Pulse buffer for the config write (+ trailing 0 for hardware bus release) */
203 : : } res_ctx_t;
204 : :
205 : : /** @brief Global resolution context instance */
206 : : static res_ctx_t res_ctx;
207 : :
208 : : /* B1 guard: the trailing zero-pulse consumed by the CCR3-feed DMA's final
209 : : * transfer must always be present at the exact slot index used for the write
210 : : * (see build_res_pulses); the buffer is sized for the longest (Match ROM) mode. */
211 : : _Static_assert(sizeof(res_ctx.pulses) >= DS18B20_RES_SLOTS_MAX + 1,
212 : : "res_ctx.pulses must be DS18B20_RES_SLOTS_MAX + 1 to hold the "
213 : : "trailing bus-release pulse consumed by the 1-Wire layer");
214 : :
215 : : /**
216 : : * @}
217 : : */
218 : :
219 : : /**
220 : : * @defgroup DS18B20_Private_Functions DS18B20 Private Functions
221 : : * @{
222 : : */
223 : :
224 : : /**
225 : : * @brief Default weak implementation for busy indicator (e.g. LED toggling during measurement)
226 : : * @param[in] action 0 = idle, non-zero = busy
227 : : */
228 : 0 : __WEAK void ds18b20_busy(unsigned action) {
229 : : (void)action;
230 : : // Default implementation - empty (no LED control)
231 : 0 : }
232 : :
233 : : /**
234 : : * @brief Default weak implementation for measurement completion callback
235 : : * @param[in] temp_tenths Temperature value in tenths of degrees Celsius, or error code
236 : : */
237 : 0 : __WEAK void ds18b20_complete(int16_t temp_tenths) {
238 : : (void)temp_tenths;
239 : : // Default implementation - empty (no temperature handling)
240 : 0 : }
241 : :
242 : : /**
243 : : * @brief Calculate Dallas/Maxim CRC-8 over a byte buffer
244 : : * @param[in] data Input buffer
245 : : * @param[in] len Number of bytes to process
246 : : * @return CRC-8 checksum value
247 : : * @note Delegates to the shared 1-Wire layer (same Dallas/Maxim algorithm).
248 : : */
249 : 1038 : uint8_t ds18b20_crc8(const uint8_t* data, uint8_t len) {
250 : 1038 : return onewire_crc8(data, len);
251 : : }
252 : :
253 : : /**
254 : : * @brief Calculate CRC8 checksum for DS18B20 scratchpad data validation
255 : : * @return CRC8 checksum value
256 : : */
257 : : __STATIC_FORCEINLINE uint8_t check_scratchpad_crc(void) {
258 : 36 : return ds18b20_crc8(ctx.scratchpad, DS18B20_CRC8_BYTES);
259 : : }
260 : :
261 : : /**
262 : : * @brief Decode pulse durations into scratchpad bytes using bit timing analysis
263 : : * @note Branchless implementation: accumulates bits into native-width variable,
264 : : * then writes once per byte. Relies on union aliasing invariant — see DS18B20_ctx_t.
265 : : */
266 : : __STATIC_FORCEINLINE void decode_scratchpad(void) {
267 : : /* Captured pulse durations (volatile, written by the read DMA) carry one
268 : : * bit each; onewire_decode_pulses() recovers the scratchpad bytes. */
269 : 240 : onewire_decode_pulses(ctx.scratchpad, ctx.pulse, DS18B20_SCRATCHPAD_LEN);
270 : 240 : }
271 : :
272 : : /**
273 : : * @brief Convert raw temperature data from scratchpad to tenths of degrees Celsius
274 : : * @return Temperature value in tenths of degrees Celsius
275 : : */
276 : : __STATIC_FORCEINLINE int16_t decode_temperature(void) {
277 : : // Combine LSB and MSB of temperature register (bytes 0 and 1)
278 : 192 : int16_t raw = (int16_t)((ctx.scratchpad[1] << 8) | ctx.scratchpad[0]);
279 : : // Convert to tenths of degrees Celsius (raw value in 1/16th degrees):
280 : : // multiply by 10 and divide by 16 with round-half-away-from-zero so the
281 : : // sign is preserved for small negative values (raw = -1 would otherwise
282 : : // truncate to 0 and report +0.0 °C for a temperature below freezing).
283 [ + + ]: 192 : return (int16_t)(((int32_t)raw * 10 + ((raw < 0) ? -8 : 8)) / 16);
284 : : }
285 : :
286 : : /**
287 : : * @brief Map a conversion resolution to its exact DS18B20 conversion time
288 : : * @param[in] res Resolution in bits (9..12)
289 : : * @param[out] arr Auto-reload value (one timer period in µs)
290 : : * @param[out] rcr Repetition counter (number of periods - 1)
291 : : * @note DS18B20 datasheet conversion times: 9-bit 93.75ms, 10-bit 187.5ms,
292 : : * 11-bit 375ms, 12-bit 750ms. The (ARR, RCR) pairs below reproduce
293 : : * exactly those minimum waits at 1µs/tick with the invariant
294 : : * (RCR + 1) × ARR = wait in µs.
295 : : */
296 : : __STATIC_FORCEINLINE void resolution_to_wait(uint8_t res, uint16_t* arr, uint8_t* rcr) {
297 : 171 : switch (res) {
298 : 12 : case 9:
299 : 12 : *arr = 9375;
300 : 12 : *rcr = 9;
301 : 12 : break; /* 10 × 9.375ms = 93.75ms */
302 : 6 : case 10:
303 : 6 : *arr = 18750;
304 : 6 : *rcr = 9;
305 : 6 : break; /* 10 × 18.75ms = 187.5ms */
306 : 6 : case 11:
307 : 6 : *arr = 18750;
308 : 6 : *rcr = 19;
309 : 6 : break; /* 20 × 18.75ms = 375ms */
310 : 147 : case 12:
311 : : default:
312 : 147 : *arr = 62500;
313 : 147 : *rcr = 11;
314 : 147 : break; /* 12 × 62.5ms = 750ms */
315 : : }
316 : 171 : }
317 : :
318 : : /**
319 : : * @brief Wait for temperature conversion to complete
320 : : * @note Non-blocking - starts a timer that generates an update event when the
321 : : * conversion of the currently configured resolution (ctx.resolution)
322 : : * is guaranteed finished: 93.75ms (9 bit) .. 750ms (12 bit).
323 : : */
324 : : __STATIC_FORCEINLINE void wait_conversion(void) {
325 : : uint16_t arr;
326 : : uint8_t rcr;
327 [ + + + + : 171 : resolution_to_wait(ctx.resolution, &arr, &rcr);
+ - - + ]
328 : 171 : onewire_start_timer(arr, rcr);
329 : 171 : }
330 : :
331 : : /**
332 : : * @brief Start inter-measurement pause period (5s)
333 : : * @note Non-blocking - starts timer for inter-measurement delay
334 : : */
335 : 195 : __STATIC_FORCEINLINE void start_cycle_pause(void) { onewire_start_timer(PAUSE_ARR, PAUSE_RCR); }
336 : :
337 : : /**
338 : : * @brief Build the invariant Match ROM prefix (0x55 + selected ROM)
339 : : * @note Fills the first DS18B20_PREFIX_SLOTS entries of ctx.addr_cmd.
340 : : * The prefix depends only on the selected device, so it is built
341 : : * once in ds18b20_select() and reused for every command.
342 : : */
343 : : __STATIC_FORCEINLINE void build_addr_prefix(void) {
344 : 216 : uint8_t* p = ctx.addr_cmd;
345 : 216 : onewire_encode_byte(p, DS18B20_MATCH_ROM);
346 : 216 : p += DS18B20_BITS_PER_BYTE;
347 [ + + + + : 1944 : for (uint8_t i = 0; i < DS18B20_ROM_BYTES; i++) {
+ + ]
348 : 1728 : onewire_encode_byte(p, ctx.selected_rom[i]);
349 : 1728 : p += DS18B20_BITS_PER_BYTE;
350 : : }
351 : : /* B1: guarantee the trailing zero-pulse that the 1-Wire layer reads as its
352 : : * final DMA transfer into CCR3 is present, even though build_addr_cmd()
353 : : * only ever writes slots 0 .. DS18B20_MATCH_SLOTS - 1. Without this, the
354 : : * bus-release pulse would depend on whatever happened to sit at
355 : : * addr_cmd[DS18B20_MATCH_SLOTS] (typically 0 from .bss, but not guaranteed). */
356 : 216 : ctx.addr_cmd[DS18B20_MATCH_SLOTS] = 0;
357 : 216 : }
358 : :
359 : : /**
360 : : * @brief Append one command byte to the pre-built Match ROM prefix
361 : : * @param[in] cmd_byte Command byte to send after the ROM address
362 : : * @note Requires build_addr_prefix() to have been called for the current
363 : : * selected device. Only the last byte (8 slots) is re-encoded per call.
364 : : */
365 : : __STATIC_FORCEINLINE void build_addr_cmd(uint8_t cmd_byte) {
366 : 24 : onewire_encode_byte(&ctx.addr_cmd[DS18B20_PREFIX_SLOTS], cmd_byte);
367 : 114 : }
368 : :
369 : : /**
370 : : * @}
371 : : */
372 : :
373 : : /**
374 : : * @defgroup DS18B20_Search_Internal DS18B20 Device Search (via the 1-Wire layer)
375 : : * @brief Wraps the generic Search ROM (0xF0) / Alarm Search (0xEC) engine of
376 : : * the shared 1-Wire layer. The device search additionally stores every
377 : : * found ROM in the scan-mode device table; the alarm search leaves the
378 : : * table untouched so a previous scan keeps its addresses.
379 : : * @{
380 : : */
381 : :
382 : : /** @brief User sink stored for the duration of a search */
383 : : static ds18b20_search_sink_t search_user_sink;
384 : :
385 : : /**
386 : : * @brief Device-search sink: store the ROM in the scan-mode device table
387 : : * (capped at DS18B20_MAX_DEVICES), then forward to the user sink.
388 : : */
389 : 156 : static uint8_t search_store_sink(const uint8_t* rom) {
390 [ + + ]: 156 : if (dev_count < DS18B20_MAX_DEVICES) {
391 [ + + ]: 1350 : for (uint8_t i = 0; i < DS18B20_ROM_BYTES; i++) {
392 : 1200 : dev_roms[dev_count][i] = rom[i];
393 : : }
394 : 150 : dev_count++;
395 : : }
396 [ + + ]: 156 : return search_user_sink ? search_user_sink(rom) : 0;
397 : : }
398 : :
399 : : /**
400 : : * @brief Alarm-search sink: forward to the user sink without touching the
401 : : * scan-mode device table.
402 : : */
403 : 48 : static uint8_t search_alarm_sink(const uint8_t* rom) {
404 [ + + ]: 48 : return search_user_sink ? search_user_sink(rom) : 0;
405 : : }
406 : :
407 : : /**
408 : : * @brief Start a non-blocking device search
409 : : * @param[in] sink Callback invoked per found DS18B20 device (may be NULL)
410 : : * @param[in] max_devices Maximum number of devices to report (0 aborts)
411 : : * @note The device search (re)populates the scan-mode device table.
412 : : * @note Ownership guards: the search, the measurement state machine, command
413 : : * transactions and resolution changes all share TIM1/DMA, so a new search
414 : : * may only be started while all of them are idle; a running search
415 : : * rejects a new start.
416 : : */
417 : 162 : void ds18b20_search_start(ds18b20_search_sink_t sink, uint8_t max_devices) {
418 [ + + ]: 162 : if (ctx.current_state != DS18B20_ST_IDLE) {
419 : 6 : return; // a measurement cycle is in progress
420 : : }
421 [ + + ]: 156 : if (onewire_search_active()) {
422 : 6 : return; // a search is already running - keep its sink and table
423 : : }
424 [ + + ]: 150 : if (!txn_ctx.finished) {
425 : 6 : return; // a command transaction is running
426 : : }
427 [ + + ]: 144 : if (!res_ctx.finished) {
428 : 6 : return; // a resolution change owns the timer
429 : : }
430 : 138 : dev_count = 0;
431 : 138 : search_user_sink = sink;
432 : 138 : onewire_search_start(search_store_sink, max_devices, DS18B20_SEARCH_ROM, DS18B20_FAMILY_CODE);
433 : : }
434 : :
435 : : /**
436 : : * @brief Start a non-blocking alarm search
437 : : * @param[in] sink Callback invoked per DS18B20 currently in alarm (may be NULL)
438 : : * @param[in] max_devices Maximum number of alarmed devices to report (0 aborts)
439 : : * @note Only devices in alarm state respond to Alarm Search (0xEC). The
440 : : * scan-mode device table is left untouched.
441 : : */
442 : 96 : void ds18b20_alarm_search_start(ds18b20_search_sink_t sink, uint8_t max_devices) {
443 [ + + ]: 96 : if (ctx.current_state != DS18B20_ST_IDLE) {
444 : 6 : return; // a measurement cycle is in progress
445 : : }
446 [ + + ]: 90 : if (onewire_search_active()) {
447 : 6 : return; // a search is already running - keep its sink
448 : : }
449 [ + + ]: 84 : if (!txn_ctx.finished) {
450 : 6 : return; // a command transaction is running
451 : : }
452 [ + + ]: 78 : if (!res_ctx.finished) {
453 : 6 : return; // a resolution change owns the timer
454 : : }
455 : 72 : search_user_sink = sink;
456 : 72 : onewire_search_start(search_alarm_sink, max_devices, DS18B20_ALARM_SEARCH, DS18B20_FAMILY_CODE);
457 : : }
458 : :
459 : : /**
460 : : * @brief Advance the non-blocking device search by one hardware operation
461 : : * @return 1 when the search is finished, 0 while still running
462 : : */
463 : 11526 : uint8_t ds18b20_search_poll(void) { return onewire_search_poll(); }
464 : :
465 : : /**
466 : : * @brief Number of DS18B20 devices found (valid once the search finished)
467 : : * @return Count of found devices
468 : : */
469 : 132 : uint8_t ds18b20_search_count(void) { return onewire_search_count(); }
470 : :
471 : : /**
472 : : * @brief Advance the non-blocking alarm search by one hardware operation
473 : : * @return 1 when the search is finished, 0 while still running
474 : : */
475 : 4614 : uint8_t ds18b20_alarm_search_poll(void) { return onewire_search_poll(); }
476 : :
477 : : /**
478 : : * @brief Number of DS18B20 devices found in alarm (valid once finished)
479 : : * @return Count of alarmed devices
480 : : */
481 : 78 : uint8_t ds18b20_alarm_search_count(void) { return onewire_search_count(); }
482 : :
483 : : /**
484 : : * @}
485 : : */
486 : :
487 : : /**
488 : : * @brief Build the DS18B20 configuration register byte for a resolution
489 : : * @param[in] res Resolution in bits (9..12)
490 : : * @return Configuration register byte (R1/R0 bits set, rest at reset value)
491 : : * @note 9 bit -> 0x1F, 10 bit -> 0x3F, 11 bit -> 0x5F, 12 bit -> 0x7F.
492 : : */
493 : : __STATIC_FORCEINLINE uint8_t res_config_byte(uint8_t res) {
494 : 192 : return (uint8_t)(0x1Fu | ((uint8_t)(res - DS18B20_RES_MIN) << 5));
495 : : }
496 : :
497 : : /**
498 : : * @brief Pre-build the resolution config write into res_ctx.pulses
499 : : * @param[in] res Resolution in bits (9..12)
500 : : * @note Encodes Skip ROM (0xCC) or Match ROM (0x55 + selected ROM) followed by
501 : : * Write Scratchpad (0x4E), TH, TL and the config byte. The trailing
502 : : * zero-pulse that the 1-Wire layer consumes as the final DMA transfer
503 : : * (hardware bus release) is written at the slot index of the mode
504 : : * actually used, not always at the end of the buffer.
505 : : */
506 : : __STATIC_FORCEINLINE void build_res_pulses(uint8_t res) {
507 : : // In scan mode the config write must reach every sensor, so the Match ROM
508 : : // address is skipped even if a single-device address is still selected.
509 [ + + ]: 114 : const uint8_t use_match = ctx.address_mode && !ctx.scan_mode;
510 : 114 : uint8_t* p = res_ctx.pulses;
511 [ + + ]: 114 : if (use_match) {
512 : 18 : onewire_encode_byte(p, DS18B20_MATCH_ROM);
513 : 18 : p += DS18B20_BITS_PER_BYTE;
514 [ + + ]: 162 : for (uint8_t i = 0; i < DS18B20_ROM_BYTES; i++) {
515 : 144 : onewire_encode_byte(p, ctx.selected_rom[i]);
516 : 144 : p += DS18B20_BITS_PER_BYTE;
517 : : }
518 : : } else {
519 : 96 : onewire_encode_byte(p, 0xCC); /* Skip ROM */
520 : 96 : p += DS18B20_BITS_PER_BYTE;
521 : : }
522 : 114 : onewire_encode_byte(p, DS18B20_WRITE_SCRATCHPAD);
523 : 114 : p += DS18B20_BITS_PER_BYTE;
524 : 114 : onewire_encode_byte(p, DS18B20_RES_TH);
525 : 114 : p += DS18B20_BITS_PER_BYTE;
526 : 114 : onewire_encode_byte(p, DS18B20_RES_TL);
527 : 114 : p += DS18B20_BITS_PER_BYTE;
528 : 228 : onewire_encode_byte(p, res_config_byte(res));
529 [ + + ]: 114 : res_ctx.slots = use_match ? DS18B20_RES_SLOTS_MAX : DS18B20_RES_SLOTS_MIN;
530 : 114 : res_ctx.pulses[res_ctx.slots] = 0;
531 : 114 : }
532 : :
533 : : /**
534 : : * @brief Start a non-blocking resolution change
535 : : * @param[in] bits New resolution in bits: DS18B20_RES_MIN (9) .. DS18B20_RES_MAX (12)
536 : : * @note Out-of-range values are ignored. The change is scheduled only between
537 : : * measurement cycles and only while the device search is idle; otherwise
538 : : * it is ignored. While running, it owns TIM1/DMA; poll it with
539 : : * ds18b20_set_resolution_poll() until it reports completion, then call
540 : : * ds18b20_poll() again to resume measuring with the new resolution.
541 : : */
542 : 162 : void ds18b20_set_resolution(uint8_t bits) {
543 [ + + + + ]: 162 : if (bits < DS18B20_RES_MIN || bits > DS18B20_RES_MAX) {
544 : 24 : return; // out of range - ignore
545 : : }
546 [ + + ]: 138 : if (!res_ctx.finished) {
547 : 6 : return; // a resolution change is already running
548 : : }
549 [ + + ]: 132 : if (!txn_ctx.finished) {
550 : 6 : return; // a command transaction is running
551 : : }
552 [ + + ]: 126 : if (onewire_search_active()) {
553 : 6 : return; // the device search owns the timer
554 : : }
555 [ + + ]: 120 : if (ctx.current_state != DS18B20_ST_IDLE) {
556 : 6 : return; // a measurement cycle is in progress
557 : : }
558 : 114 : res_ctx.pending_res = bits;
559 : 114 : res_ctx.applied = 0;
560 : 114 : res_ctx.finished = 0;
561 [ + + ]: 114 : build_res_pulses(bits); // Pre-build the config write for the current address mode
562 : 114 : res_ctx.phase = DS18B20_RES_RESET;
563 : 114 : onewire_reset(ctx.capture); // Schedule the first hardware operation
564 : : }
565 : :
566 : : /**
567 : : * @brief Advance the non-blocking resolution change by one hardware operation
568 : : * @return 1 when the change is finished (successfully or aborted), 0 while running
569 : : * @note When this returns 1 the next measurement uses the requested resolution
570 : : * if (and only if) the config write actually completed; an aborted change
571 : : * (e.g. no device present) leaves the resolution unchanged.
572 : : */
573 : 414 : uint8_t ds18b20_set_resolution_poll(void) {
574 [ + + ]: 414 : if (res_ctx.finished) {
575 : 72 : return 1;
576 : : }
577 : :
578 [ + + ]: 342 : if (res_ctx.phase == DS18B20_RES_DONE) {
579 : : // The last hardware operation completed (config written or aborted):
580 : : // hand the timer back to the measurement state machine exactly once.
581 : : ow_port_kick();
582 [ + + ]: 90 : if (res_ctx.applied) {
583 : 84 : ctx.resolution = res_ctx.pending_res;
584 : : }
585 : 90 : res_ctx.finished = 1;
586 : 90 : return 1;
587 : : }
588 : :
589 : : // Wait for the currently scheduled hardware operation to complete.
590 : : // This is a non-blocking poll, not a busy-wait.
591 [ + + ]: 252 : if (!onewire_bus_done()) {
592 : 78 : return 0;
593 : : }
594 : :
595 [ + + - ]: 174 : switch (res_ctx.phase) {
596 : 90 : case DS18B20_RES_RESET:
597 : : // Reset completed: a presence pulse means at least one device is on
598 : : // the bus, so send the config write for the requested resolution.
599 [ + + ]: 90 : if (!onewire_present(ctx.capture)) {
600 : 6 : res_ctx.phase = DS18B20_RES_DONE;
601 : 6 : break;
602 : : }
603 : 84 : onewire_write_slots(res_ctx.pulses, res_ctx.slots);
604 : 84 : res_ctx.phase = DS18B20_RES_WRITE;
605 : 84 : break;
606 : :
607 : 84 : case DS18B20_RES_WRITE:
608 : : // Config write completed: the sensor now uses the new resolution.
609 : 84 : res_ctx.applied = 1;
610 : 84 : res_ctx.phase = DS18B20_RES_DONE;
611 : 84 : break;
612 : :
613 : 0 : case DS18B20_RES_DONE:
614 : : default:
615 : 0 : break;
616 : : }
617 : :
618 : 174 : return 0;
619 : : }
620 : :
621 : : /**
622 : : * @brief Current conversion resolution in bits
623 : : * @return Resolution in bits (9..12); the default is 12
624 : : * @note Auto-derived from the last valid scratchpad read (byte 4, R1/R0),
625 : : * so it also tracks a resolution changed externally.
626 : : */
627 : 168 : uint8_t ds18b20_get_resolution(void) { return ctx.resolution; }
628 : :
629 : : /**
630 : : * @brief Finish the current scan-mode device read
631 : : * @note Called after every per-device report in scan mode. Advances to the
632 : : * next device (CONTINUE, skipping a fresh conversion) or, after the last
633 : : * device, returns to IDLE and starts the inter-measurement pause so the
634 : : * next round begins with a new broadcast Convert T. In single-device
635 : : * mode it only starts the inter-measurement pause.
636 : : */
637 : 180 : static void scan_finish_or_next(void) {
638 [ + + ]: 180 : if (!ctx.scan_mode) {
639 : : // Parasite power: hold the strong pull-up during the inter-round pause
640 : : // so the device capacitors stay charged for the next measurement cycle.
641 [ + + ]: 132 : if (ctx.parasite) {
642 : 12 : onewire_strong_pullup(1);
643 : : }
644 : : start_cycle_pause();
645 : 132 : return;
646 : : }
647 : 48 : ctx.scan_index++;
648 [ + + ]: 48 : if (ctx.scan_index < dev_count) {
649 : 24 : ctx.current_state = DS18B20_ST_CONTINUE;
650 : : /* DECODE armed nothing, so without a running timer no UIF would ever
651 : : * drive the CONTINUE state again (single-device mode gets its UIF from
652 : : * the inter-measurement pause). Arm a short scheduling delay: its UIF
653 : : * is the bridge to CONTINUE, which then arms the real bus reset. */
654 : 24 : onewire_start_timer(SCAN_DEVICE_GAP);
655 : : } else {
656 : 24 : ctx.current_state = DS18B20_ST_IDLE;
657 : : // Parasite power: keep the strong pull-up engaged across the pause.
658 [ + + ]: 24 : if (ctx.parasite) {
659 : 6 : onewire_strong_pullup(1);
660 : : }
661 : : start_cycle_pause();
662 : : }
663 : : }
664 : :
665 : : /**
666 : : * @brief Begin simultaneous conversion of every discovered device
667 : : * @see ds18b20_scan_start() in ds18b20.h
668 : : */
669 : 84 : void ds18b20_scan_start(void) {
670 [ + + ]: 84 : if (ctx.current_state != DS18B20_ST_IDLE) {
671 : 6 : return; // a measurement cycle is in progress
672 : : }
673 [ + + + + : 78 : if (onewire_search_active() || !res_ctx.finished || !txn_ctx.finished) {
+ + ]
674 : 18 : return; // the search, a resolution change or a command owns the timer
675 : : }
676 [ + + ]: 60 : if (dev_count == 0) {
677 : 6 : return; // nothing discovered: there is no device to convert
678 : : }
679 : 54 : ctx.scan_mode = 1;
680 : 54 : ctx.scan_index = 0;
681 : : }
682 : :
683 : : /**
684 : : * @brief Number of DS18B20 devices stored by the driver
685 : : * @see ds18b20_device_count() in ds18b20.h
686 : : */
687 : 30 : uint8_t ds18b20_device_count(void) { return dev_count; }
688 : :
689 : : /**
690 : : * @brief ROM address of a discovered device
691 : : * @see ds18b20_device_rom() in ds18b20.h
692 : : */
693 : 228 : const uint8_t* ds18b20_device_rom(uint8_t index) {
694 [ + + ]: 228 : if (index >= dev_count) {
695 : 24 : return 0;
696 : : }
697 : 204 : return dev_roms[index];
698 : : }
699 : :
700 : : /**
701 : : * @brief Index of the device whose result ds18b20_complete() just reported
702 : : * @see ds18b20_scan_index() in ds18b20.h
703 : : */
704 : 216 : uint8_t ds18b20_scan_index(void) { return ctx.scan_index; }
705 : :
706 : : /**
707 : : * @}
708 : : */
709 : :
710 : : /**
711 : : * @defgroup DS18B20_Command_Impl DS18B20 Non-Blocking Command Transactions
712 : : * @brief Shared non-blocking engine for the infrequent DS18B20 commands that
713 : : * the measurement state machine does not issue: Read ROM (0x33),
714 : : * Write Scratchpad thresholds (0x4E), Copy Scratchpad (0x48), Recall
715 : : * EEPROM (0xB8), Read Power Supply (0xB4) and raw Read Scratchpad
716 : : * (0xBE). Every command runs reset -> presence -> write -> (read |
717 : : * timed wait) -> done, one hardware-timed operation per poll call, so
718 : : * the same non-blocking discipline as the measurement, search and
719 : : * resolution state machines is preserved. Each command owns TIM1/DMA
720 : : * while it runs and hands the timer back to ds18b20_poll() when done.
721 : : * @{
722 : : */
723 : :
724 : : /**
725 : : * @brief Ownership guard shared by every command transaction start
726 : : * @return 1 when a new transaction may be scheduled
727 : : */
728 : : __STATIC_FORCEINLINE uint8_t txn_can_start(void) {
729 : : /* A scan session owns the timer for its whole duration (scan_mode stays 1
730 : : * until ds18b20_select() clears it): a command transaction started then
731 : : * would clobber the in-flight measurement/scan cycle, so it must be
732 : : * rejected. Without this check a command could slip through during the
733 : : * brief IDLE pause between scan rounds. */
734 : 318 : return (uint8_t)(ctx.current_state == DS18B20_ST_IDLE &&
735 [ + + + + ]: 312 : !ctx.scan_mode && !onewire_search_active() &&
736 [ + + + + ]: 318 : res_ctx.finished && txn_ctx.finished);
737 : : }
738 : :
739 : : /**
740 : : * @brief Build the command pulse sequence into txn_ctx.pulses
741 : : * @note Encodes the addressing prefix (Skip ROM 0xCC, or Match ROM 0x55 +
742 : : * selected ROM; none for a bare command such as Read ROM), the function
743 : : * command byte and the optional payload (Write Scratchpad TH/TL/CFG).
744 : : * The trailing zero-pulse that the 1-Wire layer consumes as the final
745 : : * DMA transfer (hardware bus release) is written at the slot index of
746 : : * the mode actually used, not always at the end of the buffer.
747 : : */
748 : : __STATIC_FORCEINLINE void txn_build_pulses(void) {
749 : : // In scan mode the command must reach every sensor, so the Match ROM
750 : : // address is skipped even if a single-device address is still selected.
751 [ + - + - ]: 282 : const uint8_t use_match = ctx.address_mode && !ctx.scan_mode && !txn_ctx.bare;
752 : 282 : uint8_t* p = txn_ctx.pulses;
753 : 282 : uint8_t bytes = 0;
754 [ + + ]: 282 : if (!txn_ctx.bare) {
755 [ + + ]: 216 : if (use_match) {
756 : 18 : onewire_encode_byte(p, DS18B20_MATCH_ROM);
757 : 18 : p += DS18B20_BITS_PER_BYTE;
758 : 18 : bytes++;
759 [ + + ]: 162 : for (uint8_t i = 0; i < DS18B20_ROM_BYTES; i++) {
760 : 144 : onewire_encode_byte(p, ctx.selected_rom[i]);
761 : 144 : p += DS18B20_BITS_PER_BYTE;
762 : 144 : bytes++;
763 : : }
764 : : } else {
765 : 198 : onewire_encode_byte(p, 0xCC); /* Skip ROM */
766 : 198 : p += DS18B20_BITS_PER_BYTE;
767 : 198 : bytes++;
768 : : }
769 : : }
770 : 282 : onewire_encode_byte(p, txn_ctx.command);
771 : 282 : p += DS18B20_BITS_PER_BYTE;
772 : 282 : bytes++;
773 [ + + ]: 480 : for (uint8_t i = 0; i < txn_ctx.payload_len; i++) {
774 : 198 : onewire_encode_byte(p, txn_ctx.payload[i]);
775 : 198 : p += DS18B20_BITS_PER_BYTE;
776 : 198 : bytes++;
777 : : }
778 : 282 : txn_ctx.slots = (uint8_t)(bytes * DS18B20_BITS_PER_BYTE);
779 : : /* B1: guarantee the trailing zero-pulse that the 1-Wire layer reads as its
780 : : * final DMA transfer into CCR3, even though the command write only ever
781 : : * fills slots 0 .. slots - 1 (see build_res_pulses for the same pattern). */
782 : 282 : txn_ctx.pulses[txn_ctx.slots] = 0;
783 : 282 : }
784 : :
785 : : /**
786 : : * @brief Decode the captured read pulses into txn_ctx.raw
787 : : * @note Reads ctx.pulse (written by the read DMA), never aliased with raw:
788 : : * the union invariant of decode_scratchpad() does not apply here.
789 : : */
790 : : __STATIC_FORCEINLINE void txn_decode_read(void) {
791 : 72 : onewire_decode_pulses(txn_ctx.raw, ctx.pulse, txn_ctx.read_bytes);
792 : 72 : }
793 : :
794 : : /**
795 : : * @brief Copy the decoded read result into the user buffer
796 : : * @param[in] len Number of bytes to copy (txn_ctx.out must hold at least len)
797 : : */
798 : : __STATIC_FORCEINLINE void txn_copy_out(uint8_t len) {
799 [ + + + + ]: 456 : for (uint8_t i = 0; i < len; i++) {
800 : 408 : txn_ctx.out[i] = txn_ctx.raw[i];
801 : : }
802 : 48 : }
803 : :
804 : : /**
805 : : * @brief Advance the active command transaction by one hardware operation
806 : : * @return 1 when the transaction finished (successfully or aborted), 0 while
807 : : * running
808 : : */
809 : 870 : static uint8_t txn_poll(void) {
810 [ + + ]: 870 : if (txn_ctx.finished) {
811 : 18 : return 1;
812 : : }
813 : :
814 [ + + ]: 852 : if (txn_ctx.phase == DS18B20_TXN_DONE) {
815 : : // The last hardware operation completed (command done or aborted):
816 : : // hand the timer back to the measurement state machine exactly once.
817 : : ow_port_kick();
818 : 198 : txn_ctx.finished = 1;
819 : 198 : return 1;
820 : : }
821 : :
822 : : // Wait for the currently scheduled hardware operation to complete.
823 : : // This is a non-blocking poll, not a busy-wait.
824 [ + + ]: 654 : if (!onewire_bus_done()) {
825 : 180 : return 0;
826 : : }
827 : :
828 [ + + + + : 474 : switch (txn_ctx.phase) {
- ]
829 : 198 : case DS18B20_TXN_RESET:
830 : : // Reset completed: a presence pulse means at least one device is on
831 : : // the bus, so send the command for this transaction.
832 [ + + ]: 198 : if (!onewire_present(ctx.capture)) {
833 : 42 : txn_ctx.phase = DS18B20_TXN_DONE;
834 : 42 : break;
835 : : }
836 [ + + ]: 156 : if (ctx.parasite) {
837 : 24 : onewire_strong_pullup(1);
838 : : }
839 : 156 : onewire_write_slots(txn_ctx.pulses, txn_ctx.slots);
840 : 156 : txn_ctx.phase = DS18B20_TXN_WRITE;
841 : 156 : break;
842 : :
843 : 156 : case DS18B20_TXN_WRITE:
844 : : // Command write completed: read the response back if the command has
845 : : // one, otherwise wait the required hold-off or finish immediately.
846 [ + + ]: 156 : if (txn_ctx.read_bytes) {
847 [ + + ]: 72 : if (ctx.parasite) {
848 : 6 : onewire_strong_pullup(0);
849 : : }
850 : 72 : onewire_read_data(ctx.pulse, txn_ctx.read_bytes);
851 : 72 : txn_ctx.phase = DS18B20_TXN_READ;
852 [ + + ]: 84 : } else if (txn_ctx.wait_us) {
853 : : // Parasite power: Copy Scratchpad / Recall E² draw their supply
854 : : // from the bus while the EEPROM programs - drive HIGH actively
855 : : // for the hold-off window.
856 [ + + ]: 48 : if (ctx.parasite) {
857 : 12 : onewire_strong_pullup(1);
858 : : }
859 : 48 : onewire_start_timer(txn_ctx.wait_us, 0);
860 : 48 : txn_ctx.phase = DS18B20_TXN_WAIT;
861 : : } else {
862 [ + + ]: 36 : if (ctx.parasite) {
863 : 6 : onewire_strong_pullup(0);
864 : : }
865 : 36 : txn_ctx.ok = 1;
866 : 36 : txn_ctx.phase = DS18B20_TXN_DONE;
867 : : }
868 : 156 : break;
869 : :
870 : 72 : case DS18B20_TXN_READ:
871 : : // Data read completed: decode the captured pulse durations.
872 : : txn_decode_read();
873 : 72 : txn_ctx.ok = 1;
874 : 72 : txn_ctx.phase = DS18B20_TXN_DONE;
875 : 72 : break;
876 : :
877 : 48 : case DS18B20_TXN_WAIT:
878 : : // Hold-off completed (Copy Scratchpad / Recall EEPROM): release the
879 : : // strong pull-up unconditionally (idempotent) so a parasite flag
880 : : // cleared mid-window cannot leave the bus actively driven.
881 : 48 : onewire_strong_pullup(0);
882 : 48 : txn_ctx.ok = 1;
883 : 48 : txn_ctx.phase = DS18B20_TXN_DONE;
884 : 48 : break;
885 : :
886 : 0 : case DS18B20_TXN_DONE:
887 : : default:
888 : 0 : break;
889 : : }
890 : :
891 : 474 : return 0;
892 : : }
893 : :
894 : : /**
895 : : * @brief Schedule a new non-blocking command transaction
896 : : * @param[in] command DS18B20 function command byte
897 : : * @param[in] out User result buffer (may be NULL; only written on success)
898 : : * @param[in] payload Up to 3 payload bytes (Write Scratchpad TH/TL/CFG)
899 : : * @param[in] payload_len Number of payload bytes (0..3)
900 : : * @param[in] read_bytes Bytes to read back after the command (0 = none)
901 : : * @param[in] wait_us Timed hold-off after the command (0 = none)
902 : : * @param[in] bare 1 to send the command without an addressing prefix
903 : : * @note Ignored unless the driver is IDLE, the device search and any
904 : : * resolution change are finished, and no transaction is already
905 : : * running. The result buffer must stay valid until the transaction
906 : : * completes (ds18b20_*_poll() reports 1).
907 : : */
908 [ + + ]: 318 : static void txn_start(uint8_t command, uint8_t* out, const uint8_t* payload,
909 : : uint8_t payload_len, uint8_t read_bytes, uint16_t wait_us,
910 : : uint8_t bare) {
911 [ + + ]: 318 : if (!txn_can_start()) {
912 : 36 : return; // the timer belongs to someone else right now
913 : : }
914 : 282 : txn_ctx.command = command;
915 : 282 : txn_ctx.out = out;
916 : 282 : txn_ctx.read_bytes = read_bytes;
917 : 282 : txn_ctx.wait_us = wait_us;
918 : 282 : txn_ctx.bare = bare;
919 : 282 : txn_ctx.payload_len = payload_len;
920 [ + + + - ]: 480 : for (uint8_t i = 0; i < payload_len && i < sizeof(txn_ctx.payload); i++) {
921 : 198 : txn_ctx.payload[i] = payload[i];
922 : : }
923 : 282 : txn_ctx.ok = 0;
924 [ + + ]: 282 : txn_ctx.finished = 0;
925 : : txn_build_pulses(); // Pre-build the command for the current address mode
926 : 282 : onewire_strong_pullup(0);
927 : 282 : txn_ctx.phase = DS18B20_TXN_RESET;
928 : 282 : onewire_reset(ctx.capture); // Schedule the first hardware operation
929 : : }
930 : :
931 : : /**
932 : : * @brief Read the 64-bit ROM of the (only) DS18B20 on the bus
933 : : * @param[in,out] rom Buffer for the 8-byte ROM (LSB first); written on success
934 : : * @note Valid only when exactly one device is on the bus (datasheet Read ROM
935 : : * 0x33). With several devices use the device search (ds18b20_search_*).
936 : : * @note Result validity: check ds18b20_last_command_ok() or the CRC over the
937 : : * 7 leading bytes (ds18b20_crc8(rom, 7) == rom[7]).
938 : : */
939 : 90 : void ds18b20_read_rom(uint8_t* rom) {
940 : 90 : txn_start(DS18B20_READ_ROM, rom, 0, 0, DS18B20_ROM_BYTES, 0, 1);
941 : 90 : }
942 : :
943 : : /**
944 : : * @brief Advance the non-blocking Read ROM transaction
945 : : * @return 1 when finished (successfully or aborted), 0 while running
946 : : */
947 : 156 : uint8_t ds18b20_read_rom_poll(void) {
948 [ + + ]: 156 : if (!txn_poll()) {
949 : 108 : return 0;
950 : : }
951 [ + + + - ]: 48 : if (txn_ctx.ok && txn_ctx.out) {
952 : : txn_copy_out(DS18B20_ROM_BYTES);
953 : : }
954 : 48 : return 1;
955 : : }
956 : :
957 : : /**
958 : : * @brief Configure the alarm trigger thresholds TH and TL
959 : : * @param[in] th High-alarm trigger value (DS18B20 8-bit threshold code)
960 : : * @param[in] tl Low-alarm trigger value (DS18B20 8-bit threshold code)
961 : : * @note Uses the DS18B20 8-bit sign-extended temperature code, the same
962 : : * encoding the scratchpad TH/TL bytes use; converting to/from Celsius is
963 : : * left to the application. The current conversion resolution (byte 4,
964 : : * R1/R0) is written unchanged, so the resolution is not disturbed.
965 : : * @note Takes effect immediately in the scratchpad; run ds18b20_copy_scratchpad()
966 : : * afterwards to persist TH/TL/CFG to the EEPROM.
967 : : */
968 : 78 : void ds18b20_set_alarm_thresholds(uint8_t th, uint8_t tl) {
969 : 78 : const uint8_t payload[3] = {th, tl, res_config_byte(ctx.resolution)};
970 : 78 : txn_start(DS18B20_WRITE_SCRATCHPAD, 0, payload, 3, 0, 0, 0);
971 : 78 : }
972 : :
973 : : /**
974 : : * @brief Advance the non-blocking alarm threshold write
975 : : * @return 1 when finished (successfully or aborted), 0 while running
976 : : */
977 : 156 : uint8_t ds18b20_set_alarm_thresholds_poll(void) { return txn_poll(); }
978 : :
979 : : /**
980 : : * @brief Read the 9-byte scratchpad (raw; includes TH, TL and the CRC)
981 : : * @param[in,out] buf Buffer for the 9 scratchpad bytes (byte 0 = temp LSB,
982 : : * bytes 2/3 = TH/TL, byte 8 = CRC); written on success
983 : : * @note Result validity: check ds18b20_last_command_ok() or the CRC over the
984 : : * 8 leading bytes (buf[8] == ds18b20_crc8(buf, 8)).
985 : : */
986 : 30 : void ds18b20_read_scratchpad(uint8_t* buf) {
987 : 30 : txn_start(DS18B20_READ_SCRATCHPAD, buf, 0, 0, DS18B20_SCRATCHPAD_LEN, 0, 0);
988 : 30 : }
989 : :
990 : : /**
991 : : * @brief Advance the non-blocking raw scratchpad read
992 : : * @return 1 when finished (successfully or aborted), 0 while running
993 : : * @note On a valid read (CRC byte matches) the conversion resolution is
994 : : * auto-derived from the config byte (byte 4), like the measurement path.
995 : : */
996 : 138 : uint8_t ds18b20_read_scratchpad_poll(void) {
997 [ + + ]: 138 : if (!txn_poll()) {
998 : 108 : return 0;
999 : : }
1000 [ + + + - ]: 30 : if (txn_ctx.ok && txn_ctx.out) {
1001 : : txn_copy_out(DS18B20_SCRATCHPAD_LEN);
1002 [ + + ]: 24 : if (txn_ctx.raw[DS18B20_SCRATCHPAD_LEN - 1] ==
1003 : 24 : ds18b20_crc8(txn_ctx.raw, DS18B20_CRC8_BYTES)) {
1004 : 18 : ctx.resolution = DS18B20_RES_MIN + ((txn_ctx.raw[4] >> 5) & 0x3);
1005 : : }
1006 : : }
1007 : 30 : return 1;
1008 : : }
1009 : :
1010 : : /**
1011 : : * @brief Copy the scratchpad into the EEPROM (non-volatile)
1012 : : * @note The copy draws its supply from VDD on externally powered devices;
1013 : : * parasite-powered devices are supplied by the strong pull-up, which
1014 : : * the driver engages for the t_COPY hold-off window when
1015 : : * ds18b20_set_parasite(1) is set. The driver waits the datasheet
1016 : : * t_COPY hold-off (10ms) before finishing.
1017 : : */
1018 : 48 : void ds18b20_copy_scratchpad(void) {
1019 : 48 : txn_start(DS18B20_COPY_SCRATCHPAD, 0, 0, 0, 0, DS18B20_EEPROM_WAIT_US, 0);
1020 : 48 : }
1021 : :
1022 : : /**
1023 : : * @brief Advance the non-blocking Copy Scratchpad transaction
1024 : : * @return 1 when finished (successfully or aborted), 0 while running
1025 : : */
1026 : 162 : uint8_t ds18b20_copy_scratchpad_poll(void) { return txn_poll(); }
1027 : :
1028 : : /**
1029 : : * @brief Recall the EEPROM contents into the scratchpad
1030 : : * @note Loads the last EEPROM copy (TH/TL/CFG) into the volatile scratchpad.
1031 : : * The driver waits the datasheet t_RECALL hold-off (10ms) before
1032 : : * finishing.
1033 : : * @note After recall, the scratchpad holds the EEPROM-stored configuration
1034 : : * (TH/TL/CFG, including the conversion-resolution bits). The driver's
1035 : : * tracked ctx.resolution is NOT updated by this call: Recall is a
1036 : : * write-only command with no data returned. If the EEPROM resolution
1037 : : * may differ from ctx.resolution, follow this with
1038 : : * ds18b20_read_scratchpad() / ds18b20_read_scratchpad_poll() to
1039 : : * resynchronise ctx.resolution before the next conversion.
1040 : : */
1041 : 30 : void ds18b20_recall_eeprom(void) {
1042 : 30 : txn_start(DS18B20_RECALL_EEPROM, 0, 0, 0, 0, DS18B20_EEPROM_WAIT_US, 0);
1043 : 30 : }
1044 : :
1045 : : /**
1046 : : * @brief Advance the non-blocking Recall EEPROM transaction
1047 : : * @return 1 when finished (successfully or aborted), 0 while running
1048 : : * @warning ctx.resolution is NOT updated on success. Recall is a write-only
1049 : : * command; the device does not return the restored config. To keep
1050 : : * ctx.resolution in sync with a possibly-different EEPROM resolution,
1051 : : * call ds18b20_read_scratchpad_poll() after this returns 1 and
1052 : : * ds18b20_last_command_ok() is set.
1053 : : */
1054 : 102 : uint8_t ds18b20_recall_eeprom_poll(void) {
1055 [ + + ]: 102 : if (!txn_poll()) {
1056 : 78 : return 0;
1057 : : }
1058 : : /* Nothing to decode: Recall returns no data, so there is no scratchpad
1059 : : * frame to parse here. The caller is responsible for resynchronising
1060 : : * ctx.resolution via ds18b20_read_scratchpad_poll() if needed. */
1061 : 24 : return 1;
1062 : : }
1063 : :
1064 : : /**
1065 : : * @brief Result of the last completed command transaction
1066 : : * @return 1 when the last ds18b20_*_poll() finished a transaction that found
1067 : : * a device present (and, for read commands, read its data back),
1068 : : * 0 when it aborted (e.g. no device present) or nothing ran yet
1069 : : */
1070 : 126 : uint8_t ds18b20_last_command_ok(void) { return txn_ctx.ok; }
1071 : :
1072 : : /**
1073 : : * @brief Declare the bus as parasite-powered
1074 : : * @param[in] parasite 1 = devices are powered over the data line, 0 =
1075 : : * external VDD supply (default)
1076 : : * @note In parasite mode the driver engages the strong pull-up (bus pin
1077 : : * switched to push-pull HIGH) during every temperature conversion wait
1078 : : * and EEPROM programming hold-off, then releases the line again. The
1079 : : * flag is read at the start of each window, so call this once after
1080 : : * ds18b20_init() - or between measurement cycles - and it applies to
1081 : : * all subsequent operations. The detection helper
1082 : : * ds18b20_detect_parasite() reports the wiring and stores it back into
1083 : : * this flag on success; this setter tells the driver how to behave.
1084 : : */
1085 : 120 : void ds18b20_set_parasite(uint8_t parasite) {
1086 [ + + ]: 120 : ctx.parasite = parasite ? 1u : 0u;
1087 : 120 : ow_set_parasite_guard(ctx.parasite);
1088 : 120 : }
1089 : :
1090 : : /**
1091 : : * @brief Current parasite-power configuration of the driver
1092 : : * @return 1 when the strong pull-up will be engaged during conversion and
1093 : : * EEPROM programming windows, 0 for external VDD supply
1094 : : */
1095 : 54 : uint8_t ds18b20_parasite_mode(void) { return ctx.parasite; }
1096 : :
1097 : : /**
1098 : : * @brief Detect the bus wiring and configure parasite mode automatically
1099 : : * @note Issues a Read Power Supply command and stores the decoded answer in
1100 : : * ctx.parasite on success (see ds18b20_detect_parasite_poll()).
1101 : : */
1102 : 42 : void ds18b20_detect_parasite(void) { txn_start(DS18B20_READ_POWER_SUPPLY, &detect_buf, 0, 0, 1, 0, 0); }
1103 : :
1104 : 156 : uint8_t ds18b20_detect_parasite_poll(void) {
1105 [ + + ]: 156 : if (!txn_poll()) {
1106 : 120 : return 0;
1107 : : }
1108 [ + + ]: 36 : if (txn_ctx.ok) {
1109 : : // The sensor drives one bit: 0 = parasite power, 1 = external power.
1110 : 24 : ctx.parasite = (txn_ctx.raw[0] & 0x01) ? 0u : 1u;
1111 : 24 : ow_set_parasite_guard(ctx.parasite);
1112 : : }
1113 : 36 : return 1;
1114 : : }
1115 : :
1116 : : /**
1117 : : * @}
1118 : : */
1119 : :
1120 : : /**
1121 : : * @defgroup DS18B20_Public_Functions DS18B20 Public Functions
1122 : : * @{
1123 : : */
1124 : :
1125 : : /**
1126 : : * @brief Initialize DS18B20 driver - configure clocks and peripherals
1127 : : * @note Initializes the shared 1-Wire layer (timer/DMA/GPIO) and marks the
1128 : : * driver idle so the measurement state machine owns the timer until the
1129 : : * application starts a device search.
1130 : : */
1131 : 415 : void ds18b20_init(void) {
1132 : 415 : onewire_init();
1133 : : // No resolution change or command transaction running after init; the
1134 : : // DS18B20 powers up at 12 bit (750ms conversion), so wait for exactly that
1135 : : // until a scratchpad read or set_resolution tells us otherwise.
1136 : 415 : res_ctx.finished = 1;
1137 : 415 : txn_ctx.finished = 1;
1138 : 415 : ctx.resolution = DS18B20_RES_DEFAULT;
1139 : 415 : ctx.scan_mode = 0;
1140 : 415 : ctx.scan_index = 0;
1141 : : // External power is the default wiring assumption; parasite-powered
1142 : : // setups opt in explicitly via ds18b20_set_parasite().
1143 : 415 : ctx.parasite = 0;
1144 : 415 : }
1145 : :
1146 : : /**
1147 : : * @brief Select which DS18B20 device to measure by its ROM address
1148 : : * @param[in] rom Pointer to the 8-byte ROM address (LSB first), or NULL to
1149 : : * return to Skip ROM (broadcast) addressing
1150 : : * @note With a non-NULL address, the state machine sends Match ROM (0x55)
1151 : : * plus the device address before each command, so only that device
1152 : : * responds. Pass NULL (or a freshly initialised driver) to keep the
1153 : : * legacy single-sensor Skip ROM behaviour. The address should come from
1154 : : * the non-blocking device search (ds18b20_search_*).
1155 : : * @note The selection is applied only between measurement cycles (driver
1156 : : * IDLE). Calls made while a cycle is running are ignored, including from
1157 : : * the per-device scan callback (which the driver invokes at
1158 : : * DS18B20_ST_DECODE mid-round): a select() there is rejected and the
1159 : : * scan round continues. Applying a select mid-cycle would overwrite
1160 : : * ctx.addr_cmd while the DMA is still feeding it, corrupting the
1161 : : * in-flight bus transaction. Re-call at IDLE (e.g. from
1162 : : * ds18b20_complete() in single-device mode, or between rounds) to switch
1163 : : * addressing.
1164 : : */
1165 : 192 : void ds18b20_select(const uint8_t* rom) {
1166 : : /* Select is accepted only when no bus transaction is in flight, i.e. at
1167 : : * driver IDLE. The per-device scan callback runs at DS18B20_ST_DECODE
1168 : : * mid-round: a select() there is rejected so it cannot interrupt the
1169 : : * in-progress scan. To leave scan mode, call select() between measurement
1170 : : * rounds (at IDLE) or from the single-device ds18b20_complete() callback
1171 : : * (which runs at IDLE). */
1172 [ + + ]: 192 : if (ctx.current_state != DS18B20_ST_IDLE) {
1173 : 12 : return;
1174 : : }
1175 [ + + ]: 180 : if (!txn_ctx.finished) {
1176 : : // A command transaction is running - reject to keep its addressing.
1177 : 6 : return;
1178 : : }
1179 [ + + ]: 174 : if (!res_ctx.finished) {
1180 : : // A resolution change is running - reject to keep its addressing.
1181 : 6 : return;
1182 : : }
1183 [ + + ]: 168 : if (onewire_search_active()) {
1184 : : // The device search owns the timer - reject to keep its addressing.
1185 : 6 : return;
1186 : : }
1187 : : // Explicit single-device addressing: leave simultaneous-conversion mode.
1188 : 162 : ctx.scan_mode = 0;
1189 [ + + ]: 162 : if (rom == 0) {
1190 : 6 : ctx.address_mode = 0;
1191 : 6 : return;
1192 : : }
1193 [ + + ]: 1404 : for (uint8_t i = 0; i < DS18B20_ROM_BYTES; i++) {
1194 : 1248 : ctx.selected_rom[i] = rom[i];
1195 : : }
1196 : : build_addr_prefix(); // Build the invariant Match ROM prefix once per selection
1197 : 156 : ctx.address_mode = 1;
1198 : : }
1199 : :
1200 : : static uint8_t conv_cmd[DS18B20_DMA_TRANSFERS + 1];
1201 : : static uint8_t read_cmd[DS18B20_DMA_TRANSFERS + 1];
1202 : :
1203 : : /* B1 guard: same trailing bus-release invariant as addr_cmd/txn_ctx/res_ctx —
1204 : : * the 1-Wire layer's CCR3-feed DMA reads cmd[DS18B20_DMA_TRANSFERS] as the
1205 : : * final zero-pulse. Keep both Skip-ROM buffers at +1 for uniformity. */
1206 : : _Static_assert(sizeof(conv_cmd) >= DS18B20_DMA_TRANSFERS + 1,
1207 : : "conv_cmd must be DS18B20_DMA_TRANSFERS + 1 to hold the trailing "
1208 : : "bus-release pulse consumed by the 1-Wire layer");
1209 : : _Static_assert(sizeof(read_cmd) >= DS18B20_DMA_TRANSFERS + 1,
1210 : : "read_cmd must be DS18B20_DMA_TRANSFERS + 1 to hold the trailing "
1211 : : "bus-release pulse consumed by the 1-Wire layer");
1212 : :
1213 : : /* Lifetime note: conv_cmd/read_cmd are shared static buffers reused on every
1214 : : * build_skip_cmd() call. This is safe only because issue_command() is invoked
1215 : : * exclusively from the CONVERT/REQUEST states after onewire_bus_done() has
1216 : : * confirmed that the timer/DMA of the previous 1-Wire operation is idle, and
1217 : : * the ownership guards (ds18b20_select/search/resolution reject while busy)
1218 : : * prevent any concurrent re-entry that could interleave a second build while
1219 : : * the CCR3-feed DMA is still reading the table. In other words the rewrite
1220 : : * happens strictly between DMA bursts, never during one — the invariant is
1221 : : * implicit in the call site, hence documented here at the same level of
1222 : : * detail as the B1 guards for the other pulse buffers. */
1223 : 186 : static void build_skip_cmd(uint8_t* dst, uint8_t cmd_byte) {
1224 : 186 : onewire_encode_byte(dst, 0xCC);
1225 : 186 : onewire_encode_byte(dst + 8, cmd_byte);
1226 : 186 : dst[DS18B20_DMA_TRANSFERS] = 0;
1227 : 186 : }
1228 : :
1229 : : /**
1230 : : * @brief Check presence and issue a DS18B20 command (shared by CONVERT and
1231 : : * REQUEST states)
1232 : : * @param[in] cmd_byte Command byte to send (after the recycled Match ROM
1233 : : * prefix in address mode, or via the Skip-ROM table in
1234 : : * broadcast mode)
1235 : : * @param[in] next_state State to transition to on success
1236 : : */
1237 : 300 : static void issue_command(uint8_t cmd_byte, ds18b20_state_t next_state) {
1238 [ + + ]: 300 : if (!onewire_present(ctx.capture)) {
1239 : : // Return to IDLE before the callback so a re-selection from inside
1240 : : // ds18b20_complete() is accepted (ds18b20_select() only acts at IDLE).
1241 : 24 : ctx.current_state = DS18B20_ST_IDLE;
1242 : : // Turn the busy indicator off: busy(1) was set in START and this early
1243 : : // exit skips the DECODE state where busy(0) is normally cleared.
1244 : 24 : ds18b20_busy(0);
1245 : 24 : ds18b20_complete(DS18B20_TEMP_ERROR_NO_SENSOR);
1246 : : // Parasite power: hold the strong pull-up during the retry pause.
1247 [ + + ]: 24 : if (ctx.parasite) {
1248 : 6 : onewire_strong_pullup(1);
1249 : : }
1250 : : start_cycle_pause();
1251 : 24 : return;
1252 : : }
1253 [ + + ]: 276 : if (ctx.address_mode) {
1254 : 90 : build_addr_cmd(cmd_byte);
1255 : 90 : onewire_write_slots(ctx.addr_cmd, DS18B20_MATCH_SLOTS);
1256 : : } else {
1257 [ + + ]: 186 : uint8_t* skip_tbl = (cmd_byte == DS18B20_CONVERT_T) ? conv_cmd : read_cmd;
1258 : 186 : build_skip_cmd(skip_tbl, cmd_byte);
1259 : 186 : onewire_write_slots(skip_tbl, DS18B20_DMA_TRANSFERS);
1260 : : }
1261 : 276 : ctx.current_state = next_state;
1262 : : }
1263 : :
1264 : : /**
1265 : : * @brief Main state machine function - must be called periodically from main loop
1266 : : * @note Non-blocking state machine that advances 1-Wire communication state
1267 : : * @note Uses timer update interrupt flag to determine when operations complete
1268 : : */
1269 : 1050 : void ds18b20_poll(void) {
1270 : : // Ownership guard: while the device search, a resolution change or a
1271 : : // command transaction owns the timer, the measurement state machine must
1272 : : // stay out of the way and not react to their UIFs.
1273 [ + + + + : 1050 : if (onewire_search_active() || !res_ctx.finished || !txn_ctx.finished) {
- + ]
1274 : 12 : return;
1275 : : }
1276 : :
1277 : : // Check if timer update interrupt occurred (indicates operation completion)
1278 : : // This is the non-blocking way to detect when timed operations finish
1279 [ + + ]: 1038 : if (!ow_port_bus_done()) return;
1280 : :
1281 : : // State machine to manage 1-Wire communication sequence
1282 [ + - + + : 1032 : switch (ctx.current_state) {
+ + + +
+ ]
1283 : 150 : case DS18B20_ST_IDLE:
1284 : : // Initialize union memory (fills with 0xFF pattern)
1285 : 150 : ctx.fill_union = (uint64_t)-1;
1286 : : // Transition to START state
1287 : 150 : ctx.current_state = DS18B20_ST_START;
1288 : : /* fallthrough to START state immediately */
1289 : : __attribute__((fallthrough));
1290 : :
1291 : 150 : case DS18B20_ST_START:
1292 : : // Turn on LED to indicate measurement in progress
1293 : 150 : ds18b20_busy(1);
1294 : : // Parasite power: release the strong pull-up so the reset pulse can
1295 : : // drive the line LOW; it is re-engaged for the conversion window.
1296 [ + + ]: 150 : if (ctx.parasite) {
1297 : 24 : onewire_strong_pullup(0);
1298 : : }
1299 : : // Initiate 1-Wire bus reset sequence
1300 : 150 : onewire_reset(ctx.capture);
1301 : : // Transition to CONVERT state
1302 : 150 : ctx.current_state = DS18B20_ST_CONVERT;
1303 : 150 : break;
1304 : :
1305 : 162 : case DS18B20_ST_CONVERT:
1306 [ + + ]: 162 : if (ctx.scan_mode) {
1307 : : // Scan mode: broadcast Convert T (Skip ROM) so every sensor starts
1308 : : // converting in parallel; a single conversion wait covers them all.
1309 : 36 : ctx.scan_index = 0; // new round: read back starting from device 0
1310 : 36 : ctx.address_mode = 0;
1311 : : }
1312 : : // Parasite power: the Convert T command is master-only (the slave does
1313 : : // not pull the line LOW during it), so keep the strong pull-up engaged
1314 : : // while the command is transmitted. This feeds the slave through the
1315 : : // command phase; the conversion window below re-asserts it anyway.
1316 [ + + ]: 162 : if (ctx.parasite) {
1317 : 30 : onewire_strong_pullup(1);
1318 : : }
1319 : 162 : issue_command(DS18B20_CONVERT_T, DS18B20_ST_WAIT);
1320 : 162 : break;
1321 : :
1322 : 126 : case DS18B20_ST_WAIT:
1323 : : // Parasite power: the sensors draw their supply from the bus line
1324 : : // during the whole conversion, so drive the line HIGH actively before
1325 : : // the wait starts (engaging here and starting the timer in the same
1326 : : // transition keeps wait and supply aligned regardless of poll latency).
1327 [ + + ]: 126 : if (ctx.parasite) {
1328 : 24 : onewire_strong_pullup(1);
1329 : : }
1330 : : // Start timer for the conversion wait (93.75ms @ 9-bit .. 750ms @ 12-bit)
1331 : : wait_conversion();
1332 : 126 : ctx.current_state = DS18B20_ST_CONTINUE;
1333 : 126 : break;
1334 : :
1335 : 144 : case DS18B20_ST_CONTINUE:
1336 : : // Release the strong pull-up BEFORE the reset pulse pulls the line
1337 : : // low: the conversion is complete, the devices no longer need the
1338 : : // parasite supply and the bus must be free again.
1339 : 144 : onewire_strong_pullup(0);
1340 : : // Initiate second 1-Wire bus reset sequence
1341 : 144 : onewire_reset(ctx.capture);
1342 : 144 : ctx.current_state = DS18B20_ST_REQUEST;
1343 : 144 : break;
1344 : :
1345 : 138 : case DS18B20_ST_REQUEST:
1346 [ + + ]: 138 : if (ctx.scan_mode) {
1347 : : // Scan mode: read the current device back via Match ROM.
1348 [ + + ]: 486 : for (uint8_t i = 0; i < DS18B20_ROM_BYTES; i++) {
1349 : 432 : ctx.selected_rom[i] = dev_roms[ctx.scan_index][i];
1350 : : }
1351 : : build_addr_prefix();
1352 : 54 : ctx.address_mode = 1;
1353 : : }
1354 [ + + ]: 138 : if (ctx.parasite) {
1355 : 18 : onewire_strong_pullup(1);
1356 : : }
1357 : 138 : issue_command(DS18B20_READ_SCRATCHPAD, DS18B20_ST_READ);
1358 : 138 : break;
1359 : :
1360 : 126 : case DS18B20_ST_READ:
1361 [ + + ]: 126 : if (ctx.parasite) {
1362 : 18 : onewire_strong_pullup(0);
1363 : : }
1364 : 126 : onewire_read_data(ctx.pulse, DS18B20_SCRATCHPAD_LEN);
1365 : 126 : ctx.current_state = DS18B20_ST_DECODE;
1366 : 126 : break;
1367 : :
1368 : 180 : case DS18B20_ST_DECODE: // Process received data and report temperature
1369 : : /* Snapshot pulse widths before decode_scratchpad() overwrites them
1370 : : * via the union alias (scratchpad[n] == pulse[n]). */
1371 : 180 : ow_stats_capture_pulse(ctx.pulse, DS18B20_SCRATCHPAD_BITS,
1372 [ + + ]: 180 : ctx.address_mode ? ctx.selected_rom : (const uint8_t*)0);
1373 : : // Decode captured pulse durations into scratchpad bytes
1374 : : decode_scratchpad();
1375 : : // Turn off LED to indicate measurement complete
1376 : 180 : ds18b20_busy(0);
1377 : :
1378 : : // In single-device mode the callback runs at IDLE, so a re-selection
1379 : : // from inside ds18b20_complete() is accepted there. Scan mode keeps its
1380 : : // own per-device addressing and stays in DECODE: a select() from the
1381 : : // scan callback is rejected, and it reports every device before
1382 : : // returning to IDLE at the round end.
1383 [ + + ]: 180 : if (!ctx.scan_mode) {
1384 : 132 : ctx.current_state = DS18B20_ST_IDLE;
1385 : : }
1386 : :
1387 : : // Match ROM mode: if the addressed device is absent, nobody drives
1388 : : // the bus after the address, so the whole scratchpad reads back as
1389 : : // 0xFF. Report it as a missing sensor instead of a bogus CRC error.
1390 [ + + ]: 180 : if (ctx.address_mode) {
1391 : 72 : uint8_t all_ones = 1;
1392 [ + + ]: 180 : for (uint8_t i = 0; i < DS18B20_SCRATCHPAD_LEN; i++) {
1393 [ + + ]: 168 : if (ctx.scratchpad[i] != 0xFF) {
1394 : 60 : all_ones = 0;
1395 : 60 : break;
1396 : : }
1397 : : }
1398 [ + + ]: 72 : if (all_ones) {
1399 : 12 : ds18b20_complete(DS18B20_TEMP_ERROR_NO_SENSOR);
1400 : 12 : ow_stats_count_error(DS18B20_TEMP_ERROR_NO_SENSOR,
1401 : : ctx.selected_rom);
1402 : 12 : scan_finish_or_next();
1403 : 12 : break;
1404 : : }
1405 : : }
1406 : :
1407 : : // Validate reserved bytes per DS18B20 specification:
1408 : : // Byte 5 must be 0xFF, Byte 7 must be 0x10.
1409 : : // This catches all-zero, all-0xFF, and bus fault conditions.
1410 [ + + - + ]: 168 : if (ctx.scratchpad[5] != 0xFF || ctx.scratchpad[7] != 0x10) {
1411 : 12 : ds18b20_complete(DS18B20_TEMP_ERROR_CRC_FAIL);
1412 : 12 : ow_stats_count_error(DS18B20_TEMP_ERROR_CRC_FAIL,
1413 : : ctx.selected_rom);
1414 : 12 : scan_finish_or_next();
1415 : 12 : break;
1416 : : }
1417 : :
1418 : : // Validate CRC and report temperature or error
1419 [ + + ]: 312 : if (ctx.scratchpad[DS18B20_SCRATCHPAD_LEN - 1] == check_scratchpad_crc()) {
1420 : : // CRC valid - decode and report temperature. The scratchpad is
1421 : : // trustworthy, so also trust the config byte (byte 4, R1/R0 bits
1422 : : // 6:5) and adapt the conversion wait for the next cycle: this keeps
1423 : : // the wait in sync with a resolution changed via
1424 : : // ds18b20_set_resolution() or externally. (R1/R0 are 0..3, so the
1425 : : // derived value is always within DS18B20_RES_MIN..DS18B20_RES_MAX.)
1426 : : // It is derived only on a valid CRC so a corrupted config byte can
1427 : : // never shorten the next conversion wait prematurely.
1428 [ - + ]: 114 : ctx.resolution = DS18B20_RES_MIN + ((ctx.scratchpad[4] >> 5) & 0x3);
1429 : 114 : ds18b20_complete(decode_temperature());
1430 : : } else {
1431 : : // CRC invalid - report error (resolution kept unchanged)
1432 : 42 : ds18b20_complete(DS18B20_TEMP_ERROR_CRC_FAIL);
1433 : 42 : ow_stats_count_error(DS18B20_TEMP_ERROR_CRC_FAIL,
1434 : : ctx.selected_rom);
1435 : : }
1436 : :
1437 : : // Next scan-mode device (CONTINUE, no fresh conversion) or, after the
1438 : : // last device, back to IDLE plus the inter-measurement pause. In
1439 : : // single-device mode this only starts the pause.
1440 : 156 : scan_finish_or_next();
1441 : 156 : break;
1442 : :
1443 : 6 : default:
1444 : : // Unexpected state - report generic error
1445 : 6 : ctx.current_state = DS18B20_ST_IDLE;
1446 : 6 : ds18b20_complete(DS18B20_TEMP_ERROR_GENERIC);
1447 : 6 : ow_stats_count_error(DS18B20_TEMP_ERROR_GENERIC,
1448 : : (const uint8_t*)0);
1449 : 6 : break;
1450 : : }
1451 : : }
1452 : :
1453 : : /**
1454 : : * @}
1455 : : */
|