Skip to content

Commit 3ae24d9

Browse files
committed
add neopixel_write implementation, work ok
1 parent e172530 commit 3ae24d9

File tree

1 file changed

+233
-1
lines changed

1 file changed

+233
-1
lines changed

ports/nrf/common-hal/neopixel_write/__init__.c

Lines changed: 233 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,238 @@
2727
#include "py/mphal.h"
2828
#include "shared-bindings/neopixel_write/__init__.h"
2929

30-
void common_hal_neopixel_write(const digitalio_digitalinout_obj_t* digitalinout, uint8_t *pixels, uint32_t numBytes) {
30+
// https://github.com/adafruit/Adafruit_NeoPixel/blob/master/Adafruit_NeoPixel.cpp
31+
// [[[Begin of the Neopixel NRF52 EasyDMA implementation
32+
// by the Hackerspace San Salvador]]]
33+
// This technique uses the PWM peripheral on the NRF52. The PWM uses the
34+
// EasyDMA feature included on the chip. This technique loads the duty
35+
// cycle configuration for each cycle when the PWM is enabled. For this
36+
// to work we need to store a 16 bit configuration for each bit of the
37+
// RGB(W) values in the pixel buffer.
38+
// Comparator values for the PWM were hand picked and are guaranteed to
39+
// be 100% organic to preserve freshness and high accuracy. Current
40+
// parameters are:
41+
// * PWM Clock: 16Mhz
42+
// * Minimum step time: 62.5ns
43+
// * Time for zero in high (T0H): 0.31ms
44+
// * Time for one in high (T1H): 0.75ms
45+
// * Cycle time: 1.25us
46+
// * Frequency: 800Khz
47+
// For 400Khz we just double the calculated times.
48+
// ---------- BEGIN Constants for the EasyDMA implementation -----------
49+
// The PWM starts the duty cycle in LOW. To start with HIGH we
50+
// need to set the 15th bit on each register.
3151

52+
// WS2812 (rev A) timing is 0.35 and 0.7us
53+
//#define MAGIC_T0H 5UL | (0x8000) // 0.3125us
54+
//#define MAGIC_T1H 12UL | (0x8000) // 0.75us
55+
56+
// WS2812B (rev B) timing is 0.4 and 0.8 us
57+
#define MAGIC_T0H 6UL | (0x8000) // 0.375us
58+
#define MAGIC_T1H 13UL | (0x8000) // 0.8125us
59+
#define CTOPVAL 20UL // 1.25us
60+
61+
// ---------- END Constants for the EasyDMA implementation -------------
62+
//
63+
// If there is no device available an alternative cycle-counter
64+
// implementation is tried.
65+
// The nRF52832 runs with a fixed clock of 64Mhz. The alternative
66+
// implementation is the same as the one used for the Teensy 3.0/1/2 but
67+
// with the Nordic SDK HAL & registers syntax.
68+
// The number of cycles was hand picked and is guaranteed to be 100%
69+
// organic to preserve freshness and high accuracy.
70+
// ---------- BEGIN Constants for cycle counter implementation ---------
71+
#define CYCLES_800_T0H 18 // ~0.36 uS
72+
#define CYCLES_800_T1H 41 // ~0.76 uS
73+
#define CYCLES_800 71 // ~1.25 uS
74+
75+
// ---------- END of Constants for cycle counter implementation --------
76+
77+
// find a free PWM device, which is not enabled and has no connected pins
78+
static NRF_PWM_Type* find_free_pwm (void) {
79+
NRF_PWM_Type* PWM[3] = { NRF_PWM0, NRF_PWM1, NRF_PWM2 };
80+
81+
for ( int device = 0; device < 3; device++ ) {
82+
if ( (PWM[device]->ENABLE == 0) && (PWM[device]->PSEL.OUT[0] & PWM_PSEL_OUT_CONNECT_Msk)
83+
&& (PWM[device]->PSEL.OUT[1] & PWM_PSEL_OUT_CONNECT_Msk)
84+
&& (PWM[device]->PSEL.OUT[2] & PWM_PSEL_OUT_CONNECT_Msk)
85+
&& (PWM[device]->PSEL.OUT[3] & PWM_PSEL_OUT_CONNECT_Msk) ) {
86+
return PWM[device];
87+
}
88+
}
89+
90+
return NULL;
91+
}
92+
93+
void common_hal_neopixel_write (const digitalio_digitalinout_obj_t* digitalinout, uint8_t *pixels, uint32_t numBytes) {
94+
// To support both the SoftDevice + Neopixels we use the EasyDMA
95+
// feature from the NRF25. However this technique implies to
96+
// generate a pattern and store it on the memory. The actual
97+
// memory used in bytes corresponds to the following formula:
98+
// totalMem = numBytes*8*2+(2*2)
99+
// The two additional bytes at the end are needed to reset the
100+
// sequence.
101+
//
102+
// If there is not enough memory, we will fall back to cycle counter
103+
// using DWT
104+
uint32_t pattern_size = numBytes * 8 * sizeof(uint16_t) + 2 * sizeof(uint16_t);
105+
uint16_t* pixels_pattern = NULL;
106+
107+
NRF_PWM_Type* pwm = find_free_pwm();
108+
109+
// only malloc if there is PWM device available
110+
if ( pwm != NULL ) {
111+
pixels_pattern = (uint16_t *) m_malloc(pattern_size, false);
112+
}
113+
114+
// Use the identified device to choose the implementation
115+
// If a PWM device is available use DMA
116+
if ( (pixels_pattern != NULL) && (pwm != NULL) ) {
117+
uint16_t pos = 0; // bit position
118+
119+
for ( uint16_t n = 0; n < numBytes; n++ ) {
120+
uint8_t pix = pixels[n];
121+
122+
for ( uint8_t mask = 0x80, i = 0; mask > 0; mask >>= 1, i++ ) {
123+
pixels_pattern[pos] = (pix & mask) ? MAGIC_T1H : MAGIC_T0H;
124+
pos++;
125+
}
126+
}
127+
128+
// Zero padding to indicate the end of sequence
129+
pixels_pattern[++pos] = 0 | (0x8000); // Seq end
130+
pixels_pattern[++pos] = 0 | (0x8000); // Seq end
131+
132+
// Set the wave mode to count UP
133+
pwm->MODE = (PWM_MODE_UPDOWN_Up << PWM_MODE_UPDOWN_Pos);
134+
135+
// Set the PWM to use the 16MHz clock
136+
pwm->PRESCALER = (PWM_PRESCALER_PRESCALER_DIV_1 << PWM_PRESCALER_PRESCALER_Pos);
137+
138+
// Setting of the maximum count
139+
// but keeping it on 16Mhz allows for more granularity just
140+
// in case someone wants to do more fine-tuning of the timing.
141+
pwm->COUNTERTOP = (CTOPVAL << PWM_COUNTERTOP_COUNTERTOP_Pos);
142+
143+
// Disable loops, we want the sequence to repeat only once
144+
pwm->LOOP = (PWM_LOOP_CNT_Disabled << PWM_LOOP_CNT_Pos);
145+
146+
// On the "Common" setting the PWM uses the same pattern for the
147+
// for supported sequences. The pattern is stored on half-word
148+
// of 16bits
149+
pwm->DECODER = (PWM_DECODER_LOAD_Common << PWM_DECODER_LOAD_Pos)
150+
| (PWM_DECODER_MODE_RefreshCount << PWM_DECODER_MODE_Pos);
151+
152+
// Pointer to the memory storing the patter
153+
pwm->SEQ[0].PTR = (uint32_t) (pixels_pattern) << PWM_SEQ_PTR_PTR_Pos;
154+
155+
// Calculation of the number of steps loaded from memory.
156+
pwm->SEQ[0].CNT = (pattern_size / sizeof(uint16_t)) << PWM_SEQ_CNT_CNT_Pos;
157+
158+
// The following settings are ignored with the current config.
159+
pwm->SEQ[0].REFRESH = 0;
160+
pwm->SEQ[0].ENDDELAY = 0;
161+
162+
// The Neopixel implementation is a blocking algorithm. DMA
163+
// allows for non-blocking operation. To "simulate" a blocking
164+
// operation we enable the interruption for the end of sequence
165+
// and block the execution thread until the event flag is set by
166+
// the peripheral.
167+
// pwm->INTEN |= (PWM_INTEN_SEQEND0_Enabled<<PWM_INTEN_SEQEND0_Pos);
168+
169+
// PSEL must be configured before enabling PWM
170+
pwm->PSEL.OUT[0] = ( digitalinout->pin->port*32 + digitalinout->pin->pin );
171+
172+
// Enable the PWM
173+
pwm->ENABLE = 1;
174+
175+
// After all of this and many hours of reading the documentation
176+
// we are ready to start the sequence...
177+
pwm->EVENTS_SEQEND[0] = 0;
178+
pwm->TASKS_SEQSTART[0] = 1;
179+
180+
// But we have to wait for the flag to be set.
181+
while ( !pwm->EVENTS_SEQEND[0] ) {
182+
//#ifdef MICROPY_VM_HOOK_LOOP
183+
// MICROPY_VM_HOOK_LOOP
184+
//#endif
185+
}
186+
187+
// Before leave we clear the flag for the event.
188+
pwm->EVENTS_SEQEND[0] = 0;
189+
190+
// We need to disable the device and disconnect
191+
// all the outputs before leave or the device will not
192+
// be selected on the next call.
193+
// TODO: Check if disabling the device causes performance issues.
194+
pwm->ENABLE = 0;
195+
pwm->PSEL.OUT[0] = 0xFFFFFFFFUL;
196+
197+
m_free(pixels_pattern);
198+
} // End of DMA implementation
199+
// ---------------------------------------------------------------------
200+
else {
201+
// Fall back to DWT
202+
// If you are using the Bluetooth SoftDevice we advise you to not disable
203+
// the interrupts. Disabling the interrupts even for short periods of time
204+
// causes the SoftDevice to stop working.
205+
// Disable the interrupts only in cases where you need high performance for
206+
// the LEDs and if you are not using the EasyDMA feature.
207+
__disable_irq();
208+
209+
NRF_GPIO_Type* port = ( digitalinout->pin->port ? NRF_P1 : NRF_P0 );
210+
uint32_t pinMask = ( 1UL << digitalinout->pin->pin );
211+
212+
uint32_t CYCLES_X00 = CYCLES_800;
213+
uint32_t CYCLES_X00_T1H = CYCLES_800_T1H;
214+
uint32_t CYCLES_X00_T0H = CYCLES_800_T0H;
215+
216+
// Enable DWT in debug core
217+
CoreDebug->DEMCR |= CoreDebug_DEMCR_TRCENA_Msk;
218+
DWT->CTRL |= DWT_CTRL_CYCCNTENA_Msk;
219+
220+
// Tries to re-send the frame if is interrupted by the SoftDevice.
221+
while ( 1 ) {
222+
uint8_t *p = pixels;
223+
224+
uint32_t cycStart = DWT->CYCCNT;
225+
uint32_t cyc = 0;
226+
227+
for ( uint16_t n = 0; n < numBytes; n++ ) {
228+
uint8_t pix = *p++;
229+
230+
for ( uint8_t mask = 0x80; mask; mask >>= 1 ) {
231+
while ( DWT->CYCCNT - cyc < CYCLES_X00 )
232+
;
233+
cyc = DWT->CYCCNT;
234+
235+
port->OUTSET |= pinMask;
236+
237+
if ( pix & mask ) {
238+
while ( DWT->CYCCNT - cyc < CYCLES_X00_T1H )
239+
;
240+
} else {
241+
while ( DWT->CYCCNT - cyc < CYCLES_X00_T0H )
242+
;
243+
}
244+
245+
port->OUTCLR |= pinMask;
246+
}
247+
}
248+
while ( DWT->CYCCNT - cyc < CYCLES_X00 )
249+
;
250+
251+
// If total time longer than 25%, resend the whole data.
252+
// Since we are likely to be interrupted by SoftDevice
253+
if ( (DWT->CYCCNT - cycStart) < (8 * numBytes * ((CYCLES_X00 * 5) / 4)) ) {
254+
break;
255+
}
256+
257+
// re-send need 300us delay
258+
mp_hal_delay_us(300);
259+
}
260+
261+
// Enable interrupts again
262+
__enable_irq();
263+
}
32264
}

0 commit comments

Comments
 (0)