From 2693e2ce0ab6d3b63825bb8e8270bce975619caf Mon Sep 17 00:00:00 2001 From: Scott Shawcroft Date: Fri, 10 Dec 2021 17:08:47 -0800 Subject: [PATCH] Add neopixel support on PWM capable pins It'll be hard to do PWM from them concurrently Fixes #5697 --- .pre-commit-config.yaml | 2 +- ports/broadcom/Makefile | 4 +- .../common-hal/microcontroller/__init__.c | 1 + .../common-hal/neopixel_write/__init__.c | 158 ++++++++++++++++++ ports/broadcom/mpconfigport.mk | 4 +- ports/broadcom/mphalport.c | 23 +++ ports/broadcom/peripherals | 2 +- 7 files changed, 188 insertions(+), 6 deletions(-) create mode 100644 ports/broadcom/common-hal/neopixel_write/__init__.c diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index cfee897258..bcf7f8da2b 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -4,7 +4,7 @@ repos: - repo: https://github.com/pre-commit/pre-commit-hooks - rev: v2.3.0 + rev: v4.0.1 hooks: - id: check-yaml - id: end-of-file-fixer diff --git a/ports/broadcom/Makefile b/ports/broadcom/Makefile index 401d601528..1179d8fa16 100644 --- a/ports/broadcom/Makefile +++ b/ports/broadcom/Makefile @@ -82,7 +82,7 @@ SRC_SHARED_MODULE_EXPANDED = $(addprefix shared-bindings/, $(SRC_SHARED_MODULE)) # because a few modules have files both in common-hal/ and shared-modules/. # Doing a $(sort ...) removes duplicates as part of sorting. SRC_COMMON_HAL_SHARED_MODULE_EXPANDED = $(sort $(SRC_COMMON_HAL_EXPANDED) $(SRC_SHARED_MODULE_EXPANDED)) -SRC_S = peripherals/broadcom/boot.s +SRC_S = peripherals/broadcom/boot8.s OBJ = $(PY_O) $(SUPERVISOR_O) $(addprefix $(BUILD)/, $(SRC_C:.c=.o)) OBJ += $(addprefix $(BUILD)/, $(SRC_COMMON_HAL_SHARED_MODULE_EXPANDED:.c=.o)) @@ -134,7 +134,7 @@ CFLAGS += $(INC) -Wall -Werror -std=gnu11 $(BASE_CFLAGS) $(CFLAGS_MOD) $(COPT) $ SRC_QSTR += $(SRC_C) $(SRC_SUPERVISOR) $(SRC_COMMON_HAL_EXPANDED) $(SRC_SHARED_MODULE_EXPANDED) -LDFLAGS += $(CFLAGS) -T peripherals/broadcom/link.ld -Wl,--gc-sections -Wl,-Map=$@.map # -Wl,--cref +LDFLAGS += $(CFLAGS) -T peripherals/broadcom/link8.ld -Wl,--gc-sections -Wl,-Map=$@.map # -Wl,--cref # Use toolchain libm if we're not using our own. ifndef INTERNAL_LIBM diff --git a/ports/broadcom/common-hal/microcontroller/__init__.c b/ports/broadcom/common-hal/microcontroller/__init__.c index e6f7b9c737..a1491d9668 100644 --- a/ports/broadcom/common-hal/microcontroller/__init__.c +++ b/ports/broadcom/common-hal/microcontroller/__init__.c @@ -29,6 +29,7 @@ #include "shared-bindings/microcontroller/Pin.h" #include "shared-bindings/microcontroller/Processor.h" #include "common-hal/microcontroller/__init__.h" +#include "peripherals/broadcom/defines.h" #include "peripherals/broadcom/interrupts.h" #include "mphalport.h" diff --git a/ports/broadcom/common-hal/neopixel_write/__init__.c b/ports/broadcom/common-hal/neopixel_write/__init__.c new file mode 100644 index 0000000000..420ca369da --- /dev/null +++ b/ports/broadcom/common-hal/neopixel_write/__init__.c @@ -0,0 +1,158 @@ +/* + * 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; + uint8_t index; + uint8_t channel; + 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; + } + #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; +} diff --git a/ports/broadcom/mpconfigport.mk b/ports/broadcom/mpconfigport.mk index b17eb12f63..f29f48d545 100644 --- a/ports/broadcom/mpconfigport.mk +++ b/ports/broadcom/mpconfigport.mk @@ -8,7 +8,7 @@ CIRCUITPY_BUSIO = 1 CIRCUITPY_ONEWIREIO = 0 CIRCUITPY_PWMIO = 0 CIRCUITPY_COUNTIO = 0 -CIRCUITPY_NEOPIXEL_WRITE = 0 +CIRCUITPY_NEOPIXEL_WRITE = 1 CIRCUITPY_PULSEIO = 0 CIRCUITPY_OS = 1 CIRCUITPY_NVM = 0 @@ -31,7 +31,7 @@ CIRCUITPY_BITBANGIO = 1 # Requires DigitalIO CIRCUITPY_GAMEPAD = 0 # Requires neopixel_write or SPI (dotstar) -CIRCUITPY_PIXELBUF = 0 +CIRCUITPY_PIXELBUF = 1 # Requires OS CIRCUITPY_RANDOM = 1 # Requires OS, filesystem diff --git a/ports/broadcom/mphalport.c b/ports/broadcom/mphalport.c index f4bfd8653b..91e7b631bc 100644 --- a/ports/broadcom/mphalport.c +++ b/ports/broadcom/mphalport.c @@ -6,7 +6,15 @@ #include "shared-bindings/microcontroller/__init__.h" #include "mphalport.h" +#include "peripherals/broadcom/defines.h" + void mp_hal_delay_us(mp_uint_t delay) { + uint32_t end = SYSTMR->CLO + delay; + // Wait if end is before current time because it must have wrapped. + while (end < SYSTMR->CLO) { + } + while (SYSTMR->CLO < end) { + } } void mp_hal_disable_all_interrupts(void) { @@ -19,6 +27,7 @@ void mp_hal_enable_all_interrupts(void) { mp_uint_t cpu_get_regs_and_sp(mp_uint_t *regs) { size_t sp = 0; + #if defined(__ARM_ARCH) && (__ARM_ARCH >= 8) __asm__ ("mov %[out], sp" : [out] "=r" (sp)); __asm__ ("mov %[out], x19" : [out] "=r" (regs[0])); __asm__ ("mov %[out], x20" : [out] "=r" (regs[1])); @@ -30,5 +39,19 @@ mp_uint_t cpu_get_regs_and_sp(mp_uint_t *regs) { __asm__ ("mov %[out], x26" : [out] "=r" (regs[7])); __asm__ ("mov %[out], x27" : [out] "=r" (regs[8])); __asm__ ("mov %[out], x28" : [out] "=r" (regs[9])); + #else + __asm__ ("mov %[out], sp" : [out] "=r" (sp)); + __asm__ ("mov %[out], x19" : [out] "=r" (regs[0])); + __asm__ ("mov %[out], x20" : [out] "=r" (regs[1])); + __asm__ ("mov %[out], x21" : [out] "=r" (regs[2])); + __asm__ ("mov %[out], x22" : [out] "=r" (regs[3])); + __asm__ ("mov %[out], x23" : [out] "=r" (regs[4])); + __asm__ ("mov %[out], x24" : [out] "=r" (regs[5])); + __asm__ ("mov %[out], x25" : [out] "=r" (regs[6])); + __asm__ ("mov %[out], x26" : [out] "=r" (regs[7])); + __asm__ ("mov %[out], x27" : [out] "=r" (regs[8])); + __asm__ ("mov %[out], x28" : [out] "=r" (regs[9])); + #endif + return sp; } diff --git a/ports/broadcom/peripherals b/ports/broadcom/peripherals index e136e38717..2c10889a4b 160000 --- a/ports/broadcom/peripherals +++ b/ports/broadcom/peripherals @@ -1 +1 @@ -Subproject commit e136e387177446c3c9979bbf274a4856bf13797d +Subproject commit 2c10889a4b2d78987bc4e0783db2e7584aa4d572