Compare commits

..

10 commits

Author SHA1 Message Date
Phillip Burgess
32407dd631 Overhaul examples - more comments etc., rename "protomatter" example to "simple" 2020-10-19 13:24:57 -07:00
Phillip Burgess
316af7345d Copious notes on gamma handling, as things might change big soon 2020-10-14 10:50:31 -07:00
Phillip Burgess
a53a33ef1a Comment fix 2020-10-13 20:34:57 -07:00
Phillip Burgess
4869e64850 Tweak timer interrupt, more efficient? 2020-10-13 20:17:28 -07:00
Phillip Burgess
1615dc4c1d SAMD51 brightness working a little better 2020-10-13 18:58:52 -07:00
Phillip Burgess
6f312c642f Brightness starting to semi-work-ish on SAMD51 2020-10-13 17:08:48 -07:00
Phillip Burgess
c9ecf7c735 More SAMD juggling things around 2020-10-13 11:46:27 -07:00
Phillip Burgess
0a75527d30 Overflow interrupt setup fix 2020-10-12 16:54:12 -07:00
Phillip Burgess
a413580aa2 Gamma shenanigans underway 2020-10-12 15:44:28 -07:00
Phillip Burgess
da1c4fcc68 Starting to bring in bits of the gamma code 2020-10-11 20:02:05 -07:00
28 changed files with 1028 additions and 2683 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
python-version: '3.x'
- uses: actions/checkout@v2
- uses: actions/checkout@v2
with:
repository: adafruit/ci-arduino
path: ci

View file

@ -64,9 +64,9 @@ This repository currently consists of:
# Arduino Library
This supersedes the RGBmatrixPanel library on non-AVR devices, as the older
library has painted itself into a few corners. The newer library uses a
single constructor for all matrix setups, potentially handling parallel
This will likely supersede the RGBmatrixPanel library on non-AVR devices, as
the older library has painted itself into a few corners. The newer library
uses a single constructor for all matrix setups, potentially handling parallel
chains (not yet fully implemented), various matrix sizes and chain lengths,
and variable bit depths from 1 to 6 (refresh rate is a function of all of
these). Note however that it is NOT A DROP-IN REPLACEMENT for RGBmatrixPanel.
@ -77,13 +77,13 @@ aimed at low-cost color LCD displays), even if the matrix is configured for
a lower bit depth (colors will be decimated/quantized in this case).
It does have some new limitations, mostly significant RAM overhead (hence
no plans for AVR port) and (with a few exceptions) that all RGB data pins
and the clock pin MUST be on the same PORT register (e.g. all PORTA or PORTB
,can't intermix). RAM overhead is somewhat reduced (but still large) if
those pins are all in a single 8-bit byte within the PORT (they do not need
to be contiguous or sequential within this byte, if for instance it makes
PCB routing easier, but they should all aim for a single byte). Other pins
(matrix address lines, latch and output enable) can reside on any PORT or bit.
no plans for AVR port) and that all RGB data pins and the clock pin MUST be
on the same PORT register (e.g. all PORTA or PORTB, can't intermix). RAM
overhead is somewhat reduced (but still large) if those pins are all in a
single 8-bit byte within the PORT (they do not need to be contiguous or
sequential within this byte, if for instance it makes PCB routing easier,
but they should all aim for a single byte). Other pins (matrix address lines,
latch and output enable) can reside on any PORT or bit.
# C Library
@ -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,8 +1,9 @@
// 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
// definitions. See the "simple" example for a run-down on matrix config.
// ***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 addrPins[] array
// (height) and/or matrix constructor call (width). See the "simple" example
// for a run-down on matrix configuration.
// Adapted from examples from Larry Bank's AnimatedGIF library and
// msc_external_flash example in Adafruit_TinyUSB_Arduino.
// Prerequisite libraries:
@ -25,17 +26,11 @@
char GIFpath[] = "/gifs"; // Absolute path to GIFs on CIRCUITPY drive
uint16_t GIFminimumTime = 10; // Min. repeat time (seconds) until next GIF
#define WIDTH 64 // Matrix width in pixels
#define HEIGHT 32 // Matrix height in pixels
// Maximim matrix height is 32px on most boards, 64 on MatrixPortal if the
// 'E' jumper is set.
// 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,
@ -52,42 +47,28 @@ Adafruit_USBD_MSC usb_msc; // USB mass storage object
#if defined(_VARIANT_MATRIXPORTAL_M4_)
uint8_t rgbPins[] = {7, 8, 9, 10, 11, 12};
uint8_t addrPins[] = {17, 18, 19, 20, 21}; // 16/32/64 pixels tall
uint8_t addrPins[] = {17, 18, 19, 20};
uint8_t clockPin = 14;
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
uint8_t addrPins[] = {A0, A1, A2, A3};
uint8_t clockPin = A4;
uint8_t latchPin = 10;
uint8_t oePin = 9;
#elif defined(_VARIANT_FEATHER_M4_)
uint8_t rgbPins[] = {6, 5, 9, 11, 10, 12};
uint8_t addrPins[] = {A5, A4, A3, A2}; // 16 or 32 pixels tall
uint8_t addrPins[] = {A5, A4, A3, A2};
uint8_t clockPin = 13;
uint8_t latchPin = 0;
uint8_t oePin = 1;
#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, 6, 1, rgbPins, NUM_ADDR_PINS, addrPins,
// Matrix width is first arg here, height is inferred from addrPins[]
Adafruit_Protomatter matrix(64, 6, 1, rgbPins, sizeof addrPins, addrPins,
clockPin, latchPin, oePin, true);
// ANIMATEDGIF LIBRARY STUFF -----------------------------------------------
@ -99,7 +80,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();
@ -352,6 +333,7 @@ void loop() {
GIFisOpen = false;
}
GIFindex += GIFincrement; // Fwd or back 1 file
GIFincrement = 0; // Reset increment flag
int num_files = numFiles(GIFpath, "GIF");
if(GIFindex >= num_files) GIFindex = 0; // 'Wrap around' file index
else if(GIFindex < 0) GIFindex = num_files - 1; // both directions
@ -370,17 +352,15 @@ void loop() {
yPos = (matrix.height() - GIF.getCanvasHeight()) / 2;
GIFisOpen = true;
GIFstartTime = millis();
GIFincrement = 0; // Reset increment flag
}
}
} else if(GIFisOpen) {
if (GIF.playFrame(true, NULL) >= 0) { // Auto resets to start if needed
if (GIF.playFrame(true, NULL)) {
matrix.show();
if ((millis() - GIFstartTime) >= (GIFminimumTime * 1000)) {
GIFincrement = 1; // Minimum time has elapsed, proceed to next GIF
}
} else if ((millis() - GIFstartTime) < (GIFminimumTime * 1000)) {
GIF.reset(); // Minimum time hasn't elapsed yet, repeat this GIF
} else {
GIFincrement = 1; // Decode error, proceed to next GIF
GIFincrement = 1; // Minimum time has elapsed, proceed to next GIF
}
}
}

View file

