/* * This file is part of the MicroPython project, http://micropython.org/ * * The MIT License (MIT) * * Copyright (c) 2021 Scott Shawcroft for Adafruit Industries * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal * in the Software without restriction, including without limitation the rights * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell * copies of the Software, and to permit persons to whom the Software is * furnished to do so, subject to the following conditions: * * The above copyright notice and this permission notice shall be included in * all copies or substantial portions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN * THE SOFTWARE. */ #include "shared-bindings/neopixel_write/__init__.h" #include "py/runtime.h" #include "shared-bindings/microcontroller/__init__.h" #include "shared-bindings/digitalio/DigitalInOut.h" #include "shared-bindings/time/__init__.h" #include "peripherals/broadcom/cpu.h" #include "supervisor/port.h" uint64_t next_start_raw_ticks = 0; // NeoPixels are 800khz bit streams. Zeroes are 1/3 duty cycle (~416ns) and ones // are 2/3 duty cycle (~833ns). void common_hal_neopixel_write(const digitalio_digitalinout_obj_t *digitalinout, uint8_t *pixels, uint32_t num_bytes) { // Wait to make sure we don't append onto the last transmission. This should only be a tick or // two. while (port_get_raw_ticks(NULL) < next_start_raw_ticks) { } BP_Function_Enum alt_function = GPIO_FUNCTION_OUTPUT; uint8_t index = 0; uint8_t channel = 0; bool found = false; for (size_t i = 0; i < NUM_ALT_FUNC; i++) { const pin_function_t *f = &digitalinout->pin->functions[i]; if (f->type == PIN_FUNCTION_PWM) { index = f->index; channel = f->function; alt_function = FSEL_VALUES[i]; found = true; break; } } if (!found) { mp_raise_ValueError(translate("NeoPixel not supported on pin")); return; } // Turn on the PWM clock. The speed is NeoPixel specific. if (CM_PWM->CS_b.BUSY == 0) { uint32_t source_clock; #if BCM_VERSION == 2711 source_clock = 54000000; #else source_clock = 19200000; #endif // Three clocks per 800khz bit to get the 1/3 or 2/3 timing. uint32_t target_clock = 3 * 800000; uint32_t int_div = source_clock / target_clock; CM_PWM->DIV = CM_PCM_DIV_PASSWD_PASSWD << CM_PCM_DIV_PASSWD_Pos | (int_div) << CM_PCM_DIV_DIVI_Pos; CM_PWM->CS = CM_PCM_CS_PASSWD_PASSWD << CM_PCM_CS_PASSWD_Pos | CM_PCM_CS_SRC_XOSC << CM_PCM_CS_SRC_Pos; // Set enable after setting the source to ensure it is stable. CM_PWM->CS = CM_PCM_CS_PASSWD_PASSWD << CM_PCM_CS_PASSWD_Pos | CM_PCM_CS_SRC_XOSC << CM_PCM_CS_SRC_Pos | CM_PCM_CS_ENAB_Msk; // Wait for the clock to start up. COMPLETE_MEMORY_READS; while (CM_PWM->CS_b.BUSY == 0) { } } PWM0_Type *pwm = PWM0; #if BCM_VERSION == 2711 if (index == 1) { pwm = PWM1; } #else (void)index; #endif pwm->RNG1 = 24; pwm->RNG2 = 24; COMPLETE_MEMORY_READS; pwm->CTL = PWM0_CTL_CLRF1_Msk; COMPLETE_MEMORY_READS; // Even though we're only transmitting one channel, we enable both. Without // the second channel enabled, the output is repeated forever. pwm->CTL = PWM0_CTL_USEF2_Msk | PWM0_CTL_MODE2_Msk | PWM0_CTL_USEF1_Msk | PWM0_CTL_MODE1_Msk; COMPLETE_MEMORY_READS; pwm->CTL |= PWM0_CTL_PWEN1_Msk | PWM0_CTL_PWEN2_Msk; gpio_set_function(digitalinout->pin->number, alt_function); for (size_t i = 0; i < num_bytes; i++) { uint32_t expanded = 0; for (size_t j = 0; j < 8; j++) { expanded = expanded >> 3; if ((pixels[i] & (1 << j)) != 0) { expanded |= 0xc0000000; } else { expanded |= 0x80000000; } } while (pwm->STA_b.FULL1 == 1) { RUN_BACKGROUND_TASKS; } if (channel == 1) { // Dummy value for the first channel. pwm->FIF1 = 0x000000; } pwm->FIF1 = expanded; if (channel == 0) { // Dummy value for the second channel. pwm->FIF1 = 0x000000; } } // Wait just a little bit so that transmission can start. common_hal_mcu_delay_us(2); while (pwm->STA_b.STA1 == 1) { RUN_BACKGROUND_TASKS; } gpio_set_function(digitalinout->pin->number, GPIO_FUNCTION_OUTPUT); // Update the next start. next_start_raw_ticks = port_get_raw_ticks(NULL) + 1; }