diff options
Diffstat (limited to 'embassy-nxp/src/pwm/lpc55.rs')
| -rw-r--r-- | embassy-nxp/src/pwm/lpc55.rs | 248 |
1 files changed, 248 insertions, 0 deletions
diff --git a/embassy-nxp/src/pwm/lpc55.rs b/embassy-nxp/src/pwm/lpc55.rs new file mode 100644 index 000000000..4cdbd8526 --- /dev/null +++ b/embassy-nxp/src/pwm/lpc55.rs | |||
| @@ -0,0 +1,248 @@ | |||
| 1 | #![macro_use] | ||
| 2 | |||
| 3 | use core::sync::atomic::{AtomicU8, AtomicU32, Ordering}; | ||
| 4 | |||
| 5 | use embassy_hal_internal::Peri; | ||
| 6 | |||
| 7 | use crate::gpio::AnyPin; | ||
| 8 | use crate::pac::iocon::vals::{PioDigimode, PioMode, PioOd, PioSlew}; | ||
| 9 | use crate::pac::sct0::vals; | ||
| 10 | use crate::pac::syscon::vals::{SctRst, SctclkselSel}; | ||
| 11 | use crate::pac::{SCT0, SYSCON}; | ||
| 12 | use crate::sct; | ||
| 13 | |||
| 14 | // Since for now the counter is shared, the TOP value has to be kept. | ||
| 15 | static TOP_VALUE: AtomicU32 = AtomicU32::new(0); | ||
| 16 | // To check if there are still active instances. | ||
| 17 | static REF_COUNT: AtomicU8 = AtomicU8::new(0); | ||
| 18 | |||
| 19 | /// The configuration of a PWM output. | ||
| 20 | /// Note the period in clock cycles of an output can be computed as: | ||
| 21 | /// `(top + 1) * (phase_correct ? 1 : 2) * divider * prescale_factor` | ||
| 22 | /// By default, the clock used is 96 MHz. | ||
| 23 | #[non_exhaustive] | ||
| 24 | #[derive(Clone)] | ||
| 25 | pub struct Config { | ||
| 26 | /// Inverts the PWM output signal. | ||
| 27 | pub invert: bool, | ||
| 28 | /// Enables phase-correct mode for PWM operation. | ||
| 29 | /// In phase-correct mode, the PWM signal is generated in such a way that | ||
| 30 | /// the pulse is always centered regardless of the duty cycle. | ||
| 31 | /// The output frequency is halved when phase-correct mode is enabled. | ||
| 32 | pub phase_correct: bool, | ||
| 33 | /// Enables the PWM output, allowing it to generate an output. | ||
| 34 | pub enable: bool, | ||
| 35 | /// A SYSCON clock divider allows precise control over | ||
| 36 | /// the PWM output frequency by gating the PWM counter increment. | ||
| 37 | /// A higher value will result in a slower output frequency. | ||
| 38 | /// The clock is divided by `divider + 1`. | ||
| 39 | pub divider: u8, | ||
| 40 | /// Specifies the factor by which the SCT clock is prescaled to produce the unified | ||
| 41 | /// counter clock. The counter clock is clocked at the rate of the SCT clock divided by | ||
| 42 | /// `PRE + 1`. | ||
| 43 | pub prescale_factor: u8, | ||
| 44 | /// The output goes high when `compare` is higher than the | ||
| 45 | /// counter. A compare of 0 will produce an always low output, while a | ||
| 46 | /// compare of `top` will produce an always high output. | ||
| 47 | pub compare: u32, | ||
| 48 | /// The point at which the counter resets, representing the maximum possible | ||
| 49 | /// period. The counter will either wrap to 0 or reverse depending on the | ||
| 50 | /// setting of `phase_correct`. | ||
| 51 | pub top: u32, | ||
| 52 | } | ||
| 53 | |||
| 54 | impl Config { | ||
| 55 | pub fn new(compare: u32, top: u32) -> Self { | ||
| 56 | Self { | ||
| 57 | invert: false, | ||
| 58 | phase_correct: false, | ||
| 59 | enable: true, | ||
| 60 | divider: 255, | ||
| 61 | prescale_factor: 255, | ||
| 62 | compare, | ||
| 63 | top, | ||
| 64 | } | ||
| 65 | } | ||
| 66 | } | ||
| 67 | |||
| 68 | /// PWM driver. | ||
| 69 | pub struct Pwm<'d> { | ||
| 70 | _pin: Peri<'d, AnyPin>, | ||
| 71 | output: usize, | ||
| 72 | } | ||
| 73 | |||
| 74 | impl<'d> Pwm<'d> { | ||
| 75 | pub(crate) fn reset() { | ||
| 76 | // Reset SCTimer => Reset counter and halt it. | ||
| 77 | // It should be done only once during the initialization of the board. | ||
| 78 | SYSCON.presetctrl1().modify(|w| w.set_sct_rst(SctRst::ASSERTED)); | ||
| 79 | SYSCON.presetctrl1().modify(|w| w.set_sct_rst(SctRst::RELEASED)); | ||
| 80 | } | ||
| 81 | fn new_inner<T: sct::Instance, O: sct::Output<T>>( | ||
| 82 | output: usize, | ||
| 83 | channel: Peri<'d, impl sct::OutputPin<T, O>>, | ||
| 84 | config: Config, | ||
| 85 | ) -> Self { | ||
| 86 | // Enable clocks (Syscon is enabled by default) | ||
| 87 | critical_section::with(|_cs| { | ||
| 88 | if !SYSCON.ahbclkctrl0().read().iocon() { | ||
| 89 | SYSCON.ahbclkctrl0().modify(|w| w.set_iocon(true)); | ||
| 90 | } | ||
| 91 | if !SYSCON.ahbclkctrl1().read().sct() { | ||
| 92 | SYSCON.ahbclkctrl1().modify(|w| w.set_sct(true)); | ||
| 93 | } | ||
| 94 | }); | ||
| 95 | |||
| 96 | // Choose the clock for PWM. | ||
| 97 | SYSCON.sctclksel().modify(|w| w.set_sel(SctclkselSel::ENUM_0X3)); | ||
| 98 | // For now, 96 MHz. | ||
| 99 | |||
| 100 | // IOCON Setup | ||
| 101 | channel.pio().modify(|w| { | ||
| 102 | w.set_func(channel.pin_func()); | ||
| 103 | w.set_digimode(PioDigimode::DIGITAL); | ||
| 104 | w.set_slew(PioSlew::STANDARD); | ||
| 105 | w.set_mode(PioMode::INACTIVE); | ||
| 106 | w.set_od(PioOd::NORMAL); | ||
| 107 | }); | ||
| 108 | |||
| 109 | Self::configure(output, &config); | ||
| 110 | REF_COUNT.fetch_add(1, Ordering::Relaxed); | ||
| 111 | Self { | ||
| 112 | _pin: channel.into(), | ||
| 113 | output, | ||
| 114 | } | ||
| 115 | } | ||
| 116 | |||
| 117 | /// Create PWM driver with a single 'a' pin as output. | ||
| 118 | #[inline] | ||
| 119 | pub fn new_output<T: sct::Instance, O: sct::Output<T>>( | ||
| 120 | output: Peri<'d, O>, | ||
| 121 | channel: Peri<'d, impl sct::OutputPin<T, O>>, | ||
| 122 | config: Config, | ||
| 123 | ) -> Self { | ||
| 124 | Self::new_inner::<T, O>(output.number(), channel, config) | ||
| 125 | } | ||
| 126 | |||
| 127 | /// Set the PWM config. | ||
| 128 | pub fn set_config(&mut self, config: &Config) { | ||
| 129 | Self::configure(self.output, config); | ||
| 130 | } | ||
| 131 | |||
| 132 | fn configure(output_number: usize, config: &Config) { | ||
| 133 | // Stop and reset the counter | ||
| 134 | SCT0.ctrl().modify(|w| { | ||
| 135 | if config.phase_correct { | ||
| 136 | w.set_bidir_l(vals::Bidir::UP_DOWN); | ||
| 137 | } else { | ||
| 138 | w.set_bidir_l(vals::Bidir::UP); | ||
| 139 | } | ||
| 140 | w.set_halt_l(true); // halt the counter to make new changes | ||
| 141 | w.set_clrctr_l(true); // clear the counter | ||
| 142 | }); | ||
| 143 | // Divides clock by 1-255 | ||
| 144 | SYSCON.sctclkdiv().modify(|w| w.set_div(config.divider)); | ||
| 145 | |||
| 146 | SCT0.config().modify(|w| { | ||
| 147 | w.set_unify(vals::Unify::UNIFIED_COUNTER); | ||
| 148 | w.set_clkmode(vals::Clkmode::SYSTEM_CLOCK_MODE); | ||
| 149 | w.set_noreload_l(true); | ||
| 150 | w.set_autolimit_l(true); | ||
| 151 | }); | ||
| 152 | |||
| 153 | // Before setting the match registers, we have to make sure that `compare` is lower or equal to `top`, | ||
| 154 | // otherwise the counter will not reach the match and, therefore, no events will happen. | ||
| 155 | assert!(config.compare <= config.top); | ||
| 156 | |||
| 157 | if TOP_VALUE.load(Ordering::Relaxed) == 0 { | ||
| 158 | // Match 0 will reset the timer using TOP value | ||
| 159 | SCT0.match_(0).modify(|w| { | ||
| 160 | w.set_matchn_l((config.top & 0xFFFF) as u16); | ||
| 161 | w.set_matchn_h((config.top >> 16) as u16); | ||
| 162 | }); | ||
| 163 | } else { | ||
| 164 | panic!("The top value cannot be changed after the initialization."); | ||
| 165 | } | ||
| 166 | // The actual matches that are used for event logic | ||
| 167 | SCT0.match_(output_number + 1).modify(|w| { | ||
| 168 | w.set_matchn_l((config.compare & 0xFFFF) as u16); | ||
| 169 | w.set_matchn_h((config.compare >> 16) as u16); | ||
| 170 | }); | ||
| 171 | |||
| 172 | SCT0.match_(15).modify(|w| { | ||
| 173 | w.set_matchn_l(0); | ||
| 174 | w.set_matchn_h(0); | ||
| 175 | }); | ||
| 176 | |||
| 177 | // Event configuration | ||
| 178 | critical_section::with(|_cs| { | ||
| 179 | // If it is already set, don't change | ||
| 180 | if SCT0.ev(0).ev_ctrl().read().matchsel() != 15 { | ||
| 181 | SCT0.ev(0).ev_ctrl().modify(|w| { | ||
| 182 | w.set_matchsel(15); | ||
| 183 | w.set_combmode(vals::Combmode::MATCH); | ||
| 184 | // STATE + statev, where STATE is a on-board variable. | ||
| 185 | w.set_stateld(vals::Stateld::ADD); | ||
| 186 | w.set_statev(0); | ||
| 187 | }); | ||
| 188 | } | ||
| 189 | }); | ||
| 190 | SCT0.ev(output_number + 1).ev_ctrl().modify(|w| { | ||
| 191 | w.set_matchsel((output_number + 1) as u8); | ||
| 192 | w.set_combmode(vals::Combmode::MATCH); | ||
| 193 | w.set_stateld(vals::Stateld::ADD); | ||
| 194 | // STATE + statev, where STATE is a on-board variable. | ||
| 195 | w.set_statev(0); | ||
| 196 | }); | ||
| 197 | |||
| 198 | // Assign events to states | ||
| 199 | SCT0.ev(0).ev_state().modify(|w| w.set_statemskn(1 << 0)); | ||
| 200 | SCT0.ev(output_number + 1) | ||
| 201 | .ev_state() | ||
| 202 | .modify(|w| w.set_statemskn(1 << 0)); | ||
| 203 | // TODO(frihetselsker): optimize nxp-pac so that `set_clr` and `set_set` are turned into a bit array. | ||
| 204 | if config.invert { | ||
| 205 | // Low when event 0 is active | ||
| 206 | SCT0.out(output_number).out_clr().modify(|w| w.set_clr(0, true)); | ||
| 207 | // High when event `output_number + 1` is active | ||
| 208 | SCT0.out(output_number) | ||
| 209 | .out_set() | ||
| 210 | .modify(|w| w.set_set(output_number, true)); | ||
| 211 | } else { | ||
| 212 | // High when event 0 is active | ||
| 213 | SCT0.out(output_number).out_set().modify(|w| w.set_set(0, true)); | ||
| 214 | // Low when event `output_number + 1` is active | ||
| 215 | SCT0.out(output_number) | ||
| 216 | .out_clr() | ||
| 217 | .modify(|w| w.set_clr(output_number, true)); | ||
| 218 | } | ||
| 219 | |||
| 220 | if config.phase_correct { | ||
| 221 | // Take into account the set matches and reverse their actions while counting back. | ||
| 222 | SCT0.outputdirctrl() | ||
| 223 | .modify(|w| w.set_setclr(output_number, vals::Setclr::L_REVERSED)); | ||
| 224 | } | ||
| 225 | |||
| 226 | // State 0 by default | ||
| 227 | SCT0.state().modify(|w| w.set_state_l(0)); | ||
| 228 | // Remove halt and start the actual counter | ||
| 229 | SCT0.ctrl().modify(|w| { | ||
| 230 | w.set_halt_l(!config.enable); | ||
| 231 | }); | ||
| 232 | } | ||
| 233 | |||
| 234 | /// Read PWM counter. | ||
| 235 | #[inline] | ||
| 236 | pub fn counter(&self) -> u32 { | ||
| 237 | SCT0.count().read().0 | ||
| 238 | } | ||
| 239 | } | ||
| 240 | |||
| 241 | impl<'d> Drop for Pwm<'d> { | ||
| 242 | fn drop(&mut self) { | ||
| 243 | REF_COUNT.fetch_sub(1, Ordering::AcqRel); | ||
| 244 | if REF_COUNT.load(Ordering::Acquire) == 0 { | ||
| 245 | TOP_VALUE.store(0, Ordering::Release); | ||
| 246 | } | ||
| 247 | } | ||
| 248 | } | ||
