Compare commits
10 commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
32407dd631 | ||
|
|
316af7345d | ||
|
|
a53a33ef1a | ||
|
|
4869e64850 | ||
|
|
1615dc4c1d | ||
|
|
6f312c642f | ||
|
|
c9ecf7c735 | ||
|
|
0a75527d30 | ||
|
|
a413580aa2 | ||
|
|
da1c4fcc68 |
28 changed files with 1028 additions and 2683 deletions
10
.github/workflows/githubci.yml
vendored
10
.github/workflows/githubci.yml
vendored
|
|
@ -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
|
||||
|
|
|
|||
35
README.md
35
README.md
|
|
@ -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.
|
||||
|
|
|
|||
|
|
@ -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
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
}
|
||||
|
|
@ -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 ----------------------------
|
||||
|
|
|
|||
138
examples/pixeldust_64x64/pixeldust_64x64.ino
Normal file
138
examples/pixeldust_64x64/pixeldust_64x64.ino
Normal 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
|
||||
}
|
||||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
}
|
||||
|
|
@ -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
22
notes.txt
Normal 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.
|
||||
|
|
@ -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);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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
63
src/arch/arch.h
Normal file → Executable 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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
@ -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
|
||||
|
|
@ -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)
|
||||
|
|
@ -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
|
||||
|
|
@ -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
109
src/arch/esp32.h
Normal file → Executable 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
44
src/arch/nrf52.h
Normal file → Executable 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");
|
||||
|
|
|
|||
|
|
@ -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
81
src/arch/samd-common.h
Normal file → Executable 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
47
src/arch/samd21.h
Normal file → Executable 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
306
src/arch/samd51.h
Normal file → Executable 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
23
src/arch/stm32.h
Normal file → Executable 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
22
src/arch/teensy4.h
Normal file → Executable 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 \
|
||||
|
|
|
|||
988
src/core.c
988
src/core.c
File diff suppressed because it is too large
Load diff
65
src/core.h
65
src/core.h
|
|
@ -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)
|
||||
|
||||
/*!
|
||||
|
|
|
|||
Loading…
Reference in a new issue