Skip to content

Instantly share code, notes, and snippets.

@imliubo
Last active September 22, 2024 12:30
Show Gist options
  • Save imliubo/5227a7df4f1b8b08663b4afb4f294989 to your computer and use it in GitHub Desktop.
Save imliubo/5227a7df4f1b8b08663b4afb4f294989 to your computer and use it in GitHub Desktop.
M5Stack Puzzle Demo
#include <Arduino.h>
#include "M5Unified.h"
#include "unit_puzzle.hpp"
static void plasma_mode(bool en_mask);
static void rainbow_mode(bool en_mask);
static void rainbow_move_mode(int hue_offset, bool en_mask);
M5Canvas canvas(&M5.Lcd);
M5Canvas canvas_m5_mask(&M5.Lcd);
UnitPuzzle unit_puzzle(7, 1, 0.05);
// ATOMS3 and Unit Puzzle Demo code
void setup()
{
M5.begin();
M5.Lcd.begin();
M5.Lcd.setColorDepth(24);
if (!unit_puzzle.init()) {
M5.Lcd.setTextColor(RED);
M5.Lcd.printf("Failed to initialize the unit puzzle.\r\n");
while (1) {
delay(100);
}
}
canvas.setColorDepth(24);
canvas.setFont(&fonts::Font0);
canvas.setTextWrap(false);
canvas.createSprite(unit_puzzle.width(), 8);
canvas_m5_mask.setColorDepth(1);
canvas_m5_mask.setFont(&fonts::Font0);
canvas_m5_mask.setTextWrap(false);
canvas_m5_mask.createSprite(unit_puzzle.width(), 8);
canvas_m5_mask.drawChar(0 + 1, 0, 'M', BLACK, WHITE, 1);
canvas_m5_mask.drawChar(8 + 1, 0, '5', BLACK, WHITE, 1);
canvas_m5_mask.drawChar(16 + 1, 0, 'S', BLACK, WHITE, 1);
canvas_m5_mask.drawChar(24 + 1, 0, 'T', BLACK, WHITE, 1);
canvas_m5_mask.drawChar(32 + 1, 0, 'A', BLACK, WHITE, 1);
canvas_m5_mask.drawChar(40 + 1, 0, 'C', BLACK, WHITE, 1);
canvas_m5_mask.drawChar(48 + 1, 0, 'K', BLACK, WHITE, 1);
M5.Lcd.printf("Puzzle width() = %d\r\n", unit_puzzle.width());
M5.Lcd.printf("Puzzle height() = %d\r\n", unit_puzzle.height());
M5.Lcd.printf("Canvas width() = %d\r\n", canvas.width());
M5.Lcd.printf("Canvas height() = %d\r\n", canvas.height());
M5.Lcd.drawRect(((M5.Lcd.width() - unit_puzzle.width()) / 2 - 1),
((M5.Lcd.height() - unit_puzzle.height()) / 2 - 1), unit_puzzle.width() + 2,
unit_puzzle.height() + 2, WHITE);
}
static int hueOffset = 0;
static int modeCount = 0;
void loop()
{
switch (modeCount) {
case 0:
plasma_mode(false);
break;
case 1:
plasma_mode(true);
break;
case 2:
rainbow_mode(false);
break;
case 3:
rainbow_mode(true);
break;
case 4:
rainbow_move_mode(hueOffset, false);
hueOffset += 1;
if (hueOffset >= unit_puzzle.width()) {
hueOffset = 0;
}
break;
case 5:
rainbow_move_mode(hueOffset, true);
hueOffset += 1;
if (hueOffset >= unit_puzzle.width()) {
hueOffset = 0;
}
break;
default:
break;
}
canvas.pushSprite(&M5.Lcd, (M5.Lcd.width() - unit_puzzle.width()) / 2,
(M5.Lcd.height() - unit_puzzle.height()) / 2);
unit_puzzle.pushRGB(&canvas);
M5.update();
if (M5.BtnA.wasClicked()) {
modeCount++;
if (modeCount > 5) {
modeCount = 0;
}
canvas.clear();
}
}
static void rainbow_mode(bool en_mask)
{
for (int x = 0; x < unit_puzzle.width(); ++x) {
long hue = (1536L * x) / unit_puzzle.width();
for (int y = 0; y < unit_puzzle.height(); ++y) {
if (en_mask) {
if (canvas_m5_mask.readPixelValue(x, y) > 0) {
canvas.drawPixel(x, y, unit_puzzle.ColorHSV(hue, 255, 255, true));
}
} else {
canvas.drawPixel(x, y, unit_puzzle.ColorHSV(hue, 255, 255, true));
}
}
}
}
static void rainbow_move_mode(int hue_offset, bool en_mask)
{
for (int x = 0; x < unit_puzzle.width(); ++x) {
long hue = (1536L * (x + hue_offset)) / unit_puzzle.width();
for (int y = 0; y < unit_puzzle.height(); ++y) {
if (en_mask) {
if (canvas_m5_mask.readPixelValue(x, y) > 0) {
canvas.drawPixel(x, y, unit_puzzle.ColorHSV(hue, 255, 255, true));
}
} else {
canvas.drawPixel(x, y, unit_puzzle.ColorHSV(hue, 255, 255, true));
}
}
}
}
#define FPS 15 // Maximum frames-per-second
static const int8_t PROGMEM sinetab[256] = {
0, 2, 5, 8, 11, 15, 18, 21, 24, 27, 30, 33, 36, 39, 42, 45, 48, 51, 54,
56, 59, 62, 65, 67, 70, 72, 75, 77, 80, 82, 85, 87, 89, 91, 93, 96, 98, 100,
101, 103, 105, 107, 108, 110, 111, 113, 114, 116, 117, 118, 119, 120, 121, 122, 123, 123, 124,
125, 125, 126, 126, 126, 126, 126, 127, 126, 126, 126, 126, 126, 125, 125, 124, 123, 123, 122,
121, 120, 119, 118, 117, 116, 114, 113, 111, 110, 108, 107, 105, 103, 101, 100, 98, 96, 93,
91, 89, 87, 85, 82, 80, 77, 75, 72, 70, 67, 65, 62, 59, 56, 54, 51, 48, 45,
42, 39, 36, 33, 30, 27, 24, 21, 18, 15, 11, 8, 5, 2, 0, -3, -6, -9, -12,
-16, -19, -22, -25, -28, -31, -34, -37, -40, -43, -46, -49, -52, -55, -57, -60, -63, -66, -68,
-71, -73, -76, -78, -81, -83, -86, -88, -90, -92, -94, -97, -99, -101, -102, -104, -106, -108, -109,
-111, -112, -114, -115, -117, -118, -119, -120, -121, -122, -123, -124, -124, -125, -126, -126, -127, -127, -127,
-127, -127, -128, -127, -127, -127, -127, -127, -126, -126, -125, -124, -124, -123, -122, -121, -120, -119, -118,
-117, -115, -114, -112, -111, -109, -108, -106, -104, -102, -101, -99, -97, -94, -92, -90, -88, -86, -83,
-81, -78, -76, -73, -71, -68, -66, -63, -60, -57, -55, -52, -49, -46, -43, -40, -37, -34, -31,
-28, -25, -22, -19, -16, -12, -9, -6, -3};
static const float radius1 = 16.3, radius2 = 23.0, radius3 = 40.8, radius4 = 44.2, centerx1 = 16.1, centerx2 = 11.6,
centerx3 = 23.4, centerx4 = 4.1, centery1 = 8.7, centery2 = 6.5, centery3 = 14.0, centery4 = -2.9;
static float angle1 = 0.0, angle2 = 0.0, angle3 = 0.0, angle4 = 0.0;
static long hueShift = 0;
static uint32_t prevTime = 0;
static void plasma_mode(bool en_mask)
{
int x1, x2, x3, x4, y1, y2, y3, y4, sx1, sx2, sx3, sx4;
unsigned char x, y;
long value;
// To ensure that animation speed is similar on AVR & SAMD boards,
// limit frame rate to FPS value (might go slower, but never faster).
// This is preferable to delay() because the AVR is already plenty slow.
uint32_t t;
while (((t = millis()) - prevTime) < (1000 / FPS));
prevTime = t;
sx1 = (int)(cos(angle1) * radius1 + centerx1);
sx2 = (int)(cos(angle2) * radius2 + centerx2);
sx3 = (int)(cos(angle3) * radius3 + centerx3);
sx4 = (int)(cos(angle4) * radius4 + centerx4);
y1 = (int)(sin(angle1) * radius1 + centery1);
y2 = (int)(sin(angle2) * radius2 + centery2);
y3 = (int)(sin(angle3) * radius3 + centery3);
y4 = (int)(sin(angle4) * radius4 + centery4);
for (y = 0; y < unit_puzzle.height(); y++) {
x1 = sx1;
x2 = sx2;
x3 = sx3;
x4 = sx4;
for (x = 0; x < unit_puzzle.width(); x++) {
value = hueShift + (int8_t)pgm_read_byte(sinetab + (uint8_t)((x1 * x1 + y1 * y1) >> 2)) +
(int8_t)pgm_read_byte(sinetab + (uint8_t)((x2 * x2 + y2 * y2) >> 2)) +
(int8_t)pgm_read_byte(sinetab + (uint8_t)((x3 * x3 + y3 * y3) >> 3)) +
(int8_t)pgm_read_byte(sinetab + (uint8_t)((x4 * x4 + y4 * y4) >> 3));
if (en_mask) {
if (canvas_m5_mask.readPixelValue(x, y) > 0) {
canvas.drawPixel(x, y, unit_puzzle.ColorHSV(value * 3, 255, 255, true));
}
} else {
canvas.drawPixel(x, y, unit_puzzle.ColorHSV(value * 3, 255, 255, true));
}
x1--;
x2--;
x3--;
x4--;
}
y1--;
y2--;
y3--;
y4--;
}
angle1 += 0.03;
angle2 -= 0.07;
angle3 += 0.13;
angle4 -= 0.15;
hueShift += 2;
}
#include "FastLED.h"
#include "lgfx/v1/LGFX_Sprite.hpp"
#include "M5GFX.h"
#define UNIT_PUZZLE_DATA_PIN 2
#define UNIT_PUZZLE_SINGLE_ROW 8
#define UNIT_PUZZLE_SINGLE_COL 8
#define UNIT_PUZZLE_SINGLE_NUM (UNIT_PUZZLE_SINGLE_ROW * UNIT_PUZZLE_SINGLE_COL)
class UnitPuzzle {
public:
UnitPuzzle(int row, int col, float scale = 0.1, int order = 0)
{
if (row <= 0 || col <= 0) {
return;
}
this->data.row = row;
this->data.col = col;
this->data.scale = scale;
this->data.width = row * UNIT_PUZZLE_SINGLE_ROW;
this->data.height = col * UNIT_PUZZLE_SINGLE_COL;
this->data.rgb_total_num = data.col * data.row * UNIT_PUZZLE_SINGLE_NUM;
}
~UnitPuzzle()
{
if (this->data.rgb_buffer != nullptr) {
delete[] this->data.rgb_buffer;
}
}
bool init()
{
// Allocate memory for the RGB buffer
this->data.rgb_buffer = new CRGB[this->data.row * this->data.col * UNIT_PUZZLE_SINGLE_NUM];
if (this->data.rgb_buffer == nullptr) {
return false;
}
memset(this->data.rgb_buffer, 0, sizeof(CRGB) * this->data.rgb_total_num);
FastLED.addLeds<SK6812, UNIT_PUZZLE_DATA_PIN, GRB>(this->data.rgb_buffer, this->data.rgb_total_num);
FastLED.clear();
FastLED.show();
return true;
}
void setScale(float scale)
{
this->data.scale = scale;
}
CRGB* getRgbBuffer()
{
return this->data.rgb_buffer;
}
int width()
{
return this->data.width;
}
int height()
{
return this->data.height;
}
float scale()
{
return this->data.scale;
}
uint32_t ColorHSV(long hue, uint8_t sat, uint8_t val, boolean gflag)
{
uint8_t r, g, b, lo;
uint16_t s1, v1;
// Hue
hue %= 1536; // -1535 to +1535
if (hue < 0) hue += 1536; // 0 to +1535
lo = hue & 255; // Low byte = primary/secondary color mix
switch (hue >> 8) { // High byte = sextant of colorwheel
case 0:
r = 255;
g = lo;
b = 0;
break; // R to Y
case 1:
r = 255 - lo;
g = 255;
b = 0;
break; // Y to G
case 2:
r = 0;
g = 255;
b = lo;
break; // G to C
case 3:
r = 0;
g = 255 - lo;
b = 255;
break; // C to B
case 4:
r = lo;
g = 0;
b = 255;
break; // B to M
default:
r = 255;
g = 0;
b = 255 - lo;
break; // M to R
}
// Saturation: add 1 so range is 1 to 256, allowig a quick shift operation
// on the result rather than a costly divide, while the type upgrade to int
// avoids repeated type conversions in both directions.
s1 = sat + 1;
r = 255 - (((255 - r) * s1) >> 8);
g = 255 - (((255 - g) * s1) >> 8);
b = 255 - (((255 - b) * s1) >> 8);
return (r << 16) | (g << 8) | b;
}
// clang-format off
/*
RGB Buffer Layout (total 7 puzzle):
007 015 023 031 039 047 055 063 | 007 015 023 031 039 047 055 063 | 007 015 023 031 039 047 055 063 | 007 015 023 031 039 047 055 063
006 014 022 030 038 046 054 062 | 006 014 022 030 038 046 054 062 | 006 014 022 030 038 046 054 062 | 006 014 022 030 038 046 054 062
005 013 021 029 037 045 053 061 | 005 013 021 029 037 045 053 061 | 005 013 021 029 037 045 053 061 | 005 013 021 029 037 045 053 061
004 012 020 028 036 044 052 060 | 004 012 020 028 036 044 052 060 | 004 012 020 028 036 044 052 060 | 004 012 020 028 036 044 052 060
003 011 019 027 035 043 051 059 | 003 011 019 027 035 043 051 059 | 003 011 019 027 035 043 051 059 | 003 011 019 027 035 043 051 059
002 010 018 026 034 042 050 058 | 002 010 018 026 034 042 050 058 | 002 010 018 026 034 042 050 058 | 002 010 018 026 034 042 050 058
001 009 017 025 033 041 049 057 | 001 009 017 025 033 041 049 057 | 001 009 017 025 033 041 049 057 | 001 009 017 025 033 041 049 057
000 008 016 024 032 040 048 056 | 000 008 016 024 032 040 048 056 | 000 008 016 024 032 040 048 056 | 000 008 016 024 032 040 048 056
Sprite Buffer Layout:
Y X ->000 001 002 003 004 005 006 007 | 008 009 010 011 012 013 014 015 | 016 017 018 019 020 021 022 023 | 024 025 026 027 028 029 030 031
0 000 001 002 003 004 005 006 007 | 008 009 010 011 012 013 014 015 | 016 017 018 019 020 021 022 023 | 024 025 026 027 028 029 030 031
1 032 033 034 035 036 037 038 039 | 040 041 042 043 044 045 046 047 | 048 049 050 051 052 053 054 055 | 056 057 058 059 060 061 062 063
2 064 065 066 067 068 069 070 071 | 072 073 074 075 076 077 078 079 | 080 081 082 083 084 085 086 087 | 088 089 090 091 092 093 094 095
3 096 097 098 099 100 101 102 103 | 104 105 106 107 108 109 110 111 | 112 113 114 115 116 117 118 119 | 120 121 122 123 124 125 126 127
4 128 129 130 131 132 133 134 135 | 136 137 138 139 140 141 142 143 | 144 145 146 147 148 149 150 151 | 152 153 154 155 156 157 158 159
5 160 161 162 163 164 165 166 167 | 168 169 170 171 172 173 174 175 | 176 177 178 179 180 181 182 183 | 184 185 186 187 188 189 190 191
6 192 193 194 195 196 197 198 199 | 200 201 202 203 204 205 206 207 | 208 209 210 211 212 213 214 215 | 216 217 218 219 220 221 222 223
7 224 225 226 227 228 229 230 231 | 232 233 234 235 236 237 238 239 | 240 241 242 243 244 245 246 247 | 248 249 250 251 252 253 254 255
*/
// clang-format on
void pushRGB(M5Canvas* canvas = nullptr)
{
if (canvas == nullptr) {
return;
}
// Copy the RGB data from the canvas to the RGB buffer
for (int y = 0; y < this->height(); y++) {
for (int x = 0; x < this->width(); x++) {
int index = (UNIT_PUZZLE_SINGLE_ROW - y - 1) + (x * UNIT_PUZZLE_SINGLE_COL);
RGBColor color = canvas->readPixelRGB(x, y);
this->getRgbBuffer()[index] =
CRGB(color.r * this->scale(), color.g * this->scale(), color.b * this->scale());
}
}
FastLED.show();
}
private:
struct Data_t {
uint16_t row = 0;
uint16_t col = 0;
uint16_t width = 0;
uint16_t height = 0;
int rgb_total_num = 0;
float scale = 1.0;
CRGB* rgb_buffer = nullptr;
};
Data_t data;
};
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment