nrf52/pwm.rs
1// Licensed under the Apache License, Version 2.0 or the MIT License.
2// SPDX-License-Identifier: Apache-2.0 OR MIT
3// Copyright Tock Contributors 2022.
4
5//! PWM driver for nRF52.
6
7use kernel::hil;
8use kernel::utilities::cells::VolatileCell;
9use kernel::utilities::registers::interfaces::Writeable;
10use kernel::utilities::registers::{register_bitfields, ReadWrite, WriteOnly};
11use kernel::utilities::StaticRef;
12use kernel::ErrorCode;
13
14#[repr(C)]
15struct PwmRegisters {
16 _reserved0: [u8; 4],
17 /// Stops PWM pulse generation on all channels at the end of current PWM period
18 tasks_stop: WriteOnly<u32, TASK::Register>,
19 /// Loads the first PWM value on all enabled channels
20 tasks_seqstart: [WriteOnly<u32, TASK::Register>; 2],
21 /// Steps by one value in the current sequence on all enabled channels if DECODER.MO
22 tasks_nextstep: WriteOnly<u32, TASK::Register>,
23 _reserved1: [u8; 240],
24 /// Response to STOP task, emitted when PWM pulses are no longer generated
25 events_stopped: ReadWrite<u32, EVENT::Register>,
26 /// First PWM period started on sequence 0
27 events_seqstarted: [ReadWrite<u32, EVENT::Register>; 2],
28 /// Emitted at end of every sequence 0, when last value from RAM has been
29 /// applied to the wave counter
30 events_seqend: [ReadWrite<u32, EVENT::Register>; 2],
31 /// Emitted at the end of each PWM period
32 events_pwmperiodend: ReadWrite<u32, EVENT::Register>,
33 /// Concatenated sequences have been played the amount of times defined in LOOP.CNT
34 events_loopsdone: ReadWrite<u32, EVENT::Register>,
35 _reserved2: [u8; 224],
36 /// Shortcut register
37 shorts: ReadWrite<u32, SHORTS::Register>,
38 _reserved3: [u8; 252],
39 /// Enable or disable interrupt
40 inten: ReadWrite<u32, INTEN::Register>,
41 /// Enable interrupt
42 intenset: ReadWrite<u32, INTEN::Register>,
43 /// Disable interrupt
44 intenclr: ReadWrite<u32, INTEN::Register>,
45 _reserved4: [u8; 500],
46 /// PWM module enable register
47 enable: ReadWrite<u32, ENABLE::Register>,
48 /// Selects operating mode of the wave counter
49 mode: ReadWrite<u32, MODE::Register>,
50 /// Value up to which the pulse generator counter counts
51 countertop: ReadWrite<u32, COUNTERTOP::Register>,
52 /// Configuration for PWM_CLK
53 prescaler: ReadWrite<u32, PRESCALER::Register>,
54 /// Configuration of the decoder
55 decoder: ReadWrite<u32, DECODER::Register>,
56 /// Amount of playback of a loop
57 loopreg: ReadWrite<u32, LOOP::Register>,
58 _reserved5: [u8; 8],
59 seq0: PwmSeqRegisters,
60 _reserved6: [u8; 16],
61 seq1: PwmSeqRegisters,
62 _reserved7: [u8; 16],
63 psel_out: [VolatileCell<nrf5x::pinmux::Pinmux>; 4],
64}
65
66#[repr(C)]
67struct PwmSeqRegisters {
68 seq_ptr: VolatileCell<*const u16>,
69 seq_cnt: ReadWrite<u32, SEQ_CNT::Register>,
70 seq_refresh: ReadWrite<u32, SEQ_REFRESH::Register>,
71 seq_enddelay: ReadWrite<u32, SEQ_ENDDELAY::Register>,
72}
73
74register_bitfields![u32,
75 SHORTS [
76 /// Shortcut between EVENTS_SEQEND[0] event and TASKS_STOP task
77 SEQEND0_STOP 0,
78 /// Shortcut between EVENTS_SEQEND[1] event and TASKS_STOP task
79 SEQEND1_STOP 1,
80 /// Shortcut between EVENTS_LOOPSDONE event and TASKS_SEQSTART[0] task
81 LOOPSDONE_SEQSTART0 2,
82 /// Shortcut between EVENTS_LOOPSDONE event and TASKS_SEQSTART[1] task
83 LOOPSDONE_SEQSTART1 3,
84 /// Shortcut between EVENTS_LOOPSDONE event and TASKS_STOP task
85 LOOPSDONE_STOP 4
86 ],
87 INTEN [
88 /// Enable or disable interrupt on EVENTS_STOPPED event
89 STOPPED 1,
90 /// Enable or disable interrupt on EVENTS_SEQSTARTED[0] event
91 SEQSTARTED0 2,
92 /// Enable or disable interrupt on EVENTS_SEQSTARTED[1] event
93 SEQSTARTED1 3,
94 /// Enable or disable interrupt on EVENTS_SEQEND[0] event
95 SEQEND0 4,
96 /// Enable or disable interrupt on EVENTS_SEQEND[1] event
97 SEQEND1 5,
98 /// Enable or disable interrupt on EVENTS_PWMPERIODEND event
99 PWMPERIODEND 6,
100 /// Enable or disable interrupt on EVENTS_LOOPSDONE event
101 LOOPSDONE 7
102 ],
103 ENABLE [
104 ENABLE 0
105 ],
106 MODE [
107 UPDOWN OFFSET(0) NUMBITS(1) [
108 Up = 0,
109 UpAndDown = 1
110 ]
111 ],
112 COUNTERTOP [
113 COUNTERTOP OFFSET(0) NUMBITS(15) []
114 ],
115 PRESCALER [
116 PRESCALER OFFSET(0) NUMBITS(3) [
117 DIV_1 = 0,
118 DIV_2 = 1,
119 DIV_4 = 2,
120 DIV_8 = 3,
121 DIV_16 = 4,
122 DIV_32 = 5,
123 DIV_64 = 6,
124 DIV_128 = 7
125 ]
126 ],
127 DECODER [
128 /// How a sequence is read from RAM and spread to the compare register
129 LOAD OFFSET(0) NUMBITS(2) [
130 /// 1st half word (16-bit) used in all PWM channels 0..3
131 Common = 0,
132 /// 1st half word (16-bit) used in channel 0..1; 2nd word in channel 2..3
133 Grouped = 1,
134 /// 1st half word (16-bit) in ch.0; 2nd in ch.1; ...; 4th in ch.3
135 Individual = 2,
136 /// 1st half word (16-bit) in ch.0; 2nd in ch.1; ...; 4th in COUNTERTOP
137 Waveform = 3
138 ],
139 /// Selects source for advancing the active sequence
140 MODE OFFSET(8) NUMBITS(1) [
141 /// SEQ[n].REFRESH is used to determine loading internal compare registers
142 RefreshCount = 0,
143 /// NEXTSTEP task causes a new value to be loaded to internal compare registers
144 NextStep = 1
145 ]
146 ],
147 LOOP [
148 /// Number of playbacks of pattern cycles. 0 to disable.
149 CNT OFFSET(0) NUMBITS(16) []
150 ],
151 EVENT [
152 EVENT 0
153 ],
154 TASK [
155 TASK 0
156 ],
157 SEQ_CNT [
158 CNT OFFSET(0) NUMBITS(15) []
159 ],
160 SEQ_REFRESH [
161 CNT OFFSET(0) NUMBITS(24) []
162 ],
163 SEQ_ENDDELAY [
164 CNT OFFSET(0) NUMBITS(24) []
165 ]
166];
167
168const PWM0_BASE: StaticRef<PwmRegisters> =
169 unsafe { StaticRef::new(0x4001C000 as *const PwmRegisters) };
170
171/// `DUTY_CYCLES` is a static array that must be passed to the PWM hardware.
172/// The nRF52 hardware uses this static array in memory to enable switching
173/// between multiple duty cycles automatically while generating the PWM output.
174/// This isn't ideal from a Rust perspective, but the peripheral hardware must
175/// be passed a pointer.
176static mut DUTY_CYCLES: [u16; 4] = [0; 4];
177
178pub struct Pwm {
179 registers: StaticRef<PwmRegisters>,
180}
181
182impl Pwm {
183 pub const fn new() -> Pwm {
184 Pwm {
185 registers: PWM0_BASE,
186 }
187 }
188
189 fn start_pwm(
190 &self,
191 pin: &nrf5x::pinmux::Pinmux,
192 frequency_hz: usize,
193 duty_cycle: usize,
194 ) -> Result<(), ErrorCode> {
195 // If frequency is 0, we can just stop the PWM and have it do nothing.
196 if frequency_hz == 0 {
197 return self.stop_pwm(pin);
198 }
199
200 let prescaler = 0;
201 let counter_top = (16000000 / frequency_hz) >> prescaler;
202
203 // Use the passed in duty cycle to calculate the value we pass to the
204 // hardware. A 50% duty cycle is half of counter_top, a 10% duty cycle
205 // would be 90% of counter_top.
206 //
207 // duty_cycle
208 // dc_out = counter_top * (1 - ---------- )
209 // 5333333
210 let dc_out = counter_top - ((3 * duty_cycle) / frequency_hz);
211
212 // Configure the pin
213 self.registers.psel_out[0].set(*pin);
214
215 // Start by enabling the peripheral.
216 self.registers.enable.write(ENABLE::ENABLE::SET);
217 // Want count up mode.
218 self.registers.mode.write(MODE::UPDOWN::Up);
219 // Disable loop (repeat) mode.
220 self.registers.loopreg.write(LOOP::CNT.val(0));
221 // Set the decoder settings.
222 self.registers
223 .decoder
224 .write(DECODER::LOAD::Common + DECODER::MODE::RefreshCount);
225 // Set the prescaler.
226 self.registers.prescaler.write(PRESCALER::PRESCALER::DIV_1);
227 // Set the value to count to.
228 self.registers
229 .countertop
230 .write(COUNTERTOP::COUNTERTOP.val(counter_top as u32));
231
232 // Setup the duty cycles
233 unsafe {
234 DUTY_CYCLES[0] = dc_out as u16;
235 self.registers
236 .seq0
237 .seq_ptr
238 .set(core::ptr::addr_of!(DUTY_CYCLES) as *const u16);
239 }
240 self.registers.seq0.seq_cnt.write(SEQ_CNT::CNT.val(1));
241 self.registers
242 .seq0
243 .seq_refresh
244 .write(SEQ_REFRESH::CNT.val(0));
245 self.registers
246 .seq0
247 .seq_enddelay
248 .write(SEQ_ENDDELAY::CNT.val(0));
249
250 // Start
251 self.registers.tasks_seqstart[0].write(TASK::TASK::SET);
252
253 Ok(())
254 }
255
256 fn stop_pwm(&self, _pin: &nrf5x::pinmux::Pinmux) -> Result<(), ErrorCode> {
257 self.registers.tasks_stop.write(TASK::TASK::SET);
258 self.registers.enable.write(ENABLE::ENABLE::CLEAR);
259 Ok(())
260 }
261}
262
263impl hil::pwm::Pwm for Pwm {
264 type Pin = nrf5x::pinmux::Pinmux;
265
266 fn start(&self, pin: &Self::Pin, frequency: usize, duty_cycle: usize) -> Result<(), ErrorCode> {
267 self.start_pwm(pin, frequency, duty_cycle)
268 }
269
270 fn stop(&self, pin: &Self::Pin) -> Result<(), ErrorCode> {
271 self.stop_pwm(pin)
272 }
273
274 fn get_maximum_frequency_hz(&self) -> usize {
275 // Counter runs at 16 MHz, and the minimum value for the COUNTERTOP
276 // register is 3. 16000000 / 3 = 5333333
277 5333333
278 }
279
280 fn get_maximum_duty_cycle(&self) -> usize {
281 // We use the max frequency as the max duty cycle as well. This makes
282 // calculating `dc_out` straightforward.
283 5333333
284 }
285}