servo_pio/
servo_cluster.rs

1use crate::calibration::{Calibration, CalibrationData, NoCustom, Point};
2use crate::pwm_cluster::{DynPin, GlobalState, GlobalStates, PwmCluster, PwmClusterBuilder};
3use crate::servo_state::{self, ServoState, DEFAULT_FREQUENCY};
4use crate::{initialize_array_by_index, initialize_array_from, initialize_arrays_from};
5use defmt::Format;
6use fugit::HertzU32;
7use rp2040_hal::clocks::SystemClock;
8use rp2040_hal::dma::{Channel, ChannelIndex};
9use rp2040_hal::gpio::Function;
10use rp2040_hal::pio::{PIOExt, StateMachineIndex, UninitStateMachine, PIO};
11use rp2040_hal::Clock;
12
13/// Use to index the servos in [ServoCluster methods].
14#[derive(Copy, Clone)]
15pub struct ServoIdx(u8);
16
17/// A type to manage a cluster of servos all run from the same PIO state machine.
18pub struct ServoCluster<const NUM_SERVOS: usize, P, SM, Cal = NoCustom>
19where
20    P: PIOExt,
21    SM: StateMachineIndex,
22{
23    /// The [PwmCluster] used to control the pwm signals for the servos.
24    pwms: PwmCluster<NUM_SERVOS, P, SM>,
25    /// The pwm period shared by all servos in this cluster.
26    pwm_period: u32,
27    /// The pwm frequency shared by all servos in this cluster.
28    pwm_frequency: f32,
29    /// The individual servo states.
30    states: [ServoState<Cal>; NUM_SERVOS],
31    /// The phases for each servo.
32    ///
33    /// The phases are needed to prevent voltage spikes when attempting to move many servos at once.
34    /// A phase shift (or offset) is applied so there is not a sudden demand for current in the
35    /// system.
36    servo_phases: [f32; NUM_SERVOS],
37}
38
39/// A builder for [ServoCluster].
40pub struct ServoClusterBuilder<
41    'a,
42    Cal,
43    C1,
44    C2,
45    P,
46    SM,
47    F,
48    const NUM_SERVOS: usize,
49    const NUM_CHANNELS: usize,
50> where
51    C1: ChannelIndex + 'static,
52    C2: ChannelIndex + 'static,
53    P: PIOExt<PinFunction = F> + 'static,
54    F: Function,
55    SM: StateMachineIndex + 'static,
56{
57    /// The [PIO] instance to use.
58    pio: &'a mut PIO<P>,
59    /// The specific [UninitStateMachine] to run this cluster on.
60    sm: UninitStateMachine<(P, SM)>,
61    /// The DMA [Channel]s to manage the transitions.
62    dma_channels: (Channel<C1>, Channel<C2>),
63    /// The [GlobalStates] to manage the interrupt handler for this cluster.
64    global_states: &'static mut GlobalStates<NUM_CHANNELS>,
65    /// The output pins this cluster will use to communicate with the servos.
66    pins: Option<[DynPin<F>; NUM_SERVOS]>,
67    /// The side set pin used for debugging the pio program.
68    #[cfg(feature = "debug_pio")]
69    side_set_pin: Option<DynPin<F>>,
70    /// The pwm frequency the servos will share.
71    pwm_frequency: Option<f32>,
72    /// The calibration for each servo.
73    calibrations: Option<[Calibration<Cal>; NUM_SERVOS]>,
74    /// Whether or not to enable a phase shift for each servo. The default is true.
75    auto_phase: Option<bool>,
76}
77
78pub struct ServoData<C, F>
79where
80    F: Function,
81{
82    pub pin: DynPin<F>,
83    pub calibration: Calibration<C>,
84}
85
86impl<'a, Cal, C1, C2, P, SM, F, const NUM_SERVOS: usize, const NUM_CHANNELS: usize>
87    ServoClusterBuilder<'a, Cal, C1, C2, P, SM, F, NUM_SERVOS, NUM_CHANNELS>
88where
89    Cal: CalibrationData + Default + Clone,
90    for<'i> <Cal as CalibrationData>::Iterator<'i>: Iterator<Item = (Point, Point)>,
91    C1: ChannelIndex,
92    C2: ChannelIndex,
93    P: PIOExt<PinFunction = F>,
94    F: Function,
95    SM: StateMachineIndex,
96{
97    /// Set the calibration for each servos.
98    pub fn calibrations(mut self, calibrations: [Calibration<Cal>; NUM_SERVOS]) -> Self {
99        self.calibrations = Some(calibrations);
100        self
101    }
102
103    /// Configure whether or not to enable phase shifts for the servos.
104    pub fn auto_phase(mut self, auto_phase: bool) -> Self {
105        self.auto_phase = Some(auto_phase);
106        self
107    }
108
109    // pub fn pin_mask(mut self, pin_mask: u32) -> Self {
110    //     if true {
111    //         // self.pins is [DynPin;]
112    //         todo!()
113    //     }
114    //     // Safety: MaybeUninit<[T; N]> is always initialized.
115    //     let mut pins: [MaybeUninit<u8>; NUM_SERVOS] =
116    //         unsafe { MaybeUninit::uninit().assume_init() };
117    //     for (pin, bit) in pins.iter_mut().zip(0..32) {
118    //         pin.write(if pin_mask & (1 << bit) == 1 << bit {
119    //             bit
120    //         } else {
121    //             0
122    //         });
123    //     }
124
125    //     // Safety: all entries initialized above.
126    //     self.pins = Some(unsafe {
127    //         (*(&MaybeUninit::new(pins) as *const _ as *const MaybeUninit<_>)).assume_init_read()
128    //     });
129
130    //     self
131    // }
132
133    // pub fn pin_base(mut self, pin_base: u8) -> Self {
134    //     if true {
135    //         // self.pins is [DynPin;]
136    //         todo!()
137    //     }
138    //     // Safety: MaybeUninit<[T; N]> is always initialized.
139    //     let mut pins: [MaybeUninit<u8>; NUM_SERVOS] =
140    //         unsafe { MaybeUninit::uninit().assume_init() };
141    //     for (pin, id) in pins.iter_mut().zip(pin_base..(pin_base + NUM_SERVOS as u8)) {
142    //         pin.write(id);
143    //     }
144
145    //     // Safety: all entries initialized above.
146    //     self.pins = Some(unsafe {
147    //         (*(&MaybeUninit::new(pins) as *const _ as *const MaybeUninit<_>)).assume_init_read()
148    //     });
149
150    //     self
151    // }
152
153    /// Set the output pins to correspond to the servos. Note that the order they are passed in here
154    /// will map how the servos are accessed in [ServoCluster] when specifying the servo index.
155    pub fn pins_and_calibration(mut self, pin_data: [ServoData<Cal, F>; NUM_SERVOS]) -> Self {
156        let (dyn_pins, calibrations) =
157            initialize_arrays_from(pin_data, |data| (data.pin, data.calibration));
158        self.pins = Some(dyn_pins);
159        self.calibrations = Some(calibrations);
160        self
161    }
162
163    /// Set the sideset pin to use for debugging the PIO program.
164    #[cfg(feature = "debug_pio")]
165    pub fn side_set_pin(mut self, side_set_pin: DynPin<F>) -> Self {
166        self.side_set_pin = Some(side_set_pin);
167        self
168    }
169
170    /// Set the frequency for the pwm signal for the cluster.
171    pub fn pwm_frequency(mut self, pwm_frequency: f32) -> Self {
172        self.pwm_frequency = Some(pwm_frequency);
173        self
174    }
175
176    /// Build the [ServoCluster]. Note that the global state passed in here should not have been
177    /// used to initialize another [ServoCluster]. If so, this function will return an error.
178    pub fn build(
179        mut self,
180        system_clock: &SystemClock,
181        maybe_global_state: &'static mut Option<GlobalState<C1, C2, P, SM>>,
182    ) -> Result<ServoCluster<NUM_SERVOS, P, SM, Cal>, ServoClusterBuilderError> {
183        let pins = self.pins.ok_or(ServoClusterBuilderError::MissingPins)?;
184        let calibrations = self
185            .calibrations
186            .ok_or(ServoClusterBuilderError::MissingCalibrations)?;
187        let (states, servo_phases) = ServoCluster::<NUM_SERVOS, P, SM, _>::create_servo_states(
188            calibrations,
189            self.auto_phase.unwrap_or(true),
190        );
191        let global_state = self.global_states.get_mut(&mut self.dma_channels, move || {
192            PwmClusterBuilder::<NUM_SERVOS, P>::prep_global_state(maybe_global_state)
193        });
194        let mut pwms = {
195            {
196                unsafe {
197                    #[allow(unused_mut)]
198                    let mut cluster = PwmCluster::<NUM_SERVOS, P, SM>::builder().pins(&pins);
199                    #[cfg(feature = "debug_pio")]
200                    {
201                        let side_set_pin = self
202                            .side_set_pin
203                            .ok_or(ServoClusterBuilderError::MissingSideSet)?;
204                        cluster = cluster.side_pin(&side_set_pin);
205                    }
206                    cluster.build(
207                        pins,
208                        self.pio,
209                        self.sm,
210                        self.dma_channels,
211                        system_clock,
212                        global_state.ok_or(ServoClusterBuilderError::MismatchingGlobalState)?,
213                    )
214                }
215            }
216        };
217
218        // Calculate a suitable pwm top period for this frequency
219        let pwm_frequency = self.pwm_frequency.unwrap_or(DEFAULT_FREQUENCY);
220        let pwm_period = if let Some((pwm_period, div256)) =
221            PwmCluster::<NUM_SERVOS, P, SM>::calculate_pwm_factors(
222                system_clock.freq(),
223                pwm_frequency,
224            ) {
225            // Update the pwm before setting the new top
226            for servo in 0..NUM_SERVOS as u8 {
227                let _ = pwms.set_channel_level(servo, 0, false);
228                let _ = pwms.set_channel_offset(
229                    servo,
230                    (servo_phases[servo as usize] * pwm_period as f32) as u32,
231                    false,
232                );
233            }
234
235            // Set the new top (should be 1 less than the period to get full 0 to 100%)
236            pwms.set_top(pwm_period, true); // NOTE Minus 1 not needed here. Maybe should change Wrap behaviour so it is needed, for consistency with hardware pwm?
237
238            // Apply the new divider
239            // This is done after loading new PWM values to avoid a lockup condition
240            let div: u16 = (div256 >> 8) as u16;
241            let modulus: u8 = (div256 % 256) as u8;
242            pwms.clock_divisor_fixed_point(div, modulus);
243            pwm_period
244        } else {
245            0
246        };
247
248        Ok(ServoCluster {
249            pwms,
250            pwm_period,
251            pwm_frequency,
252            states,
253            servo_phases,
254        })
255    }
256}
257
258/// Possible errors when attempting to build a [ServoCluster].
259#[derive(Format)]
260pub enum ServoClusterBuilderError {
261    /// The GlobalState passed in does not match one of the DMA channels, PIO
262    /// instance or PIO State Machine number.
263    MismatchingGlobalState,
264    /// The output pins are missing.
265    MissingPins,
266    MissingCalibrations,
267    /// The side set pin is missing.
268    #[cfg(feature = "debug_pio")]
269    MissingSideSet,
270}
271
272impl<'a, const NUM_SERVOS: usize, P, SM, Cal, F> ServoCluster<NUM_SERVOS, P, SM, Cal>
273where
274    Cal: Default + CalibrationData + Clone,
275    for<'i> <Cal as CalibrationData>::Iterator<'i>: Iterator<Item = (Point, Point)>,
276    P: PIOExt<PinFunction = F>,
277    F: Function,
278    SM: StateMachineIndex,
279{
280    /// Get a builder to help construct a `ServoCluster`.
281    pub fn builder<C1, C2, const NUM_CHANNELS: usize>(
282        pio: &'a mut PIO<P>,
283        sm: UninitStateMachine<(P, SM)>,
284        dma_channels: (Channel<C1>, Channel<C2>),
285        global_states: &'static mut GlobalStates<NUM_CHANNELS>,
286    ) -> ServoClusterBuilder<'a, Cal, C1, C2, P, SM, F, NUM_SERVOS, NUM_CHANNELS>
287    where
288        C1: ChannelIndex + 'static,
289        C2: ChannelIndex + 'static,
290    {
291        ServoClusterBuilder {
292            pio,
293            sm,
294            dma_channels,
295            global_states,
296            pins: None,
297            #[cfg(feature = "debug_pio")]
298            side_set_pin: None,
299            pwm_frequency: None,
300            calibrations: None,
301            auto_phase: None,
302        }
303    }
304
305    /// Create the servo states.
306    fn create_servo_states(
307        calibrations: [Calibration<Cal>; NUM_SERVOS],
308        auto_phase: bool,
309    ) -> ([ServoState<Cal>; NUM_SERVOS], [f32; NUM_SERVOS]) {
310        (
311            initialize_array_from::<Calibration<Cal>, ServoState<Cal>, NUM_SERVOS>(
312                calibrations,
313                move |calibration| ServoState::with_calibration(calibration),
314            ),
315            initialize_array_by_index::<f32, NUM_SERVOS>(|i| {
316                if auto_phase {
317                    i as f32 / NUM_SERVOS as f32
318                } else {
319                    0.0
320                }
321            }),
322        )
323    }
324
325    pub fn servos(&self) -> [ServoIdx; NUM_SERVOS] {
326        initialize_array_by_index(|i| ServoIdx(i as u8))
327    }
328
329    /// Return whether or not this `ServoCluster` is enabled.
330    pub fn enabled(&self, servo: ServoIdx) -> bool {
331        self.states[servo.0 as usize].enabled()
332    }
333
334    /// Control whether the `ServoCluster` is enabled or not. If `load` is true, immeditely update
335    /// the servo pwm signal.
336    pub fn set_enabled(&mut self, servo: ServoIdx, enable: bool, load: bool) {
337        let state = &mut self.states[servo.0 as usize];
338        if enable {
339            let new_pulse = state.enable_with_return();
340            self.apply_pulse(servo, new_pulse, load);
341        } else {
342            state.disable();
343        }
344    }
345
346    /// Get the pulse for a particular servo.
347    pub fn pulse(&self, servo: ServoIdx) -> Option<f32> {
348        self.states[servo.0 as usize].pulse()
349    }
350
351    /// Set the pulse for a particular servo. If `load` is true, immeditely update the servo pwm
352    /// signal.
353    pub fn set_pulse(&mut self, servo: ServoIdx, pulse: f32, load: bool) {
354        let new_pulse = self.states[servo.0 as usize].set_pulse_with_return(pulse);
355        if let Some(new_pulse) = new_pulse {
356            self.apply_pulse(servo, new_pulse, load);
357        }
358    }
359
360    /// Get the value for a particular servo.
361    pub fn value(&self, servo: ServoIdx) -> f32 {
362        self.states[servo.0 as usize].value()
363    }
364
365    /// Set the value for a particular servo. If `load` is true, immeditely update the servo pwm
366    /// signal.
367    pub fn set_value(&mut self, servo: ServoIdx, value: f32, load: bool) {
368        let new_pulse = self.states[servo.0 as usize].set_value_with_return(value);
369        self.apply_pulse(servo, new_pulse, load);
370    }
371
372    /// Get the phase for a particular servo.
373    pub fn phase(&self, servo: ServoIdx) -> f32 {
374        self.servo_phases[servo.0 as usize]
375    }
376
377    /// Set the phase for a particular servo. If `load` is true, immeditely update the servo pwm
378    /// signal.
379    pub fn set_phase(&mut self, servo: ServoIdx, phase: f32, load: bool) {
380        self.servo_phases[servo.0 as usize] = phase.clamp(0.0, 1.0);
381        // servo count already checked above
382        let _ = self.pwms.set_channel_offset(
383            servo.0,
384            (self.servo_phases[servo.0 as usize] * self.pwms.top() as f32) as u32,
385            load,
386        );
387    }
388
389    /// Get the pwm frequency being used for this `ServoCluster`.
390    pub fn frequency(&self) -> f32 {
391        self.pwm_frequency
392    }
393
394    /// Set the pwm frequency being used for this `ServoCluster`.
395    pub fn set_frequency(&mut self, system_clock_hz: HertzU32, frequency: f32) {
396        if (servo_state::MIN_FREQUENCY..=servo_state::MAX_FREQUENCY).contains(&frequency) {
397            if let Some((period, div256)) =
398                PwmCluster::<NUM_SERVOS, P, SM>::calculate_pwm_factors(system_clock_hz, frequency)
399            {
400                self.pwm_period = period;
401                self.pwm_frequency = frequency;
402
403                for servo in 0..NUM_SERVOS {
404                    if self.states[servo].enabled() {
405                        // Unwrap ok because state is enabled
406                        self.apply_pulse(
407                            ServoIdx(servo as u8),
408                            self.states[servo].pulse().unwrap(),
409                            false,
410                        );
411                    }
412                    // Won't error because iterationg over NUM_SERVOS.
413                    let _ = self.pwms.set_channel_offset(
414                        servo as u8,
415                        (self.servo_phases[servo] * self.pwm_period as f32) as u32,
416                        false,
417                    );
418                }
419
420                self.pwms.set_top(self.pwm_period, true);
421                let div = (div256 >> 8) as u16;
422                let frac = (div256 % 256) as u8;
423                self.pwms.clock_divisor_fixed_point(div, frac);
424            }
425        }
426    }
427
428    /// Get the min value for a particular servo.
429    pub fn min_value(&self, servo: ServoIdx) -> f32 {
430        self.states[servo.0 as usize].min_value()
431    }
432
433    /// Get the mid value for a particular servo.
434    pub fn mid_value(&self, servo: ServoIdx) -> f32 {
435        self.states[servo.0 as usize].mid_value()
436    }
437
438    /// Get the max value for a particular servo.
439    pub fn max_value(&self, servo: ServoIdx) -> f32 {
440        self.states[servo.0 as usize].max_value()
441    }
442
443    /// Set the min value for a particular servo. If `load` is true, immediately update the servo
444    /// pwm signal.
445    pub fn to_min(&mut self, servo: ServoIdx, load: bool) {
446        let new_pulse = self.states[servo.0 as usize].to_min_with_return();
447        self.apply_pulse(servo, new_pulse, load);
448    }
449
450    /// Set the mid value for a particular servo. If `load` is true, immediately update the servo
451    /// pwm signal.
452    pub fn to_mid(&mut self, servo: ServoIdx, load: bool) {
453        let new_pulse = self.states[servo.0 as usize].to_mid_with_return();
454        self.apply_pulse(servo, new_pulse, load);
455    }
456
457    /// Set the max value for a particular servo. If `load` is true, immediately update the servo
458    /// pwm signal.
459    pub fn to_max(&mut self, servo: ServoIdx, load: bool) {
460        let new_pulse = self.states[servo.0 as usize].to_max_with_return();
461        self.apply_pulse(servo, new_pulse, load);
462    }
463
464    /// Set a particular servo to a percentage of its range. 0% maps to the servo's minimum value,
465    /// 100% maps to the servo's maximum value.
466    pub fn to_percent(&mut self, servo: ServoIdx, percent: f32, load: bool) {
467        let new_pulse = self.states[servo.0 as usize].to_percent_with_return(percent);
468        self.apply_pulse(servo, new_pulse, load);
469    }
470
471    /// Get a shared reference to a particular servo's calibration.
472    pub fn calibration(&self, servo: ServoIdx) -> &Calibration<Cal> {
473        self.states[servo.0 as usize].calibration()
474    }
475
476    /// Get a unique reference to a particular servo's calibration.
477    pub fn calibration_mut(&mut self, servo: ServoIdx) -> &mut Calibration<Cal> {
478        self.states[servo.0 as usize].calibration_mut()
479    }
480
481    /// Immediately update the pwm state for all servos.
482    pub fn load(&mut self) {
483        self.pwms.load_pwm()
484    }
485
486    /// Apply a pulse to a particular servo. If `load` is true, immeditely update the servo pwm
487    /// signal.
488    fn apply_pulse(&mut self, servo: ServoIdx, pulse: f32, load: bool) {
489        // unwrap ok because servo checked at call sites.
490        let level = ServoState::<Cal>::pulse_to_level(pulse, self.pwm_period, self.pwm_frequency);
491        let _ = self.pwms.set_channel_level(servo.0, level, load);
492    }
493}