Skip to content

Commit a0872e8

Browse files
authored
Merge pull request #1161 from hathach/nrf52_neopixel
Nrf52 neopixel
2 parents 7654106 + ccd87a3 commit a0872e8

File tree

5 files changed

+283
-2
lines changed

5 files changed

+283
-2
lines changed

ports/nrf/Makefile

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -141,6 +141,7 @@ SRC_COMMON_HAL += \
141141
microcontroller/__init__.c \
142142
microcontroller/Pin.c \
143143
microcontroller/Processor.c \
144+
neopixel_write/__init__.c \
144145
os/__init__.c \
145146
time/__init__.c \
146147
analogio/__init__.c \
Lines changed: 267 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,267 @@
1+
/*
2+
* This file is part of the MicroPython project, http://micropython.org/
3+
*
4+
* The MIT License (MIT)
5+
*
6+
* Copyright (c) 2018 hathach for Adafruit Industries
7+
*
8+
* Permission is hereby granted, free of charge, to any person obtaining a copy
9+
* of this software and associated documentation files (the "Software"), to deal
10+
* in the Software without restriction, including without limitation the rights
11+
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
12+
* copies of the Software, and to permit persons to whom the Software is
13+
* furnished to do so, subject to the following conditions:
14+
*
15+
* The above copyright notice and this permission notice shall be included in
16+
* all copies or substantial portions of the Software.
17+
*
18+
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
19+
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
20+
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
21+
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
22+
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
23+
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
24+
* THE SOFTWARE.
25+
*/
26+
27+
#include "py/mphal.h"
28+
#include "shared-bindings/neopixel_write/__init__.h"
29+
#include "nrf_pwm.h"
30+
31+
// https://github.com/adafruit/Adafruit_NeoPixel/blob/master/Adafruit_NeoPixel.cpp
32+
// [[[Begin of the Neopixel NRF52 EasyDMA implementation
33+
// by the Hackerspace San Salvador]]]
34+
// This technique uses the PWM peripheral on the NRF52. The PWM uses the
35+
// EasyDMA feature included on the chip. This technique loads the duty
36+
// cycle configuration for each cycle when the PWM is enabled. For this
37+
// to work we need to store a 16 bit configuration for each bit of the
38+
// RGB(W) values in the pixel buffer.
39+
// Comparator values for the PWM were hand picked and are guaranteed to
40+
// be 100% organic to preserve freshness and high accuracy. Current
41+
// parameters are:
42+
// * PWM Clock: 16Mhz
43+
// * Minimum step time: 62.5ns
44+
// * Time for zero in high (T0H): 0.31ms
45+
// * Time for one in high (T1H): 0.75ms
46+
// * Cycle time: 1.25us
47+
// * Frequency: 800Khz
48+
// For 400Khz we just double the calculated times.
49+
// ---------- BEGIN Constants for the EasyDMA implementation -----------
50+
// The PWM starts the duty cycle in LOW. To start with HIGH we
51+
// need to set the 15th bit on each register.
52+
53+
// WS2812 (rev A) timing is 0.35 and 0.7us
54+
//#define MAGIC_T0H 5UL | (0x8000) // 0.3125us
55+
//#define MAGIC_T1H 12UL | (0x8000) // 0.75us
56+
57+
// WS2812B (rev B) timing is 0.4 and 0.8 us
58+
#define MAGIC_T0H 6UL | (0x8000) // 0.375us
59+
#define MAGIC_T1H 13UL | (0x8000) // 0.8125us
60+
#define CTOPVAL 20UL // 1.25us
61+
62+
// ---------- END Constants for the EasyDMA implementation -------------
63+
//
64+
// If there is no device available an alternative cycle-counter
65+
// implementation is tried.
66+
// The nRF52832 runs with a fixed clock of 64Mhz. The alternative
67+
// implementation is the same as the one used for the Teensy 3.0/1/2 but
68+
// with the Nordic SDK HAL & registers syntax.
69+
// The number of cycles was hand picked and is guaranteed to be 100%
70+
// organic to preserve freshness and high accuracy.
71+
// ---------- BEGIN Constants for cycle counter implementation ---------
72+
#define CYCLES_800_T0H 18 // ~0.36 uS
73+
#define CYCLES_800_T1H 41 // ~0.76 uS
74+
#define CYCLES_800 71 // ~1.25 uS
75+
76+
// ---------- END of Constants for cycle counter implementation --------
77+
78+
// find a free PWM device, which is not enabled and has no connected pins
79+
static NRF_PWM_Type* find_free_pwm (void) {
80+
NRF_PWM_Type* PWM[] = {
81+
NRF_PWM0, NRF_PWM1, NRF_PWM2
82+
#ifdef NRF_PWM3
83+
, NRF_PWM3
84+
#endif
85+
};
86+
87+
for ( int device = 0; device < ARRAY_SIZE(PWM); device++ ) {
88+
if ( (PWM[device]->ENABLE == 0) &&
89+
(PWM[device]->PSEL.OUT[0] & PWM_PSEL_OUT_CONNECT_Msk) && (PWM[device]->PSEL.OUT[1] & PWM_PSEL_OUT_CONNECT_Msk) &&
90+
(PWM[device]->PSEL.OUT[2] & PWM_PSEL_OUT_CONNECT_Msk) && (PWM[device]->PSEL.OUT[3] & PWM_PSEL_OUT_CONNECT_Msk) ) {
91+
return PWM[device];
92+
}
93+
}
94+
95+
return NULL;
96+
}
97+
98+
void common_hal_neopixel_write (const digitalio_digitalinout_obj_t* digitalinout, uint8_t *pixels, uint32_t numBytes) {
99+
// To support both the SoftDevice + Neopixels we use the EasyDMA
100+
// feature from the NRF25. However this technique implies to
101+
// generate a pattern and store it on the memory. The actual
102+
// memory used in bytes corresponds to the following formula:
103+
// totalMem = numBytes*8*2+(2*2)
104+
// The two additional bytes at the end are needed to reset the
105+
// sequence.
106+
//
107+
// If there is not enough memory, we will fall back to cycle counter
108+
// using DWT
109+
uint32_t pattern_size = numBytes * 8 * sizeof(uint16_t) + 2 * sizeof(uint16_t);
110+
uint16_t* pixels_pattern = NULL;
111+
112+
NRF_PWM_Type* pwm = find_free_pwm();
113+
114+
// only malloc if there is PWM device available
115+
if ( pwm != NULL ) {
116+
pixels_pattern = (uint16_t *) m_malloc(pattern_size, false);
117+
}
118+
119+
// Use the identified device to choose the implementation
120+
// If a PWM device is available use DMA
121+
if ( (pixels_pattern != NULL) && (pwm != NULL) ) {
122+
uint16_t pos = 0; // bit position
123+
124+
for ( uint16_t n = 0; n < numBytes; n++ ) {
125+
uint8_t pix = pixels[n];
126+
127+
for ( uint8_t mask = 0x80, i = 0; mask > 0; mask >>= 1, i++ ) {
128+
pixels_pattern[pos] = (pix & mask) ? MAGIC_T1H : MAGIC_T0H;
129+
pos++;
130+
}
131+
}
132+
133+
// Zero padding to indicate the end of sequence
134+
pixels_pattern[++pos] = 0 | (0x8000); // Seq end
135+
pixels_pattern[++pos] = 0 | (0x8000); // Seq end
136+
137+
// Set the wave mode to count UP
138+
// Set the PWM to use the 16MHz clock
139+
// Setting of the maximum count
140+
// but keeping it on 16Mhz allows for more granularity just
141+
// in case someone wants to do more fine-tuning of the timing.
142+
nrf_pwm_configure(pwm, NRF_PWM_CLK_16MHz, NRF_PWM_MODE_UP, CTOPVAL);
143+
144+
// Disable loops, we want the sequence to repeat only once
145+
nrf_pwm_loop_set(pwm, 0);
146+
147+
// On the "Common" setting the PWM uses the same pattern for the
148+
// for supported sequences. The pattern is stored on half-word of 16bits
149+
nrf_pwm_decoder_set(pwm, PWM_DECODER_LOAD_Common, PWM_DECODER_MODE_RefreshCount);
150+
151+
// Pointer to the memory storing the pattern
152+
nrf_pwm_seq_ptr_set(pwm, 0, pixels_pattern);
153+
154+
// Calculation of the number of steps loaded from memory.
155+
nrf_pwm_seq_cnt_set(pwm, 0, pattern_size / sizeof(uint16_t));
156+
157+
// The following settings are ignored with the current config.
158+
nrf_pwm_seq_refresh_set(pwm, 0, 0);
159+
nrf_pwm_seq_end_delay_set(pwm, 0, 0);
160+
161+
// The Neopixel implementation is a blocking algorithm. DMA
162+
// allows for non-blocking operation. To "simulate" a blocking
163+
// operation we enable the interruption for the end of sequence
164+
// and block the execution thread until the event flag is set by
165+
// the peripheral.
166+
// pwm->INTEN |= (PWM_INTEN_SEQEND0_Enabled<<PWM_INTEN_SEQEND0_Pos);
167+
168+
// PSEL must be configured before enabling PWM
169+
nrf_pwm_pins_set(pwm, (uint32_t[]) {digitalinout->pin->port*32 + digitalinout->pin->pin, 0xFFFFFFFFUL, 0xFFFFFFFFUL, 0xFFFFFFFFUL} );
170+
171+
// Enable the PWM
172+
nrf_pwm_enable(pwm);
173+
174+
// After all of this and many hours of reading the documentation
175+
// we are ready to start the sequence...
176+
nrf_pwm_event_clear(pwm, NRF_PWM_EVENT_SEQEND0);
177+
nrf_pwm_task_trigger(pwm, NRF_PWM_TASK_SEQSTART0);
178+
179+
// But we have to wait for the flag to be set.
180+
while ( !nrf_pwm_event_check(pwm, NRF_PWM_EVENT_SEQEND0) ) {
181+
#ifdef MICROPY_VM_HOOK_LOOP
182+
MICROPY_VM_HOOK_LOOP
183+
#endif
184+
}
185+
186+
// Before leave we clear the flag for the event.
187+
nrf_pwm_event_clear(pwm, NRF_PWM_EVENT_SEQEND0);
188+
189+
// We need to disable the device and disconnect
190+
// all the outputs before leave or the device will not
191+
// be selected on the next call.
192+
// TODO: Check if disabling the device causes performance issues.
193+
nrf_pwm_disable(pwm);
194+
nrf_pwm_pins_set(pwm, (uint32_t[]) {0xFFFFFFFFUL, 0xFFFFFFFFUL, 0xFFFFFFFFUL, 0xFFFFFFFFUL} );
195+
196+
m_free(pixels_pattern);
197+
} // End of DMA implementation
198+
// ---------------------------------------------------------------------
199+
else {
200+
// Fall back to DWT
201+
// If you are using the Bluetooth SoftDevice we advise you to not disable
202+
// the interrupts. Disabling the interrupts even for short periods of time
203+
// causes the SoftDevice to stop working.
204+
// Disable the interrupts only in cases where you need high performance for
205+
// the LEDs and if you are not using the EasyDMA feature.
206+
__disable_irq();
207+
208+
#ifdef NRF_P1
209+
NRF_GPIO_Type* port = ( digitalinout->pin->port ? NRF_P1 : NRF_P0 );
210+
#else
211+
NRF_GPIO_Type* port = NRF_P0;
212+
#endif
213+
uint32_t pinMask = ( 1UL << digitalinout->pin->pin );
214+
215+
uint32_t CYCLES_X00 = CYCLES_800;
216+
uint32_t CYCLES_X00_T1H = CYCLES_800_T1H;
217+
uint32_t CYCLES_X00_T0H = CYCLES_800_T0H;
218+
219+
// Enable DWT in debug core
220+
CoreDebug->DEMCR |= CoreDebug_DEMCR_TRCENA_Msk;
221+
DWT->CTRL |= DWT_CTRL_CYCCNTENA_Msk;
222+
223+
// Tries to re-send the frame if is interrupted by the SoftDevice.
224+
while ( 1 ) {
225+
uint8_t *p = pixels;
226+
227+
uint32_t cycStart = DWT->CYCCNT;
228+
uint32_t cyc = 0;
229+
230+
for ( uint16_t n = 0; n < numBytes; n++ ) {
231+
uint8_t pix = *p++;
232+
233+
for ( uint8_t mask = 0x80; mask; mask >>= 1 ) {
234+
while ( DWT->CYCCNT - cyc < CYCLES_X00 )
235+
;
236+
cyc = DWT->CYCCNT;
237+
238+
port->OUTSET |= pinMask;
239+
240+
if ( pix & mask ) {
241+
while ( DWT->CYCCNT - cyc < CYCLES_X00_T1H )
242+
;
243+
} else {
244+
while ( DWT->CYCCNT - cyc < CYCLES_X00_T0H )
245+
;
246+
}
247+
248+
port->OUTCLR |= pinMask;
249+
}
250+
}
251+
while ( DWT->CYCCNT - cyc < CYCLES_X00 )
252+
;
253+
254+
// If total time longer than 25%, resend the whole data.
255+
// Since we are likely to be interrupted by SoftDevice
256+
if ( (DWT->CYCCNT - cycStart) < (8 * numBytes * ((CYCLES_X00 * 5) / 4)) ) {
257+
break;
258+
}
259+
260+
// re-send need 300us delay
261+
mp_hal_delay_us(300);
262+
}
263+
264+
// Enable interrupts again
265+
__enable_irq();
266+
}
267+
}

