From 8f10e3638d77cadf058b9083de09fc7189048b0b Mon Sep 17 00:00:00 2001 From: Matt Johnston Date: Sun, 14 Sep 2025 16:30:31 +0800 Subject: rp/pio: Add onewire strong pullups, parasite power DS18B20 sensors require a strong pullup to be applied for the duration of the temperature conversion, within 10us of the command. The rp2xxx pins have sufficient drive strength to use as the pullup (no external mosfet needed). Add a new write_bytes_pullup() that will apply the pullup after bytes are written. Existing read_bytes()/write_bytes() has no change to onewire timing. A pio_onewire_parasite example reads multiple sensors individually, applying the strong pullup. --- embassy-rp/CHANGELOG.md | 1 + embassy-rp/src/pio_programs/onewire.rs | 45 ++++++++++++++- examples/rp/src/bin/pio_onewire.rs | 1 + examples/rp/src/bin/pio_onewire_parasite.rs | 89 +++++++++++++++++++++++++++++ 4 files changed, 133 insertions(+), 3 deletions(-) create mode 100644 examples/rp/src/bin/pio_onewire_parasite.rs diff --git a/embassy-rp/CHANGELOG.md b/embassy-rp/CHANGELOG.md index b50d41dd1..841c9f068 100644 --- a/embassy-rp/CHANGELOG.md +++ b/embassy-rp/CHANGELOG.md @@ -10,6 +10,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - Add PIO SPI - Add PIO I2S input +- Add PIO onewire parasite power strong pullup ## 0.8.0 - 2025-08-26 diff --git a/embassy-rp/src/pio_programs/onewire.rs b/embassy-rp/src/pio_programs/onewire.rs index 287ddab41..980d0fe5f 100644 --- a/embassy-rp/src/pio_programs/onewire.rs +++ b/embassy-rp/src/pio_programs/onewire.rs @@ -52,7 +52,8 @@ impl<'a, PIO: Instance> PioOneWireProgram<'a, PIO> { ; The low pulse was already done, we only need to delay and poll the bit in case we are reading write_1: - nop side 0 [( 6 / CLK) - 1] ; Delay before sampling the input pin + jmp y--, continue_1 side 0 [( 6 / CLK) - 1] ; Delay before sampling input. Always decrement y + continue_1: in pins, 1 side 0 [(48 / CLK) - 1] ; This writes the state of the pin into the ISR ; Fallthrough @@ -61,9 +62,24 @@ impl<'a, PIO: Instance> PioOneWireProgram<'a, PIO> { .wrap_target out x, 1 side 0 [(12 / CLK) - 1] ; Stalls if no data available in TX FIFO and OSR jmp x--, write_1 side 1 [( 6 / CLK) - 1] ; Do the always low part of a bit, jump to write_1 if we want to write a 1 bit - in null, 1 side 1 [(54 / CLK) - 1] ; Do the remainder of the low part of a 0 bit - ; This writes 0 into the ISR so that the shift count stays in sync + jmp y--, continue_0 side 1 [(48 / CLK) - 1] ; Do the remainder of the low part of a 0 bit + jmp pullup side 1 [( 6 / CLK) - 1] ; Remain low while jumping + continue_0: + in null, 1 side 1 [( 6 / CLK) - 1] ; This writes 0 into the ISR so that the shift count stays in sync .wrap + + ; Assume that strong pullup commands always have MSB (the last bit) = 0, + ; since the rising edge can be used to start the operation. + ; That's the case for DS18B20 (44h and 48h). + pullup: + set pins, 1 side 1[( 6 / CLK) - 1] ; Drive pin high output immediately. + ; Strong pullup must be within 10us of rise. + in null, 1 side 1[( 6 / CLK) - 1] ; Keep ISR in sync. Must occur after the y--. + out null, 8 side 1[( 6 / CLK) - 1] ; Wait for write_bytes_pullup() delay to complete. + ; The delay is hundreds of ms, so done externally. + set pins, 0 side 0[( 6 / CLK) - 1] ; Back to open drain, pin low when driven + in null, 8 side 1[( 6 / CLK) - 1] ; Inform write_bytes_pullup() it's ready + jmp next_bit side 0[( 6 / CLK) - 1] ; Continue "# ); @@ -98,6 +114,7 @@ impl<'d, PIO: Instance, const SM: usize> PioOneWire<'d, PIO, SM> { let mut cfg = Config::default(); cfg.use_program(&program.prg, &[&pin]); cfg.set_in_pins(&[&pin]); + cfg.set_set_pins(&[&pin]); let shift_cfg = ShiftConfig { auto_fill: true, @@ -146,6 +163,19 @@ impl<'d, PIO: Instance, const SM: usize> PioOneWire<'d, PIO, SM> { /// Write bytes to the onewire bus pub async fn write_bytes(&mut self, data: &[u8]) { + unsafe { self.sm.set_y(u32::MAX as u32) }; + let (rx, tx) = self.sm.rx_tx(); + for b in data { + tx.wait_push(*b as u32).await; + + // Empty the buffer that is being filled with every write + let _ = rx.wait_pull().await; + } + } + + /// Write bytes to the onewire bus, then apply a strong pullup + pub async fn write_bytes_pullup(&mut self, data: &[u8], pullup_time: embassy_time::Duration) { + unsafe { self.sm.set_y(data.len() as u32 * 8 - 1) }; let (rx, tx) = self.sm.rx_tx(); for b in data { tx.wait_push(*b as u32).await; @@ -153,10 +183,19 @@ impl<'d, PIO: Instance, const SM: usize> PioOneWire<'d, PIO, SM> { // Empty the buffer that is being filled with every write let _ = rx.wait_pull().await; } + + // Perform the delay, usually hundreds of ms. + embassy_time::Timer::after(pullup_time).await; + + // Signal that delay has completed + tx.wait_push(0 as u32).await; + // Wait until it's back at 0 low, open drain + let _ = rx.wait_pull().await; } /// Read bytes from the onewire bus pub async fn read_bytes(&mut self, data: &mut [u8]) { + unsafe { self.sm.set_y(u32::MAX as u32) }; let (rx, tx) = self.sm.rx_tx(); for b in data { // Write all 1's so that we can read what the device responds diff --git a/examples/rp/src/bin/pio_onewire.rs b/examples/rp/src/bin/pio_onewire.rs index 379e2b8f9..102f13c45 100644 --- a/examples/rp/src/bin/pio_onewire.rs +++ b/examples/rp/src/bin/pio_onewire.rs @@ -1,4 +1,5 @@ //! This example shows how you can use PIO to read one or more `DS18B20` one-wire temperature sensors. +//! This uses externally powered sensors. For parasite power, see the pio_onewire_parasite.rs example. #![no_std] #![no_main] diff --git a/examples/rp/src/bin/pio_onewire_parasite.rs b/examples/rp/src/bin/pio_onewire_parasite.rs new file mode 100644 index 000000000..fd076dee0 --- /dev/null +++ b/examples/rp/src/bin/pio_onewire_parasite.rs @@ -0,0 +1,89 @@ +//! This example shows how you can use PIO to read one or more `DS18B20` +//! one-wire temperature sensors using parasite power. +//! It applies a strong pullup during conversion, see "Powering the DS18B20" in the datasheet. +//! For externally powered sensors, use the pio_onewire.rs example. + +#![no_std] +#![no_main] +use defmt::*; +use embassy_executor::Spawner; +use embassy_rp::bind_interrupts; +use embassy_rp::peripherals::PIO0; +use embassy_rp::pio::{InterruptHandler, Pio}; +use embassy_rp::pio_programs::onewire::{PioOneWire, PioOneWireProgram, PioOneWireSearch}; +use embassy_time::Duration; +use heapless::Vec; +use {defmt_rtt as _, panic_probe as _}; + +bind_interrupts!(struct Irqs { + PIO0_IRQ_0 => InterruptHandler; +}); + +#[embassy_executor::main] +async fn main(_spawner: Spawner) { + let p = embassy_rp::init(Default::default()); + let mut pio = Pio::new(p.PIO0, Irqs); + + let prg = PioOneWireProgram::new(&mut pio.common); + let mut onewire = PioOneWire::new(&mut pio.common, pio.sm0, p.PIN_2, &prg); + + info!("Starting onewire search"); + + let mut devices = Vec::::new(); + let mut search = PioOneWireSearch::new(); + for _ in 0..10 { + if !search.is_finished() { + if let Some(address) = search.next(&mut onewire).await { + if crc8(&address.to_le_bytes()) == 0 { + info!("Found address: {:x}", address); + let _ = devices.push(address); + } else { + warn!("Found invalid address: {:x}", address); + } + } + } + } + + info!("Search done, found {} devices", devices.len()); + + loop { + // Read all devices one by one + for device in &devices { + onewire.reset().await; + onewire.write_bytes(&[0x55]).await; // Match rom + onewire.write_bytes(&device.to_le_bytes()).await; + // 750 ms delay required for default 12-bit resolution. + onewire.write_bytes_pullup(&[0x44], Duration::from_millis(750)).await; + + onewire.reset().await; + onewire.write_bytes(&[0x55]).await; // Match rom + onewire.write_bytes(&device.to_le_bytes()).await; + onewire.write_bytes(&[0xBE]).await; // Read scratchpad + + let mut data = [0; 9]; + onewire.read_bytes(&mut data).await; + if crc8(&data) == 0 { + let temp = ((data[1] as u32) << 8 | data[0] as u32) as f32 / 16.; + info!("Read device {:x}: {} deg C", device, temp); + } else { + warn!("Reading device {:x} failed. {:02x}", device, data); + } + } + } +} + +fn crc8(data: &[u8]) -> u8 { + let mut crc = 0; + for b in data { + let mut data_byte = *b; + for _ in 0..8 { + let temp = (crc ^ data_byte) & 0x01; + crc >>= 1; + if temp != 0 { + crc ^= 0x8C; + } + data_byte >>= 1; + } + } + crc +} -- cgit From 7c551b4fdfd7fcf410423355a3a1b3f92d5f65a6 Mon Sep 17 00:00:00 2001 From: Matt Johnston Date: Sun, 14 Sep 2025 16:38:20 +0800 Subject: rp/pio: Copy onewire examples from rp to rp235x The rp pio_onewire example was updated on cd27a8a06b0160d654ebed7b89ca473041710235 but not rp235x. Copy them to be the same. --- examples/rp235x/src/bin/pio_onewire.rs | 103 +++++++++++++----------- examples/rp235x/src/bin/pio_onewire_parasite.rs | 89 ++++++++++++++++++++ 2 files changed, 143 insertions(+), 49 deletions(-) create mode 100644 examples/rp235x/src/bin/pio_onewire_parasite.rs diff --git a/examples/rp235x/src/bin/pio_onewire.rs b/examples/rp235x/src/bin/pio_onewire.rs index 991510851..102f13c45 100644 --- a/examples/rp235x/src/bin/pio_onewire.rs +++ b/examples/rp235x/src/bin/pio_onewire.rs @@ -1,4 +1,5 @@ -//! This example shows how you can use PIO to read a `DS18B20` one-wire temperature sensor. +//! This example shows how you can use PIO to read one or more `DS18B20` one-wire temperature sensors. +//! This uses externally powered sensors. For parasite power, see the pio_onewire_parasite.rs example. #![no_std] #![no_main] @@ -6,9 +7,10 @@ use defmt::*; use embassy_executor::Spawner; use embassy_rp::bind_interrupts; use embassy_rp::peripherals::PIO0; -use embassy_rp::pio::{self, InterruptHandler, Pio}; -use embassy_rp::pio_programs::onewire::{PioOneWire, PioOneWireProgram}; +use embassy_rp::pio::{InterruptHandler, Pio}; +use embassy_rp::pio_programs::onewire::{PioOneWire, PioOneWireProgram, PioOneWireSearch}; use embassy_time::Timer; +use heapless::Vec; use {defmt_rtt as _, panic_probe as _}; bind_interrupts!(struct Irqs { @@ -21,63 +23,66 @@ async fn main(_spawner: Spawner) { let mut pio = Pio::new(p.PIO0, Irqs); let prg = PioOneWireProgram::new(&mut pio.common); - let onewire = PioOneWire::new(&mut pio.common, pio.sm0, p.PIN_2, &prg); + let mut onewire = PioOneWire::new(&mut pio.common, pio.sm0, p.PIN_2, &prg); - let mut sensor = Ds18b20::new(onewire); + info!("Starting onewire search"); - loop { - sensor.start().await; // Start a new measurement - Timer::after_secs(1).await; // Allow 1s for the measurement to finish - match sensor.temperature().await { - Ok(temp) => info!("temp = {:?} deg C", temp), - _ => error!("sensor error"), + let mut devices = Vec::::new(); + let mut search = PioOneWireSearch::new(); + for _ in 0..10 { + if !search.is_finished() { + if let Some(address) = search.next(&mut onewire).await { + if crc8(&address.to_le_bytes()) == 0 { + info!("Found addres: {:x}", address); + let _ = devices.push(address); + } else { + warn!("Found invalid address: {:x}", address); + } + } } - Timer::after_secs(1).await; } -} -/// DS18B20 temperature sensor driver -pub struct Ds18b20<'d, PIO: pio::Instance, const SM: usize> { - wire: PioOneWire<'d, PIO, SM>, -} + info!("Search done, found {} devices", devices.len()); -impl<'d, PIO: pio::Instance, const SM: usize> Ds18b20<'d, PIO, SM> { - pub fn new(wire: PioOneWire<'d, PIO, SM>) -> Self { - Self { wire } - } + loop { + onewire.reset().await; + // Skip rom and trigger conversion, we can trigger all devices on the bus immediately + onewire.write_bytes(&[0xCC, 0x44]).await; - /// Calculate CRC8 of the data - fn crc8(data: &[u8]) -> u8 { - let mut temp; - let mut data_byte; - let mut crc = 0; - for b in data { - data_byte = *b; - for _ in 0..8 { - temp = (crc ^ data_byte) & 0x01; - crc >>= 1; - if temp != 0 { - crc ^= 0x8C; - } - data_byte >>= 1; + Timer::after_secs(1).await; // Allow 1s for the measurement to finish + + // Read all devices one by one + for device in &devices { + onewire.reset().await; + onewire.write_bytes(&[0x55]).await; // Match rom + onewire.write_bytes(&device.to_le_bytes()).await; + onewire.write_bytes(&[0xBE]).await; // Read scratchpad + + let mut data = [0; 9]; + onewire.read_bytes(&mut data).await; + if crc8(&data) == 0 { + let temp = ((data[1] as u32) << 8 | data[0] as u32) as f32 / 16.; + info!("Read device {:x}: {} deg C", device, temp); + } else { + warn!("Reading device {:x} failed", device); } } - crc - } - - /// Start a new measurement. Allow at least 1000ms before getting `temperature`. - pub async fn start(&mut self) { - self.wire.write_bytes(&[0xCC, 0x44]).await; + Timer::after_secs(1).await; } +} - /// Read the temperature. Ensure >1000ms has passed since `start` before calling this. - pub async fn temperature(&mut self) -> Result { - self.wire.write_bytes(&[0xCC, 0xBE]).await; - let mut data = [0; 9]; - self.wire.read_bytes(&mut data).await; - match Self::crc8(&data) == 0 { - true => Ok(((data[1] as u32) << 8 | data[0] as u32) as f32 / 16.), - false => Err(()), +fn crc8(data: &[u8]) -> u8 { + let mut crc = 0; + for b in data { + let mut data_byte = *b; + for _ in 0..8 { + let temp = (crc ^ data_byte) & 0x01; + crc >>= 1; + if temp != 0 { + crc ^= 0x8C; + } + data_byte >>= 1; } } + crc } diff --git a/examples/rp235x/src/bin/pio_onewire_parasite.rs b/examples/rp235x/src/bin/pio_onewire_parasite.rs new file mode 100644 index 000000000..fd076dee0 --- /dev/null +++ b/examples/rp235x/src/bin/pio_onewire_parasite.rs @@ -0,0 +1,89 @@ +//! This example shows how you can use PIO to read one or more `DS18B20` +//! one-wire temperature sensors using parasite power. +//! It applies a strong pullup during conversion, see "Powering the DS18B20" in the datasheet. +//! For externally powered sensors, use the pio_onewire.rs example. + +#![no_std] +#![no_main] +use defmt::*; +use embassy_executor::Spawner; +use embassy_rp::bind_interrupts; +use embassy_rp::peripherals::PIO0; +use embassy_rp::pio::{InterruptHandler, Pio}; +use embassy_rp::pio_programs::onewire::{PioOneWire, PioOneWireProgram, PioOneWireSearch}; +use embassy_time::Duration; +use heapless::Vec; +use {defmt_rtt as _, panic_probe as _}; + +bind_interrupts!(struct Irqs { + PIO0_IRQ_0 => InterruptHandler; +}); + +#[embassy_executor::main] +async fn main(_spawner: Spawner) { + let p = embassy_rp::init(Default::default()); + let mut pio = Pio::new(p.PIO0, Irqs); + + let prg = PioOneWireProgram::new(&mut pio.common); + let mut onewire = PioOneWire::new(&mut pio.common, pio.sm0, p.PIN_2, &prg); + + info!("Starting onewire search"); + + let mut devices = Vec::::new(); + let mut search = PioOneWireSearch::new(); + for _ in 0..10 { + if !search.is_finished() { + if let Some(address) = search.next(&mut onewire).await { + if crc8(&address.to_le_bytes()) == 0 { + info!("Found address: {:x}", address); + let _ = devices.push(address); + } else { + warn!("Found invalid address: {:x}", address); + } + } + } + } + + info!("Search done, found {} devices", devices.len()); + + loop { + // Read all devices one by one + for device in &devices { + onewire.reset().await; + onewire.write_bytes(&[0x55]).await; // Match rom + onewire.write_bytes(&device.to_le_bytes()).await; + // 750 ms delay required for default 12-bit resolution. + onewire.write_bytes_pullup(&[0x44], Duration::from_millis(750)).await; + + onewire.reset().await; + onewire.write_bytes(&[0x55]).await; // Match rom + onewire.write_bytes(&device.to_le_bytes()).await; + onewire.write_bytes(&[0xBE]).await; // Read scratchpad + + let mut data = [0; 9]; + onewire.read_bytes(&mut data).await; + if crc8(&data) == 0 { + let temp = ((data[1] as u32) << 8 | data[0] as u32) as f32 / 16.; + info!("Read device {:x}: {} deg C", device, temp); + } else { + warn!("Reading device {:x} failed. {:02x}", device, data); + } + } + } +} + +fn crc8(data: &[u8]) -> u8 { + let mut crc = 0; + for b in data { + let mut data_byte = *b; + for _ in 0..8 { + let temp = (crc ^ data_byte) & 0x01; + crc >>= 1; + if temp != 0 { + crc ^= 0x8C; + } + data_byte >>= 1; + } + } + crc +} -- cgit