Skip to main content

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}