aboutsummaryrefslogtreecommitdiff
path: root/embassy-nxp/src/pwm/lpc55.rs
diff options
context:
space:
mode:
Diffstat (limited to 'embassy-nxp/src/pwm/lpc55.rs')
-rw-r--r--embassy-nxp/src/pwm/lpc55.rs248
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
3use core::sync::atomic::{AtomicU8, AtomicU32, Ordering};
4
5use embassy_hal_internal::Peri;
6
7use crate::gpio::AnyPin;
8use crate::pac::iocon::vals::{PioDigimode, PioMode, PioOd, PioSlew};
9use crate::pac::sct0::vals;
10use crate::pac::syscon::vals::{SctRst, SctclkselSel};
11use crate::pac::{SCT0, SYSCON};
12use crate::sct;
13
14// Since for now the counter is shared, the TOP value has to be kept.
15static TOP_VALUE: AtomicU32 = AtomicU32::new(0);
16// To check if there are still active instances.
17static 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)]
25pub 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
54impl 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.
69pub struct Pwm<'d> {
70 _pin: Peri<'d, AnyPin>,
71 output: usize,
72}
73
74impl<'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
241impl<'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}