Branch data Line data Source code
1 : : /**
2 : : * @file onewire.h
3 : : * @brief Universal non-blocking 1-Wire bus layer for STM32
4 : : * @details The driver (ds18b20.c) and any other 1-Wire slave driver (DS2413,
5 : : * DS2431, ...) are built on top of this layer. Every operation is
6 : : * scheduled on TIM1/DMA and completes asynchronously: callers poll
7 : : * onewire_bus_done() / onewire_search_poll() to advance, never wait.
8 : : */
9 : :
10 : : #ifndef ONEWIRE_H
11 : : #define ONEWIRE_H
12 : :
13 : : #include <stdint.h>
14 : :
15 : : /**
16 : : * @defgroup ONEWIRE_Protocol 1-Wire Protocol Constants
17 : : * @{
18 : : */
19 : :
20 : : /** @brief Bytes in a device ROM address */
21 : : #define ONEWIRE_ROM_BYTES 8
22 : : /** @brief Bits in a device ROM address */
23 : : #define ONEWIRE_ROM_BITS (ONEWIRE_ROM_BYTES * 8)
24 : : /** @brief Bits per byte */
25 : : #define ONEWIRE_BITS_PER_BYTE 8
26 : : /** @brief Family selection: a single OW_PORT_FAMILY_* token resolved from
27 : : * either the explicit OW_PORT_TARGET_* knob or the family macros
28 : : * (STM32F1, STM32F0, STM32G0) that PlatformIO / STM32CubeMX define on their
29 : : * own. ow_port.h picks the backend and app.c the device header/clock config
30 : : * from the token — never from the individual spellings — so the backend and
31 : : * the clock default cannot drift. To add a family, extend this chain (token
32 : : * and default clock together in one branch), then add the #include branch in
33 : : * ow_port.h, the app.c config, and a case in tests/test/test_sysclk_fallback.c. */
34 : : #if defined(OW_PORT_TARGET_F1) || defined(STM32F1)
35 : : #define OW_PORT_FAMILY_F1
36 : : #elif defined(OW_PORT_TARGET_F0) || defined(STM32F0)
37 : : #define OW_PORT_FAMILY_F0
38 : : #elif defined(OW_PORT_TARGET_G0) || defined(STM32G0)
39 : : #define OW_PORT_FAMILY_G0
40 : : #endif
41 : : /** @brief System clock frequency in MHz after application clock setup.
42 : : * Single source of truth for the clock-dependent settings: the timer
43 : : * prescaler (1µs ticks) and the input-capture filter selection below
44 : : * derive from it; the bit-slot durations (ONEWIRE_ONE_PULSE and friends)
45 : : * are fixed constants validated on every supported clock. Family defaults
46 : : * are provided per OW_PORT_FAMILY_* above; override via
47 : : * -DOWN_PORT_SYSCLK_MHZ=N (see app.c for the clock sources available
48 : : * per family). */
49 : : #if !defined(OW_PORT_SYSCLK_MHZ)
50 : : #if defined(OW_PORT_FAMILY_F1)
51 : : #define OW_PORT_SYSCLK_MHZ 72 /* STM32F103: HSE + PLL x9 */
52 : : #elif defined(OW_PORT_FAMILY_F0)
53 : : #define OW_PORT_SYSCLK_MHZ 48 /* STM32F030: HSI/2 + PLL x12 */
54 : : #elif defined(OW_PORT_FAMILY_G0)
55 : : #define OW_PORT_SYSCLK_MHZ 64 /* STM32G031: HSI16 + PLL */
56 : : #endif
57 : : #endif
58 : : /** @brief Opt-in low-power WFE sleep: when defined, long hardware stages
59 : : * (> 1 ms: conversion, scratchpad read, EEPROM hold-off, inter-cycle pause)
60 : : * enable the TIM1 update interrupt (UIE) and SEVONPEND so that a pending
61 : : * update event wakes the core from WFE without an ISR; short stages stay fully
62 : : * polled. Long stages can sleep with ow_port_sleep_until_done(). Disabled by
63 : : * default so non-low-power builds pay zero cost. Enable with -DOW_PORT_LOW_POWER.
64 : : * @note No ISR is ever installed and NVIC_EnableIRQ is never called; the
65 : : * pending bit is cleared explicitly in ow_port_bus_done() so WFE does
66 : : * not degrade into a busy-loop. */
67 : : #ifdef OW_PORT_LOW_POWER
68 : : #if defined(OW_PORT_TARGET_F0) || defined(OW_PORT_TARGET_G0)
69 : : #define OW_PORT_TIM1_UPD_IRQn TIM1_BRK_UP_TRG_COM_IRQn
70 : : #else
71 : : #define OW_PORT_TIM1_UPD_IRQn TIM1_UP_IRQn
72 : : #endif
73 : : #endif
74 : : /** @brief Duration of a '1' bit write/read pulse in microseconds.
75 : : * A single universal value for every clock: DS18B20 requires only ≥1µs and
76 : : * samples the slot at ≥15µs after its start, and the read-slot capture
77 : : * latency (bus RC rise + input filter + timer sync) stays far below the
78 : : * ONEWIRE_SHORT_PULSE_MAX window on all supported clocks. Releases v1.6.0's
79 : : * ≤16MHz compensation (2µs pulse): hardware on STM32F030@8MHz showed that a
80 : : * 2µs master pulse breaks the sensor's slot decoding outright — every
81 : : * capture stretches past the threshold regardless of the answer — while a
82 : : * plain 5µs pulse measures ~9µs there with every input-filter variant
83 : : * swept (fCK_INT N=2/4/8 and fDTS/4 N=8). The short-pulse path was tuned on
84 : : * F103@8MHz bench wiring whose slower rise is not reproduced by other
85 : : * boards; re-validate per board before reintroducing anything similar.
86 : : * @note Hardware-validated at 5µs on every supported clock:
87 : : * STM32F030@48/8MHz, STM32F103@72/8MHz and STM32G031@64/16MHz. */
88 : : #define ONEWIRE_ONE_PULSE 5
89 : : #define ONEWIRE_ZERO_PULSE 60
90 : : #define ONEWIRE_GUARD_BAND 5
91 : : #define ONEWIRE_SHORT_PULSE_MAX 10
92 : :
93 : : typedef enum {
94 : : ONEWIRE_TIMING_FAST = 0,
95 : : ONEWIRE_TIMING_STANDARD,
96 : : ONEWIRE_TIMING_SLOW,
97 : : ONEWIRE_TIMING_ROBUST,
98 : : ONEWIRE_TIMING_CUSTOM,
99 : : ONEWIRE_TIMING_COUNT
100 : : } onewire_timing_profile_t;
101 : :
102 : : typedef struct {
103 : : uint8_t one_pulse;
104 : : uint8_t zero_pulse;
105 : : uint8_t guard_band;
106 : : uint8_t parasite_guard_band;
107 : : uint8_t short_pulse_max;
108 : : } onewire_timing_t;
109 : :
110 : : #ifndef OW_TIMING_DEFAULT
111 : : #define OW_TIMING_DEFAULT ONEWIRE_TIMING_STANDARD
112 : : #endif
113 : : #ifndef ONEWIRE_TIMING_PROFILE_DEFAULT
114 : : #define ONEWIRE_TIMING_PROFILE_DEFAULT OW_TIMING_DEFAULT
115 : : #endif
116 : :
117 : : extern uint8_t ow_one_pulse_us;
118 : : extern uint8_t ow_zero_pulse_us;
119 : : extern uint8_t ow_guard_band_us;
120 : : extern uint8_t ow_short_pulse_max_us;
121 : : void ow_set_parasite_guard(uint8_t parasite);
122 : : void onewire_set_timing_profile(onewire_timing_profile_t profile);
123 : : onewire_timing_profile_t onewire_get_timing_profile(void);
124 : :
125 : : /** @} */
126 : :
127 : : /**
128 : : * @defgroup ONEWIRE_Init Init
129 : : * @{
130 : : */
131 : :
132 : : /**
133 : : * @brief Initialize the shared 1-Wire timer/DMA/GPIO resources
134 : : * @note Enables GPIOA/TIM1/DMA1 clocks, sets the timer prescaler for 1µs
135 : : * resolution, configures PA10 as alternate-function open-drain and marks
136 : : * the search engine idle. Called once at startup, e.g. by the slave
137 : : * driver's own init.
138 : : */
139 : : void onewire_init(void);
140 : :
141 : : /** @} */
142 : :
143 : : /**
144 : : * @defgroup ONEWIRE_Bus Non-Blocking 1-Wire Bus Primitives
145 : : * @brief Each primitive schedules exactly one hardware-timed operation on
146 : : * TIM1/DMA and returns immediately. Poll onewire_bus_done() to learn
147 : : * when the operation finished, then decode the result.
148 : : * @{
149 : : */
150 : :
151 : : /**
152 : : * @brief Non-blocking completion check for the scheduled bus operation
153 : : * @return 1 if finished (update flag cleared), 0 while still running
154 : : */
155 : : uint8_t onewire_bus_done(void);
156 : :
157 : : /**
158 : : * @brief Schedule a 1-Wire bus reset (presence pulse captured via DMA)
159 : : * @param[out] reset_pulses Buffer for the captured reset + presence pulse
160 : : * durations (2 x 16-bit)
161 : : * @note On completion, decode the presence pulse with onewire_present().
162 : : */
163 : : void onewire_reset(volatile uint16_t* reset_pulses);
164 : :
165 : : /**
166 : : * @brief Decode the presence pulse captured by onewire_reset()
167 : : * @param[in] pulses Reset + presence pulse durations captured by onewire_reset()
168 : : * @return 1 if at least one device answered, 0 otherwise
169 : : */
170 : : uint8_t onewire_present(const volatile uint16_t* pulses);
171 : :
172 : : /**
173 : : * @brief Schedule a write of `slots` bit slots
174 : : * @param[in] pulses Pulse buffer (one entry per slot); for `slots > 1` the
175 : : * entry at index `slots` must be 0 (hardware bus release)
176 : : * @param[in] slots Number of bit slots to transmit
177 : : * @note Non-blocking: the DMA feeds CCR3 from the buffer asynchronously, so
178 : : * the buffer must stay valid until onewire_bus_done() reports completion.
179 : : */
180 : : void onewire_write_slots(const uint8_t* pulses, uint16_t slots);
181 : :
182 : : /**
183 : : * @brief Schedule a single-slot write of one raw bit
184 : : * @param[in] bit Bit value to write (0 or 1)
185 : : */
186 : : void onewire_write_bit(uint8_t bit);
187 : :
188 : : /**
189 : : * @brief Schedule a two-slot read of a Search ROM id/cmp bit pair
190 : : * @param[out] pair_pulses Buffer for the captured pulse durations (2 × 16-bit)
191 : : * @note On completion, decode the pair with onewire_pair_bits().
192 : : */
193 : : void onewire_read_pair(volatile uint16_t* pair_pulses);
194 : :
195 : : /**
196 : : * @brief Decode the id/cmp bits of a two-slot read pair
197 : : * @param[in] pair_pulses Pulses captured by onewire_read_pair()
198 : : * @param[out] id_bit Id bit (0 or 1)
199 : : * @param[out] cmp_bit Complement bit (0 or 1)
200 : : */
201 : : void onewire_pair_bits(const volatile uint16_t* pair_pulses, uint8_t* id_bit, uint8_t* cmp_bit);
202 : :
203 : : /**
204 : : * @brief Schedule a merged single-slot write followed by a two-slot read pair
205 : : * @param[in] bit Direction bit to write in slot 1 (0 or 1)
206 : : * @note One timer pass runs three slots: a write of `bit`, then a read of the
207 : : * next id/cmp pair. Halves the timer passes per search bit compared to a
208 : : * plain write plus a separate read pair. On completion, the internal
209 : : * merged buffer holds [write-slot capture, id pulse, cmp pulse]: decode the
210 : : * pair from entries 1 and 2 with onewire_bit_from_pulse(). Do not pass
211 : : * this buffer to onewire_pair_bits(), which instead decodes entries 0 and
212 : : * 1 as produced by onewire_read_pair().
213 : : */
214 : : void onewire_write_then_read(uint8_t bit);
215 : :
216 : : /**
217 : : * @brief Schedule a read of `bytes` bytes from the bus
218 : : * @param[out] dst Buffer for the captured pulse durations (bytes × 8 × 8-bit)
219 : : * @param[in] bytes Number of bytes to read
220 : : * @note On completion, the caller decodes the 8-bit pulse durations (pulse
221 : : * `<= ONEWIRE_SHORT_PULSE_MAX` reads as bit 1) into data bytes.
222 : : */
223 : : void onewire_read_data(volatile uint8_t* dst, uint8_t bytes);
224 : :
225 : : /**
226 : : * @brief Decode a single captured pulse duration into a 1-Wire bit
227 : : * @param[in] dur Pulse duration in microseconds
228 : : * @return 1 if the pulse is short (bit value '1'), 0 if long (bit value '0')
229 : : * @note A pulse duration `<= ONEWIRE_SHORT_PULSE_MAX` is the short ('1') slot.
230 : : */
231 : 50988 : static inline uint8_t onewire_bit_from_pulse(uint16_t dur) {
232 [ + + ]: 50988 : return (dur <= ow_short_pulse_max_us) ? 1u : 0u;
233 : : }
234 : :
235 : : /**
236 : : * @brief Decode captured pulse durations into data bytes (LSB-first bits)
237 : : * @param[out] dst Decoded bytes
238 : : * @param[in] pulse Captured per-bit pulse durations (one entry per bit)
239 : : * @param[in] nbytes Number of bytes to decode (pulse must hold nbytes × 8 entries)
240 : : * @note Each bit is recovered with onewire_bit_from_pulse(); bit 0 maps to the
241 : : * LSB of its byte. Recovers scratchpad / register bytes from the 1-Wire
242 : : * read capture.
243 : : */
244 : : void onewire_decode_pulses(uint8_t* dst, const volatile uint8_t* pulse, uint8_t nbytes);
245 : :
246 : : /**
247 : : * @brief Encode a byte into write-pulse durations
248 : : * @param[out] out Output buffer (8 entries)
249 : : * @param[in] byte Byte value to encode
250 : : */
251 : : void onewire_encode_byte(uint8_t* out, uint8_t byte);
252 : :
253 : : /**
254 : : * @brief Start a hardware-timed wait with the shared one-pulse timer
255 : : * @param[in] arr Auto-reload value (one timer period in µs)
256 : : * @param[in] rcr Repetition counter (number of periods - 1)
257 : : * @note The timer update event fires after (RCR + 1) × ARR microseconds; used
258 : : * for DS18B20 conversion waits and inter-measurement pauses.
259 : : */
260 : : void onewire_start_timer(uint16_t arr, uint8_t rcr);
261 : :
262 : : /**
263 : : * @brief Engage or release the parasite-power strong pull-up on the bus
264 : : * @param[in] on 1 drives the bus line HIGH actively (pin leaves the timer-
265 : : * driven open-drain mode for a push-pull HIGH), 0 releases the
266 : : * line back to the passive external pull-up
267 : : * @note Parasite-powered devices source their supply from the bus line and
268 : : * need this active pull-up during energy-intensive phases: the whole
269 : : * temperature conversion (tCONV, up to 750 ms) after Convert T and the
270 : : * EEPROM programming window (tPROG) after Copy Scratchpad / Recall E².
271 : : * Must only be called between hardware operations (bus idle), never
272 : : * while a timed transaction is running.
273 : : */
274 : : void onewire_strong_pullup(uint8_t on);
275 : :
276 : : /** @} */
277 : :
278 : : /**
279 : : * @defgroup ONEWIRE_Search Generic Non-Blocking Search ROM Engine
280 : : * @brief Implements the Maxim Search ROM (0xF0) / Alarm Search (0xEC)
281 : : * algorithm as a polled state machine. The direction bit written at
282 : : * position i determines which devices keep participating at position
283 : : * i+1, so the transaction cannot be batched into one DMA pass; the
284 : : * linear loop is decomposed into states with loop counters kept in the
285 : : * context. Every state performs exactly one hardware-timed operation,
286 : : * so a poll call never blocks.
287 : : * @{
288 : : */
289 : :
290 : : /** @brief Per-device callback invoked by the search engine */
291 : : typedef uint8_t (*onewire_search_sink_t)(const uint8_t* rom);
292 : :
293 : : /**
294 : : * @brief Start a non-blocking search (Search ROM or Alarm Search)
295 : : * @param[in] sink Callback invoked per found device (may be NULL). A non-zero
296 : : * return value stops the search early.
297 : : * @param[in] max_devices Maximum number of devices to report (0 aborts)
298 : : * @param[in] command Search command byte (0xF0 Search ROM / 0xEC Alarm Search)
299 : : * @param[in] family 1-Wire family code to accept, or 0 to accept every family.
300 : : * Only accepted devices increment the found counter and
301 : : * reach the sink.
302 : : * @note Ignores the call while a search pass is already running. Arbitration
303 : : * of the shared bus/timer against other 1-Wire operations lives in the
304 : : * ds18b20_* layer, not here.
305 : : */
306 : : void onewire_search_start(onewire_search_sink_t sink, uint8_t max_devices,
307 : : uint8_t command, uint8_t family);
308 : :
309 : : /**
310 : : * @brief Advance the non-blocking search by one hardware operation
311 : : * @return 1 when the search is finished, 0 while still running
312 : : */
313 : : uint8_t onewire_search_poll(void);
314 : :
315 : : /**
316 : : * @brief Number of devices found (valid once the search finished)
317 : : * @return Count of found devices
318 : : */
319 : : uint8_t onewire_search_count(void);
320 : :
321 : : /**
322 : : * @brief Check whether a search is currently running
323 : : * @return 1 while the search owns the timer, 0 when it is idle
324 : : */
325 : : uint8_t onewire_search_active(void);
326 : :
327 : : /** @} */
328 : :
329 : : /**
330 : : * @defgroup ONEWIRE_CRC CRC-8
331 : : * @{
332 : : */
333 : :
334 : : /**
335 : : * @brief Calculate the Dallas/Maxim 1-Wire CRC-8 over a byte buffer
336 : : * @param[in] data Input buffer
337 : : * @param[in] len Number of bytes to process
338 : : * @return CRC-8 checksum value
339 : : */
340 : : uint8_t onewire_crc8(const uint8_t* data, uint8_t len);
341 : :
342 : : /** @} */
343 : :
344 : : #ifdef DS18B20_TEST_HARNESS
345 : : /**
346 : : * @brief [TEST] Set the idle-HIGH gap injected between search slots
347 : : * @param[in] us Gap duration in microseconds (0 disables the injection)
348 : : * @note Temporary test hook for the RTOS-latency experiment only.
349 : : */
350 : : void onewire_test_set_gap_us(uint16_t us);
351 : : #endif
352 : :
353 : : #endif /* ONEWIRE_H */
|