Compare commits
10 commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
32407dd631 | ||
|
|
316af7345d | ||
|
|
a53a33ef1a | ||
|
|
4869e64850 | ||
|
|
1615dc4c1d | ||
|
|
6f312c642f | ||
|
|
c9ecf7c735 | ||
|
|
0a75527d30 | ||
|
|
a413580aa2 | ||
|
|
da1c4fcc68 |
19 changed files with 929 additions and 605 deletions
|
|
@ -1,8 +1,9 @@
|
|||
// Play GIFs from CIRCUITPY drive (USB-accessible filesystem) to LED matrix.
|
||||
// Designed for Adafruit MatrixPortal M4, but may run on some other M4 & M0
|
||||
// and nRF52 boards (relies on TinyUSB stack). As written, runs on 64x32 pixel
|
||||
// matrix, this can be changed by editing the addrPins[] array (height) and/or
|
||||
// matrix constructor call (width).
|
||||
// ***DESIGNED FOR ADAFRUIT MATRIXPORTAL M4***, but may run on some other
|
||||
// M4 & M0 and nRF52 boards (relies on TinyUSB stack). As written, runs on
|
||||
// 64x32 pixel matrix, this can be changed by editing the addrPins[] array
|
||||
// (height) and/or matrix constructor call (width). See the "simple" example
|
||||
// for a run-down on matrix configuration.
|
||||
// Adapted from examples from Larry Bank's AnimatedGIF library and
|
||||
// msc_external_flash example in Adafruit_TinyUSB_Arduino.
|
||||
// Prerequisite libraries:
|
||||
|
|
|
|||
|
|
@ -1,173 +1,164 @@
|
|||
#include "Adafruit_Protomatter.h"
|
||||
/* ----------------------------------------------------------------------
|
||||
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.
|
||||
|
||||
/*
|
||||
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
|
||||
This example is written for a 64x32 matrix but can be adapted to others.
|
||||
------------------------------------------------------------------------- */
|
||||
|
||||
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
|
||||
#include <Adafruit_Protomatter.h>
|
||||
#include <Fonts/FreeSansBold18pt7b.h> // Large friendly font
|
||||
|
||||
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
|
||||
/* ----------------------------------------------------------------------
|
||||
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.
|
||||
------------------------------------------------------------------------- */
|
||||
|
||||
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
|
||||
#if defined(_VARIANT_MATRIXPORTAL_M4_) // MatrixPortal M4
|
||||
uint8_t rgbPins[] = {7, 8, 9, 10, 11, 12};
|
||||
uint8_t addrPins[] = {17, 18, 19, 20};
|
||||
uint8_t clockPin = 14;
|
||||
uint8_t latchPin = 15;
|
||||
uint8_t oePin = 16;
|
||||
#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(_SAMD21_)
|
||||
#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
|
||||
#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):
|
||||
// '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)
|
||||
// 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 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 (yes that's a 38, NOT 28!)
|
||||
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;
|
||||
#endif
|
||||
|
||||
// Last arg here enables double-buffering
|
||||
Adafruit_Protomatter matrix(
|
||||
64, 6, 1, rgbPins, 4, addrPins, clockPin, latchPin, oePin, true);
|
||||
/* ----------------------------------------------------------------------
|
||||
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.
|
||||
------------------------------------------------------------------------- */
|
||||
|
||||
int16_t textMin,
|
||||
textX = matrix.width(),
|
||||
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 MAGIG FOR DOUBLE-BUFFERING!
|
||||
|
||||
// Sundry globals used for animation ---------------------------------------
|
||||
|
||||
int16_t textX = matrix.width(), // Current text position (X)
|
||||
textY, // Current text position (Y)
|
||||
textMin, // Text pos. (X) when scrolled off left edge
|
||||
hue = 0;
|
||||
char str[40];
|
||||
char str[50]; // Buffer to hold scrolling message text
|
||||
int8_t ball[3][4] = {
|
||||
{ 3, 0, 1, 1 }, // Initial X,Y pos & velocity for 3 bouncy balls
|
||||
{ 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())
|
||||
|
||||
const uint16_t ballcolor[3] = {
|
||||
0b0000000001000000, // Dark green
|
||||
0b0000000000000001, // Dark blue
|
||||
0b0000100000000000 // Dark red
|
||||
};
|
||||
// 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());
|
||||
textMin = strlen(str) * -12;
|
||||
matrix.setTextWrap(false);
|
||||
matrix.setTextSize(2);
|
||||
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
|
||||
textY = matrix.height() / 2 - (y1 + h / 2); // Center text vertically
|
||||
|
||||
// Set up the colors of the bouncy balls.
|
||||
ballcolor[0] = color565(0, 20, 0); // Dark green
|
||||
ballcolor[1] = color565(0, 0, 20); // Dark blue
|
||||
ballcolor[2] = color565(20, 0, 0); // ark red
|
||||
}
|
||||
|
||||
// LOOP - RUNS REPEATEDLY AFTER SETUP --------------------------------------
|
||||
|
||||
void loop(void) {
|
||||
byte i;
|
||||
// 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.
|
||||
|
||||
// Clear background
|
||||
matrix.fillScreen(0);
|
||||
matrix.fillScreen(0); // Fill background black
|
||||
|
||||
// Bounce three balls around
|
||||
for(i=0; i<3; i++) {
|
||||
// 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 X, Y position
|
||||
// Update ball's X,Y position for next frame
|
||||
ball[i][0] += ball[i][2];
|
||||
ball[i][1] += ball[i][3];
|
||||
// Bounce off edges
|
||||
|
|
@ -177,16 +168,19 @@ void loop(void) {
|
|||
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;
|
||||
// AFTER DRAWING, A show() CALL IS REQUIRED TO UPDATE THE MATRIX!
|
||||
|
||||
matrix.show();
|
||||
|
||||
delay(20);
|
||||
delay(20); // 20 milliseconds = ~50 frames/second
|
||||
}
|
||||
|
||||
// Utility function converts 24-bit color (8 bits red, green, blue) used in
|
||||
// a lot of existing graphics code down to the "565" color format used by
|
||||
// Adafruit_GFX. Might get further quantized by matrix if using less than
|
||||
// 6-bit depth.
|
||||
uint16_t color565(uint8_t red, uint8_t green, uint8_t blue) {
|
||||
return ((red & 0b11111000) << 8) |
|
||||
((green & 0b11111100) << 3) |
|
||||
( blue >> 3);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,7 +1,21 @@
|
|||
/* ----------------------------------------------------------------------
|
||||
"Pixel dust" Protomatter library example. As written, this is
|
||||
SPECIFICALLY FOR THE ADAFRUIT MATRIXPORTAL M4 with 64x32 pixel matrix.
|
||||
(see "pixeldust_64x64" example for a 64x64 matrix), but could be adapted
|
||||
to other Protomatter-capable boards with an attached LIS3DH accelerometer.
|
||||
|
||||
PLEASE SEE THE "simple" EXAMPLE FOR AN INTRODUCTORY SKETCH,
|
||||
or "doublebuffer" for animation basics.
|
||||
------------------------------------------------------------------------- */
|
||||
|
||||
#include <Wire.h> // For I2C communication
|
||||
#include <Adafruit_LIS3DH.h>
|
||||
#include <Adafruit_PixelDust.h> // For simulation
|
||||
#include "Adafruit_Protomatter.h"
|
||||
#include <Adafruit_Protomatter.h>
|
||||
|
||||
#define WIDTH 64 // Display width in pixels
|
||||
#define HEIGHT 32 // Display height in pixels
|
||||
#define MAX_FPS 45 // Maximum redraw rate, frames/second
|
||||
|
||||
uint8_t rgbPins[] = {7, 8, 9, 10, 11, 12};
|
||||
uint8_t addrPins[] = {17, 18, 19, 20};
|
||||
|
|
@ -12,12 +26,7 @@ uint8_t oePin = 16;
|
|||
Adafruit_LIS3DH accel = Adafruit_LIS3DH();
|
||||
|
||||
Adafruit_Protomatter matrix(
|
||||
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
|
||||
WIDTH, 4, 1, rgbPins, 4, addrPins, clockPin, latchPin, oePin, true);
|
||||
|
||||
#define N_COLORS 8
|
||||
#define BOX_HEIGHT 8
|
||||
|
|
@ -27,7 +36,6 @@ 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 --------------------------------------
|
||||
|
|
@ -89,6 +97,7 @@ void setup(void) {
|
|||
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() {
|
||||
|
|
@ -126,6 +135,4 @@ void loop() {
|
|||
//Serial.printf("(%d, %d)\n", x, y);
|
||||
}
|
||||
matrix.show(); // Copy data to matrix buffers
|
||||
|
||||
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,7 +1,21 @@
|
|||
/* ----------------------------------------------------------------------
|
||||
"Pixel dust" Protomatter library example. As written, this is
|
||||
SPECIFICALLY FOR THE ADAFRUIT MATRIXPORTAL M4 with 64x32 pixel matrix.
|
||||
(see "pixeldust_64x64" example for a 64x64 matrix), but could be adapted
|
||||
to other Protomatter-capable boards with an attached LIS3DH accelerometer.
|
||||
|
||||
PLEASE SEE THE "simple" EXAMPLE FOR AN INTRODUCTORY SKETCH,
|
||||
or "doublebuffer" for animation basics.
|
||||
------------------------------------------------------------------------- */
|
||||
|
||||
#include <Wire.h> // For I2C communication
|
||||
#include <Adafruit_LIS3DH.h>
|
||||
#include <Adafruit_PixelDust.h> // For simulation
|
||||
#include "Adafruit_Protomatter.h"
|
||||
#include <Adafruit_Protomatter.h>
|
||||
|
||||
#define WIDTH 64 // Display width in pixels
|
||||
#define HEIGHT 64 // Display height in pixels
|
||||
#define MAX_FPS 45 // Maximum redraw rate, frames/second
|
||||
|
||||
uint8_t rgbPins[] = {7, 8, 9, 10, 11, 12};
|
||||
uint8_t addrPins[] = {17, 18, 19, 20, 21};
|
||||
|
|
@ -12,12 +26,7 @@ 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
|
||||
WIDTH, 4, 1, rgbPins, 5, addrPins, clockPin, latchPin, oePin, true);
|
||||
|
||||
#define N_COLORS 8
|
||||
#define BOX_HEIGHT 8
|
||||
|
|
@ -27,7 +36,6 @@ 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 --------------------------------------
|
||||
|
|
@ -89,6 +97,7 @@ void setup(void) {
|
|||
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() {
|
||||
|
|
@ -126,6 +135,4 @@ void loop() {
|
|||
//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);
|
||||
}
|
||||
273
examples/simple/simple.ino
Normal file
273
examples/simple/simple.ino
Normal file
|
|
@ -0,0 +1,273 @@
|
|||
/* ----------------------------------------------------------------------
|
||||
"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};
|
||||
uint8_t clockPin = 14;
|
||||
uint8_t latchPin = 15;
|
||||
uint8_t oePin = 16;
|
||||
#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 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;
|
||||
#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, color565(level, 0, 0));
|
||||
matrix.drawPixel(x, matrix.height() - 3, color565(0, level, 0));
|
||||
matrix.drawPixel(x, matrix.height() - 2, color565(0, 0, level));
|
||||
matrix.drawPixel(x, matrix.height() - 1, color565(level, level, level));
|
||||
}
|
||||
// You'll notice the ramp looks smoother as bit depth increases
|
||||
// (second argument to the matrix constructor call above setup()).
|
||||
|
||||
// Simple shapes and text, showing GFX library calls:
|
||||
matrix.drawCircle(12, 10, 9, color565(255, 0, 0)); // Red
|
||||
matrix.drawRect(14, 6, 17, 17, color565(0, 255, 0)); // Green
|
||||
matrix.drawTriangle(32, 9, 41, 27, 23, 27, color565(0, 0, 255)); // Blue
|
||||
matrix.println("ADAFRUIT"); // Default text color is white
|
||||
|
||||
// AFTER DRAWING, A show() CALL IS REQUIRED TO UPDATE THE MATRIX!
|
||||
|
||||
matrix.show(); // Copy data to matrix buffers
|
||||
}
|
||||
|
||||
// Utility function converts 24-bit color (8 bits red, green, blue) used in
|
||||
// a lot of existing graphics code down to the "565" color format used by
|
||||
// Adafruit_GFX. Might get further quantized by matrix if using less than
|
||||
// 6-bit depth.
|
||||
uint16_t color565(uint8_t red, uint8_t green, uint8_t blue) {
|
||||
return ((red & 0b11111000) << 8) |
|
||||
((green & 0b11111100) << 3) |
|
||||
( blue >> 3);
|
||||
}
|
||||
|
||||
// LOOP - RUNS REPEATEDLY AFTER SETUP --------------------------------------
|
||||
|
||||
void loop(void) {
|
||||
// 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)
|
||||
|
||||
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!
|
||||
*/
|
||||
22
notes.txt
Normal file
22
notes.txt
Normal file
|
|
@ -0,0 +1,22 @@
|
|||
Code is now in the 'src' folder.
|
||||
arch.h has been split out into multiple files (one per architecture),
|
||||
in the 'arch' folder. There's still an arch.h file, but it mostly
|
||||
#includes the device-specific headers and then defaults any undefined
|
||||
items at the end.
|
||||
|
||||
A few functions have been renamed:
|
||||
|
||||
_PM_free(Protomatter_core *) is now _PM_deallocate(Protomatter_core *)
|
||||
_PM_setReg(bit) is now _PM_setBit(bit)
|
||||
_PM_clearReg(bit) is now _PM_clearBit(bit)
|
||||
_PM_ALLOCATOR(size) is now _PM_allocate(size)
|
||||
_PM_FREE(addr) is now _PM_free(addr)
|
||||
|
||||
_PM_timerStart() now accepts two tick intervals. First is the overflow
|
||||
interval (when the per-bitplane switchover occurs), as before. Second is
|
||||
when the matrix OE line is de-asserted, if this needs to occur before the
|
||||
overflow interval (e.g. when brightness scaling or for gamma correction).
|
||||
Most architectures will ignore the second value for now because the
|
||||
necessary interrupt changes have not been made yet. Only SAMD51 uses it.
|
||||
If the second interval arg is omitted, the early OE de-assert does not
|
||||
occur, and the normal brief OE de-assert occurs in the bitplane switchover.
|
||||
|
|
@ -50,8 +50,6 @@ Adafruit_Protomatter::Adafruit_Protomatter(uint16_t bitWidth, uint8_t bitDepth,
|
|||
void *timer)
|
||||
: GFXcanvas16(bitWidth,
|
||||
(2 << min((int)addrCount, 5)) * min((int)rgbCount, 5)) {
|
||||
if (bitDepth > 6)
|
||||
bitDepth = 6; // GFXcanvas16 color limit (565)
|
||||
|
||||
// Arguments are passed through to the C _PM_init() function which does
|
||||
// some input validation and minor allocation. Return value is ignored
|
||||
|
|
@ -67,8 +65,9 @@ Adafruit_Protomatter::~Adafruit_Protomatter(void) {
|
|||
_PM_protoPtr = NULL;
|
||||
}
|
||||
|
||||
ProtomatterStatus Adafruit_Protomatter::begin(void) {
|
||||
ProtomatterStatus Adafruit_Protomatter::begin(uint8_t brightness) {
|
||||
_PM_protoPtr = &core;
|
||||
core.brightness = brightness;
|
||||
return _PM_begin(&core);
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -67,6 +67,10 @@ public:
|
|||
/*!
|
||||
@brief Start a Protomatter matrix display running -- initialize
|
||||
pins, timer and interrupt into existence.
|
||||
@param brightness Initial matrix brightness setting,
|
||||
0 = minimum (off), 255 = maximum (default if unset).
|
||||
May not be supported by all architectures.
|
||||
See setBrightness() notes.
|
||||
@return A ProtomatterStatus status, one of:
|
||||
PROTOMATTER_OK if everything is good.
|
||||
PROTOMATTER_ERR_PINS if data and/or clock pins are split
|
||||
|
|
@ -76,7 +80,19 @@ public:
|
|||
PROTOMATTER_ERR_ARG if a bad value was passed to the
|
||||
constructor.
|
||||
*/
|
||||
ProtomatterStatus begin(void);
|
||||
ProtomatterStatus begin(uint8_t brightness = 255);
|
||||
|
||||
/*!
|
||||
@brief Change brightness of an already-running matrix.
|
||||
May not be supported by all architectures.
|
||||
Brightness value is a linear scaling of the duty cycle,
|
||||
NOT perceptual brightness -- a ~50% value here (127) will
|
||||
halve the LEDs' duty cycle, but will likely appear more
|
||||
than half as bright as 100%.
|
||||
@param brightness Brightness setting, 0 = minimum (off),
|
||||
255 = maximum (default if unset).
|
||||
*/
|
||||
void setBrightness(uint8_t brightness = 255) { core.brightness = brightness; }
|
||||
|
||||
/*!
|
||||
@brief Process data from GFXcanvas16 to the matrix framebuffer's
|
||||
|
|
|
|||
10
src/arch/arch.h
Normal file → Executable file
10
src/arch/arch.h
Normal file → Executable file
|
|
@ -83,7 +83,11 @@ Timer-related macros/functions:
|
|||
_PM_timerFreq: A numerical constant - the source clock rate
|
||||
(in Hz) that's fed to the timer peripheral.
|
||||
_PM_timerInit(void*): Initialize (but do not start) timer.
|
||||
_PM_timerStart(void*,count): (Re)start timer for a given timer-tick interval.
|
||||
_PM_timerStart(void*,a,b): (Re)start timer for a given timer-tick interval,
|
||||
'a' is the top/overflow period (when the timer
|
||||
rolls over to zero), 'b' is a second shorter
|
||||
period that can be used to de-assert the OE
|
||||
line early (pass 0xFFFF to ignore).
|
||||
_PM_timerStop(void*): Stop timer, return current timer counter value.
|
||||
_PM_timerGetCount(void*): Get current timer counter value (whether timer
|
||||
is running or stopped).
|
||||
|
|
@ -206,3 +210,7 @@ _PM_free: Corresponding deallocator for _PM_allocate().
|
|||
#if !defined(_PM_PORT_TYPE)
|
||||
#define _PM_PORT_TYPE uint32_t ///< PORT register size/type
|
||||
#endif
|
||||
|
||||
#if !defined(_PM_maxBitplanes)
|
||||
#define _PM_maxBitplanes 6 ///< RGB bit depth handled by architecture
|
||||
#endif
|
||||
|
|
|
|||
5
src/arch/esp32.h
Normal file → Executable file
5
src/arch/esp32.h
Normal file → Executable file
|
|
@ -90,9 +90,10 @@ void _PM_timerInit(void *tptr) {
|
|||
}
|
||||
|
||||
// Set timer period, initialize count value to zero, enable timer.
|
||||
IRAM_ATTR inline void _PM_timerStart(void *tptr, uint32_t period) {
|
||||
IRAM_ATTR inline void _PM_timerStart(void *tptr, uint32_t top,
|
||||
uint32_t match) {
|
||||
hw_timer_t *timer = *(hw_timer_t **)tptr;
|
||||
timerAlarmWrite(timer, period, true);
|
||||
timerAlarmWrite(timer, top, true);
|
||||
timerAlarmEnable(timer);
|
||||
timerStart(timer);
|
||||
}
|
||||
|
|
|
|||
4
src/arch/nrf52.h
Normal file → Executable file
4
src/arch/nrf52.h
Normal file → Executable file
|
|
@ -183,11 +183,11 @@ void _PM_timerInit(void *tptr) {
|
|||
NVIC_EnableIRQ(timer[timerNum].IRQn);
|
||||
}
|
||||
|
||||
inline void _PM_timerStart(void *tptr, uint32_t period) {
|
||||
inline void _PM_timerStart(void *tptr, uint32_t top, uint32_t match) {
|
||||
volatile NRF_TIMER_Type *tc = (volatile NRF_TIMER_Type *)tptr;
|
||||
tc->TASKS_STOP = 1; // Stop timer
|
||||
tc->TASKS_CLEAR = 1; // Reset to 0
|
||||
tc->CC[0] = period;
|
||||
tc->CC[0] = top;
|
||||
tc->TASKS_START = 1; // Start timer
|
||||
}
|
||||
|
||||
|
|
|
|||
75
src/arch/samd-common.h
Normal file → Executable file
75
src/arch/samd-common.h
Normal file → Executable file
|
|
@ -31,7 +31,10 @@
|
|||
#define _PM_wordOffset(pin) (1 - (g_APinDescription[pin].ulPin / 16))
|
||||
#endif
|
||||
|
||||
// Arduino implementation is tied to a specific timer/counter & freq:
|
||||
// Arduino implementation is tied to a specific timer/counter & freq.
|
||||
// Partly because IRQs must be declared at compile-time, and partly
|
||||
// because we know Arduino's already set up one of the GCLK sources
|
||||
// for 48 MHz (declared above in the environment-neutral code).
|
||||
#if defined(TC4)
|
||||
#define _PM_TIMER_DEFAULT TC4
|
||||
#define _PM_IRQ_HANDLER TC4_Handler
|
||||
|
|
@ -39,22 +42,6 @@
|
|||
#define _PM_TIMER_DEFAULT TC3
|
||||
#define _PM_IRQ_HANDLER TC3_Handler
|
||||
#endif
|
||||
#define _PM_timerFreq 48000000
|
||||
// Partly because IRQs must be declared at compile-time, and partly
|
||||
// because we know Arduino's already set up one of the GCLK sources
|
||||
// for 48 MHz.
|
||||
|
||||
// Because it's tied to a specific timer right now, there can be only
|
||||
// one instance of the Protomatter_core struct. The Arduino library
|
||||
// sets up this pointer when calling begin().
|
||||
void *_PM_protoPtr = NULL;
|
||||
|
||||
// Timer interrupt service routine
|
||||
void _PM_IRQ_HANDLER(void) {
|
||||
// Clear overflow flag:
|
||||
_PM_TIMER_DEFAULT->COUNT16.INTFLAG.reg = TC_INTFLAG_OVF;
|
||||
_PM_row_handler(_PM_protoPtr); // In core.c
|
||||
}
|
||||
|
||||
#elif defined(CIRCUITPY) // COMPILING FOR CIRCUITPYTHON --------------------
|
||||
|
||||
|
|
@ -74,25 +61,49 @@ void _PM_IRQ_HANDLER(void) {
|
|||
#define _PM_wordOffset(pin) (1 - ((pin & 31) / 16))
|
||||
#endif
|
||||
|
||||
// CircuitPython implementation is tied to a specific freq (but the counter
|
||||
// is dynamically allocated):
|
||||
#define _PM_timerFreq 48000000
|
||||
|
||||
// As currently implemented, there can be only one instance of the
|
||||
// Protomatter_core struct. This pointer is set up when starting the matrix.
|
||||
void *_PM_protoPtr = NULL;
|
||||
|
||||
// Timer interrupt service routine
|
||||
void _PM_IRQ_HANDLER(void) {
|
||||
((Tc *)(((Protomatter_core *)_PM_protoPtr)->timer))->COUNT16.INTFLAG.reg =
|
||||
TC_INTFLAG_OVF;
|
||||
_PM_row_handler(_PM_protoPtr); // In core.c
|
||||
}
|
||||
|
||||
#else // END CIRCUITPYTHON -------------------------------------------------
|
||||
|
||||
// Byte offset macros, timer and ISR work for other environments go here.
|
||||
|
||||
#endif
|
||||
|
||||
// SETTINGS COMMON TO ALL ENVIRONMENTS -------------------------------------
|
||||
// Keep this down here because there's environment-specific defines above.
|
||||
|
||||
#define _PM_maxBitplanes 16 ///< RGB bit depth handled by architecture
|
||||
|
||||
// Arduino and CircuitPython implementations both rely on a 48 MHz timer.
|
||||
// In Arduino the timer # is fixed, in CircuitPython it's dynamically
|
||||
// allocated from a timer pool. If that's not the case in other
|
||||
// enviromnents, this will need to be broken out into each section.
|
||||
#define _PM_timerFreq 48000000
|
||||
|
||||
// As currently implemented, there can be only one instance of the
|
||||
// Protomatter_core struct. This pointer is initialized when starting
|
||||
// the matrix (in the environment-specific code -- e.g. matrix.begin()
|
||||
// in the Arduino library).
|
||||
void *_PM_protoPtr = NULL;
|
||||
|
||||
// Timer interrupt service routine. _PM_IRQ_HANDLER *must* be defined by
|
||||
// environment (as with Arduino above) to trigger as an interrupt, else
|
||||
// this is a function that must be invoked by interrupt function elsewhere
|
||||
// (as with CircuitPython).
|
||||
void _PM_IRQ_HANDLER(void) {
|
||||
Protomatter_core *core = (Protomatter_core *)_PM_protoPtr;
|
||||
Tc *timer = core->timer;
|
||||
// If overflow flag is set, clear both the overflow AND match-compare
|
||||
// bits (sometimes both are set if the two trigger in close proximity),
|
||||
// handle the overflow case (load new row in core.c), don't bother with
|
||||
// the match compare now, it's unneeded.
|
||||
if (timer->COUNT16.INTFLAG.bit.OVF) {
|
||||
timer->COUNT16.INTFLAG.reg = TC_INTFLAG_OVF | TC_INTFLAG_MC1;
|
||||
_PM_row_handler(core);
|
||||
} else {
|
||||
// Otherwise, must be the match-compare bit only. Clear just the MC1
|
||||
// flag (but not OVF) and disable the LEDs early (in core.c).
|
||||
timer->COUNT16.INTFLAG.reg = TC_INTFLAG_MC1;
|
||||
_PM_matrix_oe_off(core);
|
||||
}
|
||||
}
|
||||
|
||||
#endif // END SAMD51/SAMD21
|
||||
|
|
|
|||
21
src/arch/samd21.h
Normal file → Executable file
21
src/arch/samd21.h
Normal file → Executable file
|
|
@ -43,6 +43,11 @@
|
|||
|
||||
// CODE COMMON TO ALL ENVIRONMENTS -----------------------------------------
|
||||
|
||||
// Note that the SAMD21 and SAMD51 functions are nearly but not actually
|
||||
// identical -- the registers involved are different between the two.
|
||||
// If any additions or bug fixes are made in one, check the other to see if
|
||||
// similar changes should be made there.
|
||||
|
||||
// Initialize, but do not start, timer
|
||||
void _PM_timerInit(void *tptr) {
|
||||
static const struct {
|
||||
|
|
@ -99,8 +104,8 @@ void _PM_timerInit(void *tptr) {
|
|||
while (tc->COUNT16.STATUS.bit.SYNCBUSY)
|
||||
;
|
||||
|
||||
// Overflow interrupt
|
||||
tc->COUNT16.INTENSET.reg = TC_INTENSET_OVF;
|
||||
// Enable overflow and match-compare-1 interrupts
|
||||
tc->COUNT16.INTENSET.reg = TC_INTENSET_OVF | TC_INTENSET_MC1;
|
||||
|
||||
NVIC_DisableIRQ(timer[timerNum].IRQn);
|
||||
NVIC_ClearPendingIRQ(timer[timerNum].IRQn);
|
||||
|
|
@ -113,12 +118,20 @@ void _PM_timerInit(void *tptr) {
|
|||
// Set timer period, initialize count value to zero, enable timer.
|
||||
// Timer must be initialized to 16-bit mode using the init function
|
||||
// above, but must be inactive before calling this.
|
||||
inline void _PM_timerStart(void *tptr, uint32_t period) {
|
||||
// 'top' is the timer rollover period, 'match' is a compare-match value
|
||||
// that can be used to de-assert the matrix OE line early (e.g. for
|
||||
// brightness or gamma correction).
|
||||
// This does not test the args for validity! Calling func should handle.
|
||||
// It's OK if match exceeds top; interrupt simply doesn't happen then.
|
||||
inline void _PM_timerStart(void *tptr, uint32_t top, uint32_t match) {
|
||||
Tc *tc = (Tc *)tptr; // Cast peripheral address passed in
|
||||
tc->COUNT16.COUNT.reg = 0;
|
||||
while (tc->COUNT16.STATUS.bit.SYNCBUSY)
|
||||
;
|
||||
tc->COUNT16.CC[0].reg = period;
|
||||
tc->COUNT16.CC[0].reg = top;
|
||||
while (tc->COUNT16.STATUS.bit.SYNCBUSY)
|
||||
;
|
||||
tc->COUNT16.CC[1].reg = match;
|
||||
while (tc->COUNT16.STATUS.bit.SYNCBUSY)
|
||||
;
|
||||
tc->COUNT16.CTRLA.bit.ENABLE = 1;
|
||||
|
|
|
|||
21
src/arch/samd51.h
Normal file → Executable file
21
src/arch/samd51.h
Normal file → Executable file
|
|
@ -54,6 +54,11 @@
|
|||
|
||||
// CODE COMMON TO ALL ENVIRONMENTS -----------------------------------------
|
||||
|
||||
// Note that the SAMD21 and SAMD51 functions are nearly but not actually
|
||||
// identical -- the registers involved are different between the two.
|
||||
// If any additions or bug fixes are made in one, check the other to see if
|
||||
// similar changes should be made there.
|
||||
|
||||
// Initialize, but do not start, timer
|
||||
void _PM_timerInit(void *tptr) {
|
||||
static const struct {
|
||||
|
|
@ -147,8 +152,8 @@ void _PM_timerInit(void *tptr) {
|
|||
while (tc->COUNT16.SYNCBUSY.bit.CTRLB)
|
||||
;
|
||||
|
||||
// Overflow interrupt
|
||||
tc->COUNT16.INTENSET.reg = TC_INTENSET_OVF;
|
||||
// Enable overflow and match-compare-1 interrupts
|
||||
tc->COUNT16.INTENSET.reg = TC_INTENSET_OVF | TC_INTENSET_MC1;
|
||||
|
||||
NVIC_DisableIRQ(timer[timerNum].IRQn);
|
||||
NVIC_ClearPendingIRQ(timer[timerNum].IRQn);
|
||||
|
|
@ -161,14 +166,22 @@ void _PM_timerInit(void *tptr) {
|
|||
// Set timer period, initialize count value to zero, enable timer.
|
||||
// Timer must be initialized to 16-bit mode using the init function
|
||||
// above, but must be inactive before calling this.
|
||||
inline void _PM_timerStart(void *tptr, uint32_t period) {
|
||||
// 'top' is the timer rollover period, 'match' is a compare-match value
|
||||
// that can be used to de-assert the matrix OE line early (e.g. for
|
||||
// brightness or gamma correction), or pass 0xFFFFFFFF to ignore.
|
||||
// This does not test the args for validity! Calling func should handle.
|
||||
// It's OK if match exceeds top; interrupt simply doesn't happen then.
|
||||
inline void _PM_timerStart(void *tptr, uint32_t top, uint32_t match) {
|
||||
Tc *tc = (Tc *)tptr; // Cast peripheral address passed in
|
||||
tc->COUNT16.COUNT.reg = 0;
|
||||
while (tc->COUNT16.SYNCBUSY.bit.COUNT)
|
||||
;
|
||||
tc->COUNT16.CC[0].reg = period;
|
||||
tc->COUNT16.CC[0].reg = top;
|
||||
while (tc->COUNT16.SYNCBUSY.bit.CC0)
|
||||
;
|
||||
tc->COUNT16.CC[1].reg = match;
|
||||
while (tc->COUNT16.SYNCBUSY.bit.CC1)
|
||||
;
|
||||
tc->COUNT16.CTRLA.bit.ENABLE = 1;
|
||||
while (tc->COUNT16.SYNCBUSY.bit.STATUS)
|
||||
;
|
||||
|
|
|
|||
4
src/arch/stm32.h
Normal file → Executable file
4
src/arch/stm32.h
Normal file → Executable file
|
|
@ -114,10 +114,10 @@ void _PM_timerInit(void *tptr) {
|
|||
NVIC_SetPriority(tim_irq, 0); // Top priority
|
||||
}
|
||||
|
||||
inline void _PM_timerStart(void *tptr, uint32_t period) {
|
||||
inline void _PM_timerStart(void *tptr, uint32_t top, uint32_t match) {
|
||||
TIM_TypeDef *tim = tptr;
|
||||
tim->SR = 0;
|
||||
tim->ARR = period;
|
||||
tim->ARR = top;
|
||||
tim->CR1 |= TIM_CR1_CEN;
|
||||
tim->DIER |= TIM_DIER_UIE;
|
||||
HAL_NVIC_EnableIRQ(stm_peripherals_timer_get_irqnum(tim));
|
||||
|
|
|
|||
6
src/arch/teensy4.h
Normal file → Executable file
6
src/arch/teensy4.h
Normal file → Executable file
|
|
@ -130,11 +130,11 @@ void _PM_timerInit(void *tptr) {
|
|||
}
|
||||
|
||||
// Set timer period, initialize count value to zero, enable timer.
|
||||
inline void _PM_timerStart(void *tptr, uint32_t period) {
|
||||
inline void _PM_timerStart(void *tptr, uint32_t top, uint32_t match) {
|
||||
IMXRT_PIT_CHANNEL_t *timer = (IMXRT_PIT_CHANNEL_t *)tptr;
|
||||
timer->TCTRL = 0; // Disable timer and interrupt
|
||||
timer->LDVAL = period; // Set load value
|
||||
// timer->CVAL = period; // And current value (just in case?)
|
||||
timer->LDVAL = top; // Set load value
|
||||
// timer->CVAL = top; // And current value (just in case?)
|
||||
timer->TFLG = 1; // Clear timer interrupt
|
||||
timer->TCTRL = 3; // Enable timer and interrupt
|
||||
}
|
||||
|
|
|
|||
525
src/core.c
525
src/core.c
|
|
@ -1,4 +1,5 @@
|
|||
/*!
|
||||
p
|
||||
* @file core.c
|
||||
*
|
||||
* Part of Adafruit's Protomatter library for HUB75-style RGB LED matrices.
|
||||
|
|
@ -56,6 +57,12 @@
|
|||
// arch.h) because it's not architecture-specific.
|
||||
#define _PM_ROW_DELAY 8 ///< Delay time between row address line changes (ms)
|
||||
|
||||
// Gamma correction is provided if the number of bitplanes requested
|
||||
// exceeds 6 bits (the limit of RGB565 color fidelity). Gamma correction
|
||||
// makes intermediate shades more perceptually linear, but incurs more RAM
|
||||
// use and processor load.
|
||||
#define _PM_GAMMA 2.6 ///< Exponent for pow() function in gamma setting
|
||||
|
||||
// These are the lowest-level functions for issing data to matrices.
|
||||
// There are three versions because it depends on how the six RGB data bits
|
||||
// (and clock bit) are arranged within a 32-bit PORT register. If all six
|
||||
|
|
@ -72,10 +79,10 @@ static void blast_byte(Protomatter_core *core, uint8_t *data);
|
|||
static void blast_word(Protomatter_core *core, uint16_t *data);
|
||||
static void blast_long(Protomatter_core *core, uint32_t *data);
|
||||
|
||||
#define _PM_clearReg(x) \
|
||||
#define _PM_clearBit(x) \
|
||||
(*(volatile _PM_PORT_TYPE *)((x).clearReg) = \
|
||||
((x).bit)) ///< Clear non-RGB-data-or-clock control line (_PM_pin type)
|
||||
#define _PM_setReg(x) \
|
||||
#define _PM_setBit(x) \
|
||||
(*(volatile _PM_PORT_TYPE *)((x).setReg) = \
|
||||
((x).bit)) ///< Set non-RGB-data-or-clock control line (_PM_pin type)
|
||||
|
||||
|
|
@ -94,9 +101,8 @@ ProtomatterStatus _PM_init(Protomatter_core *core, uint16_t bitWidth,
|
|||
rgbCount = 5; // Max 5 in parallel (32-bit PORT)
|
||||
if (addrCount > 5)
|
||||
addrCount = 5; // Max 5 address lines (A-E)
|
||||
// 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 (bitDepth > _PM_maxBitplanes)
|
||||
bitDepth = _PM_maxBitplanes; // Varies with architecture & environment
|
||||
|
||||
#if defined(_PM_TIMER_DEFAULT)
|
||||
// If NULL timer was passed in (the default case for the constructor),
|
||||
|
|
@ -105,6 +111,8 @@ ProtomatterStatus _PM_init(Protomatter_core *core, uint16_t bitWidth,
|
|||
if (timer == NULL)
|
||||
timer = _PM_TIMER_DEFAULT;
|
||||
#else
|
||||
// Some environments (e.g. CircuitPython) don't use a default timer --
|
||||
// it's allocated from a pool, period.
|
||||
if (timer == NULL)
|
||||
return PROTOMATTER_ERR_ARG;
|
||||
#endif
|
||||
|
|
@ -303,13 +311,67 @@ ProtomatterStatus _PM_begin(Protomatter_core *core) {
|
|||
}
|
||||
}
|
||||
|
||||
// Set up remap_rb and remap_g tables, which assist in quickly converting
|
||||
// RGB565 pixel values (from the image canvas) to the number of bitplanes
|
||||
// allocated to the matrix (not always a simple shift).
|
||||
if (core->numPlanes < 6) {
|
||||
// 5 or fewer bitplanes, decimate 5-bit red+blue and 6-bit green to
|
||||
// that many planes. Shift right, in-to-out conversion is linear.
|
||||
uint8_t shift = 5 - core->numPlanes; // Might be zero, that's OK
|
||||
for (uint8_t i = 0; i < 32; i++) {
|
||||
core->remap_rb[i] = i >> shift;
|
||||
}
|
||||
shift = 6 - core->numPlanes;
|
||||
for (uint8_t i = 0; i < 64; i++) {
|
||||
core->remap_g[i] = i >> shift;
|
||||
}
|
||||
} else if (core->numPlanes == 6) {
|
||||
// 6 bitplanes exactly, 6-bit green is preserved, 5-bit red+blue
|
||||
// is expanded to 6 bits, in-to-out conversion is still linear.
|
||||
for (uint8_t i = 0; i < 32; i++) {
|
||||
core->remap_rb[i] = (i << 1) | (i >> 4); // Copy msb to lsb
|
||||
}
|
||||
for (uint8_t i = 0; i < 64; i++) {
|
||||
core->remap_g[i] = i;
|
||||
}
|
||||
} else {
|
||||
// Above 6 bitplanes, gamma correction kicks in, in-to-out conversion
|
||||
// is no longer linear, aiming for perceptual linearity instead. 5-bit
|
||||
// red+blue and 6-bit green are expanded to the number of bitplanes
|
||||
// requested (10 should be ample, but you can use more or less to
|
||||
// balance accuracy vs RAM & processor load).
|
||||
float top = (float)((1 << core->numPlanes) - 1);
|
||||
for (uint8_t i = 0; i < 32; i++) { // 5 bits red, blue
|
||||
core->remap_rb[i] =
|
||||
(uint16_t)(pow((float)i / 31.0, _PM_GAMMA) * top + 0.5);
|
||||
}
|
||||
for (uint8_t i = 0; i < 64; i++) { // 6 bits green
|
||||
core->remap_g[i] =
|
||||
(uint16_t)(pow((float)i / 63.0, _PM_GAMMA) * top + 0.5);
|
||||
}
|
||||
}
|
||||
|
||||
// TO DO: this now needs to evolve for gamma correction.
|
||||
// There's now two periods, the 'top' and the 'match' value for
|
||||
// each plane. top can't be less than minMinPeriod, but match
|
||||
// can. There may be several planes (least bits) that all have
|
||||
// the same top value, but different match values.
|
||||
// Match will always be powers-of-two, top will be whatever it
|
||||
// takes to get the frame rate.
|
||||
|
||||
// Estimate minimum bitplane #0 period for _PM_MAX_REFRESH_HZ rate.
|
||||
uint32_t minPeriodPerFrame = _PM_timerFreq / _PM_MAX_REFRESH_HZ;
|
||||
uint32_t minPeriodPerLine = minPeriodPerFrame / core->numRowPairs;
|
||||
core->minPeriod = minPeriodPerLine / ((1 << core->numPlanes) - 1);
|
||||
#if 0
|
||||
if (core->minPeriod < _PM_minMinPeriod) {
|
||||
core->minPeriod = _PM_minMinPeriod;
|
||||
}
|
||||
#else
|
||||
if (core->minPeriod < 1) {
|
||||
core->minPeriod = 1;
|
||||
}
|
||||
#endif
|
||||
// Actual frame rate may be lower than this...it's only an estimate
|
||||
// and does not factor in things like address line selection delays
|
||||
// or interrupt overhead. That's OK, just don't want to exceed this
|
||||
|
|
@ -317,7 +379,11 @@ ProtomatterStatus _PM_begin(Protomatter_core *core) {
|
|||
// 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.
|
||||
|
||||
#if 0
|
||||
core->bitZeroPeriod = core->width * 5; // Initial guesstimate
|
||||
#else
|
||||
core->bitZeroPeriod = core->minPeriod;
|
||||
#endif
|
||||
|
||||
core->activeBuffer = 0;
|
||||
|
||||
|
|
@ -391,7 +457,7 @@ void _PM_stop(Protomatter_core *core) {
|
|||
while (core->swapBuffers)
|
||||
; // Wait for any pending buffer swap
|
||||
_PM_timerStop(core->timer); // Halt timer
|
||||
_PM_setReg(core->oe); // Set OE HIGH (disable output)
|
||||
_PM_setBit(core->oe); // Set OE HIGH (disable output)
|
||||
// So, in PRINCIPLE, setting OE high would be sufficient...
|
||||
// but in case that pin is shared with another function such
|
||||
// as the onloard LED (which pulses during bootloading) let's
|
||||
|
|
@ -408,8 +474,8 @@ void _PM_stop(Protomatter_core *core) {
|
|||
_PM_clockHoldLow;
|
||||
}
|
||||
// Latch data
|
||||
_PM_setReg(core->latch);
|
||||
_PM_clearReg(core->latch);
|
||||
_PM_setBit(core->latch);
|
||||
_PM_clearBit(core->latch);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -423,7 +489,7 @@ void _PM_resume(Protomatter_core *core) {
|
|||
core->frameCount = 0;
|
||||
|
||||
_PM_timerInit(core->timer); // Configure timer
|
||||
_PM_timerStart(core->timer, 1000); // Start timer
|
||||
_PM_timerStart(core->timer, 1000, 0xFFFF); // Start timer
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -443,35 +509,35 @@ void _PM_deallocate(Protomatter_core *core) {
|
|||
}
|
||||
}
|
||||
|
||||
// ISR function (in arch.h) calls this function which it extern'd.
|
||||
// Profuse apologies for the ESP32-specific IRAM_ATTR here -- the goal was
|
||||
// for all architecture-specific detauls to be in arch.h -- but the need
|
||||
// for one here caught me off guard. So, in arch.h, for all non-ESP32
|
||||
// devices, IRAM_ATTR is defined to nothing and is ignored here. If any
|
||||
// future architectures have their own attribute for making a function
|
||||
// RAM-resident, #define IRAM_ATTR to that in the corresponding device-
|
||||
// specific section of arch.h. Sorry. :/
|
||||
// ISR function (in arch-specific header) calls this function which it
|
||||
// extern'd. Profuse apologies for the ESP32-specific IRAM_ATTR here --
|
||||
// the goal was for all architecture-specific detauls to be in the
|
||||
// aforementioned header -- but the need for one here caught me off guard.
|
||||
// So, in those headers, for all non-ESP32 devices, IRAM_ATTR is defined to
|
||||
// nothing and is ignored here. If any future architectures have their own
|
||||
// attribute for making a function RAM-resident, #define IRAM_ATTR to that
|
||||
// in the corresponding device-specific file. Sorry. :/
|
||||
// Any functions called by this function should also be IRAM_ATTR'd.
|
||||
IRAM_ATTR void _PM_row_handler(Protomatter_core *core) {
|
||||
|
||||
_PM_setReg(core->oe); // Disable LED output
|
||||
_PM_setBit(core->oe); // Disable LED output
|
||||
|
||||
// ESP32 requires this next line, but not wanting to put arch-specific
|
||||
// ifdefs in this code...it's a trivial operation so just do it.
|
||||
// Latch is already clear at this point, but we go through the motions
|
||||
// to clear it again in order to sync up the setReg(OE) above with the
|
||||
// setReg(latch) that follows. Reason being, bit set/clear operations
|
||||
// to clear it again in order to sync up the setBit(OE) above with the
|
||||
// setBit(latch) that follows. Reason being, bit set/clear operations
|
||||
// on ESP32 aren't truly atomic, and if those two pins are on the same
|
||||
// port (quite common) the second setReg will be ignored. The nonsense
|
||||
// clearReg is used to sync up the two setReg operations. See also the
|
||||
// port (quite common) the second setBit will be ignored. The nonsense
|
||||
// clearBit is used to sync up the two setBit operations. See also the
|
||||
// ESP32-specific PEW define in arch.h, same deal.
|
||||
_PM_clearReg(core->latch);
|
||||
_PM_clearBit(core->latch);
|
||||
|
||||
_PM_setReg(core->latch);
|
||||
_PM_setBit(core->latch);
|
||||
// Stop timer, save count value at stop
|
||||
uint32_t elapsed = _PM_timerStop(core->timer);
|
||||
uint8_t prevPlane = core->plane; // Save that plane # for later timing
|
||||
_PM_clearReg(core->latch); // (split to add a few cycles)
|
||||
_PM_clearBit(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
|
||||
|
|
@ -513,9 +579,9 @@ IRAM_ATTR void _PM_row_handler(Protomatter_core *core) {
|
|||
line++, bit <<= 1) {
|
||||
if ((core->row & bit) != (core->prevRow & bit)) {
|
||||
if (core->row & bit) { // Set addr line high
|
||||
_PM_setReg(core->addr[line]);
|
||||
_PM_setBit(core->addr[line]);
|
||||
} else { // Set addr line low
|
||||
_PM_clearReg(core->addr[line]);
|
||||
_PM_clearBit(core->addr[line]);
|
||||
}
|
||||
_PM_delayMicroseconds(_PM_ROW_DELAY);
|
||||
}
|
||||
|
|
@ -545,9 +611,81 @@ IRAM_ATTR void _PM_row_handler(Protomatter_core *core) {
|
|||
// now while the next plane data is loaded.
|
||||
|
||||
// Set timer and enable LED output for data loaded on PRIOR pass:
|
||||
_PM_timerStart(core->timer, core->bitZeroPeriod << prevPlane);
|
||||
uint32_t top = core->bitZeroPeriod << prevPlane;
|
||||
uint32_t match = top * (core->brightness + 1) / 256;
|
||||
if (top == match) { // Don't need match interrupt
|
||||
match = 0xFFFF;
|
||||
}
|
||||
if (match > 0) {
|
||||
_PM_timerStart(core->timer, top, match);
|
||||
_PM_delayMicroseconds(1); // Appease Teensy4
|
||||
_PM_clearReg(core->oe); // Enable LED output
|
||||
_PM_clearBit(core->oe); // Enable LED output
|
||||
} else {
|
||||
// If match time is 0, don't enable LEDs, just run for bitplane time.
|
||||
_PM_timerStart(core->timer, top, 0xFFFF);
|
||||
}
|
||||
|
||||
/* --------------------------------------------------------------------
|
||||
So, I'm at an impasse here. Going to push the code and take a moment to
|
||||
regroup. At present, the library WILL allow more than 6 bitplanes and
|
||||
WILL attempt to apply gamma correction and brightness, but this WILL
|
||||
NOT LOOK CORRECT on the matrix (though maybe a marginal improvement vs.
|
||||
the prior 6-bit limit).
|
||||
|
||||
Issue at the moment is timer/counter interrupt handling on SAMD. This
|
||||
function we're in is called from a timer ISR. The single timer ISR
|
||||
handles two different situations -- timer overflow (in which case this
|
||||
function is called, loading new data to the matrix) and timer match
|
||||
(disabling the matrix OE, different function). It used to ONLY handle
|
||||
the overflow case, because we had few enough bitplanes that the timer
|
||||
match wasn't required ... but gamma correction and brightness will
|
||||
both require it.
|
||||
|
||||
On SAMD, timer/counters have a single interrupt handler. There are
|
||||
multiple interrupt flags for different situations (overflow vs match),
|
||||
but both go to a single interrupt handler...and, therefore, it can't
|
||||
interrupt itself. Thus the compare-match CAN'T trigger until the
|
||||
overflow interrupt (which is issuing data to the matrix) completes...
|
||||
and this totally throws off the least-bit timing, and this ruins gamma
|
||||
correction.
|
||||
|
||||
There are several possible workarounds:
|
||||
|
||||
1) Use a TCC rather than a TC peripheral. These have distinct
|
||||
interrupts for overflow vs. compare match, and one should be able
|
||||
to interrupt the other. This would require identifying a "safe" TCC
|
||||
to use for Arduino, and I don't know how CircuitPython doles out
|
||||
TCCs as opposed to TCs. Also, the timer setup and interrupt code
|
||||
would need to change as the peripheral registers are different.
|
||||
Possible, but messy. Also, unsure of applicability to other devices.
|
||||
|
||||
2) Use hardware PWM for the matrix OE line, and don't use the compare
|
||||
match interrupt. Since the WHOLE GOAL of Protomatter was to avoid
|
||||
tying ourselves to specific pin numbers and use mundane GPIO
|
||||
wherever possible (exception for the RGB+clock constrtaints), this
|
||||
option is OFF THE TABLE.
|
||||
|
||||
3) Do the matrix data-loading while OE is OFF, rather than the current
|
||||
strategy of loading the next bitplane while the prior plane is ON
|
||||
(which is done to maximize matrix brightness -- it's off as little
|
||||
as possible). Larger matrices, or longer matrix chains, will incur
|
||||
more off time, with a corresponding impact on brightness. We're
|
||||
realistically talking some small percentage though...maybe 10%,
|
||||
amd given how bright these are at full-blast, that's probably
|
||||
acceptable. Getting this to work WELL though -- balancing a desired
|
||||
refresh rate against an acceptable CPU load -- this will require an
|
||||
empirical step of determining how many timer cycles are needed to
|
||||
shift out a known number of bits (or alternately, number of bits per
|
||||
cycle), which will need to be repeated for each architecture (and on
|
||||
SAMD, each clock speed). This is the MOST LIKELY approach to be
|
||||
used, just accepting the slight brightness hit and the collecting
|
||||
timing data step. (A "golden option" here would be to issue data
|
||||
during OE off on least bitplanes, switching to during OE on for
|
||||
higher (longer duration) bitplanes...but this would require some
|
||||
extra math to figure out the refresh rate throttling, weird handling
|
||||
of the current-bitplane counter, and all-around hurts my brain, so
|
||||
I'm skipping it for now.)
|
||||
----------------------------------------------------------------------- */
|
||||
|
||||
uint32_t elementsPerLine =
|
||||
_PM_chunkSize * ((core->width + (_PM_chunkSize - 1)) / _PM_chunkSize);
|
||||
|
|
@ -569,6 +707,12 @@ IRAM_ATTR void _PM_row_handler(Protomatter_core *core) {
|
|||
// 'plane' data is now loaded, will be shown on NEXT pass
|
||||
}
|
||||
|
||||
// ISR function (in arch-specific header) calls this function which it
|
||||
// extern'd. Same apologies as above.
|
||||
IRAM_ATTR void _PM_matrix_oe_off(Protomatter_core *core) {
|
||||
_PM_setBit(core->oe); // Disable LED output
|
||||
}
|
||||
|
||||
// Innermost data-stuffing loop functions
|
||||
|
||||
// The presence of a bit-toggle register can make the data-stuffing loop a
|
||||
|
|
@ -824,7 +968,8 @@ void _PM_swapbuffer_maybe(Protomatter_core *core) {
|
|||
#if defined(ARDUINO) || defined(CIRCUITPY)
|
||||
|
||||
// Arduino and CircuitPython happen to use the same internal canvas
|
||||
// representation.
|
||||
// representation. This won't necessarily be true for other environments,
|
||||
// can't really say.
|
||||
|
||||
// 16-bit (565) color conversion functions go here (rather than in the
|
||||
// Arduino lib .cpp) because knowledge is required of chunksize and the
|
||||
|
|
@ -859,26 +1004,24 @@ __attribute__((noinline)) void _PM_convert_565_byte(Protomatter_core *core,
|
|||
dest += core->bufferSize * (1 - core->activeBuffer);
|
||||
}
|
||||
|
||||
#if defined(_PM_portToggleRegister)
|
||||
#if !defined(_PM_STRICT_32BIT_IO)
|
||||
#if defined(_PM_portToggleRegister) && !defined(_PM_STRICT_32BIT_IO)
|
||||
// core->clockMask mask is already an 8-bit value
|
||||
uint8_t clockMask = core->clockMask;
|
||||
#else
|
||||
// core->clockMask mask is 32-bit, shift down to 8-bit for this func.
|
||||
uint8_t clockMask = core->clockMask >> (core->portOffset * 8);
|
||||
#endif
|
||||
#endif
|
||||
|
||||
// No need to clear matrix buffer, loops below do a full overwrite
|
||||
// (except for any scanline pad, which was already initialized in the
|
||||
// begin() function and won't be touched here).
|
||||
|
||||
// Determine matrix bytes per bitplane & row (row pair really):
|
||||
|
||||
// Determine matrix bytes per bitplane & row pair
|
||||
uint32_t bitplaneSize =
|
||||
_PM_chunkSize *
|
||||
((width + (_PM_chunkSize - 1)) / _PM_chunkSize); // 1 plane of row pair
|
||||
uint8_t pad = bitplaneSize - width; // Start-of-plane pad
|
||||
uint32_t rowSize = bitplaneSize * core->numPlanes;
|
||||
|
||||
// Skip initial scanline padding if present (HUB75 matrices shift data
|
||||
// in from right-to-left, so if we need scanline padding it occurs at
|
||||
|
|
@ -887,87 +1030,71 @@ __attribute__((noinline)) void _PM_convert_565_byte(Protomatter_core *core,
|
|||
// handle that here, just the pad...
|
||||
dest += pad;
|
||||
|
||||
uint32_t initialRedBit, initialGreenBit, initialBlueBit;
|
||||
if (core->numPlanes == 6) {
|
||||
// If numPlanes is 6, red and blue are expanded from 5 to 6 bits.
|
||||
// This involves duplicating the MSB of the 5-bit value to the LSB
|
||||
// of its corresponding 6-bit value...or in this case, bitmasks for
|
||||
// red and blue are initially assigned to canvas MSBs, while green
|
||||
// starts at LSB (because it's already 6-bit). Inner loop below then
|
||||
// wraps red & blue after the first bitplane.
|
||||
initialRedBit = 0b1000000000000000; // MSB red
|
||||
initialGreenBit = 0b0000000000100000; // LSB green
|
||||
initialBlueBit = 0b0000000000010000; // MSB blue
|
||||
} else {
|
||||
// If numPlanes is 1 to 5, no expansion is needed, and one or all
|
||||
// three color components might be decimated by some number of bits.
|
||||
// The initial bitmasks are set to the components' numPlanesth bit
|
||||
// (e.g. for 5 planes, start at red & blue bit #0, green bit #1,
|
||||
// for 4 planes, everything starts at the next bit up, etc.).
|
||||
uint8_t shiftLeft = 5 - core->numPlanes;
|
||||
initialRedBit = 0b0000100000000000 << shiftLeft;
|
||||
initialGreenBit = 0b0000000001000000 << shiftLeft;
|
||||
initialBlueBit = 0b0000000000000001 << shiftLeft;
|
||||
}
|
||||
|
||||
// This works sequentially-ish through the destination buffer,
|
||||
// reading from the canvas source pixels in repeated passes,
|
||||
// beginning from the least bit.
|
||||
for (uint8_t row = 0; row < core->numRowPairs; row++) {
|
||||
uint32_t redBit = initialRedBit;
|
||||
uint32_t greenBit = initialGreenBit;
|
||||
uint32_t blueBit = initialBlueBit;
|
||||
for (uint8_t plane = 0; plane < core->numPlanes; plane++) {
|
||||
#if defined(_PM_portToggleRegister)
|
||||
uint8_t prior = clockMask; // Set clock bit on 1st out
|
||||
uint8_t prior[16]; // Initial bit state for each plane
|
||||
// At row start, set 'prior' value for each bitplane to clock mask
|
||||
memset(prior, clockMask, core->numPlanes);
|
||||
#endif
|
||||
|
||||
// This has been juggled around from prior code (loop hierarchy was
|
||||
// row.plane.column, now row.column.plane) to reduce the amount of
|
||||
// shifting and masking needed in the column loop, while the addition
|
||||
// of the remap_rb[] and remap_g[] tables allow all bit depths to be
|
||||
// handled similarly, no special bit checks. It does require a bit of
|
||||
// jumping around with the dest offset, but still saves much math.
|
||||
uint32_t offset;
|
||||
|
||||
for (uint16_t x = 0; x < width; x++) {
|
||||
uint16_t upperRGB = upperSrc[x]; // Pixel in upper half
|
||||
uint16_t lowerRGB = lowerSrc[x]; // Pixel in lower half
|
||||
uint16_t rgb565 = upperSrc[x]; // Pixel in upper half
|
||||
uint16_t upperRed = core->remap_rb[rgb565 >> 11];
|
||||
uint16_t upperGreen = core->remap_g[(rgb565 >> 5) & 0x3F];
|
||||
uint16_t upperBlue = core->remap_rb[rgb565 & 0x1F];
|
||||
rgb565 = lowerSrc[x]; // Pixel in lower half
|
||||
uint16_t lowerRed = core->remap_rb[rgb565 >> 11];
|
||||
uint16_t lowerGreen = core->remap_g[(rgb565 >> 5) & 0x3F];
|
||||
uint16_t lowerBlue = core->remap_rb[rgb565 & 0x1F];
|
||||
uint16_t planeMask = 1; // Start from LSB, work up
|
||||
offset = x; // Offset into current dest pointer
|
||||
for (uint8_t plane = 0; plane < core->numPlanes; plane++) {
|
||||
uint8_t result = 0;
|
||||
if (upperRGB & redBit)
|
||||
if (upperRed & planeMask)
|
||||
result |= pinMask[0];
|
||||
if (upperRGB & greenBit)
|
||||
if (upperGreen & planeMask)
|
||||
result |= pinMask[1];
|
||||
if (upperRGB & blueBit)
|
||||
if (upperBlue & planeMask)
|
||||
result |= pinMask[2];
|
||||
if (lowerRGB & redBit)
|
||||
if (lowerRed & planeMask)
|
||||
result |= pinMask[3];
|
||||
if (lowerRGB & greenBit)
|
||||
if (lowerGreen & planeMask)
|
||||
result |= pinMask[4];
|
||||
if (lowerRGB & blueBit)
|
||||
if (lowerBlue & planeMask)
|
||||
result |= pinMask[5];
|
||||
#if defined(_PM_portToggleRegister)
|
||||
dest[x] = result ^ prior;
|
||||
prior = result | clockMask; // Set clock bit on next out
|
||||
dest[offset] = result ^ prior[plane];
|
||||
prior[plane] = result | clockMask; // Set clock bit on next out
|
||||
#else
|
||||
dest[x] = result;
|
||||
dest[offset] = result;
|
||||
#endif
|
||||
} // end x
|
||||
greenBit <<= 1;
|
||||
if (plane || (core->numPlanes < 6)) {
|
||||
// In most cases red & blue bit scoot 1 left...
|
||||
redBit <<= 1;
|
||||
blueBit <<= 1;
|
||||
} else {
|
||||
// Exception being after bit 0 with 6-plane display,
|
||||
// in which case they're reset to red & blue LSBs
|
||||
// (so 5-bit colors are expanded to 6 bits).
|
||||
redBit = 0b0000100000000000;
|
||||
blueBit = 0b0000000000000001;
|
||||
}
|
||||
#if defined(_PM_portToggleRegister)
|
||||
// If using bit-toggle register, erase the toggle bit on the
|
||||
// first element of each bitplane & row pair. The matrix-driving
|
||||
// interrupt functions correspondingly set the clock low before
|
||||
// finishing. This is all done for legibility on oscilloscope --
|
||||
// so idle clock appears LOW -- but really the matrix samples on
|
||||
// a rising edge and we could leave it high, but at this stage
|
||||
// in development just want the scope "readable."
|
||||
dest[-pad] &= ~clockMask; // Negative index is legal & intentional
|
||||
#endif
|
||||
dest += bitplaneSize; // Advance one scanline in dest buffer
|
||||
offset += bitplaneSize; // Same column, next bitplane
|
||||
planeMask <<= 1;
|
||||
} // end plane
|
||||
} // end x
|
||||
|
||||
#if defined(_PM_portToggleRegister)
|
||||
// If using bit-toggle register, erase clock toggle bit on 1st element
|
||||
// of each bitplane & row pair. The matrix-driving interrupt functions
|
||||
// correspondingly set the clock low before finishing. This is just
|
||||
// for legibility on oscilloscope -- so idle clock appears LOW --
|
||||
// but really the matrix samples on a rising edge and we could leave
|
||||
// it high, but at this dev stage just want the scope readable.
|
||||
offset = -pad; // Negative index is legal & intentional
|
||||
for (uint8_t plane = 0; plane < core->numPlanes; plane++) {
|
||||
dest[offset] &= ~clockMask;
|
||||
offset += bitplaneSize;
|
||||
}
|
||||
#endif
|
||||
dest += rowSize; // Advance one scanline in dest buffer
|
||||
upperSrc += width; // Advance one scanline in source buffer
|
||||
lowerSrc += width;
|
||||
} // end row
|
||||
|
|
@ -991,40 +1118,23 @@ void _PM_convert_565_word(Protomatter_core *core, uint16_t *source,
|
|||
_PM_chunkSize *
|
||||
((width + (_PM_chunkSize - 1)) / _PM_chunkSize); // 1 plane of row pair
|
||||
uint8_t pad = bitplaneSize - width; // Start-of-plane pad
|
||||
uint32_t rowSize = bitplaneSize * core->numPlanes;
|
||||
|
||||
uint32_t initialRedBit, initialGreenBit, initialBlueBit;
|
||||
if (core->numPlanes == 6) {
|
||||
initialRedBit = 0b1000000000000000; // MSB red
|
||||
initialGreenBit = 0b0000000000100000; // LSB green
|
||||
initialBlueBit = 0b0000000000010000; // MSB blue
|
||||
} else {
|
||||
uint8_t shiftLeft = 5 - core->numPlanes;
|
||||
initialRedBit = 0b0000100000000000 << shiftLeft;
|
||||
initialGreenBit = 0b0000000001000000 << shiftLeft;
|
||||
initialBlueBit = 0b0000000000000001 << shiftLeft;
|
||||
}
|
||||
uint32_t offset; // Current position in the 'dest' buffer
|
||||
|
||||
// Unlike the 565 byte converter, the word converter DOES clear out the
|
||||
// matrix buffer (because each chain is OR'd into place). If a toggle
|
||||
// register exists, "clear" really means the clock mask is set in all
|
||||
// but the first element on a scanline (per bitplane). If no toggle
|
||||
// register, can just zero everything out.
|
||||
#if defined(_PM_portToggleRegister)
|
||||
// No per-chain loop is required; one clock bit handles all chains
|
||||
uint32_t offset = 0; // Current position in the 'dest' buffer
|
||||
uint16_t mask = core->clockMask >> (core->portOffset * 16);
|
||||
for (uint8_t row = 0; row < core->numRowPairs; row++) {
|
||||
for (uint8_t plane = 0; plane < core->numPlanes; plane++) {
|
||||
dest[offset++] = 0; // First element of each plane
|
||||
for (uint16_t x = 1; x < bitplaneSize; x++) { // All subsequent items
|
||||
dest[offset++] = mask;
|
||||
}
|
||||
}
|
||||
}
|
||||
#if defined(_PM_portToggleRegister) && !defined(_PM_STRICT_32BIT_IO)
|
||||
// core->clockMask mask is already an 16-bit value
|
||||
uint16_t clockMask = core->clockMask;
|
||||
#else
|
||||
memset(dest, 0, core->bufferSize);
|
||||
// core->clockMask mask is 32-bit, shift down to 16-bit for this func.
|
||||
uint16_t clockMask = core->clockMask >> (core->portOffset * 16);
|
||||
#endif
|
||||
|
||||
// Clear matrix buffer (because each chain's data is OR'd into place).
|
||||
// If _PM_portToggleRegister is used, no need to specially handle the
|
||||
// clock bit, the 'prior' var takes care of that across each row/plane.
|
||||
memset(dest, 0, core->bufferSize);
|
||||
|
||||
dest += pad; // Pad value is in 'elements,' not bytes, so this is OK
|
||||
|
||||
// After a set of rows+bitplanes are processed, upperSrc and lowerSrc
|
||||
|
|
@ -1034,57 +1144,60 @@ void _PM_convert_565_word(Protomatter_core *core, uint16_t *source,
|
|||
|
||||
for (uint8_t chain = 0; chain < core->parallel; chain++) {
|
||||
for (uint8_t row = 0; row < core->numRowPairs; row++) {
|
||||
uint32_t redBit = initialRedBit;
|
||||
uint32_t greenBit = initialGreenBit;
|
||||
uint32_t blueBit = initialBlueBit;
|
||||
for (uint8_t plane = 0; plane < core->numPlanes; plane++) {
|
||||
#if defined(_PM_portToggleRegister)
|
||||
uint16_t prior[16]; // Initial bit state for each plane
|
||||
// Since we're ORing in bits over an existing clock bit,
|
||||
// prior is 0 rather than clockMask as in the byte case.
|
||||
uint16_t prior = 0;
|
||||
memset(prior, 0, core->numPlanes * sizeof(uint16_t));
|
||||
#endif
|
||||
|
||||
for (uint16_t x = 0; x < width; x++) {
|
||||
uint16_t upperRGB = upperSrc[x]; // Pixel in upper half
|
||||
uint16_t lowerRGB = lowerSrc[x]; // Pixel in lower half
|
||||
uint16_t rgb565 = upperSrc[x]; // Pixel in upper half
|
||||
uint16_t upperRed = core->remap_rb[rgb565 >> 11];
|
||||
uint16_t upperGreen = core->remap_g[(rgb565 >> 5) & 0x3F];
|
||||
uint16_t upperBlue = core->remap_rb[rgb565 & 0x1F];
|
||||
rgb565 = lowerSrc[x]; // Pixel in lower half
|
||||
uint16_t lowerRed = core->remap_rb[rgb565 >> 11];
|
||||
uint16_t lowerGreen = core->remap_g[(rgb565 >> 5) & 0x3F];
|
||||
uint16_t lowerBlue = core->remap_rb[rgb565 & 0x1F];
|
||||
uint16_t planeMask = 1; // Start from LSB, work up
|
||||
offset = x; // Offset into current dest pointer
|
||||
for (uint8_t plane = 0; plane < core->numPlanes; plane++) {
|
||||
uint16_t result = 0;
|
||||
if (upperRGB & redBit)
|
||||
if (upperRed & planeMask)
|
||||
result |= pinMask[0];
|
||||
if (upperRGB & greenBit)
|
||||
if (upperGreen & planeMask)
|
||||
result |= pinMask[1];
|
||||
if (upperRGB & blueBit)
|
||||
if (upperBlue & planeMask)
|
||||
result |= pinMask[2];
|
||||
if (lowerRGB & redBit)
|
||||
if (lowerRed & planeMask)
|
||||
result |= pinMask[3];
|
||||
if (lowerRGB & greenBit)
|
||||
if (lowerGreen & planeMask)
|
||||
result |= pinMask[4];
|
||||
if (lowerRGB & blueBit)
|
||||
if (lowerBlue & planeMask)
|
||||
result |= pinMask[5];
|
||||
#if defined(_PM_portToggleRegister)
|
||||
// Main difference here vs byte converter is each chain
|
||||
// ORs new bits into place (vs single-pass overwrite).
|
||||
#if defined(_PM_portToggleRegister)
|
||||
dest[x] |= result ^ prior; // Bitwise OR
|
||||
prior = result;
|
||||
dest[offset] |= result ^ prior[plane]; // Bitwise OR
|
||||
prior[plane] = result | clockMask; // Set clock on next out
|
||||
#else
|
||||
dest[x] |= result; // Bitwise OR
|
||||
dest[offset] |= result; // Bitwise OR
|
||||
#endif
|
||||
} // end x
|
||||
greenBit <<= 1;
|
||||
if (plane || (core->numPlanes < 6)) {
|
||||
redBit <<= 1;
|
||||
blueBit <<= 1;
|
||||
} else {
|
||||
redBit = 0b0000100000000000;
|
||||
blueBit = 0b0000000000000001;
|
||||
}
|
||||
dest += bitplaneSize; // Advance one scanline in dest buffer
|
||||
offset += bitplaneSize; // Same column, next bitplane
|
||||
planeMask <<= 1;
|
||||
} // end plane
|
||||
} // end x
|
||||
|
||||
dest += rowSize; // Advance one scanline in dest buffer
|
||||
upperSrc += width; // Advance one scanline in source buffer
|
||||
lowerSrc += width;
|
||||
} // end row
|
||||
pinMask += 6; // Next chain's RGB pin masks
|
||||
upperSrc += halfMatrixOffset; // Advance to next matrix start pos
|
||||
lowerSrc += halfMatrixOffset;
|
||||
}
|
||||
dest -= rowSize * core->numRowPairs; // Reset dest to initial value
|
||||
} // end chain
|
||||
}
|
||||
|
||||
// Corresponding function for long output -- either several parallel chains
|
||||
|
|
@ -1104,33 +1217,14 @@ void _PM_convert_565_long(Protomatter_core *core, uint16_t *source,
|
|||
_PM_chunkSize *
|
||||
((width + (_PM_chunkSize - 1)) / _PM_chunkSize); // 1 plane of row pair
|
||||
uint8_t pad = bitplaneSize - width; // Start-of-plane pad
|
||||
uint32_t rowSize = bitplaneSize * core->numPlanes;
|
||||
|
||||
uint32_t initialRedBit, initialGreenBit, initialBlueBit;
|
||||
if (core->numPlanes == 6) {
|
||||
initialRedBit = 0b1000000000000000; // MSB red
|
||||
initialGreenBit = 0b0000000000100000; // LSB green
|
||||
initialBlueBit = 0b0000000000010000; // MSB blue
|
||||
} else {
|
||||
uint8_t shiftLeft = 5 - core->numPlanes;
|
||||
initialRedBit = 0b0000100000000000 << shiftLeft;
|
||||
initialGreenBit = 0b0000000001000000 << shiftLeft;
|
||||
initialBlueBit = 0b0000000000000001 << shiftLeft;
|
||||
}
|
||||
uint32_t offset; // Current position in the 'dest' buffer
|
||||
|
||||
#if defined(_PM_portToggleRegister)
|
||||
// No per-chain loop is required; one clock bit handles all chains
|
||||
uint32_t offset = 0; // Current position in the 'dest' buffer
|
||||
for (uint8_t row = 0; row < core->numRowPairs; row++) {
|
||||
for (uint8_t plane = 0; plane < core->numPlanes; plane++) {
|
||||
dest[offset++] = 0; // First element of each plane
|
||||
for (uint16_t x = 1; x < bitplaneSize; x++) { // All subsequent items
|
||||
dest[offset++] = core->clockMask;
|
||||
}
|
||||
}
|
||||
}
|
||||
#else
|
||||
// Clear matrix buffer (because each chain's data is OR'd into place).
|
||||
// If _PM_portToggleRegister is used, no need to specially handle the
|
||||
// clock bit, the 'prior' var takes care of that across each row/plane.
|
||||
memset(dest, 0, core->bufferSize);
|
||||
#endif
|
||||
|
||||
dest += pad; // Pad value is in 'elements,' not bytes, so this is OK
|
||||
|
||||
|
|
@ -1138,55 +1232,60 @@ void _PM_convert_565_long(Protomatter_core *core, uint16_t *source,
|
|||
|
||||
for (uint8_t chain = 0; chain < core->parallel; chain++) {
|
||||
for (uint8_t row = 0; row < core->numRowPairs; row++) {
|
||||
uint32_t redBit = initialRedBit;
|
||||
uint32_t greenBit = initialGreenBit;
|
||||
uint32_t blueBit = initialBlueBit;
|
||||
for (uint8_t plane = 0; plane < core->numPlanes; plane++) {
|
||||
#if defined(_PM_portToggleRegister)
|
||||
uint32_t prior = 0;
|
||||
uint32_t prior[16]; // Initial bit state for each plane
|
||||
// Since we're ORing in bits over an existing clock bit,
|
||||
// prior is 0 rather than clockMask as in the byte case.
|
||||
memset(prior, 0, core->numPlanes * sizeof(uint32_t));
|
||||
#endif
|
||||
|
||||
for (uint16_t x = 0; x < width; x++) {
|
||||
uint16_t upperRGB = upperSrc[x]; // Pixel in upper half
|
||||
uint16_t lowerRGB = lowerSrc[x]; // Pixel in lower half
|
||||
uint16_t rgb565 = upperSrc[x]; // Pixel in upper half
|
||||
uint16_t upperRed = core->remap_rb[rgb565 >> 11];
|
||||
uint16_t upperGreen = core->remap_g[(rgb565 >> 5) & 0x3F];
|
||||
uint16_t upperBlue = core->remap_rb[rgb565 & 0x1F];
|
||||
rgb565 = lowerSrc[x]; // Pixel in lower half
|
||||
uint16_t lowerRed = core->remap_rb[rgb565 >> 11];
|
||||
uint16_t lowerGreen = core->remap_g[(rgb565 >> 5) & 0x3F];
|
||||
uint16_t lowerBlue = core->remap_rb[rgb565 & 0x1F];
|
||||
uint16_t planeMask = 1; // Start from LSB, work up
|
||||
offset = x; // Offset into current dest pointer
|
||||
for (uint8_t plane = 0; plane < core->numPlanes; plane++) {
|
||||
uint32_t result = 0;
|
||||
if (upperRGB & redBit)
|
||||
if (upperRed & planeMask)
|
||||
result |= pinMask[0];
|
||||
if (upperRGB & greenBit)
|
||||
if (upperGreen & planeMask)
|
||||
result |= pinMask[1];
|
||||
if (upperRGB & blueBit)
|
||||
if (upperBlue & planeMask)
|
||||
result |= pinMask[2];
|
||||
if (lowerRGB & redBit)
|
||||
if (lowerRed & planeMask)
|
||||
result |= pinMask[3];
|
||||
if (lowerRGB & greenBit)
|
||||
if (lowerGreen & planeMask)
|
||||
result |= pinMask[4];
|
||||
if (lowerRGB & blueBit)
|
||||
if (lowerBlue & planeMask)
|
||||
result |= pinMask[5];
|
||||
#if defined(_PM_portToggleRegister)
|
||||
// Main difference here vs byte converter is each chain
|
||||
// ORs new bits into place (vs single-pass overwrite).
|
||||
#if defined(_PM_portToggleRegister)
|
||||
dest[x] |= result ^ prior; // Bitwise OR
|
||||
prior = result;
|
||||
dest[offset] |= result ^ prior[plane]; // Bitwise OR
|
||||
prior[plane] = result | core->clockMask; // Set clock on next out
|
||||
#else
|
||||
dest[x] |= result; // Bitwise OR
|
||||
dest[offset] |= result; // Bitwise OR
|
||||
#endif
|
||||
} // end x
|
||||
greenBit <<= 1;
|
||||
if (plane || (core->numPlanes < 6)) {
|
||||
redBit <<= 1;
|
||||
blueBit <<= 1;
|
||||
} else {
|
||||
redBit = 0b0000100000000000;
|
||||
blueBit = 0b0000000000000001;
|
||||
}
|
||||
dest += bitplaneSize; // Advance one scanline in dest buffer
|
||||
offset += bitplaneSize; // Same column, next bitplane
|
||||
planeMask <<= 1;
|
||||
} // end plane
|
||||
} // end x
|
||||
|
||||
dest += rowSize; // Advance one scanline in dest buffer
|
||||
upperSrc += width; // Advance one scanline in source buffer
|
||||
lowerSrc += width;
|
||||
} // end row
|
||||
pinMask += 6; // Next chain's RGB pin masks
|
||||
upperSrc += halfMatrixOffset; // Advance to next matrix start pos
|
||||
lowerSrc += halfMatrixOffset;
|
||||
}
|
||||
dest -= rowSize * core->numRowPairs; // Reset dest to initial value
|
||||
} // end chain
|
||||
}
|
||||
|
||||
void _PM_convert_565(Protomatter_core *core, uint16_t *source, uint16_t width) {
|
||||
|
|
@ -1216,7 +1315,7 @@ void _PM_convert_565(Protomatter_core *core, uint16_t *source, uint16_t width) {
|
|||
// single matrix chain (doing parallel chains would require either an
|
||||
// impractically large lookup table, or adding together multiple tables'
|
||||
// worth of bitmasks, which would slow things down in the vital inner loop).
|
||||
// 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
|
||||
// Although parallel matrix chains aren't yet 100% verified 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.
|
||||
|
|
|
|||
19
src/core.h
19
src/core.h
|
|
@ -65,6 +65,8 @@ typedef struct {
|
|||
uint32_t rgbAndClockMask; ///< PORT bit mask for RGB data + clock
|
||||
volatile void *addrPortToggle; ///< See singleAddrPort below
|
||||
void *screenData; ///< Per-bitplane RGB data for matrix
|
||||
uint16_t remap_rb[32]; ///< Gamma or decimation table for red+blue
|
||||
uint16_t remap_g[64]; ///< Gamma or decimation table for green
|
||||
_PM_pin latch; ///< RGB data latch
|
||||
_PM_pin oe; ///< !OE (LOW out enable)
|
||||
_PM_pin *addr; ///< Array of address pins
|
||||
|
|
@ -80,6 +82,7 @@ typedef struct {
|
|||
uint8_t portOffset; ///< Active 8- or 16-bit pos. in PORT
|
||||
uint8_t numPlanes; ///< Display bitplanes (1 to 6)
|
||||
uint8_t numRowPairs; ///< Addressable row pairs
|
||||
uint8_t brightness; ///< Max brightness (if supported by arch)
|
||||
bool doubleBuffer; ///< 2X buffers for clean switchover
|
||||
bool singleAddrPort; ///< If 1, all addr lines on same PORT
|
||||
volatile uint8_t activeBuffer; ///< Index of currently-displayed buf
|
||||
|
|
@ -198,6 +201,15 @@ extern void _PM_deallocate(Protomatter_core *core);
|
|||
*/
|
||||
extern void _PM_row_handler(Protomatter_core *core);
|
||||
|
||||
/*!
|
||||
@brief Sometimes called by a timer interrupt to disable matrix output
|
||||
prior to the normal bitplane switchover time (handled by the
|
||||
row handler above). Usually only occurs on the least bitplanes
|
||||
of high-depth setups.
|
||||
@param core Pointer to Protomatter_core structure.
|
||||
*/
|
||||
extern void _PM_matrix_oe_off(Protomatter_core *core);
|
||||
|
||||
/*!
|
||||
@brief Returns current value of frame counter and resets its value to
|
||||
zero. Two calls to this, timed one second apart (or use math with
|
||||
|
|
@ -214,9 +226,12 @@ extern uint32_t _PM_getFrameCount(Protomatter_core *core);
|
|||
@param tptr Pointer to timer/counter peripheral OR a struct
|
||||
encapsulating information about a timer/counter
|
||||
periph (architecture-dependent).
|
||||
@param period Timer 'top' / rollover value.
|
||||
@param top Timer 'top' / rollover value.
|
||||
@param match Timer 'match' value, can be used to de-assert the matrix
|
||||
OE line before the 'top' interval (e.g. for brightness or
|
||||
gamma correction), or pass 0xFFFFFFFF to ignore.
|
||||
*/
|
||||
extern void _PM_timerStart(void *tptr, uint32_t period);
|
||||
extern void _PM_timerStart(void *tptr, uint32_t top, uint32_t match);
|
||||
|
||||
/*!
|
||||
@brief Stop timer/counter peripheral.
|
||||
|
|
|
|||
Loading…
Reference in a new issue