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}