m5stack_core/driver/onewire.rs
1// SPDX-License-Identifier: MIT OR Apache-2.0
2//! 1-Wire master over the ESP32 RMT peripheral (async).
3//!
4//! Vendored from the `esp-hal-rmt-onewire` crate (v0.4.0) by **jonored**
5//! — <https://github.com/jonored/esp-hal-rmt-onewire> — dual-licensed
6//! MIT OR Apache-2.0 (its `LICENSE-MIT` reads "Copyright 2021 esp-rs").
7//! Original authorship and copyright are retained, with thanks to the author.
8//!
9//! Adapted for in-tree use: updated for the esp-hal 1.1 RMT API, and the
10//! const-generic `exchange_bits` reworked to a slice form (dropping the
11//! `generic_const_exprs` nightly feature) — modified from the original.
12
13use core::fmt::LowerHex;
14use embassy_futures::join::join;
15use embassy_futures::select::*;
16use embassy_time::{Duration, Timer};
17use esp_hal::rmt::{Rx, Tx};
18use esp_hal::{
19 Async,
20 gpio::{
21 DriveMode, DriveStrength, Flex, InputConfig, Level, OutputConfig, Pin, Pull,
22 interconnect::*,
23 },
24 rmt::{
25 Channel, ConfigError, PulseCode, RxChannelConfig, RxChannelCreator, TxChannelConfig,
26 TxChannelCreator,
27 },
28};
29use thiserror_no_std::Error as ThisError;
30
31// --- RMT timing -------------------------------------------------------------
32// The RMT is clocked at 80 MHz (see `Rmt::new` in the DS18B20 driver) and every
33// channel here uses a clock divider of 80, giving exactly one tick per
34// microsecond. Consequently all pulse lengths in this module are in µs.
35/// RMT clock divider: 80 MHz / 80 = 1 MHz ⇒ 1 µs per tick.
36const RMT_CLK_DIVIDER: u8 = 80;
37/// RX end-of-frame: capture stops once the bus stays idle this long (µs). Well
38/// above a single slot (~73 µs) but below the 480 µs reset, so the idle tail of
39/// each transaction terminates the capture.
40const RX_IDLE_THRESHOLD: u16 = 1000;
41/// RX glitch filter (source-clock cycles): shorter pulses are rejected as edge
42/// noise on slot transitions.
43const RX_FILTER_THRESHOLD: u8 = 10;
44
45/// Async 1-Wire bus master built on a pair of RMT TX/RX channels.
46pub struct OneWire<'a> {
47 rx: Channel<'a, Async, Rx>,
48 tx: Channel<'a, Async, Tx>,
49 input: InputSignal<'a>,
50}
51
52impl<'a> OneWire<'a> {
53 /// Create a 1-Wire master driving `pin` (open-drain, pull-up) using the
54 /// supplied RMT TX and RX channel creators.
55 pub fn new<Txc: TxChannelCreator<'a, Async>, Rxc: RxChannelCreator<'a, Async>, P: Pin + 'a>(
56 txcc: Txc,
57 rxcc: Rxc,
58 pin: P,
59 ) -> Result<Self, Error> {
60 let rx_config = RxChannelConfig::default()
61 .with_clk_divider(RMT_CLK_DIVIDER)
62 .with_idle_threshold(RX_IDLE_THRESHOLD)
63 .with_filter_threshold(RX_FILTER_THRESHOLD)
64 .with_carrier_modulation(false);
65 let tx_config = TxChannelConfig::default()
66 .with_clk_divider(RMT_CLK_DIVIDER)
67 .with_carrier_modulation(false);
68
69 // Open-drain with an internal pull-up: a 1-Wire master only ever drives
70 // the bus low or releases it. An external 4.7 kΩ pull-up is still
71 // required for reliable edges with multiple devices / long cabling.
72 let mut pin: Flex = Flex::new(pin);
73
74 pin.apply_input_config(&InputConfig::default().with_pull(Pull::Up));
75 pin.apply_output_config(
76 &OutputConfig::default()
77 .with_drive_mode(DriveMode::OpenDrain)
78 .with_drive_strength(DriveStrength::_40mA),
79 );
80 pin.set_input_enable(true);
81 pin.set_output_enable(true);
82 let (input, output) = pin.split();
83
84 // The RMT idles its output high, which on an open-drain pad would *drive*
85 // the bus. Invert TX so an RMT `High` symbol pulls the bus LOW (an active
86 // 1-Wire drive) and a `Low` symbol releases it to the pull-up; invert RX
87 // to match, so a captured `length1` measures the bus-low duration.
88 let tx = txcc.configure_tx(&tx_config)?.with_pin(output.with_output_inverter(true));
89 let rx = rxcc
90 .configure_rx(&rx_config)?
91 .with_pin(input.clone().with_input_inverter(true));
92
93 Ok(OneWire { rx, tx, input })
94 }
95}
96
97impl<'a> OneWire<'a> {
98 /// Issue a 1-Wire reset pulse and return `true` if at least one device
99 /// responded with a presence pulse.
100 pub async fn reset(&mut self) -> Result<bool, Error> {
101 // Reset/presence sequence (DS18B20 datasheet "RESET PROCEDURE"). Recall
102 // the TX inverter: an RMT `Low` symbol releases the bus (pull-up high), a
103 // `High` symbol drives it low. So this is: 60 µs released lead-in, 600 µs
104 // reset low (≥480 µs required), then 600 µs released during which any
105 // present device asserts its presence pulse.
106 let data = [
107 PulseCode::new(Level::Low, 60, Level::High, 600),
108 PulseCode::new(Level::Low, 600, Level::Low, 0),
109 PulseCode::end_marker(),
110 ];
111 let mut indata = [PulseCode::end_marker(); 10];
112
113 let _res = self.send_and_receive(&mut indata, &data).await?;
114
115 // A present device pulls the bus low for 60–240 µs after the reset is
116 // released. Require both edges of the first captured symbol plus a
117 // second-symbol low inside a 100–200 µs window (within the presence
118 // spec, margined against noise).
119 Ok(indata[0].length1() > 0
120 && indata[0].length2() > 0
121 && indata[1].length1() > 100
122 && indata[1].length1() < 200)
123 }
124
125 /// Upper bound on a single bus transaction before the RX channel is treated
126 /// as stuck. Every DS18B20 reset/slot completes well under this; the long
127 /// temperature-conversion wait is handled by the caller, not here.
128 const RX_TIMEOUT: Duration = Duration::from_millis(10);
129
130 /// Transmit `data` while simultaneously sampling the bus into `indata`,
131 /// returning the number of received RMT symbols.
132 pub async fn send_and_receive(
133 &mut self,
134 indata: &mut [PulseCode],
135 data: &[PulseCode],
136 ) -> Result<usize, Error> {
137 if self.input.level() == Level::Low {
138 Err(Error::InputNotHigh)?;
139 }
140 // The master drives the bus (TX) while sampling it (RX) in the same time
141 // slots, so both must run concurrently: `join` arms RX first, then drives
142 // TX, and both complete once the line returns idle. A separate software
143 // timer bounds the wait so a stuck (never-idle) bus cannot hang forever —
144 // replacing the original TX-pulse timeout hack.
145 match select(
146 join(self.rx.receive(indata), self.tx.transmit(data)),
147 Timer::after(Self::RX_TIMEOUT),
148 )
149 .await
150 {
151 Either::First((rx_res, tx_res)) => {
152 tx_res.map_err(Error::SendError)?;
153 rx_res.map_err(Error::ReceiveError)
154 }
155 Either::Second(()) => Err(Error::ReceiveTimedOut),
156 }
157 }
158
159 /// Bus-low duration (µs) for a written '0' — a 1-Wire write-0 holds the line
160 /// low for 60–120 µs; 70 µs sits in that window.
161 const ZERO_BIT_LEN: u16 = 70;
162 /// Bus-low duration (µs) for a written '1' / read slot — a brief 1–15 µs low
163 /// that opens the slot before the line is released to be sampled.
164 const ONE_BIT_LEN: u16 = 3;
165 /// A read slot reads '1' when the captured bus-low is shorter than this many
166 /// µs (only the master's own opening pulse); a device signalling '0' holds
167 /// the line low well past it.
168 const READ_SAMPLE_THRESHOLD: u16 = 20;
169
170 /// Encode a single 1-Wire bit as an RMT pulse code (write/read time slot).
171 pub fn encode_bit(bit: bool) -> PulseCode {
172 if bit {
173 PulseCode::new(
174 Level::High,
175 Self::ONE_BIT_LEN,
176 Level::Low,
177 Self::ZERO_BIT_LEN,
178 )
179 } else {
180 PulseCode::new(
181 Level::High,
182 Self::ZERO_BIT_LEN,
183 Level::Low,
184 Self::ONE_BIT_LEN,
185 )
186 }
187 }
188
189 /// Decode a sampled RMT pulse code back into the 1-Wire bit value.
190 pub fn decode_bit(code: PulseCode) -> bool {
191 code.length1() < Self::READ_SAMPLE_THRESHOLD
192 }
193
194 /// Write one byte (LSB first) and read the byte the bus returns in the
195 /// same time slots.
196 pub async fn exchange_byte(&mut self, byte: u8) -> Result<u8, Error> {
197 // 8 bit-slots followed by a trailing end marker; sized 10 for headroom.
198 let mut data = [PulseCode::end_marker(); 10];
199 let mut indata = [PulseCode::end_marker(); 10];
200 for (n, slot) in data.iter_mut().take(8).enumerate() {
201 *slot = Self::encode_bit(byte & (1 << n) != 0);
202 }
203 let _res = self.send_and_receive(&mut indata, &data).await?;
204 let mut res: u8 = 0;
205 for (n, &code) in indata.iter().take(8).enumerate() {
206 if Self::decode_bit(code) {
207 res |= 1 << n;
208 }
209 }
210 Ok(res)
211 }
212
213 /// Write one byte (LSB first) without reading the response.
214 pub async fn send_byte(&mut self, byte: u8) -> Result<(), Error> {
215 let mut data = [PulseCode::end_marker(); 10];
216 for (n, slot) in data.iter_mut().take(8).enumerate() {
217 *slot = Self::encode_bit(byte & (1 << n) != 0);
218 }
219 self.tx.transmit(&data).await?;
220 Ok(())
221 }
222
223 /// Maximum number of bits a single [`OneWire::exchange_bits`] call accepts.
224 const MAX_EXCHANGE_BITS: usize = 8;
225
226 /// Write the bits in `bits` and read the bus response into `out` in the
227 /// same time slots. `bits` and `out` must have equal length, and at most
228 /// [`Self::MAX_EXCHANGE_BITS`] elements.
229 ///
230 /// This replaces the original const-generic `exchange_bits<const N>` (which
231 /// required `generic_const_exprs`) with a slice-based API backed by a small
232 /// fixed-capacity buffer. The RMT framing is identical: each bit maps to one
233 /// `PulseCode` followed by a trailing `end_marker()`.
234 pub async fn exchange_bits(&mut self, bits: &[bool], out: &mut [bool]) -> Result<(), Error> {
235 debug_assert_eq!(bits.len(), out.len());
236 debug_assert!(bits.len() <= Self::MAX_EXCHANGE_BITS);
237 let n_bits = bits.len();
238
239 // One PulseCode per bit, plus a trailing end_marker (pre-filled).
240 let mut data = [PulseCode::end_marker(); Self::MAX_EXCHANGE_BITS + 1];
241 let mut indata = [PulseCode::end_marker(); Self::MAX_EXCHANGE_BITS + 1];
242 for n in 0..n_bits {
243 data[n] = Self::encode_bit(bits[n]);
244 }
245 let _res = self
246 .send_and_receive(&mut indata[..n_bits + 1], &data[..n_bits + 1])
247 .await?;
248 for n in 0..n_bits {
249 out[n] = Self::decode_bit(indata[n]);
250 }
251 Ok(())
252 }
253
254 /// Write a 64-bit value (typically a ROM address) least-significant byte
255 /// first.
256 pub async fn send_u64(&mut self, val: u64) -> Result<(), Error> {
257 for byte in val.to_le_bytes() {
258 self.send_byte(byte).await?;
259 }
260 Ok(())
261 }
262
263 /// Write a 64-bit 1-Wire ROM [`Address`].
264 pub async fn send_address(&mut self, val: Address) -> Result<(), Error> {
265 self.send_u64(val.0).await
266 }
267}
268
269/// Errors returned by 1-Wire bus operations.
270#[derive(Debug, ThisError)]
271pub enum Error {
272 /// The bus was not idle-high before a transaction (missing pull-up?).
273 #[error("1-Wire bus was not idle-high before a transaction (missing pull-up?)")]
274 InputNotHigh,
275 /// No presence/response was sampled before the RX timeout elapsed.
276 #[error("no 1-Wire response sampled before the RX timeout")]
277 ReceiveTimedOut,
278 /// The RMT RX channel reported an error.
279 #[error("RMT RX channel error: {0:?}")]
280 ReceiveError(esp_hal::rmt::Error),
281 /// The RMT TX channel reported an error.
282 #[error("RMT TX channel error: {0:?}")]
283 SendError(esp_hal::rmt::Error),
284 /// An RMT channel could not be configured.
285 #[error("RMT channel configuration error: {0:?}")]
286 ConfigError(#[from] ConfigError),
287}
288
289// Two variants wrap `rmt::Error` (RX vs TX), so `#[from]` would be ambiguous;
290// a bare bus error is conventionally a send-side failure.
291impl From<esp_hal::rmt::Error> for Error {
292 fn from(e: esp_hal::rmt::Error) -> Error {
293 Error::SendError(e)
294 }
295}
296
297/// Dallas/Maxim 1-Wire CRC-8 (polynomial X^8 + X^5 + X^4 + 1, reflected 0x8C).
298///
299/// Used to validate ROM addresses (byte 7 covers bytes 0..7) and DS18B20
300/// scratchpad reads (byte 8 covers bytes 0..8). A correct frame CRCs to 0 when
301/// the trailing CRC byte is included; this helper returns the CRC of `data`, so
302/// callers compare it against the received CRC byte. See Maxim app note 27.
303pub fn crc8(data: &[u8]) -> u8 {
304 let mut crc = 0u8;
305 for &byte in data {
306 let mut b = byte;
307 for _ in 0..8 {
308 let mix = (crc ^ b) & 0x01;
309 crc >>= 1;
310 if mix != 0 {
311 crc ^= 0x8C;
312 }
313 b >>= 1;
314 }
315 }
316 crc
317}
318
319/// A 64-bit 1-Wire ROM address (family code, serial, CRC).
320#[derive(PartialEq, Eq, Clone, Copy, Hash)]
321pub struct Address(pub u64);
322
323// All three formats render the ROM byte-wise, family code first (the LSB of the
324// underlying u64), zero-padded so a leading-zero byte is never ambiguous.
325impl LowerHex for Address {
326 fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
327 for byte in self.0.to_le_bytes() {
328 core::write!(f, "{:02x}", byte)?;
329 }
330 Ok(())
331 }
332}
333
334impl core::fmt::Display for Address {
335 fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
336 for (i, byte) in self.0.to_le_bytes().iter().enumerate() {
337 if i > 0 {
338 core::write!(f, ":")?;
339 }
340 core::write!(f, "{:02X}", byte)?;
341 }
342 Ok(())
343 }
344}
345
346impl core::fmt::Debug for Address {
347 fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
348 core::write!(f, "Address({})", self)
349 }
350}
351
352/// State machine for the 1-Wire ROM Search algorithm, enumerating every device
353/// address on the bus across repeated [`Search::next`] calls.
354pub struct Search {
355 command: u8,
356 address: u64,
357 #[cfg(feature = "search-masks")]
358 address_mask: u64,
359 last_discrepancy: Option<usize>,
360 complete: bool,
361}
362
363/// Errors returned while iterating a ROM [`Search`].
364#[derive(Debug, ThisError)]
365pub enum SearchError {
366 /// All devices have already been enumerated.
367 #[error("all devices have been enumerated")]
368 SearchComplete,
369 /// No device responded to the search.
370 #[error("no device responded to the search")]
371 NoDevicesPresent,
372 /// The enumerated ROM address failed its CRC-8 check (bus glitch). The
373 /// search state has still advanced, so a subsequent [`Search::next`] call
374 /// continues enumeration past the corrupt address.
375 #[error("enumerated ROM address failed its CRC-8 check")]
376 CrcMismatch,
377 /// An underlying bus error occurred.
378 #[error("1-Wire bus error during search: {0}")]
379 BusError(#[from] Error),
380}
381
382impl Search {
383 /// Start a normal (0xF0) ROM search over all devices on the bus.
384 pub fn new() -> Search {
385 Search {
386 command: 0xF0,
387 address: 0,
388 #[cfg(feature = "search-masks")]
389 address_mask: 0,
390 last_discrepancy: None,
391 complete: false,
392 }
393 }
394
395 /// Start an alarm (0xEC) search, enumerating only devices in an alarm state.
396 pub fn new_alarm() -> Search {
397 Search {
398 command: 0xEC,
399 address: 0,
400 #[cfg(feature = "search-masks")]
401 address_mask: 0,
402 last_discrepancy: None,
403 complete: false,
404 }
405 }
406
407 /// Start a search constrained to addresses matching `fixed_bits` under
408 /// `bit_mask`.
409 #[cfg(feature = "search-masks")]
410 pub fn new_with_mask(fixed_bits: u64, bit_mask: u64) -> Search {
411 Search {
412 command: 0xEC,
413 address: fixed_bits,
414 address_mask: bit_mask,
415 last_discrepancy: None,
416 complete: false,
417 }
418 }
419
420 /// Advance the search and return the next device [`Address`], or a
421 /// [`SearchError`] once enumeration finishes or the bus errors.
422 pub async fn next<'d>(&mut self, ow: &mut OneWire<'d>) -> Result<Address, SearchError> {
423 if self.complete {
424 return Err(SearchError::SearchComplete);
425 }
426 let have_devices = ow.reset().await?;
427 let mut last_zero = None;
428 ow.send_byte(self.command).await?;
429 if have_devices {
430 for id_bit_number in 0..64 {
431 let mut id_bits = [false; 2];
432 ow.exchange_bits(&[true, true], &mut id_bits).await?;
433 let search_direction = match id_bits {
434 #[cfg(feature = "search-masks")]
435 _ if self.address_mask & (1 << id_bit_number) != 0 => {
436 self.address & (1 << id_bit_number) != 0
437 }
438 [false, true] => false,
439 [true, false] => true,
440 [true, true] => {
441 return Err(SearchError::NoDevicesPresent);
442 }
443 [false, false] => {
444 if self.last_discrepancy == Some(id_bit_number) {
445 true
446 } else if Some(id_bit_number) > self.last_discrepancy {
447 last_zero = Some(id_bit_number);
448 false
449 } else {
450 self.address & (1 << id_bit_number) != 0
451 }
452 }
453 };
454 if search_direction {
455 self.address |= 1 << id_bit_number;
456 } else {
457 self.address &= !(1 << id_bit_number);
458 }
459 let mut sent = [false; 1];
460 ow.exchange_bits(&[search_direction], &mut sent).await?;
461 }
462 self.last_discrepancy = last_zero;
463 self.complete = last_zero.is_none();
464 // Byte 7 of the ROM is a CRC-8 over the family code + 48-bit serial
465 // (bytes 0..7). Reject a corrupt enumeration rather than handing the
466 // caller a bogus address; enumeration state has already advanced.
467 let rom = self.address.to_le_bytes();
468 if crc8(&rom[..7]) != rom[7] {
469 return Err(SearchError::CrcMismatch);
470 }
471 Ok(Address(self.address))
472 } else {
473 Err(SearchError::NoDevicesPresent)
474 }
475 }
476}
477
478impl Default for Search {
479 fn default() -> Self {
480 Self::new()
481 }
482}