aboutsummaryrefslogtreecommitdiff
path: root/embassy-rp/src/pio_programs/pwm.rs
blob: e4ad4a6f0dab840f021c360b07cb3b80c7b523a2 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
//! PIO backed PWM driver

use core::time::Duration;

use pio::InstructionOperands;

use crate::gpio::Level;
use crate::pio::{Common, Config, Direction, Instance, LoadedProgram, Pin, PioPin, StateMachine};
use crate::{Peri, clocks};

/// This converts the duration provided into the number of cycles the PIO needs to run to make it take the same time
fn to_pio_cycles(duration: Duration) -> u32 {
    (clocks::clk_sys_freq() / 1_000_000) / 3 * duration.as_micros() as u32 // parentheses are required to prevent overflow
}

/// This struct represents a PWM program loaded into pio instruction memory.
pub struct PioPwmProgram<'a, PIO: Instance> {
    prg: LoadedProgram<'a, PIO>,
}

impl<'a, PIO: Instance> PioPwmProgram<'a, PIO> {
    /// Load the program into the given pio
    pub fn new(common: &mut Common<'a, PIO>) -> Self {
        let prg = pio::pio_asm!(
            ".side_set 1 opt"
                "pull noblock    side 0"
                "mov x, osr"
                "mov y, isr"
            "countloop:"
                "jmp x!=y noset"
                "jmp skip        side 1"
            "noset:"
                "nop"
            "skip:"
                "jmp y-- countloop"
        );

        let prg = common.load_program(&prg.program);

        Self { prg }
    }
}

/// Pio backed PWM output
pub struct PioPwm<'d, T: Instance, const SM: usize> {
    sm: StateMachine<'d, T, SM>,
    pin: Pin<'d, T>,
}

impl<'d, T: Instance, const SM: usize> PioPwm<'d, T, SM> {
    /// Configure a state machine as a PWM output
    pub fn new(
        pio: &mut Common<'d, T>,
        mut sm: StateMachine<'d, T, SM>,
        pin: Peri<'d, impl PioPin>,
        program: &PioPwmProgram<'d, T>,
    ) -> Self {
        let pin = pio.make_pio_pin(pin);
        sm.set_pins(Level::High, &[&pin]);
        sm.set_pin_dirs(Direction::Out, &[&pin]);

        let mut cfg = Config::default();
        cfg.use_program(&program.prg, &[&pin]);

        sm.set_config(&cfg);

        Self { sm, pin }
    }

    /// Enables the PIO program, continuing the wave generation from the PIO program.
    pub fn start(&mut self) {
        self.sm.set_enable(true);
    }

    /// Stops the PIO program, ceasing all signals from the PIN that were generated via PIO.
    pub fn stop(&mut self) {
        self.sm.set_enable(false);
    }

    /// Sets the pwm period, which is the length of time for each pio wave until reset.
    pub fn set_period(&mut self, duration: Duration) {
        let is_enabled = self.sm.is_enabled();
        while !self.sm.tx().empty() {} // Make sure that the queue is empty
        self.sm.set_enable(false);
        self.sm.tx().push(to_pio_cycles(duration));
        unsafe {
            self.sm.exec_instr(
                InstructionOperands::PULL {
                    if_empty: false,
                    block: false,
                }
                .encode(),
            );
            self.sm.exec_instr(
                InstructionOperands::OUT {
                    destination: ::pio::OutDestination::ISR,
                    bit_count: 32,
                }
                .encode(),
            );
        };
        if is_enabled {
            self.sm.set_enable(true) // Enable if previously enabled
        }
    }

    /// Set the number of pio cycles to set the wave on high to.
    pub fn set_level(&mut self, level: u32) {
        self.sm.tx().push(level);
    }

    /// Set the pulse width high time
    pub fn write(&mut self, duration: Duration) {
        self.set_level(to_pio_cycles(duration));
    }

    /// Return the state machine and pin.
    pub fn release(self) -> (StateMachine<'d, T, SM>, Pin<'d, T>) {
        (self.sm, self.pin)
    }
}