/* Support of multiple LED layouts. Non-panel version used by SR WLED documentation.
By: Sutaburosu
Adapted and 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
SERPENTINE - Select if your layout is serpentine. Otherwise, it's not.
ROWMAJOR - Select if your layout is horizontal. De-select if it's vertical.
FLIPMAJOR - Flip the major axis, ie top to bottom if it's a horizontal layout.
FLIPMINOR - Flip the minor axis, ie left to right if it's a horizontal layout.
TRANSPOSE - Swap the major and the minor axes (otherwise no swap). Not for general usage.
*/
// Here's a list of definitions 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.
enum XY_Layout {
SERPENTINE = 16, ROWMAJOR = 8, TRANSPOSE = 4, FLIPMAJOR = 2, FLIPMINOR = 1
};
// Inisialisasi objek AnimatedGIF
#include <FastLED.h> // Pastikan library FastLED sudah di-include
#include <AnimatedGIF.h>
AnimatedGIF gif;
AnimatedGIF intro;
#include "a.h"
#include "yoong.h"
#include "saber.h"
#define LED_PIN 13
#define WIDTH 12
#define HEIGHT 14
#define NUMPIXELS (WIDTH * HEIGHT)
#define NUM_LEDS (WIDTH * HEIGHT)
#define displayWidth 12
#define displayHeight 14
CRGB leds[NUM_LEDS + 1]; // Array LED WS2812
CRGB ledsR[NUM_LEDS + 1]; // Array LED WS2812
// Fungsi untuk menggambar GIF ke WS2812 Matrix
void GIFDraw(GIFDRAW *pDraw) {
uint8_t *s = pDraw->pPixels; // Data pixel dari frame GIF
uint16_t *usPalette = pDraw->pPalette; // Palet warna dari GIF
int x, y, iWidth;
// Membatasi lebar gambar sesuai dengan dimensi matrix
iWidth = pDraw->iWidth;
if (iWidth + pDraw->iX > WIDTH)
iWidth = WIDTH - pDraw->iX;
// Memeriksa jika baris gambar valid untuk digambar
y = pDraw->iY + pDraw->y; // Baris saat ini dari gambar
if (y >= HEIGHT || pDraw->iX >= WIDTH || iWidth < 1)
return;
// Mengatur transparansi (jika ada)
if (pDraw->ucHasTransparency) {
uint8_t *pEnd, c, ucTransparent = pDraw->ucTransparent;
pEnd = s + iWidth;
x = 0;
while (s < pEnd) {
c = *s++;
if (c != ucTransparent) {
uint16_t color = usPalette[c];
// RGB565 ke RGB8 conversion yang lebih tepat
uint8_t red = (color >> 11) & 0x1F; // Ambil 5 bit merah
uint8_t green = (color >> 5) & 0x3F; // Ambil 6 bit hijau
uint8_t blue = color & 0x1F; // Ambil 5 bit biru
// Skala warna RGB565 ke RGB8 (0-255)
red = (red * 255) / 31;
green = (green * 255) / 63;
blue = (blue * 255) / 31;
// Menampilkan warna pada LED menggunakan FastLED
leds[XY(pDraw->iX + x, y)] = CRGB(red, green, blue); // Menampilkan warna yang benar
}
x++;
}
}
else {
// Jika tidak ada transparansi, langsung terjemahkan piksel ke LED
for (x = 0; x < iWidth; x++) {
uint8_t c = *s++;
uint16_t color = usPalette[c];
// RGB565 ke RGB8 conversion yang lebih tepat
uint8_t red = (color >> 11) & 0x1F; // Ambil 5 bit merah
uint8_t green = (color >> 5) & 0x3F; // Ambil 6 bit hijau
uint8_t blue = color & 0x1F; // Ambil 5 bit biru
// Skala warna RGB565 ke RGB8 (0-255)
red = (red * 255) / 31;
green = (green * 255) / 63;
blue = (blue * 255) / 31;
// Serial.println(color);
// Menampilkan warna pada LED menggunakan FastLED
leds[XY(pDraw->iX + x, y)] = CRGB(red, green, blue); // Menampilkan warna yang benar
}
}
}
// Fungsi XY untuk mengonversi koordinat x,y ke indeks LED sesuai layout
uint16_t XY(uint8_t x, uint8_t y) {
uint8_t major, minor, sz_major, sz_minor;
if (x >= WIDTH || y >= HEIGHT)
return NUM_LEDS; // Menghindari akses indeks yang tidak valid
// Layout baris utama (ROWMAJOR) atau serpentine
major = x, minor = y, sz_major = WIDTH, sz_minor = HEIGHT;
if (((minor & 1) != 0) && (XY_LAYOUT & SERPENTINE)) // Serpentine
major = sz_major - 1 - major; // Balikkan arah jika serpentine
if (XY_LAYOUT & FLIPMINOR)
minor = sz_minor - 1 - minor;
return minor * sz_major + major; // Kembalikan indeks LED
}
// Fungsi setup
void setup() {
Serial.begin(115200);
FastLED.addLeds<WS2812, LED_PIN, GRB>(leds, NUM_LEDS);
FastLED.addLeds<WS2812, 12, GRB>(ledsR, NUM_LEDS);
FastLED.setBrightness(255);
// Inisialisasi GIF dengan data yang ada di PROGMEM
gif.begin(LITTLE_ENDIAN_PIXELS); // Pastikan ini cocok dengan data GIF
if (gif.open((uint8_t *)saber, sizeof(saber), GIFDraw)) {
Serial.println("GIF successfully loaded.");
} else {
Serial.println("Failed to load GIF.");
}
intro.begin(LITTLE_ENDIAN_PIXELS); // Pastikan ini cocok dengan data GIF
if (intro.openFLASH((uint8_t *)welcome, sizeof(welcome), GIFDraw)) {
Serial.println("GIF successfully loaded.");
} else {
Serial.println("Failed to load GIF.");
}
while (1) {
bool res = intro.playFrame(true, NULL); // Mainkan frame GIF
FastLED.show();
if (res == 0) { break; }
yield();
}
}
// Fungsi loop
void loop() {
waveRun(50,25);
// fill_solid(leds, NUM_LEDS, CRGB::Black);
// runSein( 0, 30);
// FastLED.show();
// int delayMilliseconds = 100; // Atur waktu delay untuk frame, sesuaikan dengan kecepatan animasi
// Menjalankan GIF dan menggambar setiap frame ke WS2812 Matrix
// gif.playFrame(true, &delayMilliseconds); // Mainkan frame GIF
FastLED.show(); // Tampilkan hasil di WS2812 Matrix
// delay(delayMilliseconds); // Delay antar frame
}
void setLedMatrixHue(int x, int y, uint8_t hw, uint8_t value) {
int hueOffset = 0;
if (x < displayWidth) {
int xModulo = x % displayWidth; // x akan diulang dari 0 sampai displayWidth
// Tentukan segment dan intervalnya
int segment = xModulo / 5; // Bagi xModulo dengan 5 untuk mendapatkan segment
int segmentStart = segment * 5; // Mulai interval segment
int segmentEnd = segmentStart + 5; // Akhir interval segment
// Tentukan hueOffset dengan map dan segment
if (segment == 0) {
hueOffset = (hw + map(xModulo, segmentStart, segmentEnd, 0, 50)) % 256;
} else if (segment == 1) {
hueOffset = (hw + map(xModulo, segmentStart, segmentEnd, 50, 85)) % 256;
} else if (segment == 2) {
hueOffset = (hw + map(xModulo, segmentStart, segmentEnd, 85, 170)) % 256;
} else if (segment == 3) {
hueOffset = (hw + map(xModulo, segmentStart, segmentEnd, 170, 255)) % 256;
}
leds[XY(x, y)] = CHSV(hueOffset, 255, value);
} else {
int xModulo = x % displayWidth; // x akan diulang dari 0 sampai displayWidth
// Tentukan segment dan intervalnya
int segment = xModulo / 5; // Bagi xModulo dengan 5 untuk mendapatkan segment
int segmentStart = segment * 5; // Mulai interval segment
int segmentEnd = segmentStart + 5; // Akhir interval segment
// Tentukan hueOffset dengan map dan segment
if (segment == 0) {
hueOffset = (hw + map(xModulo, segmentStart, segmentEnd, 0, 50)) % 256;
} else if (segment == 1) {
hueOffset = (hw + map(xModulo, segmentStart, segmentEnd, 50, 85)) % 256;
} else if (segment == 2) {
hueOffset = (hw + map(xModulo, segmentStart, segmentEnd, 85, 170)) % 256;
} else if (segment == 3) {
hueOffset = (hw + map(xModulo, segmentStart, segmentEnd, 170, 255)) % 256;
}
ledsR[XY(x - displayWidth, y)] = CHSV(hueOffset, 255, value);
}
}
void setLedMatrix(int x, int y, CRGB colorMode) {
if (x < displayWidth) {
if (XY(x, y) != -1) { leds[XY(x, y)] = colorMode; }
} else {
if (XY(x - displayWidth, y) != -1) { ledsR[XY(x - displayWidth, y)] = colorMode; }
}
}
void setLedMatrix(int x, int y, CRGB colorMode,CRGB* led) {
if (XY(x, y) != -1) { led[XY(x, y)] = colorMode; }
}
void runSein(uint8_t arah, int spd) {
const bool sampleSein1[14][14]{
{ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 },
{ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 },
{ 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0 },
{ 1, 0, 0, 0, 1, 1, 0, 0, 0, 1, 1, 0, 0, 0 },
{ 1, 1, 0, 0, 0, 1, 1, 0, 0, 0, 1, 1, 0, 0 },
{ 0, 1, 1, 0, 0, 0, 1, 1, 0, 0, 0, 1, 1, 0 },
{ 0, 0, 1, 1, 0, 0, 0, 1, 1, 0, 0, 0, 1, 1 },
{ 0, 0, 1, 1, 0, 0, 0, 1, 1, 0, 0, 0, 1, 1 },
{ 0, 1, 1, 0, 0, 0, 1, 1, 0, 0, 0, 1, 1, 0 },
{ 1, 1, 0, 0, 0, 1, 1, 0, 0, 0, 1, 1, 0, 0 },
{ 1, 0, 0, 0, 1, 1, 0, 0, 0, 1, 1, 0, 0, 0 },
{ 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0 },
{ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 },
{ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 },
};
static unsigned long prevRunSein = 0;
static int offsetRunSein = 0;
if (arah == 0) {
fill_solid(leds, NUM_LEDS, CRGB::Black);
} else if (arah == 1) {
fill_solid(ledsR, NUM_LEDS, CRGB::Black);
}
if (arah == 2) {
for (int x = 0; x < 14; x++) {
for (int y = 0; y < 14; y++) {
if (x - 14 + offsetRunSein > 4) {
if (sampleSein1[y][x] == 1) {
setLedMatrix(x - 14 + offsetRunSein, y, CRGB::Yellow);
} else {
// setLedMatrix(x - 14 + offsetRunSein, y, CRGB::Black);
}
}
if (x + displayWidth - offsetRunSein < 5) {
if (sampleSein1[y][13 - x] == 1) {
setLedMatrix(x + displayWidth - offsetRunSein, y, CRGB::Yellow);
} else {
// setLedMatrix(x + displayWidth - offsetRunSein, y, CRGB::Black);
}
}
}
}
} else {
for (int x = 0; x < 14; x++) {
for (int y = 0; y < 14; y++) {
if (arah == 1) {
if (sampleSein1[y][x] == 1) {
setLedMatrix(x - 14 + offsetRunSein, y, CRGB::Yellow,ledsR);
} else {
// setLedMatrix(x - 14 + offsetRunSein, y, CRGB::Black);
}
} else if (arah == 0) {
if (sampleSein1[y][13 - x] == 1) {
setLedMatrix(x + displayWidth - offsetRunSein, y, CRGB::Yellow,leds);
} else {
// setLedMatrix(x + displayWidth - offsetRunSein, y, CRGB::Black);
}
}
}
}
}
//FastLED.show();
if (millis() - prevRunSein > spd) {
prevRunSein = millis();
offsetRunSein++;
if (offsetRunSein > displayWidth + 14) { offsetRunSein = 0; }
}
}