aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorMatt Johnston <[email protected]>2025-09-14 16:30:31 +0800
committerMatt Johnston <[email protected]>2025-09-14 16:54:14 +0800
commit8f10e3638d77cadf058b9083de09fc7189048b0b (patch)
tree804739dd75306154351460b0ee4dde51a3170caa
parentbe794533d3929e316c65b4296de47292ae0eae67 (diff)
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.
-rw-r--r--embassy-rp/CHANGELOG.md1
-rw-r--r--embassy-rp/src/pio_programs/onewire.rs45
-rw-r--r--examples/rp/src/bin/pio_onewire.rs1
-rw-r--r--examples/rp/src/bin/pio_onewire_parasite.rs89
4 files changed, 133 insertions, 3 deletions
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
10 10
11- Add PIO SPI 11- Add PIO SPI
12- Add PIO I2S input 12- Add PIO I2S input
13- Add PIO onewire parasite power strong pullup
13 14
14## 0.8.0 - 2025-08-26 15## 0.8.0 - 2025-08-26
15 16
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> {
52 52
53 ; The low pulse was already done, we only need to delay and poll the bit in case we are reading 53 ; The low pulse was already done, we only need to delay and poll the bit in case we are reading
54 write_1: 54 write_1:
55 nop side 0 [( 6 / CLK) - 1] ; Delay before sampling the input pin 55 jmp y--, continue_1 side 0 [( 6 / CLK) - 1] ; Delay before sampling input. Always decrement y
56 continue_1:
56 in pins, 1 side 0 [(48 / CLK) - 1] ; This writes the state of the pin into the ISR 57 in pins, 1 side 0 [(48 / CLK) - 1] ; This writes the state of the pin into the ISR
57 ; Fallthrough 58 ; Fallthrough
58 59
@@ -61,9 +62,24 @@ impl<'a, PIO: Instance> PioOneWireProgram<'a, PIO> {
61 .wrap_target 62 .wrap_target
62 out x, 1 side 0 [(12 / CLK) - 1] ; Stalls if no data available in TX FIFO and OSR 63 out x, 1 side 0 [(12 / CLK) - 1] ; Stalls if no data available in TX FIFO and OSR
63 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 64 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
64 in null, 1 side 1 [(54 / CLK) - 1] ; Do the remainder of the low part of a 0 bit 65 jmp y--, continue_0 side 1 [(48 / CLK) - 1] ; Do the remainder of the low part of a 0 bit
65 ; This writes 0 into the ISR so that the shift count stays in sync 66 jmp pullup side 1 [( 6 / CLK) - 1] ; Remain low while jumping
67 continue_0:
68 in null, 1 side 1 [( 6 / CLK) - 1] ; This writes 0 into the ISR so that the shift count stays in sync
66 .wrap 69 .wrap
70
71 ; Assume that strong pullup commands always have MSB (the last bit) = 0,
72 ; since the rising edge can be used to start the operation.
73 ; That's the case for DS18B20 (44h and 48h).
74 pullup:
75 set pins, 1 side 1[( 6 / CLK) - 1] ; Drive pin high output immediately.
76 ; Strong pullup must be within 10us of rise.
77 in null, 1 side 1[( 6 / CLK) - 1] ; Keep ISR in sync. Must occur after the y--.
78 out null, 8 side 1[( 6 / CLK) - 1] ; Wait for write_bytes_pullup() delay to complete.
79 ; The delay is hundreds of ms, so done externally.
80 set pins, 0 side 0[( 6 / CLK) - 1] ; Back to open drain, pin low when driven
81 in null, 8 side 1[( 6 / CLK) - 1] ; Inform write_bytes_pullup() it's ready
82 jmp next_bit side 0[( 6 / CLK) - 1] ; Continue
67 "# 83 "#
68 ); 84 );
69 85
@@ -98,6 +114,7 @@ impl<'d, PIO: Instance, const SM: usize> PioOneWire<'d, PIO, SM> {
98 let mut cfg = Config::default(); 114 let mut cfg = Config::default();
99 cfg.use_program(&program.prg, &[&pin]); 115 cfg.use_program(&program.prg, &[&pin]);
100 cfg.set_in_pins(&[&pin]); 116 cfg.set_in_pins(&[&pin]);
117 cfg.set_set_pins(&[&pin]);
101 118
102 let shift_cfg = ShiftConfig { 119 let shift_cfg = ShiftConfig {
103 auto_fill: true, 120 auto_fill: true,
@@ -146,6 +163,19 @@ impl<'d, PIO: Instance, const SM: usize> PioOneWire<'d, PIO, SM> {
146 163
147 /// Write bytes to the onewire bus 164 /// Write bytes to the onewire bus
148 pub async fn write_bytes(&mut self, data: &[u8]) { 165 pub async fn write_bytes(&mut self, data: &[u8]) {
166 unsafe { self.sm.set_y(u32::MAX as u32) };
167 let (rx, tx) = self.sm.rx_tx();
168 for b in data {
169 tx.wait_push(*b as u32).await;
170
171 // Empty the buffer that is being filled with every write
172 let _ = rx.wait_pull().await;
173 }
174 }
175
176 /// Write bytes to the onewire bus, then apply a strong pullup
177 pub async fn write_bytes_pullup(&mut self, data: &[u8], pullup_time: embassy_time::Duration) {
178 unsafe { self.sm.set_y(data.len() as u32 * 8 - 1) };
149 let (rx, tx) = self.sm.rx_tx(); 179 let (rx, tx) = self.sm.rx_tx();
150 for b in data { 180 for b in data {
151 tx.wait_push(*b as u32).await; 181 tx.wait_push(*b as u32).await;
@@ -153,10 +183,19 @@ impl<'d, PIO: Instance, const SM: usize> PioOneWire<'d, PIO, SM> {
153 // Empty the buffer that is being filled with every write 183 // Empty the buffer that is being filled with every write
154 let _ = rx.wait_pull().await; 184 let _ = rx.wait_pull().await;
155 } 185 }
186
187 // Perform the delay, usually hundreds of ms.
188 embassy_time::Timer::after(pullup_time).await;
189
190 // Signal that delay has completed
191 tx.wait_push(0 as u32).await;
192 // Wait until it's back at 0 low, open drain
193 let _ = rx.wait_pull().await;
156 } 194 }
157 195
158 /// Read bytes from the onewire bus 196 /// Read bytes from the onewire bus
159 pub async fn read_bytes(&mut self, data: &mut [u8]) { 197 pub async fn read_bytes(&mut self, data: &mut [u8]) {
198 unsafe { self.sm.set_y(u32::MAX as u32) };
160 let (rx, tx) = self.sm.rx_tx(); 199 let (rx, tx) = self.sm.rx_tx();
161 for b in data { 200 for b in data {
162 // Write all 1's so that we can read what the device responds 201 // 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 @@
1//! This example shows how you can use PIO to read one or more `DS18B20` one-wire temperature sensors. 1//! This example shows how you can use PIO to read one or more `DS18B20` one-wire temperature sensors.
2//! This uses externally powered sensors. For parasite power, see the pio_onewire_parasite.rs example.
2 3
3#![no_std] 4#![no_std]
4#![no_main] 5#![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 @@
1//! This example shows how you can use PIO to read one or more `DS18B20`
2//! one-wire temperature sensors using parasite power.
3//! It applies a strong pullup during conversion, see "Powering the DS18B20" in the datasheet.
4//! For externally powered sensors, use the pio_onewire.rs example.
5
6#![no_std]
7#![no_main]
8use defmt::*;
9use embassy_executor::Spawner;
10use embassy_rp::bind_interrupts;
11use embassy_rp::peripherals::PIO0;
12use embassy_rp::pio::{InterruptHandler, Pio};
13use embassy_rp::pio_programs::onewire::{PioOneWire, PioOneWireProgram, PioOneWireSearch};
14use embassy_time::Duration;
15use heapless::Vec;
16use {defmt_rtt as _, panic_probe as _};
17
18bind_interrupts!(struct Irqs {
19 PIO0_IRQ_0 => InterruptHandler<PIO0>;
20});
21
22#[embassy_executor::main]
23async fn main(_spawner: Spawner) {
24 let p = embassy_rp::init(Default::default());
25 let mut pio = Pio::new(p.PIO0, Irqs);
26
27 let prg = PioOneWireProgram::new(&mut pio.common);
28 let mut onewire = PioOneWire::new(&mut pio.common, pio.sm0, p.PIN_2, &prg);
29
30 info!("Starting onewire search");
31
32 let mut devices = Vec::<u64, 10>::new();
33 let mut search = PioOneWireSearch::new();
34 for _ in 0..10 {
35 if !search.is_finished() {
36 if let Some(address) = search.next(&mut onewire).await {
37 if crc8(&address.to_le_bytes()) == 0 {
38 info!("Found address: {:x}", address);
39 let _ = devices.push(address);
40 } else {
41 warn!("Found invalid address: {:x}", address);
42 }
43 }
44 }
45 }
46
47 info!("Search done, found {} devices", devices.len());
48
49 loop {
50 // Read all devices one by one
51 for device in &devices {
52 onewire.reset().await;
53 onewire.write_bytes(&[0x55]).await; // Match rom
54 onewire.write_bytes(&device.to_le_bytes()).await;
55 // 750 ms delay required for default 12-bit resolution.
56 onewire.write_bytes_pullup(&[0x44], Duration::from_millis(750)).await;
57
58 onewire.reset().await;
59 onewire.write_bytes(&[0x55]).await; // Match rom
60 onewire.write_bytes(&device.to_le_bytes()).await;
61 onewire.write_bytes(&[0xBE]).await; // Read scratchpad
62
63 let mut data = [0; 9];
64 onewire.read_bytes(&mut data).await;
65 if crc8(&data) == 0 {
66 let temp = ((data[1] as u32) << 8 | data[0] as u32) as f32 / 16.;
67 info!("Read device {:x}: {} deg C", device, temp);
68 } else {
69 warn!("Reading device {:x} failed. {:02x}", device, data);
70 }
71 }
72 }
73}
74
75fn crc8(data: &[u8]) -> u8 {
76 let mut crc = 0;
77 for b in data {
78 let mut data_byte = *b;
79 for _ in 0..8 {
80 let temp = (crc ^ data_byte) & 0x01;
81 crc >>= 1;
82 if temp != 0 {
83 crc ^= 0x8C;
84 }
85 data_byte >>= 1;
86 }
87 }
88 crc
89}