ports/nrf/mpconfigport.h

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -180,6 +180,7 @@ extern const struct _mp_obj_module_t struct_module;
180180
extern const struct _mp_obj_module_t time_module;
181181
extern const struct _mp_obj_module_t supervisor_module;
182182
extern const struct _mp_obj_module_t gamepad_module;
183+
extern const struct _mp_obj_module_t neopixel_write_module;
183184
extern const struct _mp_obj_module_t usb_hid_module;
184185
extern const struct _mp_obj_module_t bleio_module;
185186

@@ -210,6 +211,7 @@ extern const struct _mp_obj_module_t mp_module_ubluepy;
210211
{ MP_OBJ_NEW_QSTR (MP_QSTR_digitalio ), (mp_obj_t)&digitalio_module }, \
211212
{ MP_OBJ_NEW_QSTR (MP_QSTR_pulseio ), (mp_obj_t)&pulseio_module }, \
212213
{ MP_OBJ_NEW_QSTR (MP_QSTR_microcontroller ), (mp_obj_t)&microcontroller_module }, \
214+
{ MP_OBJ_NEW_QSTR (MP_QSTR_neopixel_write ), (mp_obj_t)&neopixel_write_module }, \
213215
{ MP_OBJ_NEW_QSTR (MP_QSTR_bitbangio ), (mp_obj_t)&bitbangio_module }, \
214216
{ MP_OBJ_NEW_QSTR (MP_QSTR_os ), (mp_obj_t)&os_module }, \
215217
{ MP_OBJ_NEW_QSTR (MP_QSTR_random ), (mp_obj_t)&random_module }, \

