diff options
Diffstat (limited to 'circuitpython/lib/protomatter/src/arch/esp32.h')
-rw-r--r-- | circuitpython/lib/protomatter/src/arch/esp32.h | 215 |
1 files changed, 215 insertions, 0 deletions
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 |