/* Support of multiple LED layouts.
By: Sutaburosu
Testing by: Andrew Tuline
Original code:
https://gist.github.com/sutaburosu/43d46240dc2b05683d1e98872885f8db#file-stream_serial-ino-L34-L55
https://github.com/sutaburosu/LEDpijp/blob/main/XYmatrix.h#L37-L58 (has a fix)
Other references:
https://github.com/marcmerlin/ArduinoOnPc-FastLED-GFX-LEDMatrix
https://github.com/Jorgen-VikingGod/LEDMatrix
https://macetech.github.io/FastLED-XY-Map-Generator/
https://github.com/macetech/FastLED-XY-Map-Generator
https://github.com/marcmerlin/Framebuffer_GFX#custom-xy-mapping-for-irregular-arrays
// Helper functions for a two-dimensional XY matrix of pixels.
//
// XY(x,y) takes x and y coordinates and returns an LED index number,
// for use like this: leds[ XY(x,y) ] == CRGB::Red;
//
// If pixels are driven horizontally at the same end, like below, it's a (ROWMAJOR) layout:
//
// 0 > 1 > 2 > 3 > 4
// |
// .----<----<----<----'
// |
// 5 > 6 > 7 > 8 > 9
// |
// .----<----<----<----'
// |
// 10 > 11 > 12 > 13 > 14
// |
// .----<----<----<----'
// |
// 15 > 16 > 17 > 18 > 19
//
//
// If pixels are driven horizontally and laid out back and forth, it's a (ROWMAJOR | SERPENTINE) layout:
//
// 0 > 1 > 2 > 3 > 4
// |
// |
// 9 < 8 < 7 < 6 < 5
// |
// |
// 10 > 11 > 12 > 13 > 14
// |
// |
// 19 < 18 < 17 < 16 < 15
//
We have 5 orientation bits. The values are:
XY_LAYOUT = SERPENTINE = 16, ROWMAJOR = 8, TRANSPOSE = 4, FLIPMAJOR = 2, FLIPMINOR = 1
ROWMAJOR - The x (or Major) value goes horizontal (otherwise vertical).
SERPENTINE - A serpentine layout (otherwise non-serpentine layout).
FLIPMAJOR - Flip the major axis, ie top to bottom (otherwise not).
FLIPMINOR - Flip the minor axis, ie left to right (otherwise not).
TRANSPOSE - Swap the major and the minor axes (otherwise no swap). Don't use on non-square.
*/
// Here's a list of definitons to test. --------------------------------------------------------------------------------
//#define XY_LAYOUT (ROWMAJOR | SERPENTINE) // Serpentine with rows at top left. Wokwi standard. Good!
// #define XY_LAYOUT (ROWMAJOR) // Non-serpentine with rows at top left. Good!
// #define XY_LAYOUT (ROWMAJOR | SERPENTINE | FLIPMAJOR) // Serpentine, leds[0] is top right. Good!
// #define XY_LAYOUT (ROWMAJOR | FLIPMAJOR) // Non-serpentine, leds[0] is top right. Good!
#define XY_LAYOUT (ROWMAJOR | SERPENTINE | FLIPMINOR) // Serpentine, leds[0] is bottom right. Good!
// #define XY_LAYOUT (ROWMAJOR | FLIPMINOR) // Non-serpentine, leds[0] is bottom right. Good!
// #define XY_LAYOUT (ROWMAJOR | SERPENTINE | FLIPMAJOR | FLIPMINOR) // Serpentine, leds[0] is bottom left. Good!
// #define XY_LAYOUT (ROWMAJOR | FLIPMAJOR | FLIPMINOR) // Non-serpentine, leds[0] is bottom left. Good!
// #define XY_LAYOUT 0 // NOT ROWMAJOR and NOT SERPENTINE. Good!
// #define XY_LAYOUT SERPENTINE // Not ROWMAJOR. Good!
// Rest are untested.
// Have NOT tested when TRANSPOSE is set. // We'll get there.
#include <FastLED.h>
#define LED_PIN 12
const uint8_t matrixWidth = 16;
const uint8_t matrixHeight = 16;
#define NUM_LEDS matrixWidth * matrixHeight
CRGB leds[NUM_LEDS+1]; // The +1 is for the out of bounds LED for any out of bound coordinates.
//const bool matrixSerpentine = true;
enum XY_Layout {
SERPENTINE = 16, ROWMAJOR = 8, TRANSPOSE = 4, FLIPMAJOR = 2, FLIPMINOR = 1
};
void setup() {
Serial.begin(115200);
FastLED.addLeds<WS2812, LED_PIN, GRB>(leds, NUM_LEDS);
FastLED.setBrightness(64);
dolights();
}
void loop() {
alternates();
}
void printLayout(void) {
for (int y=0;y<matrixHeight;y++) {
for (int x=0;x<matrixWidth;x++) {
char result[10];
sprintf(result, "%03d", XYP(x,y));
Serial.print(result);
if (x+1<matrixWidth) {
if (XYP(x+1,y) == XYP(x,y) + 1)
Serial.print(" → ");
else if (XYP(x+1,y) == XYP(x,y) - 1)
Serial.print(" ← ");
else
Serial.print(" ");
}
}
Serial.println();
for (int x=0;x<matrixWidth;x++) {
if (y+1<matrixHeight) {
if (XYP(x,y+1) == XYP(x,y) + 1)
Serial.print(" ↓ ");
else if (XYP(x,y+1) == XYP(x,y) - 1)
Serial.print(" ↑ ");
else
Serial.print(" ");
}
}
Serial.println();
}
Serial.println();
}
void dolights() {
// Raw LED writing to confirm physical layout.
/* leds[0] = CRGB::Red;
leds[1] = CRGB::Green;
leds[2] = CRGB::Blue;
leds[31] = CRGB::Orange;
*/
printLayout();
}
void alternates() {
leds[XY(0, 0)] = CRGB::Violet;
leds[XY(1, 0)] = CRGB::Green;
leds[XY(2, 0)] = CRGB::Blue;
leds[XY(0, 1)] = CRGB::Orange;
FastLED.show();
delay(500);
leds[XYP(0, 0)] = CRGB::Red;
leds[XYP(1, 0)] = CRGB::Green;
leds[XYP(2, 0)] = CRGB::Blue;
leds[XYP(0, 1)] = CRGB::Orange;
FastLED.show();
delay(500);
}
// enum XY_Layout {
// SERPENTINE = 16, ROWMAJOR = 8, TRANSPOSE = 4, FLIPMAJOR = 2, FLIPMINOR = 1
// };
// Advanced XY layout supports multiple orientations.
uint16_t XYP(uint8_t x, uint8_t y) { // By: Sutaburosu
bool matrixSerpentine = XY_LAYOUT & SERPENTINE;
bool matrixRowmajor = XY_LAYOUT & ROWMAJOR;
bool matrixFlipmajor = XY_LAYOUT & FLIPMAJOR;
bool matrixFlipminor = XY_LAYOUT & FLIPMINOR;
bool matrixTranspose = XY_LAYOUT & TRANSPOSE;
int matrixHorizontal = 1;
int matrixVertical = 1;
bool matrixPanels = true;
//temporary code, until major and minor variables have been replaced by matrixFirstLedPosition and matrixLedOrientation
//Proposed new values (instead of rowmajor/flipmajor/flipminor)
uint8_t matrixFirstLedTopBottom; //Prompt: First Led Top or Bottom Drop Down: Top/Bottom Values 0/1
uint8_t matrixFirstLedLeftRight; //Prompt: First Led Left or Right Drop Down: Left/Right Values 0/1
bool matrixLedOrientationHorVert = !matrixRowmajor; //Prompt: Led Orientation Drop down: Horizontal/Vertical Values 0/1
//temp conversion of old to new values
if (matrixLedOrientationHorVert == 0) { //horizontal
matrixFirstLedTopBottom = matrixFlipminor;
matrixFirstLedLeftRight = matrixFlipmajor;
}
else { //vertical
matrixFirstLedTopBottom = matrixFlipmajor;
matrixFirstLedLeftRight = matrixFlipminor;
}
//end of temporary code
if (x >= matrixWidth || y >= matrixHeight)
return NUM_LEDS; // Off the charts, so it's only useable by routines that use leds[x]!!!!
uint8_t major, minor, sz_major, sz_minor;
//Width, Height and Size of panel. Same as matrixWidth and Height if only one panel
uint16_t panelWidth = matrixPanels?matrixWidth / matrixHorizontal:matrixWidth;
uint16_t panelHeight = matrixPanels?matrixHeight / matrixVertical:matrixHeight;
uint16_t panelSize = panelWidth * panelHeight;
//Horizontal and vertical panel number. 0 if only one panel
uint8_t panelHorizontalNr = x / panelWidth;
uint8_t panelVerticalNr = y / panelHeight;
uint16_t panelFirstLed = 0; //0 if only one panel
if (matrixLedOrientationHorVert == 0) { //horizontal
if (matrixPanels) panelFirstLed = panelSize * (panelHorizontalNr + matrixHorizontal * panelVerticalNr);
major = x%panelWidth, minor = y%panelHeight, sz_major = panelWidth, sz_minor = panelHeight;
}
else { //vertical
if (matrixPanels) panelFirstLed = panelSize * (panelVerticalNr + matrixVertical * panelHorizontalNr);
major = y%panelHeight, minor = x%panelWidth, sz_major = panelHeight, sz_minor = panelWidth;
}
bool flipmajor = (matrixLedOrientationHorVert == 0)?matrixFirstLedLeftRight == 1:matrixFirstLedTopBottom == 1;//right:bottom
bool flipminor = (matrixLedOrientationHorVert == 0)?matrixFirstLedTopBottom == 1:matrixFirstLedLeftRight == 1;//bottom:right
//flip minor if needed, this needs to be done before flipmajor because minor value needed to identify serpentine row
if (flipminor) minor = sz_minor - 1 - minor;
// A line of magic. &=Binary AND, minor&1 is odd rows, ^=Binary XOR => flapmajor or serpentine (odd) row, but not both (XOR)
if (flipmajor ^ ((minor & 1) && matrixSerpentine)) major = sz_major - 1 - major;
if (matrixTranspose)
return major * (uint16_t) sz_minor + minor + panelFirstLed;
else
return minor * (uint16_t) sz_major + major + panelFirstLed;
}
// This is the non-panel version
uint16_t XY(uint8_t x, uint8_t y) {
uint8_t major, minor, sz_major, sz_minor;
if (x >= matrixWidth || y >= matrixHeight)
return NUM_LEDS; // Failsafe maximum value is one beyond the matrix.
if (XY_LAYOUT & ROWMAJOR) {
major = x, minor = y, sz_major = matrixWidth, sz_minor = matrixHeight; // The STANDARD layout, leds[0] is top left.
} else {
major = y, minor = x, sz_major = matrixHeight, sz_minor = matrixWidth; // Not sure how to set this. Maybe just use a '0'.
}
if (((XY_LAYOUT & FLIPMAJOR) != 0) ^ (((minor & 1) != 0) && ((XY_LAYOUT & SERPENTINE) != 0))) { // A line of magic.
major = sz_major - 1 - major;
}
if (XY_LAYOUT & FLIPMINOR) {
minor = sz_minor - 1 - minor;
}
if (XY_LAYOUT & TRANSPOSE) {
return major * (uint16_t) sz_major + minor;
} else {
return minor * (uint16_t) sz_major + major;
}
}
////////////////////////////////////////////
// OLD AND REFERENCE CODE BELOW HERE. //
////////////////////////////////////////////
/*
// Basic XY layout doesn't support multiple orientations.
#define XY_LAYOUT (ROWMAJOR | SERPENTINE)
uint16_t OLD_XY( uint8_t x, uint8_t y)
{
uint16_t i;
if( matrixSerpentine == false) {
i = (y * matrixWidth) + x;
}
if( matrixSerpentine == true) {
if( y & 0x01) {
// Odd rows run backwards
uint8_t reverseX = (matrixWidth - 1) - x;
i = (y * matrixWidth) + reverseX;
} else {
// Even rows run forwards
i = (y * matrixWidth) + x;
}
}
return i;
}
*/
/*
// Advanced XY with debug code.
// enum XY_Layout {
// SERPENTINE = 16, ROWMAJOR = 8, TRANSPOSE = 4, FLIPMAJOR = 2, FLIPMINOR = 1
// };
uint16_t XY(uint8_t x, uint8_t y) {
uint8_t major, minor, sz_major, sz_minor;
if (x >= matrixWidth || y >= matrixHeight)
return NUM_LEDS; // Failsafe maximum value is one beyond the matrix.
if (XY_LAYOUT & ROWMAJOR) {
Serial.println("Rowmajor");
major = x, minor = y, sz_major = matrixWidth, sz_minor = matrixHeight; // The STANDARD layout, leds[0] is top left.
} else {
Serial.println("Not Rowmajor");
major = y, minor = x, sz_major = matrixHeight, sz_minor = matrixWidth; // Not sure how to set this. Maybe just use a '0'.
}
if (((XY_LAYOUT & FLIPMAJOR) != 0) ^ (((minor & 1) != 0) && ((XY_LAYOUT & SERPENTINE) != 0))) { // A line of magic.
Serial.println("Flipmajor and Serpentine");
major = sz_major - 1 - major;
}
if (XY_LAYOUT & FLIPMINOR) {
Serial.println("Flipminor");
minor = sz_minor - 1 - minor;
}
if (XY_LAYOUT & TRANSPOSE) {
Serial.println("Transposed");
Serial.println(major *(uint16_t) sz_major + minor);
return major * (uint16_t) sz_major + minor;
} else {
Serial.println("Not Transposed");
Serial.println(minor * (uint16_t) sz_major + major);
return minor * (uint16_t) sz_major + major;
}
}
*/