ports/nrf/nrfx_config.h

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -34,4 +34,15 @@
3434
#define NRFX_UART_DEFAULT_CONFIG_PARITY NRF_UART_PARITY_EXCLUDED
3535
#define NRFX_UART_DEFAULT_CONFIG_BAUDRATE NRF_UART_BAUDRATE_115200
3636

37+
// PWM
38+
#define NRFX_PWM0_ENABLED 1
39+
#define NRFX_PWM1_ENABLED 1
40+
#define NRFX_PWM2_ENABLED 1
41+
42+
#ifdef NRF_PWM3
43+
#define NRFX_PWM3_ENABLED 1
44+
#else
45+
#define NRFX_PWM3_ENABLED 0
46+
#endif
47+
3748
#endif

ports/nrf/usb/usb.c

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -130,8 +130,8 @@ uint32_t tusb_hal_millis(void) {
130130
void tud_cdc_line_state_cb(uint8_t itf, bool dtr, bool rts) {
131131
(void) itf; // interface ID, not used
132132

133-
// disconnected event
134-
if ( !dtr && !rts )
133+
// DTR = false is counted as disconnected
134+
if ( !dtr )
135135
{
136136
cdc_line_coding_t coding;
137137
tud_cdc_get_line_coding(&coding);

0 commit comments

Comments
 (0)