aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authori509VCB <[email protected]>2025-10-25 02:23:51 +0000
committerGitHub <[email protected]>2025-10-25 02:23:51 +0000
commitc8c4c6f40bd8a2e548f3c8e06b798d448f67b884 (patch)
treef0c7c2462234d04a951c7f0376a98206b24abacd
parent4bff7cea1a26267ec3671250e954d9d4242fabde (diff)
parent5ed604fc0453066f0d0cf0c161823df5f4b7900f (diff)
Merge pull request #4738 from WyliodrinEmbeddedIoT/pwm-dev
lpc55: pwm simple
-rw-r--r--embassy-nxp/CHANGELOG.md1
-rw-r--r--embassy-nxp/Cargo.toml4
-rw-r--r--embassy-nxp/src/chips/lpc55.rs12
-rw-r--r--embassy-nxp/src/fmt.rs1
-rw-r--r--embassy-nxp/src/lib.rs7
-rw-r--r--embassy-nxp/src/pwm.rs5
-rw-r--r--embassy-nxp/src/pwm/lpc55.rs325
-rw-r--r--examples/lpc55s69/src/bin/pwm.rs18
8 files changed, 369 insertions, 4 deletions
diff --git a/embassy-nxp/CHANGELOG.md b/embassy-nxp/CHANGELOG.md
index ad8670854..39f5c75bd 100644
--- a/embassy-nxp/CHANGELOG.md
+++ b/embassy-nxp/CHANGELOG.md
@@ -7,6 +7,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
7 7
8<!-- next-header --> 8<!-- next-header -->
9## Unreleased - ReleaseDate 9## Unreleased - ReleaseDate
10- LPC55: PWM simple
10- LPC55: Move ALT definitions for USART to TX/RX pin impls. 11- LPC55: Move ALT definitions for USART to TX/RX pin impls.
11- LPC55: Remove internal match_iocon macro 12- LPC55: Remove internal match_iocon macro
12- LPC55: DMA Controller and asynchronous version of USART 13- LPC55: DMA Controller and asynchronous version of USART
diff --git a/embassy-nxp/Cargo.toml b/embassy-nxp/Cargo.toml
index 33f0f2dff..f8c63ba29 100644
--- a/embassy-nxp/Cargo.toml
+++ b/embassy-nxp/Cargo.toml
@@ -38,13 +38,13 @@ embassy-time-queue-utils = { version = "0.3.0", path = "../embassy-time-queue-ut
38embedded-io = "0.6.1" 38embedded-io = "0.6.1"
39embedded-hal-02 = { package = "embedded-hal", version = "0.2.6", features = ["unproven"] } 39embedded-hal-02 = { package = "embedded-hal", version = "0.2.6", features = ["unproven"] }
40## Chip dependencies 40## Chip dependencies
41nxp-pac = { version = "0.1.0", optional = true, git = "https://github.com/i509VCB/nxp-pac", rev = "b736e3038254d593024aaa1a5a7b1f95a5728538"} 41nxp-pac = { version = "0.1.0", optional = true, git = "https://github.com/i509VCB/nxp-pac", rev = "477dfdbfd5e6c75c0730c56494b601c1b2257263"}
42 42
43imxrt-rt = { version = "0.1.7", optional = true, features = ["device"] } 43imxrt-rt = { version = "0.1.7", optional = true, features = ["device"] }
44 44
45[build-dependencies] 45[build-dependencies]
46cfg_aliases = "0.2.1" 46cfg_aliases = "0.2.1"
47nxp-pac = { version = "0.1.0", git = "https://github.com/i509VCB/nxp-pac", rev = "b736e3038254d593024aaa1a5a7b1f95a5728538", features = ["metadata"], optional = true } 47nxp-pac = { version = "0.1.0", git = "https://github.com/i509VCB/nxp-pac", rev = "477dfdbfd5e6c75c0730c56494b601c1b2257263", features = ["metadata"], optional = true }
48proc-macro2 = "1.0.95" 48proc-macro2 = "1.0.95"
49quote = "1.0.15" 49quote = "1.0.15"
50 50
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")]
11pub mod pint; 11pub mod pint;
12#[cfg(feature = "lpc55-core0")] 12#[cfg(feature = "lpc55-core0")]
13pub mod pwm;
14#[cfg(feature = "lpc55-core0")]
13pub mod usart; 15pub 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")]
4mod inner;
5pub 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 @@
1use core::sync::atomic::{AtomicU8, AtomicU32, Ordering};
2
3use embassy_hal_internal::{Peri, PeripheralType};
4
5use crate::gpio::AnyPin;
6use crate::pac::iocon::vals::{PioDigimode, PioFunc, PioMode, PioOd, PioSlew};
7use crate::pac::sct0::vals;
8use crate::pac::syscon::vals::{SctRst, SctclkselSel};
9use crate::pac::{SCT0, SYSCON};
10
11// Since for now the counter is shared, the TOP value has to be kept.
12static TOP_VALUE: AtomicU32 = AtomicU32::new(0);
13// To check if there are still active instances.
14static 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)]
22pub 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
51impl 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.
66pub struct Pwm<'d> {
67 _pin: Peri<'d, AnyPin>,
68 output: usize,
69}
70
71impl<'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
234impl<'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
243trait SealedOutput {
244 /// Output number.
245 fn number(&self) -> usize;
246}
247
248/// PWM Output.
249#[allow(private_bounds)]
250pub trait Output: PeripheralType + SealedOutput {}
251
252macro_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
263output!(PWM_OUTPUT0, 0);
264output!(PWM_OUTPUT1, 1);
265output!(PWM_OUTPUT2, 2);
266output!(PWM_OUTPUT3, 3);
267output!(PWM_OUTPUT4, 4);
268output!(PWM_OUTPUT5, 5);
269output!(PWM_OUTPUT6, 6);
270output!(PWM_OUTPUT7, 7);
271output!(PWM_OUTPUT8, 8);
272output!(PWM_OUTPUT9, 9);
273
274/// PWM Output Channel.
275pub trait OutputChannelPin<T: Output>: crate::gpio::Pin {
276 fn pin_func(&self) -> PioFunc;
277}
278
279macro_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
289impl_pin!(PIO0_2, PWM_OUTPUT0, ALT3);
290impl_pin!(PIO0_17, PWM_OUTPUT0, ALT4);
291impl_pin!(PIO1_4, PWM_OUTPUT0, ALT4);
292impl_pin!(PIO1_23, PWM_OUTPUT0, ALT2);
293
294impl_pin!(PIO0_3, PWM_OUTPUT1, ALT3);
295impl_pin!(PIO0_18, PWM_OUTPUT1, ALT4);
296impl_pin!(PIO1_8, PWM_OUTPUT1, ALT4);
297impl_pin!(PIO1_24, PWM_OUTPUT1, ALT2);
298
299impl_pin!(PIO0_10, PWM_OUTPUT2, ALT5);
300impl_pin!(PIO0_15, PWM_OUTPUT2, ALT4);
301impl_pin!(PIO0_19, PWM_OUTPUT2, ALT4);
302impl_pin!(PIO1_9, PWM_OUTPUT2, ALT4);
303impl_pin!(PIO1_25, PWM_OUTPUT2, ALT2);
304
305impl_pin!(PIO0_22, PWM_OUTPUT3, ALT4);
306impl_pin!(PIO0_31, PWM_OUTPUT3, ALT4);
307impl_pin!(PIO1_10, PWM_OUTPUT3, ALT4);
308impl_pin!(PIO1_26, PWM_OUTPUT3, ALT2);
309
310impl_pin!(PIO0_23, PWM_OUTPUT4, ALT4);
311impl_pin!(PIO1_3, PWM_OUTPUT4, ALT4);
312impl_pin!(PIO1_17, PWM_OUTPUT4, ALT4);
313
314impl_pin!(PIO0_26, PWM_OUTPUT5, ALT4);
315impl_pin!(PIO1_18, PWM_OUTPUT5, ALT4);
316
317impl_pin!(PIO0_27, PWM_OUTPUT6, ALT4);
318impl_pin!(PIO1_31, PWM_OUTPUT6, ALT4);
319
320impl_pin!(PIO0_28, PWM_OUTPUT7, ALT4);
321impl_pin!(PIO1_19, PWM_OUTPUT7, ALT2);
322
323impl_pin!(PIO0_29, PWM_OUTPUT8, ALT4);
324
325impl_pin!(PIO0_30, PWM_OUTPUT9, ALT4);
diff --git a/examples/lpc55s69/src/bin/pwm.rs b/examples/lpc55s69/src/bin/pwm.rs
new file mode 100644
index 000000000..93b898b9d
--- /dev/null
+++ b/examples/lpc55s69/src/bin/pwm.rs
@@ -0,0 +1,18 @@
1#![no_std]
2#![no_main]
3
4use defmt::*;
5use embassy_executor::Spawner;
6use embassy_nxp::pwm::{Config, Pwm};
7use embassy_time::Timer;
8use {defmt_rtt as _, panic_halt as _};
9
10#[embassy_executor::main]
11async fn main(_spawner: Spawner) {
12 let p = embassy_nxp::init(Default::default());
13 let pwm = Pwm::new_output(p.PWM_OUTPUT1, p.PIO0_18, Config::new(1_000_000_000, 2_000_000_000));
14 loop {
15 info!("Counter: {}", pwm.counter());
16 Timer::after_millis(50).await;
17 }
18}