Welcome to mirror list, hosted at ThFree Co, Russian Federation.

clockless_arm_nrf52.h « nrf52 « arm « platforms - github.com/FastLED/FastLED.git - Unnamed repository; edit this file 'description' to name the repository.
summaryrefslogtreecommitdiff
blob: 56a1dbe09729a7eef1cf2857ed656bccd552b5be (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
#ifndef __INC_CLOCKLESS_ARM_NRF52
#define __INC_CLOCKLESS_ARM_NRF52

#if defined(NRF52_SERIES)


//FASTLED_NAMESPACE_BEGIN

#define FASTLED_HAS_CLOCKLESS 1
#define FASTLED_NRF52_MAXIMUM_PIXELS_PER_STRING 144 // TODO: Figure out how to safely let this be calller-defined....

// nRF52810 has a single PWM peripheral (PWM0)
// nRF52832 has three PWM peripherals (PWM0, PWM1, PWM2)
// nRF52840 has four PWM peripherals (PWM0, PWM1, PWM2, PWM3)
// NOTE: Update platforms.cpp in root of FastLED library if this changes
#define FASTLED_NRF52_PWM_ID 0


extern uint32_t isrCount;


template <uint8_t _DATA_PIN, int _T1, int _T2, int _T3, EOrder _RGB_ORDER = RGB, int _XTRA0 = 0, bool _FLIP = false, int _WAIT_TIME_MICROSECONDS = 10>
class ClocklessController : public CPixelLEDController<_RGB_ORDER> {
    static_assert(FASTLED_NRF52_MAXIMUM_PIXELS_PER_STRING > 0, "Maximum string length must be positive value (FASTLED_NRF52_MAXIMUM_PIXELS_PER_STRING)");
    static_assert(_T1         >             0 , "negative values are not allowed");
    static_assert(_T2         >             0 , "negative values are not allowed");
    static_assert(_T3         >             0 , "negative values are not allowed");
    static_assert(_T1         <  (0x8000u-2u), "_T1 must fit in 15 bits");
    static_assert(_T2         <  (0x8000u-2u), "_T2 must fit in 15 bits");
    static_assert(_T3         <  (0x8000u-2u), "_T3 must fit in 15 bits");
    static_assert(_T1         <  (0x8000u-2u), "_T0H must fit in 15 bits");
    static_assert(_T1+_T2     <  (0x8000u-2u), "_T1H must fit in 15 bits");
    static_assert(_T1+_T2+_T3 <  (0x8000u-2u), "_TOP must fit in 15 bits");
    static_assert(_T1+_T2+_T3 <= PWM_COUNTERTOP_COUNTERTOP_Msk, "_TOP too large for peripheral");

private:
    static const bool     _INITIALIZE_PIN_HIGH = (_FLIP ? 1 : 0);
    static const uint16_t _POLARITY_BIT        = (_FLIP ? 0 : 0x8000);

    static const uint8_t  _BITS_PER_PIXEL   = (8 + _XTRA0) * 3; // NOTE: 3 means RGB only...
    static const uint16_t _PWM_BUFFER_COUNT = (_BITS_PER_PIXEL * FASTLED_NRF52_MAXIMUM_PIXELS_PER_STRING);
    static const uint8_t  _T0H = ((uint16_t)(_T1        ));
    static const uint8_t  _T1H = ((uint16_t)(_T1+_T2    ));
    static const uint8_t  _TOP = ((uint16_t)(_T1+_T2+_T3));

    // may as well be static, as can only attach one LED string per _DATA_PIN....
    static uint16_t s_SequenceBuffer[_PWM_BUFFER_COUNT];
    static uint16_t s_SequenceBufferValidElements;
    static volatile uint32_t s_SequenceBufferInUse;
    static CMinWait<_WAIT_TIME_MICROSECONDS> mWait;  // ensure data has time to latch

    FASTLED_NRF52_INLINE_ATTRIBUTE static void startPwmPlayback_InitializePinState() {
        FastPin<_DATA_PIN>::setOutput();
        if (_INITIALIZE_PIN_HIGH) {
            FastPin<_DATA_PIN>::hi();
        } else {
            FastPin<_DATA_PIN>::lo();
        }
    }
    FASTLED_NRF52_INLINE_ATTRIBUTE static void startPwmPlayback_InitializePwmInstance(NRF_PWM_Type * pwm) {

        // Pins must be set before enabling the peripheral
        pwm->PSEL.OUT[0] = FastPin<_DATA_PIN>::nrf_pin();
        pwm->PSEL.OUT[1] = NRF_PWM_PIN_NOT_CONNECTED;
        pwm->PSEL.OUT[2] = NRF_PWM_PIN_NOT_CONNECTED;
        pwm->PSEL.OUT[3] = NRF_PWM_PIN_NOT_CONNECTED;
        nrf_pwm_enable(pwm);
        nrf_pwm_configure(pwm, NRF_PWM_CLK_16MHz, NRF_PWM_MODE_UP, _TOP);
        nrf_pwm_decoder_set(pwm, NRF_PWM_LOAD_COMMON, NRF_PWM_STEP_AUTO);

        // clear any prior shorts / interrupt enable bits
        nrf_pwm_shorts_set(pwm, 0);
        nrf_pwm_int_set(pwm, 0);
        // clear all prior events
        nrf_pwm_event_clear(pwm, NRF_PWM_EVENT_STOPPED);
        nrf_pwm_event_clear(pwm, NRF_PWM_EVENT_SEQSTARTED0);
        nrf_pwm_event_clear(pwm, NRF_PWM_EVENT_SEQSTARTED1);
        nrf_pwm_event_clear(pwm, NRF_PWM_EVENT_SEQEND0);
        nrf_pwm_event_clear(pwm, NRF_PWM_EVENT_SEQEND1);
        nrf_pwm_event_clear(pwm, NRF_PWM_EVENT_PWMPERIODEND);
        nrf_pwm_event_clear(pwm, NRF_PWM_EVENT_LOOPSDONE);
    }
    FASTLED_NRF52_INLINE_ATTRIBUTE static void startPwmPlayback_ConfigurePwmSequence(NRF_PWM_Type * pwm) {
        // config is easy, using SEQ0, no loops...
        nrf_pwm_sequence_t sequenceConfig;
        sequenceConfig.values.p_common = &(s_SequenceBuffer[0]);
        sequenceConfig.length          = s_SequenceBufferValidElements;
        sequenceConfig.repeats         = 0; // send the data once, and only once
        sequenceConfig.end_delay       = 0; // no extra delay at the end of SEQ[0] / SEQ[1]
        nrf_pwm_sequence_set(pwm, 0, &sequenceConfig);
        nrf_pwm_sequence_set(pwm, 1, &sequenceConfig);
        nrf_pwm_loop_set(pwm, 0);

    }
    FASTLED_NRF52_INLINE_ATTRIBUTE static void startPwmPlayback_EnableInterruptsAndShortcuts(NRF_PWM_Type * pwm) {
        IRQn_Type irqn = PWM_Arbiter<FASTLED_NRF52_PWM_ID>::getIRQn();
        // TODO: check API results...
        uint32_t result;

        result = sd_nvic_SetPriority(irqn, configMAX_SYSCALL_INTERRUPT_PRIORITY);
        (void)result;
        result = sd_nvic_EnableIRQ(irqn);
        (void)result;

        // shortcuts prevent (up to) 4-cycle delay from interrupt handler to next action
        uint32_t shortsToEnable = 0;
        shortsToEnable |= NRF_PWM_SHORT_SEQEND0_STOP_MASK;        ///< SEQEND[0] --> STOP task.
        shortsToEnable |= NRF_PWM_SHORT_SEQEND1_STOP_MASK;        ///< SEQEND[1] --> STOP task.
        //shortsToEnable |= NRF_PWM_SHORT_LOOPSDONE_SEQSTART0_MASK; ///< LOOPSDONE --> SEQSTART[0] task.
        //shortsToEnable |= NRF_PWM_SHORT_LOOPSDONE_SEQSTART1_MASK; ///< LOOPSDONE --> SEQSTART[1] task.
        shortsToEnable |= NRF_PWM_SHORT_LOOPSDONE_STOP_MASK;      ///< LOOPSDONE --> STOP task.
        nrf_pwm_shorts_set(pwm, shortsToEnable);

        // mark which events should cause interrupts...
        uint32_t interruptsToEnable = 0;
        interruptsToEnable |= NRF_PWM_INT_SEQEND0_MASK;
        interruptsToEnable |= NRF_PWM_INT_SEQEND1_MASK;
        interruptsToEnable |= NRF_PWM_INT_LOOPSDONE_MASK;
        interruptsToEnable |= NRF_PWM_INT_STOPPED_MASK;
        nrf_pwm_int_set(pwm, interruptsToEnable);

    }
    FASTLED_NRF52_INLINE_ATTRIBUTE static void startPwmPlayback_StartTask(NRF_PWM_Type * pwm) {
        nrf_pwm_task_trigger(pwm, NRF_PWM_TASK_SEQSTART0);
    }
    FASTLED_NRF52_INLINE_ATTRIBUTE static void spinAcquireSequenceBuffer() {
        while (!tryAcquireSequenceBuffer());
    }
    FASTLED_NRF52_INLINE_ATTRIBUTE static bool tryAcquireSequenceBuffer() {
        return __sync_bool_compare_and_swap(&s_SequenceBufferInUse, 0, 1);
    }
    FASTLED_NRF52_INLINE_ATTRIBUTE static void releaseSequenceBuffer() {
        uint32_t tmp = __sync_val_compare_and_swap(&s_SequenceBufferInUse, 1, 0);
        if (tmp != 1) {
            // TODO: Error / Assert / log ?
        }
    }

public:
    static void isr_handler() {
        NRF_PWM_Type * pwm = PWM_Arbiter<FASTLED_NRF52_PWM_ID>::getPWM();
        IRQn_Type irqn = PWM_Arbiter<FASTLED_NRF52_PWM_ID>::getIRQn();

        // Currently, only use SEQUENCE 0, so only event
        // of consequence is LOOPSDONE ...
        if (nrf_pwm_event_check(pwm,NRF_PWM_EVENT_STOPPED)) {
            nrf_pwm_event_clear(pwm,NRF_PWM_EVENT_STOPPED);

            // update the minimum time to next call
            mWait.mark();
            // mark the sequence as no longer in use -- pointer, comparator, exchange value
            releaseSequenceBuffer();
            // prevent further interrupts from PWM events
            nrf_pwm_int_set(pwm, 0);
            // disable PWM interrupts - None of the PWM IRQs are shared
            // with other peripherals, avoiding complexity of shared IRQs.
            sd_nvic_DisableIRQ(irqn);
            // disable the PWM instance
            nrf_pwm_disable(pwm);
            // may take up to 4 cycles for writes to propagate (APB bus @ 16MHz)
            asm __volatile__ ( "NOP; NOP; NOP; NOP;" );
            // release the PWM arbiter to be re-used by another LED string
            PWM_Arbiter<FASTLED_NRF52_PWM_ID>::releaseFromIsr();
        }
    }


    virtual void init() {
        FASTLED_NRF52_DEBUGPRINT("Clockless Timings:\n");
        FASTLED_NRF52_DEBUGPRINT("    T0H == %d", _T0H);
        FASTLED_NRF52_DEBUGPRINT("    T1H == %d", _T1H);
        FASTLED_NRF52_DEBUGPRINT("    TOP == %d\n", _TOP);
        // to avoid pin initialization from causing first LED to have invalid color,
        // call mWait.mark() to ensure data latches before color data gets sent.
        startPwmPlayback_InitializePinState();
        mWait.mark();

    }
    virtual uint16_t getMaxRefreshRate() const { return 800; }

    virtual void showPixels(PixelController<_RGB_ORDER> & pixels) {
        // wait for the only sequence buffer to become available
        spinAcquireSequenceBuffer();
        prepareSequenceBuffers(pixels);
        // ensure any prior data had time to latch
        mWait.wait();
        startPwmPlayback(s_SequenceBufferValidElements);
        return;
    }

    template<uint8_t _BIT>
    FASTLED_NRF52_INLINE_ATTRIBUTE static void WriteBitToSequence(uint8_t byte, uint16_t * e) {
        *e = _POLARITY_BIT | (((byte & (1u << _BIT)) == 0) ? _T0H : _T1H);
    }
    FASTLED_NRF52_INLINE_ATTRIBUTE static void prepareSequenceBuffers(PixelController<_RGB_ORDER> & pixels) {
        s_SequenceBufferValidElements = 0;
        int32_t    remainingSequenceElements = _PWM_BUFFER_COUNT;
        uint16_t * e = s_SequenceBuffer;
        uint32_t size_needed = pixels.size(); // count of pixels
        size_needed *= (8 + _XTRA0);          // bits per pixel
        size_needed *= 2;                     // each bit takes two bytes

        if (size_needed > _PWM_BUFFER_COUNT) {
            // TODO: assert()?
            return;
        }

        while (pixels.has(1) && (remainingSequenceElements >= _BITS_PER_PIXEL)) {
            uint8_t b0 = pixels.loadAndScale0();
            WriteBitToSequence<7>(b0, e); e++;
            WriteBitToSequence<6>(b0, e); e++;
            WriteBitToSequence<5>(b0, e); e++;
            WriteBitToSequence<4>(b0, e); e++;
            WriteBitToSequence<3>(b0, e); e++;
            WriteBitToSequence<2>(b0, e); e++;
            WriteBitToSequence<1>(b0, e); e++;
            WriteBitToSequence<0>(b0, e); e++;
            if (_XTRA0 > 0) {
                for (int i = 0; i < _XTRA0; i++) {
                    WriteBitToSequence<0>(0,e); e++;
                }
            }
            uint8_t b1 = pixels.loadAndScale1();
            WriteBitToSequence<7>(b1, e); e++;
            WriteBitToSequence<6>(b1, e); e++;
            WriteBitToSequence<5>(b1, e); e++;
            WriteBitToSequence<4>(b1, e); e++;
            WriteBitToSequence<3>(b1, e); e++;
            WriteBitToSequence<2>(b1, e); e++;
            WriteBitToSequence<1>(b1, e); e++;
            WriteBitToSequence<0>(b1, e); e++;
            if (_XTRA0 > 0) {
                for (int i = 0; i < _XTRA0; i++) {
                    WriteBitToSequence<0>(0,e); e++;
                }
            }
            uint8_t b2 = pixels.loadAndScale2();
            WriteBitToSequence<7>(b2, e); e++;
            WriteBitToSequence<6>(b2, e); e++;
            WriteBitToSequence<5>(b2, e); e++;
            WriteBitToSequence<4>(b2, e); e++;
            WriteBitToSequence<3>(b2, e); e++;
            WriteBitToSequence<2>(b2, e); e++;
            WriteBitToSequence<1>(b2, e); e++;
            WriteBitToSequence<0>(b2, e); e++;
            if (_XTRA0 > 0) {
                for (int i = 0; i < _XTRA0; i++) {
                    WriteBitToSequence<0>(0,e); e++;
                }
            }

            // advance pixel and sequence pointers
            s_SequenceBufferValidElements += _BITS_PER_PIXEL;
            remainingSequenceElements     -= _BITS_PER_PIXEL;
            pixels.advanceData();
            pixels.stepDithering();
        }
    }


    FASTLED_NRF52_INLINE_ATTRIBUTE static void startPwmPlayback(uint16_t bytesToSend) {
        PWM_Arbiter<FASTLED_NRF52_PWM_ID>::acquire(isr_handler);
        NRF_PWM_Type * pwm = PWM_Arbiter<FASTLED_NRF52_PWM_ID>::getPWM();

        // mark the sequence as being in-use
        __sync_fetch_and_or(&s_SequenceBufferInUse, 1);

        startPwmPlayback_InitializePinState();
        startPwmPlayback_InitializePwmInstance(pwm);
        startPwmPlayback_ConfigurePwmSequence(pwm);
        startPwmPlayback_EnableInterruptsAndShortcuts(pwm);
        startPwmPlayback_StartTask(pwm);
        return;
    }


#if 0
    FASTLED_NRF52_INLINE_ATTRIBUTE static uint16_t* getRawSequenceBuffer() { return s_SequenceBuffer; }
    FASTLED_NRF52_INLINE_ATTRIBUTE static uint16_t getRawSequenceBufferSize() { return _PWM_BUFFER_COUNT; }
    FASTLED_NRF52_INLINE_ATTRIBUTE static uint16_t getSequenceBufferInUse() { return s_SequenceBufferInUse; }
    FASTLED_NRF52_INLINE_ATTRIBUTE static void sendRawSequenceBuffer(uint16_t bytesToSend) {
        mWait.wait(); // ensure min time between updates
        startPwmPlayback(bytesToSend);
    }
    FASTLED_NRF52_INLINE_ATTRIBUTE static void sendRawBytes(uint8_t * arrayOfBytes, uint16_t bytesToSend) {
        // wait for sequence buffer to be available
        while (s_SequenceBufferInUse != 0);

        s_SequenceBufferValidElements = 0;
        int32_t    remainingSequenceElements = _PWM_BUFFER_COUNT;
        uint16_t * e           = s_SequenceBuffer;
        uint8_t  * nextByte    = arrayOfBytes;
        for (uint16_t bytesRemain = bytesToSend;
            (remainingSequenceElements >= 8) && (bytesRemain > 0);
            bytesRemain--,
            remainingSequenceElements     -= 8,
            s_SequenceBufferValidElements += 8
            ) {
            uint8_t b = *nextByte;
            WriteBitToSequence<7,false>(b, e); e++;
            WriteBitToSequence<6,false>(b, e); e++;
            WriteBitToSequence<5,false>(b, e); e++;
            WriteBitToSequence<4,false>(b, e); e++;
            WriteBitToSequence<3,false>(b, e); e++;
            WriteBitToSequence<2,false>(b, e); e++;
            WriteBitToSequence<1,false>(b, e); e++;
            WriteBitToSequence<0,false>(b, e); e++;
            if (_XTRA0 > 0) {
                for (int i = 0; i < _XTRA0; i++) {
                    WriteBitToSequence<0,_FLIP>(0,e); e++;
                }
            }
        }
        mWait.wait(); // ensure min time between updates

        startPwmPlayback(s_SequenceBufferValidElements);
    }
#endif // 0

};

template <uint8_t _DATA_PIN, int _T1, int _T2, int _T3, EOrder _RGB_ORDER, int _XTRA0, bool _FLIP, int _WAIT_TIME_MICROSECONDS>
uint16_t ClocklessController<_DATA_PIN, _T1, _T2, _T3, _RGB_ORDER, _XTRA0, _FLIP, _WAIT_TIME_MICROSECONDS>::s_SequenceBufferValidElements = 0;
template <uint8_t _DATA_PIN, int _T1, int _T2, int _T3, EOrder _RGB_ORDER, int _XTRA0, bool _FLIP, int _WAIT_TIME_MICROSECONDS>
uint32_t volatile ClocklessController<_DATA_PIN, _T1, _T2, _T3, _RGB_ORDER, _XTRA0, _FLIP, _WAIT_TIME_MICROSECONDS>::s_SequenceBufferInUse = 0;
template <uint8_t _DATA_PIN, int _T1, int _T2, int _T3, EOrder _RGB_ORDER, int _XTRA0, bool _FLIP, int _WAIT_TIME_MICROSECONDS>
uint16_t ClocklessController<_DATA_PIN, _T1, _T2, _T3, _RGB_ORDER, _XTRA0, _FLIP, _WAIT_TIME_MICROSECONDS>::s_SequenceBuffer[_PWM_BUFFER_COUNT];
template <uint8_t _DATA_PIN, int _T1, int _T2, int _T3, EOrder _RGB_ORDER, int _XTRA0, bool _FLIP, int _WAIT_TIME_MICROSECONDS>
CMinWait<_WAIT_TIME_MICROSECONDS> ClocklessController<_DATA_PIN, _T1, _T2, _T3, _RGB_ORDER, _XTRA0, _FLIP, _WAIT_TIME_MICROSECONDS>::mWait;

/* nrf_pwm solution
// 
// When the nRF52 softdevice (e.g., BLE) is enabled, the CPU can be pre-empted
// at any time for radio interrupts.  These interrupts cannot be disabled.
// The problem is, even simple BLE advertising interrupts may take **`348μs`**
// (per softdevice 1.40, see http://infocenter.nordicsemi.com/pdf/S140_SDS_v1.3.pdf)
// 
// The nRF52 chips have a decent Easy-DMA-enabled PWM peripheral.
//
// The major downside:
// [] The PWM peripheral has a fixed input buffer size at 16 bits per clock cycle.
//    (each clockless protocol bit == 2 bytes)
//
// The major upsides include:
// [] Fully asynchronous, freeing CPU for other tasks
// [] Softdevice interrupts do not affect PWM clocked output (reliable clocking)
//
// The initial solution generally does the following for showPixels():
// [] wait for a sequence buffer to become available
// [] prepare the entire LED string's sequence (see `prepareSequenceBuffers()`)
// [] ensures minimum wait time from prior sequence's end
//
// Options after initial solution working:
// [] 

// TODO: Double-buffers, so one can be doing DMA while the second
//       buffer is being prepared.
// TODO: Pool of buffers, so can keep N-1 active in DMA, while
//       preparing data in the final buffer?
//       Write another class similar to PWM_Arbiter, only for
//       tracking use of sequence buffers?
// TODO: Use volatile variable to track buffers that the
//       prior DMA operation is finished with, so can fill
//       in those buffers with newly-prepared data...
// apis to send the pre-generated buffer.  This would be essentially asynchronous,
// and result in efficient run time if the pixels are either (a) static, or
// (b) cycle through a limited number of options whose converted results can
// be cached and re-used.  While simple, this method takes lots of extra RAM...
// 16 bits for every full clock (high/low) cycle.
//
// Clockless chips typically send 24 bits (3x 8-bit) per pixel.
// One odd clockless chip sends 36 bits (3x 12-bit) per pixel.
// Each bit requires a 16-bit sequence entry for the PWM peripheral.
// This gives approximately:
//                 24 bpp           36 bpp
// ==========================================
//  1 pixel        48 bytes        72 bytes          
// 32 pixels    1,536 bytes     2,304 bytes
// 64 pixels    3,072 bytes     4,608 bytes
//
//
// UPDATE: this is the method I'm choosing, to get _SOMETHING_
//         clockless working...  3k RAM for 64 pixels is acceptable
//         for a first release, as it allows re-use of FASTLED
//         color correction, dithering, etc. ....
*/

//FASTLED_NAMESPACE_END

#endif // NRF52_SERIES
#endif // __INC_CLOCKLESS_ARM_NRF52