/* 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() {
}
void printLayout(void) {
for (int y=0;y<matrixHeight;y++) {
for (int x=0;x<matrixWidth;x++) {
char result[10];
sprintf(result, "%03d", XY(x,y));
Serial.print(result);
if (x+1<matrixWidth) {
if (XY(x+1,y) == XY(x,y) + 1)
Serial.print(" → ");
else if (XY(x+1,y) == XY(x,y) - 1)
Serial.print(" ← ");
else
Serial.print(" ");
}
}
Serial.println();
for (int x=0;x<matrixWidth;x++) {
if (y+1<matrixHeight) {
if (XY(x,y+1) == XY(x,y) + 1)
Serial.print(" ↓ ");
else if (XY(x,y+1) == XY(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;
*/
// Layout based led's.
leds[XY(0, 0)] = CRGB::Red;
leds[XY(1, 0)] = CRGB::Green;
leds[XY(2, 0)] = CRGB::Blue;
leds[XY(0, 1)] = CRGB::Orange;
FastLED.show();
printLayout();
}
// enum XY_Layout {
// SERPENTINE = 16, ROWMAJOR = 8, TRANSPOSE = 4, FLIPMAJOR = 2, FLIPMINOR = 1
// };
// Advanced XY layout supports multiple orientations.
uint16_t XY(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 nrOfHorizontalPanels = 1;
int nrOfVerticalPanels = 1;
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;
//temporary variables for dev/testing purposes, now FirstLed and LastLed in Time & Macros settings used
//Width, Height and Size of panel. Same as matrixWidth and Height if only one panel
int panelWidth = matrixWidth / nrOfHorizontalPanels;
int panelHeight = matrixHeight / nrOfVerticalPanels;
int panelSize = panelWidth * panelHeight;
//Horizontal and vertical panel number. 0 if only one panel
int panelHorizontalNr = x / panelWidth;
int panelVerticalNr = y / panelHeight;
int panelFirstLed = 0; //0 if only one panel
if (matrixRowmajor) {
panelFirstLed = panelSize * (panelHorizontalNr + nrOfHorizontalPanels * panelVerticalNr);
major = x%panelWidth, minor = y%panelHeight, sz_major = panelWidth, sz_minor = panelHeight;
}
else {
panelFirstLed = panelSize * (panelVerticalNr + nrOfVerticalPanels * panelHorizontalNr); //0 if only one panel, more panels: multitudes of 256 normally
major = y%panelHeight, minor = x%panelWidth, sz_major = panelHeight, sz_minor = panelWidth;
}
if (((matrixFlipmajor) != 0) ^ (((minor & 1) != 0) && ((matrixSerpentine) != 0))) // A line of magic.
major = sz_major - 1 - major;
if (matrixFlipminor)
minor = sz_minor - 1 - minor;
if (matrixTranspose)
return major * (uint16_t) sz_major + minor + panelFirstLed;
else
return minor * (uint16_t) sz_major + major + panelFirstLed;
}
////////////////////////////////////////////
// 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;
}
}
*/