Compare commits

..

No commits in common. "master" and "metroesp32s2" have entirely different histories.

24 changed files with 319 additions and 680 deletions

View file

@ -7,16 +7,16 @@ jobs:
strategy:
fail-fast: false
matrix:
arduino-platform: ["metro_m0", "metro_m4", "metroesp32s2", "feather_esp32s3", "feather_rp2040", "nrf52840", "esp32"]
arduino-platform: ["metro_m0", "metro_m4", "nrf52840", "esp32"]
runs-on: ubuntu-latest
steps:
- uses: actions/setup-python@v5
- uses: actions/setup-python@v1
with:
python-version: '3.8'
- uses: actions/checkout@v4
- uses: actions/checkout@v4
- uses: actions/checkout@v2
- uses: actions/checkout@v2
with:
repository: adafruit/ci-arduino
path: ci

View file

@ -115,18 +115,3 @@ compile-time).
Most macros and functions begin with the prefix **\_PM\_** in order to
avoid naming collisions with other code (exception being static functions,
which can't be seen outside their source file).
# Pull Requests
If you encounter artifacts (noise, sparkles, dropouts and other issues) and
it seems to resolve by adjusting the NOP counts, please do not submit this
as a PR claiming a fix. Quite often what improves stability for one matrix
type can make things worse for other types. Instead, open an issue and
describe the hardware (both microcontroller and RGB matrix) and what worked
for you. A general solution working across all matrix types typically
involves monitoring the signals on a logic analyzer and aiming for a 50%
duty cycle on the CLK signal, 20 MHz or less, and then testing across a
wide variety of different matrix types to confirm; trial and error on just
a single matrix type is problematic. Maintainers: this goes for you too.
Don't merge a "fix" unless you've checked it out on a 'scope and on tested
across a broad range of matrices.

View file

@ -1,7 +1,7 @@
// Play GIFs from CIRCUITPY drive (USB-accessible filesystem) to LED matrix.
// ***DESIGNED FOR ADAFRUIT MATRIXPORTAL***, but may run on some other M4,
// M0, ESP32S3 and nRF52 boards (relies on TinyUSB stack). As written, runs
// on 64x32 pixel matrix, this can be changed by editing the WIDTH and HEIGHT
// ***DESIGNED FOR ADAFRUIT MATRIXPORTAL M4***, but may run on some other
// M4 & M0 and nRF52 boards (relies on TinyUSB stack). As written, runs on
// 64x32 pixel matrix, this can be changed by editing the WIDTH and HEIGHT
// definitions. See the "simple" example for a run-down on matrix config.
// Adapted from examples from Larry Bank's AnimatedGIF library and
// msc_external_flash example in Adafruit_TinyUSB_Arduino.
@ -33,9 +33,7 @@ uint16_t GIFminimumTime = 10; // Min. repeat time (seconds) until next GIF
// FLASH FILESYSTEM STUFF --------------------------------------------------
// External flash macros for QSPI or SPI are defined in board variant file.
#if defined(ARDUINO_ARCH_ESP32)
static Adafruit_FlashTransport_ESP32 flashTransport;
#elif defined(EXTERNAL_FLASH_USE_QSPI)
#if defined(EXTERNAL_FLASH_USE_QSPI)
Adafruit_FlashTransport_QSPI flashTransport;
#elif defined(EXTERNAL_FLASH_USE_SPI)
Adafruit_FlashTransport_SPI flashTransport(EXTERNAL_FLASH_USE_CS,
@ -58,14 +56,6 @@ uint8_t latchPin = 15;
uint8_t oePin = 16;
#define BACK_BUTTON 2
#define NEXT_BUTTON 3
#elif defined(ARDUINO_ADAFRUIT_MATRIXPORTAL_ESP32S3)
uint8_t rgbPins[] = {42, 41, 40, 38, 39, 37};
uint8_t addrPins[] = {45, 36, 48, 35, 21}; // 16/32/64 pixels tall
uint8_t clockPin = 2;
uint8_t latchPin = 47;
uint8_t oePin = 14;
#define BACK_BUTTON 6
#define NEXT_BUTTON 7
#elif defined(_VARIANT_METRO_M4_)
uint8_t rgbPins[] = {2, 3, 4, 5, 6, 7};
uint8_t addrPins[] = {A0, A1, A2, A3}; // 16 or 32 pixels tall
@ -99,7 +89,7 @@ int16_t xPos = 0, yPos = 0; // Top-left pixel coord of GIF in matrix space
// FILE ACCESS FUNCTIONS REQUIRED BY ANIMATED GIF LIB ----------------------
// Pass in ABSOLUTE PATH of GIF file to open
void *GIFOpenFile(const char *filename, int32_t *pSize) {
void *GIFOpenFile(char *filename, int32_t *pSize) {
GIFfile = filesys.open(filename);
if (GIFfile) {
*pSize = GIFfile.size();

View file

@ -21,12 +21,6 @@ supported boards.
uint8_t clockPin = 14;
uint8_t latchPin = 15;
uint8_t oePin = 16;
#elif defined(ARDUINO_ADAFRUIT_MATRIXPORTAL_ESP32S3) // MatrixPortal ESP32-S3
uint8_t rgbPins[] = {42, 41, 40, 38, 39, 37};
uint8_t addrPins[] = {45, 36, 48, 35, 21};
uint8_t clockPin = 2;
uint8_t latchPin = 47;
uint8_t oePin = 14;
#elif defined(_VARIANT_FEATHER_M4_) // Feather M4 + RGB Matrix FeatherWing
uint8_t rgbPins[] = {6, 5, 9, 11, 10, 12};
uint8_t addrPins[] = {A5, A4, A3, A2};
@ -121,16 +115,17 @@ Adafruit_Protomatter matrix(
// Sundry globals used for animation ---------------------------------------
int16_t textX; // Current text position (X)
int16_t textY; // Current text position (Y)
int16_t textMin; // Text pos. (X) when scrolled off left edge
char str[64]; // Buffer to hold scrolling message text
int16_t ball[3][4] = {
{ 3, 0, 1, 1 }, // Initial X,Y pos+velocity of 3 bouncy balls
int16_t textX = matrix.width(), // Current text position (X)
textY, // Current text position (Y)
textMin, // Text pos. (X) when scrolled off left edge
hue = 0;
char str[50]; // Buffer to hold scrolling message text
int8_t ball[3][4] = {
{ 3, 0, 1, 1 }, // Initial X,Y pos+velocity of 3 bouncy balls
{ 17, 15, 1, -1 },
{ 27, 4, -1, 1 }
};
uint16_t ballcolor[3]; // Colors for bouncy balls (init in setup())
uint16_t ballcolor[3]; // Colors for bouncy balls (init in setup())
// SETUP - RUNS ONCE AT PROGRAM START --------------------------------------
@ -159,7 +154,6 @@ void setup(void) {
uint16_t w, h;
matrix.getTextBounds(str, 0, 0, &x1, &y1, &w, &h); // How big is it?
textMin = -w; // All text is off left edge when it reaches this point
textX = matrix.width(); // Start off right edge
textY = matrix.height() / 2 - (y1 + h / 2); // Center text vertically
// Note: when making scrolling text like this, the setTextWrap(false)
// call is REQUIRED (to allow text to go off the edge of the matrix),
@ -169,7 +163,7 @@ void setup(void) {
// Set up the colors of the bouncy balls.
ballcolor[0] = matrix.color565(0, 20, 0); // Dark green
ballcolor[1] = matrix.color565(0, 0, 20); // Dark blue
ballcolor[2] = matrix.color565(20, 0, 0); // Dark red
ballcolor[2] = matrix.color565(20, 0, 0); // ark red
}
// LOOP - RUNS REPEATEDLY AFTER SETUP --------------------------------------

View file

@ -1,6 +1,6 @@
/* ----------------------------------------------------------------------
"Pixel dust" Protomatter library example. As written, this is
SPECIFICALLY FOR THE ADAFRUIT MATRIXPORTAL with 64x32 pixel matrix.
SPECIFICALLY FOR THE ADAFRUIT MATRIXPORTAL M4 with 64x32 pixel matrix.
Change "HEIGHT" below for 64x64 matrix. Could also be adapted to other
Protomatter-capable boards with an attached LIS3DH accelerometer.
@ -17,30 +17,21 @@ or "doublebuffer" for animation basics.
#define WIDTH 64 // Matrix width (pixels)
#define MAX_FPS 45 // Maximum redraw rate, frames/second
#if defined(_VARIANT_MATRIXPORTAL_M4_) // MatrixPortal M4
uint8_t rgbPins[] = {7, 8, 9, 10, 11, 12};
#if HEIGHT == 64 // 64-pixel tall matrices have 5 address lines:
uint8_t addrPins[] = {17, 18, 19, 20, 21};
#else // 32-pixel tall matrices have 4 address lines:
uint8_t addrPins[] = {17, 18, 19, 20};
#endif
// Remaining pins are the same for all matrix sizes. These values
// are for MatrixPortal M4. See "simple" example for other boards.
uint8_t rgbPins[] = {7, 8, 9, 10, 11, 12};
uint8_t clockPin = 14;
uint8_t latchPin = 15;
uint8_t oePin = 16;
#else // MatrixPortal ESP32-S3
uint8_t rgbPins[] = {42, 41, 40, 38, 39, 37};
uint8_t addrPins[] = {45, 36, 48, 35, 21};
uint8_t clockPin = 2;
uint8_t latchPin = 47;
uint8_t oePin = 14;
#endif
#if HEIGHT == 16
#define NUM_ADDR_PINS 3
#elif HEIGHT == 32
#define NUM_ADDR_PINS 4
#elif HEIGHT == 64
#define NUM_ADDR_PINS 5
#endif
Adafruit_Protomatter matrix(
WIDTH, 4, 1, rgbPins, NUM_ADDR_PINS, addrPins,
WIDTH, 4, 1, rgbPins, sizeof(addrPins), addrPins,
clockPin, latchPin, oePin, true);
Adafruit_LIS3DH accel = Adafruit_LIS3DH();

View file

@ -26,25 +26,12 @@ supported boards. Notes have been moved to the bottom of the code.
uint8_t clockPin = 14;
uint8_t latchPin = 15;
uint8_t oePin = 16;
#elif defined(ARDUINO_ADAFRUIT_MATRIXPORTAL_ESP32S3) // MatrixPortal ESP32-S3
uint8_t rgbPins[] = {42, 41, 40, 38, 39, 37};
uint8_t addrPins[] = {45, 36, 48, 35, 21};
uint8_t clockPin = 2;
uint8_t latchPin = 47;
uint8_t oePin = 14;
#elif defined(_VARIANT_FEATHER_M4_) // Feather M4 + RGB Matrix FeatherWing
uint8_t rgbPins[] = {6, 5, 9, 11, 10, 12};
uint8_t addrPins[] = {A5, A4, A3, A2};
uint8_t clockPin = 13;
uint8_t latchPin = 0;
uint8_t oePin = 1;
#elif defined(ARDUINO_ADAFRUIT_FEATHER_ESP32C6) // Feather ESP32-C6
// not featherwing compatible, but can 'hand wire' if desired
uint8_t rgbPins[] = {6, A3, A1, A0, A2, 0};
uint8_t addrPins[] = {8, 5, 15, 7};
uint8_t clockPin = 14;
uint8_t latchPin = RX;
uint8_t oePin = TX;
#elif defined(ARDUINO_ADAFRUIT_FEATHER_ESP32S2) // Feather ESP32-S2
// M0/M4/RP2040 Matrix FeatherWing compatible:
uint8_t rgbPins[] = {6, 5, 9, 11, 10, 12};
@ -66,11 +53,11 @@ supported boards. Notes have been moved to the bottom of the code.
uint8_t latchPin = 0;
uint8_t oePin = 1;
#elif defined(_SAMD21_) // Feather M0 variants
uint8_t rgbPins[] = {6, 5, 9, 11, 10, 12};
uint8_t addrPins[] = {A5, A4, A3, A2};
uint8_t clockPin = 13;
uint8_t latchPin = 0;
uint8_t oePin = 1;
uint8_t rgbPins[] = {6, 7, 10, 11, 12, 13};
uint8_t addrPins[] = {0, 1, 2, 3};
uint8_t clockPin = SDA;
uint8_t latchPin = 4;
uint8_t oePin = 5;
#elif defined(NRF52_SERIES) // Special nRF52840 FeatherWing pinout
uint8_t rgbPins[] = {6, A5, A1, A0, A4, 11};
uint8_t addrPins[] = {10, 5, 13, 9};

View file

@ -23,12 +23,6 @@ supported boards.
uint8_t clockPin = 14;
uint8_t latchPin = 15;
uint8_t oePin = 16;
#elif defined(ARDUINO_ADAFRUIT_MATRIXPORTAL_ESP32S3) // MatrixPortal ESP32-S3
uint8_t rgbPins[] = {42, 41, 40, 38, 39, 37};
uint8_t addrPins[] = {45, 36, 48, 35, 21};
uint8_t clockPin = 2;
uint8_t latchPin = 47;
uint8_t oePin = 14;
#elif defined(_VARIANT_FEATHER_M4_) // Feather M4 + RGB Matrix FeatherWing
uint8_t rgbPins[] = {6, 5, 9, 11, 10, 12};
uint8_t addrPins[] = {A5, A4, A3, A2};

View file

@ -1,5 +1,5 @@
name=Adafruit Protomatter
version=1.7.0
version=1.5.3
author=Adafruit
maintainer=Adafruit <info@adafruit.com>
sentence=A library for Adafruit RGB LED matrices.

View file

@ -144,16 +144,6 @@ public:
*/
uint16_t colorHSV(uint16_t hue, uint8_t sat = 255, uint8_t val = 255);
/*!
@brief Adjust HUB clock signal duty cycle on architectures that support
this (currently SAMD51 only) (else ignored).
@param Duty setting, 0 minimum. Increasing values generate higher clock
duty cycles at the same frequency. Arbitrary granular units, max
varies by architecture and CPU speed, if supported at all.
e.g. SAMD51 @ 120 MHz supports 0 (~50% duty) through 2 (~75%).
*/
void setDuty(uint8_t d) { _PM_setDuty(d); };
private:
Protomatter_core core; // Underlying C struct
void convert_byte(uint8_t *dest); // GFXcanvas16-to-matrix

View file

@ -192,7 +192,6 @@ _PM_CUSTOM_BLAST If defined, instructs core code to not compile
#include "esp32-s2.h"
#include "esp32-s3.h"
#include "esp32-c3.h"
#include "esp32-c6.h"
#include "nrf52.h"
#include "rp2040.h"
#include "samd-common.h"
@ -243,11 +242,3 @@ _PM_CUSTOM_BLAST If defined, instructs core code to not compile
#if !defined(_PM_PORT_TYPE)
#define _PM_PORT_TYPE uint32_t ///< PORT register size/type
#endif
#if !defined(_PM_maxDuty)
#define _PM_maxDuty 0 ///< Max duty cycle setting (where supported)
#endif
#if !defined(_PM_defaultDuty)
#define _PM_defaultDuty 0 ///< Default duty cycle setting (where supported)
#endif

View file

@ -29,7 +29,7 @@
#define _PM_portSetRegister(pin) (volatile uint32_t *)&GPIO.out_w1ts
#define _PM_portClearRegister(pin) (volatile uint32_t *)&GPIO.out_w1tc
#define _PM_portBitMask(pin) (1U << ((pin) & 31))
#define _PM_portBitMask(pin) (1U << ((pin)&31))
#if __BYTE_ORDER__ == __ORDER_LITTLE_ENDIAN__
#define _PM_byteOffset(pin) ((pin & 31) / 8)
@ -42,7 +42,6 @@
// No special peripheral setup on ESP32C3, just use common timer init...
#define _PM_timerInit(core) _PM_esp32commonTimerInit(core);
#if defined(ARDUINO) // COMPILING FOR ARDUINO ------------------------------
// Return current count value (timer enabled or not).
// Timer must be previously initialized.
// This function is the same on all ESP32 parts EXCEPT S3.
@ -50,6 +49,8 @@ IRAM_ATTR inline uint32_t _PM_timerGetCount(Protomatter_core *core) {
return (uint32_t)timerRead((hw_timer_t *)core->timer);
}
#if defined(ARDUINO) // COMPILING FOR ARDUINO ------------------------------
#elif defined(CIRCUITPY) // COMPILING FOR CIRCUITPYTHON --------------------
#endif // END CIRCUITPYTHON ------------------------------------------------

View file

@ -1,57 +0,0 @@
/*!
* @file esp32-c3.h
*
* Part of Adafruit's Protomatter library for HUB75-style RGB LED matrices.
* This file contains ESP32-C3-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
// NOTE: there is some intentional repetition in the macros and functions
// for some ESP32 variants. Previously they were all one file, but complex
// preprocessor directives were turning into spaghetti. THEREFORE, if making
// a change or bugfix in one variant-specific header, check the others to
// see if the same should be applied!
#if defined(CONFIG_IDF_TARGET_ESP32C6)
#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
#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
// No special peripheral setup on ESP32C3, just use common timer init...
#define _PM_timerInit(core) _PM_esp32commonTimerInit(core);
#if defined(ARDUINO) // COMPILING FOR ARDUINO ------------------------------
// Return current count value (timer enabled or not).
// Timer must be previously initialized.
// This function is the same on all ESP32 parts EXCEPT S3.
IRAM_ATTR inline uint32_t _PM_timerGetCount(Protomatter_core *core) {
return (uint32_t)timerRead((hw_timer_t *)core->timer);
}
#elif defined(CIRCUITPY) // COMPILING FOR CIRCUITPYTHON --------------------
#endif // END CIRCUITPYTHON ------------------------------------------------
#endif // END ESP32C3

View file

@ -17,12 +17,7 @@
#pragma once
#if defined(ESP32) || \
defined(ESP_PLATFORM) // *All* ESP32 variants (OG, S2, S3, etc.)
#include <inttypes.h>
#include "esp_idf_version.h"
#if defined(ESP32) // *All* ESP32 variants (OG, S2, S3, etc.)
// NOTE: there is some intentional repetition in the macros and functions
// for some ESP32 variants. Previously they were all one file, but complex
@ -30,23 +25,18 @@
// a change or bugfix in one variant-specific header, check the others to
// see if the same should be applied!
#include "driver/timer.h"
#include "soc/gpio_periph.h"
// As currently written, only one instance of the Protomatter_core struct
// is allowed, set up when calling begin()...so it's just a global here:
Protomatter_core *_PM_protoPtr;
void *_PM_protoPtr = NULL;
#define _PM_timerFreq 40000000 // 40 MHz (1:2 prescale)
#if defined(ARDUINO) // COMPILING FOR ARDUINO ------------------------------
#if ESP_IDF_VERSION < ESP_IDF_VERSION_VAL(5, 0, 0)
#define _PM_timerNum 0 // Timer #0 (can be 0-3)
static hw_timer_t *_PM_esp32timer = NULL;
#define _PM_TIMER_DEFAULT &_PM_esp32timer
#else
#define _PM_TIMER_DEFAULT ((void *)-1) // some non-NULL but non valid pointer
#endif
// The following defines and functions are common to all ESP32 variants in
// the Arduino platform. Anything unique to one variant (or a subset of
@ -55,7 +45,16 @@ static hw_timer_t *_PM_esp32timer = NULL;
// started down that path, it's okay, but move the code out of here and
// into the variant-specific headers.
extern void _PM_row_handler(Protomatter_core *core); // In core.c
// 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); // In core.c
// Timer interrupt handler. This, _PM_row_handler() and any functions
// called by _PM_row_handler() should all have the IRAM_ATTR attribute
@ -69,15 +68,9 @@ IRAM_ATTR static void _PM_esp32timerCallback(void) {
// Set timer period, initialize count value to zero, enable timer.
IRAM_ATTR inline void _PM_timerStart(Protomatter_core *core, uint32_t period) {
hw_timer_t *timer = (hw_timer_t *)core->timer;
#if ESP_IDF_VERSION < ESP_IDF_VERSION_VAL(5, 0, 0)
timerAlarmWrite(timer, period, true);
timerAlarmEnable(timer);
timerStart(timer);
#else
timerWrite(timer, 0);
timerAlarm(timer, period ? period : 1, true, 0);
timerStart(timer);
#endif
}
// Disable timer and return current count value.
@ -91,19 +84,11 @@ IRAM_ATTR uint32_t _PM_timerStop(Protomatter_core *core) {
// that's common to all ESP32 variants; code in variant-specific files might
// set up its own special peripherals, then call this.
void _PM_esp32commonTimerInit(Protomatter_core *core) {
hw_timer_t *timer_in = (hw_timer_t *)core->timer;
if (!timer_in || timer_in == _PM_TIMER_DEFAULT) {
#if ESP_IDF_VERSION < ESP_IDF_VERSION_VAL(5, 0, 0)
hw_timer_t *timer = (hw_timer_t *)core->timer; // pointer-to-pointer
if (timer == _PM_TIMER_DEFAULT) {
core->timer = timerBegin(_PM_timerNum, 2, true); // 1:2 prescale, count up
#else
core->timer = timerBegin(_PM_timerFreq);
#endif
}
#if ESP_IDF_VERSION < ESP_IDF_VERSION_VAL(5, 0, 0)
timerAttachInterrupt(core->timer, &_PM_esp32timerCallback, true);
#else
timerAttachInterrupt(core->timer, _PM_esp32timerCallback);
#endif
timerAttachInterrupt(timer, &_PM_esp32timerCallback, true);
}
#elif defined(CIRCUITPY) // COMPILING FOR CIRCUITPYTHON --------------------
@ -118,12 +103,7 @@ void _PM_esp32commonTimerInit(Protomatter_core *core) {
#include "driver/gpio.h"
#include "esp_idf_version.h"
#include "hal/timer_ll.h"
#if ESP_IDF_VERSION_MAJOR == 5
#include "driver/gptimer.h"
#include "esp_memory_utils.h"
#else
#include "driver/timer.h"
#endif
#include "peripherals/timer.h"
#define _PM_TIMER_DEFAULT NULL
#define _PM_pinOutput(pin) gpio_set_direction((pin), GPIO_MODE_OUTPUT)
@ -135,24 +115,8 @@ void _PM_esp32commonTimerInit(Protomatter_core *core) {
// (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.
#if ESP_IDF_VERSION_MAJOR == 5
// This is "private" for now. We link to it anyway because there isn't a more
// public method yet.
extern bool spi_flash_cache_enabled(void);
static IRAM_ATTR bool
_PM_esp32timerCallback(gptimer_handle_t timer,
const gptimer_alarm_event_data_t *event, void *unused) {
#else
static IRAM_ATTR bool _PM_esp32timerCallback(void *unused) {
#endif
#if ESP_IDF_VERSION_MAJOR == 5
// Some functions and data used by _PM_row_handler may exist in external flash
// or PSRAM so we can't run them when their access is disabled (through the
// flash cache.)
if (_PM_protoPtr && spi_flash_cache_enabled()) {
#else
IRAM_ATTR bool _PM_esp32timerCallback(void *unused) {
if (_PM_protoPtr) {
#endif
_PM_row_handler(_PM_protoPtr); // In core.c
}
return false;
@ -161,15 +125,13 @@ static IRAM_ATTR bool _PM_esp32timerCallback(void *unused) {
// Set timer period, initialize count value to zero, enable timer.
#if (ESP_IDF_VERSION_MAJOR == 5)
IRAM_ATTR void _PM_timerStart(Protomatter_core *core, uint32_t period) {
gptimer_handle_t timer = (gptimer_handle_t)core->timer;
gptimer_alarm_config_t alarm_config = {
.reload_count = 0, // counter will reload with 0 on alarm event
.alarm_count = period, // period in ms
.flags.auto_reload_on_alarm = true, // enable auto-reload
};
gptimer_set_alarm_action(timer, &alarm_config);
gptimer_start(timer);
timer_index_t *timer = (timer_index_t *)core->timer;
timer_ll_enable_counter(timer->hw, timer->idx, false);
timer_ll_set_reload_value(timer->hw, timer->idx, 0);
timer_ll_trigger_soft_reload(timer->hw, timer->idx);
timer_ll_set_alarm_value(timer->hw, timer->idx, period);
timer_ll_enable_alarm(timer->hw, timer->idx, true);
timer_ll_enable_counter(timer->hw, timer->idx, true);
}
#else
IRAM_ATTR void _PM_timerStart(Protomatter_core *core, uint32_t period) {
@ -185,46 +147,30 @@ IRAM_ATTR void _PM_timerStart(Protomatter_core *core, uint32_t period) {
// Disable timer and return current count value.
// Timer must be previously initialized.
IRAM_ATTR uint32_t _PM_timerStop(Protomatter_core *core) {
#if (ESP_IDF_VERSION_MAJOR == 5)
gptimer_handle_t timer = (gptimer_handle_t)core->timer;
gptimer_stop(timer);
#else
timer_index_t *timer = (timer_index_t *)core->timer;
#if (ESP_IDF_VERSION_MAJOR == 5)
timer_ll_enable_counter(timer->hw, timer->idx, false);
#else
timer_ll_set_counter_enable(timer->hw, timer->idx, false);
#endif
return _PM_timerGetCount(core);
}
#if !defined(CONFIG_IDF_TARGET_ESP32S3)
IRAM_ATTR uint32_t _PM_timerGetCount(Protomatter_core *core) {
#if (ESP_IDF_VERSION_MAJOR == 5)
gptimer_handle_t timer = (gptimer_handle_t)core->timer;
uint64_t raw_count;
gptimer_get_raw_count(timer, &raw_count);
return (uint32_t)raw_count;
#else
timer_index_t *timer = (timer_index_t *)core->timer;
uint64_t result;
timer_ll_get_counter_value(timer->hw, timer->idx, &result);
return (uint32_t)result;
#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
}
#endif
// Initialize, but do not start, timer. This function contains timer setup
// that's common to all ESP32 variants; code in variant-specific files might
// set up its own special peripherals, then call this.
static void _PM_esp32commonTimerInit(Protomatter_core *core) {
#if (ESP_IDF_VERSION_MAJOR == 5)
gptimer_handle_t timer = (gptimer_handle_t)core->timer;
gptimer_event_callbacks_t cbs = {
.on_alarm = _PM_esp32timerCallback, // register user callback
};
gptimer_register_event_callbacks(timer, &cbs, NULL);
gptimer_enable(timer);
#else
void _PM_esp32commonTimerInit(Protomatter_core *core) {
timer_index_t *timer = (timer_index_t *)core->timer;
const timer_config_t config = {
.alarm_en = false,
@ -239,7 +185,6 @@ static void _PM_esp32commonTimerInit(Protomatter_core *core) {
timer_isr_callback_add(timer->group, timer->idx, _PM_esp32timerCallback, NULL,
0);
timer_enable_intr(timer->group, timer->idx);
#endif
}
#endif // END CIRCUITPYTHON ------------------------------------------------

View file

@ -139,6 +139,13 @@ IRAM_ATTR static void blast_byte(Protomatter_core *core, uint8_t *data) {
IRAM_ATTR static void blast_word(Protomatter_core *core, uint16_t *data) {}
IRAM_ATTR static void blast_long(Protomatter_core *core, uint32_t *data) {}
// Return current count value (timer enabled or not).
// Timer must be previously initialized.
// This function is the same on all ESP32 parts EXCEPT S3.
IRAM_ATTR inline uint32_t _PM_timerGetCount(Protomatter_core *core) {
return (uint32_t)timerRead((hw_timer_t *)core->timer);
}
#if defined(ARDUINO) // COMPILING FOR ARDUINO ------------------------------
void _PM_timerInit(Protomatter_core *core) {
@ -167,13 +174,6 @@ void _PM_timerInit(Protomatter_core *core) {
_PM_esp32commonTimerInit(core); // In esp32-common.h
}
// Return current count value (timer enabled or not).
// Timer must be previously initialized.
// This function is the same on all ESP32 parts EXCEPT S3.
IRAM_ATTR inline uint32_t _PM_timerGetCount(Protomatter_core *core) {
return (uint32_t)timerRead((hw_timer_t *)core->timer);
}
#elif defined(CIRCUITPY) // COMPILING FOR CIRCUITPYTHON --------------------
void _PM_timerInit(Protomatter_core *core) {

View file

@ -25,14 +25,6 @@
#if defined(CONFIG_IDF_TARGET_ESP32S3)
#define GPIO_DRIVE_STRENGTH GPIO_DRIVE_CAP_3
#define LCD_CLK_PRESCALE 9 // 8, 9, 10 allowed. Bit clock = 160 MHz / this.
#if defined(CIRCUITPY) // COMPILING FOR CIRCUITPYTHON --------------------
#include "components/esp_rom/include/esp_rom_sys.h"
#include "components/heap/include/esp_heap_caps.h"
#endif
// Use DMA-capable RAM (not PSRAM) for framebuffer:
#define _PM_allocate(x) heap_caps_malloc(x, MALLOC_CAP_DMA | MALLOC_CAP_8BIT)
#define _PM_free(x) heap_caps_free(x)
@ -65,16 +57,16 @@
// dmaSetupTime (measured in blast_byte()) measures the number of timer
// cycles to set up and trigger the DMA transfer...
static uint32_t dmaSetupTime = 100;
// ...then, the version of _PM_timerGetCount() here uses that as a starting
// point, plus the known constant DMA xfer speed (160/LCD_CLK_PRESCALE MHz)
// and timer frequency (40 MHz), to return an estimate of the one-scanline
// transfer time, from which everything is extrapolated:
// ...then, the version of _PM_timerGetCount() here uses that figure as a
// starting point, plus the known constant DMA transfer speed (20 MHz) and
// timer frequency (40 MHz), i.e. 2 cycles/column, to return a fair estimate
// of the one-scanline transfer time, from which everything is extrapolated:
IRAM_ATTR inline uint32_t _PM_timerGetCount(Protomatter_core *core) {
// Time estimate seems to come in a little high, so the -10 here is an
// Time estimate seems to come in a little high, so the -25 here is an
// empirically-derived fudge factor that may yield ever-so-slightly better
// refresh in some edge cases. If visual glitches are encountered, might
// need to dial back this number a bit or remove it.
return dmaSetupTime + core->chainBits * 40 * LCD_CLK_PRESCALE / 160 - 10;
// need to dial back this number a bit.
return dmaSetupTime + core->chainBits * 2 - 25;
}
// Note that dmaSetupTime can vary from line to line, potentially influenced
// by interrupts, nondeterministic DMA channel clearing times, etc., which is
@ -82,15 +74,13 @@ IRAM_ATTR inline uint32_t _PM_timerGetCount(Protomatter_core *core) {
// slightly different length of time, but duty cycle scales with this so it's
// perceptually consistent; don't see bright or dark rows.
#define _PM_minMinPeriod \
(200 + (uint32_t)core->chainBits * 40 * LCD_CLK_PRESCALE / 160)
#define _PM_minMinPeriod (200 + core->chainBits * 2)
#if (ESP_IDF_VERSION_MAJOR == 5)
#include <esp_private/periph_ctrl.h>
#else
#include <driver/periph_ctrl.h>
#endif
#include <driver/gpio.h>
#include <esp_private/gdma.h>
#include <esp_rom_gpio.h>
#include <hal/dma_types.h>
@ -122,17 +112,6 @@ static uint32_t _PM_directBitMask(Protomatter_core *core, int pin) {
static dma_descriptor_t desc;
static gdma_channel_handle_t dma_chan;
// If using custom "blast" function(s), all three must be declared.
// Unused ones can be empty, that's fine, just need to exist.
IRAM_ATTR static void blast_word(Protomatter_core *core, uint16_t *data) {}
IRAM_ATTR static void blast_long(Protomatter_core *core, uint32_t *data) {}
static void pinmux(int8_t pin, uint8_t signal) {
esp_rom_gpio_connect_out_signal(pin, signal, false, false);
PIN_FUNC_SELECT(GPIO_PIN_MUX_REG[pin], PIN_FUNC_GPIO);
gpio_set_drive_capability((gpio_num_t)pin, GPIO_DRIVE_STRENGTH);
}
// LCD_CAM requires a complete replacement of the "blast" functions in order
// to use the DMA-based peripheral.
#define _PM_CUSTOM_BLAST // Disable blast_*() functions in core.c
@ -160,23 +139,24 @@ IRAM_ATTR static void blast_byte(Protomatter_core *core, uint8_t *data) {
// Timer was cleared to 0 before calling blast_byte(), so this
// is the state of the timer immediately after DMA started:
#if defined(ARDUINO)
dmaSetupTime = (uint32_t)timerRead((hw_timer_t *)core->timer);
#elif defined(CIRCUITPY)
uint64_t value;
#if (ESP_IDF_VERSION_MAJOR == 5)
gptimer_handle_t timer = (gptimer_handle_t)core->timer;
gptimer_get_raw_count(timer, &value);
#else
timer_index_t *timer = (timer_index_t *)core->timer;
timer_get_counter_value(timer->group, timer->idx, &value);
#endif
dmaSetupTime = (uint32_t)value;
#endif
// See notes near top of this file for what's done with this info.
}
static void _PM_timerInit(Protomatter_core *core) {
// If using custom "blast" function(s), all three must be declared.
// Unused ones can be empty, that's fine, just need to exist.
IRAM_ATTR static void blast_word(Protomatter_core *core, uint16_t *data) {}
IRAM_ATTR static void blast_long(Protomatter_core *core, uint32_t *data) {}
static void pinmux(int8_t pin, uint8_t signal) {
esp_rom_gpio_connect_out_signal(pin, signal, false, false);
gpio_hal_iomux_func_sel(GPIO_PIN_MUX_REG[pin], PIN_FUNC_GPIO);
gpio_set_drive_capability((gpio_num_t)pin, GPIO_DRIVE_CAP_MAX);
}
#if defined(ARDUINO) // COMPILING FOR ARDUINO ------------------------------
void _PM_timerInit(Protomatter_core *core) {
// On S3, initialize the LCD_CAM peripheral and DMA.
// LCD_CAM isn't enabled by default -- MUST begin with this:
@ -188,17 +168,11 @@ static void _PM_timerInit(Protomatter_core *core) {
esp_rom_delay_us(100);
// Configure LCD clock
LCD_CAM.lcd_clock.clk_en = 1; // Enable clock
LCD_CAM.lcd_clock.lcd_clk_sel = 3; // PLL160M source
LCD_CAM.lcd_clock.lcd_clkm_div_a = 1; // 1/1 fractional divide,
LCD_CAM.lcd_clock.lcd_clkm_div_b = 1; // plus prescale below yields...
#if LCD_CLK_PRESCALE == 8
LCD_CAM.lcd_clock.lcd_clkm_div_num = 7; // 1:8 prescale (20 MHz CLK)
#elif LCD_CLK_PRESCALE == 9
LCD_CAM.lcd_clock.lcd_clkm_div_num = 8; // 1:9 prescale (17.8 MHz CLK)
#else
LCD_CAM.lcd_clock.lcd_clkm_div_num = 9; // 1:10 prescale (16 MHz CLK)
#endif
LCD_CAM.lcd_clock.clk_en = 1; // Enable clock
LCD_CAM.lcd_clock.lcd_clk_sel = 3; // PLL160M source
LCD_CAM.lcd_clock.lcd_clkm_div_a = 1; // 1/1 fractional divide,
LCD_CAM.lcd_clock.lcd_clkm_div_b = 1; // plus '7' below yields...
LCD_CAM.lcd_clock.lcd_clkm_div_num = 7; // 1:8 prescale (20 MHz CLK)
LCD_CAM.lcd_clock.lcd_ck_out_edge = 0; // PCLK low in first half of cycle
LCD_CAM.lcd_clock.lcd_ck_idle_edge = 0; // PCLK low idle
LCD_CAM.lcd_clock.lcd_clk_equ_sysclk = 1; // PCLK = CLK (ignore CLKCNT_N)
@ -234,10 +208,10 @@ static void _PM_timerInit(Protomatter_core *core) {
for (int i = 0; i < 6; i++)
pinmux(core->rgbPins[i], signal[i]);
pinmux(core->clockPin, LCD_PCLK_IDX);
gpio_set_drive_capability(core->latch.pin, GPIO_DRIVE_STRENGTH);
gpio_set_drive_capability(core->oe.pin, GPIO_DRIVE_STRENGTH);
gpio_set_drive_capability(core->latch.pin, GPIO_DRIVE_CAP_MAX);
gpio_set_drive_capability(core->oe.pin, GPIO_DRIVE_CAP_MAX);
for (uint8_t i = 0; i < core->numAddressLines; i++) {
gpio_set_drive_capability(core->addr[i].pin, GPIO_DRIVE_STRENGTH);
gpio_set_drive_capability(core->addr[i].pin, GPIO_DRIVE_CAP_MAX);
}
// Disable LCD_CAM interrupts, clear any pending interrupt
@ -252,26 +226,20 @@ static void _PM_timerInit(Protomatter_core *core) {
desc.next = NULL;
// Alloc DMA channel & connect it to LCD periph
#if defined(CIRCUITPY)
if (dma_chan == NULL) {
#endif
gdma_channel_alloc_config_t dma_chan_config = {
.sibling_chan = NULL,
.direction = GDMA_CHANNEL_DIRECTION_TX,
.flags = {.reserve_sibling = 0}};
gdma_new_channel(&dma_chan_config, &dma_chan);
gdma_connect(dma_chan, GDMA_MAKE_TRIGGER(GDMA_TRIG_PERIPH_LCD, 0));
gdma_strategy_config_t strategy_config = {.owner_check = false,
.auto_update_desc = false};
gdma_apply_strategy(dma_chan, &strategy_config);
gdma_transfer_ability_t ability = {
.sram_trans_align = 0,
.psram_trans_align = 0,
};
gdma_set_transfer_ability(dma_chan, &ability);
#if defined(CIRCUITPY)
}
#endif
gdma_channel_alloc_config_t dma_chan_config = {
.sibling_chan = NULL,
.direction = GDMA_CHANNEL_DIRECTION_TX,
.flags = {.reserve_sibling = 0}};
esp_err_t ret = gdma_new_channel(&dma_chan_config, &dma_chan);
gdma_connect(dma_chan, GDMA_MAKE_TRIGGER(GDMA_TRIG_PERIPH_LCD, 0));
gdma_strategy_config_t strategy_config = {.owner_check = false,
.auto_update_desc = false};
gdma_apply_strategy(dma_chan, &strategy_config);
gdma_transfer_ability_t ability = {
.sram_trans_align = 0,
.psram_trans_align = 0,
};
gdma_set_transfer_ability(dma_chan, &ability);
gdma_start(dma_chan, (intptr_t)&desc);
// Enable TRANS_DONE interrupt. Note that we do NOT require nor install
@ -282,4 +250,109 @@ static void _PM_timerInit(Protomatter_core *core) {
_PM_esp32commonTimerInit(core); // In esp32-common.h
}
#elif defined(CIRCUITPY) // COMPILING FOR CIRCUITPYTHON --------------------
void _PM_timerInit(Protomatter_core *core) {
// TO DO: adapt this function for any CircuitPython-specific changes.
// If none are required, this function can be deleted and the version
// above can be moved before the ARDUIO/CIRCUITPY checks. If minimal
// changes, consider a single _PM_timerInit() implementation with
// ARDUINO/CIRCUITPY checks inside. It's all good.
// On S3, initialize the LCD_CAM peripheral and DMA.
// LCD_CAM isn't enabled by default -- MUST begin with this:
periph_module_enable(PERIPH_LCD_CAM_MODULE);
periph_module_reset(PERIPH_LCD_CAM_MODULE);
// Reset LCD bus
LCD_CAM.lcd_user.lcd_reset = 1;
esp_rom_delay_us(100);
// Configure LCD clock
LCD_CAM.lcd_clock.clk_en = 1; // Enable clock
LCD_CAM.lcd_clock.lcd_clk_sel = 3; // PLL160M source
LCD_CAM.lcd_clock.lcd_clkm_div_a = 1; // 1/1 fractional divide,
LCD_CAM.lcd_clock.lcd_clkm_div_b = 1; // plus '7' below yields...
LCD_CAM.lcd_clock.lcd_clkm_div_num = 7; // 1:8 prescale (20 MHz CLK)
LCD_CAM.lcd_clock.lcd_ck_out_edge = 0; // PCLK low in first half of cycle
LCD_CAM.lcd_clock.lcd_ck_idle_edge = 0; // PCLK low idle
LCD_CAM.lcd_clock.lcd_clk_equ_sysclk = 1; // PCLK = CLK (ignore CLKCNT_N)
// Configure frame format. Some of these could probably be skipped and
// use defaults, but being verbose for posterity...
LCD_CAM.lcd_ctrl.lcd_rgb_mode_en = 0; // i8080 mode (not RGB)
LCD_CAM.lcd_rgb_yuv.lcd_conv_bypass = 0; // Disable RGB/YUV converter
LCD_CAM.lcd_misc.lcd_next_frame_en = 0; // Do NOT auto-frame
LCD_CAM.lcd_data_dout_mode.val = 0; // No data delays
LCD_CAM.lcd_user.lcd_always_out_en = 0; // Only when requested
LCD_CAM.lcd_user.lcd_8bits_order = 0; // Do not swap bytes
LCD_CAM.lcd_user.lcd_bit_order = 0; // Do not reverse bit order
LCD_CAM.lcd_user.lcd_2byte_en = 0; // 8-bit data mode
// MUST enable at least one dummy phase at start of output, else clock and
// data are randomly misaligned by 1-2 cycles following required TX FIFO
// reset in blast_byte(). One phase MOSTLY works but sparkles a tiny bit
// (as in still very occasionally misaligned by 1 cycle). Two seems ideal;
// no sparkle. Since HUB75 is just a shift register, the extra clock ticks
// are harmless and the zero-data shifts off end of the chain.
LCD_CAM.lcd_user.lcd_dummy = 1; // Enable dummy phase(s) @ LCD start
LCD_CAM.lcd_user.lcd_dummy_cyclelen = 1; // 2 dummy phases
LCD_CAM.lcd_user.lcd_cmd = 0; // No command at LCD start
LCD_CAM.lcd_user.lcd_cmd_2_cycle_en = 0;
LCD_CAM.lcd_user.lcd_update = 1;
// Configure signal pins. IN THEORY this could be expanded to support
// 2 parallel chains, but the rest of the LCD & DMA setup is not currently
// written for that, so it's limited to a single chain for now.
const uint8_t signal[] = {LCD_DATA_OUT0_IDX, LCD_DATA_OUT1_IDX,
LCD_DATA_OUT2_IDX, LCD_DATA_OUT3_IDX,
LCD_DATA_OUT4_IDX, LCD_DATA_OUT5_IDX};
for (int i = 0; i < 6; i++)
pinmux(core->rgbPins[i], signal[i]);
pinmux(core->clockPin, LCD_PCLK_IDX);
gpio_set_drive_capability(core->latch.pin, GPIO_DRIVE_CAP_MAX);
gpio_set_drive_capability(core->oe.pin, GPIO_DRIVE_CAP_MAX);
for (uint8_t i = 0; i < core->numAddressLines; i++) {
gpio_set_drive_capability(core->addr[i].pin, GPIO_DRIVE_CAP_MAX);
}
// Disable LCD_CAM interrupts, clear any pending interrupt
LCD_CAM.lc_dma_int_ena.val &= ~LCD_LL_EVENT_TRANS_DONE;
LCD_CAM.lc_dma_int_clr.val = 0x03;
// Set up DMA TX descriptor
desc.dw0.owner = DMA_DESCRIPTOR_BUFFER_OWNER_DMA;
desc.dw0.suc_eof = 1;
desc.dw0.size = desc.dw0.length = core->chainBits;
desc.buffer = core->screenData;
desc.next = NULL;
// Alloc DMA channel & connect it to LCD periph
gdma_channel_alloc_config_t dma_chan_config = {
.sibling_chan = NULL,
.direction = GDMA_CHANNEL_DIRECTION_TX,
.flags = {.reserve_sibling = 0}};
gdma_new_channel(&dma_chan_config, &dma_chan);
gdma_connect(dma_chan, GDMA_MAKE_TRIGGER(GDMA_TRIG_PERIPH_LCD, 0));
gdma_strategy_config_t strategy_config = {.owner_check = false,
.auto_update_desc = false};
gdma_apply_strategy(dma_chan, &strategy_config);
gdma_transfer_ability_t ability = {
.sram_trans_align = 0,
.psram_trans_align = 0,
};
gdma_set_transfer_ability(dma_chan, &ability);
gdma_start(dma_chan, (intptr_t)&desc);
// Enable TRANS_DONE interrupt. Note that we do NOT require nor install
// an interrupt service routine, but DO need to enable the TRANS_DONE
// flag to make the LCD DMA transfer work.
LCD_CAM.lc_dma_int_ena.val |= LCD_LL_EVENT_TRANS_DONE & 0x03;
_PM_esp32commonTimerInit(core); // In esp32-common.h
}
#endif // END CIRCUITPYTHON ------------------------------------------------
#endif // END ESP32S3

View file

@ -32,7 +32,7 @@
#define _PM_portClearRegister(pin) \
(volatile uint32_t *)((pin < 32) ? &GPIO.out_w1tc : &GPIO.out1_w1tc.val)
#define _PM_portBitMask(pin) (1U << ((pin) & 31))
#define _PM_portBitMask(pin) (1U << ((pin)&31))
#if __BYTE_ORDER__ == __ORDER_LITTLE_ENDIAN__
#define _PM_byteOffset(pin) ((pin & 31) / 8)

View file

@ -99,7 +99,7 @@ volatile uint32_t *_PM_portClearRegister(uint32_t pin) {
#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))
#define _PM_portBitMask(pin) (1u << ((pin)&31))
#if __BYTE_ORDER__ == __ORDER_LITTLE_ENDIAN__
#define _PM_byteOffset(pin) ((pin & 31) / 8)
@ -142,19 +142,19 @@ void _PM_timerInit(Protomatter_core *core) {
IRQn_Type IRQn; // Interrupt number
} timer[] = {
#if defined(NRF_TIMER0)
{NRF_TIMER0, TIMER0_IRQn},
{NRF_TIMER0, TIMER0_IRQn},
#endif
#if defined(NRF_TIMER1)
{NRF_TIMER1, TIMER1_IRQn},
{NRF_TIMER1, TIMER1_IRQn},
#endif
#if defined(NRF_TIMER2)
{NRF_TIMER2, TIMER2_IRQn},
{NRF_TIMER2, TIMER2_IRQn},
#endif
#if defined(NRF_TIMER3)
{NRF_TIMER3, TIMER3_IRQn},
{NRF_TIMER3, TIMER3_IRQn},
#endif
#if defined(NRF_TIMER4)
{NRF_TIMER4, TIMER4_IRQn},
{NRF_TIMER4, TIMER4_IRQn},
#endif
};
#define NUM_TIMERS (sizeof timer / sizeof timer[0])

View file

@ -26,8 +26,7 @@
#pragma once
#if defined(ARDUINO_ARCH_RP2040) || defined(PICO_BOARD) || \
defined(__RP2040__) || defined(__RP2350__)
#if defined(ARDUINO_ARCH_RP2040) || defined(PICO_BOARD) || defined(__RP2040__)
#include "../../hardware_pwm/include/hardware/pwm.h"
#include "hardware/irq.h"
@ -105,14 +104,9 @@ void _PM_timerInit(Protomatter_core *core) {
#elif defined(CIRCUITPY) // COMPILING FOR CIRCUITPYTHON --------------------
#if !defined(F_CPU) // Not sure if CircuitPython build defines this
#ifdef __RP2040__
#if !defined(F_CPU) // Not sure if CircuitPython build defines this
#define F_CPU 125000000 // Standard RP2040 clock speed
#endif
#ifdef __RP2350__
#define F_CPU 150000000 // Standard RP2350 clock speed
#endif
#endif
// 'pin' here is GPXX #
#if __BYTE_ORDER__ == __ORDER_LITTLE_ENDIAN__
@ -246,10 +240,10 @@ uint32_t _PM_timerStop(Protomatter_core *core) {
#define _PM_clockHoldLow asm("nop; nop; nop;");
#define _PM_clockHoldHigh asm("nop; nop; nop;");
#elif (F_CPU >= 175000000)
#define _PM_clockHoldLow asm("nop; nop; nop;");
#define _PM_clockHoldLow asm("nop; nop;");
#define _PM_clockHoldHigh asm("nop;");
#elif (F_CPU >= 125000000)
#define _PM_clockHoldLow asm("nop; nop; nop;");
#define _PM_clockHoldLow asm("nop;");
#define _PM_clockHoldHigh asm("nop;");
#elif (F_CPU >= 100000000)
#define _PM_clockHoldLow asm("nop;");

View file

@ -64,7 +64,7 @@ void _PM_IRQ_HANDLER(void) {
#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))
#define _PM_portBitMask(pin) (1u << ((pin)&31))
#if __BYTE_ORDER__ == __ORDER_LITTLE_ENDIAN__
#define _PM_byteOffset(pin) ((pin & 31) / 8)

View file

@ -51,19 +51,19 @@ void _PM_timerInit(Protomatter_core *core) {
uint8_t GCM_ID; // GCLK selection ID
} timer[] = {
#if defined(TC0)
{TC0, TC0_IRQn, GCM_TCC0_TCC1},
{TC0, TC0_IRQn, GCM_TCC0_TCC1},
#endif
#if defined(TC1)
{TC1, TC1_IRQn, GCM_TCC0_TCC1},
{TC1, TC1_IRQn, GCM_TCC0_TCC1},
#endif
#if defined(TC2)
{TC2, TC2_IRQn, GCM_TCC2_TC3},
{TC2, TC2_IRQn, GCM_TCC2_TC3},
#endif
#if defined(TC3)
{TC3, TC3_IRQn, GCM_TCC2_TC3},
{TC3, TC3_IRQn, GCM_TCC2_TC3},
#endif
#if defined(TC4)
{TC4, TC4_IRQn, GCM_TC4_TC5},
{TC4, TC4_IRQn, GCM_TC4_TC5},
#endif
};
#define NUM_TIMERS (sizeof timer / sizeof timer[0])

View file

@ -47,21 +47,6 @@
#define F_CPU (120000000)
// Enable high output driver strength on one pin. Arduino does this by
// default on pinMode(OUTPUT), but CircuitPython requires the motions...
static void _hi_drive(uint8_t pin) {
// For Arduino testing only:
// pin = g_APinDescription[pin].ulPort * 32 + g_APinDescription[pin].ulPin;
// Input, pull-up and peripheral MUX are disabled as we're only using
// vanilla PORT writes on Protomatter GPIO.
PORT->Group[pin / 32].WRCONFIG.reg =
(pin & 16)
? PORT_WRCONFIG_WRPINCFG | PORT_WRCONFIG_DRVSTR |
PORT_WRCONFIG_HWSEL | (1 << (pin & 15))
: PORT_WRCONFIG_WRPINCFG | PORT_WRCONFIG_DRVSTR | (1 << (pin & 15));
}
#else
// Other port register lookups go here
@ -78,43 +63,43 @@ void _PM_timerInit(Protomatter_core *core) {
uint8_t GCLK_ID; // Peripheral channel # for clock source
} timer[] = {
#if defined(TC0)
{TC0, TC0_IRQn, TC0_GCLK_ID},
{TC0, TC0_IRQn, TC0_GCLK_ID},
#endif
#if defined(TC1)
{TC1, TC1_IRQn, TC1_GCLK_ID},
{TC1, TC1_IRQn, TC1_GCLK_ID},
#endif
#if defined(TC2)
{TC2, TC2_IRQn, TC2_GCLK_ID},
{TC2, TC2_IRQn, TC2_GCLK_ID},
#endif
#if defined(TC3)
{TC3, TC3_IRQn, TC3_GCLK_ID},
{TC3, TC3_IRQn, TC3_GCLK_ID},
#endif
#if defined(TC4)
{TC4, TC4_IRQn, TC4_GCLK_ID},
{TC4, TC4_IRQn, TC4_GCLK_ID},
#endif
#if defined(TC5)
{TC5, TC5_IRQn, TC5_GCLK_ID},
{TC5, TC5_IRQn, TC5_GCLK_ID},
#endif
#if defined(TC6)
{TC6, TC6_IRQn, TC6_GCLK_ID},
{TC6, TC6_IRQn, TC6_GCLK_ID},
#endif
#if defined(TC7)
{TC7, TC7_IRQn, TC7_GCLK_ID},
{TC7, TC7_IRQn, TC7_GCLK_ID},
#endif
#if defined(TC8)
{TC8, TC8_IRQn, TC8_GCLK_ID},
{TC8, TC8_IRQn, TC8_GCLK_ID},
#endif
#if defined(TC9)
{TC9, TC9_IRQn, TC9_GCLK_ID},
{TC9, TC9_IRQn, TC9_GCLK_ID},
#endif
#if defined(TC10)
{TC10, TC10_IRQn, TC10_GCLK_ID},
{TC10, TC10_IRQn, TC10_GCLK_ID},
#endif
#if defined(TC11)
{TC11, TC11_IRQn, TC11_GCLK_ID},
{TC11, TC11_IRQn, TC11_GCLK_ID},
#endif
#if defined(TC12)
{TC12, TC12_IRQn, TC12_GCLK_ID},
{TC12, TC12_IRQn, TC12_GCLK_ID},
#endif
};
#define NUM_TIMERS (sizeof timer / sizeof timer[0])
@ -172,20 +157,6 @@ void _PM_timerInit(Protomatter_core *core) {
NVIC_EnableIRQ(timer[timerNum].IRQn);
// Timer is configured but NOT enabled by default
#if defined(CIRCUITPY) // See notes earlier; Arduino doesn't need this.
// Enable high drive strength on all Protomatter pins. TBH this is kind
// of a jerky place to do this (it's not actually related to the timer
// peripheral) but Protomatter doesn't really have a spot for it.
uint8_t i;
for (i = 0; i < core->parallel * 6; i++)
_hi_drive(core->rgbPins[i]);
for (i = 0; i < core->numAddressLines; i++)
_hi_drive(core->addr[i].pin);
_hi_drive(core->clockPin);
_hi_drive(core->latch.pin);
_hi_drive(core->oe.pin);
#endif
}
// Set timer period, initialize count value to zero, enable timer.
@ -225,204 +196,21 @@ uint32_t _PM_timerStop(Protomatter_core *core) {
return count;
}
// SAMD51 takes a WEIRD TURN here, in an attempt to make the HUB75 clock
// waveform slightly adjustable. Old vs new matrices seem to have different
// preferences, and this tries to address that. If this works well then the
// approach might be applied to other architectures (which are all fixed
// duty cycle right now). THE CHALLENGE is that Protomatter works in a bit-
// bangingly way (this is on purpose and by design, avoiding peripherals
// that might work only on certain pins, for better compatibility with
// existing shields and wings from the AVR era), we're aiming for nearly a
// 20 MHz signal, and the SAMD51 cycle clock is ostensibly 120 MHz. With
// just a few cycles to toggle the data and clock lines, that doesn't even
// leave enough time for a counter loop.
#define _PM_CUSTOM_BLAST ///< Disable blast_*() functions in core.c
#define _PM_chunkSize 8 ///< Data-stuffing loop is unrolled to this size
extern uint8_t _PM_duty; // In core.c
// The approach is to use a small list of pointers, with a clock-toggling
// value written to each one in succession. Most of the pointers are aimed
// on a nonsense "bit bucket" variable, effectively becoming NOPs, and just
// one is set to the PORT toggle register, raising the clock. A couple of
// actual traditional NOPs are also present because concurrent PORT writes
// on SAMD51 incur a 1-cycle delay, so the NOPs keep the clock frequency
// constant (tradeoff is that the clock is now 7 rather than 6 cycles --
// ~17.1 MHz rather than 20 with F_CPU at 120 MHz). The NOPs could be
// removed and duty range increased by one, but the tradeoff then is
// inconsistent timing at different duty settings. That 1-cycle delay is
// also why this uses a list of pointers with a common value, rather than
// a common pointer (the PORT reg) with a list of values -- because those
// writes would all take 2 cycles, way too slow. A counter loop would also
// take 2 cycles/count, because of the branch.
#if F_CPU >= 200000000 // 200 MHz; 10 cycles/bit; 20 MHz, 6 duty settings
#define _PM_maxDuty 5 ///< Allow duty settings 0-5
#define _PM_defaultDuty 2 ///< ~60%
#define PEW \
asm("nop"); \
*toggle = *data++; \
asm("nop"); \
*ptr0 = clock; \
*ptr1 = clock; \
*ptr2 = clock; \
*ptr3 = clock; \
*ptr4 = clock; \
*ptr5 = clock;
#elif F_CPU >= 180000000 // 180 MHz; 9 cycles/bit; 20 MHz, 5 duty settings
#define _PM_maxDuty 4 ///< Allow duty settings 0-4
#define _PM_defaultDuty 1 ///< ~50%
#define PEW \
asm("nop"); \
*toggle = *data++; \
asm("nop"); \
*ptr0 = clock; \
*ptr1 = clock; \
*ptr2 = clock; \
*ptr3 = clock; \
*ptr4 = clock;
#elif F_CPU >= 150000000 // 150 MHz; 8 cycles/bit; 18.75 MHz, 4 duty settings
#define _PM_maxDuty 3 ///< Allow duty settings 0-3
#define _PM_defaultDuty 1 ///< ~55%
#define PEW \
asm("nop"); \
*toggle = *data++; \
asm("nop"); \
*ptr0 = clock; \
*ptr1 = clock; \
*ptr2 = clock; \
*ptr3 = clock;
#else // 120 MHz; 7 cycles/bit; 17.1 MHz, 3 duty settings
#define _PM_maxDuty 2 ///< Allow duty settings 0-2
#define _PM_defaultDuty 0 ///< ~50%
#define PEW \
asm("nop"); \
*toggle = *data++; \
asm("nop"); \
*ptr0 = clock; \
*ptr1 = clock; \
*ptr2 = clock;
// 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
static void blast_byte(Protomatter_core *core, uint8_t *data) {
// If here, it was established in begin() that the RGB data bits and
// clock are all within the same byte of a PORT register, else we'd be
// in the word- or long-blasting functions now. So we just need an
// 8-bit pointer to the PORT:
volatile uint8_t *toggle =
(volatile uint8_t *)core->toggleReg + core->portOffset;
uint8_t bucket, clock = core->clockMask;
// Pointer list must be distinct vars, not an array, else slow.
volatile uint8_t *ptr0 =
(_PM_duty == _PM_maxDuty) ? toggle : (volatile uint8_t *)&bucket;
volatile uint8_t *ptr1 =
(_PM_duty == (_PM_maxDuty - 1)) ? toggle : (volatile uint8_t *)&bucket;
volatile uint8_t *ptr2 =
(_PM_duty == (_PM_maxDuty - 2)) ? toggle : (volatile uint8_t *)&bucket;
#if _PM_maxDuty >= 3
volatile uint8_t *ptr3 =
(_PM_duty == (_PM_maxDuty - 3)) ? toggle : (volatile uint8_t *)&bucket;
#endif
#if _PM_maxDuty >= 4
volatile uint8_t *ptr4 =
(_PM_duty == (_PM_maxDuty - 4)) ? toggle : (volatile uint8_t *)&bucket;
#endif
#if _PM_maxDuty >= 5
volatile uint8_t *ptr5 =
(_PM_duty == (_PM_maxDuty - 5)) ? toggle : (volatile uint8_t *)&bucket;
#endif
uint16_t chunks = core->chainBits / 8;
// PORT has already been initialized with RGB data + clock bits
// all LOW, so we don't need to initialize that state here.
do {
PEW PEW PEW PEW PEW PEW PEW PEW
} while (--chunks);
// Want the PORT left with RGB data and clock LOW on function exit
// (so it's easier to see on 'scope, and to prime it for the next call).
// This is implicit in the no-toggle case (due to how the PEW macro
// works), but toggle case requires explicitly clearing those bits.
// rgbAndClockMask is an 8-bit value when toggling, hence offset here.
*((volatile uint8_t *)core->clearReg + core->portOffset) =
core->rgbAndClockMask;
}
// This is a copypasta of blast_byte() with types changed to uint16_t.
static void blast_word(Protomatter_core *core, uint16_t *data) {
volatile uint16_t *toggle = (uint16_t *)core->toggleReg + core->portOffset;
uint16_t bucket, clock = core->clockMask;
volatile uint16_t *ptr0 =
(_PM_duty == _PM_maxDuty) ? toggle : (volatile uint16_t *)&bucket;
volatile uint16_t *ptr1 =
(_PM_duty == (_PM_maxDuty - 1)) ? toggle : (volatile uint16_t *)&bucket;
volatile uint16_t *ptr2 =
(_PM_duty == (_PM_maxDuty - 2)) ? toggle : (volatile uint16_t *)&bucket;
#if _PM_maxDuty >= 3
volatile uint16_t *ptr3 =
(_PM_duty == (_PM_maxDuty - 3)) ? toggle : (volatile uint16_t *)&bucket;
#endif
#if _PM_maxDuty >= 4
volatile uint16_t *ptr4 =
(_PM_duty == (_PM_maxDuty - 4)) ? toggle : (volatile uint16_t *)&bucket;
#endif
#if _PM_maxDuty >= 5
volatile uint16_t *ptr5 =
(_PM_duty == (_PM_maxDuty - 5)) ? toggle : (volatile uint16_t *)&bucket;
#endif
uint16_t chunks = core->chainBits / 8;
do {
PEW PEW PEW PEW PEW PEW PEW PEW
} while (--chunks);
*((volatile uint16_t *)core->clearReg + core->portOffset) =
core->rgbAndClockMask;
}
// This is a copypasta of blast_byte() with types changed to uint32_t.
static void blast_long(Protomatter_core *core, uint32_t *data) {
volatile uint32_t *toggle = (uint32_t *)core->toggleReg;
uint32_t bucket, clock = core->clockMask;
volatile uint32_t *ptr0 =
(_PM_duty == _PM_maxDuty) ? toggle : (volatile uint32_t *)&bucket;
volatile uint32_t *ptr1 =
(_PM_duty == (_PM_maxDuty - 1)) ? toggle : (volatile uint32_t *)&bucket;
volatile uint32_t *ptr2 =
(_PM_duty == (_PM_maxDuty - 2)) ? toggle : (volatile uint32_t *)&bucket;
#if _PM_maxDuty >= 3
volatile uint32_t *ptr3 =
(_PM_duty == (_PM_maxDuty - 3)) ? toggle : (volatile uint32_t *)&bucket;
#endif
#if _PM_maxDuty >= 4
volatile uint32_t *ptr4 =
(_PM_duty == (_PM_maxDuty - 4)) ? toggle : (volatile uint32_t *)&bucket;
#endif
#if _PM_maxDuty >= 5
volatile uint32_t *ptr5 =
(_PM_duty == (_PM_maxDuty - 5)) ? toggle : (volatile uint32_t *)&bucket;
#endif
uint16_t chunks = core->chainBits / 8;
do {
PEW PEW PEW PEW PEW PEW PEW PEW
} while (--chunks);
*((volatile uint32_t *)core->clearReg + core->portOffset) =
core->rgbAndClockMask;
}
#define _PM_minMinPeriod 160
#endif // END __SAMD51__ || SAM_D5X_E5X

View file

@ -28,7 +28,7 @@
#include "timers.h"
#undef _PM_portBitMask
#define _PM_portBitMask(pin) (1u << ((pin) & 15))
#define _PM_portBitMask(pin) (1u << ((pin)&15))
#define _PM_byteOffset(pin) ((pin & 15) / 8)
#define _PM_wordOffset(pin) ((pin & 15) / 16)
@ -83,7 +83,7 @@ volatile uint16_t *_PM_portClearRegister(uint32_t pin) {
// TODO: this is no longer true, should it change?
void *_PM_protoPtr = NULL;
static TIM_HandleTypeDef tim_handle;
STATIC TIM_HandleTypeDef tim_handle;
// Timer interrupt service routine
void _PM_IRQ_HANDLER(void) {

View file

@ -95,13 +95,12 @@ ProtomatterStatus _PM_init(Protomatter_core *core, uint16_t bitWidth,
uint8_t addrCount, uint8_t *addrList,
uint8_t clockPin, uint8_t latchPin, uint8_t oePin,
bool doubleBuffer, int8_t tile, void *timer) {
if (!core) {
if (!core)
return PROTOMATTER_ERR_ARG;
}
// bitDepth is NOT constrained here, handle in calling function
// (varies with implementation, e.g. GFX lib is max 6 bitplanes,
// but might be more or less elsewhere)
// bitDepth is NOT constrained here, handle in calling function
// (varies with implementation, e.g. GFX lib is max 6 bitplanes,
// but might be more or less elsewhere)
#if defined(_PM_bytesPerElement)
#if _PM_bytesPerElement == 1
if (rgbCount > 1)
@ -727,7 +726,7 @@ IRAM_ATTR static void blast_byte(Protomatter_core *core, uint8_t *data) {
_PM_PORT_TYPE rgbclock = core->rgbAndClockMask; // RGB + clock bit
#endif
_PM_PORT_TYPE clock = core->clockMask; // Clock bit
uint16_t chunks = (core->chainBits + (_PM_chunkSize - 1)) / _PM_chunkSize;
uint8_t chunks = (core->chainBits + (_PM_chunkSize - 1)) / _PM_chunkSize;
// PORT has already been initialized with RGB data + clock bits
// all LOW, so we don't need to initialize that state here.
@ -781,9 +780,9 @@ IRAM_ATTR static void blast_word(Protomatter_core *core, uint16_t *data) {
volatile uint16_t *toggle =
(volatile uint16_t *)core->toggleReg + core->portOffset;
#else
volatile uint16_t *set; // For RGB data set
volatile _PM_PORT_TYPE *set_full; // For clock set
volatile _PM_PORT_TYPE *clear_full; // For RGB data + clock clear
volatile uint16_t *set; // For RGB data set
volatile _PM_PORT_TYPE *set_full; // For clock set
volatile _PM_PORT_TYPE *clear_full; // For RGB data + clock clear
set = (volatile uint16_t *)core->setReg + core->portOffset;
set_full = (volatile _PM_PORT_TYPE *)core->setReg;
clear_full = (volatile _PM_PORT_TYPE *)core->clearReg;
@ -830,7 +829,7 @@ IRAM_ATTR static void blast_long(Protomatter_core *core, uint32_t *data) {
// Note in this case two copies exist of the PORT set register.
// The optimizer will most likely simplify this; leaving as-is, not
// wanting a special case of the PEW macro due to divergence risk.
volatile uint32_t *set; // For RGB data set
volatile uint32_t *set; // For RGB data set
#if !defined(_PM_STRICT_32BIT_IO)
volatile _PM_PORT_TYPE *set_full; // For clock set
set_full = (volatile _PM_PORT_TYPE *)core->setReg;
@ -923,10 +922,6 @@ static void _PM_resetFM6126A(Protomatter_core *core) {
_PM_rgbState(core, 0); // Set all RGB low so port toggle can work
}
uint8_t _PM_duty = _PM_defaultDuty;
void _PM_setDuty(uint8_t d) { _PM_duty = (d > _PM_maxDuty) ? _PM_maxDuty : d; }
#if defined(ARDUINO) || defined(CIRCUITPY)
// Arduino and CircuitPython happen to use the same internal canvas
@ -961,7 +956,7 @@ __attribute__((noinline)) void _PM_convert_565_byte(Protomatter_core *core,
dest += core->bufferSize * (1 - core->activeBuffer);
}
// #if defined(_PM_portToggleRegister)
//#if defined(_PM_portToggleRegister)
#if defined(_PM_USE_TOGGLE_FORMAT)
#if !defined(_PM_STRICT_32BIT_IO)
// core->clockMask mask is already an 8-bit value
@ -1021,7 +1016,7 @@ __attribute__((noinline)) void _PM_convert_565_byte(Protomatter_core *core,
uint32_t greenBit = initialGreenBit;
uint32_t blueBit = initialBlueBit;
for (uint8_t plane = 0; plane < core->numPlanes; plane++) {
// #if defined(_PM_portToggleRegister)
//#if defined(_PM_portToggleRegister)
#if defined(_PM_USE_TOGGLE_FORMAT)
uint8_t prior = clockMask; // Set clock bit on 1st out
#endif
@ -1067,7 +1062,7 @@ __attribute__((noinline)) void _PM_convert_565_byte(Protomatter_core *core,
if (lowerRGB & blueBit)
result |= pinMask[5];
// THIS is where toggle format (without toggle reg.) messes up
// #if defined(_PM_portToggleRegister)
//#if defined(_PM_portToggleRegister)
#if defined(_PM_USE_TOGGLE_FORMAT)
*d2++ = result ^ prior;
prior = result | clockMask; // Set clock bit on next out
@ -1075,7 +1070,7 @@ __attribute__((noinline)) void _PM_convert_565_byte(Protomatter_core *core,
*d2++ = result;
#endif
} // end x
} // end tile
} // end tile
greenBit <<= 1;
if (plane || (core->numPlanes < 6)) {
@ -1089,7 +1084,7 @@ __attribute__((noinline)) void _PM_convert_565_byte(Protomatter_core *core,
redBit = 0b0000100000000000;
blueBit = 0b0000000000000001;
}
// #if defined(_PM_portToggleRegister)
//#if defined(_PM_portToggleRegister)
#if defined(_PM_USE_TOGGLE_FORMAT)
// If using bit-toggle register, erase the toggle bit on the
// first element of each bitplane & row pair. The matrix-driving
@ -1101,8 +1096,8 @@ __attribute__((noinline)) void _PM_convert_565_byte(Protomatter_core *core,
dest[-pad] &= ~clockMask; // Negative index is legal & intentional
#endif
dest += bitplaneSize; // Advance one scanline in dest buffer
} // end plane
} // end row
} // end plane
} // end row
}
// Corresponding function for word output -- either 12 RGB bits (2 parallel
@ -1140,7 +1135,7 @@ void _PM_convert_565_word(Protomatter_core *core, uint16_t *source,
// register exists, "clear" really means the clock mask is set in all
// but the first element on a scanline (per bitplane). If no toggle
// register, can just zero everything out.
// #if defined(_PM_portToggleRegister)
//#if defined(_PM_portToggleRegister)
#if defined(_PM_USE_TOGGLE_FORMAT)
// No per-chain loop is required; one clock bit handles all chains
uint32_t offset = 0; // Current position in the 'dest' buffer
@ -1165,7 +1160,7 @@ void _PM_convert_565_word(Protomatter_core *core, uint16_t *source,
uint32_t greenBit = initialGreenBit;
uint32_t blueBit = initialBlueBit;
for (uint8_t plane = 0; plane < core->numPlanes; plane++) {
// #if defined(_PM_portToggleRegister)
//#if defined(_PM_portToggleRegister)
#if defined(_PM_USE_TOGGLE_FORMAT)
// Since we're ORing in bits over an existing clock bit,
// prior is 0 rather than clockMask as in the byte case.
@ -1200,27 +1195,21 @@ void _PM_convert_565_word(Protomatter_core *core, uint16_t *source,
uint16_t upperRGB = upperSrc[srcIdx]; // Pixel in upper half
uint16_t lowerRGB = lowerSrc[srcIdx]; // Pixel in lower half
uint16_t result = 0;
if (upperRGB & redBit) {
if (upperRGB & redBit)
result |= pinMask[0];
}
if (upperRGB & greenBit) {
if (upperRGB & greenBit)
result |= pinMask[1];
}
if (upperRGB & blueBit) {
if (upperRGB & blueBit)
result |= pinMask[2];
}
if (lowerRGB & redBit) {
if (lowerRGB & redBit)
result |= pinMask[3];
}
if (lowerRGB & greenBit) {
if (lowerRGB & greenBit)
result |= pinMask[4];
}
if (lowerRGB & blueBit) {
if (lowerRGB & blueBit)
result |= pinMask[5];
}
// Main difference here vs byte converter is each chain
// ORs new bits into place (vs single-pass overwrite).
// #if defined(_PM_portToggleRegister)
// Main difference here vs byte converter is each chain
// ORs new bits into place (vs single-pass overwrite).
//#if defined(_PM_portToggleRegister)
#if defined(_PM_USE_TOGGLE_FORMAT)
*d2++ |= result ^ prior; // Bitwise OR
prior = result;
@ -1228,7 +1217,7 @@ void _PM_convert_565_word(Protomatter_core *core, uint16_t *source,
*d2++ |= result; // Bitwise OR
#endif
} // end x
} // end tile
} // end tile
greenBit <<= 1;
if (plane || (core->numPlanes < 6)) {
redBit <<= 1;
@ -1238,9 +1227,9 @@ void _PM_convert_565_word(Protomatter_core *core, uint16_t *source,
blueBit = 0b0000000000000001;
}
dest += bitplaneSize; // Advance one scanline in dest buffer
} // end plane
} // end row
pinMask += 6; // Next chain's RGB pin masks
} // end plane
} // end row
pinMask += 6; // Next chain's RGB pin masks
}
}
@ -1273,7 +1262,7 @@ void _PM_convert_565_long(Protomatter_core *core, uint16_t *source,
initialBlueBit = 0b0000000000000001 << shiftLeft;
}
// #if defined(_PM_portToggleRegister)
//#if defined(_PM_portToggleRegister)
#if defined(_PM_USE_TOGGLE_FORMAT)
// No per-chain loop is required; one clock bit handles all chains
uint32_t offset = 0; // Current position in the 'dest' buffer
@ -1297,7 +1286,7 @@ void _PM_convert_565_long(Protomatter_core *core, uint16_t *source,
uint32_t greenBit = initialGreenBit;
uint32_t blueBit = initialBlueBit;
for (uint8_t plane = 0; plane < core->numPlanes; plane++) {
// #if defined(_PM_portToggleRegister)
//#if defined(_PM_portToggleRegister)
#if defined(_PM_USE_TOGGLE_FORMAT)
uint32_t prior = 0;
#endif
@ -1330,27 +1319,21 @@ void _PM_convert_565_long(Protomatter_core *core, uint16_t *source,
uint16_t upperRGB = upperSrc[srcIdx]; // Pixel in upper half
uint16_t lowerRGB = lowerSrc[srcIdx]; // Pixel in lower half
uint32_t result = 0;
if (upperRGB & redBit) {
if (upperRGB & redBit)
result |= pinMask[0];
}
if (upperRGB & greenBit) {
if (upperRGB & greenBit)
result |= pinMask[1];
}
if (upperRGB & blueBit) {
if (upperRGB & blueBit)
result |= pinMask[2];
}
if (lowerRGB & redBit) {
if (lowerRGB & redBit)
result |= pinMask[3];
}
if (lowerRGB & greenBit) {
if (lowerRGB & greenBit)
result |= pinMask[4];
}
if (lowerRGB & blueBit) {
if (lowerRGB & blueBit)
result |= pinMask[5];
}
// Main difference here vs byte converter is each chain
// ORs new bits into place (vs single-pass overwrite).
// #if defined(_PM_portToggleRegister)
// Main difference here vs byte converter is each chain
// ORs new bits into place (vs single-pass overwrite).
//#if defined(_PM_portToggleRegister)
#if defined(_PM_USE_TOGGLE_FORMAT)
*d2++ |= result ^ prior; // Bitwise OR
prior = result;
@ -1358,7 +1341,7 @@ void _PM_convert_565_long(Protomatter_core *core, uint16_t *source,
*d2++ |= result; // Bitwise OR
#endif
} // end x
} // end tile
} // end tile
greenBit <<= 1;
if (plane || (core->numPlanes < 6)) {
redBit <<= 1;
@ -1368,9 +1351,9 @@ void _PM_convert_565_long(Protomatter_core *core, uint16_t *source,
blueBit = 0b0000000000000001;
}
dest += bitplaneSize; // Advance one scanline in dest buffer
} // end plane
} // end row
pinMask += 6; // Next chain's RGB pin masks
} // end plane
} // end row
pinMask += 6; // Next chain's RGB pin masks
}
}

View file

@ -255,16 +255,6 @@ extern uint32_t _PM_timerGetCount(Protomatter_core *core);
*/
extern void _PM_swapbuffer_maybe(Protomatter_core *core);
/*!
@brief Adjust duty cycle of HUB75 clock signal. This is not supported on
all architectures.
@param d Duty setting, 0 minimum. Increasing values generate higher clock
duty cycles at the same frequency. Arbitrary granular units, max
varies by architecture and CPU speed, if supported at all.
e.g. SAMD51 @ 120 MHz supports 0 (~50% duty) through 2 (~75%).
*/
extern void _PM_setDuty(uint8_t d);
#if defined(ARDUINO) || defined(CIRCUITPY)
/*!