|
| 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 | +} |
0 commit comments