mirror of
https://github.com/embassy-rs/embassy.git
synced 2025-09-27 04:10:25 +00:00
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.
This commit is contained in:
parent
be794533d3
commit
8f10e3638d
@ -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
|
||||
|
||||
|
@ -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,7 @@ 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;
|
||||
@ -155,8 +173,29 @@ impl<'d, PIO: Instance, const SM: usize> PioOneWire<'d, PIO, SM> {
|
||||
}
|
||||
}
|
||||
|
||||
/// 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;
|
||||
|
||||
// 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
|
||||
|
@ -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]
|
||||
|
89
examples/rp/src/bin/pio_onewire_parasite.rs
Normal file
89
examples/rp/src/bin/pio_onewire_parasite.rs
Normal file
@ -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<PIO0>;
|
||||
});
|
||||
|
||||
#[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::<u64, 10>::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
|
||||
}
|
Loading…
x
Reference in New Issue
Block a user