rp2040/
pio_pwm.rs

1// Licensed under the Apache License, Version 2.0 or the MIT License.
2// SPDX-License-Identifier: Apache-2.0 OR MIT
3// Copyright OxidOS Automotive 2024.
4//
5// Author: Radu Matei <radu.matei.05.21@gmail.com>
6
7//! Programmable Input Output (PIO) hardware test file.
8use crate::clocks::{self};
9use crate::gpio::RPGpio;
10use crate::pio::{Pio, SMNumber, StateMachineConfiguration};
11
12use kernel::utilities::cells::TakeCell;
13use kernel::{hil, ErrorCode};
14
15pub struct PioPwm<'a> {
16    clocks: &'a clocks::Clocks,
17    pio: TakeCell<'a, Pio>,
18}
19
20impl<'a> PioPwm<'a> {
21    pub fn new(pio: &'a mut Pio, clocks: &'a clocks::Clocks) -> Self {
22        Self {
23            clocks,
24            pio: TakeCell::new(pio),
25        }
26    }
27}
28
29impl hil::pwm::Pwm for PioPwm<'_> {
30    type Pin = RPGpio;
31
32    fn start(
33        &self,
34        pin: &Self::Pin,
35        frequency_hz: usize,
36        duty_cycle_percentage: usize,
37    ) -> Result<(), ErrorCode> {
38        // Ramps up the intensity of an LED using PWM.
39        // .program pwm
40        // .side_set 1 opt
41        //     pull noblock    side 0 ; Pull from FIFO to OSR if available, else copy X to OSR.
42        //     mov x, osr             ; Copy most-recently-pulled value back to scratch X
43        //     mov y, isr             ; ISR contains PWM period. Y used as counter.
44        // countloop:
45        //     jmp x!=y noset         ; Set pin high if X == Y, keep the two paths length matched
46        //     jmp skip        side 1
47        // noset:
48        //     nop                    ; Single dummy cycle to keep the two paths the same length
49        // skip:
50        //     jmp y-- countloop      ; Loop until Y hits 0, then pull a fresh PWM value from FIFO
51        let path: [u8; 14] = [
52            0x90, 0x80, 0xa0, 0x27, 0xa0, 0x46, 0x00, 0xa5, 0x18, 0x06, 0xa0, 0x42, 0x00, 0x83,
53        ];
54
55        self.pio.map(|pio| {
56            pio.init();
57            pio.add_program(Some(0), &path).ok();
58            let mut custom_config = StateMachineConfiguration::default();
59
60            let pin_nr = *pin as u32;
61            custom_config.div_frac = 0;
62            custom_config.div_int = 1;
63            custom_config.side_set_base = pin_nr;
64            custom_config.side_set_bit_count = 2;
65            custom_config.side_set_opt_enable = true;
66            custom_config.side_set_pindirs = false;
67            let max_freq = self.get_maximum_frequency_hz();
68            let pwm_period = ((max_freq / frequency_hz) / 3) as u32;
69            let sm_number = SMNumber::SM0;
70            let duty_cycle = duty_cycle_percentage as u32;
71            pio.pwm_program_init(sm_number, pin_nr, pwm_period, &custom_config);
72            pio.sm(sm_number)
73                .push_blocking(pwm_period * duty_cycle / (self.get_maximum_duty_cycle()) as u32)
74                .ok();
75        });
76
77        Ok(())
78    }
79
80    fn stop(&self, _pin: &Self::Pin) -> Result<(), ErrorCode> {
81        self.pio.map(|pio| pio.clear_instr_registers());
82        Ok(())
83    }
84
85    fn get_maximum_duty_cycle(&self) -> usize {
86        // being a percentage out of 10000, max duty cycle is 10000
87        10000
88    }
89
90    // For the rp2040, this will always return 125_000_000. Watch out as any value above
91    // 1_000_000 is not precise and WILL give modified frequency and duty cycle values.
92    fn get_maximum_frequency_hz(&self) -> usize {
93        self.clocks.get_frequency(clocks::Clock::System) as usize
94    }
95}