@ -17,16 +17,10 @@ supported boards.
#if defined(_VARIANT_MATRIXPORTAL_M4_) // MatrixPortal M4
uint8_t rgbPins[] = {7, 8, 9, 10, 11, 12};
uint8_t addrPins[] = {17, 18, 19, 20, 21};
uint8_t addrPins[] = {17, 18, 19, 20};
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};
@ -51,20 +45,6 @@ supported boards.
uint8_t clockPin = 12;
uint8_t latchPin = PIN_SERIAL1_RX;
uint8_t oePin = PIN_SERIAL1_TX;
#elif USB_VID == 0x239A && USB_PID == 0x8113 // Feather ESP32-S3 No PSRAM
// M0/M4/RP2040 Matrix FeatherWing compatible:
uint8_t rgbPins[] = {6, 5, 9, 11, 10, 12};
uint8_t addrPins[] = {A5, A4, A3, A2};
uint8_t clockPin = 13; // Must be on same port as rgbPins
uint8_t latchPin = RX;
uint8_t oePin = TX;
#elif USB_VID == 0x239A && USB_PID == 0x80EB // Feather ESP32-S2
// M0/M4/RP2040 Matrix FeatherWing compatible:
uint8_t rgbPins[] = {6, 5, 9, 11, 10, 12};
uint8_t addrPins[] = {A5, A4, A3, A2};
uint8_t clockPin = 13; // Must be on same port as rgbPins
uint8_t latchPin = RX;
uint8_t oePin = TX;
#elif defined(ESP32)
// 'Safe' pins, not overlapping any peripherals:
// GPIO.out: 4, 12, 13, 14, 15, 21, 27, GPIO.out1: 32, 33
@ -90,18 +70,6 @@ supported boards.
uint8_t clockPin = 23; // A9
uint8_t latchPin = 6;
uint8_t oePin = 9;
#elif defined(ARDUINO_ADAFRUIT_FEATHER_RP2040)
// RP2040 support requires the Earle Philhower board support package;
// will not compile with the Arduino Mbed OS board package.
// The following pinout works with the Adafruit Feather RP2040 and
// original RGB Matrix FeatherWing (M0/M4/RP2040, not nRF version).
// Pin numbers here are GP## numbers, which may be different than
// the pins printed on some boards' top silkscreen.
uint8_t rgbPins[] = {8, 7, 9, 11, 10, 12};
uint8_t addrPins[] = {25, 24, 29, 28};
uint8_t clockPin = 13;
uint8_t latchPin = 1;
uint8_t oePin = 0;
#endif
/* ----------------------------------------------------------------------
@ -117,20 +85,21 @@ Adafruit_Protomatter matrix(
1, rgbPins, // # of matrix chains, array of 6 RGB pins for each
4, addrPins, // # of address pins (height is inferred), array of pins
clockPin, latchPin, oePin, // Other matrix control pins
true); // HERE IS THE MAGIC FOR DOUBLE-BUFFERING!
true); // HERE IS THE MAGIG FOR DOUBLE-BUFFERING!
// 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,17 +128,12 @@ 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),
// AND it must be BEFORE the getTextBounds() call (or else that will
// return the bounds of "wrapped" text).
// 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[0] = color565(0, 20, 0); // Dark green
ballcolor[1] = color565(0, 0, 20); // Dark blue
ballcolor[2] = color565(20, 0, 0); // ark red
}
// LOOP - RUNS REPEATEDLY AFTER SETUP --------------------------------------
@ -210,3 +174,13 @@ void loop(void) {
delay(20); // 20 milliseconds = ~50 frames/second
}
// Utility function converts 24-bit color (8 bits red, green, blue) used in
// a lot of existing graphics code down to the "565" color format used by
// Adafruit_GFX. Might get further quantized by matrix if using less than
// 6-bit depth.
uint16_t color565(uint8_t red, uint8_t green, uint8_t blue) {
return ((red & 0b11111000) << 8) |
((green & 0b11111100) << 3) |
( blue >> 3);
}

View file

@ -1,55 +1,39 @@
/* ----------------------------------------------------------------------
"Pixel dust" Protomatter library example. As written, this is
SPECIFICALLY FOR THE ADAFRUIT MATRIXPORTAL with 64x32 pixel matrix.
Change "HEIGHT" below for 64x64 matrix. Could also be adapted to other
Protomatter-capable boards with an attached LIS3DH accelerometer.
SPECIFICALLY FOR THE ADAFRUIT MATRIXPORTAL M4 with 64x32 pixel matrix.
(see "pixeldust_64x64" example for a 64x64 matrix), but could be adapted
to other Protomatter-capable boards with an attached LIS3DH accelerometer.
PLEASE SEE THE "simple" EXAMPLE FOR AN INTRODUCTORY SKETCH,
or "doublebuffer" for animation basics.
------------------------------------------------------------------------- */
#include <Wire.h> // For I2C communication
#include <Adafruit_LIS3DH.h> // For accelerometer
#include <Adafruit_PixelDust.h> // For sand simulation
#include <Adafruit_Protomatter.h> // For RGB matrix
#include <Wire.h> // For I2C communication
#include <Adafruit_LIS3DH.h>
#include <Adafruit_PixelDust.h> // For simulation
#include <Adafruit_Protomatter.h>
#define HEIGHT 32 // Matrix height (pixels) - SET TO 64 FOR 64x64 MATRIX!
#define WIDTH 64 // Matrix width (pixels)
#define WIDTH 64 // Display width in pixels
#define HEIGHT 32 // Display height in 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};
uint8_t addrPins[] = {17, 18, 19, 20, 21};
uint8_t addrPins[] = {17, 18, 19, 20};
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,
clockPin, latchPin, oePin, true);
Adafruit_LIS3DH accel = Adafruit_LIS3DH();
Adafruit_Protomatter matrix(
WIDTH, 4, 1, rgbPins, 4, addrPins, clockPin, latchPin, oePin, true);
#define N_COLORS 8
#define BOX_HEIGHT 8
#define N_GRAINS (BOX_HEIGHT*N_COLORS*8)
uint16_t colors[N_COLORS];
// Sand object, last 2 args are accelerometer scaling and grain elasticity
Adafruit_PixelDust sand(WIDTH, HEIGHT, N_GRAINS, 1, 128, false);
uint32_t prevTime = 0; // Used for frames-per-second throttle
@ -66,18 +50,19 @@ void err(int x) {
}
void setup(void) {
uint8_t i, j, bytes;
Serial.begin(115200);
//while (!Serial) delay(10);
ProtomatterStatus status = matrix.begin();
Serial.printf("Protomatter begin() status: %d\n", status);
if (!sand.begin()) {
if (!sand.begin()) {
Serial.println("Couldn't start sand");
err(1000); // Slow blink = malloc error
}
if (!accel.begin(0x19)) {
if (!accel.begin(0x19)) {
Serial.println("Couldn't find accelerometer");
err(250); // Fast bink = I2C error
}
@ -99,14 +84,18 @@ void setup(void) {
}
Serial.printf("%d total pixels\n", n);
colors[0] = matrix.color565(64, 64, 64); // Dark Gray
colors[1] = matrix.color565(120, 79, 23); // Brown
colors[2] = matrix.color565(228, 3, 3); // Red
colors[3] = matrix.color565(255,140, 0); // Orange
colors[4] = matrix.color565(255,237, 0); // Yellow
colors[5] = matrix.color565( 0,128, 38); // Green
colors[6] = matrix.color565( 0, 77,255); // Blue
colors[7] = matrix.color565(117, 7,135); // Purple
colors[0] = color565(64, 64, 64); // Dark Gray
colors[1] = color565(120, 79, 23); // Brown
colors[2] = color565(228, 3, 3); // Red
colors[3] = color565(255,140, 0); // Orange
colors[4] = color565(255,237, 0); // Yellow
colors[5] = color565( 0,128, 38); // Green
colors[6] = color565( 0, 77,255); // Blue
colors[7] = color565(117, 7,135); // Purple
}
uint16_t color565(uint8_t red, uint8_t green, uint8_t blue) {
return ((red & 0xF8) << 8) | ((green & 0xFC) << 3) | (blue >> 3);
}
// MAIN LOOP - RUNS ONCE PER FRAME OF ANIMATION ----------------------------

View file

@ -0,0 +1,138 @@
/* ----------------------------------------------------------------------
"Pixel dust" Protomatter library example. As written, this is
SPECIFICALLY FOR THE ADAFRUIT MATRIXPORTAL M4 with 64x32 pixel matrix.
(see "pixeldust_64x64" example for a 64x64 matrix), but could be adapted
to other Protomatter-capable boards with an attached LIS3DH accelerometer.
PLEASE SEE THE "simple" EXAMPLE FOR AN INTRODUCTORY SKETCH,
or "doublebuffer" for animation basics.
------------------------------------------------------------------------- */
#include <Wire.h> // For I2C communication
#include <Adafruit_LIS3DH.h>
#include <Adafruit_PixelDust.h> // For simulation
#include <Adafruit_Protomatter.h>
#define WIDTH 64 // Display width in pixels
#define HEIGHT 64 // Display height in pixels
#define MAX_FPS 45 // Maximum redraw rate, frames/second
uint8_t rgbPins[] = {7, 8, 9, 10, 11, 12};
uint8_t addrPins[] = {17, 18, 19, 20, 21};
uint8_t clockPin = 14;
uint8_t latchPin = 15;
uint8_t oePin = 16;
Adafruit_LIS3DH accel = Adafruit_LIS3DH();
Adafruit_Protomatter matrix(
WIDTH, 4, 1, rgbPins, 5, addrPins, clockPin, latchPin, oePin, true);
#define N_COLORS 8
#define BOX_HEIGHT 8
#define N_GRAINS (BOX_HEIGHT*N_COLORS*8)
uint16_t colors[N_COLORS];
// Sand object, last 2 args are accelerometer scaling and grain elasticity
Adafruit_PixelDust sand(WIDTH, HEIGHT, N_GRAINS, 1, 128, false);
uint32_t prevTime = 0; // Used for frames-per-second throttle
// SETUP - RUNS ONCE AT PROGRAM START --------------------------------------
void err(int x) {
uint8_t i;
pinMode(LED_BUILTIN, OUTPUT); // Using onboard LED
for(i=1;;i++) { // Loop forever...
digitalWrite(LED_BUILTIN, i & 1); // LED on/off blink to alert user
delay(x);
}
}
void setup(void) {
uint8_t i, j, bytes;
Serial.begin(115200);
//while (!Serial) delay(10);
ProtomatterStatus status = matrix.begin();
Serial.printf("Protomatter begin() status: %d\n", status);
if (!sand.begin()) {
Serial.println("Couldn't start sand");
err(1000); // Slow blink = malloc error
}
if (!accel.begin(0x19)) {
Serial.println("Couldn't find accelerometer");
err(250); // Fast bink = I2C error
}
accel.setRange(LIS3DH_RANGE_4_G); // 2, 4, 8 or 16 G!
//sand.randomize(); // Initialize random sand positions
// Set up initial sand coordinates, in 8x8 blocks
int n = 0;
for(int i=0; i<N_COLORS; i++) {
int xx = i * WIDTH / N_COLORS;
int yy = HEIGHT - BOX_HEIGHT;
for(int y=0; y<BOX_HEIGHT; y++) {
for(int x=0; x < WIDTH / N_COLORS; x++) {
//Serial.printf("#%d -> (%d, %d)\n", n, xx + x, yy + y);
sand.setPosition(n++, xx + x, yy + y);
}
}
}
Serial.printf("%d total pixels\n", n);
colors[0] = color565(64, 64, 64); // Dark Gray
colors[1] = color565(120, 79, 23); // Brown
colors[2] = color565(228, 3, 3); // Red
colors[3] = color565(255,140, 0); // Orange
colors[4] = color565(255,237, 0); // Yellow
colors[5] = color565( 0,128, 38); // Green
colors[6] = color565( 0, 77,255); // Blue
colors[7] = color565(117, 7,135); // Purple
}
uint16_t color565(uint8_t red, uint8_t green, uint8_t blue) {
return ((red & 0xF8) << 8) | ((green & 0xFC) << 3) | (blue >> 3);
}
// MAIN LOOP - RUNS ONCE PER FRAME OF ANIMATION ----------------------------
void loop() {
// Limit the animation frame rate to MAX_FPS. Because the subsequent sand
// calculations are non-deterministic (don't always take the same amount
// of time, depending on their current states), this helps ensure that
// things like gravity appear constant in the simulation.
uint32_t t;
while(((t = micros()) - prevTime) < (1000000L / MAX_FPS));
prevTime = t;
// Read accelerometer...
sensors_event_t event;
accel.getEvent(&event);
//Serial.printf("(%0.1f, %0.1f, %0.1f)\n", event.acceleration.x, event.acceleration.y, event.acceleration.z);
double xx, yy, zz;
xx = event.acceleration.x * 1000;
yy = event.acceleration.y * 1000;
zz = event.acceleration.z * 1000;
// Run one frame of the simulation
sand.iterate(xx, yy, zz);
//sand.iterate(-accel.y, accel.x, accel.z);
// Update pixel data in LED driver
dimension_t x, y;
matrix.fillScreen(0x0);
for(int i=0; i<N_GRAINS ; i++) {
sand.getPosition(i, &x, &y);
int n = i / ((WIDTH / N_COLORS) * BOX_HEIGHT); // Color index
uint16_t flakeColor = colors[n];
matrix.drawPixel(x, y, flakeColor);
//Serial.printf("(%d, %d)\n", x, y);
}
matrix.show(); // Copy data to matrix buffers
}

View file

@ -22,43 +22,16 @@ supported boards. Notes have been moved to the bottom of the code.
#if defined(_VARIANT_MATRIXPORTAL_M4_) // MatrixPortal M4
uint8_t rgbPins[] = {7, 8, 9, 10, 11, 12};
uint8_t addrPins[] = {17, 18, 19, 20, 21};
uint8_t addrPins[] = {17, 18, 19, 20};
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};
uint8_t addrPins[] = {A5, A4, A3, A2};
uint8_t clockPin = 13; // Must be on same port as rgbPins
uint8_t latchPin = RX;
uint8_t oePin = TX;
#elif defined(ARDUINO_METRO_ESP32S2) // Metro ESP32-S2
// Matrix Shield compatible:
uint8_t rgbPins[] = {7, 8, 9, 10, 11, 12};
uint8_t addrPins[] = {A0, A1, A2, A3};
uint8_t clockPin = 13; // Must be on same port as rgbPins
uint8_t latchPin = 15;
uint8_t oePin = 14;
#elif defined(__SAMD51__) // M4 Metro Variants (Express, AirLift)
uint8_t rgbPins[] = {6, 5, 9, 11, 10, 12};
uint8_t addrPins[] = {A5, A4, A3, A2};
@ -66,24 +39,17 @@ 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};
uint8_t clockPin = 12;
uint8_t latchPin = PIN_SERIAL1_RX;
uint8_t oePin = PIN_SERIAL1_TX;
#elif USB_VID == 0x239A && USB_PID == 0x8113 // Feather ESP32-S3 No PSRAM
// M0/M4/RP2040 Matrix FeatherWing compatible:
uint8_t rgbPins[] = {6, 5, 9, 11, 10, 12};
uint8_t addrPins[] = {A5, A4, A3, A2};
uint8_t clockPin = 13; // Must be on same port as rgbPins
uint8_t latchPin = RX;
uint8_t oePin = TX;
#elif defined(ESP32)
// 'Safe' pins, not overlapping any peripherals:
// GPIO.out: 4, 12, 13, 14, 15, 21, 27, GPIO.out1: 32, 33
@ -109,18 +75,6 @@ supported boards. Notes have been moved to the bottom of the code.
uint8_t clockPin = 23; // A9
uint8_t latchPin = 6;
uint8_t oePin = 9;
#elif defined(ARDUINO_ADAFRUIT_FEATHER_RP2040)
// RP2040 support requires the Earle Philhower board support package;
// will not compile with the Arduino Mbed OS board package.
// The following pinout works with the Adafruit Feather RP2040 and
// original RGB Matrix FeatherWing (M0/M4/RP2040, not nRF version).
// Pin numbers here are GP## numbers, which may be different than
// the pins printed on some boards' top silkscreen.
uint8_t rgbPins[] = {8, 7, 9, 11, 10, 12};
uint8_t addrPins[] = {25, 24, 29, 28};
uint8_t clockPin = 13;
uint8_t latchPin = 1;
uint8_t oePin = 0;
#endif
/* ----------------------------------------------------------------------
@ -191,31 +145,35 @@ void setup(void) {
// Make four color bars (red, green, blue, white) with brightness ramp:
for(int x=0; x<matrix.width(); x++) {
uint8_t level = x * 256 / matrix.width(); // 0-255 brightness
matrix.drawPixel(x, matrix.height() - 4, matrix.color565(level, 0, 0));
matrix.drawPixel(x, matrix.height() - 3, matrix.color565(0, level, 0));
matrix.drawPixel(x, matrix.height() - 2, matrix.color565(0, 0, level));
matrix.drawPixel(x, matrix.height() - 1,
matrix.color565(level, level, level));
matrix.drawPixel(x, matrix.height() - 4, color565(level, 0, 0));
matrix.drawPixel(x, matrix.height() - 3, color565(0, level, 0));
matrix.drawPixel(x, matrix.height() - 2, color565(0, 0, level));
matrix.drawPixel(x, matrix.height() - 1, color565(level, level, level));
}
// You'll notice the ramp looks smoother as bit depth increases
// (second argument to the matrix constructor call above setup()).
// Simple shapes and text, showing GFX library calls:
matrix.drawCircle(12, 10, 9, matrix.color565(255, 0, 0));
matrix.drawRect(14, 6, 17, 17, matrix.color565(0, 255, 0));
matrix.drawTriangle(32, 9, 41, 27, 23, 27, matrix.color565(0, 0, 255));
matrix.drawCircle(12, 10, 9, color565(255, 0, 0)); // Red
matrix.drawRect(14, 6, 17, 17, color565(0, 255, 0)); // Green
matrix.drawTriangle(32, 9, 41, 27, 23, 27, color565(0, 0, 255)); // Blue
matrix.println("ADAFRUIT"); // Default text color is white
if (matrix.height() > 32) {
matrix.setCursor(0, 32);
matrix.println("64 pixel"); // Default text color is white
matrix.println("matrix"); // Default text color is white
}
// AFTER DRAWING, A show() CALL IS REQUIRED TO UPDATE THE MATRIX!
matrix.show(); // Copy data to matrix buffers
}
// Utility function converts 24-bit color (8 bits red, green, blue) used in
// a lot of existing graphics code down to the "565" color format used by
// Adafruit_GFX. Might get further quantized by matrix if using less than
// 6-bit depth.
uint16_t color565(uint8_t red, uint8_t green, uint8_t blue) {
return ((red & 0b11111000) << 8) |
((green & 0b11111100) << 3) |
( blue >> 3);
}
// LOOP - RUNS REPEATEDLY AFTER SETUP --------------------------------------
void loop(void) {
@ -296,40 +254,6 @@ P0.05 5/SCK P0.13 13/A12 P0.21 21 P0.29 P1.05
P0.06 P0.14 14/A6 P0.22 22/SCL P0.30 P1.06
P0.07 P0.15 15/A8 P0.23 23/SDA P0.31 P1.07 39/A3 (in)
GRAND CENTRAL M4: (___ = byte boundaries)
PA00 PB00 D12 PC00 A3 PD00
PA01 PB01 D13 (LED) PC01 A4 PD01
PA02 A0 PB02 D9 PC02 A5 PD02
PA03 84 (AREF) PB03 A2 PC03 A6 PD03
PA04 A13 PB04 A7 PC04 D48 PD04
PA05 A1 PB05 A8 PC05 D49 PD05
PA06 A14 PB06 A9 PC06 D46 PD06
PA07 A15 ______ PB07 A10 ______ PC07 D47 _____ PD07 __________
PA08 PB08 A11 PC08 PD08 D51 (SCK)
PA09 PB09 A12 PC09 PD09 D52 (MOSI)
PA10 PB10 PC10 D45 PD10 D53
PA11 PB11 PC11 D44 PD11 D50 (MISO)
PA12 D26 PB12 D18 PC12 D41 PD12 D22
PA13 D27 PB13 D19 PC13 D40 PD13
PA14 D28 PB14 D39 PC14 D43 PD14
PA15 D23 ______ PB15 D38 ______ PC15 D42 _____ PD15 __________
PA16 D37 PB16 D14 PC16 D25 PD16
PA17 D36 PB17 D15 PC17 D24 PD17
PA18 D35 PB18 D8 PC18 D2 PD18
PA19 D34 PB19 D29 PC19 D3 PD19
PA20 D33 PB20 D20 (SDA) PC20 D4 PD20 D6
PA21 D32 PB21 D21 (SCL) PC21 D5 PD21 D7
PA22 D31 PB22 D10 PC22 D16 PD22
PA23 D30 ______ PB23 D11 ______ PC23 D17 _____ PD23 __________
PA24 PB24 D1
PA25 PB25 D0
PA26 PB26
PA27 PB27
PA28 PB28
PA29 PB29
PA30 PB30 96 (SWO)
PA31 __________ PB31 95 (SD CD) ______________________________
RGB MATRIX FEATHERWING NOTES:
R1 D6 A A5
G1 D5 B A4

View file

@ -1,168 +0,0 @@
/* ----------------------------------------------------------------------
"Tiled" Protomatter library example sketch. Demonstrates use of multiple
RGB LED matrices as a single larger drawing surface. This example is
written for two 64x32 matrices (tiled into a 64x64 display) but can be
adapted to others. If using MatrixPortal, larger multi-panel tilings like
this should be powered from a separate 5V DC supply, not the USB port
(this example works OK because the graphics are very minimal).
PLEASE SEE THE "simple" EXAMPLE FOR AN INTRODUCTORY SKETCH.
------------------------------------------------------------------------- */
#include <Adafruit_Protomatter.h>
/* ----------------------------------------------------------------------
The RGB matrix must be wired to VERY SPECIFIC pins, different for each
microcontroller board. This first section sets that up for a number of
supported boards.
------------------------------------------------------------------------- */
#if defined(_VARIANT_MATRIXPORTAL_M4_) // MatrixPortal M4
uint8_t rgbPins[] = {7, 8, 9, 10, 11, 12};
uint8_t addrPins[] = {17, 18, 19, 20, 21};
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(__SAMD51__) // M4 Metro Variants (Express, AirLift)
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(_SAMD21_) // Feather M0 variants
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};
uint8_t clockPin = 12;
uint8_t latchPin = PIN_SERIAL1_RX;
uint8_t oePin = PIN_SERIAL1_TX;
#elif USB_VID == 0x239A && USB_PID == 0x8113 // Feather ESP32-S3 No PSRAM
// M0/M4/RP2040 Matrix FeatherWing compatible:
uint8_t rgbPins[] = {6, 5, 9, 11, 10, 12};
uint8_t addrPins[] = {A5, A4, A3, A2};
uint8_t clockPin = 13; // Must be on same port as rgbPins
uint8_t latchPin = RX;
uint8_t oePin = TX;
#elif USB_VID == 0x239A && USB_PID == 0x80EB // Feather ESP32-S2
// M0/M4/RP2040 Matrix FeatherWing compatible:
uint8_t rgbPins[] = {6, 5, 9, 11, 10, 12};
uint8_t addrPins[] = {A5, A4, A3, A2};
uint8_t clockPin = 13; // Must be on same port as rgbPins
uint8_t latchPin = RX;
uint8_t oePin = TX;
#elif defined(ESP32)
// 'Safe' pins, not overlapping any peripherals:
// GPIO.out: 4, 12, 13, 14, 15, 21, 27, GPIO.out1: 32, 33
// Peripheral-overlapping pins, sorted from 'most expendible':
// 16, 17 (RX, TX)
// 25, 26 (A0, A1)
// 18, 5, 9 (MOSI, SCK, MISO)
// 22, 23 (SCL, SDA)
uint8_t rgbPins[] = {4, 12, 13, 14, 15, 21};
uint8_t addrPins[] = {16, 17, 25, 26};
uint8_t clockPin = 27; // Must be on same port as rgbPins
uint8_t latchPin = 32;
uint8_t oePin = 33;
#elif defined(ARDUINO_TEENSY40)
uint8_t rgbPins[] = {15, 16, 17, 20, 21, 22}; // A1-A3, A6-A8, skip SDA,SCL
uint8_t addrPins[] = {2, 3, 4, 5};
uint8_t clockPin = 23; // A9
uint8_t latchPin = 6;
uint8_t oePin = 9;
#elif defined(ARDUINO_TEENSY41)
uint8_t rgbPins[] = {26, 27, 38, 20, 21, 22}; // A12-14, A6-A8
uint8_t addrPins[] = {2, 3, 4, 5};
uint8_t clockPin = 23; // A9
uint8_t latchPin = 6;
uint8_t oePin = 9;
#elif defined(ARDUINO_ADAFRUIT_FEATHER_RP2040)
// RP2040 support requires the Earle Philhower board support package;
// will not compile with the Arduino Mbed OS board package.
// The following pinout works with the Adafruit Feather RP2040 and
// original RGB Matrix FeatherWing (M0/M4/RP2040, not nRF version).
// Pin numbers here are GP## numbers, which may be different than
// the pins printed on some boards' top silkscreen.
uint8_t rgbPins[] = {8, 7, 9, 11, 10, 12};
uint8_t addrPins[] = {25, 24, 29, 28};
uint8_t clockPin = 13;
uint8_t latchPin = 1;
uint8_t oePin = 0;
#endif
/* ----------------------------------------------------------------------
Matrix initialization is explained EXTENSIVELY in "simple" example sketch!
It's very similar here, but we're passing an extra argument to define the
matrix tiling along the vertical axis: -2 means there are two matrices
(or rows of matrices) arranged in a "serpentine" path (the second matrix
is rotated 180 degrees relative to the first, and positioned below).
A positive 2 would indicate a "progressive" path (both matrices are
oriented the same way), but usually requires longer cables.
------------------------------------------------------------------------- */
Adafruit_Protomatter matrix(
64, // Width of matrix (or matrices, if tiled horizontally)
6, // Bit depth, 1-6
1, rgbPins, // # of matrix chains, array of 6 RGB pins for each
4, addrPins, // # of address pins (height is inferred), array of pins
clockPin, latchPin, oePin, // Other matrix control pins
false, // No double-buffering here (see "doublebuffer" example)
-2); // Row tiling: two rows in "serpentine" path
// SETUP - RUNS ONCE AT PROGRAM START --------------------------------------
void setup(void) {
Serial.begin(9600);
// Initialize matrix...
ProtomatterStatus status = matrix.begin();
Serial.print("Protomatter begin() status: ");
Serial.println((int)status);
if(status != PROTOMATTER_OK) {
// DO NOT CONTINUE if matrix setup encountered an error.
for(;;);
}
// Since this program has no animation, all the drawing can be done
// here in setup() rather than loop(). It's just a few basic shapes
// that span across the matrices...nothing showy, the goal of this
// sketch is just to demonstrate tiling basics.
matrix.drawLine(0, 0, matrix.width() - 1, matrix.height() - 1,
matrix.color565(255, 0, 0)); // Red line
matrix.drawLine(matrix.width() - 1, 0, 0, matrix.height() - 1,
matrix.color565(0, 0, 255)); // Blue line
int radius = min(matrix.width(), matrix.height()) / 2;
matrix.drawCircle(matrix.width() / 2, matrix.height() / 2, radius,
matrix.color565(0, 255, 0)); // Green circle
// AFTER DRAWING, A show() CALL IS REQUIRED TO UPDATE THE MATRIX!
matrix.show(); // Copy data to matrix buffers
}
// LOOP - RUNS REPEATEDLY AFTER SETUP --------------------------------------
void loop(void) {
// Since there's nothing more to be drawn, this loop() function just
// prints the approximate refresh rate of the matrix at current settings.
Serial.print("Refresh FPS = ~");
Serial.println(matrix.getFrameCount());
delay(1000);
}

