diff options
Diffstat (limited to 'circuitpython/lib/protomatter/src/arch')
| -rw-r--r-- | circuitpython/lib/protomatter/src/arch/arch.h | 209 | ||||
| -rw-r--r-- | circuitpython/lib/protomatter/src/arch/esp32.h | 215 | ||||
| -rw-r--r-- | circuitpython/lib/protomatter/src/arch/nrf52.h | 216 | ||||
| -rw-r--r-- | circuitpython/lib/protomatter/src/arch/rp2040.h | 245 | ||||
| -rw-r--r-- | circuitpython/lib/protomatter/src/arch/samd-common.h | 98 | ||||
| -rw-r--r-- | circuitpython/lib/protomatter/src/arch/samd21.h | 150 | ||||
| -rw-r--r-- | circuitpython/lib/protomatter/src/arch/samd51.h | 216 | ||||
| -rw-r--r-- | circuitpython/lib/protomatter/src/arch/stm32.h | 146 | ||||
| -rw-r--r-- | circuitpython/lib/protomatter/src/arch/teensy4.h | 172 |
9 files changed, 1667 insertions, 0 deletions
diff --git a/circuitpython/lib/protomatter/src/arch/arch.h b/circuitpython/lib/protomatter/src/arch/arch.h new file mode 100644 index 0000000..2e28d2f --- /dev/null +++ b/circuitpython/lib/protomatter/src/arch/arch.h @@ -0,0 +1,209 @@ +/*! + * @file arch.h + * + * Part of Adafruit's Protomatter library for HUB75-style RGB LED matrices. + * This file establishes some very low-level things and includes headers + * specific to each supported device. This should ONLY be included by + * core.c, nowhere else. Ever. + * + * Adafruit invests time and resources providing this open source code, + * please support Adafruit and open-source hardware by purchasing + * products from Adafruit! + * + * Written by Phil "Paint Your Dragon" Burgess and Jeff Epler for + * Adafruit Industries, with contributions from the open source community. + * + * BSD license, all text here must be included in any redistribution. + * + */ + +#pragma once + +#include <string.h> + +/* +Common ground for architectures to support this library: + +- 32-bit device (e.g. ARM core, ESP32, potentially others in the future) +- One or more 32-bit GPIO PORTs with atomic bitmask SET and CLEAR registers. + A TOGGLE register, if present, may improve performance but is NOT required. +- Tolerate 8-bit or word-aligned 16-bit accesses within the 32-bit PORT + registers (e.g. writing just one of four bytes, rather than the whole + 32 bits). The library does not use any unaligned accesses (i.e. the + "middle word" of a 32-bit register), even if a device tolerates such. + +"Pin" as used in this code is always a uint8_t value, but the semantics +of what it means may vary between Arduino and non-Arduino situations. +In Arduino, it's the pin index one would pass to functions such as +digitalWrite(), and doesn't necessarily correspond to physical hardware +pins or any other arrangement. Some may have names like 'A0' that really +just map to higher indices. +In non-Arduino settings (CircuitPython, other languages, etc.), how a +pin index relates to hardware is entirely implementation dependent, and +how to get from one to the other is what must be implemented in this file. +Quite often an environment will follow the Arduino pin designations +(since the numbers are on a board's silkscreen) and will have an internal +table mapping those indices to registers and bitmasks...but probably not +an identically-named and -structured table to the Arduino code, hence the +reason for many "else" situations in this code. + +Each architecture defines the following macros and/or functions (the _PM_ +prefix on each is to reduce likelihood of naming collisions...especially +on ESP32, which has some similarly-named timer functions: + +GPIO-related macros/functions: + +_PM_portOutRegister(pin): Get address of PORT out register. Code calling + this can cast it to whatever type's needed. +_PM_portSetRegister(pin): Get address of PORT set-bits register. +_PM_portClearRegister(pin): Get address of PORT clear-bits register. +_PM_portToggleRegister(pin): Get address of PORT toggle-bits register. + Not all devices support this, in which case + it must be left undefined. +_PM_portBitMask(pin): Get bit mask within PORT register corresponding + to a pin number. When compiling for Arduino, + this just maps to digitalPinToBitMask(), other + environments will need an equivalent. +_PM_byteOffset(pin): Get index of byte (0 to 3) within 32-bit PORT + corresponding to a pin number. +_PM_wordOffset(pin): Get index of word (0 or 1) within 32-bit PORT + corresponding to a pin number. +_PM_pinOutput(pin): Set a pin to output mode. In Arduino this maps + to pinMode(pin, OUTPUT). Other environments + will need an equivalent. +_PM_pinInput(pin): Set a pin to input mode, no pullup. In Arduino + this maps to pinMode(pin, INPUT). +_PM_pinHigh(pin): Set an output pin to a high or 1 state. In + Arduino this maps to digitalWrite(pin, HIGH). +_PM_pinLow(pin): Set an output pin to a low or 0 state. In + Arduino this maps to digitalWrite(pin, LOW). + +Timer-related macros/functions: + +_PM_timerFreq: A numerical constant - the source clock rate + (in Hz) that's fed to the timer peripheral. +_PM_timerInit(void*): Initialize (but do not start) timer. +_PM_timerStart(void*,count): (Re)start timer for a given timer-tick interval. +_PM_timerStop(void*): Stop timer, return current timer counter value. +_PM_timerGetCount(void*): Get current timer counter value (whether timer + is running or stopped). +A timer interrupt service routine is also required, syntax for which varies +between architectures. +The void* argument passed to the timer functions is some indeterminate type +used to uniquely identify a timer peripheral within a given environment. For +example, in the Arduino wrapper for this library, compiling for SAMD chips, +it's just a pointer directly to a timer/counter peripheral base address. If +an implementation needs more data associated alongside a peripheral, this +could instead be a pointer to a struct, or an integer index. + +Other macros/functions: + +_PM_chunkSize: Matrix bitmap width (both in RAM and as issued + to the device) is rounded up (if necessary) to + a multiple of this value as a way of explicitly + unrolling the innermost data-stuffing loops. + So far all HUB75 displays I've encountered are + a multiple of 32 pixels wide, but in case + something new comes along, or if a larger + unroll actually decreases performance due to + cache size, this can be set to whatever works + best (any additional data is simply shifted + out the other end of the matrix). Default if + unspecified is 8 (e.g. four loop passes on a + 32-pixel matrix, eight if 64-pixel). Only + certain chunkSizes are actually implemented, + see .cpp code (avoiding GCC-specific tricks + that would handle arbitrary chunk sizes). +_PM_delayMicroseconds(us): Function or macro to delay some number of + microseconds. For Arduino, this just maps to + delayMicroseconds(). Other environments will + need to provide their own or map to an + an equivalent function. +_PM_clockHoldHigh: Additional code (typically some number of NOPs) + needed to delay the clock fall after RGB data is + written to PORT. Only required on fast devices. + If left undefined, no delay happens. +_PM_clockHoldLow: Additional code (e.g. NOPs) needed to delay + clock rise after writing RGB data to PORT. + No delay if left undefined. +_PM_minMinPeriod: Mininum value for the "minPeriod" class member, + so bit-angle-modulation time always doubles with + each bitplane (else lower bits may be the same). +_PM_allocate: Memory allocation function, should return a + pointer to a buffer of requested size, aligned + to the architecture's largest native type. + If not defined, malloc() is used. +_PM_free: Corresponding deallocator for _PM_allocate(). + If not defined, free() is used. +*/ + +// ENVIRONMENT-SPECIFIC DECLARATIONS --------------------------------------- + +#if defined(ARDUINO) // COMPILING FOR ARDUINO ------------------------------ + +#include <Arduino.h> // Pull in all that stuff. + +#define _PM_delayMicroseconds(us) delayMicroseconds(us) +#define _PM_pinOutput(pin) pinMode(pin, OUTPUT) +#define _PM_pinInput(pin) pinMode(pin, INPUT) +#define _PM_pinHigh(pin) digitalWrite(pin, HIGH) +#define _PM_pinLow(pin) digitalWrite(pin, LOW) +#define _PM_portBitMask(pin) digitalPinToBitMask(pin) + +#elif defined(CIRCUITPY) // COMPILING FOR CIRCUITPYTHON -------------------- + +#include "py/mphal.h" +#include "shared-bindings/microcontroller/Pin.h" + +#define _PM_delayMicroseconds(us) mp_hal_delay_us(us) + +// No #else here. In non-Arduino case, declare things in the arch-specific +// files below...unless other environments provide device-neutral functions +// as above, in which case those could go here (w/#elif). + +#endif // END CIRCUITPYTHON ------------------------------------------------ + +// ARCHITECTURE-SPECIFIC HEADERS ------------------------------------------- + +#include "esp32.h" +#include "nrf52.h" +#include "rp2040.h" +#include "samd-common.h" +#include "samd21.h" +#include "samd51.h" +#include "stm32.h" +#include "teensy4.h" + +// DEFAULTS IF NOT DEFINED ABOVE ------------------------------------------- + +#if !defined(_PM_chunkSize) +#define _PM_chunkSize 8 ///< Unroll data-stuffing loop to this size +#endif + +#if !defined(_PM_clockHoldHigh) +#define _PM_clockHoldHigh ///< Extra cycles (if any) on clock HIGH signal +#endif + +#if !defined(_PM_clockHoldLow) +#define _PM_clockHoldLow ///< Extra cycles (if any) on clock LOW signal +#endif + +#if !defined(_PM_minMinPeriod) +#define _PM_minMinPeriod 100 ///< Minimum timer interval for least bit +#endif + +#if !defined(_PM_allocate) +#define _PM_allocate(x) (malloc((x))) ///< Memory alloc call +#endif + +#if !defined(_PM_free) +#define _PM_free(x) (free((x))) ///< Corresponding memory free call +#endif + +#if !defined(IRAM_ATTR) +#define IRAM_ATTR ///< Neutralize ESP32-specific attribute in core.c +#endif + +#if !defined(_PM_PORT_TYPE) +#define _PM_PORT_TYPE uint32_t ///< PORT register size/type +#endif diff --git a/circuitpython/lib/protomatter/src/arch/esp32.h b/circuitpython/lib/protomatter/src/arch/esp32.h new file mode 100644 index 0000000..7171f9a --- /dev/null +++ b/circuitpython/lib/protomatter/src/arch/esp32.h @@ -0,0 +1,215 @@ +/*! + * @file esp32.h + * + * Part of Adafruit's Protomatter library for HUB75-style RGB LED matrices. + * This file contains ESP32-SPECIFIC CODE. + * + * Adafruit invests time and resources providing this open source code, + * please support Adafruit and open-source hardware by purchasing + * products from Adafruit! + * + * Written by Phil "Paint Your Dragon" Burgess and Jeff Epler for + * Adafruit Industries, with contributions from the open source community. + * + * BSD license, all text here must be included in any redistribution. + * + */ + +#pragma once + +#if defined(ESP32) + +#include "driver/timer.h" + +#ifdef CONFIG_IDF_TARGET_ESP32C3 +#define _PM_portOutRegister(pin) (volatile uint32_t *)&GPIO.out +#define _PM_portSetRegister(pin) (volatile uint32_t *)&GPIO.out_w1ts +#define _PM_portClearRegister(pin) (volatile uint32_t *)&GPIO.out_w1tc +#else +#define _PM_portOutRegister(pin) \ + (volatile uint32_t *)((pin < 32) ? &GPIO.out : &GPIO.out1.val) +#define _PM_portSetRegister(pin) \ + (volatile uint32_t *)((pin < 32) ? &GPIO.out_w1ts : &GPIO.out1_w1ts.val) +#define _PM_portClearRegister(pin) \ + (volatile uint32_t *)((pin < 32) ? &GPIO.out_w1tc : &GPIO.out1_w1tc.val) +#endif + +#if __BYTE_ORDER__ == __ORDER_LITTLE_ENDIAN__ +#define _PM_byteOffset(pin) ((pin & 31) / 8) +#define _PM_wordOffset(pin) ((pin & 31) / 16) +#else +#define _PM_byteOffset(pin) (3 - ((pin & 31) / 8)) +#define _PM_wordOffset(pin) (1 - ((pin & 31) / 16)) +#endif + +// As written, because it's tied to a specific timer right now, the +// Arduino lib only permits one instance of the Protomatter_core struct, +// which it sets up when calling begin(). +void *_PM_protoPtr = NULL; + +#define _PM_timerFreq 40000000 // 40 MHz (1:2 prescale) + +#if defined(ARDUINO) // COMPILING FOR ARDUINO ------------------------------ + +// ESP32 requires a custom PEW declaration (issues one set of RGB color bits +// followed by clock pulse). Turns out the bit set/clear registers are not +// actually atomic. If two writes are made in quick succession, the second +// has no effect. One option is NOPs, other is to write a 0 (no effect) to +// the opposing register (set vs clear) to synchronize the next write. +#define PEW \ + *set = *data++; /* Set RGB data high */ \ + *clear_full = 0; /* ESP32 MUST sync before 2nd 'set' */ \ + *set_full = clock; /* Set clock high */ \ + *clear_full = rgbclock; /* Clear RGB data + clock */ \ + ///< Bitbang one set of RGB data bits to matrix + +#define _PM_timerNum 0 // Timer #0 (can be 0-3) + +// This is the default aforementioned singular timer. IN THEORY, other +// timers could be used, IF an Arduino sketch passes the address of its +// own hw_timer_t* to the Protomatter constructor and initializes that +// timer using ESP32's timerBegin(). All of the timer-related functions +// below pass around a handle rather than accessing _PM_esp32timer +// directly, in case that's ever actually used in the future. +static hw_timer_t *_PM_esp32timer = NULL; +#define _PM_TIMER_DEFAULT &_PM_esp32timer + +extern IRAM_ATTR void _PM_row_handler(Protomatter_core *core); + +// Timer interrupt handler. This, _PM_row_handler() and any functions +// called by _PM_row_handler() should all have the IRAM_ATTR attribute +// (RAM-resident functions). This isn't really the ISR itself, but a +// callback invoked by the real ISR (in arduino-esp32's esp32-hal-timer.c) +// which takes care of interrupt status bits & such. +IRAM_ATTR static void _PM_esp32timerCallback(void) { + _PM_row_handler(_PM_protoPtr); // In core.c +} + +// Initialize, but do not start, timer. +void _PM_timerInit(void *tptr) { + hw_timer_t **timer = (hw_timer_t **)tptr; // pointer-to-pointer + if (timer == _PM_TIMER_DEFAULT) { + *timer = timerBegin(_PM_timerNum, 2, true); // 1:2 prescale, count up + } + timerAttachInterrupt(*timer, &_PM_esp32timerCallback, true); +} + +// Set timer period, initialize count value to zero, enable timer. +IRAM_ATTR inline void _PM_timerStart(void *tptr, uint32_t period) { + hw_timer_t *timer = *(hw_timer_t **)tptr; + timerAlarmWrite(timer, period, true); + timerAlarmEnable(timer); + timerStart(timer); +} + +// Return current count value (timer enabled or not). +// Timer must be previously initialized. +IRAM_ATTR inline uint32_t _PM_timerGetCount(void *tptr) { + hw_timer_t *timer = *(hw_timer_t **)tptr; + return (uint32_t)timerRead(timer); +} + +// Disable timer and return current count value. +// Timer must be previously initialized. +IRAM_ATTR uint32_t _PM_timerStop(void *tptr) { + hw_timer_t *timer = *(hw_timer_t **)tptr; + timerStop(timer); + return _PM_timerGetCount(tptr); +} + +#elif defined(CIRCUITPY) // COMPILING FOR CIRCUITPYTHON -------------------- + +// ESP32 CircuitPython magic goes here. If any of the above Arduino-specific +// defines, structs or functions are useful as-is, don't copy them, just +// move them above the ARDUINO check so fixes/changes carry over, thx. + +// ESP32 requires a custom PEW declaration (issues one set of RGB color bits +// followed by clock pulse). Turns out the bit set/clear registers are not +// actually atomic. If two writes are made in quick succession, the second +// has no effect. One option is NOPs, other is to write a 0 (no effect) to +// the opposing register (set vs clear) to synchronize the next write. +#define PEW \ + *set = (*data++) << shift; /* Set RGB data high */ \ + *clear_full = 0; /* ESP32 MUST sync before 2nd 'set' */ \ + *set = clock; /* Set clock high */ \ + *clear_full = rgbclock; /* Clear RGB data + clock */ \ + ///< Bitbang one set of RGB data bits to matrix + +#include "driver/gpio.h" +#include "hal/timer_ll.h" +#include "peripherals/timer.h" + +#define _PM_STRICT_32BIT_IO (1) + +#define _PM_TIMER_DEFAULT NULL + +#define _PM_pinOutput(pin) gpio_set_direction((pin), GPIO_MODE_OUTPUT) + +#define _PM_pinLow(pin) gpio_set_level((pin), false) + +#define _PM_pinHigh(pin) gpio_set_level((pin), true) + +#define _PM_portBitMask(pin) (1U << ((pin)&31)) + +// Timer interrupt handler. This, _PM_row_handler() and any functions +// called by _PM_row_handler() should all have the IRAM_ATTR attribute +// (RAM-resident functions). This isn't really the ISR itself, but a +// callback invoked by the real ISR (in arduino-esp32's esp32-hal-timer.c) +// which takes care of interrupt status bits & such. +IRAM_ATTR bool _PM_esp32timerCallback(void *unused) { + if (_PM_protoPtr) { + _PM_row_handler(_PM_protoPtr); // In core.c + } + return false; +}; + +// Initialize, but do not start, timer. +void _PM_timerInit(void *tptr) { + const timer_config_t config = { + .alarm_en = false, + .counter_en = false, + .intr_type = TIMER_INTR_LEVEL, + .counter_dir = TIMER_COUNT_UP, + .auto_reload = true, + .divider = 2 // 40MHz + }; + + timer_index_t *timer = (timer_index_t *)tptr; + timer_init(timer->group, timer->idx, &config); + timer_isr_callback_add(timer->group, timer->idx, _PM_esp32timerCallback, NULL, + 0); + timer_enable_intr(timer->group, timer->idx); +} + +// Set timer period, initialize count value to zero, enable timer. +IRAM_ATTR void _PM_timerStart(void *tptr, uint32_t period) { + timer_index_t *timer = (timer_index_t *)tptr; + timer_ll_set_counter_enable(timer->hw, timer->idx, false); + timer_ll_set_counter_value(timer->hw, timer->idx, 0); + timer_ll_set_alarm_value(timer->hw, timer->idx, period); + timer_ll_set_alarm_enable(timer->hw, timer->idx, true); + timer_ll_set_counter_enable(timer->hw, timer->idx, true); +} + +IRAM_ATTR uint32_t _PM_timerGetCount(void *tptr) { + timer_index_t *timer = (timer_index_t *)tptr; +#ifdef CONFIG_IDF_TARGET_ESP32S3 + timer->hw->hw_timer[timer->idx].update.tn_update = 1; + return timer->hw->hw_timer[timer->idx].lo.tn_lo; +#else + timer->hw->hw_timer[timer->idx].update.tx_update = 1; + return timer->hw->hw_timer[timer->idx].lo.tx_lo; +#endif +} + +// Disable timer and return current count value. +// Timer must be previously initialized. +IRAM_ATTR uint32_t _PM_timerStop(void *tptr) { + timer_index_t *timer = (timer_index_t *)tptr; + timer_ll_set_counter_enable(timer->hw, timer->idx, false); + return _PM_timerGetCount(tptr); +} + +#endif // END CIRCUITPYTHON ------------------------------------------------ + +#endif // END ESP32 diff --git a/circuitpython/lib/protomatter/src/arch/nrf52.h b/circuitpython/lib/protomatter/src/arch/nrf52.h new file mode 100644 index 0000000..c46cf1e --- /dev/null +++ b/circuitpython/lib/protomatter/src/arch/nrf52.h @@ -0,0 +1,216 @@ +/*! + * @file nrf52.h + * + * Part of Adafruit's Protomatter library for HUB75-style RGB LED matrices. + * This file contains NRF52-SPECIFIC CODE. + * + * Adafruit invests time and resources providing this open source code, + * please support Adafruit and open-source hardware by purchasing + * products from Adafruit! + * + * Written by Phil "Paint Your Dragon" Burgess and Jeff Epler for + * Adafruit Industries, with contributions from the open source community. + * + * BSD license, all text here must be included in any redistribution. + * + */ + +#pragma once + +#if defined(NRF52_SERIES) + +#if defined(ARDUINO) // COMPILING FOR ARDUINO ------------------------------ + +// digitalPinToPort, g_ADigitalPinMap[] are Arduino specific: + +void *_PM_portOutRegister(uint32_t pin) { + NRF_GPIO_Type *port = digitalPinToPort(pin); + return &port->OUT; +} + +void *_PM_portSetRegister(uint32_t pin) { + NRF_GPIO_Type *port = digitalPinToPort(pin); + return &port->OUTSET; +} + +void *_PM_portClearRegister(uint32_t pin) { + NRF_GPIO_Type *port = digitalPinToPort(pin); + return &port->OUTCLR; +} + +// Leave _PM_portToggleRegister(pin) undefined on nRF! + +#if __BYTE_ORDER__ == __ORDER_LITTLE_ENDIAN__ +#define _PM_byteOffset(pin) ((g_ADigitalPinMap[pin] & 0x1F) / 8) +#define _PM_wordOffset(pin) ((g_ADigitalPinMap[pin] & 0x1F) / 16) +#else +#define _PM_byteOffset(pin) (3 - ((g_ADigitalPinMap[pin] & 0x1F) / 8)) +#define _PM_wordOffset(pin) (1 - ((g_ADigitalPinMap[pin] & 0x1F) / 16)) +#endif + +// Because it's tied to a specific timer right now, there can be only +// one instance of the Protomatter_core struct. The Arduino library +// sets up this pointer when calling begin(). +void *_PM_protoPtr = NULL; + +// Arduino implementation is tied to a specific timer/counter, +// Partly because IRQs must be declared at compile-time. +#define _PM_IRQ_HANDLER TIMER4_IRQHandler +#define _PM_timerFreq 16000000 +#define _PM_TIMER_DEFAULT NRF_TIMER4 + +#ifdef __cplusplus +extern "C" { +#endif + +// Timer interrupt service routine +void _PM_IRQ_HANDLER(void) { + if (_PM_TIMER_DEFAULT->EVENTS_COMPARE[0]) { + _PM_TIMER_DEFAULT->EVENTS_COMPARE[0] = 0; + } + _PM_row_handler(_PM_protoPtr); // In core.c +} + +#ifdef __cplusplus +} +#endif + +#elif defined(CIRCUITPY) // COMPILING FOR CIRCUITPYTHON -------------------- + +#include "nrf_gpio.h" + +volatile uint32_t *_PM_portOutRegister(uint32_t pin) { + NRF_GPIO_Type *port = nrf_gpio_pin_port_decode(&pin); + return &port->OUT; +} + +volatile uint32_t *_PM_portSetRegister(uint32_t pin) { + NRF_GPIO_Type *port = nrf_gpio_pin_port_decode(&pin); + return &port->OUTSET; +} + +volatile uint32_t *_PM_portClearRegister(uint32_t pin) { + NRF_GPIO_Type *port = nrf_gpio_pin_port_decode(&pin); + return &port->OUTCLR; +} +#define _PM_pinOutput(pin) \ + nrf_gpio_cfg(pin, NRF_GPIO_PIN_DIR_OUTPUT, NRF_GPIO_PIN_INPUT_DISCONNECT, \ + NRF_GPIO_PIN_NOPULL, NRF_GPIO_PIN_H0H1, NRF_GPIO_PIN_NOSENSE) +#define _PM_pinInput(pin) nrf_gpio_cfg_input(pin) +#define _PM_pinHigh(pin) nrf_gpio_pin_set(pin) +#define _PM_pinLow(pin) nrf_gpio_pin_clear(pin) +#define _PM_portBitMask(pin) (1u << ((pin)&31)) + +#if __BYTE_ORDER__ == __ORDER_LITTLE_ENDIAN__ +#define _PM_byteOffset(pin) ((pin & 31) / 8) +#define _PM_wordOffset(pin) ((pin & 31) / 16) +#else +#define _PM_byteOffset(pin) (3 - ((pin & 31) / 8)) +#define _PM_wordOffset(pin) (1 - ((pin & 31) / 16)) +#endif + +// CircuitPython implementation is tied to a specific freq (but the counter +// is dynamically allocated): +#define _PM_timerFreq 16000000 + +// Because it's tied to a specific timer right now, there can be only +// one instance of the Protomatter_core struct. The Arduino library +// sets up this pointer when calling begin(). +void *_PM_protoPtr = NULL; + +// Timer interrupt service routine +void _PM_IRQ_HANDLER(void) { + NRF_TIMER_Type *timer = (((Protomatter_core *)_PM_protoPtr)->timer); + if (timer->EVENTS_COMPARE[0]) { + timer->EVENTS_COMPARE[0] = 0; + } + + _PM_row_handler(_PM_protoPtr); // In core.c +} + +#else // END CIRCUITPYTHON ------------------------------------------------- + +// Byte offset macros, timer and ISR work for other environments go here. + +#endif + +// CODE COMMON TO ALL ENVIRONMENTS ----------------------------------------- + +void _PM_timerInit(void *tptr) { + static const struct { + NRF_TIMER_Type *tc; // -> Timer peripheral base address + IRQn_Type IRQn; // Interrupt number + } timer[] = { +#if defined(NRF_TIMER0) + {NRF_TIMER0, TIMER0_IRQn}, +#endif +#if defined(NRF_TIMER1) + {NRF_TIMER1, TIMER1_IRQn}, +#endif +#if defined(NRF_TIMER2) + {NRF_TIMER2, TIMER2_IRQn}, +#endif +#if defined(NRF_TIMER3) + {NRF_TIMER3, TIMER3_IRQn}, +#endif +#if defined(NRF_TIMER4) + {NRF_TIMER4, TIMER4_IRQn}, +#endif + }; +#define NUM_TIMERS (sizeof timer / sizeof timer[0]) + + // Determine IRQn from timer address + uint8_t timerNum = 0; + while ((timerNum < NUM_TIMERS) && (timer[timerNum].tc != tptr)) { + timerNum++; + } + if (timerNum >= NUM_TIMERS) + return; + + NRF_TIMER_Type *tc = timer[timerNum].tc; + + tc->TASKS_STOP = 1; // Stop timer + tc->MODE = TIMER_MODE_MODE_Timer; // Timer (not counter) mode + tc->TASKS_CLEAR = 1; + tc->BITMODE = TIMER_BITMODE_BITMODE_16Bit + << TIMER_BITMODE_BITMODE_Pos; // 16-bit timer res + tc->PRESCALER = 0; // 1:1 prescale (16 MHz) + tc->INTENSET = TIMER_INTENSET_COMPARE0_Enabled + << TIMER_INTENSET_COMPARE0_Pos; // Event 0 interrupt + // NVIC_DisableIRQ(timer[timerNum].IRQn); + // NVIC_ClearPendingIRQ(timer[timerNum].IRQn); + // NVIC_SetPriority(timer[timerNum].IRQn, 0); // Top priority + NVIC_EnableIRQ(timer[timerNum].IRQn); +} + +inline void _PM_timerStart(void *tptr, uint32_t period) { + volatile NRF_TIMER_Type *tc = (volatile NRF_TIMER_Type *)tptr; + tc->TASKS_STOP = 1; // Stop timer + tc->TASKS_CLEAR = 1; // Reset to 0 + tc->CC[0] = period; + tc->TASKS_START = 1; // Start timer +} + +inline uint32_t _PM_timerGetCount(void *tptr) { + volatile NRF_TIMER_Type *tc = (volatile NRF_TIMER_Type *)tptr; + tc->TASKS_CAPTURE[0] = 1; // Capture timer to CC[n] register + return tc->CC[0]; +} + +uint32_t _PM_timerStop(void *tptr) { + volatile NRF_TIMER_Type *tc = (volatile NRF_TIMER_Type *)tptr; + tc->TASKS_STOP = 1; // Stop timer + __attribute__((unused)) uint32_t count = _PM_timerGetCount(tptr); + // NOTE TO FUTURE SELF: I don't know why the GetCount code isn't + // working. It does the expected thing in a small test program but + // not here. I need to get on with testing on an actual matrix, so + // this is just a nonsense fudge value for now: + return 100; + // return count; +} + +#define _PM_clockHoldHigh asm("nop; nop"); + +#define _PM_minMinPeriod 100 + +#endif // END NRF52_SERIES diff --git a/circuitpython/lib/protomatter/src/arch/rp2040.h b/circuitpython/lib/protomatter/src/arch/rp2040.h new file mode 100644 index 0000000..b098251 --- /dev/null +++ b/circuitpython/lib/protomatter/src/arch/rp2040.h @@ -0,0 +1,245 @@ +/*! + * @file rp2040.h + * + * Part of Adafruit's Protomatter library for HUB75-style RGB LED matrices. + * This file contains RP2040 (Raspberry Pi Pico, etc.) SPECIFIC CODE. + * + * Adafruit invests time and resources providing this open source code, + * please support Adafruit and open-source hardware by purchasing + * products from Adafruit! + * + * Written by Phil "Paint Your Dragon" Burgess and Jeff Epler for + * Adafruit Industries, with contributions from the open source community. + * + * BSD license, all text here must be included in any redistribution. + * + * RP2040 NOTES: This initial implementation does NOT use PIO. That's normal + * for Protomatter, which was written for simple GPIO + timer interrupt for + * broadest portability. While not entirely optimal, it's not pessimal + * either...no worse than any other platform where we're not taking + * advantage of device-specific DMA or peripherals. Would require changes to + * the 'blast' functions or possibly the whole _PM_row_handler() (both + * currently in core.c). CPU load is just a few percent for a 64x32 + * matrix @ 6-bit depth, so I'm not losing sleep over this. + * + */ + +#pragma once + +// TO DO: PUT A *PROPER* RP2040 CHECK HERE +#if defined(PICO_BOARD) || defined(__RP2040__) + +#include "../../hardware_pwm/include/hardware/pwm.h" +#include "hardware/irq.h" +#include "hardware/timer.h" +#include "pico/stdlib.h" // For sio_hw, etc. + +// RP2040 only allows full 32-bit aligned writes to GPIO. +#define _PM_STRICT_32BIT_IO ///< Change core.c behavior for long accesses only + +// TEMPORARY: FORCING ARDUINO COMPILATION FOR INITIAL C TESTING +#if !defined(CIRCUITPY) +#define ARDUINO +#endif + +// Enable this to use PWM for bitplane timing, else a timer alarm is used. +// PWM has finer resolution, but alarm is adequate -- this is more about +// which peripheral we'd rather use, as both are finite resources. +#ifndef _PM_CLOCK_PWM +#define _PM_CLOCK_PWM (1) +#endif + +#if _PM_CLOCK_PWM // Use PWM for timing +static void _PM_PWM_ISR(void); +#else // Use timer alarm for timing +static void _PM_timerISR(void); +#endif + +#if defined(ARDUINO) // COMPILING FOR ARDUINO ------------------------------ + +// 'pin' here is GPXX # -- that might change in Arduino implementation +#if __BYTE_ORDER__ == __ORDER_LITTLE_ENDIAN__ +#define _PM_byteOffset(pin) ((pin & 31) / 8) +#define _PM_wordOffset(pin) ((pin & 31) / 16) +#else +#define _PM_byteOffset(pin) (3 - ((pin & 31) / 8)) +#define _PM_wordOffset(pin) (1 - ((pin & 31) / 16)) +#endif + +#if _PM_CLOCK_PWM + +// Arduino implementation is tied to a specific PWM slice & frequency +#define _PM_PWM_SLICE 0 +#define _PM_PWM_DIV 3 // ~41.6 MHz, similar to SAMD +#define _PM_timerFreq (125000000 / _PM_PWM_DIV) +#define _PM_TIMER_DEFAULT NULL + +#else // Use alarm for timing + +// Arduino implementation is tied to a specific timer alarm & frequency +#define _PM_ALARM_NUM 1 +#define _PM_IRQ_HANDLER TIMER_IRQ_1 +#define _PM_timerFreq 1000000 +#define _PM_TIMER_DEFAULT NULL + +// Initialize, but do not start, timer. +void _PM_timerInit(void *tptr) { +#if _PM_CLOCK_PWM + // Enable PWM wrap interrupt + pwm_clear_irq(_PM_PWM_SLICE); + pwm_set_irq_enabled(_PM_PWM_SLICE, true); + irq_set_exclusive_handler(PWM_IRQ_WRAP, _PM_PWM_ISR); + irq_set_enabled(PWM_IRQ_WRAP, true); + + // Config but do not start PWM + pwm_config config = pwm_get_default_config(); + pwm_config_set_clkdiv_int(&config, _PM_PWM_DIV); + pwm_init(_PM_PWM_SLICE, &config, true); +#else + timer_hw->alarm[_PM_ALARM_NUM] = timer_hw->timerawl; // Clear any timer + hw_set_bits(&timer_hw->inte, 1u << _PM_ALARM_NUM); + irq_set_exclusive_handler(_PM_IRQ_HANDLER, _PM_timerISR); // Set IRQ handler +#endif +} + +#endif + +#elif defined(CIRCUITPY) // COMPILING FOR CIRCUITPYTHON -------------------- + +// 'pin' here is GPXX # +#if __BYTE_ORDER__ == __ORDER_LITTLE_ENDIAN__ +#define _PM_byteOffset(pin) ((pin & 31) / 8) +#define _PM_wordOffset(pin) ((pin & 31) / 16) +#else +#define _PM_byteOffset(pin) (3 - ((pin & 31) / 8)) +#define _PM_wordOffset(pin) (1 - ((pin & 31) / 16)) +#endif + +#if _PM_CLOCK_PWM + +int _PM_pwm_slice; +#define _PM_PWM_SLICE (_PM_pwm_slice & 0xff) +#define _PM_PWM_DIV 3 // ~41.6 MHz, similar to SAMD +#define _PM_timerFreq (125000000 / _PM_PWM_DIV) +#define _PM_TIMER_DEFAULT NULL + +#else // Use alarm for timing + +// Currently tied to a specific timer alarm & frequency +#define _PM_ALARM_NUM 1 +#define _PM_IRQ_HANDLER TIMER_IRQ_1 +#define _PM_timerFreq 1000000 +#define _PM_TIMER_DEFAULT NULL + +#endif + +// Initialize, but do not start, timer. +void _PM_timerInit(void *tptr) { +#if _PM_CLOCK_PWM + _PM_pwm_slice = (int)tptr & 0xff; + // Enable PWM wrap interrupt + pwm_clear_irq(_PM_PWM_SLICE); + pwm_set_irq_enabled(_PM_PWM_SLICE, true); + irq_set_exclusive_handler(PWM_IRQ_WRAP, _PM_PWM_ISR); + irq_set_enabled(PWM_IRQ_WRAP, true); + + // Config but do not start PWM + pwm_config config = pwm_get_default_config(); + pwm_config_set_clkdiv_int(&config, _PM_PWM_DIV); + pwm_init(_PM_PWM_SLICE, &config, true); +#else + timer_hw->alarm[_PM_ALARM_NUM] = timer_hw->timerawl; // Clear any timer + hw_set_bits(&timer_hw->inte, 1u << _PM_ALARM_NUM); + irq_set_exclusive_handler(_PM_IRQ_HANDLER, _PM_timerISR); // Set IRQ handler +#endif +} + +#endif + +#if !_PM_CLOCK_PWM +// Unlike timers on other devices, on RP2040 you don't reset a counter to +// zero at the start of a cycle. To emulate that behavior (for determining +// elapsed times), the timer start time must be saved somewhere... +static volatile uint32_t _PM_timerSave; + +#endif + +// Because it's tied to a specific timer right now, there can be only +// one instance of the Protomatter_core struct. The Arduino library +// sets up this pointer when calling begin(). +void *_PM_protoPtr = NULL; + +#define _PM_portOutRegister(pin) ((void *)&sio_hw->gpio_out) +#define _PM_portSetRegister(pin) ((volatile uint32_t *)&sio_hw->gpio_set) +#define _PM_portClearRegister(pin) ((volatile uint32_t *)&sio_hw->gpio_clr) +#define _PM_portToggleRegister(pin) ((volatile uint32_t *)&sio_hw->gpio_togl) +// 'pin' here is GPXX # -- that might change in Arduino implementation +#define _PM_portBitMask(pin) (1UL << pin) +// Same for these -- using GPXX #, but Arduino might assign different order +#define _PM_pinOutput(pin) \ + { \ + gpio_init(pin); \ + gpio_set_dir(pin, GPIO_OUT); \ + } +#define _PM_pinLow(pin) gpio_clr_mask(1UL << pin) +#define _PM_pinHigh(pin) gpio_set_mask(1UL << pin) + +#ifndef _PM_delayMicroseconds +#define _PM_delayMicroseconds(n) sleep_us(n) +#endif + +#if _PM_CLOCK_PWM // Use PWM for timing +static void _PM_PWM_ISR(void) { + pwm_clear_irq(_PM_PWM_SLICE); // Reset PWM wrap interrupt + _PM_row_handler(_PM_protoPtr); // In core.c +} +#else // Use timer alarm for timing +static void _PM_timerISR(void) { + hw_clear_bits(&timer_hw->intr, 1u << _PM_ALARM_NUM); // Clear alarm flag + _PM_row_handler(_PM_protoPtr); // In core.c +} +#endif + +// Set timer period and enable timer. +inline void _PM_timerStart(void *tptr, uint32_t period) { +#if _PM_CLOCK_PWM + pwm_set_counter(_PM_PWM_SLICE, 0); + pwm_set_wrap(_PM_PWM_SLICE, period); + pwm_set_enabled(_PM_PWM_SLICE, true); +#else + irq_set_enabled(_PM_IRQ_HANDLER, true); // Enable alarm IRQ + _PM_timerSave = timer_hw->timerawl; // Time at start + timer_hw->alarm[_PM_ALARM_NUM] = _PM_timerSave + period; // Time at end +#endif +} + +// Return current count value (timer enabled or not). +// Timer must be previously initialized. +inline uint32_t _PM_timerGetCount(void *tptr) { +#if _PM_CLOCK_PWM + return pwm_get_counter(_PM_PWM_SLICE); +#else + return timer_hw->timerawl - _PM_timerSave; +#endif +} + +// Disable timer and return current count value. +// Timer must be previously initialized. +uint32_t _PM_timerStop(void *tptr) { +#if _PM_CLOCK_PWM + pwm_set_enabled(_PM_PWM_SLICE, false); +#else + irq_set_enabled(_PM_IRQ_HANDLER, false); // Disable alarm IRQ +#endif + return _PM_timerGetCount(tptr); +} + +#define _PM_chunkSize 8 +#define _PM_clockHoldLow asm("nop; nop;"); +#if _PM_CLOCK_PWM +#define _PM_minMinPeriod 100 +#else +#define _PM_minMinPeriod 8 +#endif + +#endif // END PICO_BOARD diff --git a/circuitpython/lib/protomatter/src/arch/samd-common.h b/circuitpython/lib/protomatter/src/arch/samd-common.h new file mode 100644 index 0000000..d2e039d --- /dev/null +++ b/circuitpython/lib/protomatter/src/arch/samd-common.h @@ -0,0 +1,98 @@ +/*! + * @file samd-common.h + * + * Part of Adafruit's Protomatter library for HUB75-style RGB LED matrices. + * This file contains SAMD-SPECIFIC CODE (SAMD51 & SAMD21). + * + * Adafruit invests time and resources providing this open source code, + * please support Adafruit and open-source hardware by purchasing + * products from Adafruit! + * + * Written by Phil "Paint Your Dragon" Burgess and Jeff Epler for + * Adafruit Industries, with contributions from the open source community. + * + * BSD license, all text here must be included in any redistribution. + * + */ + +#pragma once + +#if defined(__SAMD51__) || defined(SAM_D5X_E5X) || defined(_SAMD21_) || \ + defined(SAMD21) + +#if defined(ARDUINO) // COMPILING FOR ARDUINO ------------------------------ + +// g_APinDescription[] table and pin indices are Arduino specific: +#if __BYTE_ORDER__ == __ORDER_LITTLE_ENDIAN__ +#define _PM_byteOffset(pin) (g_APinDescription[pin].ulPin / 8) +#define _PM_wordOffset(pin) (g_APinDescription[pin].ulPin / 16) +#else +#define _PM_byteOffset(pin) (3 - (g_APinDescription[pin].ulPin / 8)) +#define _PM_wordOffset(pin) (1 - (g_APinDescription[pin].ulPin / 16)) +#endif + +// Arduino implementation is tied to a specific timer/counter & freq: +#if defined(TC4) +#define _PM_TIMER_DEFAULT TC4 +#define _PM_IRQ_HANDLER TC4_Handler +#else // No TC4 on some M4's +#define _PM_TIMER_DEFAULT TC3 +#define _PM_IRQ_HANDLER TC3_Handler +#endif +#define _PM_timerFreq 48000000 +// Partly because IRQs must be declared at compile-time, and partly +// because we know Arduino's already set up one of the GCLK sources +// for 48 MHz. + +// Because it's tied to a specific timer right now, there can be only +// one instance of the Protomatter_core struct. The Arduino library +// sets up this pointer when calling begin(). +void *_PM_protoPtr = NULL; + +// Timer interrupt service routine +void _PM_IRQ_HANDLER(void) { + // Clear overflow flag: + _PM_TIMER_DEFAULT->COUNT16.INTFLAG.reg = TC_INTFLAG_OVF; + _PM_row_handler(_PM_protoPtr); // In core.c +} + +#elif defined(CIRCUITPY) // COMPILING FOR CIRCUITPYTHON -------------------- + +#include "hal_gpio.h" + +#define _PM_pinOutput(pin) gpio_set_pin_direction(pin, GPIO_DIRECTION_OUT) +#define _PM_pinInput(pin) gpio_set_pin_direction(pin, GPIO_DIRECTION_IN) +#define _PM_pinHigh(pin) gpio_set_pin_level(pin, 1) +#define _PM_pinLow(pin) gpio_set_pin_level(pin, 0) +#define _PM_portBitMask(pin) (1u << ((pin)&31)) + +#if __BYTE_ORDER__ == __ORDER_LITTLE_ENDIAN__ +#define _PM_byteOffset(pin) ((pin & 31) / 8) +#define _PM_wordOffset(pin) ((pin & 31) / 16) +#else +#define _PM_byteOffset(pin) (3 - ((pin & 31) / 8)) +#define _PM_wordOffset(pin) (1 - ((pin & 31) / 16)) +#endif + +// CircuitPython implementation is tied to a specific freq (but the counter +// is dynamically allocated): +#define _PM_timerFreq 48000000 + +// As currently implemented, there can be only one instance of the +// Protomatter_core struct. This pointer is set up when starting the matrix. +void *_PM_protoPtr = NULL; + +// Timer interrupt service routine +void _PM_IRQ_HANDLER(void) { + ((Tc *)(((Protomatter_core *)_PM_protoPtr)->timer))->COUNT16.INTFLAG.reg = + TC_INTFLAG_OVF; + _PM_row_handler(_PM_protoPtr); // In core.c +} + +#else // END CIRCUITPYTHON ------------------------------------------------- + +// Byte offset macros, timer and ISR work for other environments go here. + +#endif + +#endif // END SAMD5x/SAME5x/SAMD21 diff --git a/circuitpython/lib/protomatter/src/arch/samd21.h b/circuitpython/lib/protomatter/src/arch/samd21.h new file mode 100644 index 0000000..25deef3 --- /dev/null +++ b/circuitpython/lib/protomatter/src/arch/samd21.h @@ -0,0 +1,150 @@ +/*! + * @file samd21.h + * + * Part of Adafruit's Protomatter library for HUB75-style RGB LED matrices. + * This file contains SAMD21-SPECIFIC CODE. + * + * Adafruit invests time and resources providing this open source code, + * please support Adafruit and open-source hardware by purchasing + * products from Adafruit! + * + * Written by Phil "Paint Your Dragon" Burgess and Jeff Epler for + * Adafruit Industries, with contributions from the open source community. + * + * BSD license, all text here must be included in any redistribution. + * + */ + +#pragma once + +#if defined(_SAMD21_) || defined(SAMD21) // Arduino, Circuitpy SAMD21 defs + +#if defined(ARDUINO) // COMPILING FOR ARDUINO ------------------------------ + +// g_APinDescription[] table and pin indices are Arduino specific: +#define _PM_portOutRegister(pin) \ + &PORT_IOBUS->Group[g_APinDescription[pin].ulPort].OUT.reg + +#define _PM_portSetRegister(pin) \ + &PORT_IOBUS->Group[g_APinDescription[pin].ulPort].OUTSET.reg + +#define _PM_portClearRegister(pin) \ + &PORT_IOBUS->Group[g_APinDescription[pin].ulPort].OUTCLR.reg + +#define _PM_portToggleRegister(pin) \ + &PORT_IOBUS->Group[g_APinDescription[pin].ulPort].OUTTGL.reg + +#else // END ARDUINO ------------------------------------------------------- + +// Non-Arduino port register lookups go here, if not already declared +// in samd-common.h. + +#endif + +// CODE COMMON TO ALL ENVIRONMENTS ----------------------------------------- + +// Initialize, but do not start, timer +void _PM_timerInit(void *tptr) { + static const struct { + Tc *tc; // -> Timer/counter peripheral base address + IRQn_Type IRQn; // Interrupt number + uint8_t GCM_ID; // GCLK selection ID + } timer[] = { +#if defined(TC0) + {TC0, TC0_IRQn, GCM_TCC0_TCC1}, +#endif +#if defined(TC1) + {TC1, TC1_IRQn, GCM_TCC0_TCC1}, +#endif +#if defined(TC2) + {TC2, TC2_IRQn, GCM_TCC2_TC3}, +#endif +#if defined(TC3) + {TC3, TC3_IRQn, GCM_TCC2_TC3}, +#endif +#if defined(TC4) + {TC4, TC4_IRQn, GCM_TC4_TC5}, +#endif + }; +#define NUM_TIMERS (sizeof timer / sizeof timer[0]) + + Tc *tc = (Tc *)tptr; // Cast peripheral address passed in + + uint8_t timerNum = 0; + while ((timerNum < NUM_TIMERS) && (timer[timerNum].tc != tc)) { + timerNum++; + } + if (timerNum >= NUM_TIMERS) + return; + + // Enable GCLK for timer/counter + GCLK->CLKCTRL.reg = (uint16_t)(GCLK_CLKCTRL_CLKEN | GCLK_CLKCTRL_GEN_GCLK0 | + GCLK_CLKCTRL_ID(timer[timerNum].GCM_ID)); + while (GCLK->STATUS.bit.SYNCBUSY == 1) + ; + + // Counter must first be disabled to configure it + tc->COUNT16.CTRLA.bit.ENABLE = 0; + while (tc->COUNT16.STATUS.bit.SYNCBUSY) + ; + + tc->COUNT16.CTRLA.reg = // Configure timer counter + TC_CTRLA_PRESCALER_DIV1 | // 1:1 Prescale + TC_CTRLA_WAVEGEN_MFRQ | // Match frequency generation mode (MFRQ) + TC_CTRLA_MODE_COUNT16; // 16-bit counter mode + while (tc->COUNT16.STATUS.bit.SYNCBUSY) + ; + + tc->COUNT16.CTRLBCLR.reg = TCC_CTRLBCLR_DIR; // Count up + while (tc->COUNT16.STATUS.bit.SYNCBUSY) + ; + + // Overflow interrupt + tc->COUNT16.INTENSET.reg = TC_INTENSET_OVF; + + NVIC_DisableIRQ(timer[timerNum].IRQn); + NVIC_ClearPendingIRQ(timer[timerNum].IRQn); + NVIC_SetPriority(timer[timerNum].IRQn, 0); // Top priority + NVIC_EnableIRQ(timer[timerNum].IRQn); + + // Timer is configured but NOT enabled by default +} + +// Set timer period, initialize count value to zero, enable timer. +// Timer must be initialized to 16-bit mode using the init function +// above, but must be inactive before calling this. +inline void _PM_timerStart(void *tptr, uint32_t period) { + Tc *tc = (Tc *)tptr; // Cast peripheral address passed in + tc->COUNT16.COUNT.reg = 0; + while (tc->COUNT16.STATUS.bit.SYNCBUSY) + ; + tc->COUNT16.CC[0].reg = period; + while (tc->COUNT16.STATUS.bit.SYNCBUSY) + ; + tc->COUNT16.CTRLA.bit.ENABLE = 1; + while (tc->COUNT16.STATUS.bit.SYNCBUSY) + ; +} + +// Return current count value (timer enabled or not). +// Timer must be previously initialized. +inline uint32_t _PM_timerGetCount(void *tptr) { + Tc *tc = (Tc *)tptr; // Cast peripheral address passed in + tc->COUNT16.READREQ.reg = TC_READREQ_RCONT | TC_READREQ_ADDR(0x10); + while (tc->COUNT16.STATUS.bit.SYNCBUSY) + ; + return tc->COUNT16.COUNT.reg; +} + +// Disable timer and return current count value. +// Timer must be previously initialized. +inline uint32_t _PM_timerStop(void *tptr) { + Tc *tc = (Tc *)tptr; // Cast peripheral address passed in + uint32_t count = _PM_timerGetCount(tptr); + tc->COUNT16.CTRLA.bit.ENABLE = 0; + while (tc->COUNT16.STATUS.bit.SYNCBUSY) + ; + return count; +} + +#endif // END _SAMD21_ || SAMD21 diff --git a/circuitpython/lib/protomatter/src/arch/samd51.h b/circuitpython/lib/protomatter/src/arch/samd51.h new file mode 100644 index 0000000..278cc2d --- /dev/null +++ b/circuitpython/lib/protomatter/src/arch/samd51.h @@ -0,0 +1,216 @@ +/*! + * @file samd51.h + * + * Part of Adafruit's Protomatter library for HUB75-style RGB LED matrices. + * This file contains SAMD51-SPECIFIC CODE. + * + * Adafruit invests time and resources providing this open source code, + * please support Adafruit and open-source hardware by purchasing + * products from Adafruit! + * + * Written by Phil "Paint Your Dragon" Burgess and Jeff Epler for + * Adafruit Industries, with contributions from the open source community. + * + * BSD license, all text here must be included in any redistribution. + * + */ + +#pragma once + +#if defined(__SAMD51__) || \ + defined(SAM_D5X_E5X) // Arduino, Circuitpy SAMD5x / E5x defs + +#if defined(ARDUINO) // COMPILING FOR ARDUINO ------------------------------ + +// g_APinDescription[] table and pin indices are Arduino specific: +#define _PM_portOutRegister(pin) \ + &PORT->Group[g_APinDescription[pin].ulPort].OUT.reg + +#define _PM_portSetRegister(pin) \ + &PORT->Group[g_APinDescription[pin].ulPort].OUTSET.reg + +#define _PM_portClearRegister(pin) \ + &PORT->Group[g_APinDescription[pin].ulPort].OUTCLR.reg + +#define _PM_portToggleRegister(pin) \ + &PORT->Group[g_APinDescription[pin].ulPort].OUTTGL.reg + +#elif defined(CIRCUITPY) // COMPILING FOR CIRCUITPYTHON -------------------- + +#define _PM_portOutRegister(pin) (&PORT->Group[(pin / 32)].OUT.reg) + +#define _PM_portSetRegister(pin) (&PORT->Group[(pin / 32)].OUTSET.reg) + +#define _PM_portClearRegister(pin) (&PORT->Group[(pin / 32)].OUTCLR.reg) + +#define _PM_portToggleRegister(pin) (&PORT->Group[(pin / 32)].OUTTGL.reg) + +#define F_CPU (120000000) + +#else + +// Other port register lookups go here + +#endif + +// CODE COMMON TO ALL ENVIRONMENTS ----------------------------------------- + +// Initialize, but do not start, timer +void _PM_timerInit(void *tptr) { + static const struct { + Tc *tc; // -> Timer/counter peripheral base address + IRQn_Type IRQn; // Interrupt number + uint8_t GCLK_ID; // Peripheral channel # for clock source + } timer[] = { +#if defined(TC0) + {TC0, TC0_IRQn, TC0_GCLK_ID}, +#endif +#if defined(TC1) + {TC1, TC1_IRQn, TC1_GCLK_ID}, +#endif +#if defined(TC2) + {TC2, TC2_IRQn, TC2_GCLK_ID}, +#endif +#if defined(TC3) + {TC3, TC3_IRQn, TC3_GCLK_ID}, +#endif +#if defined(TC4) + {TC4, TC4_IRQn, TC4_GCLK_ID}, +#endif +#if defined(TC5) + {TC5, TC5_IRQn, TC5_GCLK_ID}, +#endif +#if defined(TC6) + {TC6, TC6_IRQn, TC6_GCLK_ID}, +#endif +#if defined(TC7) + {TC7, TC7_IRQn, TC7_GCLK_ID}, +#endif +#if defined(TC8) + {TC8, TC8_IRQn, TC8_GCLK_ID}, +#endif +#if defined(TC9) + {TC9, TC9_IRQn, TC9_GCLK_ID}, +#endif +#if defined(TC10) + {TC10, TC10_IRQn, TC10_GCLK_ID}, +#endif +#if defined(TC11) + {TC11, TC11_IRQn, TC11_GCLK_ID}, +#endif +#if defined(TC12) + {TC12, TC12_IRQn, TC12_GCLK_ID}, +#endif + }; +#define NUM_TIMERS (sizeof timer / sizeof timer[0]) + + Tc *tc = (Tc *)tptr; // Cast peripheral address passed in + + uint8_t timerNum = 0; + while ((timerNum < NUM_TIMERS) && (timer[timerNum].tc != tc)) { + timerNum++; + } + if (timerNum >= NUM_TIMERS) + return; + + // Feed timer/counter off GCLK1 (already set 48 MHz by Arduino core). + // Sure, SAMD51 can run timers up to F_CPU (e.g. 120 MHz or up to + // 200 MHz with overclocking), but on higher bitplanes (which have + // progressively longer timer periods) I could see this possibly + // exceeding a 16-bit timer, and would have to switch prescalers. + // We don't actually need atomic precision on the timer -- point is + // simply that the period doubles with each bitplane, and this can + // work fine at 48 MHz. + GCLK->PCHCTRL[timer[timerNum].GCLK_ID].bit.CHEN = 0; // Disable + while (GCLK->PCHCTRL[timer[timerNum].GCLK_ID].bit.CHEN) + ; // Wait for it + GCLK_PCHCTRL_Type pchctrl; // Read-modify-store + pchctrl.reg = GCLK->PCHCTRL[timer[timerNum].GCLK_ID].reg; + pchctrl.bit.GEN = GCLK_PCHCTRL_GEN_GCLK1_Val; + pchctrl.bit.CHEN = 1; + GCLK->PCHCTRL[timer[timerNum].GCLK_ID].reg = pchctrl.reg; + while (!GCLK->PCHCTRL[timer[timerNum].GCLK_ID].bit.CHEN) + ; + + // Disable timer before configuring it + tc->COUNT16.CTRLA.bit.ENABLE = 0; + while (tc->COUNT16.SYNCBUSY.bit.ENABLE) + ; + + // 16-bit counter mode, 1:1 prescale + tc->COUNT16.CTRLA.bit.MODE = TC_CTRLA_MODE_COUNT16; + tc->COUNT16.CTRLA.bit.PRESCALER = TC_CTRLA_PRESCALER_DIV1_Val; + + tc->COUNT16.WAVE.bit.WAVEGEN = + TC_WAVE_WAVEGEN_MFRQ_Val; // Match frequency generation mode (MFRQ) + + tc->COUNT16.CTRLBCLR.reg = TC_CTRLBCLR_DIR; // Count up + while (tc->COUNT16.SYNCBUSY.bit.CTRLB) + ; + + // Overflow interrupt + tc->COUNT16.INTENSET.reg = TC_INTENSET_OVF; + + NVIC_DisableIRQ(timer[timerNum].IRQn); + NVIC_ClearPendingIRQ(timer[timerNum].IRQn); + NVIC_SetPriority(timer[timerNum].IRQn, 0); // Top priority + NVIC_EnableIRQ(timer[timerNum].IRQn); + + // Timer is configured but NOT enabled by default +} + +// Set timer period, initialize count value to zero, enable timer. +// Timer must be initialized to 16-bit mode using the init function +// above, but must be inactive before calling this. +inline void _PM_timerStart(void *tptr, uint32_t period) { + Tc *tc = (Tc *)tptr; // Cast peripheral address passed in + tc->COUNT16.COUNT.reg = 0; + while (tc->COUNT16.SYNCBUSY.bit.COUNT) + ; + tc->COUNT16.CC[0].reg = period; + while (tc->COUNT16.SYNCBUSY.bit.CC0) + ; + tc->COUNT16.CTRLA.bit.ENABLE = 1; + while (tc->COUNT16.SYNCBUSY.bit.STATUS) + ; +} + +// Return current count value (timer enabled or not). +// Timer must be previously initialized. +inline uint32_t _PM_timerGetCount(void *tptr) { + Tc *tc = (Tc *)tptr; // Cast peripheral address passed in + tc->COUNT16.CTRLBSET.bit.CMD = 0x4; // Sync COUNT + while (tc->COUNT16.CTRLBSET.bit.CMD) + ; // Wait for command + return tc->COUNT16.COUNT.reg; +} + +// Disable timer and return current count value. +// Timer must be previously initialized. +uint32_t _PM_timerStop(void *tptr) { + Tc *tc = (Tc *)tptr; // Cast peripheral address passed in + uint32_t count = _PM_timerGetCount(tptr); + tc->COUNT16.CTRLA.bit.ENABLE = 0; + while (tc->COUNT16.SYNCBUSY.bit.STATUS) + ; + return count; +} + +// See notes in core.c before the "blast" functions +#if F_CPU >= 200000000 +#define _PM_clockHoldHigh asm("nop; nop; nop; nop; nop"); +#define _PM_clockHoldLow asm("nop; nop"); +#elif F_CPU >= 180000000 +#define _PM_clockHoldHigh asm("nop; nop; nop; nop"); +#define _PM_clockHoldLow asm("nop"); +#elif F_CPU >= 150000000 +#define _PM_clockHoldHigh asm("nop; nop; nop"); +#define _PM_clockHoldLow asm("nop"); +#else +#define _PM_clockHoldHigh asm("nop; nop; nop"); +#define _PM_clockHoldLow asm("nop"); +#endif + +#define _PM_minMinPeriod 160 + +#endif // END __SAMD51__ || SAM_D5X_E5X diff --git a/circuitpython/lib/protomatter/src/arch/stm32.h b/circuitpython/lib/protomatter/src/arch/stm32.h new file mode 100644 index 0000000..714fdc4 --- /dev/null +++ b/circuitpython/lib/protomatter/src/arch/stm32.h @@ -0,0 +1,146 @@ +/*! + * @file stm32.h + * + * Part of Adafruit's Protomatter library for HUB75-style RGB LED matrices. + * This file contains STM32-SPECIFIC CODE. + * + * Adafruit invests time and resources providing this open source code, + * please support Adafruit and open-source hardware by purchasing + * products from Adafruit! + * + * Written by Phil "Paint Your Dragon" Burgess and Jeff Epler for + * Adafruit Industries, with contributions from the open source community. + * + * BSD license, all text here must be included in any redistribution. + * + */ + +#pragma once + +#if defined(STM32F4_SERIES) || defined(STM32F405xx) // Arduino, CircuitPy + +#if defined(ARDUINO) // COMPILING FOR ARDUINO ------------------------------ + +// Arduino port register lookups go here, else ones in arch.h are used. + +#elif defined(CIRCUITPY) // COMPILING FOR CIRCUITPYTHON -------------------- + +#include "timers.h" + +#undef _PM_portBitMask +#define _PM_portBitMask(pin) (1u << ((pin)&15)) +#define _PM_byteOffset(pin) ((pin & 15) / 8) +#define _PM_wordOffset(pin) ((pin & 15) / 16) + +#define _PM_pinOutput(pin_) \ + do { \ + int8_t pin = (pin_); \ + GPIO_InitTypeDef GPIO_InitStruct = {0}; \ + GPIO_InitStruct.Pin = 1 << (pin & 15); \ + GPIO_InitStruct.Mode = GPIO_MODE_OUTPUT_PP; \ + GPIO_InitStruct.Pull = GPIO_NOPULL; \ + GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_VERY_HIGH; \ + HAL_GPIO_Init(pin_port(pin / 16), &GPIO_InitStruct); \ + } while (0) +#define _PM_pinInput(pin_) \ + do { \ + int8_t pin = (pin_); \ + GPIO_InitTypeDef GPIO_InitStruct = {0}; \ + GPIO_InitStruct.Pin = 1 << (pin & 15); \ + GPIO_InitStruct.Mode = GPIO_MODE_INPUT; \ + GPIO_InitStruct.Pull = GPIO_NOPULL; \ + GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_VERY_HIGH; \ + HAL_GPIO_Init(pin_port(pin / 16), &GPIO_InitStruct); \ + } while (0) +#define _PM_pinHigh(pin) \ + HAL_GPIO_WritePin(pin_port(pin / 16), 1 << (pin & 15), GPIO_PIN_SET) +#define _PM_pinLow(pin) \ + HAL_GPIO_WritePin(pin_port(pin / 16), 1 << (pin & 15), GPIO_PIN_RESET) + +#define _PM_PORT_TYPE uint16_t + +volatile uint16_t *_PM_portOutRegister(uint32_t pin) { + return (uint16_t *)&pin_port(pin / 16)->ODR; +} + +volatile uint16_t *_PM_portSetRegister(uint32_t pin) { + return (uint16_t *)&pin_port(pin / 16)->BSRR; +} + +// To make things interesting, STM32F4xx places the set and clear +// GPIO bits within a single register. The "clear" bits are upper, so +// offset by 1 in uint16_ts +volatile uint16_t *_PM_portClearRegister(uint32_t pin) { + return 1 + (uint16_t *)&pin_port(pin / 16)->BSRR; +} + +// TODO: was this somehow specific to TIM6? +#define _PM_timerFreq 42000000 + +// Because it's tied to a specific timer right now, there can be only +// one instance of the Protomatter_core struct. The Arduino library +// sets up this pointer when calling begin(). +// TODO: this is no longer true, should it change? +void *_PM_protoPtr = NULL; + +STATIC TIM_HandleTypeDef tim_handle; + +// Timer interrupt service routine +void _PM_IRQ_HANDLER(void) { + // Clear overflow flag: + //_PM_TIMER_DEFAULT->COUNT16.INTFLAG.reg = TC_INTFLAG_OVF; + _PM_row_handler(_PM_protoPtr); // In core.c +} + +// Initialize, but do not start, timer +void _PM_timerInit(void *tptr) { + TIM_TypeDef *tim_instance = (TIM_TypeDef *)tptr; + stm_peripherals_timer_reserve(tim_instance); + // Set IRQs at max priority and start clock + stm_peripherals_timer_preinit(tim_instance, 0, _PM_IRQ_HANDLER); + + tim_handle.Instance = tim_instance; + tim_handle.Init.Period = 1000; // immediately replaced. + tim_handle.Init.Prescaler = 0; + tim_handle.Init.ClockDivision = TIM_CLOCKDIVISION_DIV1; + tim_handle.Init.CounterMode = TIM_COUNTERMODE_UP; + tim_handle.Init.AutoReloadPreload = TIM_AUTORELOAD_PRELOAD_DISABLE; + + HAL_TIM_Base_Init(&tim_handle); + + size_t tim_irq = stm_peripherals_timer_get_irqnum(tim_instance); + HAL_NVIC_DisableIRQ(tim_irq); + NVIC_ClearPendingIRQ(tim_irq); + NVIC_SetPriority(tim_irq, 0); // Top priority +} + +inline void _PM_timerStart(void *tptr, uint32_t period) { + TIM_TypeDef *tim = tptr; + tim->SR = 0; + tim->ARR = period; + tim->CR1 |= TIM_CR1_CEN; + tim->DIER |= TIM_DIER_UIE; + HAL_NVIC_EnableIRQ(stm_peripherals_timer_get_irqnum(tim)); +} + +inline uint32_t _PM_timerGetCount(void *tptr) { + TIM_TypeDef *tim = tptr; + return tim->CNT; +} + +uint32_t _PM_timerStop(void *tptr) { + TIM_TypeDef *tim = tptr; + HAL_NVIC_DisableIRQ(stm_peripherals_timer_get_irqnum(tim)); + tim->CR1 &= ~TIM_CR1_CEN; + tim->DIER &= ~TIM_DIER_UIE; + return tim->CNT; +} +// settings from M4 for >= 150MHz, we use this part at 168MHz +#define _PM_clockHoldHigh asm("nop; nop; nop"); +#define _PM_clockHoldLow asm("nop"); + +#define _PM_minMinPeriod 140 + +#endif // END CIRCUITPYTHON ------------------------------------------------ + +#endif // END STM32F4_SERIES || STM32F405xx diff --git a/circuitpython/lib/protomatter/src/arch/teensy4.h b/circuitpython/lib/protomatter/src/arch/teensy4.h new file mode 100644 index 0000000..75cd1f4 --- /dev/null +++ b/circuitpython/lib/protomatter/src/arch/teensy4.h @@ -0,0 +1,172 @@ +/*! + * @file teensy4.h + * + * Part of Adafruit's Protomatter library for HUB75-style RGB LED matrices. + * This file contains i.MX 1062 (Teensy 4.x) SPECIFIC CODE. + * + * Adafruit invests time and resources providing this open source code, + * please support Adafruit and open-source hardware by purchasing + * products from Adafruit! + * + * Written by Phil "Paint Your Dragon" Burgess and Jeff Epler for + * Adafruit Industries, with contributions from the open source community. + * + * BSD license, all text here must be included in any redistribution. + * + */ + +#pragma once + +#if defined(__IMXRT1062__) + +// i.MX only allows full 32-bit aligned writes to GPIO. +#define _PM_STRICT_32BIT_IO ///< Change core.c behavior for long accesses only + +#if defined(ARDUINO) // COMPILING FOR ARDUINO ------------------------------ + +static const struct { + volatile uint32_t *base; ///< GPIO base address for pin + uint8_t bit; ///< GPIO bit number for pin (0-31) +} _PM_teensyPins[] = { + {&CORE_PIN0_PORTREG, CORE_PIN0_BIT}, + {&CORE_PIN1_PORTREG, CORE_PIN1_BIT}, + {&CORE_PIN2_PORTREG, CORE_PIN2_BIT}, + {&CORE_PIN3_PORTREG, CORE_PIN3_BIT}, + {&CORE_PIN4_PORTREG, CORE_PIN4_BIT}, + {&CORE_PIN5_PORTREG, CORE_PIN5_BIT}, + {&CORE_PIN6_PORTREG, CORE_PIN6_BIT}, + {&CORE_PIN7_PORTREG, CORE_PIN7_BIT}, + {&CORE_PIN8_PORTREG, CORE_PIN8_BIT}, + {&CORE_PIN9_PORTREG, CORE_PIN9_BIT}, + {&CORE_PIN10_PORTREG, CORE_PIN10_BIT}, + {&CORE_PIN11_PORTREG, CORE_PIN11_BIT}, + {&CORE_PIN12_PORTREG, CORE_PIN12_BIT}, + {&CORE_PIN13_PORTREG, CORE_PIN13_BIT}, + {&CORE_PIN14_PORTREG, CORE_PIN14_BIT}, + {&CORE_PIN15_PORTREG, CORE_PIN15_BIT}, + {&CORE_PIN16_PORTREG, CORE_PIN16_BIT}, + {&CORE_PIN17_PORTREG, CORE_PIN17_BIT}, + {&CORE_PIN18_PORTREG, CORE_PIN18_BIT}, + {&CORE_PIN19_PORTREG, CORE_PIN19_BIT}, + {&CORE_PIN20_PORTREG, CORE_PIN20_BIT}, + {&CORE_PIN21_PORTREG, CORE_PIN21_BIT}, + {&CORE_PIN22_PORTREG, CORE_PIN22_BIT}, + {&CORE_PIN23_PORTREG, CORE_PIN23_BIT}, + {&CORE_PIN24_PORTREG, CORE_PIN24_BIT}, + {&CORE_PIN25_PORTREG, CORE_PIN25_BIT}, + {&CORE_PIN26_PORTREG, CORE_PIN26_BIT}, + {&CORE_PIN27_PORTREG, CORE_PIN27_BIT}, + {&CORE_PIN28_PORTREG, CORE_PIN28_BIT}, + {&CORE_PIN29_PORTREG, CORE_PIN29_BIT}, + {&CORE_PIN30_PORTREG, CORE_PIN30_BIT}, + {&CORE_PIN31_PORTREG, CORE_PIN31_BIT}, + {&CORE_PIN32_PORTREG, CORE_PIN32_BIT}, + {&CORE_PIN33_PORTREG, CORE_PIN33_BIT}, + {&CORE_PIN34_PORTREG, CORE_PIN34_BIT}, + {&CORE_PIN35_PORTREG, CORE_PIN35_BIT}, + {&CORE_PIN36_PORTREG, CORE_PIN36_BIT}, + {&CORE_PIN37_PORTREG, CORE_PIN37_BIT}, + {&CORE_PIN38_PORTREG, CORE_PIN38_BIT}, + {&CORE_PIN39_PORTREG, CORE_PIN39_BIT}, +}; + +#define _PM_SET_OFFSET 33 ///< 0x84 byte offset = 33 longs +#define _PM_CLEAR_OFFSET 34 ///< 0x88 byte offset = 34 longs +#define _PM_TOGGLE_OFFSET 35 ///< 0x8C byte offset = 35 longs + +#if __BYTE_ORDER__ == __ORDER_LITTLE_ENDIAN__ +#define _PM_byteOffset(pin) (_PM_teensyPins[pin].bit / 8) +#define _PM_wordOffset(pin) (_PM_teensyPins[pin].bit / 16) +#else +#define _PM_byteOffset(pin) (3 - (_PM_teensyPins[pin].bit / 8)) +#define _PM_wordOffset(pin) (1 - (_PM_teensyPins[pin].bit / 16)) +#endif + +#define _PM_portOutRegister(pin) (void *)_PM_teensyPins[pin].base + +#define _PM_portSetRegister(pin) \ + ((volatile uint32_t *)_PM_teensyPins[pin].base + _PM_SET_OFFSET) + +#define _PM_portClearRegister(pin) \ + ((volatile uint32_t *)_PM_teensyPins[pin].base + _PM_CLEAR_OFFSET) + +#define _PM_portToggleRegister(pin) \ + ((volatile uint32_t *)_PM_teensyPins[pin].base + _PM_TOGGLE_OFFSET) + +// As written, because it's tied to a specific timer right now, the +// Arduino lib only permits one instance of the Protomatter_core struct, +// which it sets up when calling begin(). +void *_PM_protoPtr = NULL; + +// Code as written works with the Periodic Interrupt Timer directly, +// rather than using the Teensy IntervalTimer library, reason being we +// need to be able to poll the current timer value in _PM_timerGetCount(), +// but that's not available from IntervalTimer, and the timer base address +// it keeps is a private member (possible alternative is to do dirty pool +// and access the pointer directly, knowing it's the first element in the +// IntervalTimer object, but this is fraught with peril). + +#define _PM_timerFreq 24000000 // 24 MHz +#define _PM_timerNum 0 // PIT timer #0 (can be 0-3) +#define _PM_TIMER_DEFAULT (IMXRT_PIT_CHANNELS + _PM_timerNum) // PIT channel * + +// Interrupt service routine for Periodic Interrupt Timer +static void _PM_timerISR(void) { + IMXRT_PIT_CHANNEL_t *timer = _PM_TIMER_DEFAULT; + _PM_row_handler(_PM_protoPtr); // In core.c + timer->TFLG = 1; // Clear timer interrupt +} + +// Initialize, but do not start, timer. +void _PM_timerInit(void *tptr) { + IMXRT_PIT_CHANNEL_t *timer = (IMXRT_PIT_CHANNEL_t *)tptr; + CCM_CCGR1 |= CCM_CCGR1_PIT(CCM_CCGR_ON); // Enable clock signal to PIT + PIT_MCR = 1; // Enable PIT + timer->TCTRL = 0; // Disable timer and interrupt + timer->LDVAL = 100000; // Timer initial load value + // Interrupt is attached but not enabled yet + attachInterruptVector(IRQ_PIT, &_PM_timerISR); + NVIC_ENABLE_IRQ(IRQ_PIT); +} + +// Set timer period, initialize count value to zero, enable timer. +inline void _PM_timerStart(void *tptr, uint32_t period) { + IMXRT_PIT_CHANNEL_t *timer = (IMXRT_PIT_CHANNEL_t *)tptr; + timer->TCTRL = 0; // Disable timer and interrupt + timer->LDVAL = period; // Set load value + // timer->CVAL = period; // And current value (just in case?) + timer->TFLG = 1; // Clear timer interrupt + timer->TCTRL = 3; // Enable timer and interrupt +} + +// Return current count value (timer enabled or not). +// Timer must be previously initialized. +inline uint32_t _PM_timerGetCount(void *tptr) { + IMXRT_PIT_CHANNEL_t *timer = (IMXRT_PIT_CHANNEL_t *)tptr; + return (timer->LDVAL - timer->CVAL); +} + +// Disable timer and return current count value. +// Timer must be previously initialized. +uint32_t _PM_timerStop(void *tptr) { + IMXRT_PIT_CHANNEL_t *timer = (IMXRT_PIT_CHANNEL_t *)tptr; + timer->TCTRL = 0; // Disable timer and interrupt + return _PM_timerGetCount(tptr); +} + +#define _PM_clockHoldHigh \ + asm("nop; nop; nop; nop; nop; nop; nop;"); \ + asm("nop; nop; nop; nop; nop; nop; nop;"); +#define _PM_clockHoldLow \ + asm("nop; nop; nop; nop; nop; nop; nop; nop; nop; nop;"); \ + asm("nop; nop; nop; nop; nop; nop; nop; nop; nop; nop;"); + +#define _PM_chunkSize 1 ///< DON'T unroll loop, Teensy 4 is SO FAST + +#elif defined(CIRCUITPY) // COMPILING FOR CIRCUITPYTHON -------------------- + +// Teensy 4 CircuitPython magic goes here. + +#endif // END CIRCUITPYTHON ------------------------------------------------ + +#endif // END __IMXRT1062__ (Teensy 4) |
