Lamp-Da 0.1
A compact lantern project
Loading...
Searching...
No Matches
strip_impl.hpp
Go to the documentation of this file.
1
7#ifndef PLATFORM_STRIPIMPL_HPP
8#define PLATFORM_STRIPIMPL_HPP
9
10#include "strip_impl.h"
11
13
14#include <Arduino.h>
15#include <cassert>
16
17namespace lampda {
18namespace platform {
20namespace strip {
21
22template<size_t LedCount, uint8_t ChannelCount> bool LampdaStrip<LedCount, ChannelCount>::begin(void)
23{
24 if (pin >= 0)
25 {
26 pinMode(pin, OUTPUT);
27 digitalWrite(pin, LOW);
28 }
29 else
30 {
31 begun = false;
32 return false;
33 }
34 begun = true;
35 return true;
36}
37
38template<size_t LedCount, uint8_t ChannelCount>
39void LampdaStrip<LedCount, ChannelCount>::setPixelColor(uint16_t n, uint32_t c)
40{
41 if (n < numLEDs)
42 {
43 uint8_t *p, r = (uint8_t)(c >> 16), g = (uint8_t)(c >> 8), b = (uint8_t)c;
44 if (wOffset == rOffset)
45 {
46 p = &pixels[n * 3];
47 }
48 else
49 {
50 p = &pixels[n * 4];
51 uint8_t w = (uint8_t)(c >> 24);
52 p[wOffset] = w;
53 }
54 p[rOffset] = r;
55 p[gOffset] = g;
56 p[bOffset] = b;
57 }
58}
59
60template<size_t LedCount, uint8_t ChannelCount>
61uint32_t LampdaStrip<LedCount, ChannelCount>::getPixelColor(uint16_t n) const
62{
63 if (n >= numLEDs)
64 return 0; // Out of bounds, return no color.
65
66 if (wOffset == rOffset)
67 { // Is RGB-type device
68 uint8_t const* p = &pixels[n * 3];
69 // No b constrightness adjustment has been made -- return 'raw' color
70 return ((uint32_t)p[rOffset] << 16) | ((uint32_t)p[gOffset] << 8) | (uint32_t)p[bOffset];
71 }
72 else
73 { // Is RGBW-type device
74 uint8_t const* p = &pixels[n * 4];
75 return ((uint32_t)p[wOffset] << 24) | ((uint32_t)p[rOffset] << 16) | ((uint32_t)p[gOffset] << 8) |
76 (uint32_t)p[bOffset];
77 }
78}
79
80template<size_t LedCount, uint8_t ChannelCount> bool LampdaStrip<LedCount, ChannelCount>::canShow(void)
81{
82 // It's normal and possible for endTime to exceed micros() if the
83 // 32-bit clock counter has rolled over (about every 70 minutes).
84 // Since both are uint32_t, a negative delta correctly maps back to
85 // positive space, and it would seem like the subtraction below would
86 // suffice. But a problem arises if code invokes show() very
87 // infrequently...the micros() counter may roll over MULTIPLE times in
88 // that interval, the delta calculation is no longer correct and the
89 // next update may stall for a very long time. The check below resets
90 // the latch counter if a rollover has occurred. This can cause an
91 // extra delay of up to 300 microseconds in the rare case where a
92 // show() call happens precisely around the rollover, but that's
93 // neither likely nor especially harmful, vs. other code that might
94 // stall for 30+ minutes, or having to document and frequently remind
95 // and/or provide tech support explaining an unintuitive need for
96 // show() calls at least once an hour.
97 uint32_t now = micros();
98 if (endTime > now)
99 {
100 endTime = now;
101 }
102 return (now - endTime) >= 300L;
103}
104
105template<size_t LedCount, uint8_t ChannelCount> void LampdaStrip<LedCount, ChannelCount>::updateType(neoPixelType t)
106{
107 wOffset = (t >> 6) & 0b11; // See notes in header file
108 rOffset = (t >> 4) & 0b11; // regarding R/G/B/W offsets
109 gOffset = (t >> 2) & 0b11;
110 bOffset = t & 0b11;
111
112 if (wOffset == rOffset)
113 {
114 assert(ChannelCount == 3);
115 }
116 else
117 {
118 assert(ChannelCount == 4);
119 }
120}
121
122template<size_t LedCount, uint8_t ChannelCount> void LampdaStrip<LedCount, ChannelCount>::setPin(int16_t p)
123{
124 if (begun && (pin >= 0))
125 pinMode(pin, INPUT); // Disable existing out pin
126 pin = p;
127 if (begun)
128 {
129 pinMode(p, OUTPUT);
130 digitalWrite(p, LOW);
131 }
132}
133
134template<size_t LedCount, uint8_t ChannelCount> void LampdaStrip<LedCount, ChannelCount>::show(void)
135{
136 // clang-format off
137
138 // Data latch = 300+ microsecond pause in the output stream. Rather than
139 // put a delay at the end of the function, the ending time is noted and
140 // the function will simply hold off (if needed) on issuing the
141 // subsequent round of data until the latch time has elapsed. This
142 // allows the mainline code to start generating the next frame of data
143 // rather than stalling for the latch.
144 while (!canShow())
145 ;
146
147 // endTime is a private member (rather than global var) so that multiple
148 // instances on different pins can be quickly issued in succession (each
149 // instance doesn't delay the next).
150
151
152// [[[Begin of the Neopixel NRF52 EasyDMA implementation
153// by the Hackerspace San Salvador]]]
154// This technique uses the PWM peripheral on the NRF52. The PWM uses the
155// EasyDMA feature included on the chip. This technique loads the duty
156// cycle configuration for each cycle when the PWM is enabled. For this
157// to work we need to store a 16 bit configuration for each bit of the
158// RGB(W) values in the pixel buffer.
159// Comparator values for the PWM were hand picked and are guaranteed to
160// be 100% organic to preserve freshness and high accuracy. Current
161// parameters are:
162// * PWM Clock: 16Mhz
163// * Minimum step time: 62.5ns
164// * Time for zero in high (T0H): 0.31ms
165// * Time for one in high (T1H): 0.75ms
166// * Cycle time: 1.25us
167// * Frequency: 800Khz
168// For 400Khz we just double the calculated times.
169// ---------- BEGIN Constants for the EasyDMA implementation -----------
170// The PWM starts the duty cycle in LOW. To start with HIGH we
171// need to set the 15th bit on each register.
172
173// WS2812 (rev A) timing is 0.35 and 0.7us
174// #define MAGIC_T0H 5UL | (0x8000) // 0.3125us
175// #define MAGIC_T1H 12UL | (0x8000) // 0.75us
176
177// WS2812B (rev B) timing is 0.4 and 0.8 us
178#define MAGIC_T0H 6UL | (0x8000) // 0.375us
179#define MAGIC_T1H 13UL | (0x8000) // 0.8125us
180
181// WS2811 (400 khz) timing is 0.5 and 1.2
182#define MAGIC_T0H_400KHz 8UL | (0x8000) // 0.5us
183#define MAGIC_T1H_400KHz 19UL | (0x8000) // 1.1875us
184
185// For 400Khz, we double value of CTOPVAL
186#define CTOPVAL 20UL // 1.25us
187#define CTOPVAL_400KHz 40UL // 2.5us
188
189// ---------- END Constants for the EasyDMA implementation -------------
190
191 // To support both the SoftDevice + Neopixels we use the EasyDMA
192 // feature from the NRF25. However this technique implies to
193 // generate a pattern and store it on the memory. The actual
194 // memory used in bytes corresponds to the following formula:
195 // totalMem = numBytes*8*2+(2*2)
196 // The two additional bytes at the end are needed to reset the
197 // sequence.
198
199 NRF_PWM_Type* pwm = NULL;
200
201 // Try to find a free PWM device, which is not enabled
202 // and has no connected pins
203 NRF_PWM_Type* PWM[] = {NRF_PWM0,
204 NRF_PWM1,
205 NRF_PWM2
206#if defined(NRF_PWM3)
207 ,
208 NRF_PWM3
209#endif
210 };
211
212 for (unsigned int device = 0; device < (sizeof(PWM) / sizeof(PWM[0])); device++)
213 {
214 if ((PWM[device]->ENABLE == 0) && (PWM[device]->PSEL.OUT[0] & PWM_PSEL_OUT_CONNECT_Msk) &&
215 (PWM[device]->PSEL.OUT[1] & PWM_PSEL_OUT_CONNECT_Msk) &&
216 (PWM[device]->PSEL.OUT[2] & PWM_PSEL_OUT_CONNECT_Msk) && (PWM[device]->PSEL.OUT[3] & PWM_PSEL_OUT_CONNECT_Msk))
217 {
218 pwm = PWM[device];
219 break;
220 }
221 }
222
223 // Use the identified device to choose the implementation
224 // If a PWM device is available use DMA
225 if ((pixels_pattern != NULL) && (pwm != NULL))
226 {
227 uint16_t pos = 0; // bit position
228
229 for (uint16_t n = 0; n < numBytes; n++)
230 {
231 uint8_t pix = pixels[n];
232
233 for (uint8_t mask = 0x80; mask > 0; mask >>= 1)
234 {
235#if defined(NEO_KHZ400)
236 if (!is800KHz)
237 {
238 pixels_pattern[pos] = (pix & mask) ? MAGIC_T1H_400KHz : MAGIC_T0H_400KHz;
239 }
240 else
241#endif
242 {
243 pixels_pattern[pos] = (pix & mask) ? MAGIC_T1H : MAGIC_T0H;
244 }
245
246 pos++;
247 }
248 }
249
250 // Zero padding to indicate the end of que sequence
251 pixels_pattern[pos++] = 0 | (0x8000); // Seq end
252 pixels_pattern[pos++] = 0 | (0x8000); // Seq end
253
254 // Set the wave mode to count UP
255 pwm->MODE = (PWM_MODE_UPDOWN_Up << PWM_MODE_UPDOWN_Pos);
256
257 // Set the PWM to use the 16MHz clock
258 pwm->PRESCALER = (PWM_PRESCALER_PRESCALER_DIV_1 << PWM_PRESCALER_PRESCALER_Pos);
259
260 // Setting of the maximum count
261 // but keeping it on 16Mhz allows for more granularity just
262 // in case someone wants to do more fine-tuning of the timing.
263#if defined(NEO_KHZ400)
264 if (!is800KHz)
265 {
266 pwm->COUNTERTOP = (CTOPVAL_400KHz << PWM_COUNTERTOP_COUNTERTOP_Pos);
267 }
268 else
269#endif
270 {
271 pwm->COUNTERTOP = (CTOPVAL << PWM_COUNTERTOP_COUNTERTOP_Pos);
272 }
273
274 // Disable loops, we want the sequence to repeat only once
275 pwm->LOOP = (PWM_LOOP_CNT_Disabled << PWM_LOOP_CNT_Pos);
276
277 // On the "Common" setting the PWM uses the same pattern for the
278 // for supported sequences. The pattern is stored on half-word
279 // of 16bits
280 pwm->DECODER =
281 (PWM_DECODER_LOAD_Common << PWM_DECODER_LOAD_Pos) | (PWM_DECODER_MODE_RefreshCount << PWM_DECODER_MODE_Pos);
282
283 // Pointer to the memory storing the patter
284 pwm->SEQ[0].PTR = (uint32_t)(pixels_pattern) << PWM_SEQ_PTR_PTR_Pos;
285
286 // Calculation of the number of steps loaded from memory.
287 pwm->SEQ[0].CNT = pattern_size << PWM_SEQ_CNT_CNT_Pos;
288
289 // The following settings are ignored with the current config.
290 pwm->SEQ[0].REFRESH = 0;
291 pwm->SEQ[0].ENDDELAY = 0;
292
293 // The Neopixel implementation is a blocking algorithm. DMA
294 // allows for non-blocking operation. To "simulate" a blocking
295 // operation we enable the interruption for the end of sequence
296 // and block the execution thread until the event flag is set by
297 // the peripheral.
298 // pwm->INTEN |= (PWM_INTEN_SEQEND0_Enabled<<PWM_INTEN_SEQEND0_Pos);
299
300// PSEL must be configured before enabling PWM
301#if defined(ARDUINO_ARCH_NRF52840)
302 pwm->PSEL.OUT[0] = g_APinDescription[pin].name;
303#else
304 pwm->PSEL.OUT[0] = g_ADigitalPinMap[pin];
305#endif
306
307 // Enable the PWM
308 pwm->ENABLE = 1;
309
310 // After all of this and many hours of reading the documentation
311 // we are ready to start the sequence...
312 pwm->EVENTS_SEQEND[0] = 0;
313 pwm->TASKS_SEQSTART[0] = 1;
314
315 // But we have to wait for the flag to be set.
316 while (!pwm->EVENTS_SEQEND[0])
317 {
318#if defined(ARDUINO_NRF52_ADAFRUIT) || defined(ARDUINO_ARCH_NRF52840)
319 yield();
320#endif
321 }
322
323 // Before leave we clear the flag for the event.
324 pwm->EVENTS_SEQEND[0] = 0;
325
326 // We need to disable the device and disconnect
327 // all the outputs before leave or the device will not
328 // be selected on the next call.
329 // TODO: Check if disabling the device causes performance issues.
330 pwm->ENABLE = 0;
331
332 pwm->PSEL.OUT[0] = 0xFFFFFFFFUL;
333 } // End of DMA implementation
334 // ---------------------------------------------------------------------
335 else
336 {
338 // If this case appears, it means that a register has gone very wrong.
339 platform::lampda_print("WRONG EXECUTION PATH FOR LAMPDA STRIP DISPLAY");
340 }
341 // END of NRF52 implementation
342
343 endTime = micros(); // Save EOD time for latch on next call
344
345 // clang-format on
346}
347
348} // namespace strip
349} // namespace platform
350} // namespace lampda
351
352#endif
void show(void)
Definition: strip_impl.hpp:134
void lampda_print(const char *format,...)
Definition: print.cpp:43
Program scope.
Definition: control_fixed_modes.hpp:12
Interface for the platform specific debug and prints.
Implement a led strip object.
uint8_t neoPixelType
3rd arg to Adafruit_NeoPixel constructor
Definition: strip_impl.h:17