Compare commits
188 commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
f83bac7e42 | ||
|
|
9c45cfb0e4 | ||
|
|
3614319ecd | ||
|
|
dbd84325c7 | ||
|
|
c8ad602b45 | ||
|
|
1037d3f080 | ||
|
|
d55e3bf833 | ||
|
|
0bd9873153 | ||
|
|
2156ca0407 | ||
|
|
fb9903f97d | ||
|
|
282920f9ce | ||
|
|
267d78d6cf | ||
|
|
386371d8fd | ||
|
|
ca4f8764be | ||
| cffac25915 | |||
| 7155be1a75 | |||
| e4506e5a57 | |||
|
|
eadf2ee814 | ||
|
|
d87532b044 | ||
|
|
68943a8fcd | ||
|
|
5ad5f33a16 | ||
|
|
429d625ba7 | ||
|
|
98a2da6da4 | ||
|
|
b0e9d045f8 | ||
|
|
40d0971635 | ||
|
|
1faaec39b3 | ||
|
|
67ac7d48d4 | ||
|
|
a6dec45c38 | ||
|
|
4430d1ceb5 | ||
|
|
c9c1189e9d | ||
|
|
4a23a0bc7d | ||
|
|
9a76ee2f81 | ||
|
|
d15b59728d | ||
|
|
67cd55d033 | ||
|
|
df50a2761b | ||
|
|
93ea4fa10d | ||
|
|
ed2e701871 | ||
|
|
eca749b742 | ||
|
|
f1956d2506 | ||
|
|
2c070c8640 | ||
|
|
b4788466de | ||
|
|
7bd6440c14 | ||
|
|
5f0b40ed59 | ||
|
|
9766126f13 | ||
|
|
afce1d5149 | ||
|
|
3be437876b | ||
|
|
bab07dfedb | ||
|
|
d18ce6e085 | ||
|
|
2216ca662f | ||
| 1deae76007 | |||
| 94a18cc08a | |||
|
|
9b88c0fead | ||
| 13b6c05d5f | |||
| 10666a9c50 | |||
| ecab2fa75e | |||
| 8419c30127 | |||
| a2711624df | |||
| ce18b6d465 | |||
| 8dd189169c | |||
| fc1d948108 | |||
| 794e92179f | |||
| 7a98ebed61 | |||
| 20ca3476e5 | |||
| e920fa9483 | |||
| 40a989b06d | |||
| ac972f78cd | |||
| 40ad09d4e9 | |||
| cade4a82f2 | |||
|
|
6967bffafe | ||
|
|
d802a5ca2a | ||
|
|
e263d6a890 | ||
|
|
5d5756737c | ||
|
|
5eefcf5316 | ||
|
|
6c8171cdca | ||
|
|
a22be49e73 | ||
|
|
ae2567aebe | ||
|
|
8e74b7308b | ||
|
|
20feb8a10d | ||
|
|
cc93ff18c3 | ||
|
|
000ca43f79 | ||
|
|
418c8e1728 | ||
|
|
a0a194ef4f | ||
|
|
a819183e61 | ||
|
|
9c8872efd1 | ||
|
|
c141ffbc46 | ||
|
|
0ba5e457d8 | ||
|
|
257a05e12a | ||
|
|
98fee126d8 | ||
|
|
db3c9f2720 | ||
|
|
94aeb8d31f | ||
|
|
a902f81142 | ||
|
|
0baa8bd097 | ||
|
|
be25ca91b7 | ||
|
|
996913d1c3 | ||
|
|
b3d8339719 | ||
|
|
b7c7c5d4f3 | ||
|
|
73c4852fe1 | ||
|
|
7469be5084 | ||
|
|
ee0f949da8 | ||
|
|
b28f9612d8 | ||
|
|
25d75cdd01 | ||
|
|
c90e29b310 | ||
|
|
c036d5a7f2 | ||
|
|
de4af68ef0 | ||
|
|
d52180b747 | ||
|
|
63b1973ab7 | ||
|
|
55194ee60f | ||
|
|
14fc498d35 | ||
|
|
5cf809733d | ||
|
|
4cb133d816 | ||
|
|
4c6a4abd10 | ||
|
|
9c6bba76af | ||
|
|
0c5fef8139 | ||
|
|
4721641f03 | ||
|
|
0ccd35551d | ||
|
|
d139dccc4d | ||
|
|
8536ff119f | ||
|
|
1e3f82fe2c | ||
|
|
b2574621aa | ||
|
|
7697ddf496 | ||
|
|
6f15c31371 | ||
|
|
c977db7641 | ||
|
|
0c6ee7ebfe | ||
|
|
3c3fc02a5f | ||
|
|
5efa76f440 | ||
|
|
b0893cfbbf | ||
|
|
11197228e8 | ||
|
|
2ef010d948 | ||
|
|
d0a07e14ad | ||
|
|
c54b683614 | ||
|
|
7fe6406aff | ||
| 594ed270d1 | |||
| f1a671d4b8 | |||
| f07cc8d6d6 | |||
| 53e040d202 | |||
| 1250705c7b | |||
|
|
98017c5734 | ||
|
|
711f0ad9ee | ||
| 62181db502 | |||
| 5f8469be05 | |||
|
|
c2c81ded11 | ||
| ea0a1d3ce3 | |||
| c982a053cd | |||
|
|
632cfcede2 | ||
|
|
1086f5e28f | ||
|
|
c3cccab105 | ||
|
|
d7e92f0b55 | ||
|
|
59e1d949cf | ||
|
|
9d80b0d6a6 | ||
|
|
ab56e7e88f | ||
|
|
e3b1c14d0d | ||
|
|
e80c101557 | ||
|
|
6a9a307f3a | ||
|
|
39de313d7e | ||
|
|
f2d0cde4e0 | ||
|
|
d3cda46436 | ||
|
|
d116005b60 | ||
|
|
c0c6413430 | ||
|
|
846d95aac1 | ||
|
|
19dc9058cd | ||
|
|
97e0929a17 | ||
|
|
e8018dd935 | ||
| 5e925cea7a | |||
| 9e71254732 | |||
| 092066ecc2 | |||
|
|
75965eb640 | ||
|
|
473c5dc3b4 | ||
|
|
560ad3e2de | ||
|
|
689f61c506 | ||
|
|
eacbdfdef4 | ||
|
|
63240eb990 | ||
|
|
7dd3ad3fec | ||
|
|
020427f66a | ||
|
|
b041e58718 | ||
|
|
c1d99cf422 | ||
|
|
902c16f491 | ||
|
|
9880ee62d9 | ||
|
|
db208212f3 | ||
|
|
7fd87fa56b | ||
|
|
b9b663e87a | ||
|
|
864cc77c62 | ||
| b0c261b2e6 | |||
|
|
4193bfaea1 | ||
|
|
9651720252 | ||
| 273aba770d | |||
| b58c78c9d2 | |||
|
|
de6b7704c5 | ||
|
|
d4f5ade40b |
29 changed files with 2904 additions and 925 deletions
10
.github/workflows/githubci.yml
vendored
10
.github/workflows/githubci.yml
vendored
|
|
@ -7,16 +7,16 @@ jobs:
|
||||||
strategy:
|
strategy:
|
||||||
fail-fast: false
|
fail-fast: false
|
||||||
matrix:
|
matrix:
|
||||||
arduino-platform: ["metro_m0", "metro_m4", "nrf52840", "esp32"]
|
arduino-platform: ["metro_m0", "metro_m4", "metroesp32s2", "feather_esp32s3", "feather_rp2040", "nrf52840", "esp32"]
|
||||||
|
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
|
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/setup-python@v1
|
- uses: actions/setup-python@v5
|
||||||
with:
|
with:
|
||||||
python-version: '3.x'
|
python-version: '3.8'
|
||||||
- uses: actions/checkout@v2
|
- uses: actions/checkout@v4
|
||||||
- uses: actions/checkout@v2
|
- uses: actions/checkout@v4
|
||||||
with:
|
with:
|
||||||
repository: adafruit/ci-arduino
|
repository: adafruit/ci-arduino
|
||||||
path: ci
|
path: ci
|
||||||
|
|
|
||||||
35
README.md
35
README.md
|
|
@ -64,9 +64,9 @@ This repository currently consists of:
|
||||||
|
|
||||||
# Arduino Library
|
# Arduino Library
|
||||||
|
|
||||||
This will likely supersede the RGBmatrixPanel library on non-AVR devices, as
|
This supersedes the RGBmatrixPanel library on non-AVR devices, as the older
|
||||||
the older library has painted itself into a few corners. The newer library
|
library has painted itself into a few corners. The newer library uses a
|
||||||
uses a single constructor for all matrix setups, potentially handling parallel
|
single constructor for all matrix setups, potentially handling parallel
|
||||||
chains (not yet fully implemented), various matrix sizes and chain lengths,
|
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
|
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.
|
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).
|
a lower bit depth (colors will be decimated/quantized in this case).
|
||||||
|
|
||||||
It does have some new limitations, mostly significant RAM overhead (hence
|
It does have some new limitations, mostly significant RAM overhead (hence
|
||||||
no plans for AVR port) and that all RGB data pins and the clock pin MUST be
|
no plans for AVR port) and (with a few exceptions) that all RGB data pins
|
||||||
on the same PORT register (e.g. all PORTA or PORTB, can't intermix). RAM
|
and the clock pin MUST be on the same PORT register (e.g. all PORTA or PORTB
|
||||||
overhead is somewhat reduced (but still large) if those pins are all in a
|
,can't intermix). RAM overhead is somewhat reduced (but still large) if
|
||||||
single 8-bit byte within the PORT (they do not need to be contiguous or
|
those pins are all in a single 8-bit byte within the PORT (they do not need
|
||||||
sequential within this byte, if for instance it makes PCB routing easier,
|
to be contiguous or sequential within this byte, if for instance it makes
|
||||||
but they should all aim for a single byte). Other pins (matrix address lines,
|
PCB routing easier, but they should all aim for a single byte). Other pins
|
||||||
latch and output enable) can reside on any PORT or bit.
|
(matrix address lines, latch and output enable) can reside on any PORT or bit.
|
||||||
|
|
||||||
# C Library
|
# C Library
|
||||||
|
|
||||||
|
|
@ -115,3 +115,18 @@ compile-time).
|
||||||
Most macros and functions begin with the prefix **\_PM\_** in order to
|
Most macros and functions begin with the prefix **\_PM\_** in order to
|
||||||
avoid naming collisions with other code (exception being static functions,
|
avoid naming collisions with other code (exception being static functions,
|
||||||
which can't be seen outside their source file).
|
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,8 @@
|
||||||
// Play GIFs from CIRCUITPY drive (USB-accessible filesystem) to LED matrix.
|
// Play GIFs from CIRCUITPY drive (USB-accessible filesystem) to LED matrix.
|
||||||
// Designed for Adafruit MatrixPortal M4, but may run on some other M4 & M0
|
// ***DESIGNED FOR ADAFRUIT MATRIXPORTAL***, but may run on some other M4,
|
||||||
// and nRF52 boards (relies on TinyUSB stack). As written, runs on 64x32 pixel
|
// M0, ESP32S3 and nRF52 boards (relies on TinyUSB stack). As written, runs
|
||||||
// matrix, this can be changed by editing the addrPins[] array (height) and/or
|
// on 64x32 pixel matrix, this can be changed by editing the WIDTH and HEIGHT
|
||||||
// matrix constructor call (width).
|
// definitions. See the "simple" example for a run-down on matrix config.
|
||||||
// Adapted from examples from Larry Bank's AnimatedGIF library and
|
// Adapted from examples from Larry Bank's AnimatedGIF library and
|
||||||
// msc_external_flash example in Adafruit_TinyUSB_Arduino.
|
// msc_external_flash example in Adafruit_TinyUSB_Arduino.
|
||||||
// Prerequisite libraries:
|
// Prerequisite libraries:
|
||||||
|
|
@ -25,11 +25,17 @@
|
||||||
|
|
||||||
char GIFpath[] = "/gifs"; // Absolute path to GIFs on CIRCUITPY drive
|
char GIFpath[] = "/gifs"; // Absolute path to GIFs on CIRCUITPY drive
|
||||||
uint16_t GIFminimumTime = 10; // Min. repeat time (seconds) until next GIF
|
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 --------------------------------------------------
|
// FLASH FILESYSTEM STUFF --------------------------------------------------
|
||||||
|
|
||||||
// External flash macros for QSPI or SPI are defined in board variant file.
|
// External flash macros for QSPI or SPI are defined in board variant file.
|
||||||
#if defined(EXTERNAL_FLASH_USE_QSPI)
|
#if defined(ARDUINO_ARCH_ESP32)
|
||||||
|
static Adafruit_FlashTransport_ESP32 flashTransport;
|
||||||
|
#elif defined(EXTERNAL_FLASH_USE_QSPI)
|
||||||
Adafruit_FlashTransport_QSPI flashTransport;
|
Adafruit_FlashTransport_QSPI flashTransport;
|
||||||
#elif defined(EXTERNAL_FLASH_USE_SPI)
|
#elif defined(EXTERNAL_FLASH_USE_SPI)
|
||||||
Adafruit_FlashTransport_SPI flashTransport(EXTERNAL_FLASH_USE_CS,
|
Adafruit_FlashTransport_SPI flashTransport(EXTERNAL_FLASH_USE_CS,
|
||||||
|
|
@ -46,28 +52,42 @@ Adafruit_USBD_MSC usb_msc; // USB mass storage object
|
||||||
|
|
||||||
#if defined(_VARIANT_MATRIXPORTAL_M4_)
|
#if defined(_VARIANT_MATRIXPORTAL_M4_)
|
||||||
uint8_t rgbPins[] = {7, 8, 9, 10, 11, 12};
|
uint8_t rgbPins[] = {7, 8, 9, 10, 11, 12};
|
||||||
uint8_t addrPins[] = {17, 18, 19, 20};
|
uint8_t addrPins[] = {17, 18, 19, 20, 21}; // 16/32/64 pixels tall
|
||||||
uint8_t clockPin = 14;
|
uint8_t clockPin = 14;
|
||||||
uint8_t latchPin = 15;
|
uint8_t latchPin = 15;
|
||||||
uint8_t oePin = 16;
|
uint8_t oePin = 16;
|
||||||
#define BACK_BUTTON 2
|
#define BACK_BUTTON 2
|
||||||
#define NEXT_BUTTON 3
|
#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_)
|
#elif defined(_VARIANT_METRO_M4_)
|
||||||
uint8_t rgbPins[] = {2, 3, 4, 5, 6, 7};
|
uint8_t rgbPins[] = {2, 3, 4, 5, 6, 7};
|
||||||
uint8_t addrPins[] = {A0, A1, A2, A3};
|
uint8_t addrPins[] = {A0, A1, A2, A3}; // 16 or 32 pixels tall
|
||||||
uint8_t clockPin = A4;
|
uint8_t clockPin = A4;
|
||||||
uint8_t latchPin = 10;
|
uint8_t latchPin = 10;
|
||||||
uint8_t oePin = 9;
|
uint8_t oePin = 9;
|
||||||
#elif defined(_VARIANT_FEATHER_M4_)
|
#elif defined(_VARIANT_FEATHER_M4_)
|
||||||
uint8_t rgbPins[] = {6, 5, 9, 11, 10, 12};
|
uint8_t rgbPins[] = {6, 5, 9, 11, 10, 12};
|
||||||
uint8_t addrPins[] = {A5, A4, A3, A2};
|
uint8_t addrPins[] = {A5, A4, A3, A2}; // 16 or 32 pixels tall
|
||||||
uint8_t clockPin = 13;
|
uint8_t clockPin = 13;
|
||||||
uint8_t latchPin = 0;
|
uint8_t latchPin = 0;
|
||||||
uint8_t oePin = 1;
|
uint8_t oePin = 1;
|
||||||
#endif
|
#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
|
||||||
|
|
||||||
// Matrix width is first arg here, height is inferred from addrPins[]
|
Adafruit_Protomatter matrix(WIDTH, 6, 1, rgbPins, NUM_ADDR_PINS, addrPins,
|
||||||
Adafruit_Protomatter matrix(64, 6, 1, rgbPins, sizeof addrPins, addrPins,
|
|
||||||
clockPin, latchPin, oePin, true);
|
clockPin, latchPin, oePin, true);
|
||||||
|
|
||||||
// ANIMATEDGIF LIBRARY STUFF -----------------------------------------------
|
// ANIMATEDGIF LIBRARY STUFF -----------------------------------------------
|
||||||
|
|
@ -79,7 +99,7 @@ int16_t xPos = 0, yPos = 0; // Top-left pixel coord of GIF in matrix space
|
||||||
// FILE ACCESS FUNCTIONS REQUIRED BY ANIMATED GIF LIB ----------------------
|
// FILE ACCESS FUNCTIONS REQUIRED BY ANIMATED GIF LIB ----------------------
|
||||||
|
|
||||||
// Pass in ABSOLUTE PATH of GIF file to open
|
// Pass in ABSOLUTE PATH of GIF file to open
|
||||||
void *GIFOpenFile(char *filename, int32_t *pSize) {
|
void *GIFOpenFile(const char *filename, int32_t *pSize) {
|
||||||
GIFfile = filesys.open(filename);
|
GIFfile = filesys.open(filename);
|
||||||
if (GIFfile) {
|
if (GIFfile) {
|
||||||
*pSize = GIFfile.size();
|
*pSize = GIFfile.size();
|
||||||
|
|
@ -332,7 +352,6 @@ void loop() {
|
||||||
GIFisOpen = false;
|
GIFisOpen = false;
|
||||||
}
|
}
|
||||||
GIFindex += GIFincrement; // Fwd or back 1 file
|
GIFindex += GIFincrement; // Fwd or back 1 file
|
||||||
GIFincrement = 0; // Reset increment flag
|
|
||||||
int num_files = numFiles(GIFpath, "GIF");
|
int num_files = numFiles(GIFpath, "GIF");
|
||||||
if(GIFindex >= num_files) GIFindex = 0; // 'Wrap around' file index
|
if(GIFindex >= num_files) GIFindex = 0; // 'Wrap around' file index
|
||||||
else if(GIFindex < 0) GIFindex = num_files - 1; // both directions
|
else if(GIFindex < 0) GIFindex = num_files - 1; // both directions
|
||||||
|
|
@ -351,15 +370,17 @@ void loop() {
|
||||||
yPos = (matrix.height() - GIF.getCanvasHeight()) / 2;
|
yPos = (matrix.height() - GIF.getCanvasHeight()) / 2;
|
||||||
GIFisOpen = true;
|
GIFisOpen = true;
|
||||||
GIFstartTime = millis();
|
GIFstartTime = millis();
|
||||||
|
GIFincrement = 0; // Reset increment flag
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} else if(GIFisOpen) {
|
} else if(GIFisOpen) {
|
||||||
if (GIF.playFrame(true, NULL)) {
|
if (GIF.playFrame(true, NULL) >= 0) { // Auto resets to start if needed
|
||||||
matrix.show();
|
matrix.show();
|
||||||
} else if ((millis() - GIFstartTime) < (GIFminimumTime * 1000)) {
|
if ((millis() - GIFstartTime) >= (GIFminimumTime * 1000)) {
|
||||||
GIF.reset(); // Minimum time hasn't elapsed yet, repeat this GIF
|
GIFincrement = 1; // Minimum time has elapsed, proceed to next GIF
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
GIFincrement = 1; // Minimum time has elapsed, proceed to next GIF
|
GIFincrement = 1; // Decode error, proceed to next GIF
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,192 +0,0 @@
|
||||||
#include "Adafruit_Protomatter.h"
|
|
||||||
|
|
||||||
/*
|
|
||||||
METRO M0 PORT-TO-PIN ASSIGNMENTS BY BYTE:
|
|
||||||
PA00 PA08 D4 PA16 D11 PB00 PB08 A1
|
|
||||||
PA01 PA09 D3 PA17 D13 PB01 PB09 A2
|
|
||||||
PA02 A0 PA10 D1 PA18 D10 PB02 A5 PB10 MOSI
|
|
||||||
PA03 PA11 D0 PA19 D12 PB03 PB11 SCK
|
|
||||||
PA04 A3 PA12 MISO PA20 D6 PB04 PB12
|
|
||||||
PA05 A4 PA13 PA21 D7 PB05 PB13
|
|
||||||
PA06 D8 PA14 D2 PA22 SDA PB06 PB14
|
|
||||||
PA07 D9 PA15 D5 PA23 SCL PB07 PB15
|
|
||||||
|
|
||||||
SAME, METRO M4:
|
|
||||||
PA00 PA08 PA16 D13 PB00 PB08 A4 PB16 D3
|
|
||||||
PA01 PA09 PA17 D12 PB01 PB09 A5 PB17 D2
|
|
||||||
PA02 A0 PA10 PA18 D10 PB02 SDA PB10 PB18
|
|
||||||
PA03 PA11 PA19 D11 PB03 SCL PB11 PB19
|
|
||||||
PA04 A3 PA12 MISO PA20 D9 PB04 PB12 D7 PB20
|
|
||||||
PA05 A1 PA13 SCK PA21 D8 PB05 PB13 D4 PB21
|
|
||||||
PA06 A2 PA14 MISO PA22 D1 PB06 PB14 D5 PB22
|
|
||||||
PA07 PA15 PA23 D0 PB07 PB15 D6 PB23
|
|
||||||
|
|
||||||
FEATHER M4:
|
|
||||||
PA00 PA08 PA16 D5 PB08 A2 PB16 D1/TX
|
|
||||||
PA01 PA09 PA17 SCK PB09 A3 PB17 D0/RX
|
|
||||||
PA02 A0 PA10 PA18 D6 PB10 PB18
|
|
||||||
PA03 PA11 PA19 D9 PB11 PB19
|
|
||||||
PA04 A4 PA12 SDA PA20 D10 PB12 PB20
|
|
||||||
PA05 A1 PA13 SCL PA21 D11 PB13 PB21
|
|
||||||
PA06 A5 PA14 D4 PA22 D12 PB14 PB22 MISO
|
|
||||||
PA07 PA15 PA23 D13 PB15 PB23 MOSI
|
|
||||||
|
|
||||||
FEATHER M0:
|
|
||||||
PA00 PA08 PA16 D11 PB00 PB08 A1
|
|
||||||
PA01 PA09 PA17 D13 PB01 PB09 A2
|
|
||||||
PA02 A0 PA10 TX/D1 PA18 D10 PB02 A5 PB10 MOSI
|
|
||||||
PA03 PA11 RX/D0 PA19 D12 PB03 PB11 SCK
|
|
||||||
PA04 A3 PA12 MISO PA20 D6 PB04 PB12
|
|
||||||
PA05 A4 PA13 PA21 D7 PB05 PB13
|
|
||||||
PA06 PA14 PA22 SDA PB06 PB14
|
|
||||||
PA07 D9 PA15 D5 PA23 SCL PB07 PB15
|
|
||||||
|
|
||||||
FEATHER nRF52840:
|
|
||||||
P0.00 P0.08 D12 P0.24 RXD P1.08 D5
|
|
||||||
P0.01 P0.09 P0.25 TXD P1.09 D13
|
|
||||||
P0.02 A4 P0.10 D2 (NFC) P0.26 D9 P1.10
|
|
||||||
P0.03 A5 P0.11 SCL P0.27 D10 P1.11
|
|
||||||
P0.04 A0 P0.12 SDA P0.28 A3 P1.12
|
|
||||||
P0.05 A1 P0.13 MOSI P0.29 P1.13
|
|
||||||
P0.06 D11 P0.14 SCK P0.30 A2 P1.14
|
|
||||||
P0.07 D6 P0.15 MISO P0.31 P1.15
|
|
||||||
|
|
||||||
FEATHER ESP32:
|
|
||||||
P0.00 P0.08 P0.16 16/RX P0.24 P1.00 32/A7
|
|
||||||
P0.01 P0.09 P0.17 17/TX P0.25 25/A1 P1.01 33/A9/SS
|
|
||||||
P0.02 P0.10 P0.18 18/MOSI P0.26 26/A0 P1.02 34/A2 (in)
|
|
||||||
P0.03 P0.11 P0.19 19/MISO P0.27 27/A10 P1.03
|
|
||||||
P0.04 4/A5 P0.12 12/A11 P0.20 P0.28 P1.04 36/A4 (in)
|
|
||||||
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)
|
|
||||||
|
|
||||||
RGB Matrix FeatherWing:
|
|
||||||
R1 D6 A A5
|
|
||||||
G1 D5 B A4
|
|
||||||
B1 D9 C A3
|
|
||||||
R2 D11 D A2
|
|
||||||
G2 D10 LAT D0/RX
|
|
||||||
B2 D12 OE D1/TX
|
|
||||||
CLK D13
|
|
||||||
RGB+clock in one PORT byte on Feather M4!
|
|
||||||
RGB+clock are on same PORT but not within same byte on Feather M0 --
|
|
||||||
the code could run there (with some work to be done in the convert_*
|
|
||||||
functions), but would be super RAM-inefficient. Should be fine on other
|
|
||||||
M0 devices like a Metro, if wiring manually so one can pick a contiguous
|
|
||||||
byte of PORT bits.
|
|
||||||
RGB+clock are on different PORTs on nRF52840.
|
|
||||||
*/
|
|
||||||
|
|
||||||
#if defined(__SAMD51__)
|
|
||||||
// Use FeatherWing pinout
|
|
||||||
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_)
|
|
||||||
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 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, skips 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 (yes that's a 38, NOT 28!)
|
|
||||||
uint8_t addrPins[] = {2, 3, 4, 5};
|
|
||||||
uint8_t clockPin = 23; // A9
|
|
||||||
uint8_t latchPin = 6;
|
|
||||||
uint8_t oePin = 9;
|
|
||||||
#endif
|
|
||||||
|
|
||||||
// Last arg here enables double-buffering
|
|
||||||
Adafruit_Protomatter matrix(
|
|
||||||
64, 6, 1, rgbPins, 4, addrPins, clockPin, latchPin, oePin, true);
|
|
||||||
|
|
||||||
int16_t textMin,
|
|
||||||
textX = matrix.width(),
|
|
||||||
hue = 0;
|
|
||||||
char str[40];
|
|
||||||
int8_t ball[3][4] = {
|
|
||||||
{ 3, 0, 1, 1 }, // Initial X,Y pos & velocity for 3 bouncy balls
|
|
||||||
{ 17, 15, 1, -1 },
|
|
||||||
{ 27, 4, -1, 1 }
|
|
||||||
};
|
|
||||||
|
|
||||||
const uint16_t ballcolor[3] = {
|
|
||||||
0b0000000001000000, // Dark green
|
|
||||||
0b0000000000000001, // Dark blue
|
|
||||||
0b0000100000000000 // Dark red
|
|
||||||
};
|
|
||||||
|
|
||||||
void setup(void) {
|
|
||||||
Serial.begin(9600);
|
|
||||||
|
|
||||||
ProtomatterStatus status = matrix.begin();
|
|
||||||
Serial.print("Protomatter begin() status: ");
|
|
||||||
Serial.println((int)status);
|
|
||||||
|
|
||||||
sprintf(str, "Adafruit %dx%d RGB LED Matrix",
|
|
||||||
matrix.width(), matrix.height());
|
|
||||||
textMin = strlen(str) * -12;
|
|
||||||
matrix.setTextWrap(false);
|
|
||||||
matrix.setTextSize(2);
|
|
||||||
matrix.setTextColor(0xFFFF); // White
|
|
||||||
}
|
|
||||||
|
|
||||||
void loop(void) {
|
|
||||||
byte i;
|
|
||||||
|
|
||||||
// Clear background
|
|
||||||
matrix.fillScreen(0);
|
|
||||||
|
|
||||||
// Bounce three balls around
|
|
||||||
for(i=0; i<3; i++) {
|
|
||||||
// Draw 'ball'
|
|
||||||
matrix.fillCircle(ball[i][0], ball[i][1], 5, ballcolor[i]);
|
|
||||||
// Update X, Y position
|
|
||||||
ball[i][0] += ball[i][2];
|
|
||||||
ball[i][1] += ball[i][3];
|
|
||||||
// Bounce off edges
|
|
||||||
if((ball[i][0] == 0) || (ball[i][0] == (matrix.width() - 1)))
|
|
||||||
ball[i][2] *= -1;
|
|
||||||
if((ball[i][1] == 0) || (ball[i][1] == (matrix.height() - 1)))
|
|
||||||
ball[i][3] *= -1;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Draw big scrolly text on top
|
|
||||||
matrix.setCursor(textX, 1);
|
|
||||||
matrix.print(str);
|
|
||||||
|
|
||||||
// Move text left (w/wrap), increase hue
|
|
||||||
if((--textX) < textMin) textX = matrix.width();
|
|
||||||
hue += 7;
|
|
||||||
if(hue >= 1536) hue -= 1536;
|
|
||||||
|
|
||||||
matrix.show();
|
|
||||||
|
|
||||||
delay(20);
|
|
||||||
}
|
|
||||||
212
examples/doublebuffer_scrolltext/doublebuffer_scrolltext.ino
Normal file
212
examples/doublebuffer_scrolltext/doublebuffer_scrolltext.ino
Normal file
|
|
@ -0,0 +1,212 @@
|
||||||
|
/* ----------------------------------------------------------------------
|
||||||
|
Double-buffering (smooth animation) Protomatter library example.
|
||||||
|
PLEASE SEE THE "simple" EXAMPLE FOR AN INTRODUCTORY SKETCH.
|
||||||
|
Comments here pare down many of the basics and focus on the new concepts.
|
||||||
|
|
||||||
|
This example is written for a 64x32 matrix but can be adapted to others.
|
||||||
|
------------------------------------------------------------------------- */
|
||||||
|
|
||||||
|
#include <Adafruit_Protomatter.h>
|
||||||
|
#include <Fonts/FreeSansBold18pt7b.h> // Large friendly font
|
||||||
|
|
||||||
|
/* ----------------------------------------------------------------------
|
||||||
|
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 "true" for the last argument,
|
||||||
|
enabling double-buffering -- this permits smooth animation by having us
|
||||||
|
draw in a second "off screen" buffer while the other is being shown.
|
||||||
|
------------------------------------------------------------------------- */
|
||||||
|
|
||||||
|
Adafruit_Protomatter matrix(
|
||||||
|
64, // Matrix width in pixels
|
||||||
|
6, // Bit depth -- 6 here provides maximum color options
|
||||||
|
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!
|
||||||
|
|
||||||
|
// 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
|
||||||
|
{ 17, 15, 1, -1 },
|
||||||
|
{ 27, 4, -1, 1 }
|
||||||
|
};
|
||||||
|
uint16_t ballcolor[3]; // Colors for bouncy balls (init in setup())
|
||||||
|
|
||||||
|
// 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(;;);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Unlike the "simple" example, we don't do any drawing in setup().
|
||||||
|
// But we DO initialize some things we plan to animate...
|
||||||
|
|
||||||
|
// Set up the scrolling message...
|
||||||
|
sprintf(str, "Adafruit %dx%d RGB LED Matrix",
|
||||||
|
matrix.width(), matrix.height());
|
||||||
|
matrix.setFont(&FreeSansBold18pt7b); // Use nice bitmap font
|
||||||
|
matrix.setTextWrap(false); // Allow text off edge
|
||||||
|
matrix.setTextColor(0xFFFF); // White
|
||||||
|
int16_t x1, y1;
|
||||||
|
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
|
||||||
|
}
|
||||||
|
|
||||||
|
// LOOP - RUNS REPEATEDLY AFTER SETUP --------------------------------------
|
||||||
|
|
||||||
|
void loop(void) {
|
||||||
|
// Every frame, we clear the background and draw everything anew.
|
||||||
|
// This happens "in the background" with double buffering, that's
|
||||||
|
// why you don't see everything flicker. It requires double the RAM,
|
||||||
|
// so it's not practical for every situation.
|
||||||
|
|
||||||
|
matrix.fillScreen(0); // Fill background black
|
||||||
|
|
||||||
|
// Draw the big scrolly text
|
||||||
|
matrix.setCursor(textX, textY);
|
||||||
|
matrix.print(str);
|
||||||
|
|
||||||
|
// Update text position for next frame. If text goes off the
|
||||||
|
// left edge, reset its position to be off the right edge.
|
||||||
|
if((--textX) < textMin) textX = matrix.width();
|
||||||
|
|
||||||
|
// Draw the three bouncy balls on top of the text...
|
||||||
|
for(byte i=0; i<3; i++) {
|
||||||
|
// Draw 'ball'
|
||||||
|
matrix.fillCircle(ball[i][0], ball[i][1], 5, ballcolor[i]);
|
||||||
|
// Update ball's X,Y position for next frame
|
||||||
|
ball[i][0] += ball[i][2];
|
||||||
|
ball[i][1] += ball[i][3];
|
||||||
|
// Bounce off edges
|
||||||
|
if((ball[i][0] == 0) || (ball[i][0] == (matrix.width() - 1)))
|
||||||
|
ball[i][2] *= -1;
|
||||||
|
if((ball[i][1] == 0) || (ball[i][1] == (matrix.height() - 1)))
|
||||||
|
ball[i][3] *= -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
// AFTER DRAWING, A show() CALL IS REQUIRED TO UPDATE THE MATRIX!
|
||||||
|
|
||||||
|
matrix.show();
|
||||||
|
|
||||||
|
delay(20); // 20 milliseconds = ~50 frames/second
|
||||||
|
}
|
||||||
|
|
@ -1,34 +1,58 @@
|
||||||
#include <Wire.h> // For I2C communication
|
/* ----------------------------------------------------------------------
|
||||||
#include <Adafruit_LIS3DH.h>
|
"Pixel dust" Protomatter library example. As written, this is
|
||||||
#include <Adafruit_PixelDust.h> // For simulation
|
SPECIFICALLY FOR THE ADAFRUIT MATRIXPORTAL with 64x32 pixel matrix.
|
||||||
#include "Adafruit_Protomatter.h"
|
Change "HEIGHT" below for 64x64 matrix. Could also 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
|
||||||
|
|
||||||
|
#define HEIGHT 32 // Matrix height (pixels) - SET TO 64 FOR 64x64 MATRIX!
|
||||||
|
#define WIDTH 64 // Matrix width (pixels)
|
||||||
|
#define MAX_FPS 45 // Maximum redraw rate, frames/second
|
||||||
|
|
||||||
|
#if defined(_VARIANT_MATRIXPORTAL_M4_) // MatrixPortal M4
|
||||||
uint8_t rgbPins[] = {7, 8, 9, 10, 11, 12};
|
uint8_t rgbPins[] = {7, 8, 9, 10, 11, 12};
|
||||||
uint8_t addrPins[] = {17, 18, 19, 20};
|
uint8_t addrPins[] = {17, 18, 19, 20, 21};
|
||||||
uint8_t clockPin = 14;
|
uint8_t clockPin = 14;
|
||||||
uint8_t latchPin = 15;
|
uint8_t latchPin = 15;
|
||||||
uint8_t oePin = 16;
|
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_LIS3DH accel = Adafruit_LIS3DH();
|
||||||
|
|
||||||
Adafruit_Protomatter matrix(
|
#define N_COLORS 8
|
||||||
64, 4, 1, rgbPins, 4, addrPins, clockPin, latchPin, oePin, true);
|
|
||||||
|
|
||||||
|
|
||||||
#define WIDTH 64 // Display width in pixels
|
|
||||||
#define HEIGHT 32 // Display height in pixels
|
|
||||||
#define MAX_FPS 45 // Maximum redraw rate, frames/second
|
|
||||||
|
|
||||||
#define N_COLORS 8
|
|
||||||
#define BOX_HEIGHT 8
|
#define BOX_HEIGHT 8
|
||||||
#define N_GRAINS (BOX_HEIGHT*N_COLORS*8)
|
#define N_GRAINS (BOX_HEIGHT*N_COLORS*8)
|
||||||
uint16_t colors[N_COLORS];
|
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);
|
Adafruit_PixelDust sand(WIDTH, HEIGHT, N_GRAINS, 1, 128, false);
|
||||||
|
|
||||||
|
uint32_t prevTime = 0; // Used for frames-per-second throttle
|
||||||
uint32_t prevTime = 0; // Used for frames-per-second throttle
|
|
||||||
|
|
||||||
// SETUP - RUNS ONCE AT PROGRAM START --------------------------------------
|
// SETUP - RUNS ONCE AT PROGRAM START --------------------------------------
|
||||||
|
|
||||||
|
|
@ -42,24 +66,23 @@ void err(int x) {
|
||||||
}
|
}
|
||||||
|
|
||||||
void setup(void) {
|
void setup(void) {
|
||||||
uint8_t i, j, bytes;
|
|
||||||
Serial.begin(115200);
|
Serial.begin(115200);
|
||||||
//while (!Serial) delay(10);
|
//while (!Serial) delay(10);
|
||||||
|
|
||||||
ProtomatterStatus status = matrix.begin();
|
ProtomatterStatus status = matrix.begin();
|
||||||
Serial.printf("Protomatter begin() status: %d\n", status);
|
Serial.printf("Protomatter begin() status: %d\n", status);
|
||||||
|
|
||||||
if (!sand.begin()) {
|
if (!sand.begin()) {
|
||||||
Serial.println("Couldn't start sand");
|
Serial.println("Couldn't start sand");
|
||||||
err(1000); // Slow blink = malloc error
|
err(1000); // Slow blink = malloc error
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!accel.begin(0x19)) {
|
if (!accel.begin(0x19)) {
|
||||||
Serial.println("Couldn't find accelerometer");
|
Serial.println("Couldn't find accelerometer");
|
||||||
err(250); // Fast bink = I2C error
|
err(250); // Fast bink = I2C error
|
||||||
}
|
}
|
||||||
accel.setRange(LIS3DH_RANGE_4_G); // 2, 4, 8 or 16 G!
|
accel.setRange(LIS3DH_RANGE_4_G); // 2, 4, 8 or 16 G!
|
||||||
|
|
||||||
//sand.randomize(); // Initialize random sand positions
|
//sand.randomize(); // Initialize random sand positions
|
||||||
|
|
||||||
// Set up initial sand coordinates, in 8x8 blocks
|
// Set up initial sand coordinates, in 8x8 blocks
|
||||||
|
|
@ -76,19 +99,16 @@ void setup(void) {
|
||||||
}
|
}
|
||||||
Serial.printf("%d total pixels\n", n);
|
Serial.printf("%d total pixels\n", n);
|
||||||
|
|
||||||
colors[0] = color565(64, 64, 64); // Dark Gray
|
colors[0] = matrix.color565(64, 64, 64); // Dark Gray
|
||||||
colors[1] = color565(120, 79, 23); // Brown
|
colors[1] = matrix.color565(120, 79, 23); // Brown
|
||||||
colors[2] = color565(228, 3, 3); // Red
|
colors[2] = matrix.color565(228, 3, 3); // Red
|
||||||
colors[3] = color565(255,140, 0); // Orange
|
colors[3] = matrix.color565(255,140, 0); // Orange
|
||||||
colors[4] = color565(255,237, 0); // Yellow
|
colors[4] = matrix.color565(255,237, 0); // Yellow
|
||||||
colors[5] = color565( 0,128, 38); // Green
|
colors[5] = matrix.color565( 0,128, 38); // Green
|
||||||
colors[6] = color565( 0, 77,255); // Blue
|
colors[6] = matrix.color565( 0, 77,255); // Blue
|
||||||
colors[7] = color565(117, 7,135); // Purple
|
colors[7] = matrix.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 ----------------------------
|
// MAIN LOOP - RUNS ONCE PER FRAME OF ANIMATION ----------------------------
|
||||||
|
|
||||||
void loop() {
|
void loop() {
|
||||||
|
|
@ -99,7 +119,7 @@ void loop() {
|
||||||
uint32_t t;
|
uint32_t t;
|
||||||
while(((t = micros()) - prevTime) < (1000000L / MAX_FPS));
|
while(((t = micros()) - prevTime) < (1000000L / MAX_FPS));
|
||||||
prevTime = t;
|
prevTime = t;
|
||||||
|
|
||||||
// Read accelerometer...
|
// Read accelerometer...
|
||||||
sensors_event_t event;
|
sensors_event_t event;
|
||||||
accel.getEvent(&event);
|
accel.getEvent(&event);
|
||||||
|
|
@ -112,7 +132,7 @@ void loop() {
|
||||||
|
|
||||||
// Run one frame of the simulation
|
// Run one frame of the simulation
|
||||||
sand.iterate(xx, yy, zz);
|
sand.iterate(xx, yy, zz);
|
||||||
|
|
||||||
//sand.iterate(-accel.y, accel.x, accel.z);
|
//sand.iterate(-accel.y, accel.x, accel.z);
|
||||||
|
|
||||||
// Update pixel data in LED driver
|
// Update pixel data in LED driver
|
||||||
|
|
@ -126,6 +146,4 @@ void loop() {
|
||||||
//Serial.printf("(%d, %d)\n", x, y);
|
//Serial.printf("(%d, %d)\n", x, y);
|
||||||
}
|
}
|
||||||
matrix.show(); // Copy data to matrix buffers
|
matrix.show(); // Copy data to matrix buffers
|
||||||
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,131 +0,0 @@
|
||||||
#include <Wire.h> // For I2C communication
|
|
||||||
#include <Adafruit_LIS3DH.h>
|
|
||||||
#include <Adafruit_PixelDust.h> // For simulation
|
|
||||||
#include "Adafruit_Protomatter.h"
|
|
||||||
|
|
||||||
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(
|
|
||||||
64, 4, 1, rgbPins, 5, addrPins, clockPin, latchPin, oePin, true);
|
|
||||||
|
|
||||||
|
|
||||||
#define WIDTH 64 // Display width in pixels
|
|
||||||
#define HEIGHT 64 // Display height in pixels
|
|
||||||
#define MAX_FPS 45 // Maximum redraw rate, frames/second
|
|
||||||
|
|
||||||
#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
|
|
||||||
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
@ -1,155 +0,0 @@
|
||||||
#include "Adafruit_Protomatter.h"
|
|
||||||
|
|
||||||
/*
|
|
||||||
METRO M0 PORT-TO-PIN ASSIGNMENTS BY BYTE:
|
|
||||||
PA00 PA08 D4 PA16 D11 PB00 PB08 A1
|
|
||||||
PA01 PA09 D3 PA17 D13 PB01 PB09 A2
|
|
||||||
PA02 A0 PA10 D1 PA18 D10 PB02 A5 PB10 MOSI
|
|
||||||
PA03 PA11 D0 PA19 D12 PB03 PB11 SCK
|
|
||||||
PA04 A3 PA12 MISO PA20 D6 PB04 PB12
|
|
||||||
PA05 A4 PA13 PA21 D7 PB05 PB13
|
|
||||||
PA06 D8 PA14 D2 PA22 SDA PB06 PB14
|
|
||||||
PA07 D9 PA15 D5 PA23 SCL PB07 PB15
|
|
||||||
|
|
||||||
SAME, METRO M4:
|
|
||||||
PA00 PA08 PA16 D13 PB00 PB08 A4 PB16 D3
|
|
||||||
PA01 PA09 PA17 D12 PB01 PB09 A5 PB17 D2
|
|
||||||
PA02 A0 PA10 PA18 D10 PB02 SDA PB10 PB18
|
|
||||||
PA03 PA11 PA19 D11 PB03 SCL PB11 PB19
|
|
||||||
PA04 A3 PA12 MISO PA20 D9 PB04 PB12 D7 PB20
|
|
||||||
PA05 A1 PA13 SCK PA21 D8 PB05 PB13 D4 PB21
|
|
||||||
PA06 A2 PA14 MISO PA22 D1 PB06 PB14 D5 PB22
|
|
||||||
PA07 PA15 PA23 D0 PB07 PB15 D6 PB23
|
|
||||||
|
|
||||||
FEATHER M4:
|
|
||||||
PA00 PA08 PA16 D5 PB08 A2 PB16 D1/TX
|
|
||||||
PA01 PA09 PA17 SCK PB09 A3 PB17 D0/RX
|
|
||||||
PA02 A0 PA10 PA18 D6 PB10 PB18
|
|
||||||
PA03 PA11 PA19 D9 PB11 PB19
|
|
||||||
PA04 A4 PA12 SDA PA20 D10 PB12 PB20
|
|
||||||
PA05 A1 PA13 SCL PA21 D11 PB13 PB21
|
|
||||||
PA06 A5 PA14 D4 PA22 D12 PB14 PB22 MISO
|
|
||||||
PA07 PA15 PA23 D13 PB15 PB23 MOSI
|
|
||||||
|
|
||||||
FEATHER M0:
|
|
||||||
PA00 PA08 PA16 D11 PB00 PB08 A1
|
|
||||||
PA01 PA09 PA17 D13 PB01 PB09 A2
|
|
||||||
PA02 A0 PA10 TX/D1 PA18 D10 PB02 A5 PB10 MOSI
|
|
||||||
PA03 PA11 RX/D0 PA19 D12 PB03 PB11 SCK
|
|
||||||
PA04 A3 PA12 MISO PA20 D6 PB04 PB12
|
|
||||||
PA05 A4 PA13 PA21 D7 PB05 PB13
|
|
||||||
PA06 PA14 PA22 SDA PB06 PB14
|
|
||||||
PA07 D9 PA15 D5 PA23 SCL PB07 PB15
|
|
||||||
|
|
||||||
FEATHER nRF52840:
|
|
||||||
P0.00 P0.08 D12 P0.24 RXD P1.08 D5
|
|
||||||
P0.01 P0.09 P0.25 TXD P1.09 D13
|
|
||||||
P0.02 A4 P0.10 D2 (NFC) P0.26 D9 P1.10
|
|
||||||
P0.03 A5 P0.11 SCL P0.27 D10 P1.11
|
|
||||||
P0.04 A0 P0.12 SDA P0.28 A3 P1.12
|
|
||||||
P0.05 A1 P0.13 MOSI P0.29 P1.13
|
|
||||||
P0.06 D11 P0.14 SCK P0.30 A2 P1.14
|
|
||||||
P0.07 D6 P0.15 MISO P0.31 P1.15
|
|
||||||
|
|
||||||
FEATHER ESP32:
|
|
||||||
P0.00 P0.08 P0.16 16/RX P0.24 P1.00 32/A7
|
|
||||||
P0.01 P0.09 P0.17 17/TX P0.25 25/A1 P1.01 33/A9/SS
|
|
||||||
P0.02 P0.10 P0.18 18/MOSI P0.26 26/A0 P1.02 34/A2 (in)
|
|
||||||
P0.03 P0.11 P0.19 19/MISO P0.27 27/A10 P1.03
|
|
||||||
P0.04 4/A5 P0.12 12/A11 P0.20 P0.28 P1.04 36/A4 (in)
|
|
||||||
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)
|
|
||||||
|
|
||||||
RGB Matrix FeatherWing:
|
|
||||||
R1 D6 A A5
|
|
||||||
G1 D5 B A4
|
|
||||||
B1 D9 C A3
|
|
||||||
R2 D11 D A2
|
|
||||||
G2 D10 LAT D0/RX
|
|
||||||
B2 D12 OE D1/TX
|
|
||||||
CLK D13
|
|
||||||
RGB+clock in one PORT byte on Feather M4!
|
|
||||||
RGB+clock are on same PORT but not within same byte on Feather M0 --
|
|
||||||
the code could run there (with some work to be done in the convert_*
|
|
||||||
functions), but would be super RAM-inefficient. Should be fine on other
|
|
||||||
M0 devices like a Metro, if wiring manually so one can pick a contiguous
|
|
||||||
byte of PORT bits.
|
|
||||||
RGB+clock are on different PORTs on nRF52840.
|
|
||||||
*/
|
|
||||||
|
|
||||||
#if defined(__SAMD51__)
|
|
||||||
// Use FeatherWing pinout
|
|
||||||
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_)
|
|
||||||
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 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, skips 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 (yes that's a 38, NOT 28!)
|
|
||||||
uint8_t addrPins[] = {2, 3, 4, 5};
|
|
||||||
uint8_t clockPin = 23; // A9
|
|
||||||
uint8_t latchPin = 6;
|
|
||||||
uint8_t oePin = 9;
|
|
||||||
#endif
|
|
||||||
|
|
||||||
Adafruit_Protomatter matrix(
|
|
||||||
64, 4, 1, rgbPins, 4, addrPins, clockPin, latchPin, oePin, false);
|
|
||||||
|
|
||||||
void setup(void) {
|
|
||||||
Serial.begin(9600);
|
|
||||||
|
|
||||||
ProtomatterStatus status = matrix.begin();
|
|
||||||
Serial.print("Protomatter begin() status: ");
|
|
||||||
Serial.println((int)status);
|
|
||||||
|
|
||||||
for(int x=0; x<matrix.width(); x++) {
|
|
||||||
uint8_t rb = x * 32 / matrix.width();
|
|
||||||
uint8_t g = x * 64 / matrix.width();
|
|
||||||
matrix.drawPixel(x, matrix.height() - 4, rb << 11);
|
|
||||||
matrix.drawPixel(x, matrix.height() - 3, g << 5);
|
|
||||||
matrix.drawPixel(x, matrix.height() - 2, rb);
|
|
||||||
matrix.drawPixel(x, matrix.height() - 1, (rb << 11) | (g << 5) | rb);
|
|
||||||
}
|
|
||||||
|
|
||||||
matrix.drawCircle(12, 10, 9, 0b1111100000000000); // Red
|
|
||||||
matrix.drawCircle(22, 14, 9, 0b0000011111100000); // Green
|
|
||||||
matrix.drawCircle(32, 18, 9, 0b0000000000011111); // Blue
|
|
||||||
matrix.println("ADAFRUIT");
|
|
||||||
matrix.show(); // Copy data to matrix buffers
|
|
||||||
}
|
|
||||||
|
|
||||||
void loop(void) {
|
|
||||||
Serial.print("Refresh FPS = ~");
|
|
||||||
Serial.println(matrix.getFrameCount());
|
|
||||||
delay(1000);
|
|
||||||
}
|
|
||||||
349
examples/simple/simple.ino
Normal file
349
examples/simple/simple.ino
Normal file
|
|
@ -0,0 +1,349 @@
|
||||||
|
/* ----------------------------------------------------------------------
|
||||||
|
"Simple" Protomatter library example sketch (once you get past all
|
||||||
|
the various pin configurations at the top, and all the comments).
|
||||||
|
Shows basic use of Adafruit_Protomatter library with different devices.
|
||||||
|
|
||||||
|
This example is written for a 64x32 matrix but can be adapted to others.
|
||||||
|
|
||||||
|
Once the RGB matrix is initialized, most functions of the Adafruit_GFX
|
||||||
|
library are available for drawing -- code from other projects that use
|
||||||
|
LCDs or OLEDs can be easily adapted, or may be insightful for reference.
|
||||||
|
GFX library is documented here:
|
||||||
|
https://learn.adafruit.com/adafruit-gfx-graphics-library
|
||||||
|
------------------------------------------------------------------------- */
|
||||||
|
|
||||||
|
#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. 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 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};
|
||||||
|
uint8_t clockPin = 13;
|
||||||
|
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;
|
||||||
|
#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
|
||||||
|
// 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
|
||||||
|
|
||||||
|
/* ----------------------------------------------------------------------
|
||||||
|
Okay, here's where the RGB LED matrix is actually declared...
|
||||||
|
|
||||||
|
First argument is the matrix width, in pixels. Usually 32 or
|
||||||
|
64, but might go larger if you're chaining multiple matrices.
|
||||||
|
|
||||||
|
Second argument is the "bit depth," which determines color
|
||||||
|
fidelity, applied to red, green and blue (e.g. "4" here means
|
||||||
|
4 bits red, 4 green, 4 blue = 2^4 x 2^4 x 2^4 = 4096 colors).
|
||||||
|
There is a trade-off between bit depth and RAM usage. Most
|
||||||
|
programs will tend to use either 1 (R,G,B on/off, 8 colors,
|
||||||
|
best for text, LED sand, etc.) or the maximum of 6 (best for
|
||||||
|
shaded images...though, because the GFX library was designed
|
||||||
|
for LCDs, only 5 of those bits are available for red and blue.
|
||||||
|
|
||||||
|
Third argument is the number of concurrent (parallel) matrix
|
||||||
|
outputs. THIS SHOULD ALWAYS BE "1" FOR NOW. Fourth is a uint8_t
|
||||||
|
array listing six pins: red, green and blue data out for the
|
||||||
|
top half of the display, and same for bottom half. There are
|
||||||
|
hard constraints as to which pins can be used -- they must all
|
||||||
|
be on the same PORT register, ideally all within the same byte
|
||||||
|
of that PORT.
|
||||||
|
|
||||||
|
Fifth argument is the number of "address" (aka row select) pins,
|
||||||
|
from which the matrix height is inferred. "4" here means four
|
||||||
|
address lines, matrix height is then (2 x 2^4) = 32 pixels.
|
||||||
|
16-pixel-tall matrices will have 3 pins here, 32-pixel will have
|
||||||
|
4, 64-pixel will have 5. Sixth argument is a uint8_t array
|
||||||
|
listing those pin numbers. No PORT constraints here.
|
||||||
|
|
||||||
|
Next three arguments are pin numbers for other RGB matrix
|
||||||
|
control lines: clock, latch and output enable (active low).
|
||||||
|
Clock pin MUST be on the same PORT register as RGB data pins
|
||||||
|
(and ideally in same byte). Other pins have no special rules.
|
||||||
|
|
||||||
|
Last argument is a boolean (true/false) to enable double-
|
||||||
|
buffering for smooth animation (requires 2X the RAM). See the
|
||||||
|
"doublebuffer" example for a demonstration.
|
||||||
|
------------------------------------------------------------------------- */
|
||||||
|
|
||||||
|
Adafruit_Protomatter matrix(
|
||||||
|
64, // Width of matrix (or matrix chain) in pixels
|
||||||
|
4, // 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)
|
||||||
|
|
||||||
|
// 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 is a simple program with no animation, all the
|
||||||
|
// drawing can be done here in setup() rather than loop():
|
||||||
|
|
||||||
|
// 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));
|
||||||
|
}
|
||||||
|
// 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.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
|
||||||
|
}
|
||||||
|
|
||||||
|
// LOOP - RUNS REPEATEDLY AFTER SETUP --------------------------------------
|
||||||
|
|
||||||
|
void loop(void) {
|
||||||
|
// Since there's nothing more to be drawn, this loop() function just
|
||||||
|
// shows the approximate refresh rate of the matrix at current settings.
|
||||||
|
Serial.print("Refresh FPS = ~");
|
||||||
|
Serial.println(matrix.getFrameCount());
|
||||||
|
delay(1000);
|
||||||
|
}
|
||||||
|
|
||||||
|
// MORE NOTES --------------------------------------------------------------
|
||||||
|
|
||||||
|
/*
|
||||||
|
The "RGB and clock bits on same PORT register" constraint requires
|
||||||
|
considerable planning and knowledge of the underlying microcontroller
|
||||||
|
hardware. These are some earlier notes on various devices' PORT registers
|
||||||
|
and bits and their corresponding Arduino pin numbers. You probably won't
|
||||||
|
need this -- it's all codified in the #if defined() sections at the top
|
||||||
|
of this sketch now -- but keeping it around for reference if needed.
|
||||||
|
|
||||||
|
METRO M0 PORT-TO-PIN ASSIGNMENTS BY BYTE:
|
||||||
|
PA00 PA08 D4 PA16 D11 PB00 PB08 A1
|
||||||
|
PA01 PA09 D3 PA17 D13 PB01 PB09 A2
|
||||||
|
PA02 A0 PA10 D1 PA18 D10 PB02 A5 PB10 MOSI
|
||||||
|
PA03 PA11 D0 PA19 D12 PB03 PB11 SCK
|
||||||
|
PA04 A3 PA12 MISO PA20 D6 PB04 PB12
|
||||||
|
PA05 A4 PA13 PA21 D7 PB05 PB13
|
||||||
|
PA06 D8 PA14 D2 PA22 SDA PB06 PB14
|
||||||
|
PA07 D9 PA15 D5 PA23 SCL PB07 PB15
|
||||||
|
|
||||||
|
SAME, METRO M4:
|
||||||
|
PA00 PA08 PA16 D13 PB00 PB08 A4 PB16 D3
|
||||||
|
PA01 PA09 PA17 D12 PB01 PB09 A5 PB17 D2
|
||||||
|
PA02 A0 PA10 PA18 D10 PB02 SDA PB10 PB18
|
||||||
|
PA03 PA11 PA19 D11 PB03 SCL PB11 PB19
|
||||||
|
PA04 A3 PA12 MISO PA20 D9 PB04 PB12 D7 PB20
|
||||||
|
PA05 A1 PA13 SCK PA21 D8 PB05 PB13 D4 PB21
|
||||||
|
PA06 A2 PA14 MISO PA22 D1 PB06 PB14 D5 PB22
|
||||||
|
PA07 PA15 PA23 D0 PB07 PB15 D6 PB23
|
||||||
|
|
||||||
|
FEATHER M4:
|
||||||
|
PA00 PA08 PA16 D5 PB08 A2 PB16 D1/TX
|
||||||
|
PA01 PA09 PA17 SCK PB09 A3 PB17 D0/RX
|
||||||
|
PA02 A0 PA10 PA18 D6 PB10 PB18
|
||||||
|
PA03 PA11 PA19 D9 PB11 PB19
|
||||||
|
PA04 A4 PA12 SDA PA20 D10 PB12 PB20
|
||||||
|
PA05 A1 PA13 SCL PA21 D11 PB13 PB21
|
||||||
|
PA06 A5 PA14 D4 PA22 D12 PB14 PB22 MISO
|
||||||
|
PA07 PA15 PA23 D13 PB15 PB23 MOSI
|
||||||
|
|
||||||
|
FEATHER M0:
|
||||||
|
PA00 PA08 PA16 D11 PB00 PB08 A1
|
||||||
|
PA01 PA09 PA17 D13 PB01 PB09 A2
|
||||||
|
PA02 A0 PA10 TX/D1 PA18 D10 PB02 A5 PB10 MOSI
|
||||||
|
PA03 PA11 RX/D0 PA19 D12 PB03 PB11 SCK
|
||||||
|
PA04 A3 PA12 MISO PA20 D6 PB04 PB12
|
||||||
|
PA05 A4 PA13 PA21 D7 PB05 PB13
|
||||||
|
PA06 PA14 PA22 SDA PB06 PB14
|
||||||
|
PA07 D9 PA15 D5 PA23 SCL PB07 PB15
|
||||||
|
|
||||||
|
FEATHER nRF52840:
|
||||||
|
P0.00 P0.08 D12 P0.24 RXD P1.08 D5
|
||||||
|
P0.01 P0.09 P0.25 TXD P1.09 D13
|
||||||
|
P0.02 A4 P0.10 D2 (NFC) P0.26 D9 P1.10
|
||||||
|
P0.03 A5 P0.11 SCL P0.27 D10 P1.11
|
||||||
|
P0.04 A0 P0.12 SDA P0.28 A3 P1.12
|
||||||
|
P0.05 A1 P0.13 MOSI P0.29 P1.13
|
||||||
|
P0.06 D11 P0.14 SCK P0.30 A2 P1.14
|
||||||
|
P0.07 D6 P0.15 MISO P0.31 P1.15
|
||||||
|
|
||||||
|
FEATHER ESP32:
|
||||||
|
P0.00 P0.08 P0.16 16/RX P0.24 P1.00 32/A7
|
||||||
|
P0.01 P0.09 P0.17 17/TX P0.25 25/A1 P1.01 33/A9/SS
|
||||||
|
P0.02 P0.10 P0.18 18/MOSI P0.26 26/A0 P1.02 34/A2 (in)
|
||||||
|
P0.03 P0.11 P0.19 19/MISO P0.27 27/A10 P1.03
|
||||||
|
P0.04 4/A5 P0.12 12/A11 P0.20 P0.28 P1.04 36/A4 (in)
|
||||||
|
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
|
||||||
|
B1 D9 C A3
|
||||||
|
R2 D11 D A2
|
||||||
|
G2 D10 LAT D0/RX
|
||||||
|
B2 D12 OE D1/TX
|
||||||
|
CLK D13
|
||||||
|
RGB+clock fit in one PORT byte on Feather M4!
|
||||||
|
RGB+clock are on same PORT but not within same byte on Feather M0 --
|
||||||
|
the code could run there, but would be super RAM-inefficient. Avoid.
|
||||||
|
Should be fine on other M0 devices like a Metro, if wiring manually
|
||||||
|
so one can pick a contiguous byte of PORT bits.
|
||||||
|
Original RGB Matrix FeatherWing will NOT work on Feather nRF52840
|
||||||
|
because RGB+clock are on different PORTs. This was resolved by making
|
||||||
|
a unique version of the FeatherWing that works with that board!
|
||||||
|
*/
|
||||||
168
examples/tiled/tiled.ino
Normal file
168
examples/tiled/tiled.ino
Normal file
|
|
@ -0,0 +1,168 @@
|
||||||
|
/* ----------------------------------------------------------------------
|
||||||
|
"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
|
name=Adafruit Protomatter
|
||||||
version=1.0.5
|
version=1.7.0
|
||||||
author=Adafruit
|
author=Adafruit
|
||||||
maintainer=Adafruit <info@adafruit.com>
|
maintainer=Adafruit <info@adafruit.com>
|
||||||
sentence=A library for Adafruit RGB LED matrices.
|
sentence=A library for Adafruit RGB LED matrices.
|
||||||
paragraph=RGB LED matrix.
|
paragraph=RGB LED matrix.
|
||||||
category=Display
|
category=Display
|
||||||
url=https://github.com/adafruit/Adafruit_protomatter
|
url=https://github.com/adafruit/Adafruit_protomatter
|
||||||
architectures=samd,nrf52,stm32,esp32
|
architectures=samd,nrf52,stm32,esp32,rp2040
|
||||||
depends=Adafruit GFX Library, Adafruit LIS3DH, Adafruit PixelDust, AnimatedGIF, Adafruit SPIFlash, Adafruit TinyUSB Library
|
depends=Adafruit GFX Library, Adafruit LIS3DH, Adafruit PixelDust, AnimatedGIF, Adafruit SPIFlash, Adafruit TinyUSB Library
|
||||||
|
|
|
||||||
|
|
@ -47,9 +47,10 @@ Adafruit_Protomatter::Adafruit_Protomatter(uint16_t bitWidth, uint8_t bitDepth,
|
||||||
uint8_t addrCount, uint8_t *addrList,
|
uint8_t addrCount, uint8_t *addrList,
|
||||||
uint8_t clockPin, uint8_t latchPin,
|
uint8_t clockPin, uint8_t latchPin,
|
||||||
uint8_t oePin, bool doubleBuffer,
|
uint8_t oePin, bool doubleBuffer,
|
||||||
void *timer)
|
int8_t tile, void *timer)
|
||||||
: GFXcanvas16(bitWidth,
|
: GFXcanvas16(bitWidth, (2 << min((int)addrCount, 5)) *
|
||||||
(2 << min((int)addrCount, 5)) * min((int)rgbCount, 5)) {
|
min((int)rgbCount, 5) *
|
||||||
|
(tile ? abs(tile) : 1)) {
|
||||||
if (bitDepth > 6)
|
if (bitDepth > 6)
|
||||||
bitDepth = 6; // GFXcanvas16 color limit (565)
|
bitDepth = 6; // GFXcanvas16 color limit (565)
|
||||||
|
|
||||||
|
|
@ -59,7 +60,8 @@ Adafruit_Protomatter::Adafruit_Protomatter(uint16_t bitWidth, uint8_t bitDepth,
|
||||||
// The class begin() function checks rgbPins for NULL to determine
|
// The class begin() function checks rgbPins for NULL to determine
|
||||||
// whether to proceed or indicate an error.
|
// whether to proceed or indicate an error.
|
||||||
(void)_PM_init(&core, bitWidth, bitDepth, rgbCount, rgbList, addrCount,
|
(void)_PM_init(&core, bitWidth, bitDepth, rgbCount, rgbList, addrCount,
|
||||||
addrList, clockPin, latchPin, oePin, doubleBuffer, timer);
|
addrList, clockPin, latchPin, oePin, doubleBuffer, tile,
|
||||||
|
timer);
|
||||||
}
|
}
|
||||||
|
|
||||||
Adafruit_Protomatter::~Adafruit_Protomatter(void) {
|
Adafruit_Protomatter::~Adafruit_Protomatter(void) {
|
||||||
|
|
@ -87,3 +89,53 @@ void Adafruit_Protomatter::show(void) {
|
||||||
uint32_t Adafruit_Protomatter::getFrameCount(void) {
|
uint32_t Adafruit_Protomatter::getFrameCount(void) {
|
||||||
return _PM_getFrameCount(_PM_protoPtr);
|
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,6 +54,14 @@ public:
|
||||||
@param doubleBuffer If true, two matrix buffers are allocated,
|
@param doubleBuffer If true, two matrix buffers are allocated,
|
||||||
so changing display contents doesn't introduce
|
so changing display contents doesn't introduce
|
||||||
artifacts mid-conversion. Requires ~2X RAM.
|
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
|
@param timer Pointer to timer peripheral or timer-related
|
||||||
struct (architecture-dependent), or NULL to
|
struct (architecture-dependent), or NULL to
|
||||||
use a default timer ID (also arch-dependent).
|
use a default timer ID (also arch-dependent).
|
||||||
|
|
@ -61,7 +69,7 @@ public:
|
||||||
Adafruit_Protomatter(uint16_t bitWidth, uint8_t bitDepth, uint8_t rgbCount,
|
Adafruit_Protomatter(uint16_t bitWidth, uint8_t bitDepth, uint8_t rgbCount,
|
||||||
uint8_t *rgbList, uint8_t addrCount, uint8_t *addrList,
|
uint8_t *rgbList, uint8_t addrCount, uint8_t *addrList,
|
||||||
uint8_t clockPin, uint8_t latchPin, uint8_t oePin,
|
uint8_t clockPin, uint8_t latchPin, uint8_t oePin,
|
||||||
bool doubleBuffer, void *timer = NULL);
|
bool doubleBuffer, int8_t tile = 1, void *timer = NULL);
|
||||||
~Adafruit_Protomatter(void);
|
~Adafruit_Protomatter(void);
|
||||||
|
|
||||||
/*!
|
/*!
|
||||||
|
|
@ -84,6 +92,16 @@ public:
|
||||||
*/
|
*/
|
||||||
void show(void);
|
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
|
@brief Returns current value of frame counter and resets its value
|
||||||
to zero. Two calls to this, timed one second apart (or use
|
to zero. Two calls to this, timed one second apart (or use
|
||||||
|
|
@ -94,6 +112,48 @@ public:
|
||||||
*/
|
*/
|
||||||
uint32_t getFrameCount(void);
|
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:
|
private:
|
||||||
Protomatter_core core; // Underlying C struct
|
Protomatter_core core; // Underlying C struct
|
||||||
void convert_byte(uint8_t *dest); // GFXcanvas16-to-matrix
|
void convert_byte(uint8_t *dest); // GFXcanvas16-to-matrix
|
||||||
|
|
|
||||||
|
|
@ -82,11 +82,13 @@ Timer-related macros/functions:
|
||||||
|
|
||||||
_PM_timerFreq: A numerical constant - the source clock rate
|
_PM_timerFreq: A numerical constant - the source clock rate
|
||||||
(in Hz) that's fed to the timer peripheral.
|
(in Hz) that's fed to the timer peripheral.
|
||||||
_PM_timerInit(void*): Initialize (but do not start) timer.
|
_PM_timerInit(Protomatter_core*): Initialize (but do not start) timer.
|
||||||
_PM_timerStart(void*,count): (Re)start timer for a given timer-tick interval.
|
_PM_timerStart(Protomatter_core*,count): (Re)start timer for a given
|
||||||
_PM_timerStop(void*): Stop timer, return current timer counter value.
|
timer-tick interval.
|
||||||
_PM_timerGetCount(void*): Get current timer counter value (whether timer
|
_PM_timerStop(Protomatter_core*): Stop timer, return current timer
|
||||||
is running or stopped).
|
counter value.
|
||||||
|
_PM_timerGetCount(Protomatter_core*): Get current timer counter value
|
||||||
|
(whether timer is running or stopped).
|
||||||
A timer interrupt service routine is also required, syntax for which varies
|
A timer interrupt service routine is also required, syntax for which varies
|
||||||
between architectures.
|
between architectures.
|
||||||
The void* argument passed to the timer functions is some indeterminate type
|
The void* argument passed to the timer functions is some indeterminate type
|
||||||
|
|
@ -135,6 +137,26 @@ _PM_allocate: Memory allocation function, should return a
|
||||||
If not defined, malloc() is used.
|
If not defined, malloc() is used.
|
||||||
_PM_free: Corresponding deallocator for _PM_allocate().
|
_PM_free: Corresponding deallocator for _PM_allocate().
|
||||||
If not defined, free() is used.
|
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 ---------------------------------------
|
// ENVIRONMENT-SPECIFIC DECLARATIONS ---------------------------------------
|
||||||
|
|
@ -148,7 +170,6 @@ _PM_free: Corresponding deallocator for _PM_allocate().
|
||||||
#define _PM_pinInput(pin) pinMode(pin, INPUT)
|
#define _PM_pinInput(pin) pinMode(pin, INPUT)
|
||||||
#define _PM_pinHigh(pin) digitalWrite(pin, HIGH)
|
#define _PM_pinHigh(pin) digitalWrite(pin, HIGH)
|
||||||
#define _PM_pinLow(pin) digitalWrite(pin, LOW)
|
#define _PM_pinLow(pin) digitalWrite(pin, LOW)
|
||||||
#define _PM_portBitMask(pin) digitalPinToBitMask(pin)
|
|
||||||
|
|
||||||
#elif defined(CIRCUITPY) // COMPILING FOR CIRCUITPYTHON --------------------
|
#elif defined(CIRCUITPY) // COMPILING FOR CIRCUITPYTHON --------------------
|
||||||
|
|
||||||
|
|
@ -165,16 +186,32 @@ _PM_free: Corresponding deallocator for _PM_allocate().
|
||||||
|
|
||||||
// ARCHITECTURE-SPECIFIC HEADERS -------------------------------------------
|
// ARCHITECTURE-SPECIFIC HEADERS -------------------------------------------
|
||||||
|
|
||||||
#include "esp32.h"
|
// 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 "nrf52.h"
|
#include "nrf52.h"
|
||||||
|
#include "rp2040.h"
|
||||||
#include "samd-common.h"
|
#include "samd-common.h"
|
||||||
#include "samd21.h"
|
#include "samd21.h"
|
||||||
#include "samd51.h"
|
#include "samd51.h"
|
||||||
#include "stm32.h"
|
#include "stm32.h"
|
||||||
#include "teensy4.h"
|
#include "teensy4.h"
|
||||||
|
// clang-format on
|
||||||
|
|
||||||
// DEFAULTS IF NOT DEFINED ABOVE -------------------------------------------
|
// 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)
|
#if !defined(_PM_chunkSize)
|
||||||
#define _PM_chunkSize 8 ///< Unroll data-stuffing loop to this size
|
#define _PM_chunkSize 8 ///< Unroll data-stuffing loop to this size
|
||||||
#endif
|
#endif
|
||||||
|
|
@ -206,3 +243,11 @@ _PM_free: Corresponding deallocator for _PM_allocate().
|
||||||
#if !defined(_PM_PORT_TYPE)
|
#if !defined(_PM_PORT_TYPE)
|
||||||
#define _PM_PORT_TYPE uint32_t ///< PORT register size/type
|
#define _PM_PORT_TYPE uint32_t ///< PORT register size/type
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
|
#if !defined(_PM_maxDuty)
|
||||||
|
#define _PM_maxDuty 0 ///< Max duty cycle setting (where supported)
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#if !defined(_PM_defaultDuty)
|
||||||
|
#define _PM_defaultDuty 0 ///< Default duty cycle setting (where supported)
|
||||||
|
#endif
|
||||||
|
|
|
||||||
57
src/arch/esp32-c3.h
Normal file
57
src/arch/esp32-c3.h
Normal file
|
|
@ -0,0 +1,57 @@
|
||||||
|
/*!
|
||||||
|
* @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
|
||||||
57
src/arch/esp32-c6.h
Normal file
57
src/arch/esp32-c6.h
Normal file
|
|
@ -0,0 +1,57 @@
|
||||||
|
/*!
|
||||||
|
* @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
|
||||||
247
src/arch/esp32-common.h
Normal file
247
src/arch/esp32-common.h
Normal file
|
|
@ -0,0 +1,247 @@
|
||||||
|
/*!
|
||||||
|
* @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)
|
||||||
213
src/arch/esp32-s2.h
Normal file
213
src/arch/esp32-s2.h
Normal file
|
|
@ -0,0 +1,213 @@
|
||||||
|
/*!
|
||||||
|
* @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
|
||||||
285
src/arch/esp32-s3.h
Normal file
285
src/arch/esp32-s3.h
Normal file
|
|
@ -0,0 +1,285 @@
|
||||||
|
/*!
|
||||||
|
* @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
|
||||||
108
src/arch/esp32.h
108
src/arch/esp32.h
|
|
@ -2,7 +2,7 @@
|
||||||
* @file esp32.h
|
* @file esp32.h
|
||||||
*
|
*
|
||||||
* Part of Adafruit's Protomatter library for HUB75-style RGB LED matrices.
|
* Part of Adafruit's Protomatter library for HUB75-style RGB LED matrices.
|
||||||
* This file contains ESP32-SPECIFIC CODE.
|
* This file contains ORIGINAL-ESP32-SPECIFIC CODE.
|
||||||
*
|
*
|
||||||
* Adafruit invests time and resources providing this open source code,
|
* Adafruit invests time and resources providing this open source code,
|
||||||
* please support Adafruit and open-source hardware by purchasing
|
* please support Adafruit and open-source hardware by purchasing
|
||||||
|
|
@ -17,21 +17,23 @@
|
||||||
|
|
||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
#if defined(ESP32)
|
// 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(ARDUINO) // COMPILING FOR ARDUINO ------------------------------
|
#if defined(CONFIG_IDF_TARGET_ESP32) // ORIGINAL ESP32, NOT S2/S3/etc.
|
||||||
|
|
||||||
#include "driver/timer.h"
|
|
||||||
|
|
||||||
#define _PM_portOutRegister(pin) \
|
#define _PM_portOutRegister(pin) \
|
||||||
(volatile uint32_t *)((pin < 32) ? &GPIO.out : &GPIO.out1.val)
|
(volatile uint32_t *)((pin < 32) ? &GPIO.out : &GPIO.out1.val)
|
||||||
|
|
||||||
#define _PM_portSetRegister(pin) \
|
#define _PM_portSetRegister(pin) \
|
||||||
(volatile uint32_t *)((pin < 32) ? &GPIO.out_w1ts : &GPIO.out1_w1ts.val)
|
(volatile uint32_t *)((pin < 32) ? &GPIO.out_w1ts : &GPIO.out1_w1ts.val)
|
||||||
|
|
||||||
#define _PM_portClearRegister(pin) \
|
#define _PM_portClearRegister(pin) \
|
||||||
(volatile uint32_t *)((pin < 32) ? &GPIO.out_w1tc : &GPIO.out1_w1tc.val)
|
(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__
|
#if __BYTE_ORDER__ == __ORDER_LITTLE_ENDIAN__
|
||||||
#define _PM_byteOffset(pin) ((pin & 31) / 8)
|
#define _PM_byteOffset(pin) ((pin & 31) / 8)
|
||||||
#define _PM_wordOffset(pin) ((pin & 31) / 16)
|
#define _PM_wordOffset(pin) ((pin & 31) / 16)
|
||||||
|
|
@ -40,83 +42,49 @@
|
||||||
#define _PM_wordOffset(pin) (1 - ((pin & 31) / 16))
|
#define _PM_wordOffset(pin) (1 - ((pin & 31) / 16))
|
||||||
#endif
|
#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
|
// 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
|
// 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
|
// 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
|
// 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.
|
// 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 \
|
#define PEW \
|
||||||
*set = *data++; /* Set RGB data high */ \
|
*set = *data++; /* Set RGB data high */ \
|
||||||
*clear_full = 0; /* ESP32 MUST sync before 2nd 'set' */ \
|
*clear_full = 0; /* ESP32 MUST sync before 2nd 'set' */ \
|
||||||
*set_full = clock; /* Set clock high */ \
|
*set_full = clock; /* Set clock high */ \
|
||||||
*clear_full = rgbclock; /* Clear RGB data + clock */ \
|
*clear_full = rgbclock; /* Clear RGB data + clock */ \
|
||||||
///< Bitbang one set of RGB data bits to matrix
|
///< 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 period) {
|
|
||||||
hw_timer_t *timer = *(hw_timer_t **)tptr;
|
|
||||||
timerAlarmWrite(timer, period, true);
|
|
||||||
timerAlarmEnable(timer);
|
|
||||||
timerStart(timer);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Return current count value (timer enabled or not).
|
|
||||||
// Timer must be previously initialized.
|
|
||||||
IRAM_ATTR inline uint32_t _PM_timerGetCount(void *tptr) {
|
|
||||||
hw_timer_t *timer = *(hw_timer_t **)tptr;
|
|
||||||
return (uint32_t)timerRead(timer);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Disable timer and return current count value.
|
|
||||||
// Timer must be previously initialized.
|
|
||||||
IRAM_ATTR uint32_t _PM_timerStop(void *tptr) {
|
|
||||||
hw_timer_t *timer = *(hw_timer_t **)tptr;
|
|
||||||
timerStop(timer);
|
|
||||||
return _PM_timerGetCount(tptr);
|
|
||||||
}
|
|
||||||
|
|
||||||
#elif defined(CIRCUITPY) // COMPILING FOR CIRCUITPYTHON --------------------
|
#elif defined(CIRCUITPY) // COMPILING FOR CIRCUITPYTHON --------------------
|
||||||
|
|
||||||
// ESP32 CircuitPython magic goes here. If any of the above Arduino-specific
|
#define _PM_STRICT_32BIT_IO (1)
|
||||||
// defines, structs or functions are useful as-is, don't copy them, just
|
|
||||||
// move them above the ARDUINO check so fixes/changes carry over, thx.
|
// ESP32 requires a custom PEW declaration (issues one set of RGB color bits
|
||||||
|
// followed by clock pulse). Turns out the bit set/clear registers are not
|
||||||
|
// actually atomic. If two writes are made in quick succession, the second
|
||||||
|
// has no effect. One option is NOPs, other is to write a 0 (no effect) to
|
||||||
|
// the opposing register (set vs clear) to synchronize the next write.
|
||||||
|
#define PEW \
|
||||||
|
*set = (*data++) << shift; /* Set RGB data high */ \
|
||||||
|
*clear_full = 0; /* ESP32 MUST sync before 2nd 'set' */ \
|
||||||
|
*set = clock; /* Set clock high */ \
|
||||||
|
*clear_full = rgbclock; /* Clear RGB data + clock */ \
|
||||||
|
///< Bitbang one set of RGB data bits to matrix
|
||||||
|
|
||||||
#endif // END CIRCUITPYTHON ------------------------------------------------
|
#endif // END CIRCUITPYTHON ------------------------------------------------
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -99,7 +99,7 @@ volatile uint32_t *_PM_portClearRegister(uint32_t pin) {
|
||||||
#define _PM_pinInput(pin) nrf_gpio_cfg_input(pin)
|
#define _PM_pinInput(pin) nrf_gpio_cfg_input(pin)
|
||||||
#define _PM_pinHigh(pin) nrf_gpio_pin_set(pin)
|
#define _PM_pinHigh(pin) nrf_gpio_pin_set(pin)
|
||||||
#define _PM_pinLow(pin) nrf_gpio_pin_clear(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__
|
#if __BYTE_ORDER__ == __ORDER_LITTLE_ENDIAN__
|
||||||
#define _PM_byteOffset(pin) ((pin & 31) / 8)
|
#define _PM_byteOffset(pin) ((pin & 31) / 8)
|
||||||
|
|
@ -136,32 +136,33 @@ void _PM_IRQ_HANDLER(void) {
|
||||||
|
|
||||||
// CODE COMMON TO ALL ENVIRONMENTS -----------------------------------------
|
// CODE COMMON TO ALL ENVIRONMENTS -----------------------------------------
|
||||||
|
|
||||||
void _PM_timerInit(void *tptr) {
|
void _PM_timerInit(Protomatter_core *core) {
|
||||||
static const struct {
|
static const struct {
|
||||||
NRF_TIMER_Type *tc; // -> Timer peripheral base address
|
NRF_TIMER_Type *tc; // -> Timer peripheral base address
|
||||||
IRQn_Type IRQn; // Interrupt number
|
IRQn_Type IRQn; // Interrupt number
|
||||||
} timer[] = {
|
} timer[] = {
|
||||||
#if defined(NRF_TIMER0)
|
#if defined(NRF_TIMER0)
|
||||||
{NRF_TIMER0, TIMER0_IRQn},
|
{NRF_TIMER0, TIMER0_IRQn},
|
||||||
#endif
|
#endif
|
||||||
#if defined(NRF_TIMER1)
|
#if defined(NRF_TIMER1)
|
||||||
{NRF_TIMER1, TIMER1_IRQn},
|
{NRF_TIMER1, TIMER1_IRQn},
|
||||||
#endif
|
#endif
|
||||||
#if defined(NRF_TIMER2)
|
#if defined(NRF_TIMER2)
|
||||||
{NRF_TIMER2, TIMER2_IRQn},
|
{NRF_TIMER2, TIMER2_IRQn},
|
||||||
#endif
|
#endif
|
||||||
#if defined(NRF_TIMER3)
|
#if defined(NRF_TIMER3)
|
||||||
{NRF_TIMER3, TIMER3_IRQn},
|
{NRF_TIMER3, TIMER3_IRQn},
|
||||||
#endif
|
#endif
|
||||||
#if defined(NRF_TIMER4)
|
#if defined(NRF_TIMER4)
|
||||||
{NRF_TIMER4, TIMER4_IRQn},
|
{NRF_TIMER4, TIMER4_IRQn},
|
||||||
#endif
|
#endif
|
||||||
};
|
};
|
||||||
#define NUM_TIMERS (sizeof timer / sizeof timer[0])
|
#define NUM_TIMERS (sizeof timer / sizeof timer[0])
|
||||||
|
|
||||||
// Determine IRQn from timer address
|
// Determine IRQn from timer address
|
||||||
uint8_t timerNum = 0;
|
uint8_t timerNum = 0;
|
||||||
while ((timerNum < NUM_TIMERS) && (timer[timerNum].tc != tptr)) {
|
while ((timerNum < NUM_TIMERS) &&
|
||||||
|
(timer[timerNum].tc != (NRF_TIMER_Type *)core->timer)) {
|
||||||
timerNum++;
|
timerNum++;
|
||||||
}
|
}
|
||||||
if (timerNum >= NUM_TIMERS)
|
if (timerNum >= NUM_TIMERS)
|
||||||
|
|
@ -183,30 +184,25 @@ void _PM_timerInit(void *tptr) {
|
||||||
NVIC_EnableIRQ(timer[timerNum].IRQn);
|
NVIC_EnableIRQ(timer[timerNum].IRQn);
|
||||||
}
|
}
|
||||||
|
|
||||||
inline void _PM_timerStart(void *tptr, uint32_t period) {
|
inline void _PM_timerStart(Protomatter_core *core, uint32_t period) {
|
||||||
volatile NRF_TIMER_Type *tc = (volatile NRF_TIMER_Type *)tptr;
|
volatile NRF_TIMER_Type *tc = (volatile NRF_TIMER_Type *)core->timer;
|
||||||
tc->TASKS_STOP = 1; // Stop timer
|
tc->TASKS_STOP = 1; // Stop timer
|
||||||
tc->TASKS_CLEAR = 1; // Reset to 0
|
tc->TASKS_CLEAR = 1; // Reset to 0
|
||||||
tc->CC[0] = period;
|
tc->CC[0] = period;
|
||||||
tc->TASKS_START = 1; // Start timer
|
tc->TASKS_START = 1; // Start timer
|
||||||
}
|
}
|
||||||
|
|
||||||
inline uint32_t _PM_timerGetCount(void *tptr) {
|
inline uint32_t _PM_timerGetCount(Protomatter_core *core) {
|
||||||
volatile NRF_TIMER_Type *tc = (volatile NRF_TIMER_Type *)tptr;
|
volatile NRF_TIMER_Type *tc = (volatile NRF_TIMER_Type *)core->timer;
|
||||||
tc->TASKS_CAPTURE[0] = 1; // Capture timer to CC[n] register
|
tc->TASKS_CAPTURE[1] = 1; // Capture timer to CC[1] register
|
||||||
return tc->CC[0];
|
return tc->CC[1]; // (don't clobber value in CC[0])
|
||||||
}
|
}
|
||||||
|
|
||||||
uint32_t _PM_timerStop(void *tptr) {
|
uint32_t _PM_timerStop(Protomatter_core *core) {
|
||||||
volatile NRF_TIMER_Type *tc = (volatile NRF_TIMER_Type *)tptr;
|
volatile NRF_TIMER_Type *tc = (volatile NRF_TIMER_Type *)core->timer;
|
||||||
tc->TASKS_STOP = 1; // Stop timer
|
tc->TASKS_STOP = 1; // Stop timer
|
||||||
__attribute__((unused)) uint32_t count = _PM_timerGetCount(tptr);
|
__attribute__((unused)) uint32_t count = _PM_timerGetCount(core);
|
||||||
// NOTE TO FUTURE SELF: I don't know why the GetCount code isn't
|
return count;
|
||||||
// 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");
|
#define _PM_clockHoldHigh asm("nop; nop");
|
||||||
|
|
|
||||||
265
src/arch/rp2040.h
Normal file
265
src/arch/rp2040.h
Normal file
|
|
@ -0,0 +1,265 @@
|
||||||
|
/*!
|
||||||
|
* @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
|
||||||
|
|
@ -17,7 +17,7 @@
|
||||||
|
|
||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
#if defined(__SAMD51__) || defined(SAMD51) || defined(_SAMD21_) || \
|
#if defined(__SAMD51__) || defined(SAM_D5X_E5X) || defined(_SAMD21_) || \
|
||||||
defined(SAMD21)
|
defined(SAMD21)
|
||||||
|
|
||||||
#if defined(ARDUINO) // COMPILING FOR ARDUINO ------------------------------
|
#if defined(ARDUINO) // COMPILING FOR ARDUINO ------------------------------
|
||||||
|
|
@ -64,7 +64,7 @@ void _PM_IRQ_HANDLER(void) {
|
||||||
#define _PM_pinInput(pin) gpio_set_pin_direction(pin, GPIO_DIRECTION_IN)
|
#define _PM_pinInput(pin) gpio_set_pin_direction(pin, GPIO_DIRECTION_IN)
|
||||||
#define _PM_pinHigh(pin) gpio_set_pin_level(pin, 1)
|
#define _PM_pinHigh(pin) gpio_set_pin_level(pin, 1)
|
||||||
#define _PM_pinLow(pin) gpio_set_pin_level(pin, 0)
|
#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__
|
#if __BYTE_ORDER__ == __ORDER_LITTLE_ENDIAN__
|
||||||
#define _PM_byteOffset(pin) ((pin & 31) / 8)
|
#define _PM_byteOffset(pin) ((pin & 31) / 8)
|
||||||
|
|
@ -95,4 +95,4 @@ void _PM_IRQ_HANDLER(void) {
|
||||||
|
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
#endif // END SAMD51/SAMD21
|
#endif // END SAMD5x/SAME5x/SAMD21
|
||||||
|
|
|
||||||
|
|
@ -44,31 +44,31 @@
|
||||||
// CODE COMMON TO ALL ENVIRONMENTS -----------------------------------------
|
// CODE COMMON TO ALL ENVIRONMENTS -----------------------------------------
|
||||||
|
|
||||||
// Initialize, but do not start, timer
|
// Initialize, but do not start, timer
|
||||||
void _PM_timerInit(void *tptr) {
|
void _PM_timerInit(Protomatter_core *core) {
|
||||||
static const struct {
|
static const struct {
|
||||||
Tc *tc; // -> Timer/counter peripheral base address
|
Tc *tc; // -> Timer/counter peripheral base address
|
||||||
IRQn_Type IRQn; // Interrupt number
|
IRQn_Type IRQn; // Interrupt number
|
||||||
uint8_t GCM_ID; // GCLK selection ID
|
uint8_t GCM_ID; // GCLK selection ID
|
||||||
} timer[] = {
|
} timer[] = {
|
||||||
#if defined(TC0)
|
#if defined(TC0)
|
||||||
{TC0, TC0_IRQn, GCM_TCC0_TCC1},
|
{TC0, TC0_IRQn, GCM_TCC0_TCC1},
|
||||||
#endif
|
#endif
|
||||||
#if defined(TC1)
|
#if defined(TC1)
|
||||||
{TC1, TC1_IRQn, GCM_TCC0_TCC1},
|
{TC1, TC1_IRQn, GCM_TCC0_TCC1},
|
||||||
#endif
|
#endif
|
||||||
#if defined(TC2)
|
#if defined(TC2)
|
||||||
{TC2, TC2_IRQn, GCM_TCC2_TC3},
|
{TC2, TC2_IRQn, GCM_TCC2_TC3},
|
||||||
#endif
|
#endif
|
||||||
#if defined(TC3)
|
#if defined(TC3)
|
||||||
{TC3, TC3_IRQn, GCM_TCC2_TC3},
|
{TC3, TC3_IRQn, GCM_TCC2_TC3},
|
||||||
#endif
|
#endif
|
||||||
#if defined(TC4)
|
#if defined(TC4)
|
||||||
{TC4, TC4_IRQn, GCM_TC4_TC5},
|
{TC4, TC4_IRQn, GCM_TC4_TC5},
|
||||||
#endif
|
#endif
|
||||||
};
|
};
|
||||||
#define NUM_TIMERS (sizeof timer / sizeof timer[0])
|
#define NUM_TIMERS (sizeof timer / sizeof timer[0])
|
||||||
|
|
||||||
Tc *tc = (Tc *)tptr; // Cast peripheral address passed in
|
Tc *tc = (Tc *)core->timer; // Cast peripheral address passed in
|
||||||
|
|
||||||
uint8_t timerNum = 0;
|
uint8_t timerNum = 0;
|
||||||
while ((timerNum < NUM_TIMERS) && (timer[timerNum].tc != tc)) {
|
while ((timerNum < NUM_TIMERS) && (timer[timerNum].tc != tc)) {
|
||||||
|
|
@ -113,8 +113,8 @@ void _PM_timerInit(void *tptr) {
|
||||||
// Set timer period, initialize count value to zero, enable timer.
|
// Set timer period, initialize count value to zero, enable timer.
|
||||||
// Timer must be initialized to 16-bit mode using the init function
|
// Timer must be initialized to 16-bit mode using the init function
|
||||||
// above, but must be inactive before calling this.
|
// above, but must be inactive before calling this.
|
||||||
inline void _PM_timerStart(void *tptr, uint32_t period) {
|
inline void _PM_timerStart(Protomatter_core *core, uint32_t period) {
|
||||||
Tc *tc = (Tc *)tptr; // Cast peripheral address passed in
|
Tc *tc = (Tc *)core->timer; // Cast peripheral address passed in
|
||||||
tc->COUNT16.COUNT.reg = 0;
|
tc->COUNT16.COUNT.reg = 0;
|
||||||
while (tc->COUNT16.STATUS.bit.SYNCBUSY)
|
while (tc->COUNT16.STATUS.bit.SYNCBUSY)
|
||||||
;
|
;
|
||||||
|
|
@ -128,8 +128,8 @@ inline void _PM_timerStart(void *tptr, uint32_t period) {
|
||||||
|
|
||||||
// Return current count value (timer enabled or not).
|
// Return current count value (timer enabled or not).
|
||||||
// Timer must be previously initialized.
|
// Timer must be previously initialized.
|
||||||
inline uint32_t _PM_timerGetCount(void *tptr) {
|
inline uint32_t _PM_timerGetCount(Protomatter_core *core) {
|
||||||
Tc *tc = (Tc *)tptr; // Cast peripheral address passed in
|
Tc *tc = (Tc *)core->timer; // Cast peripheral address passed in
|
||||||
tc->COUNT16.READREQ.reg = TC_READREQ_RCONT | TC_READREQ_ADDR(0x10);
|
tc->COUNT16.READREQ.reg = TC_READREQ_RCONT | TC_READREQ_ADDR(0x10);
|
||||||
while (tc->COUNT16.STATUS.bit.SYNCBUSY)
|
while (tc->COUNT16.STATUS.bit.SYNCBUSY)
|
||||||
;
|
;
|
||||||
|
|
@ -138,9 +138,9 @@ inline uint32_t _PM_timerGetCount(void *tptr) {
|
||||||
|
|
||||||
// Disable timer and return current count value.
|
// Disable timer and return current count value.
|
||||||
// Timer must be previously initialized.
|
// Timer must be previously initialized.
|
||||||
inline uint32_t _PM_timerStop(void *tptr) {
|
inline uint32_t _PM_timerStop(Protomatter_core *core) {
|
||||||
Tc *tc = (Tc *)tptr; // Cast peripheral address passed in
|
Tc *tc = (Tc *)core->timer; // Cast peripheral address passed in
|
||||||
uint32_t count = _PM_timerGetCount(tptr);
|
uint32_t count = _PM_timerGetCount(core);
|
||||||
tc->COUNT16.CTRLA.bit.ENABLE = 0;
|
tc->COUNT16.CTRLA.bit.ENABLE = 0;
|
||||||
while (tc->COUNT16.STATUS.bit.SYNCBUSY)
|
while (tc->COUNT16.STATUS.bit.SYNCBUSY)
|
||||||
;
|
;
|
||||||
|
|
|
||||||
|
|
@ -17,7 +17,8 @@
|
||||||
|
|
||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
#if defined(__SAMD51__) || defined(SAMD51) // Arduino, Circuitpy SAMD51 defs
|
#if defined(__SAMD51__) || \
|
||||||
|
defined(SAM_D5X_E5X) // Arduino, Circuitpy SAMD5x / E5x defs
|
||||||
|
|
||||||
#if defined(ARDUINO) // COMPILING FOR ARDUINO ------------------------------
|
#if defined(ARDUINO) // COMPILING FOR ARDUINO ------------------------------
|
||||||
|
|
||||||
|
|
@ -46,6 +47,21 @@
|
||||||
|
|
||||||
#define F_CPU (120000000)
|
#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
|
#else
|
||||||
|
|
||||||
// Other port register lookups go here
|
// Other port register lookups go here
|
||||||
|
|
@ -55,55 +71,55 @@
|
||||||
// CODE COMMON TO ALL ENVIRONMENTS -----------------------------------------
|
// CODE COMMON TO ALL ENVIRONMENTS -----------------------------------------
|
||||||
|
|
||||||
// Initialize, but do not start, timer
|
// Initialize, but do not start, timer
|
||||||
void _PM_timerInit(void *tptr) {
|
void _PM_timerInit(Protomatter_core *core) {
|
||||||
static const struct {
|
static const struct {
|
||||||
Tc *tc; // -> Timer/counter peripheral base address
|
Tc *tc; // -> Timer/counter peripheral base address
|
||||||
IRQn_Type IRQn; // Interrupt number
|
IRQn_Type IRQn; // Interrupt number
|
||||||
uint8_t GCLK_ID; // Peripheral channel # for clock source
|
uint8_t GCLK_ID; // Peripheral channel # for clock source
|
||||||
} timer[] = {
|
} timer[] = {
|
||||||
#if defined(TC0)
|
#if defined(TC0)
|
||||||
{TC0, TC0_IRQn, TC0_GCLK_ID},
|
{TC0, TC0_IRQn, TC0_GCLK_ID},
|
||||||
#endif
|
#endif
|
||||||
#if defined(TC1)
|
#if defined(TC1)
|
||||||
{TC1, TC1_IRQn, TC1_GCLK_ID},
|
{TC1, TC1_IRQn, TC1_GCLK_ID},
|
||||||
#endif
|
#endif
|
||||||
#if defined(TC2)
|
#if defined(TC2)
|
||||||
{TC2, TC2_IRQn, TC2_GCLK_ID},
|
{TC2, TC2_IRQn, TC2_GCLK_ID},
|
||||||
#endif
|
#endif
|
||||||
#if defined(TC3)
|
#if defined(TC3)
|
||||||
{TC3, TC3_IRQn, TC3_GCLK_ID},
|
{TC3, TC3_IRQn, TC3_GCLK_ID},
|
||||||
#endif
|
#endif
|
||||||
#if defined(TC4)
|
#if defined(TC4)
|
||||||
{TC4, TC4_IRQn, TC4_GCLK_ID},
|
{TC4, TC4_IRQn, TC4_GCLK_ID},
|
||||||
#endif
|
#endif
|
||||||
#if defined(TC5)
|
#if defined(TC5)
|
||||||
{TC5, TC5_IRQn, TC5_GCLK_ID},
|
{TC5, TC5_IRQn, TC5_GCLK_ID},
|
||||||
#endif
|
#endif
|
||||||
#if defined(TC6)
|
#if defined(TC6)
|
||||||
{TC6, TC6_IRQn, TC6_GCLK_ID},
|
{TC6, TC6_IRQn, TC6_GCLK_ID},
|
||||||
#endif
|
#endif
|
||||||
#if defined(TC7)
|
#if defined(TC7)
|
||||||
{TC7, TC7_IRQn, TC7_GCLK_ID},
|
{TC7, TC7_IRQn, TC7_GCLK_ID},
|
||||||
#endif
|
#endif
|
||||||
#if defined(TC8)
|
#if defined(TC8)
|
||||||
{TC8, TC8_IRQn, TC8_GCLK_ID},
|
{TC8, TC8_IRQn, TC8_GCLK_ID},
|
||||||
#endif
|
#endif
|
||||||
#if defined(TC9)
|
#if defined(TC9)
|
||||||
{TC9, TC9_IRQn, TC9_GCLK_ID},
|
{TC9, TC9_IRQn, TC9_GCLK_ID},
|
||||||
#endif
|
#endif
|
||||||
#if defined(TC10)
|
#if defined(TC10)
|
||||||
{TC10, TC10_IRQn, TC10_GCLK_ID},
|
{TC10, TC10_IRQn, TC10_GCLK_ID},
|
||||||
#endif
|
#endif
|
||||||
#if defined(TC11)
|
#if defined(TC11)
|
||||||
{TC11, TC11_IRQn, TC11_GCLK_ID},
|
{TC11, TC11_IRQn, TC11_GCLK_ID},
|
||||||
#endif
|
#endif
|
||||||
#if defined(TC12)
|
#if defined(TC12)
|
||||||
{TC12, TC12_IRQn, TC12_GCLK_ID},
|
{TC12, TC12_IRQn, TC12_GCLK_ID},
|
||||||
#endif
|
#endif
|
||||||
};
|
};
|
||||||
#define NUM_TIMERS (sizeof timer / sizeof timer[0])
|
#define NUM_TIMERS (sizeof timer / sizeof timer[0])
|
||||||
|
|
||||||
Tc *tc = (Tc *)tptr; // Cast peripheral address passed in
|
Tc *tc = (Tc *)core->timer; // Cast peripheral address passed in
|
||||||
|
|
||||||
uint8_t timerNum = 0;
|
uint8_t timerNum = 0;
|
||||||
while ((timerNum < NUM_TIMERS) && (timer[timerNum].tc != tc)) {
|
while ((timerNum < NUM_TIMERS) && (timer[timerNum].tc != tc)) {
|
||||||
|
|
@ -156,13 +172,27 @@ void _PM_timerInit(void *tptr) {
|
||||||
NVIC_EnableIRQ(timer[timerNum].IRQn);
|
NVIC_EnableIRQ(timer[timerNum].IRQn);
|
||||||
|
|
||||||
// Timer is configured but NOT enabled by default
|
// 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.
|
// Set timer period, initialize count value to zero, enable timer.
|
||||||
// Timer must be initialized to 16-bit mode using the init function
|
// Timer must be initialized to 16-bit mode using the init function
|
||||||
// above, but must be inactive before calling this.
|
// above, but must be inactive before calling this.
|
||||||
inline void _PM_timerStart(void *tptr, uint32_t period) {
|
inline void _PM_timerStart(Protomatter_core *core, uint32_t period) {
|
||||||
Tc *tc = (Tc *)tptr; // Cast peripheral address passed in
|
Tc *tc = (Tc *)core->timer; // Cast peripheral address passed in
|
||||||
tc->COUNT16.COUNT.reg = 0;
|
tc->COUNT16.COUNT.reg = 0;
|
||||||
while (tc->COUNT16.SYNCBUSY.bit.COUNT)
|
while (tc->COUNT16.SYNCBUSY.bit.COUNT)
|
||||||
;
|
;
|
||||||
|
|
@ -176,8 +206,8 @@ inline void _PM_timerStart(void *tptr, uint32_t period) {
|
||||||
|
|
||||||
// Return current count value (timer enabled or not).
|
// Return current count value (timer enabled or not).
|
||||||
// Timer must be previously initialized.
|
// Timer must be previously initialized.
|
||||||
inline uint32_t _PM_timerGetCount(void *tptr) {
|
inline uint32_t _PM_timerGetCount(Protomatter_core *core) {
|
||||||
Tc *tc = (Tc *)tptr; // Cast peripheral address passed in
|
Tc *tc = (Tc *)core->timer; // Cast peripheral address passed in
|
||||||
tc->COUNT16.CTRLBSET.bit.CMD = 0x4; // Sync COUNT
|
tc->COUNT16.CTRLBSET.bit.CMD = 0x4; // Sync COUNT
|
||||||
while (tc->COUNT16.CTRLBSET.bit.CMD)
|
while (tc->COUNT16.CTRLBSET.bit.CMD)
|
||||||
; // Wait for command
|
; // Wait for command
|
||||||
|
|
@ -186,30 +216,213 @@ inline uint32_t _PM_timerGetCount(void *tptr) {
|
||||||
|
|
||||||
// Disable timer and return current count value.
|
// Disable timer and return current count value.
|
||||||
// Timer must be previously initialized.
|
// Timer must be previously initialized.
|
||||||
uint32_t _PM_timerStop(void *tptr) {
|
uint32_t _PM_timerStop(Protomatter_core *core) {
|
||||||
Tc *tc = (Tc *)tptr; // Cast peripheral address passed in
|
Tc *tc = (Tc *)core->timer; // Cast peripheral address passed in
|
||||||
uint32_t count = _PM_timerGetCount(tptr);
|
uint32_t count = _PM_timerGetCount(core);
|
||||||
tc->COUNT16.CTRLA.bit.ENABLE = 0;
|
tc->COUNT16.CTRLA.bit.ENABLE = 0;
|
||||||
while (tc->COUNT16.SYNCBUSY.bit.STATUS)
|
while (tc->COUNT16.SYNCBUSY.bit.STATUS)
|
||||||
;
|
;
|
||||||
return count;
|
return count;
|
||||||
}
|
}
|
||||||
|
|
||||||
// See notes in core.c before the "blast" functions
|
// SAMD51 takes a WEIRD TURN here, in an attempt to make the HUB75 clock
|
||||||
#if F_CPU >= 200000000
|
// waveform slightly adjustable. Old vs new matrices seem to have different
|
||||||
#define _PM_clockHoldHigh asm("nop; nop; nop; nop; nop");
|
// preferences, and this tries to address that. If this works well then the
|
||||||
#define _PM_clockHoldLow asm("nop; nop");
|
// approach might be applied to other architectures (which are all fixed
|
||||||
#elif F_CPU >= 180000000
|
// duty cycle right now). THE CHALLENGE is that Protomatter works in a bit-
|
||||||
#define _PM_clockHoldHigh asm("nop; nop; nop; nop");
|
// bangingly way (this is on purpose and by design, avoiding peripherals
|
||||||
#define _PM_clockHoldLow asm("nop");
|
// that might work only on certain pins, for better compatibility with
|
||||||
#elif F_CPU >= 150000000
|
// existing shields and wings from the AVR era), we're aiming for nearly a
|
||||||
#define _PM_clockHoldHigh asm("nop; nop; nop");
|
// 20 MHz signal, and the SAMD51 cycle clock is ostensibly 120 MHz. With
|
||||||
#define _PM_clockHoldLow asm("nop");
|
// just a few cycles to toggle the data and clock lines, that doesn't even
|
||||||
#else
|
// leave enough time for a counter loop.
|
||||||
#define _PM_clockHoldHigh asm("nop; nop; nop");
|
|
||||||
#define _PM_clockHoldLow asm("nop");
|
#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;
|
||||||
|
|
||||||
#endif
|
#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
|
#define _PM_minMinPeriod 160
|
||||||
|
|
||||||
#endif // END __SAMD51__ || SAMD51
|
#endif // END __SAMD51__ || SAM_D5X_E5X
|
||||||
|
|
|
||||||
|
|
@ -28,7 +28,7 @@
|
||||||
#include "timers.h"
|
#include "timers.h"
|
||||||
|
|
||||||
#undef _PM_portBitMask
|
#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_byteOffset(pin) ((pin & 15) / 8)
|
||||||
#define _PM_wordOffset(pin) ((pin & 15) / 16)
|
#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?
|
// TODO: this is no longer true, should it change?
|
||||||
void *_PM_protoPtr = NULL;
|
void *_PM_protoPtr = NULL;
|
||||||
|
|
||||||
STATIC TIM_HandleTypeDef tim_handle;
|
static TIM_HandleTypeDef tim_handle;
|
||||||
|
|
||||||
// Timer interrupt service routine
|
// Timer interrupt service routine
|
||||||
void _PM_IRQ_HANDLER(void) {
|
void _PM_IRQ_HANDLER(void) {
|
||||||
|
|
@ -93,8 +93,8 @@ void _PM_IRQ_HANDLER(void) {
|
||||||
}
|
}
|
||||||
|
|
||||||
// Initialize, but do not start, timer
|
// Initialize, but do not start, timer
|
||||||
void _PM_timerInit(void *tptr) {
|
void _PM_timerInit(Protomatter_core *core) {
|
||||||
TIM_TypeDef *tim_instance = (TIM_TypeDef *)tptr;
|
TIM_TypeDef *tim_instance = (TIM_TypeDef *)core->timer;
|
||||||
stm_peripherals_timer_reserve(tim_instance);
|
stm_peripherals_timer_reserve(tim_instance);
|
||||||
// Set IRQs at max priority and start clock
|
// Set IRQs at max priority and start clock
|
||||||
stm_peripherals_timer_preinit(tim_instance, 0, _PM_IRQ_HANDLER);
|
stm_peripherals_timer_preinit(tim_instance, 0, _PM_IRQ_HANDLER);
|
||||||
|
|
@ -114,8 +114,8 @@ void _PM_timerInit(void *tptr) {
|
||||||
NVIC_SetPriority(tim_irq, 0); // Top priority
|
NVIC_SetPriority(tim_irq, 0); // Top priority
|
||||||
}
|
}
|
||||||
|
|
||||||
inline void _PM_timerStart(void *tptr, uint32_t period) {
|
inline void _PM_timerStart(Protomatter_core *core, uint32_t period) {
|
||||||
TIM_TypeDef *tim = tptr;
|
TIM_TypeDef *tim = core->timer;
|
||||||
tim->SR = 0;
|
tim->SR = 0;
|
||||||
tim->ARR = period;
|
tim->ARR = period;
|
||||||
tim->CR1 |= TIM_CR1_CEN;
|
tim->CR1 |= TIM_CR1_CEN;
|
||||||
|
|
@ -123,8 +123,13 @@ inline void _PM_timerStart(void *tptr, uint32_t period) {
|
||||||
HAL_NVIC_EnableIRQ(stm_peripherals_timer_get_irqnum(tim));
|
HAL_NVIC_EnableIRQ(stm_peripherals_timer_get_irqnum(tim));
|
||||||
}
|
}
|
||||||
|
|
||||||
uint32_t _PM_timerStop(void *tptr) {
|
inline uint32_t _PM_timerGetCount(Protomatter_core *core) {
|
||||||
TIM_TypeDef *tim = tptr;
|
TIM_TypeDef *tim = core->timer;
|
||||||
|
return tim->CNT;
|
||||||
|
}
|
||||||
|
|
||||||
|
uint32_t _PM_timerStop(Protomatter_core *core) {
|
||||||
|
TIM_TypeDef *tim = core->timer;
|
||||||
HAL_NVIC_DisableIRQ(stm_peripherals_timer_get_irqnum(tim));
|
HAL_NVIC_DisableIRQ(stm_peripherals_timer_get_irqnum(tim));
|
||||||
tim->CR1 &= ~TIM_CR1_CEN;
|
tim->CR1 &= ~TIM_CR1_CEN;
|
||||||
tim->DIER &= ~TIM_DIER_UIE;
|
tim->DIER &= ~TIM_DIER_UIE;
|
||||||
|
|
|
||||||
|
|
@ -118,8 +118,8 @@ static void _PM_timerISR(void) {
|
||||||
}
|
}
|
||||||
|
|
||||||
// Initialize, but do not start, timer.
|
// Initialize, but do not start, timer.
|
||||||
void _PM_timerInit(void *tptr) {
|
void _PM_timerInit(Protomatter_core *core) {
|
||||||
IMXRT_PIT_CHANNEL_t *timer = (IMXRT_PIT_CHANNEL_t *)tptr;
|
IMXRT_PIT_CHANNEL_t *timer = (IMXRT_PIT_CHANNEL_t *)core->timer;
|
||||||
CCM_CCGR1 |= CCM_CCGR1_PIT(CCM_CCGR_ON); // Enable clock signal to PIT
|
CCM_CCGR1 |= CCM_CCGR1_PIT(CCM_CCGR_ON); // Enable clock signal to PIT
|
||||||
PIT_MCR = 1; // Enable PIT
|
PIT_MCR = 1; // Enable PIT
|
||||||
timer->TCTRL = 0; // Disable timer and interrupt
|
timer->TCTRL = 0; // Disable timer and interrupt
|
||||||
|
|
@ -130,8 +130,8 @@ void _PM_timerInit(void *tptr) {
|
||||||
}
|
}
|
||||||
|
|
||||||
// Set timer period, initialize count value to zero, enable timer.
|
// Set timer period, initialize count value to zero, enable timer.
|
||||||
inline void _PM_timerStart(void *tptr, uint32_t period) {
|
inline void _PM_timerStart(Protomatter_core *core, uint32_t period) {
|
||||||
IMXRT_PIT_CHANNEL_t *timer = (IMXRT_PIT_CHANNEL_t *)tptr;
|
IMXRT_PIT_CHANNEL_t *timer = (IMXRT_PIT_CHANNEL_t *)core->timer;
|
||||||
timer->TCTRL = 0; // Disable timer and interrupt
|
timer->TCTRL = 0; // Disable timer and interrupt
|
||||||
timer->LDVAL = period; // Set load value
|
timer->LDVAL = period; // Set load value
|
||||||
// timer->CVAL = period; // And current value (just in case?)
|
// timer->CVAL = period; // And current value (just in case?)
|
||||||
|
|
@ -141,17 +141,17 @@ inline void _PM_timerStart(void *tptr, uint32_t period) {
|
||||||
|
|
||||||
// Return current count value (timer enabled or not).
|
// Return current count value (timer enabled or not).
|
||||||
// Timer must be previously initialized.
|
// Timer must be previously initialized.
|
||||||
inline uint32_t _PM_timerGetCount(void *tptr) {
|
inline uint32_t _PM_timerGetCount(Protomatter_core *core) {
|
||||||
IMXRT_PIT_CHANNEL_t *timer = (IMXRT_PIT_CHANNEL_t *)tptr;
|
IMXRT_PIT_CHANNEL_t *timer = (IMXRT_PIT_CHANNEL_t *)core->timer;
|
||||||
return (timer->LDVAL - timer->CVAL);
|
return (timer->LDVAL - timer->CVAL);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Disable timer and return current count value.
|
// Disable timer and return current count value.
|
||||||
// Timer must be previously initialized.
|
// Timer must be previously initialized.
|
||||||
uint32_t _PM_timerStop(void *tptr) {
|
uint32_t _PM_timerStop(Protomatter_core *core) {
|
||||||
IMXRT_PIT_CHANNEL_t *timer = (IMXRT_PIT_CHANNEL_t *)tptr;
|
IMXRT_PIT_CHANNEL_t *timer = (IMXRT_PIT_CHANNEL_t *)core->timer;
|
||||||
timer->TCTRL = 0; // Disable timer and interrupt
|
timer->TCTRL = 0; // Disable timer and interrupt
|
||||||
return _PM_timerGetCount(tptr);
|
return _PM_timerGetCount(core);
|
||||||
}
|
}
|
||||||
|
|
||||||
#define _PM_clockHoldHigh \
|
#define _PM_clockHoldHigh \
|
||||||
|
|
|
||||||
565
src/core.c
565
src/core.c
|
|
@ -32,6 +32,7 @@
|
||||||
#include "core.h" // enums and structs
|
#include "core.h" // enums and structs
|
||||||
#include "arch/arch.h" // Do NOT include this in any other source files
|
#include "arch/arch.h" // Do NOT include this in any other source files
|
||||||
#include <stddef.h>
|
#include <stddef.h>
|
||||||
|
#include <stdlib.h>
|
||||||
#include <string.h>
|
#include <string.h>
|
||||||
|
|
||||||
// Overall matrix refresh rate (frames/second) is a function of matrix width
|
// Overall matrix refresh rate (frames/second) is a function of matrix width
|
||||||
|
|
@ -72,12 +73,19 @@ static void blast_byte(Protomatter_core *core, uint8_t *data);
|
||||||
static void blast_word(Protomatter_core *core, uint16_t *data);
|
static void blast_word(Protomatter_core *core, uint16_t *data);
|
||||||
static void blast_long(Protomatter_core *core, uint32_t *data);
|
static void blast_long(Protomatter_core *core, uint32_t *data);
|
||||||
|
|
||||||
|
// Needed only for panels with FM6126A chipset
|
||||||
|
static void _PM_resetFM6126A(Protomatter_core *core);
|
||||||
|
|
||||||
|
#if !defined(_PM_clearReg)
|
||||||
#define _PM_clearReg(x) \
|
#define _PM_clearReg(x) \
|
||||||
(*(volatile _PM_PORT_TYPE *)((x).clearReg) = \
|
(*(volatile _PM_PORT_TYPE *)((x).clearReg) = \
|
||||||
((x).bit)) ///< Clear non-RGB-data-or-clock control line (_PM_pin type)
|
((x).bit)) ///< Clear non-RGB-data-or-clock control line (_PM_pin type)
|
||||||
|
#endif
|
||||||
|
#if !defined(_PM_setReg)
|
||||||
#define _PM_setReg(x) \
|
#define _PM_setReg(x) \
|
||||||
(*(volatile _PM_PORT_TYPE *)((x).setReg) = \
|
(*(volatile _PM_PORT_TYPE *)((x).setReg) = \
|
||||||
((x).bit)) ///< Set non-RGB-data-or-clock control line (_PM_pin type)
|
((x).bit)) ///< Set non-RGB-data-or-clock control line (_PM_pin type)
|
||||||
|
#endif
|
||||||
|
|
||||||
// Validate and populate vital elements of core structure.
|
// Validate and populate vital elements of core structure.
|
||||||
// Does NOT allocate core struct -- calling function must provide that.
|
// Does NOT allocate core struct -- calling function must provide that.
|
||||||
|
|
@ -86,17 +94,33 @@ ProtomatterStatus _PM_init(Protomatter_core *core, uint16_t bitWidth,
|
||||||
uint8_t bitDepth, uint8_t rgbCount, uint8_t *rgbList,
|
uint8_t bitDepth, uint8_t rgbCount, uint8_t *rgbList,
|
||||||
uint8_t addrCount, uint8_t *addrList,
|
uint8_t addrCount, uint8_t *addrList,
|
||||||
uint8_t clockPin, uint8_t latchPin, uint8_t oePin,
|
uint8_t clockPin, uint8_t latchPin, uint8_t oePin,
|
||||||
bool doubleBuffer, void *timer) {
|
bool doubleBuffer, int8_t tile, void *timer) {
|
||||||
if (!core)
|
if (!core) {
|
||||||
return PROTOMATTER_ERR_ARG;
|
return PROTOMATTER_ERR_ARG;
|
||||||
|
}
|
||||||
|
|
||||||
|
// bitDepth is NOT constrained here, handle in calling function
|
||||||
|
// (varies with implementation, e.g. GFX lib is max 6 bitplanes,
|
||||||
|
// but might be more or less elsewhere)
|
||||||
|
#if defined(_PM_bytesPerElement)
|
||||||
|
#if _PM_bytesPerElement == 1
|
||||||
|
if (rgbCount > 1)
|
||||||
|
rgbCount = 1; // Parallel output not supported if only 8-bit PORT
|
||||||
|
#elif _PM_bytesPerElement == 2
|
||||||
|
if (rgbCount > 2)
|
||||||
|
rgbCount = 2; // Max 2 in parallel (13 bits in 16-bit PORT)
|
||||||
|
#else
|
||||||
if (rgbCount > 5)
|
if (rgbCount > 5)
|
||||||
rgbCount = 5; // Max 5 in parallel (32-bit PORT)
|
rgbCount = 5; // Max 5 in parallel (31 bits in 32-bit PORT)
|
||||||
|
#endif
|
||||||
|
#else
|
||||||
|
if (rgbCount > 5)
|
||||||
|
rgbCount = 5; // Max 5 in parallel (31 bits in 32-bit PORT)
|
||||||
|
#endif // end _PM_bytesPerElement
|
||||||
if (addrCount > 5)
|
if (addrCount > 5)
|
||||||
addrCount = 5; // Max 5 address lines (A-E)
|
addrCount = 5; // Max 5 address lines (A-E)
|
||||||
// bitDepth is NOT constrained here, handle in calling function
|
if (!tile)
|
||||||
// (varies with implementation, e.g. GFX lib is max 6 bitplanes,
|
tile = 1; // Can't have zero vertical tiling. Single matrix is 1.
|
||||||
// but might be more or less elsewhere)
|
|
||||||
|
|
||||||
#if defined(_PM_TIMER_DEFAULT)
|
#if defined(_PM_TIMER_DEFAULT)
|
||||||
// If NULL timer was passed in (the default case for the constructor),
|
// If NULL timer was passed in (the default case for the constructor),
|
||||||
|
|
@ -110,7 +134,9 @@ ProtomatterStatus _PM_init(Protomatter_core *core, uint16_t bitWidth,
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
core->timer = timer;
|
core->timer = timer;
|
||||||
core->width = bitWidth; // Total matrix chain length in bits
|
core->width = bitWidth; // Matrix chain width in bits (NOT including V tile)
|
||||||
|
core->tile = tile; // Matrix chain vertical tiling
|
||||||
|
core->chainBits = bitWidth * abs(tile); // Total matrix chain bits
|
||||||
core->numPlanes = bitDepth;
|
core->numPlanes = bitDepth;
|
||||||
core->parallel = rgbCount;
|
core->parallel = rgbCount;
|
||||||
core->numAddressLines = addrCount;
|
core->numAddressLines = addrCount;
|
||||||
|
|
@ -151,6 +177,14 @@ ProtomatterStatus _PM_begin(Protomatter_core *core) {
|
||||||
return PROTOMATTER_ERR_MALLOC;
|
return PROTOMATTER_ERR_MALLOC;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#if defined(_PM_bytesPerElement)
|
||||||
|
// Some chips (e.g. ESP32S2 & S3) have potent pin MUX capabilities and
|
||||||
|
// arch-specific code might use special peripherals. The usual rules about
|
||||||
|
// RGB+clock on one PORT, and the size of the internal data representation,
|
||||||
|
// can be overridden because they're much simplified.
|
||||||
|
core->bytesPerElement = _PM_bytesPerElement;
|
||||||
|
uint32_t bitMask = 0;
|
||||||
|
#else
|
||||||
// Verify that rgbPins and clockPin are all on the same PORT. If not,
|
// Verify that rgbPins and clockPin are all on the same PORT. If not,
|
||||||
// return an error. Pin list is not freed; please call dealloc function.
|
// return an error. Pin list is not freed; please call dealloc function.
|
||||||
// Also get bitmask of which bits within 32-bit PORT register are
|
// Also get bitmask of which bits within 32-bit PORT register are
|
||||||
|
|
@ -208,10 +242,11 @@ ProtomatterStatus _PM_begin(Protomatter_core *core) {
|
||||||
core->bytesPerElement = 4; // Use 32-bit PORT accesses.
|
core->bytesPerElement = 4; // Use 32-bit PORT accesses.
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
#endif // end RGB+clock PORT check & bytesPerElement calc
|
||||||
|
|
||||||
// Planning for screen data allocation...
|
// Planning for screen data allocation...
|
||||||
core->numRowPairs = 1 << core->numAddressLines;
|
core->numRowPairs = 1 << core->numAddressLines;
|
||||||
uint8_t chunks = (core->width + (_PM_chunkSize - 1)) / _PM_chunkSize;
|
uint8_t chunks = (core->chainBits + (_PM_chunkSize - 1)) / _PM_chunkSize;
|
||||||
uint16_t columns = chunks * _PM_chunkSize; // Padded matrix width
|
uint16_t columns = chunks * _PM_chunkSize; // Padded matrix width
|
||||||
uint32_t screenBytes =
|
uint32_t screenBytes =
|
||||||
columns * core->numRowPairs * core->numPlanes * core->bytesPerElement;
|
columns * core->numRowPairs * core->numPlanes * core->bytesPerElement;
|
||||||
|
|
@ -232,7 +267,7 @@ ProtomatterStatus _PM_begin(Protomatter_core *core) {
|
||||||
// rgbMask data follows the matrix buffer(s)
|
// rgbMask data follows the matrix buffer(s)
|
||||||
core->rgbMask = core->screenData + screenBytes;
|
core->rgbMask = core->screenData + screenBytes;
|
||||||
|
|
||||||
#if !defined(_PM_portToggleRegister)
|
#if !defined(_PM_USE_TOGGLE_FORMAT)
|
||||||
// Clear entire screenData buffer so there's no cruft in any pad bytes
|
// Clear entire screenData buffer so there's no cruft in any pad bytes
|
||||||
// (if using toggle register, each is set to clockMask below instead).
|
// (if using toggle register, each is set to clockMask below instead).
|
||||||
memset(core->screenData, 0, screenBytes);
|
memset(core->screenData, 0, screenBytes);
|
||||||
|
|
@ -241,7 +276,7 @@ ProtomatterStatus _PM_begin(Protomatter_core *core) {
|
||||||
// Figure out clockMask and rgbAndClockMask, clear matrix buffers
|
// Figure out clockMask and rgbAndClockMask, clear matrix buffers
|
||||||
if (core->bytesPerElement == 1) {
|
if (core->bytesPerElement == 1) {
|
||||||
core->portOffset = _PM_byteOffset(core->rgbPins[0]);
|
core->portOffset = _PM_byteOffset(core->rgbPins[0]);
|
||||||
#if defined(_PM_portToggleRegister) && !defined(_PM_STRICT_32BIT_IO)
|
#if defined(_PM_USE_TOGGLE_FORMAT) && !defined(_PM_STRICT_32BIT_IO)
|
||||||
// Clock and rgbAndClockMask are 8-bit values
|
// Clock and rgbAndClockMask are 8-bit values
|
||||||
core->clockMask = _PM_portBitMask(core->clockPin) >> (core->portOffset * 8);
|
core->clockMask = _PM_portBitMask(core->clockPin) >> (core->portOffset * 8);
|
||||||
core->rgbAndClockMask =
|
core->rgbAndClockMask =
|
||||||
|
|
@ -258,7 +293,7 @@ ProtomatterStatus _PM_begin(Protomatter_core *core) {
|
||||||
}
|
}
|
||||||
} else if (core->bytesPerElement == 2) {
|
} else if (core->bytesPerElement == 2) {
|
||||||
core->portOffset = _PM_wordOffset(core->rgbPins[0]);
|
core->portOffset = _PM_wordOffset(core->rgbPins[0]);
|
||||||
#if defined(_PM_portToggleRegister) && !defined(_PM_STRICT_32BIT_IO)
|
#if defined(_PM_USE_TOGGLE_FORMAT) && !defined(_PM_STRICT_32BIT_IO)
|
||||||
// Clock and rgbAndClockMask are 16-bit values
|
// Clock and rgbAndClockMask are 16-bit values
|
||||||
core->clockMask =
|
core->clockMask =
|
||||||
_PM_portBitMask(core->clockPin) >> (core->portOffset * 16);
|
_PM_portBitMask(core->clockPin) >> (core->portOffset * 16);
|
||||||
|
|
@ -272,7 +307,7 @@ ProtomatterStatus _PM_begin(Protomatter_core *core) {
|
||||||
// Clock and rgbAndClockMask are 32-bit values
|
// Clock and rgbAndClockMask are 32-bit values
|
||||||
core->clockMask = _PM_portBitMask(core->clockPin);
|
core->clockMask = _PM_portBitMask(core->clockPin);
|
||||||
core->rgbAndClockMask = bitMask | core->clockMask;
|
core->rgbAndClockMask = bitMask | core->clockMask;
|
||||||
#if defined(_PM_portToggleRegister)
|
#if defined(_PM_USE_TOGGLE_FORMAT)
|
||||||
// TO DO: this ifdef and the one above can probably be wrapped up
|
// TO DO: this ifdef and the one above can probably be wrapped up
|
||||||
// in a more cohesive case. Think something similar will be needed
|
// in a more cohesive case. Think something similar will be needed
|
||||||
// for the byte case. Will need Teensy 4.1 to test.
|
// for the byte case. Will need Teensy 4.1 to test.
|
||||||
|
|
@ -291,7 +326,7 @@ ProtomatterStatus _PM_begin(Protomatter_core *core) {
|
||||||
core->portOffset = 0;
|
core->portOffset = 0;
|
||||||
core->clockMask = _PM_portBitMask(core->clockPin);
|
core->clockMask = _PM_portBitMask(core->clockPin);
|
||||||
core->rgbAndClockMask = bitMask | core->clockMask;
|
core->rgbAndClockMask = bitMask | core->clockMask;
|
||||||
#if defined(_PM_portToggleRegister)
|
#if defined(_PM_USE_TOGGLE_FORMAT)
|
||||||
uint32_t elements = screenBytes / 4;
|
uint32_t elements = screenBytes / 4;
|
||||||
for (uint32_t i = 0; i < elements; i++) {
|
for (uint32_t i = 0; i < elements; i++) {
|
||||||
((uint32_t *)core->screenData)[i] = core->clockMask;
|
((uint32_t *)core->screenData)[i] = core->clockMask;
|
||||||
|
|
@ -310,14 +345,11 @@ ProtomatterStatus _PM_begin(Protomatter_core *core) {
|
||||||
if (core->minPeriod < _PM_minMinPeriod) {
|
if (core->minPeriod < _PM_minMinPeriod) {
|
||||||
core->minPeriod = _PM_minMinPeriod;
|
core->minPeriod = _PM_minMinPeriod;
|
||||||
}
|
}
|
||||||
|
core->bitZeroPeriod = core->minPeriod;
|
||||||
// Actual frame rate may be lower than this...it's only an estimate
|
// Actual frame rate may be lower than this...it's only an estimate
|
||||||
// and does not factor in things like address line selection delays
|
// and does not factor in things like address line selection delays
|
||||||
// or interrupt overhead. That's OK, just don't want to exceed this
|
// or interrupt overhead. That's OK, just don't want to exceed this
|
||||||
// rate, as it'll eat all the CPU cycles.
|
// rate, as it'll eat all the CPU cycles.
|
||||||
// Make a wild guess for the initial bit-zero interval. It's okay
|
|
||||||
// that this is off, code adapts to actual timer results pretty quick.
|
|
||||||
|
|
||||||
core->bitZeroPeriod = core->width * 5; // Initial guesstimate
|
|
||||||
|
|
||||||
core->activeBuffer = 0;
|
core->activeBuffer = 0;
|
||||||
|
|
||||||
|
|
@ -341,6 +373,9 @@ ProtomatterStatus _PM_begin(Protomatter_core *core) {
|
||||||
_PM_pinOutput(core->rgbPins[i]);
|
_PM_pinOutput(core->rgbPins[i]);
|
||||||
_PM_pinLow(core->rgbPins[i]);
|
_PM_pinLow(core->rgbPins[i]);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
_PM_resetFM6126A(core);
|
||||||
|
|
||||||
#if defined(_PM_portToggleRegister)
|
#if defined(_PM_portToggleRegister)
|
||||||
core->addrPortToggle = _PM_portToggleRegister(core->addr[0].pin);
|
core->addrPortToggle = _PM_portToggleRegister(core->addr[0].pin);
|
||||||
core->singleAddrPort = 1;
|
core->singleAddrPort = 1;
|
||||||
|
|
@ -389,9 +424,9 @@ void _PM_stop(Protomatter_core *core) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
while (core->swapBuffers)
|
while (core->swapBuffers)
|
||||||
; // Wait for any pending buffer swap
|
; // Wait for any pending buffer swap
|
||||||
_PM_timerStop(core->timer); // Halt timer
|
_PM_timerStop(core); // Halt timer
|
||||||
_PM_setReg(core->oe); // Set OE HIGH (disable output)
|
_PM_setReg(core->oe); // Set OE HIGH (disable output)
|
||||||
// So, in PRINCIPLE, setting OE high would be sufficient...
|
// So, in PRINCIPLE, setting OE high would be sufficient...
|
||||||
// but in case that pin is shared with another function such
|
// but in case that pin is shared with another function such
|
||||||
// as the onloard LED (which pulses during bootloading) let's
|
// as the onloard LED (which pulses during bootloading) let's
|
||||||
|
|
@ -401,7 +436,7 @@ void _PM_stop(Protomatter_core *core) {
|
||||||
_PM_pinLow(core->rgbPins[i]);
|
_PM_pinLow(core->rgbPins[i]);
|
||||||
}
|
}
|
||||||
// Clock out bits (just need to toggle clock with RGBs held low)
|
// Clock out bits (just need to toggle clock with RGBs held low)
|
||||||
for (uint32_t i = 0; i < core->width; i++) {
|
for (uint32_t i = 0; i < core->chainBits; i++) {
|
||||||
_PM_pinHigh(core->clockPin);
|
_PM_pinHigh(core->clockPin);
|
||||||
_PM_clockHoldHigh;
|
_PM_clockHoldHigh;
|
||||||
_PM_pinLow(core->clockPin);
|
_PM_pinLow(core->clockPin);
|
||||||
|
|
@ -422,8 +457,18 @@ void _PM_resume(Protomatter_core *core) {
|
||||||
core->swapBuffers = 0;
|
core->swapBuffers = 0;
|
||||||
core->frameCount = 0;
|
core->frameCount = 0;
|
||||||
|
|
||||||
_PM_timerInit(core->timer); // Configure timer
|
for (uint8_t line = 0, bit = 1; line < core->numAddressLines;
|
||||||
_PM_timerStart(core->timer, 1000); // Start timer
|
line++, bit <<= 1) {
|
||||||
|
_PM_pinOutput(core->addr[line].pin);
|
||||||
|
if (core->prevRow & bit) {
|
||||||
|
_PM_pinHigh(core->addr[line].pin);
|
||||||
|
} else {
|
||||||
|
_PM_pinLow(core->addr[line].pin);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
_PM_timerInit(core); // Configure timer & any other periphs
|
||||||
|
_PM_timerStart(core, 1000); // Start timer
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -468,23 +513,10 @@ IRAM_ATTR void _PM_row_handler(Protomatter_core *core) {
|
||||||
_PM_clearReg(core->latch);
|
_PM_clearReg(core->latch);
|
||||||
|
|
||||||
_PM_setReg(core->latch);
|
_PM_setReg(core->latch);
|
||||||
// Stop timer, save count value at stop
|
(void)_PM_timerStop(core);
|
||||||
uint32_t elapsed = _PM_timerStop(core->timer);
|
|
||||||
uint8_t prevPlane = core->plane; // Save that plane # for later timing
|
uint8_t prevPlane = core->plane; // Save that plane # for later timing
|
||||||
_PM_clearReg(core->latch); // (split to add a few cycles)
|
_PM_clearReg(core->latch); // (split to add a few cycles)
|
||||||
|
|
||||||
// If plane 0 just finished being displayed (plane 1 was loaded on prior
|
|
||||||
// pass, or there's only one plane...I know, it's confusing), take note
|
|
||||||
// of the elapsed timer value, for subsequent bitplane timing (each
|
|
||||||
// plane period is double the previous). Value is filtered slightly to
|
|
||||||
// avoid jitter.
|
|
||||||
if ((prevPlane == 1) || (core->numPlanes == 1)) {
|
|
||||||
core->bitZeroPeriod = ((core->bitZeroPeriod * 7) + elapsed) / 8;
|
|
||||||
if (core->bitZeroPeriod < core->minPeriod) {
|
|
||||||
core->bitZeroPeriod = core->minPeriod;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (prevPlane == 0) { // Plane 0 just finished loading
|
if (prevPlane == 0) { // Plane 0 just finished loading
|
||||||
#if defined(_PM_portToggleRegister)
|
#if defined(_PM_portToggleRegister)
|
||||||
// If all address lines are on a single PORT (and bit toggle is
|
// If all address lines are on a single PORT (and bit toggle is
|
||||||
|
|
@ -540,17 +572,17 @@ IRAM_ATTR void _PM_row_handler(Protomatter_core *core) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// 'plane' now is index of data to issue, NOT data to display.
|
// core->plane now is index of data to issue, NOT data to display.
|
||||||
// 'prevPlane' is the previously-loaded data, which gets displayed
|
// 'prevPlane' is the previously-loaded data, which gets displayed
|
||||||
// now while the next plane data is loaded.
|
// now while the next plane data is loaded.
|
||||||
|
|
||||||
// Set timer and enable LED output for data loaded on PRIOR pass:
|
// Set timer and enable LED output for data loaded on PRIOR pass:
|
||||||
_PM_timerStart(core->timer, core->bitZeroPeriod << prevPlane);
|
_PM_timerStart(core, core->bitZeroPeriod << prevPlane);
|
||||||
_PM_delayMicroseconds(1); // Appease Teensy4
|
_PM_delayMicroseconds(1); // Appease Teensy4
|
||||||
_PM_clearReg(core->oe); // Enable LED output
|
_PM_clearReg(core->oe); // Enable LED output
|
||||||
|
|
||||||
uint32_t elementsPerLine =
|
uint32_t elementsPerLine =
|
||||||
_PM_chunkSize * ((core->width + (_PM_chunkSize - 1)) / _PM_chunkSize);
|
_PM_chunkSize * ((core->chainBits + (_PM_chunkSize - 1)) / _PM_chunkSize);
|
||||||
uint32_t srcOffset = elementsPerLine *
|
uint32_t srcOffset = elementsPerLine *
|
||||||
(core->numPlanes * core->row + core->plane) *
|
(core->numPlanes * core->row + core->plane) *
|
||||||
core->bytesPerElement;
|
core->bytesPerElement;
|
||||||
|
|
@ -566,9 +598,30 @@ IRAM_ATTR void _PM_row_handler(Protomatter_core *core) {
|
||||||
blast_long(core, (uint32_t *)(core->screenData + srcOffset));
|
blast_long(core, (uint32_t *)(core->screenData + srcOffset));
|
||||||
}
|
}
|
||||||
|
|
||||||
// 'plane' data is now loaded, will be shown on NEXT pass
|
// core->plane data is now loaded, will be shown on NEXT pass
|
||||||
|
|
||||||
|
// On the last (longest) bitplane (note that 'plane' has already wrapped
|
||||||
|
// around earlier, so a value of 0 here indicates longest plane), take
|
||||||
|
// note of the elapsed timer value at this point...that's the number of
|
||||||
|
// cycles required to issue (not necessarily display) data for one plane,
|
||||||
|
// and the bare minimum display duration allowed for plane 0.
|
||||||
|
if ((core->numPlanes > 1) && (core->plane == 0)) {
|
||||||
|
// Determine number of timer cycles taken to issue the data.
|
||||||
|
// It can vary slightly if heavy interrupts happen, things like that.
|
||||||
|
// Timer is still running and counting up at this point.
|
||||||
|
uint32_t elapsed = _PM_timerGetCount(core);
|
||||||
|
// Nudge the plane-zero time up or down (filtering to avoid jitter)
|
||||||
|
core->bitZeroPeriod = ((core->bitZeroPeriod * 7) + elapsed + 4) / 8;
|
||||||
|
// But don't allow it to drop below the minimum period calculated during
|
||||||
|
// begin(), that's a hard limit and would just waste cycles.
|
||||||
|
if (core->bitZeroPeriod < core->minPeriod) {
|
||||||
|
core->bitZeroPeriod = core->minPeriod;
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#if !defined _PM_CUSTOM_BLAST
|
||||||
|
|
||||||
// Innermost data-stuffing loop functions
|
// Innermost data-stuffing loop functions
|
||||||
|
|
||||||
// The presence of a bit-toggle register can make the data-stuffing loop a
|
// The presence of a bit-toggle register can make the data-stuffing loop a
|
||||||
|
|
@ -613,7 +666,7 @@ IRAM_ATTR void _PM_row_handler(Protomatter_core *core) {
|
||||||
_PM_clockHoldLow; \
|
_PM_clockHoldLow; \
|
||||||
*set = clock; /* Set clock high */ \
|
*set = clock; /* Set clock high */ \
|
||||||
_PM_clockHoldHigh; \
|
_PM_clockHoldHigh; \
|
||||||
*clear = rgbclock; /* Clear RGB data + clock */ \
|
*clear_full = rgbclock; /* Clear RGB data + clock */ \
|
||||||
///< Bitbang one set of RGB data bits to matrix
|
///< Bitbang one set of RGB data bits to matrix
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
|
|
@ -674,7 +727,7 @@ IRAM_ATTR static void blast_byte(Protomatter_core *core, uint8_t *data) {
|
||||||
_PM_PORT_TYPE rgbclock = core->rgbAndClockMask; // RGB + clock bit
|
_PM_PORT_TYPE rgbclock = core->rgbAndClockMask; // RGB + clock bit
|
||||||
#endif
|
#endif
|
||||||
_PM_PORT_TYPE clock = core->clockMask; // Clock bit
|
_PM_PORT_TYPE clock = core->clockMask; // Clock bit
|
||||||
uint8_t chunks = (core->width + (_PM_chunkSize - 1)) / _PM_chunkSize;
|
uint16_t chunks = (core->chainBits + (_PM_chunkSize - 1)) / _PM_chunkSize;
|
||||||
|
|
||||||
// PORT has already been initialized with RGB data + clock bits
|
// PORT has already been initialized with RGB data + clock bits
|
||||||
// all LOW, so we don't need to initialize that state here.
|
// all LOW, so we don't need to initialize that state here.
|
||||||
|
|
@ -699,12 +752,12 @@ IRAM_ATTR static void blast_byte(Protomatter_core *core, uint8_t *data) {
|
||||||
volatile _PM_PORT_TYPE *toggle = (volatile _PM_PORT_TYPE *)core->toggleReg;
|
volatile _PM_PORT_TYPE *toggle = (volatile _PM_PORT_TYPE *)core->toggleReg;
|
||||||
#else
|
#else
|
||||||
volatile _PM_PORT_TYPE *set = (volatile _PM_PORT_TYPE *)core->setReg;
|
volatile _PM_PORT_TYPE *set = (volatile _PM_PORT_TYPE *)core->setReg;
|
||||||
volatile _PM_PORT_TYPE *clear = (volatile _PM_PORT_TYPE *)core->clearReg;
|
volatile _PM_PORT_TYPE *clear_full = (volatile _PM_PORT_TYPE *)core->clearReg;
|
||||||
_PM_PORT_TYPE rgbclock = core->rgbAndClockMask; // RGB + clock bit
|
_PM_PORT_TYPE rgbclock = core->rgbAndClockMask; // RGB + clock bit
|
||||||
#endif
|
#endif
|
||||||
_PM_PORT_TYPE clock = core->clockMask; // Clock bit
|
_PM_PORT_TYPE clock = core->clockMask; // Clock bit
|
||||||
uint8_t shift = core->portOffset * 8;
|
uint8_t shift = core->portOffset * 8;
|
||||||
uint8_t chunks = (core->width + (_PM_chunkSize - 1)) / _PM_chunkSize;
|
uint8_t chunks = (core->chainBits + (_PM_chunkSize - 1)) / _PM_chunkSize;
|
||||||
|
|
||||||
// PORT has already been initialized with RGB data + clock bits
|
// PORT has already been initialized with RGB data + clock bits
|
||||||
// all LOW, so we don't need to initialize that state here.
|
// all LOW, so we don't need to initialize that state here.
|
||||||
|
|
@ -728,16 +781,16 @@ IRAM_ATTR static void blast_word(Protomatter_core *core, uint16_t *data) {
|
||||||
volatile uint16_t *toggle =
|
volatile uint16_t *toggle =
|
||||||
(volatile uint16_t *)core->toggleReg + core->portOffset;
|
(volatile uint16_t *)core->toggleReg + core->portOffset;
|
||||||
#else
|
#else
|
||||||
volatile uint16_t *set; // For RGB data set
|
volatile uint16_t *set; // For RGB data set
|
||||||
volatile _PM_PORT_TYPE *set_full; // For clock set
|
volatile _PM_PORT_TYPE *set_full; // For clock set
|
||||||
volatile _PM_PORT_TYPE *clear_full; // For RGB data + clock clear
|
volatile _PM_PORT_TYPE *clear_full; // For RGB data + clock clear
|
||||||
set = (volatile uint16_t *)core->setReg + core->portOffset;
|
set = (volatile uint16_t *)core->setReg + core->portOffset;
|
||||||
set_full = (volatile _PM_PORT_TYPE *)core->setReg;
|
set_full = (volatile _PM_PORT_TYPE *)core->setReg;
|
||||||
clear_full = (volatile _PM_PORT_TYPE *)core->clearReg;
|
clear_full = (volatile _PM_PORT_TYPE *)core->clearReg;
|
||||||
_PM_PORT_TYPE rgbclock = core->rgbAndClockMask; // RGB + clock bit
|
_PM_PORT_TYPE rgbclock = core->rgbAndClockMask; // RGB + clock bit
|
||||||
#endif
|
#endif
|
||||||
_PM_PORT_TYPE clock = core->clockMask; // Clock bit
|
_PM_PORT_TYPE clock = core->clockMask; // Clock bit
|
||||||
uint8_t chunks = (core->width + (_PM_chunkSize - 1)) / _PM_chunkSize;
|
uint8_t chunks = (core->chainBits + (_PM_chunkSize - 1)) / _PM_chunkSize;
|
||||||
while (chunks--) {
|
while (chunks--) {
|
||||||
PEW_UNROLL // _PM_chunkSize RGB+clock writes
|
PEW_UNROLL // _PM_chunkSize RGB+clock writes
|
||||||
}
|
}
|
||||||
|
|
@ -753,12 +806,12 @@ IRAM_ATTR static void blast_word(Protomatter_core *core, uint16_t *data) {
|
||||||
volatile _PM_PORT_TYPE *toggle = (volatile _PM_PORT_TYPE *)core->toggleReg;
|
volatile _PM_PORT_TYPE *toggle = (volatile _PM_PORT_TYPE *)core->toggleReg;
|
||||||
#else
|
#else
|
||||||
volatile _PM_PORT_TYPE *set = (volatile _PM_PORT_TYPE *)core->setReg;
|
volatile _PM_PORT_TYPE *set = (volatile _PM_PORT_TYPE *)core->setReg;
|
||||||
volatile _PM_PORT_TYPE *clear = (volatile _PM_PORT_TYPE *)core->clearReg;
|
volatile _PM_PORT_TYPE *clear_full = (volatile _PM_PORT_TYPE *)core->clearReg;
|
||||||
_PM_PORT_TYPE rgbclock = core->rgbAndClockMask; // RGB + clock bit
|
_PM_PORT_TYPE rgbclock = core->rgbAndClockMask; // RGB + clock bit
|
||||||
#endif
|
#endif
|
||||||
_PM_PORT_TYPE clock = core->clockMask; // Clock bit
|
_PM_PORT_TYPE clock = core->clockMask; // Clock bit
|
||||||
uint8_t shift = core->portOffset * 16;
|
uint8_t shift = core->portOffset * 16;
|
||||||
uint8_t chunks = (core->width + (_PM_chunkSize - 1)) / _PM_chunkSize;
|
uint8_t chunks = (core->chainBits + (_PM_chunkSize - 1)) / _PM_chunkSize;
|
||||||
while (chunks--) {
|
while (chunks--) {
|
||||||
PEW_UNROLL // _PM_chunkSize RGB+clock writes
|
PEW_UNROLL // _PM_chunkSize RGB+clock writes
|
||||||
}
|
}
|
||||||
|
|
@ -777,11 +830,13 @@ IRAM_ATTR static void blast_long(Protomatter_core *core, uint32_t *data) {
|
||||||
// Note in this case two copies exist of the PORT set register.
|
// Note in this case two copies exist of the PORT set register.
|
||||||
// The optimizer will most likely simplify this; leaving as-is, not
|
// The optimizer will most likely simplify this; leaving as-is, not
|
||||||
// wanting a special case of the PEW macro due to divergence risk.
|
// wanting a special case of the PEW macro due to divergence risk.
|
||||||
volatile uint32_t *set; // For RGB data set
|
volatile uint32_t *set; // For RGB data set
|
||||||
volatile _PM_PORT_TYPE *set_full; // For clock set
|
#if !defined(_PM_STRICT_32BIT_IO)
|
||||||
|
volatile _PM_PORT_TYPE *set_full; // For clock set
|
||||||
|
set_full = (volatile _PM_PORT_TYPE *)core->setReg;
|
||||||
|
#endif
|
||||||
volatile _PM_PORT_TYPE *clear_full; // For RGB data + clock clear
|
volatile _PM_PORT_TYPE *clear_full; // For RGB data + clock clear
|
||||||
set = (volatile uint32_t *)core->setReg;
|
set = (volatile uint32_t *)core->setReg;
|
||||||
set_full = (volatile _PM_PORT_TYPE *)core->setReg;
|
|
||||||
clear_full = (volatile _PM_PORT_TYPE *)core->clearReg;
|
clear_full = (volatile _PM_PORT_TYPE *)core->clearReg;
|
||||||
_PM_PORT_TYPE rgbclock = core->rgbAndClockMask; // RGB + clock bit
|
_PM_PORT_TYPE rgbclock = core->rgbAndClockMask; // RGB + clock bit
|
||||||
#endif
|
#endif
|
||||||
|
|
@ -789,7 +844,7 @@ IRAM_ATTR static void blast_long(Protomatter_core *core, uint32_t *data) {
|
||||||
#if defined(_PM_STRICT_32BIT_IO)
|
#if defined(_PM_STRICT_32BIT_IO)
|
||||||
uint8_t shift = 0;
|
uint8_t shift = 0;
|
||||||
#endif
|
#endif
|
||||||
uint8_t chunks = (core->width + (_PM_chunkSize - 1)) / _PM_chunkSize;
|
uint8_t chunks = (core->chainBits + (_PM_chunkSize - 1)) / _PM_chunkSize;
|
||||||
while (chunks--) {
|
while (chunks--) {
|
||||||
PEW_UNROLL // _PM_chunkSize RGB+clock writes
|
PEW_UNROLL // _PM_chunkSize RGB+clock writes
|
||||||
}
|
}
|
||||||
|
|
@ -798,6 +853,8 @@ IRAM_ATTR static void blast_long(Protomatter_core *core, uint32_t *data) {
|
||||||
#endif
|
#endif
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#endif // end !_PM_CUSTOM_BLAST
|
||||||
|
|
||||||
// Returns current value of frame counter and resets its value to zero.
|
// Returns current value of frame counter and resets its value to zero.
|
||||||
// Two calls to this, timed one second apart (or use math with other
|
// Two calls to this, timed one second apart (or use math with other
|
||||||
// intervals), can be used to get a rough frames-per-second value for
|
// intervals), can be used to get a rough frames-per-second value for
|
||||||
|
|
@ -821,6 +878,55 @@ void _PM_swapbuffer_maybe(Protomatter_core *core) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Set all RGB data bits high or low. Its done this way (rather than via
|
||||||
|
// setReg/clearReg members, which would be a single call) because the latter
|
||||||
|
// aren't configured on some architectures (e.g. ESP32-S3) where special
|
||||||
|
// peripherals are used. Nothing in FM6126A init needs to be super optimal
|
||||||
|
// as it's only called once briefly on startup...clocking takes more time!
|
||||||
|
static void _PM_rgbState(Protomatter_core *core, bool state) {
|
||||||
|
for (uint8_t p = 0; p < core->parallel * 6; p++) {
|
||||||
|
if (state)
|
||||||
|
_PM_pinHigh(core->rgbPins[p]);
|
||||||
|
else
|
||||||
|
_PM_pinLow(core->rgbPins[p]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Configure one register of FM6126A. Latch assumed LOW on entry.
|
||||||
|
static void _PM_FM6126A_reg(Protomatter_core *core, uint8_t reg,
|
||||||
|
uint16_t dataMask) {
|
||||||
|
for (uint16_t i = 0; i < core->chainBits; i++) {
|
||||||
|
_PM_rgbState(core, dataMask & (0x8000 >> (i & 15)));
|
||||||
|
|
||||||
|
if (i > (core->chainBits - reg))
|
||||||
|
_PM_pinHigh(core->latch.pin);
|
||||||
|
|
||||||
|
_PM_pinHigh(core->clockPin);
|
||||||
|
_PM_delayMicroseconds(1);
|
||||||
|
_PM_pinLow(core->clockPin);
|
||||||
|
_PM_delayMicroseconds(1);
|
||||||
|
}
|
||||||
|
|
||||||
|
_PM_pinLow(core->latch.pin);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Adapted from SmartMatrix: FM6126A chipset reset sequence,
|
||||||
|
// harmless and ignored by other chips. Thanks to Bob Davis:
|
||||||
|
// bobdavis321.blogspot.com/2019/02/p3-64x32-hub75e-led-matrix-panels-with.html
|
||||||
|
static void _PM_resetFM6126A(Protomatter_core *core) {
|
||||||
|
// On arrival here, clock and latch are low, OE is high, no need to config,
|
||||||
|
// but they must be in the same states when finished.
|
||||||
|
|
||||||
|
_PM_FM6126A_reg(core, 12, 0b0111111111111111);
|
||||||
|
_PM_FM6126A_reg(core, 13, 0b0000000001000000);
|
||||||
|
|
||||||
|
_PM_rgbState(core, 0); // Set all RGB low so port toggle can work
|
||||||
|
}
|
||||||
|
|
||||||
|
uint8_t _PM_duty = _PM_defaultDuty;
|
||||||
|
|
||||||
|
void _PM_setDuty(uint8_t d) { _PM_duty = (d > _PM_maxDuty) ? _PM_maxDuty : d; }
|
||||||
|
|
||||||
#if defined(ARDUINO) || defined(CIRCUITPY)
|
#if defined(ARDUINO) || defined(CIRCUITPY)
|
||||||
|
|
||||||
// Arduino and CircuitPython happen to use the same internal canvas
|
// Arduino and CircuitPython happen to use the same internal canvas
|
||||||
|
|
@ -829,11 +935,10 @@ void _PM_swapbuffer_maybe(Protomatter_core *core) {
|
||||||
// 16-bit (565) color conversion functions go here (rather than in the
|
// 16-bit (565) color conversion functions go here (rather than in the
|
||||||
// Arduino lib .cpp) because knowledge is required of chunksize and the
|
// Arduino lib .cpp) because knowledge is required of chunksize and the
|
||||||
// toggle register (or lack thereof), which are only known to this file,
|
// toggle register (or lack thereof), which are only known to this file,
|
||||||
// not the .cpp or anywhere else
|
// not the .cpp or anywhere else. However...this file knows nothing of
|
||||||
// However...this file knows nothing of the GFXcanvas16 type (from
|
// the GFXcanvas16 type (from Adafruit_GFX...another C++ lib), so the
|
||||||
// Adafruit_GFX...another C++ lib), so the .cpp just passes down some
|
// .cpp just passes down some pointers and minimal info about the canvas
|
||||||
// pointers and minimal info about the canvas buffer.
|
// buffer. It's probably not ideal but this is my life now, oh well.
|
||||||
// It's probably not ideal but this is my life now, oh well.
|
|
||||||
|
|
||||||
// Different runtime environments (which might not use the 565 canvas
|
// Different runtime environments (which might not use the 565 canvas
|
||||||
// format) will need their own conversion functions.
|
// format) will need their own conversion functions.
|
||||||
|
|
@ -846,20 +951,18 @@ void _PM_swapbuffer_maybe(Protomatter_core *core) {
|
||||||
|
|
||||||
// width argument comes from GFX canvas width, which may be less than
|
// width argument comes from GFX canvas width, which may be less than
|
||||||
// core's bitWidth (due to padding). height isn't needed, it can be
|
// core's bitWidth (due to padding). height isn't needed, it can be
|
||||||
// inferred from core->numRowPairs.
|
// inferred from core->numRowPairs and core->tile.
|
||||||
__attribute__((noinline)) void _PM_convert_565_byte(Protomatter_core *core,
|
__attribute__((noinline)) void _PM_convert_565_byte(Protomatter_core *core,
|
||||||
const uint16_t *source,
|
const uint16_t *source,
|
||||||
uint16_t width) {
|
uint16_t width) {
|
||||||
const uint16_t *upperSrc = source; // Canvas top half
|
|
||||||
const uint16_t *lowerSrc =
|
|
||||||
source + width * core->numRowPairs; // " bottom half
|
|
||||||
uint8_t *pinMask = (uint8_t *)core->rgbMask; // Pin bitmasks
|
uint8_t *pinMask = (uint8_t *)core->rgbMask; // Pin bitmasks
|
||||||
uint8_t *dest = (uint8_t *)core->screenData;
|
uint8_t *dest = (uint8_t *)core->screenData;
|
||||||
if (core->doubleBuffer) {
|
if (core->doubleBuffer) {
|
||||||
dest += core->bufferSize * (1 - core->activeBuffer);
|
dest += core->bufferSize * (1 - core->activeBuffer);
|
||||||
}
|
}
|
||||||
|
|
||||||
#if defined(_PM_portToggleRegister)
|
// #if defined(_PM_portToggleRegister)
|
||||||
|
#if defined(_PM_USE_TOGGLE_FORMAT)
|
||||||
#if !defined(_PM_STRICT_32BIT_IO)
|
#if !defined(_PM_STRICT_32BIT_IO)
|
||||||
// core->clockMask mask is already an 8-bit value
|
// core->clockMask mask is already an 8-bit value
|
||||||
uint8_t clockMask = core->clockMask;
|
uint8_t clockMask = core->clockMask;
|
||||||
|
|
@ -875,10 +978,10 @@ __attribute__((noinline)) void _PM_convert_565_byte(Protomatter_core *core,
|
||||||
|
|
||||||
// Determine matrix bytes per bitplane & row (row pair really):
|
// Determine matrix bytes per bitplane & row (row pair really):
|
||||||
|
|
||||||
|
// Size of 1 plane of row pair (across full chain / tile set)
|
||||||
uint32_t bitplaneSize =
|
uint32_t bitplaneSize =
|
||||||
_PM_chunkSize *
|
_PM_chunkSize * ((core->chainBits + (_PM_chunkSize - 1)) / _PM_chunkSize);
|
||||||
((width + (_PM_chunkSize - 1)) / _PM_chunkSize); // 1 plane of row pair
|
uint8_t pad = bitplaneSize - core->chainBits; // Plane-start pad
|
||||||
uint8_t pad = bitplaneSize - width; // Start-of-plane pad
|
|
||||||
|
|
||||||
// Skip initial scanline padding if present (HUB75 matrices shift data
|
// Skip initial scanline padding if present (HUB75 matrices shift data
|
||||||
// in from right-to-left, so if we need scanline padding it occurs at
|
// in from right-to-left, so if we need scanline padding it occurs at
|
||||||
|
|
@ -918,32 +1021,62 @@ __attribute__((noinline)) void _PM_convert_565_byte(Protomatter_core *core,
|
||||||
uint32_t greenBit = initialGreenBit;
|
uint32_t greenBit = initialGreenBit;
|
||||||
uint32_t blueBit = initialBlueBit;
|
uint32_t blueBit = initialBlueBit;
|
||||||
for (uint8_t plane = 0; plane < core->numPlanes; plane++) {
|
for (uint8_t plane = 0; plane < core->numPlanes; plane++) {
|
||||||
#if defined(_PM_portToggleRegister)
|
// #if defined(_PM_portToggleRegister)
|
||||||
|
#if defined(_PM_USE_TOGGLE_FORMAT)
|
||||||
uint8_t prior = clockMask; // Set clock bit on 1st out
|
uint8_t prior = clockMask; // Set clock bit on 1st out
|
||||||
#endif
|
#endif
|
||||||
for (uint16_t x = 0; x < width; x++) {
|
uint8_t *d2 = dest; // Incremented per-pixel across all tiles
|
||||||
uint16_t upperRGB = upperSrc[x]; // Pixel in upper half
|
|
||||||
uint16_t lowerRGB = lowerSrc[x]; // Pixel in lower half
|
// Work from bottom tile to top, because data is issued in that order
|
||||||
uint8_t result = 0;
|
for (int8_t tile = abs(core->tile) - 1; tile >= 0; tile--) {
|
||||||
if (upperRGB & redBit)
|
const uint16_t *upperSrc, *lowerSrc; // Canvas scanline pointers
|
||||||
result |= pinMask[0];
|
int16_t srcIdx;
|
||||||
if (upperRGB & greenBit)
|
int8_t srcInc;
|
||||||
result |= pinMask[1];
|
|
||||||
if (upperRGB & blueBit)
|
// Source pointer to tile's upper-left pixel
|
||||||
result |= pinMask[2];
|
const uint16_t *srcTileUL =
|
||||||
if (lowerRGB & redBit)
|
source + tile * width * core->numRowPairs * 2;
|
||||||
result |= pinMask[3];
|
if ((tile & 1) && (core->tile < 0)) {
|
||||||
if (lowerRGB & greenBit)
|
// Special handling for serpentine tiles
|
||||||
result |= pinMask[4];
|
lowerSrc = srcTileUL + width * (core->numRowPairs - 1 - row);
|
||||||
if (lowerRGB & blueBit)
|
upperSrc = lowerSrc + width * core->numRowPairs;
|
||||||
result |= pinMask[5];
|
srcIdx = width - 1; // Work right to left
|
||||||
#if defined(_PM_portToggleRegister)
|
srcInc = -1;
|
||||||
dest[x] = result ^ prior;
|
} else {
|
||||||
prior = result | clockMask; // Set clock bit on next out
|
// Progressive tile
|
||||||
|
upperSrc = srcTileUL + width * row; // Top row
|
||||||
|
lowerSrc = upperSrc + width * core->numRowPairs; // Bottom row
|
||||||
|
srcIdx = 0; // Left to right
|
||||||
|
srcInc = 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
for (uint16_t x = 0; x < width; x++, srcIdx += srcInc) {
|
||||||
|
uint16_t upperRGB = upperSrc[srcIdx]; // Pixel in upper half
|
||||||
|
uint16_t lowerRGB = lowerSrc[srcIdx]; // Pixel in lower half
|
||||||
|
uint8_t result = 0;
|
||||||
|
if (upperRGB & redBit)
|
||||||
|
result |= pinMask[0];
|
||||||
|
if (upperRGB & greenBit)
|
||||||
|
result |= pinMask[1];
|
||||||
|
if (upperRGB & blueBit)
|
||||||
|
result |= pinMask[2];
|
||||||
|
if (lowerRGB & redBit)
|
||||||
|
result |= pinMask[3];
|
||||||
|
if (lowerRGB & greenBit)
|
||||||
|
result |= pinMask[4];
|
||||||
|
if (lowerRGB & blueBit)
|
||||||
|
result |= pinMask[5];
|
||||||
|
// THIS is where toggle format (without toggle reg.) messes up
|
||||||
|
// #if defined(_PM_portToggleRegister)
|
||||||
|
#if defined(_PM_USE_TOGGLE_FORMAT)
|
||||||
|
*d2++ = result ^ prior;
|
||||||
|
prior = result | clockMask; // Set clock bit on next out
|
||||||
#else
|
#else
|
||||||
dest[x] = result;
|
*d2++ = result;
|
||||||
#endif
|
#endif
|
||||||
} // end x
|
} // end x
|
||||||
|
} // end tile
|
||||||
|
|
||||||
greenBit <<= 1;
|
greenBit <<= 1;
|
||||||
if (plane || (core->numPlanes < 6)) {
|
if (plane || (core->numPlanes < 6)) {
|
||||||
// In most cases red & blue bit scoot 1 left...
|
// In most cases red & blue bit scoot 1 left...
|
||||||
|
|
@ -956,7 +1089,8 @@ __attribute__((noinline)) void _PM_convert_565_byte(Protomatter_core *core,
|
||||||
redBit = 0b0000100000000000;
|
redBit = 0b0000100000000000;
|
||||||
blueBit = 0b0000000000000001;
|
blueBit = 0b0000000000000001;
|
||||||
}
|
}
|
||||||
#if defined(_PM_portToggleRegister)
|
// #if defined(_PM_portToggleRegister)
|
||||||
|
#if defined(_PM_USE_TOGGLE_FORMAT)
|
||||||
// If using bit-toggle register, erase the toggle bit on the
|
// If using bit-toggle register, erase the toggle bit on the
|
||||||
// first element of each bitplane & row pair. The matrix-driving
|
// first element of each bitplane & row pair. The matrix-driving
|
||||||
// interrupt functions correspondingly set the clock low before
|
// interrupt functions correspondingly set the clock low before
|
||||||
|
|
@ -967,9 +1101,7 @@ __attribute__((noinline)) void _PM_convert_565_byte(Protomatter_core *core,
|
||||||
dest[-pad] &= ~clockMask; // Negative index is legal & intentional
|
dest[-pad] &= ~clockMask; // Negative index is legal & intentional
|
||||||
#endif
|
#endif
|
||||||
dest += bitplaneSize; // Advance one scanline in dest buffer
|
dest += bitplaneSize; // Advance one scanline in dest buffer
|
||||||
} // end plane
|
} // end plane
|
||||||
upperSrc += width; // Advance one scanline in source buffer
|
|
||||||
lowerSrc += width;
|
|
||||||
} // end row
|
} // end row
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -977,20 +1109,19 @@ __attribute__((noinline)) void _PM_convert_565_byte(Protomatter_core *core,
|
||||||
// matrix chains), or 1 chain with RGB bits not in the same byte (but in the
|
// matrix chains), or 1 chain with RGB bits not in the same byte (but in the
|
||||||
// same 16-bit word). Some of the comments have been stripped out since it's
|
// same 16-bit word). Some of the comments have been stripped out since it's
|
||||||
// largely the same operation, but changes are noted.
|
// largely the same operation, but changes are noted.
|
||||||
|
// WORD OUTPUT IS UNTESTED AND ROW TILING MAY ESPECIALLY PRESENT ISSUES.
|
||||||
void _PM_convert_565_word(Protomatter_core *core, uint16_t *source,
|
void _PM_convert_565_word(Protomatter_core *core, uint16_t *source,
|
||||||
uint16_t width) {
|
uint16_t width) {
|
||||||
uint16_t *upperSrc = source; // Matrix top half
|
uint16_t *pinMask = (uint16_t *)core->rgbMask; // Pin bitmasks
|
||||||
uint16_t *lowerSrc = source + width * core->numRowPairs; // " bottom half
|
|
||||||
uint16_t *pinMask = (uint16_t *)core->rgbMask; // Pin bitmasks
|
|
||||||
uint16_t *dest = (uint16_t *)core->screenData;
|
uint16_t *dest = (uint16_t *)core->screenData;
|
||||||
if (core->doubleBuffer) {
|
if (core->doubleBuffer) {
|
||||||
dest += core->bufferSize / core->bytesPerElement * (1 - core->activeBuffer);
|
dest += core->bufferSize / core->bytesPerElement * (1 - core->activeBuffer);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Size of 1 plane of row pair (across full chain / tile set)
|
||||||
uint32_t bitplaneSize =
|
uint32_t bitplaneSize =
|
||||||
_PM_chunkSize *
|
_PM_chunkSize * ((core->chainBits + (_PM_chunkSize - 1)) / _PM_chunkSize);
|
||||||
((width + (_PM_chunkSize - 1)) / _PM_chunkSize); // 1 plane of row pair
|
uint8_t pad = bitplaneSize - core->chainBits; // Plane-start pad
|
||||||
uint8_t pad = bitplaneSize - width; // Start-of-plane pad
|
|
||||||
|
|
||||||
uint32_t initialRedBit, initialGreenBit, initialBlueBit;
|
uint32_t initialRedBit, initialGreenBit, initialBlueBit;
|
||||||
if (core->numPlanes == 6) {
|
if (core->numPlanes == 6) {
|
||||||
|
|
@ -1009,7 +1140,8 @@ void _PM_convert_565_word(Protomatter_core *core, uint16_t *source,
|
||||||
// register exists, "clear" really means the clock mask is set in all
|
// register exists, "clear" really means the clock mask is set in all
|
||||||
// but the first element on a scanline (per bitplane). If no toggle
|
// but the first element on a scanline (per bitplane). If no toggle
|
||||||
// register, can just zero everything out.
|
// register, can just zero everything out.
|
||||||
#if defined(_PM_portToggleRegister)
|
// #if defined(_PM_portToggleRegister)
|
||||||
|
#if defined(_PM_USE_TOGGLE_FORMAT)
|
||||||
// No per-chain loop is required; one clock bit handles all chains
|
// No per-chain loop is required; one clock bit handles all chains
|
||||||
uint32_t offset = 0; // Current position in the 'dest' buffer
|
uint32_t offset = 0; // Current position in the 'dest' buffer
|
||||||
uint16_t mask = core->clockMask >> (core->portOffset * 16);
|
uint16_t mask = core->clockMask >> (core->portOffset * 16);
|
||||||
|
|
@ -1027,47 +1159,76 @@ void _PM_convert_565_word(Protomatter_core *core, uint16_t *source,
|
||||||
|
|
||||||
dest += pad; // Pad value is in 'elements,' not bytes, so this is OK
|
dest += pad; // Pad value is in 'elements,' not bytes, so this is OK
|
||||||
|
|
||||||
// After a set of rows+bitplanes are processed, upperSrc and lowerSrc
|
|
||||||
// have advanced halfway down one matrix. This offset is used after
|
|
||||||
// each chain to advance them to the start/middle of the next matrix.
|
|
||||||
uint32_t halfMatrixOffset = width * core->numPlanes * core->numRowPairs;
|
|
||||||
|
|
||||||
for (uint8_t chain = 0; chain < core->parallel; chain++) {
|
for (uint8_t chain = 0; chain < core->parallel; chain++) {
|
||||||
for (uint8_t row = 0; row < core->numRowPairs; row++) {
|
for (uint8_t row = 0; row < core->numRowPairs; row++) {
|
||||||
uint32_t redBit = initialRedBit;
|
uint32_t redBit = initialRedBit;
|
||||||
uint32_t greenBit = initialGreenBit;
|
uint32_t greenBit = initialGreenBit;
|
||||||
uint32_t blueBit = initialBlueBit;
|
uint32_t blueBit = initialBlueBit;
|
||||||
for (uint8_t plane = 0; plane < core->numPlanes; plane++) {
|
for (uint8_t plane = 0; plane < core->numPlanes; plane++) {
|
||||||
#if defined(_PM_portToggleRegister)
|
// #if defined(_PM_portToggleRegister)
|
||||||
|
#if defined(_PM_USE_TOGGLE_FORMAT)
|
||||||
// Since we're ORing in bits over an existing clock bit,
|
// Since we're ORing in bits over an existing clock bit,
|
||||||
// prior is 0 rather than clockMask as in the byte case.
|
// prior is 0 rather than clockMask as in the byte case.
|
||||||
uint16_t prior = 0;
|
uint16_t prior = 0;
|
||||||
#endif
|
#endif
|
||||||
for (uint16_t x = 0; x < width; x++) {
|
uint16_t *d2 = dest; // Incremented per-pixel across all tiles
|
||||||
uint16_t upperRGB = upperSrc[x]; // Pixel in upper half
|
|
||||||
uint16_t lowerRGB = lowerSrc[x]; // Pixel in lower half
|
// Work from bottom tile to top, because data is issued in that order
|
||||||
uint16_t result = 0;
|
for (int8_t tile = abs(core->tile) - 1; tile >= 0; tile--) {
|
||||||
if (upperRGB & redBit)
|
uint16_t *upperSrc, *lowerSrc; // Canvas scanline pointers
|
||||||
result |= pinMask[0];
|
int16_t srcIdx;
|
||||||
if (upperRGB & greenBit)
|
int8_t srcInc;
|
||||||
result |= pinMask[1];
|
|
||||||
if (upperRGB & blueBit)
|
// Source pointer to tile's upper-left pixel
|
||||||
result |= pinMask[2];
|
uint16_t *srcTileUL = source + (chain * abs(core->tile) + tile) *
|
||||||
if (lowerRGB & redBit)
|
width * core->numRowPairs * 2;
|
||||||
result |= pinMask[3];
|
if ((tile & 1) && (core->tile < 0)) {
|
||||||
if (lowerRGB & greenBit)
|
// Special handling for serpentine tiles
|
||||||
result |= pinMask[4];
|
lowerSrc = srcTileUL + width * (core->numRowPairs - 1 - row);
|
||||||
if (lowerRGB & blueBit)
|
upperSrc = lowerSrc + width * core->numRowPairs;
|
||||||
result |= pinMask[5];
|
srcIdx = width - 1; // Work right to left
|
||||||
|
srcInc = -1;
|
||||||
|
} else {
|
||||||
|
// Progressive tile
|
||||||
|
upperSrc = srcTileUL + width * row; // Top row
|
||||||
|
lowerSrc = upperSrc + width * core->numRowPairs; // Bottom row
|
||||||
|
srcIdx = 0; // Left to right
|
||||||
|
srcInc = 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
for (uint16_t x = 0; x < width; x++, srcIdx += srcInc) {
|
||||||
|
uint16_t upperRGB = upperSrc[srcIdx]; // Pixel in upper half
|
||||||
|
uint16_t lowerRGB = lowerSrc[srcIdx]; // Pixel in lower half
|
||||||
|
uint16_t result = 0;
|
||||||
|
if (upperRGB & redBit) {
|
||||||
|
result |= pinMask[0];
|
||||||
|
}
|
||||||
|
if (upperRGB & greenBit) {
|
||||||
|
result |= pinMask[1];
|
||||||
|
}
|
||||||
|
if (upperRGB & blueBit) {
|
||||||
|
result |= pinMask[2];
|
||||||
|
}
|
||||||
|
if (lowerRGB & redBit) {
|
||||||
|
result |= pinMask[3];
|
||||||
|
}
|
||||||
|
if (lowerRGB & greenBit) {
|
||||||
|
result |= pinMask[4];
|
||||||
|
}
|
||||||
|
if (lowerRGB & blueBit) {
|
||||||
|
result |= pinMask[5];
|
||||||
|
}
|
||||||
// Main difference here vs byte converter is each chain
|
// Main difference here vs byte converter is each chain
|
||||||
// ORs new bits into place (vs single-pass overwrite).
|
// ORs new bits into place (vs single-pass overwrite).
|
||||||
#if defined(_PM_portToggleRegister)
|
// #if defined(_PM_portToggleRegister)
|
||||||
dest[x] |= result ^ prior; // Bitwise OR
|
#if defined(_PM_USE_TOGGLE_FORMAT)
|
||||||
prior = result;
|
*d2++ |= result ^ prior; // Bitwise OR
|
||||||
|
prior = result;
|
||||||
#else
|
#else
|
||||||
dest[x] |= result; // Bitwise OR
|
*d2++ |= result; // Bitwise OR
|
||||||
#endif
|
#endif
|
||||||
} // end x
|
} // end x
|
||||||
|
} // end tile
|
||||||
greenBit <<= 1;
|
greenBit <<= 1;
|
||||||
if (plane || (core->numPlanes < 6)) {
|
if (plane || (core->numPlanes < 6)) {
|
||||||
redBit <<= 1;
|
redBit <<= 1;
|
||||||
|
|
@ -1077,33 +1238,28 @@ void _PM_convert_565_word(Protomatter_core *core, uint16_t *source,
|
||||||
blueBit = 0b0000000000000001;
|
blueBit = 0b0000000000000001;
|
||||||
}
|
}
|
||||||
dest += bitplaneSize; // Advance one scanline in dest buffer
|
dest += bitplaneSize; // Advance one scanline in dest buffer
|
||||||
} // end plane
|
} // end plane
|
||||||
upperSrc += width; // Advance one scanline in source buffer
|
} // end row
|
||||||
lowerSrc += width;
|
pinMask += 6; // Next chain's RGB pin masks
|
||||||
} // end row
|
|
||||||
pinMask += 6; // Next chain's RGB pin masks
|
|
||||||
upperSrc += halfMatrixOffset; // Advance to next matrix start pos
|
|
||||||
lowerSrc += halfMatrixOffset;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Corresponding function for long output -- either several parallel chains
|
// Corresponding function for long output -- either several parallel chains
|
||||||
// (up to 5), or 1 chain with RGB bits scattered widely about the PORT.
|
// (up to 5), or 1 chain with RGB bits scattered widely about the PORT.
|
||||||
// Same deal, comments are pared back, see above functions for explanations.
|
// Same deal, comments are pared back, see above functions for explanations.
|
||||||
|
// LONG OUTPUT IS UNTESTED AND ROW TILING MAY ESPECIALLY PRESENT ISSUES.
|
||||||
void _PM_convert_565_long(Protomatter_core *core, uint16_t *source,
|
void _PM_convert_565_long(Protomatter_core *core, uint16_t *source,
|
||||||
uint16_t width) {
|
uint16_t width) {
|
||||||
uint16_t *upperSrc = source; // Matrix top half
|
uint32_t *pinMask = (uint32_t *)core->rgbMask; // Pin bitmasks
|
||||||
uint16_t *lowerSrc = source + width * core->numRowPairs; // " bottom half
|
|
||||||
uint32_t *pinMask = (uint32_t *)core->rgbMask; // Pin bitmasks
|
|
||||||
uint32_t *dest = (uint32_t *)core->screenData;
|
uint32_t *dest = (uint32_t *)core->screenData;
|
||||||
if (core->doubleBuffer) {
|
if (core->doubleBuffer) {
|
||||||
dest += core->bufferSize / core->bytesPerElement * (1 - core->activeBuffer);
|
dest += core->bufferSize / core->bytesPerElement * (1 - core->activeBuffer);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Size of 1 plane of row pair (across full chain / tile set)
|
||||||
uint32_t bitplaneSize =
|
uint32_t bitplaneSize =
|
||||||
_PM_chunkSize *
|
_PM_chunkSize * ((core->chainBits + (_PM_chunkSize - 1)) / _PM_chunkSize);
|
||||||
((width + (_PM_chunkSize - 1)) / _PM_chunkSize); // 1 plane of row pair
|
uint8_t pad = bitplaneSize - core->chainBits; // Plane-start pad
|
||||||
uint8_t pad = bitplaneSize - width; // Start-of-plane pad
|
|
||||||
|
|
||||||
uint32_t initialRedBit, initialGreenBit, initialBlueBit;
|
uint32_t initialRedBit, initialGreenBit, initialBlueBit;
|
||||||
if (core->numPlanes == 6) {
|
if (core->numPlanes == 6) {
|
||||||
|
|
@ -1117,7 +1273,8 @@ void _PM_convert_565_long(Protomatter_core *core, uint16_t *source,
|
||||||
initialBlueBit = 0b0000000000000001 << shiftLeft;
|
initialBlueBit = 0b0000000000000001 << shiftLeft;
|
||||||
}
|
}
|
||||||
|
|
||||||
#if defined(_PM_portToggleRegister)
|
// #if defined(_PM_portToggleRegister)
|
||||||
|
#if defined(_PM_USE_TOGGLE_FORMAT)
|
||||||
// No per-chain loop is required; one clock bit handles all chains
|
// No per-chain loop is required; one clock bit handles all chains
|
||||||
uint32_t offset = 0; // Current position in the 'dest' buffer
|
uint32_t offset = 0; // Current position in the 'dest' buffer
|
||||||
for (uint8_t row = 0; row < core->numRowPairs; row++) {
|
for (uint8_t row = 0; row < core->numRowPairs; row++) {
|
||||||
|
|
@ -1134,42 +1291,74 @@ void _PM_convert_565_long(Protomatter_core *core, uint16_t *source,
|
||||||
|
|
||||||
dest += pad; // Pad value is in 'elements,' not bytes, so this is OK
|
dest += pad; // Pad value is in 'elements,' not bytes, so this is OK
|
||||||
|
|
||||||
uint32_t halfMatrixOffset = width * core->numPlanes * core->numRowPairs;
|
|
||||||
|
|
||||||
for (uint8_t chain = 0; chain < core->parallel; chain++) {
|
for (uint8_t chain = 0; chain < core->parallel; chain++) {
|
||||||
for (uint8_t row = 0; row < core->numRowPairs; row++) {
|
for (uint8_t row = 0; row < core->numRowPairs; row++) {
|
||||||
uint32_t redBit = initialRedBit;
|
uint32_t redBit = initialRedBit;
|
||||||
uint32_t greenBit = initialGreenBit;
|
uint32_t greenBit = initialGreenBit;
|
||||||
uint32_t blueBit = initialBlueBit;
|
uint32_t blueBit = initialBlueBit;
|
||||||
for (uint8_t plane = 0; plane < core->numPlanes; plane++) {
|
for (uint8_t plane = 0; plane < core->numPlanes; plane++) {
|
||||||
#if defined(_PM_portToggleRegister)
|
// #if defined(_PM_portToggleRegister)
|
||||||
|
#if defined(_PM_USE_TOGGLE_FORMAT)
|
||||||
uint32_t prior = 0;
|
uint32_t prior = 0;
|
||||||
#endif
|
#endif
|
||||||
for (uint16_t x = 0; x < width; x++) {
|
uint32_t *d2 = dest; // Incremented per-pixel across all tiles
|
||||||
uint16_t upperRGB = upperSrc[x]; // Pixel in upper half
|
|
||||||
uint16_t lowerRGB = lowerSrc[x]; // Pixel in lower half
|
// Work from bottom tile to top, because data is issued in that order
|
||||||
uint32_t result = 0;
|
for (int8_t tile = abs(core->tile) - 1; tile >= 0; tile--) {
|
||||||
if (upperRGB & redBit)
|
uint16_t *upperSrc, *lowerSrc; // Canvas scanline pointers
|
||||||
result |= pinMask[0];
|
int16_t srcIdx;
|
||||||
if (upperRGB & greenBit)
|
int8_t srcInc;
|
||||||
result |= pinMask[1];
|
|
||||||
if (upperRGB & blueBit)
|
// Source pointer to tile's upper-left pixel
|
||||||
result |= pinMask[2];
|
uint16_t *srcTileUL = source + (chain * abs(core->tile) + tile) *
|
||||||
if (lowerRGB & redBit)
|
width * core->numRowPairs * 2;
|
||||||
result |= pinMask[3];
|
if ((tile & 1) && (core->tile < 0)) {
|
||||||
if (lowerRGB & greenBit)
|
// Special handling for serpentine tiles
|
||||||
result |= pinMask[4];
|
lowerSrc = srcTileUL + width * (core->numRowPairs - 1 - row);
|
||||||
if (lowerRGB & blueBit)
|
upperSrc = lowerSrc + width * core->numRowPairs;
|
||||||
result |= pinMask[5];
|
srcIdx = width - 1; // Work right to left
|
||||||
|
srcInc = -1;
|
||||||
|
} else {
|
||||||
|
// Progressive tile
|
||||||
|
upperSrc = srcTileUL + width * row; // Top row
|
||||||
|
lowerSrc = upperSrc + width * core->numRowPairs; // Bottom row
|
||||||
|
srcIdx = 0; // Left to right
|
||||||
|
srcInc = 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
for (uint16_t x = 0; x < width; x++, srcIdx += srcInc) {
|
||||||
|
uint16_t upperRGB = upperSrc[srcIdx]; // Pixel in upper half
|
||||||
|
uint16_t lowerRGB = lowerSrc[srcIdx]; // Pixel in lower half
|
||||||
|
uint32_t result = 0;
|
||||||
|
if (upperRGB & redBit) {
|
||||||
|
result |= pinMask[0];
|
||||||
|
}
|
||||||
|
if (upperRGB & greenBit) {
|
||||||
|
result |= pinMask[1];
|
||||||
|
}
|
||||||
|
if (upperRGB & blueBit) {
|
||||||
|
result |= pinMask[2];
|
||||||
|
}
|
||||||
|
if (lowerRGB & redBit) {
|
||||||
|
result |= pinMask[3];
|
||||||
|
}
|
||||||
|
if (lowerRGB & greenBit) {
|
||||||
|
result |= pinMask[4];
|
||||||
|
}
|
||||||
|
if (lowerRGB & blueBit) {
|
||||||
|
result |= pinMask[5];
|
||||||
|
}
|
||||||
// Main difference here vs byte converter is each chain
|
// Main difference here vs byte converter is each chain
|
||||||
// ORs new bits into place (vs single-pass overwrite).
|
// ORs new bits into place (vs single-pass overwrite).
|
||||||
#if defined(_PM_portToggleRegister)
|
// #if defined(_PM_portToggleRegister)
|
||||||
dest[x] |= result ^ prior; // Bitwise OR
|
#if defined(_PM_USE_TOGGLE_FORMAT)
|
||||||
prior = result;
|
*d2++ |= result ^ prior; // Bitwise OR
|
||||||
|
prior = result;
|
||||||
#else
|
#else
|
||||||
dest[x] |= result; // Bitwise OR
|
*d2++ |= result; // Bitwise OR
|
||||||
#endif
|
#endif
|
||||||
} // end x
|
} // end x
|
||||||
|
} // end tile
|
||||||
greenBit <<= 1;
|
greenBit <<= 1;
|
||||||
if (plane || (core->numPlanes < 6)) {
|
if (plane || (core->numPlanes < 6)) {
|
||||||
redBit <<= 1;
|
redBit <<= 1;
|
||||||
|
|
@ -1179,13 +1368,9 @@ void _PM_convert_565_long(Protomatter_core *core, uint16_t *source,
|
||||||
blueBit = 0b0000000000000001;
|
blueBit = 0b0000000000000001;
|
||||||
}
|
}
|
||||||
dest += bitplaneSize; // Advance one scanline in dest buffer
|
dest += bitplaneSize; // Advance one scanline in dest buffer
|
||||||
} // end plane
|
} // end plane
|
||||||
upperSrc += width; // Advance one scanline in source buffer
|
} // end row
|
||||||
lowerSrc += width;
|
pinMask += 6; // Next chain's RGB pin masks
|
||||||
} // end row
|
|
||||||
pinMask += 6; // Next chain's RGB pin masks
|
|
||||||
upperSrc += halfMatrixOffset; // Advance to next matrix start pos
|
|
||||||
lowerSrc += halfMatrixOffset;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -1205,18 +1390,22 @@ void _PM_convert_565(Protomatter_core *core, uint16_t *source, uint16_t width) {
|
||||||
|
|
||||||
#endif // END ARDUINO || CIRCUITPY
|
#endif // END ARDUINO || CIRCUITPY
|
||||||
|
|
||||||
// Note to future self: I've gone back and forth between implementing all
|
/* NOTES TO FUTURE SELF ----------------------------------------------------
|
||||||
// this either as it currently is (with byte, word and long cases for various
|
|
||||||
// steps), or using a uint32_t[64] table for expanding RGB bit combos to PORT
|
ON BYTES, WORDS and LONGS:
|
||||||
// bit combos. The latter would certainly simplify the code a ton, and the
|
I've gone back and forth between implementing all this either as it
|
||||||
// additional table lookup step wouldn't significantly impact performance,
|
currently is (with byte, word and long cases for various steps), or using
|
||||||
// especially going forward with faster processors (the SAMD51 code already
|
a uint32_t[64] table for expanding RGB bit combos to PORT bit combos.
|
||||||
// requires a few NOPs in the innermost loop to avoid outpacing the matrix).
|
The latter would certainly simplify the code a ton, and the additional
|
||||||
// BUT, the reason this is NOT currently done is that it only allows for a
|
table lookup step wouldn't significantly impact performance, especially
|
||||||
// single matrix chain (doing parallel chains would require either an
|
going forward with faster processors (several devices already require a
|
||||||
// impractically large lookup table, or adding together multiple tables'
|
few NOPs in the innermost loop to avoid outpacing the matrix).
|
||||||
// worth of bitmasks, which would slow things down in the vital inner loop).
|
BUT, the reason this is NOT currently done is that it only allows for a
|
||||||
// Although parallel matrix chains aren't yet 100% implemented in this code
|
single matrix chain (doing parallel chains would require either an
|
||||||
// right now, I wanted to leave that possibility for the future, as a way to
|
impractically large lookup table, or adding together multiple tables'
|
||||||
// handle larger matrix combos, because long chains will slow down the
|
worth of bitmasks, which would slow things down in the vital inner loop).
|
||||||
// refresh rate.
|
Although parallel matrix chains aren't yet 100% implemented in this code
|
||||||
|
right now, I wanted to leave that possibility for the future, as a way to
|
||||||
|
handle larger matrix combos, because long chains will slow down the
|
||||||
|
refresh rate.
|
||||||
|
*/
|
||||||
|
|
|
||||||
50
src/core.h
50
src/core.h
|
|
@ -72,7 +72,8 @@ typedef struct {
|
||||||
uint32_t bitZeroPeriod; ///< Bitplane 0 timer period
|
uint32_t bitZeroPeriod; ///< Bitplane 0 timer period
|
||||||
uint32_t minPeriod; ///< Plane 0 timer period for ~250Hz
|
uint32_t minPeriod; ///< Plane 0 timer period for ~250Hz
|
||||||
volatile uint32_t frameCount; ///< For estimating refresh rate
|
volatile uint32_t frameCount; ///< For estimating refresh rate
|
||||||
uint16_t width; ///< Matrix chain width in bits
|
uint16_t width; ///< Matrix chain width only in bits
|
||||||
|
uint16_t chainBits; ///< Matrix chain width*tiling in bits
|
||||||
uint8_t bytesPerElement; ///< Using 8, 16 or 32 bits of PORT?
|
uint8_t bytesPerElement; ///< Using 8, 16 or 32 bits of PORT?
|
||||||
uint8_t clockPin; ///< RGB clock pin identifier
|
uint8_t clockPin; ///< RGB clock pin identifier
|
||||||
uint8_t parallel; ///< Number of concurrent matrix outs
|
uint8_t parallel; ///< Number of concurrent matrix outs
|
||||||
|
|
@ -80,6 +81,7 @@ typedef struct {
|
||||||
uint8_t portOffset; ///< Active 8- or 16-bit pos. in PORT
|
uint8_t portOffset; ///< Active 8- or 16-bit pos. in PORT
|
||||||
uint8_t numPlanes; ///< Display bitplanes (1 to 6)
|
uint8_t numPlanes; ///< Display bitplanes (1 to 6)
|
||||||
uint8_t numRowPairs; ///< Addressable row pairs
|
uint8_t numRowPairs; ///< Addressable row pairs
|
||||||
|
int8_t tile; ///< Vertical tiling repetitions
|
||||||
bool doubleBuffer; ///< 2X buffers for clean switchover
|
bool doubleBuffer; ///< 2X buffers for clean switchover
|
||||||
bool singleAddrPort; ///< If 1, all addr lines on same PORT
|
bool singleAddrPort; ///< If 1, all addr lines on same PORT
|
||||||
volatile uint8_t activeBuffer; ///< Index of currently-displayed buf
|
volatile uint8_t activeBuffer; ///< Index of currently-displayed buf
|
||||||
|
|
@ -135,6 +137,14 @@ typedef struct {
|
||||||
@param doubleBuffer If true, two matrix buffers are allocated,
|
@param doubleBuffer If true, two matrix buffers are allocated,
|
||||||
so changing display contents doesn't introduce
|
so changing display contents doesn't introduce
|
||||||
artifacts mid-conversion. Requires ~2X RAM.
|
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
|
@param timer Pointer to timer peripheral or timer-related
|
||||||
struct (architecture-dependent), or NULL to
|
struct (architecture-dependent), or NULL to
|
||||||
use a default timer ID (also arch-dependent).
|
use a default timer ID (also arch-dependent).
|
||||||
|
|
@ -152,7 +162,7 @@ extern ProtomatterStatus _PM_init(Protomatter_core *core, uint16_t bitWidth,
|
||||||
uint8_t *rgbList, uint8_t addrCount,
|
uint8_t *rgbList, uint8_t addrCount,
|
||||||
uint8_t *addrList, uint8_t clockPin,
|
uint8_t *addrList, uint8_t clockPin,
|
||||||
uint8_t latchPin, uint8_t oePin,
|
uint8_t latchPin, uint8_t oePin,
|
||||||
bool doubleBuffer, void *timer);
|
bool doubleBuffer, int8_t tile, void *timer);
|
||||||
|
|
||||||
/*!
|
/*!
|
||||||
@brief Allocate display buffers and populate additional elements of a
|
@brief Allocate display buffers and populate additional elements of a
|
||||||
|
|
@ -198,6 +208,11 @@ extern void _PM_deallocate(Protomatter_core *core);
|
||||||
*/
|
*/
|
||||||
extern void _PM_row_handler(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 Returns current value of frame counter and resets its value to
|
@brief Returns current value of frame counter and resets its value to
|
||||||
zero. Two calls to this, timed one second apart (or use math with
|
zero. Two calls to this, timed one second apart (or use math with
|
||||||
|
|
@ -211,30 +226,27 @@ extern uint32_t _PM_getFrameCount(Protomatter_core *core);
|
||||||
|
|
||||||
/*!
|
/*!
|
||||||
@brief Start (or restart) a timer/counter peripheral.
|
@brief Start (or restart) a timer/counter peripheral.
|
||||||
@param tptr Pointer to timer/counter peripheral OR a struct
|
@param core Pointer to Protomatter core structure, from which timer
|
||||||
encapsulating information about a timer/counter
|
details can be derived.
|
||||||
periph (architecture-dependent).
|
|
||||||
@param period Timer 'top' / rollover value.
|
@param period Timer 'top' / rollover value.
|
||||||
*/
|
*/
|
||||||
extern void _PM_timerStart(void *tptr, uint32_t period);
|
extern void _PM_timerStart(Protomatter_core *core, uint32_t period);
|
||||||
|
|
||||||
/*!
|
/*!
|
||||||
@brief Stop timer/counter peripheral.
|
@brief Stop timer/counter peripheral.
|
||||||
@param tptr Pointer to timer/counter peripheral OR a struct
|
@param core Pointer to Protomatter core structure, from which timer
|
||||||
encapsulating information about a timer/counter
|
details can be derived.
|
||||||
periph (architecture-dependent).
|
|
||||||
@return Counter value when timer was stopped.
|
@return Counter value when timer was stopped.
|
||||||
*/
|
*/
|
||||||
extern uint32_t _PM_timerStop(void *tptr);
|
extern uint32_t _PM_timerStop(Protomatter_core *core);
|
||||||
|
|
||||||
/*!
|
/*!
|
||||||
@brief Query a timer/counter peripheral's current count.
|
@brief Query a timer/counter peripheral's current count.
|
||||||
@param tptr Pointer to timer/counter peripheral OR a struct
|
@param core Pointer to Protomatter core structure, from which timer
|
||||||
encapsulating information about a timer/counter
|
details can be derived.
|
||||||
periph (architecture-dependent).
|
|
||||||
@return Counter value.
|
@return Counter value.
|
||||||
*/
|
*/
|
||||||
extern uint32_t _PM_timerGetCount(void *tptr);
|
extern uint32_t _PM_timerGetCount(Protomatter_core *core);
|
||||||
|
|
||||||
/*!
|
/*!
|
||||||
@brief Pauses until the next vertical blank to avoid 'tearing' animation
|
@brief Pauses until the next vertical blank to avoid 'tearing' animation
|
||||||
|
|
@ -243,6 +255,16 @@ extern uint32_t _PM_timerGetCount(void *tptr);
|
||||||
*/
|
*/
|
||||||
extern void _PM_swapbuffer_maybe(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)
|
#if defined(ARDUINO) || defined(CIRCUITPY)
|
||||||
|
|
||||||
/*!
|
/*!
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue