aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorJames Munns <[email protected]>2025-04-25 10:25:29 +0000
committerGitHub <[email protected]>2025-04-25 10:25:29 +0000
commit52e8979d7ca96bcc294196249fd4a7f69e452e31 (patch)
treea8164dd43acb6dd6c5084eff104b270b20659b4a
parenta687fb20f65e383376d87ccc5772d96cc78a3a9d (diff)
parent5d8b0e0327955039d58542ee2036744e155561e6 (diff)
Merge pull request #4128 from marcemmers/pio-onewire-refactor
[embassy-rp] Rewrite PIO onewire implementation
-rw-r--r--embassy-rp/src/pio_programs/onewire.rs312
-rw-r--r--examples/rp/src/bin/pio_onewire.rs102
2 files changed, 302 insertions, 112 deletions
diff --git a/embassy-rp/src/pio_programs/onewire.rs b/embassy-rp/src/pio_programs/onewire.rs
index 00783aab0..287ddab41 100644
--- a/embassy-rp/src/pio_programs/onewire.rs
+++ b/embassy-rp/src/pio_programs/onewire.rs
@@ -1,11 +1,17 @@
1//! OneWire pio driver 1//! OneWire pio driver
2 2
3use crate::pio::{Common, Config, Instance, LoadedProgram, PioPin, ShiftConfig, ShiftDirection, StateMachine}; 3use crate::clocks::clk_sys_freq;
4use crate::gpio::Level;
5use crate::pio::{
6 Common, Config, Direction, Instance, LoadedProgram, PioPin, ShiftConfig, ShiftDirection, StateMachine,
7};
4use crate::Peri; 8use crate::Peri;
5 9
6/// This struct represents an onewire driver program 10/// This struct represents a onewire driver program
7pub struct PioOneWireProgram<'a, PIO: Instance> { 11pub struct PioOneWireProgram<'a, PIO: Instance> {
8 prg: LoadedProgram<'a, PIO>, 12 prg: LoadedProgram<'a, PIO>,
13 reset_addr: u8,
14 next_bit_addr: u8,
9} 15}
10 16
11impl<'a, PIO: Instance> PioOneWireProgram<'a, PIO> { 17impl<'a, PIO: Instance> PioOneWireProgram<'a, PIO> {
@@ -13,56 +19,67 @@ impl<'a, PIO: Instance> PioOneWireProgram<'a, PIO> {
13 pub fn new(common: &mut Common<'a, PIO>) -> Self { 19 pub fn new(common: &mut Common<'a, PIO>) -> Self {
14 let prg = pio::pio_asm!( 20 let prg = pio::pio_asm!(
15 r#" 21 r#"
16 .wrap_target 22 ; We need to use the pins direction to simulate open drain output
17 again: 23 ; This results in all the side-set values being swapped from the actual pin value
18 pull block 24 .side_set 1 pindirs
19 mov x, osr 25
20 jmp !x, read 26 ; Set the origin to 0 so we can correctly use jmp instructions externally
21 write: 27 .origin 0
22 set pindirs, 1 28
23 set pins, 0 29 ; Tick rate is 1 tick per 6us, so all delays should be calculated back to that
24 loop1: 30 ; All the instructions have a calculated delay XX in us as [(XX / CLK) - 1].
25 jmp x--,loop1 31 ; The - 1 is for the instruction which also takes one clock cyle.
26 set pindirs, 0 [31] 32 ; The delay can be 0 which will result in just 6us for the instruction itself
27 wait 1 pin 0 [31] 33 .define CLK 6
28 pull block 34
29 mov x, osr 35 ; Write the reset block after trigger
30 bytes1: 36 public reset:
31 pull block 37 set x, 4 side 0 [(60 / CLK) - 1] ; idle before reset
32 set y, 7 38 reset_inner: ; Repeat the following 5 times, so 5*96us = 480us in total
33 set pindirs, 1 39 nop side 1 [(90 / CLK) - 1]
34 bit1: 40 jmp x--, reset_inner side 1 [( 6 / CLK) - 1]
35 set pins, 0 [1] 41 ; Fallthrough
36 out pins,1 [31] 42
37 set pins, 1 [20] 43 ; Check for presence of one or more devices.
38 jmp y--,bit1 44 ; This samples 32 times with an interval of 12us after a 18us delay.
39 jmp x--,bytes1 45 ; If any bit is zero in the end value, there is a detection
40 set pindirs, 0 [31] 46 ; This whole function takes 480us
41 jmp again 47 set x, 31 side 0 [(24 / CLK) - 1] ; Loop 32 times -> 32*12us = 384us
42 read: 48 presence_check:
43 pull block 49 in pins, 1 side 0 [( 6 / CLK) - 1] ; poll pin and push to isr
44 mov x, osr 50 jmp x--, presence_check side 0 [( 6 / CLK) - 1]
45 bytes2: 51 jmp next_bit side 0 [(72 / CLK) - 1]
46 set y, 7 52
47 bit2: 53 ; The low pulse was already done, we only need to delay and poll the bit in case we are reading
48 set pindirs, 1 54 write_1:
49 set pins, 0 [1] 55 nop side 0 [( 6 / CLK) - 1] ; Delay before sampling the input pin
50 set pindirs, 0 [5] 56 in pins, 1 side 0 [(48 / CLK) - 1] ; This writes the state of the pin into the ISR
51 in pins,1 [10] 57 ; Fallthrough
52 jmp y--,bit2 58
53 jmp x--,bytes2 59 ; This is the entry point when reading and writing data
54 .wrap 60 public next_bit:
55 "#, 61 .wrap_target
62 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 in null, 1 side 1 [(54 / 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 .wrap
67 "#
56 ); 68 );
57 let prg = common.load_program(&prg.program);
58 69
59 Self { prg } 70 Self {
71 prg: common.load_program(&prg.program),
72 reset_addr: prg.public_defines.reset as u8,
73 next_bit_addr: prg.public_defines.next_bit as u8,
74 }
60 } 75 }
61} 76}
62
63/// Pio backed OneWire driver 77/// Pio backed OneWire driver
64pub struct PioOneWire<'d, PIO: Instance, const SM: usize> { 78pub struct PioOneWire<'d, PIO: Instance, const SM: usize> {
65 sm: StateMachine<'d, PIO, SM>, 79 sm: StateMachine<'d, PIO, SM>,
80 cfg: Config<'d, PIO>,
81 reset_addr: u8,
82 next_bit_addr: u8,
66} 83}
67 84
68impl<'d, PIO: Instance, const SM: usize> PioOneWire<'d, PIO, SM> { 85impl<'d, PIO: Instance, const SM: usize> PioOneWire<'d, PIO, SM> {
@@ -74,37 +91,206 @@ impl<'d, PIO: Instance, const SM: usize> PioOneWire<'d, PIO, SM> {
74 program: &PioOneWireProgram<'d, PIO>, 91 program: &PioOneWireProgram<'d, PIO>,
75 ) -> Self { 92 ) -> Self {
76 let pin = common.make_pio_pin(pin); 93 let pin = common.make_pio_pin(pin);
94
95 sm.set_pin_dirs(Direction::In, &[&pin]);
96 sm.set_pins(Level::Low, &[&pin]);
97
77 let mut cfg = Config::default(); 98 let mut cfg = Config::default();
78 cfg.use_program(&program.prg, &[]); 99 cfg.use_program(&program.prg, &[&pin]);
79 cfg.set_out_pins(&[&pin]);
80 cfg.set_in_pins(&[&pin]); 100 cfg.set_in_pins(&[&pin]);
81 cfg.set_set_pins(&[&pin]); 101
82 cfg.shift_in = ShiftConfig { 102 let shift_cfg = ShiftConfig {
83 auto_fill: true, 103 auto_fill: true,
84 direction: ShiftDirection::Right, 104 direction: ShiftDirection::Right,
85 threshold: 8, 105 threshold: 8,
86 }; 106 };
87 cfg.clock_divider = 255_u8.into(); 107 cfg.shift_in = shift_cfg;
108 cfg.shift_out = shift_cfg;
109
110 let divider = (clk_sys_freq() / 1000000) as u16 * 6;
111 cfg.clock_divider = divider.into();
112
88 sm.set_config(&cfg); 113 sm.set_config(&cfg);
114 sm.clear_fifos();
115 sm.restart();
116 unsafe {
117 sm.exec_jmp(program.next_bit_addr);
118 }
89 sm.set_enable(true); 119 sm.set_enable(true);
90 Self { sm } 120
121 Self {
122 sm,
123 cfg,
124 reset_addr: program.reset_addr,
125 next_bit_addr: program.next_bit_addr,
126 }
127 }
128
129 /// Perform an initialization sequence, will return true if a presence pulse was detected from a device
130 pub async fn reset(&mut self) -> bool {
131 // The state machine immediately starts running when jumping to this address
132 unsafe {
133 self.sm.exec_jmp(self.reset_addr);
134 }
135
136 let rx = self.sm.rx();
137 let mut found = false;
138 for _ in 0..4 {
139 if rx.wait_pull().await != 0 {
140 found = true;
141 }
142 }
143
144 found
91 } 145 }
92 146
93 /// Write bytes over the wire 147 /// Write bytes to the onewire bus
94 pub async fn write_bytes(&mut self, bytes: &[u8]) { 148 pub async fn write_bytes(&mut self, data: &[u8]) {
95 self.sm.tx().wait_push(250).await; 149 let (rx, tx) = self.sm.rx_tx();
96 self.sm.tx().wait_push(bytes.len() as u32 - 1).await; 150 for b in data {
97 for b in bytes { 151 tx.wait_push(*b as u32).await;
98 self.sm.tx().wait_push(*b as u32).await; 152
153 // Empty the buffer that is being filled with every write
154 let _ = rx.wait_pull().await;
99 } 155 }
100 } 156 }
101 157
102 /// Read bytes from the wire 158 /// Read bytes from the onewire bus
103 pub async fn read_bytes(&mut self, bytes: &mut [u8]) { 159 pub async fn read_bytes(&mut self, data: &mut [u8]) {
104 self.sm.tx().wait_push(0).await; 160 let (rx, tx) = self.sm.rx_tx();
105 self.sm.tx().wait_push(bytes.len() as u32 - 1).await; 161 for b in data {
106 for b in bytes.iter_mut() { 162 // Write all 1's so that we can read what the device responds
107 *b = (self.sm.rx().wait_pull().await >> 24) as u8; 163 tx.wait_push(0xff).await;
164
165 *b = (rx.wait_pull().await >> 24) as u8;
108 } 166 }
109 } 167 }
168
169 async fn search(&mut self, state: &mut PioOneWireSearch) -> Option<u64> {
170 if !self.reset().await {
171 // No device present, no use in searching
172 state.finished = true;
173 return None;
174 }
175 self.write_bytes(&[0xF0]).await; // 0xF0 is the search rom command
176
177 self.prepare_search();
178
179 let (rx, tx) = self.sm.rx_tx();
180
181 let mut value = 0;
182 let mut last_zero = 0;
183
184 for bit in 0..64 {
185 // Write 2 dummy bits to read a bit and its complement
186 tx.wait_push(0x1).await;
187 tx.wait_push(0x1).await;
188 let in1 = rx.wait_pull().await;
189 let in2 = rx.wait_pull().await;
190 let push = match (in1, in2) {
191 (0, 0) => {
192 // If both are 0, it means we have devices with 0 and 1 bits in this position
193 let write_value = if bit < state.last_discrepancy {
194 (state.last_rom & (1 << bit)) != 0
195 } else {
196 bit == state.last_discrepancy
197 };
198
199 if write_value {
200 1
201 } else {
202 last_zero = bit;
203 0
204 }
205 }
206 (0, 1) => 0, // Only devices with a 0 bit in this position
207 (1, 0) => 1, // Only devices with a 1 bit in this position
208 _ => {
209 // If both are 1, it means there is no device active and there is no point in continuing
210 self.restore_after_search();
211 state.finished = true;
212 return None;
213 }
214 };
215 value >>= 1;
216 if push == 1 {
217 value |= 1 << 63;
218 }
219 tx.wait_push(push).await;
220 let _ = rx.wait_pull().await; // Discard the result of the write action
221 }
222
223 self.restore_after_search();
224
225 state.last_discrepancy = last_zero;
226 state.finished = last_zero == 0;
227 state.last_rom = value;
228 Some(value)
229 }
230
231 fn prepare_search(&mut self) {
232 self.cfg.shift_in.threshold = 1;
233 self.cfg.shift_in.direction = ShiftDirection::Left;
234 self.cfg.shift_out.threshold = 1;
235
236 self.sm.set_enable(false);
237 self.sm.set_config(&self.cfg);
238
239 // set_config jumps to the wrong address so jump to the right one here
240 unsafe {
241 self.sm.exec_jmp(self.next_bit_addr);
242 }
243 self.sm.set_enable(true);
244 }
245
246 fn restore_after_search(&mut self) {
247 self.cfg.shift_in.threshold = 8;
248 self.cfg.shift_in.direction = ShiftDirection::Right;
249 self.cfg.shift_out.threshold = 8;
250
251 self.sm.set_enable(false);
252 self.sm.set_config(&self.cfg);
253
254 // Clear the state in case we aborted prematurely with some bits still in the shift registers
255 self.sm.clear_fifos();
256 self.sm.restart();
257
258 // set_config jumps to the wrong address so jump to the right one here
259 unsafe {
260 self.sm.exec_jmp(self.next_bit_addr);
261 }
262 self.sm.set_enable(true);
263 }
264}
265
266/// Onewire search state
267pub struct PioOneWireSearch {
268 last_rom: u64,
269 last_discrepancy: u8,
270 finished: bool,
271}
272
273impl PioOneWireSearch {
274 /// Create a new Onewire search state
275 pub fn new() -> Self {
276 Self {
277 last_rom: 0,
278 last_discrepancy: 0,
279 finished: false,
280 }
281 }
282
283 /// Search for the next address on the bus
284 pub async fn next<PIO: Instance, const SM: usize>(&mut self, pio: &mut PioOneWire<'_, PIO, SM>) -> Option<u64> {
285 if self.finished {
286 None
287 } else {
288 pio.search(self).await
289 }
290 }
291
292 /// Is finished when all devices have been found
293 pub fn is_finished(&self) -> bool {
294 self.finished
295 }
110} 296}
diff --git a/examples/rp/src/bin/pio_onewire.rs b/examples/rp/src/bin/pio_onewire.rs
index 991510851..379e2b8f9 100644
--- a/examples/rp/src/bin/pio_onewire.rs
+++ b/examples/rp/src/bin/pio_onewire.rs
@@ -1,4 +1,4 @@
1//! This example shows how you can use PIO to read a `DS18B20` one-wire temperature sensor. 1//! This example shows how you can use PIO to read one or more `DS18B20` one-wire temperature sensors.
2 2
3#![no_std] 3#![no_std]
4#![no_main] 4#![no_main]
@@ -6,9 +6,10 @@ use defmt::*;
6use embassy_executor::Spawner; 6use embassy_executor::Spawner;
7use embassy_rp::bind_interrupts; 7use embassy_rp::bind_interrupts;
8use embassy_rp::peripherals::PIO0; 8use embassy_rp::peripherals::PIO0;
9use embassy_rp::pio::{self, InterruptHandler, Pio}; 9use embassy_rp::pio::{InterruptHandler, Pio};
10use embassy_rp::pio_programs::onewire::{PioOneWire, PioOneWireProgram}; 10use embassy_rp::pio_programs::onewire::{PioOneWire, PioOneWireProgram, PioOneWireSearch};
11use embassy_time::Timer; 11use embassy_time::Timer;
12use heapless::Vec;
12use {defmt_rtt as _, panic_probe as _}; 13use {defmt_rtt as _, panic_probe as _};
13 14
14bind_interrupts!(struct Irqs { 15bind_interrupts!(struct Irqs {
@@ -21,63 +22,66 @@ async fn main(_spawner: Spawner) {
21 let mut pio = Pio::new(p.PIO0, Irqs); 22 let mut pio = Pio::new(p.PIO0, Irqs);
22 23
23 let prg = PioOneWireProgram::new(&mut pio.common); 24 let prg = PioOneWireProgram::new(&mut pio.common);
24 let onewire = PioOneWire::new(&mut pio.common, pio.sm0, p.PIN_2, &prg); 25 let mut onewire = PioOneWire::new(&mut pio.common, pio.sm0, p.PIN_2, &prg);
25 26
26 let mut sensor = Ds18b20::new(onewire); 27 info!("Starting onewire search");
27 28
28 loop { 29 let mut devices = Vec::<u64, 10>::new();
29 sensor.start().await; // Start a new measurement 30 let mut search = PioOneWireSearch::new();
30 Timer::after_secs(1).await; // Allow 1s for the measurement to finish 31 for _ in 0..10 {
31 match sensor.temperature().await { 32 if !search.is_finished() {
32 Ok(temp) => info!("temp = {:?} deg C", temp), 33 if let Some(address) = search.next(&mut onewire).await {
33 _ => error!("sensor error"), 34 if crc8(&address.to_le_bytes()) == 0 {
35 info!("Found addres: {:x}", address);
36 let _ = devices.push(address);
37 } else {
38 warn!("Found invalid address: {:x}", address);
39 }
40 }
34 } 41 }
35 Timer::after_secs(1).await;
36 } 42 }
37}
38 43
39/// DS18B20 temperature sensor driver 44 info!("Search done, found {} devices", devices.len());
40pub struct Ds18b20<'d, PIO: pio::Instance, const SM: usize> {
41 wire: PioOneWire<'d, PIO, SM>,
42}
43 45
44impl<'d, PIO: pio::Instance, const SM: usize> Ds18b20<'d, PIO, SM> { 46 loop {
45 pub fn new(wire: PioOneWire<'d, PIO, SM>) -> Self { 47 onewire.reset().await;
46 Self { wire } 48 // Skip rom and trigger conversion, we can trigger all devices on the bus immediately
47 } 49 onewire.write_bytes(&[0xCC, 0x44]).await;
48 50
49 /// Calculate CRC8 of the data 51 Timer::after_secs(1).await; // Allow 1s for the measurement to finish
50 fn crc8(data: &[u8]) -> u8 { 52
51 let mut temp; 53 // Read all devices one by one
52 let mut data_byte; 54 for device in &devices {
53 let mut crc = 0; 55 onewire.reset().await;
54 for b in data { 56 onewire.write_bytes(&[0x55]).await; // Match rom
55 data_byte = *b; 57 onewire.write_bytes(&device.to_le_bytes()).await;
56 for _ in 0..8 { 58 onewire.write_bytes(&[0xBE]).await; // Read scratchpad
57 temp = (crc ^ data_byte) & 0x01; 59
58 crc >>= 1; 60 let mut data = [0; 9];
59 if temp != 0 { 61 onewire.read_bytes(&mut data).await;
60 crc ^= 0x8C; 62 if crc8(&data) == 0 {
61 } 63 let temp = ((data[1] as u32) << 8 | data[0] as u32) as f32 / 16.;
62 data_byte >>= 1; 64 info!("Read device {:x}: {} deg C", device, temp);
65 } else {
66 warn!("Reading device {:x} failed", device);
63 } 67 }
64 } 68 }
65 crc 69 Timer::after_secs(1).await;
66 }
67
68 /// Start a new measurement. Allow at least 1000ms before getting `temperature`.
69 pub async fn start(&mut self) {
70 self.wire.write_bytes(&[0xCC, 0x44]).await;
71 } 70 }
71}
72 72
73 /// Read the temperature. Ensure >1000ms has passed since `start` before calling this. 73fn crc8(data: &[u8]) -> u8 {
74 pub async fn temperature(&mut self) -> Result<f32, ()> { 74 let mut crc = 0;
75 self.wire.write_bytes(&[0xCC, 0xBE]).await; 75 for b in data {
76 let mut data = [0; 9]; 76 let mut data_byte = *b;
77 self.wire.read_bytes(&mut data).await; 77 for _ in 0..8 {
78 match Self::crc8(&data) == 0 { 78 let temp = (crc ^ data_byte) & 0x01;
79 true => Ok(((data[1] as u32) << 8 | data[0] as u32) as f32 / 16.), 79 crc >>= 1;
80 false => Err(()), 80 if temp != 0 {
81 crc ^= 0x8C;
82 }
83 data_byte >>= 1;
81 } 84 }
82 } 85 }
86 crc
83} 87}