aboutsummaryrefslogtreecommitdiff
path: root/circuitpython/lib/protomatter/src/arch/rp2040.h
diff options
context:
space:
mode:
Diffstat (limited to '')
-rw-r--r--circuitpython/lib/protomatter/src/arch/rp2040.h245
1 files changed, 245 insertions, 0 deletions
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