diff options
| author | i509VCB <[email protected]> | 2025-10-25 02:23:51 +0000 |
|---|---|---|
| committer | GitHub <[email protected]> | 2025-10-25 02:23:51 +0000 |
| commit | c8c4c6f40bd8a2e548f3c8e06b798d448f67b884 (patch) | |
| tree | f0c7c2462234d04a951c7f0376a98206b24abacd /embassy-nxp/src | |
| parent | 4bff7cea1a26267ec3671250e954d9d4242fabde (diff) | |
| parent | 5ed604fc0453066f0d0cf0c161823df5f4b7900f (diff) | |
Merge pull request #4738 from WyliodrinEmbeddedIoT/pwm-dev
lpc55: pwm simple
Diffstat (limited to 'embassy-nxp/src')
| -rw-r--r-- | embassy-nxp/src/chips/lpc55.rs | 12 | ||||
| -rw-r--r-- | embassy-nxp/src/fmt.rs | 1 | ||||
| -rw-r--r-- | embassy-nxp/src/lib.rs | 7 | ||||
| -rw-r--r-- | embassy-nxp/src/pwm.rs | 5 | ||||
| -rw-r--r-- | embassy-nxp/src/pwm/lpc55.rs | 325 |
5 files changed, 348 insertions, 2 deletions
diff --git a/embassy-nxp/src/chips/lpc55.rs b/embassy-nxp/src/chips/lpc55.rs index 9f4e7269f..e9addddb6 100644 --- a/embassy-nxp/src/chips/lpc55.rs +++ b/embassy-nxp/src/chips/lpc55.rs | |||
| @@ -97,6 +97,18 @@ embassy_hal_internal::peripherals! { | |||
| 97 | DMA_CH21, | 97 | DMA_CH21, |
| 98 | DMA_CH22, | 98 | DMA_CH22, |
| 99 | 99 | ||
| 100 | // Pulse-Width Modulation Outputs. | ||
| 101 | PWM_OUTPUT0, | ||
| 102 | PWM_OUTPUT1, | ||
| 103 | PWM_OUTPUT2, | ||
| 104 | PWM_OUTPUT3, | ||
| 105 | PWM_OUTPUT4, | ||
| 106 | PWM_OUTPUT5, | ||
| 107 | PWM_OUTPUT6, | ||
| 108 | PWM_OUTPUT7, | ||
| 109 | PWM_OUTPUT8, | ||
| 110 | PWM_OUTPUT9, | ||
| 111 | |||
| 100 | // Universal Synchronous/Asynchronous Receiver/Transmitter (USART) instances. | 112 | // Universal Synchronous/Asynchronous Receiver/Transmitter (USART) instances. |
| 101 | USART0, | 113 | USART0, |
| 102 | USART1, | 114 | USART1, |
diff --git a/embassy-nxp/src/fmt.rs b/embassy-nxp/src/fmt.rs index 27d41ace6..11275235e 100644 --- a/embassy-nxp/src/fmt.rs +++ b/embassy-nxp/src/fmt.rs | |||
| @@ -1,5 +1,4 @@ | |||
| 1 | //! Copied from embassy-rp | 1 | //! Copied from embassy-rp |
| 2 | |||
| 3 | #![macro_use] | 2 | #![macro_use] |
| 4 | #![allow(unused)] | 3 | #![allow(unused)] |
| 5 | 4 | ||
diff --git a/embassy-nxp/src/lib.rs b/embassy-nxp/src/lib.rs index 9576f02b1..4058881a5 100644 --- a/embassy-nxp/src/lib.rs +++ b/embassy-nxp/src/lib.rs | |||
| @@ -10,6 +10,8 @@ pub mod gpio; | |||
| 10 | #[cfg(feature = "lpc55-core0")] | 10 | #[cfg(feature = "lpc55-core0")] |
| 11 | pub mod pint; | 11 | pub mod pint; |
| 12 | #[cfg(feature = "lpc55-core0")] | 12 | #[cfg(feature = "lpc55-core0")] |
| 13 | pub mod pwm; | ||
| 14 | #[cfg(feature = "lpc55-core0")] | ||
| 13 | pub mod usart; | 15 | pub mod usart; |
| 14 | 16 | ||
| 15 | #[cfg(feature = "_time_driver")] | 17 | #[cfg(feature = "_time_driver")] |
| @@ -154,7 +156,10 @@ pub fn init(_config: config::Config) -> Peripherals { | |||
| 154 | gpio::init(); | 156 | gpio::init(); |
| 155 | 157 | ||
| 156 | #[cfg(feature = "lpc55-core0")] | 158 | #[cfg(feature = "lpc55-core0")] |
| 157 | pint::init(); | 159 | { |
| 160 | pint::init(); | ||
| 161 | pwm::Pwm::reset(); | ||
| 162 | } | ||
| 158 | 163 | ||
| 159 | #[cfg(feature = "_time_driver")] | 164 | #[cfg(feature = "_time_driver")] |
| 160 | time_driver::init(); | 165 | time_driver::init(); |
diff --git a/embassy-nxp/src/pwm.rs b/embassy-nxp/src/pwm.rs new file mode 100644 index 000000000..68980924a --- /dev/null +++ b/embassy-nxp/src/pwm.rs | |||
| @@ -0,0 +1,5 @@ | |||
| 1 | //! Pulse-Width Modulation (PWM) driver. | ||
| 2 | |||
| 3 | #[cfg_attr(feature = "lpc55-core0", path = "./pwm/lpc55.rs")] | ||
| 4 | mod inner; | ||
| 5 | pub use inner::*; | ||
diff --git a/embassy-nxp/src/pwm/lpc55.rs b/embassy-nxp/src/pwm/lpc55.rs new file mode 100644 index 000000000..197184ad6 --- /dev/null +++ b/embassy-nxp/src/pwm/lpc55.rs | |||
| @@ -0,0 +1,325 @@ | |||
| 1 | use core::sync::atomic::{AtomicU8, AtomicU32, Ordering}; | ||
| 2 | |||
| 3 | use embassy_hal_internal::{Peri, PeripheralType}; | ||
| 4 | |||
| 5 | use crate::gpio::AnyPin; | ||
| 6 | use crate::pac::iocon::vals::{PioDigimode, PioFunc, PioMode, PioOd, PioSlew}; | ||
| 7 | use crate::pac::sct0::vals; | ||
| 8 | use crate::pac::syscon::vals::{SctRst, SctclkselSel}; | ||
| 9 | use crate::pac::{SCT0, SYSCON}; | ||
| 10 | |||
| 11 | // Since for now the counter is shared, the TOP value has to be kept. | ||
| 12 | static TOP_VALUE: AtomicU32 = AtomicU32::new(0); | ||
| 13 | // To check if there are still active instances. | ||
| 14 | static REF_COUNT: AtomicU8 = AtomicU8::new(0); | ||
| 15 | |||
| 16 | /// The configuration of a PWM output. | ||
| 17 | /// Note the period in clock cycles of an output can be computed as: | ||
| 18 | /// `(top + 1) * (phase_correct ? 1 : 2) * divider * prescale_factor` | ||
| 19 | /// By default, the clock used is 96 MHz. | ||
| 20 | #[non_exhaustive] | ||
| 21 | #[derive(Clone)] | ||
| 22 | pub struct Config { | ||
| 23 | /// Inverts the PWM output signal. | ||
| 24 | pub invert: bool, | ||
| 25 | /// Enables phase-correct mode for PWM operation. | ||
| 26 | /// In phase-correct mode, the PWM signal is generated in such a way that | ||
| 27 | /// the pulse is always centered regardless of the duty cycle. | ||
| 28 | /// The output frequency is halved when phase-correct mode is enabled. | ||
| 29 | pub phase_correct: bool, | ||
| 30 | /// Enables the PWM output, allowing it to generate an output. | ||
| 31 | pub enable: bool, | ||
| 32 | /// A SYSCON clock divider allows precise control over | ||
| 33 | /// the PWM output frequency by gating the PWM counter increment. | ||
| 34 | /// A higher value will result in a slower output frequency. | ||
| 35 | /// The clock is divided by `divider + 1`. | ||
| 36 | pub divider: u8, | ||
| 37 | /// Specifies the factor by which the SCT clock is prescaled to produce the unified | ||
| 38 | /// counter clock. The counter clock is clocked at the rate of the SCT clock divided by | ||
| 39 | /// `PRE + 1`. | ||
| 40 | pub prescale_factor: u8, | ||
| 41 | /// The output goes high when `compare` is higher than the | ||
| 42 | /// counter. A compare of 0 will produce an always low output, while a | ||
| 43 | /// compare of `top` will produce an always high output. | ||
| 44 | pub compare: u32, | ||
| 45 | /// The point at which the counter resets, representing the maximum possible | ||
| 46 | /// period. The counter will either wrap to 0 or reverse depending on the | ||
| 47 | /// setting of `phase_correct`. | ||
| 48 | pub top: u32, | ||
| 49 | } | ||
| 50 | |||
| 51 | impl Config { | ||
| 52 | pub fn new(compare: u32, top: u32) -> Self { | ||
| 53 | Self { | ||
| 54 | invert: false, | ||
| 55 | phase_correct: false, | ||
| 56 | enable: true, | ||
| 57 | divider: 255, | ||
| 58 | prescale_factor: 255, | ||
| 59 | compare, | ||
| 60 | top, | ||
| 61 | } | ||
| 62 | } | ||
| 63 | } | ||
| 64 | |||
| 65 | /// PWM driver. | ||
| 66 | pub struct Pwm<'d> { | ||
| 67 | _pin: Peri<'d, AnyPin>, | ||
| 68 | output: usize, | ||
| 69 | } | ||
| 70 | |||
| 71 | impl<'d> Pwm<'d> { | ||
| 72 | pub(crate) fn reset() { | ||
| 73 | // Reset SCTimer => Reset counter and halt it. | ||
| 74 | // It should be done only once during the initialization of the board. | ||
| 75 | SYSCON.presetctrl1().modify(|w| w.set_sct_rst(SctRst::ASSERTED)); | ||
| 76 | SYSCON.presetctrl1().modify(|w| w.set_sct_rst(SctRst::RELEASED)); | ||
| 77 | } | ||
| 78 | fn new_inner<T: Output>(output: usize, channel: Peri<'d, impl OutputChannelPin<T>>, config: Config) -> Self { | ||
| 79 | // Enable clocks (Syscon is enabled by default) | ||
| 80 | critical_section::with(|_cs| { | ||
| 81 | if !SYSCON.ahbclkctrl0().read().iocon() { | ||
| 82 | SYSCON.ahbclkctrl0().modify(|w| w.set_iocon(true)); | ||
| 83 | } | ||
| 84 | if !SYSCON.ahbclkctrl1().read().sct() { | ||
| 85 | SYSCON.ahbclkctrl1().modify(|w| w.set_sct(true)); | ||
| 86 | } | ||
| 87 | }); | ||
| 88 | |||
| 89 | // Choose the clock for PWM. | ||
| 90 | SYSCON.sctclksel().modify(|w| w.set_sel(SctclkselSel::ENUM_0X3)); | ||
| 91 | // For now, 96 MHz. | ||
| 92 | |||
| 93 | // IOCON Setup | ||
| 94 | channel.pio().modify(|w| { | ||
| 95 | w.set_func(channel.pin_func()); | ||
| 96 | w.set_digimode(PioDigimode::DIGITAL); | ||
| 97 | w.set_slew(PioSlew::STANDARD); | ||
| 98 | w.set_mode(PioMode::INACTIVE); | ||
| 99 | w.set_od(PioOd::NORMAL); | ||
| 100 | }); | ||
| 101 | |||
| 102 | Self::configure(output, &config); | ||
| 103 | REF_COUNT.fetch_add(1, Ordering::Relaxed); | ||
| 104 | Self { | ||
| 105 | _pin: channel.into(), | ||
| 106 | output, | ||
| 107 | } | ||
| 108 | } | ||
| 109 | |||
| 110 | /// Create PWM driver with a single 'a' pin as output. | ||
| 111 | #[inline] | ||
| 112 | pub fn new_output<T: Output>( | ||
| 113 | output: Peri<'d, T>, | ||
| 114 | channel: Peri<'d, impl OutputChannelPin<T>>, | ||
| 115 | config: Config, | ||
| 116 | ) -> Self { | ||
| 117 | Self::new_inner(output.number(), channel, config) | ||
| 118 | } | ||
| 119 | |||
| 120 | /// Set the PWM config. | ||
| 121 | pub fn set_config(&mut self, config: &Config) { | ||
| 122 | Self::configure(self.output, config); | ||
| 123 | } | ||
| 124 | |||
| 125 | fn configure(output_number: usize, config: &Config) { | ||
| 126 | // Stop and reset the counter | ||
| 127 | SCT0.ctrl().modify(|w| { | ||
| 128 | if config.phase_correct { | ||
| 129 | w.set_bidir_l(vals::Bidir::UP_DOWN); | ||
| 130 | } else { | ||
| 131 | w.set_bidir_l(vals::Bidir::UP); | ||
| 132 | } | ||
| 133 | w.set_halt_l(true); // halt the counter to make new changes | ||
| 134 | w.set_clrctr_l(true); // clear the counter | ||
| 135 | }); | ||
| 136 | // Divides clock by 1-255 | ||
| 137 | SYSCON.sctclkdiv().modify(|w| w.set_div(config.divider)); | ||
| 138 | |||
| 139 | SCT0.config().modify(|w| { | ||
| 140 | w.set_unify(vals::Unify::UNIFIED_COUNTER); | ||
| 141 | w.set_clkmode(vals::Clkmode::SYSTEM_CLOCK_MODE); | ||
| 142 | w.set_noreload_l(true); | ||
| 143 | w.set_autolimit_l(true); | ||
| 144 | }); | ||
| 145 | |||
| 146 | // Before setting the match registers, we have to make sure that `compare` is lower or equal to `top`, | ||
| 147 | // otherwise the counter will not reach the match and, therefore, no events will happen. | ||
| 148 | assert!(config.compare <= config.top); | ||
| 149 | |||
| 150 | if TOP_VALUE.load(Ordering::Relaxed) == 0 { | ||
| 151 | // Match 0 will reset the timer using TOP value | ||
| 152 | SCT0.match_(0).modify(|w| { | ||
| 153 | w.set_matchn_l((config.top & 0xFFFF) as u16); | ||
| 154 | w.set_matchn_h((config.top >> 16) as u16); | ||
| 155 | }); | ||
| 156 | } else { | ||
| 157 | panic!("The top value cannot be changed after the initialization."); | ||
| 158 | } | ||
| 159 | // The actual matches that are used for event logic | ||
| 160 | SCT0.match_(output_number + 1).modify(|w| { | ||
| 161 | w.set_matchn_l((config.compare & 0xFFFF) as u16); | ||
| 162 | w.set_matchn_h((config.compare >> 16) as u16); | ||
| 163 | }); | ||
| 164 | |||
| 165 | SCT0.match_(15).modify(|w| { | ||
| 166 | w.set_matchn_l(0); | ||
| 167 | w.set_matchn_h(0); | ||
| 168 | }); | ||
| 169 | |||
| 170 | // Event configuration | ||
| 171 | critical_section::with(|_cs| { | ||
| 172 | // If it is already set, don't change | ||
| 173 | if SCT0.ev(0).ev_ctrl().read().matchsel() != 15 { | ||
| 174 | SCT0.ev(0).ev_ctrl().modify(|w| { | ||
| 175 | w.set_matchsel(15); | ||
| 176 | w.set_combmode(vals::Combmode::MATCH); | ||
| 177 | // STATE + statev, where STATE is a on-board variable. | ||
| 178 | w.set_stateld(vals::Stateld::ADD); | ||
| 179 | w.set_statev(0); | ||
| 180 | }); | ||
| 181 | } | ||
| 182 | }); | ||
| 183 | SCT0.ev(output_number + 1).ev_ctrl().modify(|w| { | ||
| 184 | w.set_matchsel((output_number + 1) as u8); | ||
| 185 | w.set_combmode(vals::Combmode::MATCH); | ||
| 186 | w.set_stateld(vals::Stateld::ADD); | ||
| 187 | // STATE + statev, where STATE is a on-board variable. | ||
| 188 | w.set_statev(0); | ||
| 189 | }); | ||
| 190 | |||
| 191 | // Assign events to states | ||
| 192 | SCT0.ev(0).ev_state().modify(|w| w.set_statemskn(1 << 0)); | ||
| 193 | SCT0.ev(output_number + 1) | ||
| 194 | .ev_state() | ||
| 195 | .modify(|w| w.set_statemskn(1 << 0)); | ||
| 196 | // TODO(frihetselsker): optimize nxp-pac so that `set_clr` and `set_set` are turned into a bit array. | ||
| 197 | if config.invert { | ||
| 198 | // Low when event 0 is active | ||
| 199 | SCT0.out(output_number).out_clr().modify(|w| w.set_clr(1 << 0)); | ||
| 200 | // High when event `output_number + 1` is active | ||
| 201 | SCT0.out(output_number) | ||
| 202 | .out_set() | ||
| 203 | .modify(|w| w.set_set(1 << (output_number + 1))); | ||
| 204 | } else { | ||
| 205 | // High when event 0 is active | ||
| 206 | SCT0.out(output_number).out_set().modify(|w| w.set_set(1 << 0)); | ||
| 207 | // Low when event `output_number + 1` is active | ||
| 208 | SCT0.out(output_number) | ||
| 209 | .out_clr() | ||
| 210 | .modify(|w| w.set_clr(1 << (output_number + 1))); | ||
| 211 | } | ||
| 212 | |||
| 213 | if config.phase_correct { | ||
| 214 | // Take into account the set matches and reverse their actions while counting back. | ||
| 215 | SCT0.outputdirctrl() | ||
| 216 | .modify(|w| w.set_setclr(output_number, vals::Setclr::L_REVERSED)); | ||
| 217 | } | ||
| 218 | |||
| 219 | // State 0 by default | ||
| 220 | SCT0.state().modify(|w| w.set_state_l(0)); | ||
| 221 | // Remove halt and start the actual counter | ||
| 222 | SCT0.ctrl().modify(|w| { | ||
| 223 | w.set_halt_l(!config.enable); | ||
| 224 | }); | ||
| 225 | } | ||
| 226 | |||
| 227 | /// Read PWM counter. | ||
| 228 | #[inline] | ||
| 229 | pub fn counter(&self) -> u32 { | ||
| 230 | SCT0.count().read().0 | ||
| 231 | } | ||
| 232 | } | ||
| 233 | |||
| 234 | impl<'d> Drop for Pwm<'d> { | ||
| 235 | fn drop(&mut self) { | ||
| 236 | REF_COUNT.fetch_sub(1, Ordering::AcqRel); | ||
| 237 | if REF_COUNT.load(Ordering::Acquire) == 0 { | ||
| 238 | TOP_VALUE.store(0, Ordering::Release); | ||
| 239 | } | ||
| 240 | } | ||
| 241 | } | ||
| 242 | |||
| 243 | trait SealedOutput { | ||
| 244 | /// Output number. | ||
| 245 | fn number(&self) -> usize; | ||
| 246 | } | ||
| 247 | |||
| 248 | /// PWM Output. | ||
| 249 | #[allow(private_bounds)] | ||
| 250 | pub trait Output: PeripheralType + SealedOutput {} | ||
| 251 | |||
| 252 | macro_rules! output { | ||
| 253 | ($name:ident, $num:expr) => { | ||
| 254 | impl SealedOutput for crate::peripherals::$name { | ||
| 255 | fn number(&self) -> usize { | ||
| 256 | $num | ||
| 257 | } | ||
| 258 | } | ||
| 259 | impl Output for crate::peripherals::$name {} | ||
| 260 | }; | ||
| 261 | } | ||
| 262 | |||
| 263 | output!(PWM_OUTPUT0, 0); | ||
| 264 | output!(PWM_OUTPUT1, 1); | ||
| 265 | output!(PWM_OUTPUT2, 2); | ||
| 266 | output!(PWM_OUTPUT3, 3); | ||
| 267 | output!(PWM_OUTPUT4, 4); | ||
| 268 | output!(PWM_OUTPUT5, 5); | ||
| 269 | output!(PWM_OUTPUT6, 6); | ||
| 270 | output!(PWM_OUTPUT7, 7); | ||
| 271 | output!(PWM_OUTPUT8, 8); | ||
| 272 | output!(PWM_OUTPUT9, 9); | ||
| 273 | |||
| 274 | /// PWM Output Channel. | ||
| 275 | pub trait OutputChannelPin<T: Output>: crate::gpio::Pin { | ||
| 276 | fn pin_func(&self) -> PioFunc; | ||
| 277 | } | ||
| 278 | |||
| 279 | macro_rules! impl_pin { | ||
| 280 | ($pin:ident, $output:ident, $func:ident) => { | ||
| 281 | impl crate::pwm::inner::OutputChannelPin<crate::peripherals::$output> for crate::peripherals::$pin { | ||
| 282 | fn pin_func(&self) -> PioFunc { | ||
| 283 | PioFunc::$func | ||
| 284 | } | ||
| 285 | } | ||
| 286 | }; | ||
| 287 | } | ||
| 288 | |||
| 289 | impl_pin!(PIO0_2, PWM_OUTPUT0, ALT3); | ||
| 290 | impl_pin!(PIO0_17, PWM_OUTPUT0, ALT4); | ||
| 291 | impl_pin!(PIO1_4, PWM_OUTPUT0, ALT4); | ||
| 292 | impl_pin!(PIO1_23, PWM_OUTPUT0, ALT2); | ||
| 293 | |||
| 294 | impl_pin!(PIO0_3, PWM_OUTPUT1, ALT3); | ||
| 295 | impl_pin!(PIO0_18, PWM_OUTPUT1, ALT4); | ||
| 296 | impl_pin!(PIO1_8, PWM_OUTPUT1, ALT4); | ||
| 297 | impl_pin!(PIO1_24, PWM_OUTPUT1, ALT2); | ||
| 298 | |||
| 299 | impl_pin!(PIO0_10, PWM_OUTPUT2, ALT5); | ||
| 300 | impl_pin!(PIO0_15, PWM_OUTPUT2, ALT4); | ||
| 301 | impl_pin!(PIO0_19, PWM_OUTPUT2, ALT4); | ||
| 302 | impl_pin!(PIO1_9, PWM_OUTPUT2, ALT4); | ||
| 303 | impl_pin!(PIO1_25, PWM_OUTPUT2, ALT2); | ||
| 304 | |||
| 305 | impl_pin!(PIO0_22, PWM_OUTPUT3, ALT4); | ||
| 306 | impl_pin!(PIO0_31, PWM_OUTPUT3, ALT4); | ||
| 307 | impl_pin!(PIO1_10, PWM_OUTPUT3, ALT4); | ||
| 308 | impl_pin!(PIO1_26, PWM_OUTPUT3, ALT2); | ||
| 309 | |||
| 310 | impl_pin!(PIO0_23, PWM_OUTPUT4, ALT4); | ||
| 311 | impl_pin!(PIO1_3, PWM_OUTPUT4, ALT4); | ||
| 312 | impl_pin!(PIO1_17, PWM_OUTPUT4, ALT4); | ||
| 313 | |||
| 314 | impl_pin!(PIO0_26, PWM_OUTPUT5, ALT4); | ||
| 315 | impl_pin!(PIO1_18, PWM_OUTPUT5, ALT4); | ||
| 316 | |||
| 317 | impl_pin!(PIO0_27, PWM_OUTPUT6, ALT4); | ||
| 318 | impl_pin!(PIO1_31, PWM_OUTPUT6, ALT4); | ||
| 319 | |||
| 320 | impl_pin!(PIO0_28, PWM_OUTPUT7, ALT4); | ||
| 321 | impl_pin!(PIO1_19, PWM_OUTPUT7, ALT2); | ||
| 322 | |||
| 323 | impl_pin!(PIO0_29, PWM_OUTPUT8, ALT4); | ||
| 324 | |||
| 325 | impl_pin!(PIO0_30, PWM_OUTPUT9, ALT4); | ||