View file

@ -1,10 +1,10 @@
name=Adafruit Protomatter
version=1.7.0
version=1.0.5
author=Adafruit
maintainer=Adafruit <info@adafruit.com>
sentence=A library for Adafruit RGB LED matrices.
paragraph=RGB LED matrix.
category=Display
url=https://github.com/adafruit/Adafruit_protomatter
architectures=samd,nrf52,stm32,esp32,rp2040
architectures=samd,nrf52,stm32,esp32
depends=Adafruit GFX Library, Adafruit LIS3DH, Adafruit PixelDust, AnimatedGIF, Adafruit SPIFlash, Adafruit TinyUSB Library

22
notes.txt Normal file
View file

@ -0,0 +1,22 @@
Code is now in the 'src' folder.
arch.h has been split out into multiple files (one per architecture),
in the 'arch' folder. There's still an arch.h file, but it mostly
#includes the device-specific headers and then defaults any undefined
items at the end.
A few functions have been renamed:
_PM_free(Protomatter_core *) is now _PM_deallocate(Protomatter_core *)
_PM_setReg(bit) is now _PM_setBit(bit)
_PM_clearReg(bit) is now _PM_clearBit(bit)
_PM_ALLOCATOR(size) is now _PM_allocate(size)
_PM_FREE(addr) is now _PM_free(addr)
_PM_timerStart() now accepts two tick intervals. First is the overflow
interval (when the per-bitplane switchover occurs), as before. Second is
when the matrix OE line is de-asserted, if this needs to occur before the
overflow interval (e.g. when brightness scaling or for gamma correction).
Most architectures will ignore the second value for now because the
necessary interrupt changes have not been made yet. Only SAMD51 uses it.
If the second interval arg is omitted, the early OE de-assert does not
occur, and the normal brief OE de-assert occurs in the bitplane switchover.

