1use crate::decode::{decode_telemetry_packet, extract_telemetry_payload_with_crc_test, fold_gcr};
2use crate::esc_channel::EscChannel;
3use crate::messages::{DShotTelemetry, EscCommand};
4use cortex_m::asm;
5use cu29::prelude::CuError;
6use cu29::CuResult;
7use hal::dma::Word;
8use hal::pac;
9use hal::pio::{PIOBuilder, StateMachineIndex, ValidStateMachine};
10use pio_proc::pio_file;
11use rp235x_hal as hal;
12use spin::Mutex;
13
14use crate::bridge::BdshotBoardProvider;
15
16pub trait BdshotBoard {
17 const CHANNEL_COUNT: usize;
18
19 fn exchange(&mut self, channel: usize, frame: u32) -> Option<DShotTelemetry>;
20
21 fn delay(&mut self, micros: u32);
22}
23
24pub struct Rp2350Board {
25 cycles_per_micro: u32,
26 ch0: EscChannel<pac::PIO0, hal::pio::SM0, Word, Word>,
27 ch1: EscChannel<pac::PIO0, hal::pio::SM1, Word, Word>,
28 ch2: EscChannel<pac::PIO0, hal::pio::SM2, Word, Word>,
29 ch3: EscChannel<pac::PIO0, hal::pio::SM3, Word, Word>,
30}
31
32#[derive(Clone, Copy)]
33pub struct Rp2350BoardConfig {
34 pub pins: [u8; 4],
35 pub target_pio_clock_hz: u32,
36}
37
38impl Default for Rp2350BoardConfig {
39 fn default() -> Self {
40 Self {
41 pins: [6, 7, 8, 9],
42 target_pio_clock_hz: 15_300_000,
43 }
44 }
45}
46
47pub struct Rp2350BoardResources {
48 pub pio: hal::pio::PIO<pac::PIO0>,
49 pub sm0: hal::pio::UninitStateMachine<(pac::PIO0, hal::pio::SM0)>,
50 pub sm1: hal::pio::UninitStateMachine<(pac::PIO0, hal::pio::SM1)>,
51 pub sm2: hal::pio::UninitStateMachine<(pac::PIO0, hal::pio::SM2)>,
52 pub sm3: hal::pio::UninitStateMachine<(pac::PIO0, hal::pio::SM3)>,
53}
54
55impl Rp2350BoardResources {
56 pub fn new(
57 pio: hal::pio::PIO<pac::PIO0>,
58 sm0: hal::pio::UninitStateMachine<(pac::PIO0, hal::pio::SM0)>,
59 sm1: hal::pio::UninitStateMachine<(pac::PIO0, hal::pio::SM1)>,
60 sm2: hal::pio::UninitStateMachine<(pac::PIO0, hal::pio::SM2)>,
61 sm3: hal::pio::UninitStateMachine<(pac::PIO0, hal::pio::SM3)>,
62 ) -> Self {
63 Self {
64 pio,
65 sm0,
66 sm1,
67 sm2,
68 sm3,
69 }
70 }
71}
72
73impl Rp2350Board {
74 pub fn new(
75 resources: Rp2350BoardResources,
76 system_clock_hz: u32,
77 config: Rp2350BoardConfig,
78 ) -> CuResult<Self> {
79 let Rp2350BoardResources {
80 mut pio,
81 sm0,
82 sm1,
83 sm2,
84 sm3,
85 } = resources;
86 let pins = config.pins;
87 let (div_int, div_frac) = pio_clkdiv_8p8(system_clock_hz, config.target_pio_clock_hz);
88 let program = pio_file!("src/dshot.pio", select_program("bdshot_300"));
89 let installed = pio
90 .install(&program.program)
91 .map_err(|_| CuError::from("Failed to install BDShot PIO program"))?;
92
93 let ch0 = crate::build_ch!(installed, sm0, pins[0], div_int, div_frac);
94 let ch1 = crate::build_ch!(installed, sm1, pins[1], div_int, div_frac);
95 let ch2 = crate::build_ch!(installed, sm2, pins[2], div_int, div_frac);
96 let ch3 = crate::build_ch!(installed, sm3, pins[3], div_int, div_frac);
97
98 Ok(Self {
99 cycles_per_micro: system_clock_hz / 1_000_000,
100 ch0,
101 ch1,
102 ch2,
103 ch3,
104 })
105 }
106}
107
108impl BdshotBoard for Rp2350Board {
109 const CHANNEL_COUNT: usize = 4;
110
111 fn exchange(&mut self, channel: usize, frame: u32) -> Option<DShotTelemetry> {
112 let cycles = self.cycles_per_micro;
113 match channel {
114 0 => Self::exchange_impl(cycles, &mut self.ch0, frame),
115 1 => Self::exchange_impl(cycles, &mut self.ch1, frame),
116 2 => Self::exchange_impl(cycles, &mut self.ch2, frame),
117 3 => Self::exchange_impl(cycles, &mut self.ch3, frame),
118 _ => None,
119 }
120 }
121
122 fn delay(&mut self, micros: u32) {
123 Self::delay_ticks(self.cycles_per_micro, micros as u64);
124 }
125}
126
127impl Rp2350Board {
128 fn exchange_impl<SM>(
130 cycles_per_micro: u32,
131 ch: &mut EscChannel<pac::PIO0, SM, Word, Word>,
132 frame: u32,
133 ) -> Option<DShotTelemetry>
134 where
135 SM: StateMachineIndex,
136 (pac::PIO0, SM): ValidStateMachine,
137 {
138 if ch.is_full() {
139 return None;
140 }
141 ch.write(frame);
142 if let Some(resp) = ch.read() {
144 let raw20 = fold_gcr(resp);
146 let data = crate::decode::gcr_to_16bit(raw20)?;
148 let payload = extract_telemetry_payload_with_crc_test(data)?;
150 return Some(decode_telemetry_packet(payload));
151 }
152 Self::delay_ticks(cycles_per_micro, 150);
153 ch.restart();
154 None
155 }
156
157 fn delay_ticks(cycles_per_micro: u32, mut micros: u64) {
158 let cycles = cycles_per_micro.max(1) as u64;
159 while micros > 0 {
160 let chunk = micros.min(1000);
161 let total = cycles * chunk;
162 asm::delay(total as u32);
163 micros -= chunk;
164 }
165 }
166}
167
168fn pio_clkdiv_8p8(sys_hz: u32, target_hz: u32) -> (u16, u8) {
170 let div_fixed = ((sys_hz as f32 / target_hz as f32) * 256.0 + 0.5) as u32;
171 ((div_fixed >> 8) as u16, (div_fixed & 0xFF) as u8)
172}
173
174fn bdshot_frame(payload: u16) -> u32 {
176 let d = ((payload & 0x07FF) << 1) | 1;
177 let inv = (!d) & 0x0FFF;
178 let crc = !(!((inv ^ (inv >> 4) ^ (inv >> 8)) & 0x0F)) & 0x0F;
179 !((d << 4) | crc) as u32
180}
181
182pub fn encode_frame(command: EscCommand) -> u32 {
184 let mut bits = (command.throttle & 0x07FF) << 1;
185 if command.request_telemetry {
186 bits |= 0x01;
187 }
188 bdshot_frame(bits)
189}
190
191static RP_BOARD_SLOT: Mutex<Option<Rp2350Board>> = Mutex::new(None);
193
194pub fn register_rp2350_board(board: Rp2350Board) -> CuResult<()> {
196 let mut slot = RP_BOARD_SLOT.lock();
197 if slot.is_some() {
198 return Err(CuError::from("RP2350 BDShot board already registered"));
199 }
200 *slot = Some(board);
201 Ok(())
202}
203
204pub struct Rp2350BoardProvider;
205
206impl BdshotBoardProvider for Rp2350BoardProvider {
207 type Board = Rp2350Board;
208
209 fn create_board() -> CuResult<Self::Board> {
210 RP_BOARD_SLOT
211 .lock()
212 .take()
213 .ok_or_else(|| CuError::from("No RP2350 BDShot board registered"))
214 }
215}