LCOV - code coverage report
Current view: top level - src - ds18b20.c (source / functions) Coverage Total Hit
Test: stm32-async-1wire core coverage Lines: 98.2 % 440 432
Test Date: 2026-09-10 04:33:53 Functions: 95.1 % 41 39
Branches: 94.8 % 249 236

             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                 :             :  */
        

Generated by: LCOV version 2.0-1