View file

@ -47,12 +47,9 @@ Adafruit_Protomatter::Adafruit_Protomatter(uint16_t bitWidth, uint8_t bitDepth,
uint8_t addrCount, uint8_t *addrList,
uint8_t clockPin, uint8_t latchPin,
uint8_t oePin, bool doubleBuffer,
int8_t tile, void *timer)
: GFXcanvas16(bitWidth, (2 << min((int)addrCount, 5)) *
min((int)rgbCount, 5) *
(tile ? abs(tile) : 1)) {
if (bitDepth > 6)
bitDepth = 6; // GFXcanvas16 color limit (565)
void *timer)
: GFXcanvas16(bitWidth,
(2 << min((int)addrCount, 5)) * min((int)rgbCount, 5)) {
// Arguments are passed through to the C _PM_init() function which does
// some input validation and minor allocation. Return value is ignored
@ -60,8 +57,7 @@ Adafruit_Protomatter::Adafruit_Protomatter(uint16_t bitWidth, uint8_t bitDepth,
// The class begin() function checks rgbPins for NULL to determine
// whether to proceed or indicate an error.
(void)_PM_init(&core, bitWidth, bitDepth, rgbCount, rgbList, addrCount,
addrList, clockPin, latchPin, oePin, doubleBuffer, tile,
timer);
addrList, clockPin, latchPin, oePin, doubleBuffer, timer);
}
Adafruit_Protomatter::~Adafruit_Protomatter(void) {
@ -69,8 +65,9 @@ Adafruit_Protomatter::~Adafruit_Protomatter(void) {
_PM_protoPtr = NULL;
}
ProtomatterStatus Adafruit_Protomatter::begin(void) {
ProtomatterStatus Adafruit_Protomatter::begin(uint8_t brightness) {
_PM_protoPtr = &core;
core.brightness = brightness;
return _PM_begin(&core);
}
@ -89,53 +86,3 @@ void Adafruit_Protomatter::show(void) {
uint32_t Adafruit_Protomatter::getFrameCount(void) {
return _PM_getFrameCount(_PM_protoPtr);
}
// This is based on the HSV function in Adafruit_NeoPixel.cpp, but with
// 16-bit RGB565 output for GFX lib rather than 24-bit. See that code for
// an explanation of the math, this is stripped of comments for brevity.
uint16_t Adafruit_Protomatter::colorHSV(uint16_t hue, uint8_t sat,
uint8_t val) {
uint8_t r, g, b;
hue = (hue * 1530L + 32768) / 65536;
if (hue < 510) { // Red to Green-1
b = 0;
if (hue < 255) { // Red to Yellow-1
r = 255;
g = hue; // g = 0 to 254
} else { // Yellow to Green-1
r = 510 - hue; // r = 255 to 1
g = 255;
}
} else if (hue < 1020) { // Green to Blue-1
r = 0;
if (hue < 765) { // Green to Cyan-1
g = 255;
b = hue - 510; // b = 0 to 254
} else { // Cyan to Blue-1
g = 1020 - hue; // g = 255 to 1
b = 255;
}
} else if (hue < 1530) { // Blue to Red-1
g = 0;
if (hue < 1275) { // Blue to Magenta-1
r = hue - 1020; // r = 0 to 254
b = 255;
} else { // Magenta to Red-1
r = 255;
b = 1530 - hue; // b = 255 to 1
}
} else { // Last 0.5 Red (quicker than % operator)
r = 255;
g = b = 0;
}
// Apply saturation and value to R,G,B, pack into 16-bit 'RGB565' result:
uint32_t v1 = 1 + val; // 1 to 256; allows >>8 instead of /255
uint16_t s1 = 1 + sat; // 1 to 256; same reason
uint8_t s2 = 255 - sat; // 255 to 0
return (((((r * s1) >> 8) + s2) * v1) & 0xF800) |
((((((g * s1) >> 8) + s2) * v1) & 0xFC00) >> 5) |
(((((b * s1) >> 8) + s2) * v1) >> 11);
}

View file

@ -54,14 +54,6 @@ public:
@param doubleBuffer If true, two matrix buffers are allocated,
so changing display contents doesn't introduce
artifacts mid-conversion. Requires ~2X RAM.
@param tile If multiple matrices are chained and stacked
vertically (rather than or in addition to
horizontally), the number of vertical tiles is
specified here. Positive values indicate a
"progressive" arrangement (always left-to-right),
negative for a "serpentine" arrangement (alternating
180 degree orientation). Horizontal tiles are implied
in the 'bitWidth' argument.
@param timer Pointer to timer peripheral or timer-related
struct (architecture-dependent), or NULL to
use a default timer ID (also arch-dependent).
@ -69,12 +61,16 @@ public:
Adafruit_Protomatter(uint16_t bitWidth, uint8_t bitDepth, uint8_t rgbCount,
uint8_t *rgbList, uint8_t addrCount, uint8_t *addrList,
uint8_t clockPin, uint8_t latchPin, uint8_t oePin,
bool doubleBuffer, int8_t tile = 1, void *timer = NULL);
bool doubleBuffer, void *timer = NULL);
~Adafruit_Protomatter(void);
/*!
@brief Start a Protomatter matrix display running -- initialize
pins, timer and interrupt into existence.
@param brightness Initial matrix brightness setting,
0 = minimum (off), 255 = maximum (default if unset).
May not be supported by all architectures.
See setBrightness() notes.
@return A ProtomatterStatus status, one of:
PROTOMATTER_OK if everything is good.
PROTOMATTER_ERR_PINS if data and/or clock pins are split
@ -84,7 +80,19 @@ public:
PROTOMATTER_ERR_ARG if a bad value was passed to the
constructor.
*/
ProtomatterStatus begin(void);
ProtomatterStatus begin(uint8_t brightness = 255);
/*!
@brief Change brightness of an already-running matrix.
May not be supported by all architectures.
Brightness value is a linear scaling of the duty cycle,
NOT perceptual brightness -- a ~50% value here (127) will
halve the LEDs' duty cycle, but will likely appear more
than half as bright as 100%.
@param brightness Brightness setting, 0 = minimum (off),
255 = maximum (default if unset).
*/
void setBrightness(uint8_t brightness = 255) { core.brightness = brightness; }
/*!
@brief Process data from GFXcanvas16 to the matrix framebuffer's
@ -92,16 +100,6 @@ public:
*/
void show(void);
/*!
@brief Disable (but do not deallocate) a Protomatter matrix.
*/
void stop(void) { _PM_stop(&core); }
/*!
@brief Resume a previously-stopped matrix.
*/
void resume(void) { _PM_resume(&core); }
/*!
@brief Returns current value of frame counter and resets its value
to zero. Two calls to this, timed one second apart (or use
@ -112,48 +110,6 @@ public:
*/
uint32_t getFrameCount(void);
/*!
@brief Converts 24-bit color (8 bits red, green, blue) used in a lot
a lot of existing graphics code down to the "565" color format
used by Adafruit_GFX. Might get further quantized by matrix if
using less than 6-bit depth.
@param red Red brightness, 0 (min) to 255 (max).
@param green Green brightness, 0 (min) to 255 (max).
@param blue Blue brightness, 0 (min) to 255 (max).
@return Packed 16-bit (uint16_t) color value suitable for GFX drawing
functions.
*/
uint16_t color565(uint8_t red, uint8_t green, uint8_t blue) {
return ((red & 0xF8) << 8) | ((green & 0xFC) << 3) | (blue >> 3);
}
/*!
@brief Convert hue, saturation and value into a packed 16-bit RGB color
that can be passed to GFX drawing functions.
@param hue An unsigned 16-bit value, 0 to 65535, representing one full
loop of the color wheel, which allows 16-bit hues to "roll
over" while still doing the expected thing (and allowing
more precision than the wheel() function that was common to
older graphics examples).
@param sat Saturation, 8-bit value, 0 (min or pure grayscale) to 255
(max or pure hue). Default of 255 if unspecified.
@param val Value (brightness), 8-bit value, 0 (min / black / off) to
255 (max or full brightness). Default of 255 if unspecified.
@return Packed 16-bit '565' RGB color. Result is linearly but not
perceptually correct (no gamma correction).
*/
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

63
src/arch/arch.h Normal file → Executable file
View file

@ -82,13 +82,15 @@ Timer-related macros/functions:
_PM_timerFreq: A numerical constant - the source clock rate
(in Hz) that's fed to the timer peripheral.
_PM_timerInit(Protomatter_core*): Initialize (but do not start) timer.
_PM_timerStart(Protomatter_core*,count): (Re)start timer for a given
timer-tick interval.
_PM_timerStop(Protomatter_core*): Stop timer, return current timer
counter value.
_PM_timerGetCount(Protomatter_core*): Get current timer counter value
(whether timer is running or stopped).
_PM_timerInit(void*): Initialize (but do not start) timer.
_PM_timerStart(void*,a,b): (Re)start timer for a given timer-tick interval,
'a' is the top/overflow period (when the timer
rolls over to zero), 'b' is a second shorter
period that can be used to de-assert the OE
line early (pass 0xFFFF to ignore).
_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
@ -137,26 +139,6 @@ _PM_allocate: Memory allocation function, should return a
If not defined, malloc() is used.
_PM_free: Corresponding deallocator for _PM_allocate().
If not defined, free() is used.
_PM_bytesPerElement If defined, this allows an arch-specific source
file to override core's data size that's based
on pin selections. Reasonable values would be 1,
2 or 4. This came about during ESP32-S2
development; GPIO or I2S/LCD peripherals there
allows super flexible pin MUXing, so one byte
could be used even w/pins spread all over.
_PM_USE_TOGGLE_FORMAT If defined, this instructs the core code to
format pixel data for GPIO bit-toggling, even
if _PM_portToggleRegister is not defined.
_PM_CUSTOM_BLAST If defined, instructs core code to not compile
the blast_byte(), blast_word() or blast_long()
functions; these will be declared in the arch-
specific file instead. This might benefit
architectures, where DMA, PIO or other
specialized peripherals could be set up to
issue data independent of the CPU. This goes
against's Protomatter's normal design of using
the most baseline peripherals for a given
architecture, but time marches on, y'know?
*/
// ENVIRONMENT-SPECIFIC DECLARATIONS ---------------------------------------
@ -170,6 +152,7 @@ _PM_CUSTOM_BLAST If defined, instructs core code to not compile
#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 --------------------
@ -186,32 +169,16 @@ _PM_CUSTOM_BLAST If defined, instructs core code to not compile
// ARCHITECTURE-SPECIFIC HEADERS -------------------------------------------
// clang-format off
#include "esp32-common.h"
#include "esp32.h" // Original ESP32
#include "esp32-s2.h"
#include "esp32-s3.h"
#include "esp32-c3.h"
#include "esp32-c6.h"
#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"
// clang-format on
// DEFAULTS IF NOT DEFINED ABOVE -------------------------------------------
#if defined(_PM_portToggleRegister)
#define _PM_USE_TOGGLE_FORMAT
#endif
#if !defined(_PM_portBitMask)
#define _PM_portBitMask(pin) digitalPinToBitMask(pin)
#endif
#if !defined(_PM_chunkSize)
#define _PM_chunkSize 8 ///< Unroll data-stuffing loop to this size
#endif
@ -244,10 +211,6 @@ _PM_CUSTOM_BLAST If defined, instructs core code to not compile
#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)
#if !defined(_PM_maxBitplanes)
#define _PM_maxBitplanes 6 ///< RGB bit depth handled by architecture
#endif

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_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
#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

@ -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

@ -1,247 +0,0 @@
/*!
* @file esp32-common.h
*
* Part of Adafruit's Protomatter library for HUB75-style RGB LED matrices.
* This file contains ESP32-SPECIFIC CODE (common to all ESP variants).
*
* 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) || \
defined(ESP_PLATFORM) // *All* ESP32 variants (OG, S2, S3, etc.)
#include <inttypes.h>
#include "esp_idf_version.h"
// 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!
#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;
#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
// variants) is declared in the corresponding esp32-*.h header(s); please
// no #if defined(CONFIG_IDF_TARGET_*) action here...if you find yourself
// 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
// 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
}
// 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.
// Timer must be previously initialized.
IRAM_ATTR uint32_t _PM_timerStop(Protomatter_core *core) {
timerStop((hw_timer_t *)core->timer);
return _PM_timerGetCount(core);
}
// 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.
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)
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
}
#elif defined(CIRCUITPY) // COMPILING FOR CIRCUITPYTHON --------------------
// The following defines and functions are common to all ESP32 variants in
// the CircuitPython platform. Anything unique to one variant (or a subset
// of variants) is declared in the corresponding esp32-*.h header(s);
// please no #if defined(CONFIG_IDF_TARGET_*) action here...if you find
// yourself started down that path, it's okay, but move the code out of
// here and into the variant-specific headers.
#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
#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)
// 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.
#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
if (_PM_protoPtr) {
#endif
_PM_row_handler(_PM_protoPtr); // In core.c
}
return false;
};
// 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);
}
#else
IRAM_ATTR void _PM_timerStart(Protomatter_core *core, uint32_t period) {
timer_index_t *timer = (timer_index_t *)core->timer;
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);
}
#endif
// 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;
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;
#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
timer_index_t *timer = (timer_index_t *)core->timer;
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_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);
#endif
}
#endif // END CIRCUITPYTHON ------------------------------------------------
#endif // END ESP32 (all variants)

View file

@ -1,213 +0,0 @@
/*!
* @file esp32-s2.h
*
* Part of Adafruit's Protomatter library for HUB75-style RGB LED matrices.
* This file contains ESP32-S2-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_ESP32S2)
#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)
// On ESP32-S2, use the Dedicated GPIO peripheral, which allows faster bit-
// toggling than the conventional GPIO registers. Unfortunately NOT present
// on S3 or other ESP32 devices. Normal GPIO has a bottleneck where toggling
// a pin is limited to 8 MHz max. Dedicated GPIO can work around this and
// get over twice this rate, but requires some very weird hoops!
// Dedicated GPIO only supports 8-bit output, so parallel output isn't
// supported, but positives include that there's very flexible pin MUXing,
// so matrix data in RAM can ALWAYS be stored in byte format regardless how
// the RGB+clock bits are distributed among pins.
#define _PM_bytesPerElement 1
#define _PM_byteOffset(pin) 0
#define _PM_wordOffset(pin) 0
// Odd thing with Dedicated GPIO is that the bit-toggle operation is faster
// than bit set or clear (perhaps some underlying operation is atomic rather
// than read-modify-write). So, instruct core.c to format the matrix data in
// RAM as if we're using a port toggle register, even though
// _PM_portToggleRegister is NOT defined because ESP32 doesn't have that in
// conventional GPIO. Good times.
#define _PM_USE_TOGGLE_FORMAT
// This table is used to remap 7-bit (RGB+RGB+clock) data to the 2-bits-per
// GPIO format used by Dedicated GPIO. Bits corresponding to clock output
// are always set here, as we want that bit toggled low at the same time new
// RGB data is set up.
static uint16_t _bit_toggle[128] = {
0x3000, 0x3003, 0x300C, 0x300F, 0x3030, 0x3033, 0x303C, 0x303F, 0x30C0,
0x30C3, 0x30CC, 0x30CF, 0x30F0, 0x30F3, 0x30FC, 0x30FF, 0x3300, 0x3303,
0x330C, 0x330F, 0x3330, 0x3333, 0x333C, 0x333F, 0x33C0, 0x33C3, 0x33CC,
0x33CF, 0x33F0, 0x33F3, 0x33FC, 0x33FF, 0x3C00, 0x3C03, 0x3C0C, 0x3C0F,
0x3C30, 0x3C33, 0x3C3C, 0x3C3F, 0x3CC0, 0x3CC3, 0x3CCC, 0x3CCF, 0x3CF0,
0x3CF3, 0x3CFC, 0x3CFF, 0x3F00, 0x3F03, 0x3F0C, 0x3F0F, 0x3F30, 0x3F33,
0x3F3C, 0x3F3F, 0x3FC0, 0x3FC3, 0x3FCC, 0x3FCF, 0x3FF0, 0x3FF3, 0x3FFC,
0x3FFF, 0x3000, 0x3003, 0x300C, 0x300F, 0x3030, 0x3033, 0x303C, 0x303F,
0x30C0, 0x30C3, 0x30CC, 0x30CF, 0x30F0, 0x30F3, 0x30FC, 0x30FF, 0x3300,
0x3303, 0x330C, 0x330F, 0x3330, 0x3333, 0x333C, 0x333F, 0x33C0, 0x33C3,
0x33CC, 0x33CF, 0x33F0, 0x33F3, 0x33FC, 0x33FF, 0x3C00, 0x3C03, 0x3C0C,
0x3C0F, 0x3C30, 0x3C33, 0x3C3C, 0x3C3F, 0x3CC0, 0x3CC3, 0x3CCC, 0x3CCF,
0x3CF0, 0x3CF3, 0x3CFC, 0x3CFF, 0x3F00, 0x3F03, 0x3F0C, 0x3F0F, 0x3F30,
0x3F33, 0x3F3C, 0x3F3F, 0x3FC0, 0x3FC3, 0x3FCC, 0x3FCF, 0x3FF0, 0x3FF3,
0x3FFC, 0x3FFF,
};
#include <driver/dedic_gpio.h>
#include <soc/dedic_gpio_reg.h>
#include <soc/dedic_gpio_struct.h>
// Override the behavior of _PM_portBitMask macro so instead of returning
// a 32-bit mask for a pin within its corresponding GPIO register, it instead
// returns a 7-bit mask for the pin within the Direct GPIO register *IF* it's
// one of the RGB bits or the clock bit...this requires comparing against pin
// numbers in the core struct.
static uint32_t _PM_directBitMask(Protomatter_core *core, int pin) {
if (pin == core->clockPin)
return 1 << 6;
for (uint8_t i = 0; i < 6; i++) {
if (pin == core->rgbPins[i])
return 1 << i;
}
// Else return the bit that would normally be used for regular GPIO
return (1U << (pin & 31));
}
// Thankfully, at present, any core code which calls _PM_portBitMask()
// currently has a 'core' variable, so we can safely do this...
#define _PM_portBitMask(pin) _PM_directBitMask(core, pin)
// Dedicated GPIO requires a complete replacement of the "blast" functions
// in order to get sufficient speed.
#define _PM_CUSTOM_BLAST // Disable blast_*() functions in core.c
IRAM_ATTR static void blast_byte(Protomatter_core *core, uint8_t *data) {
volatile uint32_t *gpio = &DEDIC_GPIO.gpio_out_idv.val;
// GPIO has already been initialized with RGB data + clock bits
// all LOW, so we don't need to initialize that state here.
for (uint32_t bits = core->chainBits / 8; bits--;) {
*gpio = _bit_toggle[*data++]; // Toggle in new data + toggle clock low
*gpio = 0b11000000000000; // Toggle clock high
*gpio = _bit_toggle[*data++];
*gpio = 0b11000000000000;
*gpio = _bit_toggle[*data++];
*gpio = 0b11000000000000;
*gpio = _bit_toggle[*data++];
*gpio = 0b11000000000000;
*gpio = _bit_toggle[*data++];
*gpio = 0b11000000000000;
*gpio = _bit_toggle[*data++];
*gpio = 0b11000000000000;
*gpio = _bit_toggle[*data++];
*gpio = 0b11000000000000;
*gpio = _bit_toggle[*data++];
*gpio = 0b11000000000000;
}
// Want the pins 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.
*gpio = 0b10101010101010; // Clear RGB + clock bits
}
// 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) {}
#if defined(ARDUINO) // COMPILING FOR ARDUINO ------------------------------
void _PM_timerInit(Protomatter_core *core) {
// On S2, initialize the Dedicated GPIO peripheral using the RGB pin list
// list from the core struct, plus the clock pin (7 pins total). Unsure if
// these structs & arrays need to be persistent. Declaring static just in
// case...could experiment with removing one by one.
static int pins[7];
for (uint8_t i = 0; i < 6; i++)
pins[i] = core->rgbPins[i];
pins[6] = core->clockPin;
static dedic_gpio_bundle_config_t config_in = {
.gpio_array = pins, // Array of GPIO numbers
.array_size = 7, // RGB pins + clock pin
.flags = {
.in_en = 0, // Disable input
.out_en = 1, // Enable output
.out_invert = 0, // Non-inverted
}};
static dedic_gpio_bundle_handle_t bundle;
(void)dedic_gpio_new_bundle(&config_in, &bundle);
dedic_gpio_bundle_write(bundle, config_in.array_size, 1);
DEDIC_GPIO.gpio_out_cpu.val = 0; // Use GPIO registers, not CPU instructions
_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) {
// 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.
// On S2, initialize the Dedicated GPIO peripheral using the RGB pin list
// list from the core struct, plus the clock pin (7 pins total). Unsure if
// these structs & arrays need to be persistent. Declaring static just in
// case...could experiment with removing one by one.
static int pins[7];
for (uint8_t i = 0; i < 6; i++)
pins[i] = core->rgbPins[i];
pins[6] = core->clockPin;
static dedic_gpio_bundle_config_t config_in = {
.gpio_array = pins, // Array of GPIO numbers
.array_size = 7, // RGB pins + clock pin
.flags = {
.in_en = 0, // Disable input
.out_en = 1, // Enable output
.out_invert = 0, // Non-inverted
}};
static dedic_gpio_bundle_handle_t bundle;
(void)dedic_gpio_new_bundle(&config_in, &bundle);
dedic_gpio_bundle_write(bundle, config_in.array_size, 1);
DEDIC_GPIO.gpio_out_cpu.val = 0; // Use GPIO registers, not CPU instructions
_PM_esp32commonTimerInit(core); // In esp32-common.h
}
#endif // END CIRCUITPYTHON ------------------------------------------------
#endif // END ESP32S2

View file

@ -1,285 +0,0 @@
/*!
* @file esp32-s3.h
*
* Part of Adafruit's Protomatter library for HUB75-style RGB LED matrices.
* This file contains ESP32-S3-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_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)
#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)
// On ESP32-S3, use the LCD_CAM peripheral for fast parallel output.
// Thanks to ESP's pin MUXing, matrix data in RAM can ALWAYS be stored in
// byte format regardless how RGB+clock bits are distributed among pins.
#define _PM_bytesPerElement 1
#define _PM_byteOffset(pin) 0
#define _PM_wordOffset(pin) 0
// On ESP32-S3, use the LCD_CAM peripheral for fast parallel output.
// Thanks to ESP's pin MUXing, matrix data in RAM can ALWAYS be stored in
// byte format regardless how RGB+clock bits are distributed among pins.
#define _PM_bytesPerElement 1
#define _PM_byteOffset(pin) 0
#define _PM_wordOffset(pin) 0
// On most architectures, _PM_timerGetCount() is used to measure bitbang
// speed for one scanline, which is then used for bitplane 0 time, and each
// subsequent plane doubles that. Since ESP32-S3 uses DMA and we don't have
// an end-of-transfer interrupt, we make an informed approximation.
// 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:
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
// 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;
}
// Note that dmaSetupTime can vary from line to line, potentially influenced
// by interrupts, nondeterministic DMA channel clearing times, etc., which is
// why we don't just use a constant value. Each scanline might show for a
// 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)
#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>
#include <hal/gpio_hal.h>
#include <hal/lcd_ll.h>
#include <soc/lcd_cam_reg.h>
#include <soc/lcd_cam_struct.h>
// Override the behavior of _PM_portBitMask macro so instead of returning
// a 32-bit mask for a pin within its corresponding GPIO register, it instead
// returns a 7-bit mask for the pin within the LCD_CAM data order *IF* it's
// one of the RGB bits or the clock bit...this requires comparing against pin
// numbers in the core struct.
static uint32_t _PM_directBitMask(Protomatter_core *core, int pin) {
if (pin == core->clockPin)
return 1 << 6;
for (uint8_t i = 0; i < 6; i++) {
if (pin == core->rgbPins[i])
return 1 << i;
}
// Else return the bit that would normally be used for regular GPIO
return (1U << (pin & 31));
}
// Thankfully, at present, any core code which calls _PM_portBitMask()
// currently has a 'core' variable, so we can safely do this...
#define _PM_portBitMask(pin) _PM_directBitMask(core, 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
IRAM_ATTR static void blast_byte(Protomatter_core *core, uint8_t *data) {
// Reset LCD DOUT parameters each time (required).
// IN PRINCIPLE, cyclelen should be chainBits-1 (resulting in chainBits
// cycles). But due to the required dummy phases at start of transfer,
// extend by 1; set to chainBits, issue chainBits+1 cycles.
LCD_CAM.lcd_user.lcd_dout_cyclelen = core->chainBits;
LCD_CAM.lcd_user.lcd_dout = 1;
LCD_CAM.lcd_user.lcd_update = 1;
// Reset LCD TX FIFO each time, else we see old data. When doing this,
// it's REQUIRED in the setup code to enable at least one dummy pulse,
// else the PCLK & data are randomly misaligned by 1-2 clocks!
LCD_CAM.lcd_misc.lcd_afifo_reset = 1;
// Partially re-init descriptor each time (required)
desc.dw0.size = desc.dw0.length = core->chainBits;
desc.buffer = data;
gdma_start(dma_chan, (intptr_t)&desc);
esp_rom_delay_us(1); // Necessary before starting xfer
LCD_CAM.lcd_user.lcd_start = 1; // Begin LCD DMA xfer
// 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) {
// 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 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.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_STRENGTH);
gpio_set_drive_capability(core->oe.pin, GPIO_DRIVE_STRENGTH);
for (uint8_t i = 0; i < core->numAddressLines; i++) {
gpio_set_drive_capability(core->addr[i].pin, GPIO_DRIVE_STRENGTH);
}
// 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
#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_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 ESP32S3

109
src/arch/esp32.h Normal file → Executable file
View file

@ -2,7 +2,7 @@
* @file esp32.h
*
* Part of Adafruit's Protomatter library for HUB75-style RGB LED matrices.
* This file contains ORIGINAL-ESP32-SPECIFIC CODE.
* 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
@ -17,23 +17,21 @@
#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(ESP32)
#if defined(CONFIG_IDF_TARGET_ESP32) // ORIGINAL ESP32, NOT S2/S3/etc.
#if defined(ARDUINO) // COMPILING FOR ARDUINO ------------------------------
#include "driver/timer.h"
#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)
#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)
@ -42,49 +40,84 @@
#define _PM_wordOffset(pin) (1 - ((pin & 31) / 16))
#endif
// No special peripheral setup on OG ESP32, just use common timer init...
#define _PM_timerInit(core) _PM_esp32commonTimerInit(core);
// 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 ------------------------------
// 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.
// S2, S3 replace the whole "blast" functions and don't use PEW. C3 can use
// the default PEW.
#if !defined(CONFIG_IDF_TARGET_ESP32S3) && !defined(CONFIG_IDF_TARGET_ESP32S2)
#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
#endif // end !ESP32S3/S2
// 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)
#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 top,
uint32_t match) {
hw_timer_t *timer = *(hw_timer_t **)tptr;
timerAlarmWrite(timer, top, 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 --------------------
#define _PM_STRICT_32BIT_IO (1)
// 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
// 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.
#endif // END CIRCUITPYTHON ------------------------------------------------

44
src/arch/nrf52.h Normal file → Executable file
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)
@ -136,33 +136,32 @@ void _PM_IRQ_HANDLER(void) {
// CODE COMMON TO ALL ENVIRONMENTS -----------------------------------------
void _PM_timerInit(Protomatter_core *core) {
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},
{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])
// Determine IRQn from timer address
uint8_t timerNum = 0;
while ((timerNum < NUM_TIMERS) &&
(timer[timerNum].tc != (NRF_TIMER_Type *)core->timer)) {
while ((timerNum < NUM_TIMERS) && (timer[timerNum].tc != tptr)) {
timerNum++;
}
if (timerNum >= NUM_TIMERS)
@ -184,25 +183,30 @@ void _PM_timerInit(Protomatter_core *core) {
NVIC_EnableIRQ(timer[timerNum].IRQn);
}
inline void _PM_timerStart(Protomatter_core *core, uint32_t period) {
volatile NRF_TIMER_Type *tc = (volatile NRF_TIMER_Type *)core->timer;
inline void _PM_timerStart(void *tptr, uint32_t top, uint32_t match) {
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->CC[0] = top;
tc->TASKS_START = 1; // Start timer
}
inline uint32_t _PM_timerGetCount(Protomatter_core *core) {
volatile NRF_TIMER_Type *tc = (volatile NRF_TIMER_Type *)core->timer;
tc->TASKS_CAPTURE[1] = 1; // Capture timer to CC[1] register
return tc->CC[1]; // (don't clobber value in CC[0])
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(Protomatter_core *core) {
volatile NRF_TIMER_Type *tc = (volatile NRF_TIMER_Type *)core->timer;
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(core);
return count;
__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");

View file

@ -1,265 +0,0 @@
/*!
* @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
#if defined(ARDUINO_ARCH_RP2040) || defined(PICO_BOARD) || \
defined(__RP2040__) || defined(__RP2350__)
#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
// 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 ------------------------------
// THIS CURRENTLY ONLY WORKS WITH THE PHILHOWER RP2040 CORE.
// mbed Arduino RP2040 core won't compile due to missing stdio.h.
// Also, much of this currently assumes GPXX pin numbers with no remapping.
// 'pin' here is GPXX # (might change if pin remapping gets added in core)
#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 // Use PWM for timing
// Arduino implementation is tied to a specific PWM slice & frequency
#define _PM_PWM_SLICE 0
#define _PM_PWM_DIV ((F_CPU + 20000000) / 40000000) // 125 MHz->3->~41.6 MHz
#define _PM_timerFreq (F_CPU / _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
#endif
// Initialize, but do not start, timer.
void _PM_timerInit(Protomatter_core *core) {
#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
}
#elif defined(CIRCUITPY) // COMPILING FOR CIRCUITPYTHON --------------------
#if !defined(F_CPU) // Not sure if CircuitPython build defines this
#ifdef __RP2040__
#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__
#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 (F_CPU / _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 // end PWM/alarm
// Initialize, but do not start, timer.
void _PM_timerInit(Protomatter_core *core) {
#if _PM_CLOCK_PWM
_PM_pwm_slice = (int)core->timer & 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
}
// 'pin' here is GPXX #
#define _PM_portBitMask(pin) (1UL << pin)
// Same for these -- using GPXX #
#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
#endif // end CIRCUITPY
#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)
#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;
#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(Protomatter_core *core, 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(Protomatter_core *core) {
#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(Protomatter_core *core) {
#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(core);
}
#if (F_CPU >= 250000000)
#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_clockHoldHigh asm("nop;");
#elif (F_CPU >= 125000000)
#define _PM_clockHoldLow asm("nop; nop; nop;");
#define _PM_clockHoldHigh asm("nop;");
#elif (F_CPU >= 100000000)
#define _PM_clockHoldLow asm("nop;");
#endif // No NOPs needed at lower speeds
#define _PM_chunkSize 8
#if _PM_CLOCK_PWM
#define _PM_minMinPeriod 100
#else
#define _PM_minMinPeriod 8
#endif
#endif // END RP2040

81
src/arch/samd-common.h Normal file → Executable file
View file

@ -17,7 +17,7 @@
#pragma once
#if defined(__SAMD51__) || defined(SAM_D5X_E5X) || defined(_SAMD21_) || \
#if defined(__SAMD51__) || defined(SAMD51) || defined(_SAMD21_) || \
defined(SAMD21)
#if defined(ARDUINO) // COMPILING FOR ARDUINO ------------------------------
@ -31,7 +31,10 @@
#define _PM_wordOffset(pin) (1 - (g_APinDescription[pin].ulPin / 16))
#endif
// Arduino implementation is tied to a specific timer/counter & freq:
// Arduino implementation is tied to a specific timer/counter & freq.
// 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 (declared above in the environment-neutral code).
#if defined(TC4)
#define _PM_TIMER_DEFAULT TC4
#define _PM_IRQ_HANDLER TC4_Handler
@ -39,22 +42,6 @@
#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 --------------------
@ -64,7 +51,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)
@ -74,25 +61,49 @@ void _PM_IRQ_HANDLER(void) {
#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
// SETTINGS COMMON TO ALL ENVIRONMENTS -------------------------------------
// Keep this down here because there's environment-specific defines above.
#define _PM_maxBitplanes 16 ///< RGB bit depth handled by architecture
// Arduino and CircuitPython implementations both rely on a 48 MHz timer.
// In Arduino the timer # is fixed, in CircuitPython it's dynamically
// allocated from a timer pool. If that's not the case in other
// enviromnents, this will need to be broken out into each section.
#define _PM_timerFreq 48000000
// As currently implemented, there can be only one instance of the
// Protomatter_core struct. This pointer is initialized when starting
// the matrix (in the environment-specific code -- e.g. matrix.begin()
// in the Arduino library).
void *_PM_protoPtr = NULL;
// Timer interrupt service routine. _PM_IRQ_HANDLER *must* be defined by
// environment (as with Arduino above) to trigger as an interrupt, else
// this is a function that must be invoked by interrupt function elsewhere
// (as with CircuitPython).
void _PM_IRQ_HANDLER(void) {
Protomatter_core *core = (Protomatter_core *)_PM_protoPtr;
Tc *timer = core->timer;
// If overflow flag is set, clear both the overflow AND match-compare
// bits (sometimes both are set if the two trigger in close proximity),
// handle the overflow case (load new row in core.c), don't bother with
// the match compare now, it's unneeded.
if (timer->COUNT16.INTFLAG.bit.OVF) {
timer->COUNT16.INTFLAG.reg = TC_INTFLAG_OVF | TC_INTFLAG_MC1;
_PM_row_handler(core);
} else {
// Otherwise, must be the match-compare bit only. Clear just the MC1
// flag (but not OVF) and disable the LEDs early (in core.c).
timer->COUNT16.INTFLAG.reg = TC_INTFLAG_MC1;
_PM_matrix_oe_off(core);
}
}
#endif // END SAMD51/SAMD21

47
src/arch/samd21.h Normal file → Executable file
View file

@ -43,32 +43,37 @@
// CODE COMMON TO ALL ENVIRONMENTS -----------------------------------------
// Note that the SAMD21 and SAMD51 functions are nearly but not actually
// identical -- the registers involved are different between the two.
// If any additions or bug fixes are made in one, check the other to see if
// similar changes should be made there.
// Initialize, but do not start, timer
void _PM_timerInit(Protomatter_core *core) {
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},
{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])
Tc *tc = (Tc *)core->timer; // Cast peripheral address passed in
Tc *tc = (Tc *)tptr; // Cast peripheral address passed in
uint8_t timerNum = 0;
while ((timerNum < NUM_TIMERS) && (timer[timerNum].tc != tc)) {
@ -99,8 +104,8 @@ void _PM_timerInit(Protomatter_core *core) {
while (tc->COUNT16.STATUS.bit.SYNCBUSY)
;
// Overflow interrupt
tc->COUNT16.INTENSET.reg = TC_INTENSET_OVF;
// Enable overflow and match-compare-1 interrupts
tc->COUNT16.INTENSET.reg = TC_INTENSET_OVF | TC_INTENSET_MC1;
NVIC_DisableIRQ(timer[timerNum].IRQn);
NVIC_ClearPendingIRQ(timer[timerNum].IRQn);
@ -113,12 +118,20 @@ void _PM_timerInit(Protomatter_core *core) {
// 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(Protomatter_core *core, uint32_t period) {
Tc *tc = (Tc *)core->timer; // Cast peripheral address passed in
// 'top' is the timer rollover period, 'match' is a compare-match value
// that can be used to de-assert the matrix OE line early (e.g. for
// brightness or gamma correction).
// This does not test the args for validity! Calling func should handle.
// It's OK if match exceeds top; interrupt simply doesn't happen then.
inline void _PM_timerStart(void *tptr, uint32_t top, uint32_t match) {
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;
tc->COUNT16.CC[0].reg = top;
while (tc->COUNT16.STATUS.bit.SYNCBUSY)
;
tc->COUNT16.CC[1].reg = match;
while (tc->COUNT16.STATUS.bit.SYNCBUSY)
;
tc->COUNT16.CTRLA.bit.ENABLE = 1;
@ -128,8 +141,8 @@ inline void _PM_timerStart(Protomatter_core *core, uint32_t period) {
// Return current count value (timer enabled or not).
// Timer must be previously initialized.
inline uint32_t _PM_timerGetCount(Protomatter_core *core) {
Tc *tc = (Tc *)core->timer; // Cast peripheral address passed in
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)
;
@ -138,9 +151,9 @@ inline uint32_t _PM_timerGetCount(Protomatter_core *core) {
// Disable timer and return current count value.
// Timer must be previously initialized.
inline uint32_t _PM_timerStop(Protomatter_core *core) {
Tc *tc = (Tc *)core->timer; // Cast peripheral address passed in
uint32_t count = _PM_timerGetCount(core);
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)
;

306
src/arch/samd51.h Normal file → Executable file
View file

@ -17,8 +17,7 @@
#pragma once
#if defined(__SAMD51__) || \
defined(SAM_D5X_E5X) // Arduino, Circuitpy SAMD5x / E5x defs
#if defined(__SAMD51__) || defined(SAMD51) // Arduino, Circuitpy SAMD51 defs
#if defined(ARDUINO) // COMPILING FOR ARDUINO ------------------------------
@ -47,21 +46,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
@ -70,56 +54,61 @@ static void _hi_drive(uint8_t pin) {
// CODE COMMON TO ALL ENVIRONMENTS -----------------------------------------
// Note that the SAMD21 and SAMD51 functions are nearly but not actually
// identical -- the registers involved are different between the two.
// If any additions or bug fixes are made in one, check the other to see if
// similar changes should be made there.
// Initialize, but do not start, timer
void _PM_timerInit(Protomatter_core *core) {
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},
{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])
Tc *tc = (Tc *)core->timer; // Cast peripheral address passed in
Tc *tc = (Tc *)tptr; // Cast peripheral address passed in
uint8_t timerNum = 0;
while ((timerNum < NUM_TIMERS) && (timer[timerNum].tc != tc)) {
@ -163,8 +152,8 @@ void _PM_timerInit(Protomatter_core *core) {
while (tc->COUNT16.SYNCBUSY.bit.CTRLB)
;
// Overflow interrupt
tc->COUNT16.INTENSET.reg = TC_INTENSET_OVF;
// Enable overflow and match-compare-1 interrupts
tc->COUNT16.INTENSET.reg = TC_INTENSET_OVF | TC_INTENSET_MC1;
NVIC_DisableIRQ(timer[timerNum].IRQn);
NVIC_ClearPendingIRQ(timer[timerNum].IRQn);
@ -172,33 +161,27 @@ 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.
// Timer must be initialized to 16-bit mode using the init function
// above, but must be inactive before calling this.
inline void _PM_timerStart(Protomatter_core *core, uint32_t period) {
Tc *tc = (Tc *)core->timer; // Cast peripheral address passed in
// 'top' is the timer rollover period, 'match' is a compare-match value
// that can be used to de-assert the matrix OE line early (e.g. for
// brightness or gamma correction), or pass 0xFFFFFFFF to ignore.
// This does not test the args for validity! Calling func should handle.
// It's OK if match exceeds top; interrupt simply doesn't happen then.
inline void _PM_timerStart(void *tptr, uint32_t top, uint32_t match) {
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;
tc->COUNT16.CC[0].reg = top;
while (tc->COUNT16.SYNCBUSY.bit.CC0)
;
tc->COUNT16.CC[1].reg = match;
while (tc->COUNT16.SYNCBUSY.bit.CC1)
;
tc->COUNT16.CTRLA.bit.ENABLE = 1;
while (tc->COUNT16.SYNCBUSY.bit.STATUS)
;
@ -206,8 +189,8 @@ inline void _PM_timerStart(Protomatter_core *core, uint32_t period) {
// Return current count value (timer enabled or not).
// Timer must be previously initialized.
inline uint32_t _PM_timerGetCount(Protomatter_core *core) {
Tc *tc = (Tc *)core->timer; // Cast peripheral address passed in
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
@ -216,213 +199,30 @@ inline uint32_t _PM_timerGetCount(Protomatter_core *core) {
// Disable timer and return current count value.
// Timer must be previously initialized.
uint32_t _PM_timerStop(Protomatter_core *core) {
Tc *tc = (Tc *)core->timer; // Cast peripheral address passed in
uint32_t count = _PM_timerGetCount(core);
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;
}
// 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
#endif // END __SAMD51__ || SAMD51

23
src/arch/stm32.h Normal file → Executable file
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) {
@ -93,8 +93,8 @@ void _PM_IRQ_HANDLER(void) {
}
// Initialize, but do not start, timer
void _PM_timerInit(Protomatter_core *core) {
TIM_TypeDef *tim_instance = (TIM_TypeDef *)core->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);
@ -114,22 +114,17 @@ void _PM_timerInit(Protomatter_core *core) {
NVIC_SetPriority(tim_irq, 0); // Top priority
}
inline void _PM_timerStart(Protomatter_core *core, uint32_t period) {
TIM_TypeDef *tim = core->timer;
inline void _PM_timerStart(void *tptr, uint32_t top, uint32_t match) {
TIM_TypeDef *tim = tptr;
tim->SR = 0;
tim->ARR = period;
tim->ARR = top;
tim->CR1 |= TIM_CR1_CEN;
tim->DIER |= TIM_DIER_UIE;
HAL_NVIC_EnableIRQ(stm_peripherals_timer_get_irqnum(tim));
}
inline uint32_t _PM_timerGetCount(Protomatter_core *core) {
TIM_TypeDef *tim = core->timer;
return tim->CNT;
}
uint32_t _PM_timerStop(Protomatter_core *core) {
TIM_TypeDef *tim = core->timer;
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;

22
src/arch/teensy4.h Normal file → Executable file
View file

@ -118,8 +118,8 @@ static void _PM_timerISR(void) {
}
// Initialize, but do not start, timer.
void _PM_timerInit(Protomatter_core *core) {
IMXRT_PIT_CHANNEL_t *timer = (IMXRT_PIT_CHANNEL_t *)core->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
@ -130,28 +130,28 @@ void _PM_timerInit(Protomatter_core *core) {
}
// Set timer period, initialize count value to zero, enable timer.
inline void _PM_timerStart(Protomatter_core *core, uint32_t period) {
IMXRT_PIT_CHANNEL_t *timer = (IMXRT_PIT_CHANNEL_t *)core->timer;
inline void _PM_timerStart(void *tptr, uint32_t top, uint32_t match) {
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->LDVAL = top; // Set load value
// timer->CVAL = top; // 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(Protomatter_core *core) {
IMXRT_PIT_CHANNEL_t *timer = (IMXRT_PIT_CHANNEL_t *)core->timer;
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(Protomatter_core *core) {
IMXRT_PIT_CHANNEL_t *timer = (IMXRT_PIT_CHANNEL_t *)core->timer;
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(core);
return _PM_timerGetCount(tptr);
}
#define _PM_clockHoldHigh \

File diff suppressed because it is too large Load diff

View file

@ -65,6 +65,8 @@ typedef struct {
uint32_t rgbAndClockMask; ///< PORT bit mask for RGB data + clock
volatile void *addrPortToggle; ///< See singleAddrPort below
void *screenData; ///< Per-bitplane RGB data for matrix
uint16_t remap_rb[32]; ///< Gamma or decimation table for red+blue
uint16_t remap_g[64]; ///< Gamma or decimation table for green
_PM_pin latch; ///< RGB data latch
_PM_pin oe; ///< !OE (LOW out enable)
_PM_pin *addr; ///< Array of address pins
@ -72,8 +74,7 @@ typedef struct {
uint32_t bitZeroPeriod; ///< Bitplane 0 timer period
uint32_t minPeriod; ///< Plane 0 timer period for ~250Hz
volatile uint32_t frameCount; ///< For estimating refresh rate
uint16_t width; ///< Matrix chain width only in bits
uint16_t chainBits; ///< Matrix chain width*tiling in bits
uint16_t width; ///< Matrix chain width in bits
uint8_t bytesPerElement; ///< Using 8, 16 or 32 bits of PORT?
uint8_t clockPin; ///< RGB clock pin identifier
uint8_t parallel; ///< Number of concurrent matrix outs
@ -81,7 +82,7 @@ typedef struct {
uint8_t portOffset; ///< Active 8- or 16-bit pos. in PORT
uint8_t numPlanes; ///< Display bitplanes (1 to 6)
uint8_t numRowPairs; ///< Addressable row pairs
int8_t tile; ///< Vertical tiling repetitions
uint8_t brightness; ///< Max brightness (if supported by arch)
bool doubleBuffer; ///< 2X buffers for clean switchover
bool singleAddrPort; ///< If 1, all addr lines on same PORT
volatile uint8_t activeBuffer; ///< Index of currently-displayed buf
@ -137,14 +138,6 @@ typedef struct {
@param doubleBuffer If true, two matrix buffers are allocated,
so changing display contents doesn't introduce
artifacts mid-conversion. Requires ~2X RAM.
@param tile If multiple matrices are chained and stacked
vertically (rather than or in addition to
horizontally), the number of vertical tiles is
specified here. Positive values indicate a
"progressive" arrangement (always left-to-right),
negative for a "serpentine" arrangement (alternating
180 degree orientation). Horizontal tiles are implied
in the 'bitWidth' argument.
@param timer Pointer to timer peripheral or timer-related
struct (architecture-dependent), or NULL to
use a default timer ID (also arch-dependent).
@ -162,7 +155,7 @@ extern ProtomatterStatus _PM_init(Protomatter_core *core, uint16_t bitWidth,
uint8_t *rgbList, uint8_t addrCount,
uint8_t *addrList, uint8_t clockPin,
uint8_t latchPin, uint8_t oePin,
bool doubleBuffer, int8_t tile, void *timer);
bool doubleBuffer, void *timer);
/*!
@brief Allocate display buffers and populate additional elements of a
@ -208,10 +201,14 @@ extern void _PM_deallocate(Protomatter_core *core);
*/
extern void _PM_row_handler(Protomatter_core *core);
// *********************************************************************
// NOTE: AS OF 1.3.0, TIMER-RELATED FUNCTIONS REQUIRE A Protomatter_core
// STRUCT POINTER, RATHER THAN A void* TIMER-RELATED POINTER.
// *********************************************************************
/*!
@brief Sometimes called by a timer interrupt to disable matrix output
prior to the normal bitplane switchover time (handled by the
row handler above). Usually only occurs on the least bitplanes
of high-depth setups.
@param core Pointer to Protomatter_core structure.
*/
extern void _PM_matrix_oe_off(Protomatter_core *core);
/*!
@brief Returns current value of frame counter and resets its value to
@ -226,27 +223,33 @@ extern uint32_t _PM_getFrameCount(Protomatter_core *core);
/*!
@brief Start (or restart) a timer/counter peripheral.
@param core Pointer to Protomatter core structure, from which timer
details can be derived.
@param period Timer 'top' / rollover value.
@param tptr Pointer to timer/counter peripheral OR a struct
encapsulating information about a timer/counter
periph (architecture-dependent).
@param top Timer 'top' / rollover value.
@param match Timer 'match' value, can be used to de-assert the matrix
OE line before the 'top' interval (e.g. for brightness or
gamma correction), or pass 0xFFFFFFFF to ignore.
*/
extern void _PM_timerStart(Protomatter_core *core, uint32_t period);
extern void _PM_timerStart(void *tptr, uint32_t top, uint32_t match);
/*!
@brief Stop timer/counter peripheral.
@param core Pointer to Protomatter core structure, from which timer
details can be derived.
@param tptr Pointer to timer/counter peripheral OR a struct
encapsulating information about a timer/counter
periph (architecture-dependent).
@return Counter value when timer was stopped.
*/
extern uint32_t _PM_timerStop(Protomatter_core *core);
extern uint32_t _PM_timerStop(void *tptr);
/*!
@brief Query a timer/counter peripheral's current count.
@param core Pointer to Protomatter core structure, from which timer
details can be derived.
@param tptr Pointer to timer/counter peripheral OR a struct
encapsulating information about a timer/counter
periph (architecture-dependent).
@return Counter value.
*/
extern uint32_t _PM_timerGetCount(Protomatter_core *core);
extern uint32_t _PM_timerGetCount(void *tptr);
/*!
@brief Pauses until the next vertical blank to avoid 'tearing' animation
@ -255,16 +258,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)
/*!