#include <Adafruit_GFX.h>
#include <Adafruit_NeoMatrix.h>
#include <Adafruit_NeoPixel.h>
// Declaração de funções (protótipos)
void updateScrollingText();
void updateStarrySky();
void updatePartyMode();
void updateRGBOutline();
void updateFunIcons();
// Definição dos pinos
#define PIN 25 // Pino de dados do painel WS2812B
#define POT_PIN 34 // Pino do potenciômetro
#define BUTTON_PIN 21 // Pino do botão de modos de cor
#define BUTTON2_PIN 22 // Pino do segundo botão para estados
// Dimensões reais do seu display
#define WIDTH 32
#define HEIGHT 8
// Variáveis globais
int brightness = 100; // Brilho inicial (ajustável pelo potenciômetro)
String message = "GamboTECH"; // Texto a ser exibido
int16_t scrollX = WIDTH; // Posição inicial do texto
unsigned long lastScroll = 0;
const int scrollSpeed = 85; // Tempo entre deslocamentos (ms)
byte colorIndex = 0; // Índice para transição de cores (arco-íris)
int effectMode = 1; // Modo de efeito: 1-10 (começa no Modo 1: Arco-íris)
int stateMode = 1; // Estado atual: 1-5 (começa no Estado 1: Texto rolando)
// Variáveis para os botões
int button1State = HIGH; // Estado atual do botão 1
int lastButton1State = HIGH; // Estado anterior do botão 1
int button2State = HIGH; // Estado atual do botão 2
int lastButton2State = HIGH; // Estado anterior do botão 2
unsigned long lastDebounceTime1 = 0; // Tempo do último debounce botão 1
unsigned long lastDebounceTime2 = 0; // Tempo do último debounce botão 2
const unsigned long debounceDelay = 100; // Delay para debounce (ms)
// Variáveis para efeitos especiais
unsigned long lastEffectUpdate = 0;
const int effectSpeed = 100; // Velocidade dos efeitos especiais
int animationStep = 0; // Passo da animação para efeitos
int starCount = 20; // Número de estrelas no céu estrelado
int partyMode = 0; // Submodo para o modo balada
int iconIndex = 0; // Índice do ícone atual
// Inicializa a matriz de LED
Adafruit_NeoMatrix matrix = Adafruit_NeoMatrix(
WIDTH, HEIGHT, PIN,
NEO_MATRIX_TOP + NEO_MATRIX_LEFT + NEO_MATRIX_COLUMNS + NEO_MATRIX_ZIGZAG,
NEO_GRB + NEO_KHZ800
);
// Bitmap do aviãozinho (8x16 pixels)
const uint8_t airplaneBitmap[8][16] = {
{0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0},
{0, 0, 0, 1, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0},
{0, 0, 0, 1, 1, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0},
{0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1},
{0, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0},
{0, 0, 0, 1, 1, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0},
{0, 0, 0, 1, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0},
{0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}
};
// Ícones divertidos (8x8 pixels)
const uint8_t smileyBitmap[8][8] = {
{0, 0, 1, 1, 1, 1, 0, 0},
{0, 1, 0, 0, 0, 0, 1, 0},
{1, 0, 1, 0, 0, 1, 0, 1},
{1, 0, 0, 0, 0, 0, 0, 1},
{1, 0, 1, 0, 0, 1, 0, 1},
{1, 0, 0, 1, 1, 0, 0, 1},
{0, 1, 0, 0, 0, 0, 1, 0},
{0, 0, 1, 1, 1, 1, 0, 0}
};
const uint8_t heartBitmap[8][8] = {
{0, 1, 1, 0, 0, 1, 1, 0},
{1, 1, 1, 1, 1, 1, 1, 1},
{1, 1, 1, 1, 1, 1, 1, 1},
{1, 1, 1, 1, 1, 1, 1, 1},
{0, 1, 1, 1, 1, 1, 1, 0},
{0, 0, 1, 1, 1, 1, 0, 0},
{0, 0, 0, 1, 1, 0, 0, 0},
{0, 0, 0, 0, 0, 0, 0, 0}
};
const uint8_t pacmanBitmap[8][8] = {
{0, 0, 1, 1, 1, 1, 0, 0},
{0, 1, 1, 1, 1, 1, 1, 0},
{1, 1, 1, 1, 1, 0, 0, 0},
{1, 1, 1, 1, 0, 0, 0, 0},
{1, 1, 1, 1, 0, 0, 0, 0},
{1, 1, 1, 1, 1, 0, 0, 0},
{0, 1, 1, 1, 1, 1, 1, 0},
{0, 0, 1, 1, 1, 1, 0, 0}
};
const uint8_t alienBitmap[8][8] = {
{0, 0, 1, 0, 0, 1, 0, 0},
{0, 0, 0, 1, 1, 0, 0, 0},
{0, 0, 1, 1, 1, 1, 0, 0},
{0, 1, 0, 1, 1, 0, 1, 0},
{1, 1, 1, 1, 1, 1, 1, 1},
{0, 0, 1, 0, 0, 1, 0, 0},
{0, 1, 0, 0, 0, 0, 1, 0},
{1, 0, 0, 0, 0, 0, 0, 1}
};
const uint8_t starBitmap[8][8] = {
{0, 0, 0, 1, 0, 0, 0, 0},
{0, 0, 0, 1, 0, 0, 0, 0},
{0, 0, 1, 1, 1, 0, 0, 0},
{1, 1, 1, 1, 1, 1, 1, 0},
{0, 1, 1, 1, 1, 1, 0, 0},
{0, 0, 1, 1, 1, 0, 0, 0},
{0, 0, 1, 0, 1, 0, 0, 0},
{0, 1, 0, 0, 0, 1, 0, 0}
};
// Array de ícones (ponteiros para os bitmaps)
const uint8_t* iconBitmaps[5] = {
&smileyBitmap[0][0],
&heartBitmap[0][0],
&pacmanBitmap[0][0],
&alienBitmap[0][0],
&starBitmap[0][0]
};
// Função para gerar cores do arco-íris
uint32_t Wheel(byte WheelPos) {
WheelPos = 255 - WheelPos;
if(WheelPos < 85) {
return matrix.Color(255 - WheelPos * 3, 0, WheelPos * 3);
}
if(WheelPos < 170) {
WheelPos -= 85;
return matrix.Color(0, WheelPos * 3, 255 - WheelPos * 3);
}
WheelPos -= 170;
return matrix.Color(WheelPos * 3, 255 - WheelPos * 3, 0);
}
// Array de cores para os diferentes modos
uint32_t modeColors[10]; // Será inicializado no setup
// Array de nomes para os diferentes modos
const char* modeNames[] = {
"Arco-íris",
"Azul",
"Vermelho",
"Verde",
"Amarelo",
"Ciano",
"Magenta",
"Laranja",
"Roxo",
"Branco"
};
// Array de nomes para os diferentes estados
const char* stateNames[] = {
"Texto Rolando",
"Céu Estrelado",
"Modo Balada",
"Minhoca",
"Ícones Divertidos"
};
// Função para desenhar o bitmap do aviãozinho
void drawAirplane(int16_t x, int16_t y, uint32_t color) {
for (int row = 0; row < 8; row++) {
for (int col = 0; col < 16; col++) {
if (airplaneBitmap[row][col]) {
matrix.drawPixel(x + col, y + row, color);
}
}
}
}
// Função para desenhar um ícone
void drawIcon(const uint8_t* iconData, int16_t x, int16_t y, uint32_t color) {
for (int row = 0; row < 8; row++) {
for (int col = 0; col < 8; col++) {
if (*(iconData + row * 8 + col)) {
matrix.drawPixel(x + col, y + row, color);
}
}
}
}
// Função para atualizar o estado do botão 1 com debounce
bool checkButton1Press() {
int reading = digitalRead(BUTTON_PIN);
bool buttonPressed = false;
if (reading != lastButton1State) {
lastDebounceTime1 = millis();
}
if ((millis() - lastDebounceTime1) > debounceDelay) {
if (reading != button1State) {
button1State = reading;
if (button1State == LOW) {
buttonPressed = true;
}
}
}
lastButton1State = reading;
return buttonPressed;
}
// Função para atualizar o estado do botão 2 com debounce
bool checkButton2Press() {
int reading = digitalRead(BUTTON2_PIN);
bool buttonPressed = false;
if (reading != lastButton2State) {
lastDebounceTime2 = millis();
}
if ((millis() - lastDebounceTime2) > debounceDelay) {
if (reading != button2State) {
button2State = reading;
if (button2State == LOW) {
buttonPressed = true;
}
}
}
lastButton2State = reading;
return buttonPressed;
}
void setup() {
Serial.begin(115200);
Serial.println("Iniciando setup...");
// Configura os pinos dos botões com resistor pull-up interno
pinMode(BUTTON_PIN, INPUT_PULLUP);
pinMode(BUTTON2_PIN, INPUT_PULLUP);
// Inicializa a matriz de LEDs
matrix.begin();
matrix.setTextWrap(false);
matrix.setBrightness(brightness);
matrix.fillScreen(0);
matrix.show();
Serial.println("Matrix inicializada");
// Agora que a matriz foi inicializada, podemos definir as cores
modeColors[0] = 0; // Modo 1: 0 indica arco-íris (tratado de forma especial)
modeColors[1] = matrix.Color(0, 0, 255); // Azul
modeColors[2] = matrix.Color(255, 0, 0); // Vermelho
modeColors[3] = matrix.Color(0, 255, 0); // Verde
modeColors[4] = matrix.Color(255, 255, 0); // Amarelo
modeColors[5] = matrix.Color(0, 255, 255); // Ciano
modeColors[6] = matrix.Color(255, 0, 255); // Magenta
modeColors[7] = matrix.Color(255, 165, 0); // Laranja
modeColors[8] = matrix.Color(128, 0, 128); // Roxo
modeColors[9] = matrix.Color(255, 255, 255); // Branco
Serial.println("Cores definidas");
// Inicializa com o modo 1 e estado 1
effectMode = 1;
stateMode = 1;
Serial.print("Modo inicial: ");
Serial.println(modeNames[effectMode - 1]);
Serial.print("Estado inicial: ");
Serial.println(stateNames[stateMode - 1]);
delay(1000); // Delay para estabilização
}
void loop() {
// 1. Verifica se o botão 1 foi pressionado (cicla pelos modos de cor)
if (checkButton1Press()) {
effectMode++;
if (effectMode > 10) {
effectMode = 1;
}
Serial.print("Botão 1 pressionado! Novo modo: ");
Serial.print(effectMode);
Serial.print(" (");
Serial.print(modeNames[effectMode - 1]);
Serial.println(")");
// Reinicia algumas variáveis ao mudar de modo
colorIndex = 0;
matrix.fillScreen(0);
matrix.show();
delay(200); // Pequeno delay para estabilização
}
// 2. Verifica se o botão 2 foi pressionado (cicla pelos estados)
if (checkButton2Press()) {
stateMode++;
if (stateMode > 5) {
stateMode = 1;
}
Serial.print("Botão 2 pressionado! Novo estado: ");
Serial.print(stateMode);
Serial.print(" (");
Serial.print(stateNames[stateMode - 1]);
Serial.println(")");
// Reinicia as variáveis de animação ao mudar de estado
animationStep = 0;
partyMode = 0;
matrix.fillScreen(0);
matrix.show();
delay(200); // Pequeno delay para estabilização
}
// 3. Atualiza o brilho com base no potenciômetro
int potValue = analogRead(POT_PIN);
brightness = map(potValue, 0, 4095, 1, 100);
matrix.setBrightness(brightness);
// 4. Executa o efeito correspondente ao estado atual
switch (stateMode) {
case 1:
// Estado 1: Texto rolando (funcionalidade original)
updateScrollingText();
break;
case 2:
// Estado 2: Céu estrelado
updateStarrySky();
break;
case 3:
// Estado 3: Modo balada
updatePartyMode();
break;
case 4:
// Estado 4: Contorno RGB
updateRGBOutline();
break;
case 5:
// Estado 5: Ícones divertidos
updateFunIcons();
break;
}
}
// Estado 1: Texto rolando (funcionalidade original)
void updateScrollingText() {
unsigned long now = millis();
if (now - lastScroll >= scrollSpeed) {
// Limpa a tela antes de desenhar
matrix.fillScreen(0);
// Define posições
int16_t airplaneX = scrollX;
int16_t textX = scrollX + 16; // Espaço após o avião
// Modo arco-íris
if (effectMode == 1) {
// Desenha o avião com cor do arco-íris
drawAirplane(airplaneX, 0, Wheel(colorIndex));
// Desenha cada caractere do texto com sua própria cor
for (int i = 0; i < message.length(); i++) {
matrix.setCursor(textX + (i * 6), 0);
matrix.setTextColor(Wheel((colorIndex + (i * 32)) % 256));
matrix.print(message[i]);
}
// Avança o índice de cor para o efeito arco-íris em movimento
colorIndex = (colorIndex + 5) % 256;
}
// Modos de cor fixa
else {
// Obtém a cor do modo atual
uint32_t currentColor = modeColors[effectMode - 1];
// Desenha o avião e o texto com a mesma cor
drawAirplane(airplaneX, 0, currentColor);
matrix.setCursor(textX, 0);
matrix.setTextColor(currentColor);
matrix.print(message);
}
// Atualiza o display
matrix.show();
// Move o texto para a esquerda
scrollX--;
// Recomeça quando sair da tela
int textWidth = (message.length() * 6) + 16; // Comprimento do texto + avião
if (scrollX < -textWidth) {
scrollX = WIDTH;
}
lastScroll = now;
}
}
// Estado 2: Céu estrelado
void updateStarrySky() {
unsigned long now = millis();
if (now - lastEffectUpdate >= effectSpeed) {
// Limpa a tela a cada 5 passos para criar o efeito de oscilação
if (animationStep % 5 == 0) {
matrix.fillScreen(0);
}
// Gera estrelas aleatórias
for (int i = 0; i < 3; i++) { // Adiciona 3 estrelas a cada atualização
int x = random(WIDTH);
int y = random(HEIGHT);
uint32_t starColor;
if (effectMode == 1) {
// No modo arco-íris, cada estrela tem uma cor diferente
starColor = Wheel(random(256));
} else {
// Usa a cor do modo atual
starColor = modeColors[effectMode - 1];
}
// Intensidade aleatória (brilho)
int intensity = random(50, 255);
uint8_t r = ((starColor >> 16) & 0xFF) * intensity / 255;
uint8_t g = ((starColor >> 8) & 0xFF) * intensity / 255;
uint8_t b = (starColor & 0xFF) * intensity / 255;
matrix.drawPixel(x, y, matrix.Color(r, g, b));
}
// Ocasionalmente adiciona uma "estrela cadente"
if (random(100) < 5) { // 5% de chance
int startX = random(WIDTH);
int startY = 0;
uint32_t meteorColor;
if (effectMode == 1) {
meteorColor = Wheel(random(256));
} else {
meteorColor = modeColors[effectMode - 1];
}
// Desenha a trilha da estrela cadente
for (int i = 0; i < 3; i++) {
int tailX = startX - i;
int tailY = startY + i;
if (tailX >= 0 && tailX < WIDTH && tailY >= 0 && tailY < HEIGHT) {
// Diminui o brilho com a distância
int fade = 255 - (i * 80);
uint8_t r = ((meteorColor >> 16) & 0xFF) * fade / 255;
uint8_t g = ((meteorColor >> 8) & 0xFF) * fade / 255;
uint8_t b = (meteorColor & 0xFF) * fade / 255;
matrix.drawPixel(tailX, tailY, matrix.Color(r, g, b));
}
}
}
matrix.show();
animationStep++;
lastEffectUpdate = now;
}
}
// Estado 3: Modo balada
void updatePartyMode() {
unsigned long now = millis();
if (now - lastEffectUpdate >= effectSpeed) {
// Alterna entre diferentes efeitos de festa
partyMode = (partyMode + 1) % 5;
matrix.fillScreen(0);
switch (partyMode) {
case 0:
// Efeito estroboscópico
if (animationStep % 2 == 0) {
if (effectMode == 1) {
// No modo arco-íris, alterna cores
matrix.fillScreen(Wheel(colorIndex));
colorIndex = (colorIndex + 40) % 256;
} else {
matrix.fillScreen(modeColors[effectMode - 1]);
}
}
break;
case 1:
// Linhas horizontais dançantes
for (int y = 0; y < HEIGHT; y++) {
if ((y + animationStep) % 3 == 0) {
for (int x = 0; x < WIDTH; x++) {
uint32_t lineColor;
if (effectMode == 1) {
lineColor = Wheel((x * 8 + colorIndex) % 256);
} else {
lineColor = modeColors[effectMode - 1];
}
matrix.drawPixel(x, y, lineColor);
}
}
}
if (effectMode == 1) {
colorIndex = (colorIndex + 10) % 256;
}
break;
case 2:
// Padrão xadrez pulsante
for (int y = 0; y < HEIGHT; y++) {
for (int x = 0; x < WIDTH; x++) {
if ((x + y + animationStep) % 2 == 0) {
uint32_t pixelColor;
if (effectMode == 1) {
pixelColor = Wheel((x * 8 + y * 32 + colorIndex) % 256);
} else {
pixelColor = modeColors[effectMode - 1];
}
matrix.drawPixel(x, y, pixelColor);
}
}
}
if (effectMode == 1) {
colorIndex = (colorIndex + 15) % 256;
}
break;
case 3:
// Barras verticais movendo-se
for (int x = 0; x < WIDTH; x++) {
if ((x + animationStep) % 4 < 2) {
for (int y = 0; y < HEIGHT; y++) {
uint32_t barColor;
if (effectMode == 1) {
barColor = Wheel((x * 8 + colorIndex) % 256);
} else {
barColor = modeColors[effectMode - 1];
}
matrix.drawPixel(x, y, barColor);
}
}
}
if (effectMode == 1) {
colorIndex = (colorIndex + 20) % 256;
}
break;
case 4:
// Explosão de pixels aleatórios
for (int i = 0; i < 20; i++) {
int x = random(WIDTH);
int y = random(HEIGHT);
uint32_t pixelColor;
if (effectMode == 1) {
pixelColor = Wheel(random(256));
} else {
pixelColor = modeColors[effectMode - 1];
}
matrix.drawPixel(x, y, pixelColor);
}
break;
}
matrix.show();
animationStep++;
lastEffectUpdate = now;
}
}
// Estado 4: Minhoca andando pelo display
void updateRGBOutline() {
unsigned long now = millis();
if (now - lastEffectUpdate >= effectSpeed) {
matrix.fillScreen(0);
// Comprimento da minhoca
const int wormLength = 8;
// Coordenadas dos segmentos da minhoca
int wormSegments[wormLength][2];
// Gerar os segmentos da minhoca com base no passo da animação
// A animationStep controla o movimento da cabeça da minhoca
int headPosition = animationStep % (WIDTH * HEIGHT);
int headX = headPosition % WIDTH;
int headY = headPosition / WIDTH;
// Gerar um movimento mais interessante (serpentear)
// A cada 32 passos, a minhoca muda de direção
int directionPhase = (animationStep / 32) % 4;
// Calcular posição da cabeça da minhoca
switch (directionPhase) {
case 0: // Movimento da esquerda para a direita
headX = animationStep % WIDTH;
headY = (animationStep / WIDTH) % HEIGHT;
break;
case 1: // Movimento de cima para baixo
headX = WIDTH - 1 - ((animationStep / HEIGHT) % WIDTH);
headY = animationStep % HEIGHT;
break;
case 2: // Movimento da direita para a esquerda
headX = WIDTH - 1 - (animationStep % WIDTH);
headY = HEIGHT - 1 - ((animationStep / WIDTH) % HEIGHT);
break;
case 3: // Movimento de baixo para cima
headX = (animationStep / HEIGHT) % WIDTH;
headY = HEIGHT - 1 - (animationStep % HEIGHT);
break;
}
// Definir cada segmento da minhoca
for (int i = 0; i < wormLength; i++) {
// Segmentos anteriores seguem a cabeça com atraso
int segmentPosition = (animationStep - i) % (WIDTH * HEIGHT);
if (segmentPosition < 0) segmentPosition += (WIDTH * HEIGHT);
// Calcular com base na fase de direção atual
int segX, segY;
switch (directionPhase) {
case 0:
segX = segmentPosition % WIDTH;
segY = (segmentPosition / WIDTH) % HEIGHT;
break;
case 1:
segX = WIDTH - 1 - ((segmentPosition / HEIGHT) % WIDTH);
segY = segmentPosition % HEIGHT;
break;
case 2:
segX = WIDTH - 1 - (segmentPosition % WIDTH);
segY = HEIGHT - 1 - ((segmentPosition / WIDTH) % HEIGHT);
break;
case 3:
segX = (segmentPosition / HEIGHT) % WIDTH;
segY = HEIGHT - 1 - (segmentPosition % HEIGHT);
break;
}
// Garantir que as coordenadas estejam dentro dos limites
segX = constrain(segX, 0, WIDTH - 1);
segY = constrain(segY, 0, HEIGHT - 1);
wormSegments[i][0] = segX;
wormSegments[i][1] = segY;
}
// Desenhar a minhoca
for (int i = 0; i < wormLength; i++) {
int x = wormSegments[i][0];
int y = wormSegments[i][1];
uint32_t segmentColor;
if (effectMode == 1) {
// No modo arco-íris, cada segmento tem uma cor diferente
segmentColor = Wheel((i * 256 / wormLength + colorIndex) % 256);
} else {
// Usa a cor do modo atual, com variação de intensidade
uint32_t baseColor = modeColors[effectMode - 1];
// A cabeça é mais brilhante, a cauda vai escurecendo
float intensity = 1.0 - (float)i / wormLength * 0.7;
uint8_t r = ((baseColor >> 16) & 0xFF) * intensity;
uint8_t g = ((baseColor >> 8) & 0xFF) * intensity;
uint8_t b = (baseColor & 0xFF) * intensity;
segmentColor = matrix.Color(r, g, b);
}
// Desenhar um pixel maior para a cabeça (2x2)
if (i == 0) {
matrix.drawPixel(x, y, segmentColor);
// Desenhar pixels adjacentes para a cabeça ficar maior, se estiverem dentro dos limites
if (x + 1 < WIDTH) matrix.drawPixel(x + 1, y, segmentColor);
if (y + 1 < HEIGHT) matrix.drawPixel(x, y + 1, segmentColor);
if (x + 1 < WIDTH && y + 1 < HEIGHT) matrix.drawPixel(x + 1, y + 1, segmentColor);
} else {
matrix.drawPixel(x, y, segmentColor);
}
}
if (effectMode == 1) {
colorIndex = (colorIndex + 5) % 256;
}
matrix.show();
animationStep = (animationStep + 1) % (WIDTH * HEIGHT * 4); // 4 fases de direção
lastEffectUpdate = now;
}
}
// Estado 5: Ícones divertidos
void updateFunIcons() {
unsigned long now = millis();
if (now - lastEffectUpdate >= 800) { // Exibe cada ícone por mais tempo
matrix.fillScreen(0);
// Rotaciona entre os ícones
iconIndex = (iconIndex + 1) % 5;
// Determina a cor com base no modo
uint32_t iconColor;
if (effectMode == 1) {
// No modo arco-íris, cicla as cores
iconColor = Wheel(colorIndex);
colorIndex = (colorIndex + 32) % 256;
} else {
// Usa a cor do modo atual
iconColor = modeColors[effectMode - 1];
}
// Centraliza o ícone no display
int centerX = (WIDTH - 8) / 2;
int centerY = (HEIGHT - 8) / 2;
// Desenha o ícone atual
drawIcon(iconBitmaps[iconIndex], centerX, centerY, iconColor);
matrix.show();
lastEffectUpdate = now;
}
}