Código de Pesquisa

Projeto de Matriz LED RGB ESP32-S3 3 - Texto do telefone móvel

Projeto de Matriz LED RGB ESP32-S3 3 - Texto do telefone móvel

Projeto 3 - Matriz de Controle Texto do Seu Telefone (Texto HTTP)

Neste projeto, a Matriz de LED RGB ESP32-S3 hospeda uma pequena página da web para que você possa alterar o texto em movimento, a cor, a direção e a velocidade diretamente do seu telefone ou computador. Você não precisa de um aplicativo separado - apenas de um navegador da web. Isso torna o módulo um pequeno display de texto Wi-Fi que você pode atualizar em tempo real.

Todos os seis projetos desta série são explicados e demonstrados em um vídeo no YouTube. O mesmo vídeo está incorporado nesta página, para que você possa ver exatamente como a interface da web se parece e como o texto é atualizado instantaneamente na matriz. O código-fonte completo para este projeto é carregado automaticamente abaixo do artigo, e você pode adquirir o módulo LED RGB matrix ESP32-S3 em lojas afiliadas listadas na seção de código.

Neste artigo, focamos em como a lógica da rede funciona (Wi-Fi doméstico vs ponto de acesso) e quais configurações você pode alterar no código para personalizar o comportamento.

Módulo de Matriz de LED RGB ESP32-S3 Visão Geral

O hardware é o mesmo que para todos os outros projetos desta série: uma placa de microcontrolador ESP32-S3 com uma matriz de LED RGB 8×8 integrada e um sensor de movimento QMI8658C na parte traseira. A porta USB-C é usada para alimentação e programação, e os pinos ao redor das bordas ainda estão disponíveis para outros I/O.:contentReference[oaicite:0]{index=0}

  • ESP32-S3- Microcontrolador com capacidade para Wi-Fi e Bluetooth.
  • matriz RGB 8×8- 64 LEDs RGB endereçáveis para texto e gráficos.
  • Acelerômetro QMI8658C- usado nos projetos tilt e game.
  • Porta USB- alimenta a placa e carrega sketches do Arduino IDE.
  • Pinos expostos- permitir sensores ou atuadores adicionais, se necessário.
  • Botões de Inicialização/Reinício- para upload de firmware e reinicialização.

Para o Projeto 3, o recurso mais importante é o Wi-Fi do ESP32, que permite que a placa atue como um pequeno servidor web para a página de controle de texto.:contentReference[oaicite:1]{index=1}

Projetos Cobertos no Vídeo (Carimbos de Tempo)

O único vídeo desta série cobre todos os seis projetos. Para referência rápida:

  • 00:00- Introdução
  • 02:01- Instalando placas ESP32
  • 03:32- Instalando bibliotecas
  • 05:32- Projeto 1: Ponto Móvel
  • 11:11- Projeto 2: Rolagem de Texto
  • 12:59-Projeto 3: Texto HTTP (este projeto)
  • 16:41- Projeto 4: Tilt Dot
  • 18:55- Projeto 5: Seta Para Cima
  • 20:02- Projeto 6: Jogo de Alvo

Você é encorajado a assistir à seção de Texto HTTP no vídeo enquanto trabalha com este artigo. O vídeo mostra como a página da web é gerada pelo ESP32 e como a alteração de texto, cor e velocidade é refletida instantaneamente na matriz de LEDs.:contentReference[oaicite:2]{index=2}

Instalando Placas ESP32 no Arduino IDE

Se você já completou os Projetos 1 ou 2, a configuração da placa está feita e você pode pular esta seção. Caso contrário, siga estas etapas no Arduino IDE:

  1. AbrirFile > Preferencese adicione a URL das placas ESP32 em "URLs Adicionais do Gerenciador de Placas".
  2. Vá paraTools > Board > Boards Manager…, procure porESP32, e instale o pacote oficial do ESP32.
  3. Selecione a placa correta do Matriz RGB ESP32-S3 deTools > Board.
  4. Conecte o módulo via USB e escolha a porta serial correta emTools > Port.

Sem o suporte adequado da placa ESP32 e a porta correta, o esboço do servidor web não será carregado.

Instalando o NeoMatrix e Bibliotecas Necessárias

Este projeto utiliza as mesmas bibliotecas que o projeto anterior de rolagem de texto:

  • Adafruit NeoMatrix
  • Adafruit NeoPixel
  • Adafruit GFX Library

Instale via Gerenciador de Bibliotecas:

  1. AbrirSketch > Include Library > Manage Libraries….
  2. Pesquisar porAdafruit NeoMatrixe cliqueInstalar.
  3. Aceite a instalação das dependências (Adafruit GFXeAdafruit NeoPixel).

Uma vez instalado, você deve ver os exemplos de esboços NeoMatrix e NeoPixel emFile > Examples.

Dois Modos Wi-Fi no Projeto 3

O conceito mais importante neste projeto é que o ESP32 pode funcionar emdois modos diferentes:

  1. Modo Estação (STA)- o ESP32 conecta-se à sua rede Wi-Fi doméstica existente.
  2. Modo Ponto de Acesso (AP)- o ESP32 cria sua própria rede Wi-Fi se a rede Wi-Fi da casa não estiver disponível.

Ambos os modos usam a mesma interface web: uma página HTML servida pelo próprio ESP32, onde você pode alterar o texto, a cor, a direção de rolagem e a velocidade.:contentReference[oaicite:3]{index=3}

Modo 1 - Conectar ao Wi-Fi doméstico (Modo Estação)

No modo Estação, o ESP32 se conecta à sua rede Wi-Fi doméstica ou de escritório. Este é o modo preferido sempre que seu roteador estiver disponível porque:

  • Seu telefone e computador já estão conectados à mesma rede Wi-Fi.
  • Você pode apontar seu navegador para o endereço IP do ESP32 e controlar o texto de qualquer dispositivo naquela rede.

Na seção de configurações do sketch, você fornece seu SSID e senha do Wi-Fi:


// Home Wi-Fi credentials (Station mode)
const char* WIFI_SSID = "YourHomeWiFi";
const char* WIFI_PASS = "YourHomePassword";

Após a placa inicializar, ela tenta se conectar aWIFI_SSIDSe for bem-sucedido, o código imprime o endereço IP atribuído no Monitor Serial, por exemplo:


Connected to WiFi
IP address: 192.168.1.16

Para controlar o texto:

  1. Certifique-se de que seu telefone ou PC esteja conectado à mesma rede Wi-Fi (por exemplo,YourHomeWiFi).
  2. Abra um navegador e digite o endereço IP impresso, comohttp://192.168.1.16/. :contentReference[oaicite:4]{index=4}
  3. A página de controle aparecerá, permitindo que você digite texto, escolha a cor, selecione a direção e ajuste a velocidade de rolagem.

Modo 2 - Ponto de Acesso Autônomo (Modo AP)

Se o ESP32 não consegue se conectar ao seu Wi-Fi doméstico (senha errada, rede indisponível ou você está usando o módulo fora), o sketch automaticamente volta para o modo Ponto de Acesso. No modo AP, o ESP32 se torna um hotspot Wi-Fi com seu próprio nome de rede e senha.

Neste projeto, as configurações do AP estão fixadas em:


// Access Point (AP) credentials (fallback mode)
const char* AP_SSID = "ESP32";
const char* AP_PASS = "password";

Quando o modo Estação falha, o módulo muda para o modo AP e começa a transmitir uma rede Wi-Fi chamadaESP32. Para controlar a matriz:

  1. No seu telefone ou computador, abra as configurações de Wi-Fi e conecte-se à rede.ESP32.
  2. Digite a senhasenha(conforme definido no código).
  3. Uma vez conectado, abra um navegador e vá parahttp://192.168.4.1/(o IP padrão para o modo AP do ESP32).
  4. A mesma página de controle aparece, permitindo que você altere o texto, a cor, a velocidade e a direção.

Esse comportamento de reserva torna o projeto útil em qualquer lugar: em casa, no laboratório ou em um ambiente de demonstração onde não há roteador disponível.

Projeto 3 - Configurações Principais no Código

O esboço completo do HTTP Text está carregado abaixo deste artigo pelo site. Aqui, apenas documentamos as opções de configuração mais importantes que você provavelmente irá editar.

Configurações de Wi-Fi e Ponto de Acesso

No topo do esboço, você encontrará a seção de configuração do Wi-Fi. Altere apenas as credenciais da Estação (Wi-Fi doméstico); as configurações do AP normalmente são mantidas como padrão:


// ---------- Wi-Fi SETTINGS ----------

// Home Wi-Fi (Station mode)
const char* WIFI_SSID = "YourHomeWiFi";      // put your router SSID here
const char* WIFI_PASS = "YourHomePassword";  // put your router password here

// Fallback Access Point (AP mode)
const char* AP_SSID = "ESP32";               // fixed AP name
const char* AP_PASS = "password";            // fixed AP password

Comportamento:

  • SeWIFI_SSIDeWIFI_PASSestão corretas e a rede está disponível → ESP32 conecta-se como um dispositivo Wi-Fi normal (modo Estação).
  • Se a conexão falhar após um tempo limite → ESP32 inicia seu próprio AP usandoAP_SSIDeAP_PASS.

Pino da Matriz, Tamanho e Brilho

Estas configurações são as mesmas dos projetos anteriores:


// Matrix configuration
const int MATRIX_PIN    = 14;   // RGB matrix data pin
const int MATRIX_WIDTH  = 8;
const int MATRIX_HEIGHT = 8;

// Overall display brightness (0–255)
uint8_t matrixBrightness = 40;  // adjust for your environment

ManterMATRIX_PINem14para esta placa. Você pode aumentarmatrixBrightnessse você precisar de mais luz, mas valores mais baixos são mais confortáveis para visualização de perto.

Texto Padrão e Configurações de Rolagem

Quando a placa é iniciada, ela exibe uma mensagem inicial até que você abra a página da web e digite uma nova. Você pode mudar o texto padrão na configuração:


// Default message shown at startup
String currentText = "Robojax";   // overwrite from web UI later

O restante do comportamento de rolagem é controlado por um conjunto de variáveis que são atualizadas pela interface da web:


// Scroll delay in milliseconds (lower = faster)
int scrollDelayMs = 50;

// Scroll direction: 0=left, 1=right, 2=up, 3=down
int scrollDirection = 0;   // default: scroll left

A página da web envia novos valores com base nas seleções do deslizante e dos botões. Do lado do Arduino, você só precisa saber que:

  • DiminuindoscrollDelayMsfaz o texto se mover mais rápido.
  • AumentandoscrollDelayMsfaz com que se mova mais devagar.
  • MudandoscrollDirectionalternar entre modos de rolagem para a esquerda, direita, para cima ou para baixo.

Cor da Fonte (Controlada a partir da Página da Web)

A cor do texto é controlada por três valores de 0-255 (vermelho, verde, azul). Eles são atualizados sempre que você escolhe uma nova cor na página da web:


// Current text color (R, G, B)
uint8_t textRed   = 255;
uint8_t textGreen = 255;
uint8_t textBlue  = 255;

Quando você seleciona uma cor no navegador e clica em Aplicar, o ESP32 analisa os valores RGB e atualiza essas três variáveis; o texto muda de cor imediatamente na matriz. No vídeo, esse comportamento é demonstrado com várias mudanças de cor, incluindo exemplos nas cores vermelha, verde e azul.:contentReference[oaicite:5]{index=5}

Resumo

O Projeto 3 transforma sua matriz de LED RGB ESP32-S3 em um display de texto totalmente sem fio que você pode controlar usando qualquer dispositivo com um navegador web. O esboço foi projetado para ser robusto:

  • Primeiro, tenta conectar-se à sua rede Wi-Fi doméstica usando o SSID e a senha que você configurou.
  • Se isso falhar, ele automaticamente se torna um ponto de acesso com o nomeESP32e senhapassword.
  • Em ambos os modos, abrir o endereço IP correto em um navegador exibe a mesma página de controle para texto, cor, direção e velocidade.

O código completo de texto HTTP está disponível abaixo deste artigo (carregado automaticamente no site). Para um guia completo passo a passo e uma demonstração ao vivo de como o texto é atualizado em tempo real, não deixe de assistir à seção do Projeto 3 do vídeo. Se você quiser construir o projeto por conta própria, também pode adquirir o módulo de matriz LED RGB ESP32-S3 usando os links de afiliados listados abaixo do código.

Imagens

ESP32 S3 Matrix
ESP32 S3 Matrix
ESP32 S3 Matrix  pin out
ESP32 S3 Matrix pin out
ESP32-S3_RGB_8x8_matrix-3
ESP32-S3_RGB_8x8_matrix-3
ESP32 S3 Matrix displaying rainbow heart
ESP32 S3 Matrix displaying rainbow heart
ESP32-S3_RGB_8x8_matrix1
ESP32-S3_RGB_8x8_matrix1
ESP32-S3_RGB_8x8_matrix-2
ESP32-S3_RGB_8x8_matrix-2
ESP32-S3 RGB Matrix- Mobile Phone Text
ESP32-S3 RGB Matrix- Mobile Phone Text
801-ESP32-S3 RGB LED Matrix Project 3 - Text from mobile phone
Idioma: C++
/*
 * Projeto 4: Rolagem de Texto HTTP - Matriz de LEDs RGB ESP32-S3 (Waveshare)
 * 
 * - Conecta-se ao seu WiFi doméstico (modo estação, com fallback de AP).
 * - Serve uma página da web onde você pode configurar:
 * Texto
 * Cor
 * Exibição LIGADA/DESLIGADA
 * Direção da rolagem (Esquerda / Direita / Cima / Baixo)
 * Atraso na rolagem (velocidade)
 * - Suporta matriz de LED RGB 8×8 usando Adafruit_NeoMatrix.
 * 
 * ▶️ Tutorial em Vídeo:
 * https://youtu.be/JKLuYrRcLMI
 * 
 * 📄 Recursos & Página do Código:
 * https://robojax.com/your-resources-page-here
 * 
 * HTTP_Text_Scroll
 */
#include <Arduino.h>
#include <WiFi.h>
#include <WebServer.h>

#include <Adafruit_GFX.h>
#include <Adafruit_NeoMatrix.h>
#include <Adafruit_NeoPixel.h>

 // ======================= CONFIGURAÇÕES DO WIFI =========================
 // WiFi doméstico (mude isso para as credenciais do seu roteador)
const char* WIFI_SSID = "Biseem";
const char* WIFI_PASS = "wan9&Jang~";

 // Ponto de Acesso de Reserva
const char* AP_SSID = "ESP32";
const char* AP_PASS = "password";

 // ======================= CONFIGURAÇÕES DA MATRIZ =======================

#define MATRIX_PIN    14
#define MATRIX_WIDTH  8
#define MATRIX_HEIGHT 8

 // 0, 1, 2 ou 3 - ajuste se a orientação do texto estiver errada com USB para cima
#define MATRIX_ROTATION 0
#define BRIGHTNESS 15

Adafruit_NeoMatrix matrix = Adafruit_NeoMatrix(
  MATRIX_WIDTH, MATRIX_HEIGHT, MATRIX_PIN,
  NEO_MATRIX_TOP + NEO_MATRIX_LEFT +
  NEO_MATRIX_ROWS + NEO_MATRIX_PROGRESSIVE,
  NEO_RGB + NEO_KHZ800
);

 // ======================= ESTADO DE ROLAGEM ==========================

enum ScrollDir {
  DIR_LEFT = 0,
  DIR_RIGHT,
  DIR_UP,
  DIR_DOWN
};

String   scrollText  = "Robojax";
uint16_t textColor   = 0xFFFF; // branco padrão
bool     displayOn   = true;
ScrollDir currentDir = DIR_LEFT;

 // 1 passo a cada quantos ms (menor = mais rápido)
unsigned long scrollInterval = 80;
unsigned long lastScrollTime = 0;

 // Posição de rolagem horizontal
int scrollX = MATRIX_WIDTH;
int scrollY = 0; // linha superior

 // Estado para rolagem vertical letra por letra
int vertCharIndex = 0; // qual caractere da string
int vertY         = MATRIX_HEIGHT; // posição vertical daquele caractere

 // ======================= SERVIDOR WEB ============================

WebServer server(80);

 // ======================= PÁGINA HTML ============================

const char MAIN_page[] PROGMEM = R"rawliteral(
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>ESP32 Text Scroll</title>
<style>
  body {
    font-family: Arial, sans-serif;
    background: #111;
    color: #eee;
    text-align: center;
    margin: 0;
    padding: 10px;
  }
  .container {
    max-width: 360px;
    width: 100%%;
    margin: 0 auto;
    background: #222;
    padding: 12px;
    border-radius: 10px;
    box-sizing: border-box;
  }
  input[type="text"] {
    width: 100%%;
    padding: 8px;
    box-sizing: border-box;
    border-radius: 5px;
    border: 1px solid #444;
    background: #111;
    color: #eee;
    margin-bottom: 10px;
  }
  .row {
    margin: 10px 0;
  }
  .label {
    display: block;
    margin-bottom: 5px;
    text-align: left;
  }
  .toggle {
    display: inline-flex;
    align-items: center;
    gap: 8px;
  }
  .arrow-grid {
    display: grid;
    grid-template-columns: repeat(3, minmax(0, 1fr));
    grid-template-rows: repeat(3, 50px);
    gap: 8px;
    justify-content: center;
    margin-top: 10px;
  }
  .arrow-btn {
    background: #333;
    border: 1px solid #555;
    color: #eee;
    font-size: 18px;
    border-radius: 10px;
    cursor: pointer;
    width: 100%%;
    height: 100%%;
  }
  .arrow-btn.active {
    background: #0a84ff;
  }
  .arrow-btn:disabled {
    opacity: 0.5;
    cursor: default;
  }
  button#applyBtn {
    margin-top: 15px;
    padding: 10px 20px;
    border-radius: 8px;
    border: none;
    background: #0a84ff;
    color: #fff;
    font-size: 16px;
    cursor: pointer;
  }
  button#applyBtn:active {
    transform: scale(0.97);
  }
</style>
</head>
<body>
  <div class="container">
    <h2>Robojax ESP32-S3 Matrix Text Scroll</h2>

    <div class="row">
      <span class="label">Text to scroll:</span>
      <input type="text" id="text" value="Hello" />
    </div>

    <div class="row">
      <span class="label">Color:</span>
      <input type="color" id="color" value="#ffffff" />
    </div>

    <div class="row">
      <span class="label">Scroll Delay (fast → slow):</span>
      <input
        type="range"
        id="speed"
        min="20"
        max="300"
        value="80"
        oninput="document.getElementById('speedVal').innerText = this.value + ' ms';"
      />
      <div id="speedVal" style="text-align:right;font-size:12px;margin-top:4px;">
        80 ms
      </div>
    </div>

    <div class="row">
      <label class="toggle">
        <input type="checkbox" id="power" checked />
        <span>Display ON</span>
      </label>
    </div>

    <div class="row">
      <span class="label">Scroll Direction:</span>
      <div class="arrow-grid">
        <div></div>
        <button class="arrow-btn" id="btnUp" onclick="setDir('up')">&#9650;</button>
        <div></div>

        <button class="arrow-btn" id="btnLeft" onclick="setDir('left')">&#9664;</button>
        <div></div>
        <button class="arrow-btn" id="btnRight" onclick="setDir('right')">&#9654;</button>

        <div></div>
        <button class="arrow-btn" id="btnDown" onclick="setDir('down')">&#9660;</button>
        <div></div>
      </div>
    </div>

    <button id="applyBtn" onclick="sendUpdate()">Apply</button>
    <p id="status"></p>
  </div>

<script>
  let currentDir = 'left';

  function setDir(dir) {
    currentDir = dir;
    document.querySelectorAll('.arrow-btn').forEach(b => b.classList.remove('active'));
    if (dir === 'up')    document.getElementById('btnUp').classList.add('active');
    if (dir === 'down')  document.getElementById('btnDown').classList.add('active');
    if (dir === 'left')  document.getElementById('btnLeft').classList.add('active');
    if (dir === 'right') document.getElementById('btnRight').classList.add('active');
    sendUpdate();
  }

  function sendUpdate() {
    const text  = document.getElementById('text').value;
    const color = document.getElementById('color').value;
    const power = document.getElementById('power').checked ? '1' : '0';
    const speed = document.getElementById('speed').value;

    const url = `/update?text=${encodeURIComponent(text)}&color=${encodeURIComponent(color)}&power=${power}&dir=${currentDir}&speed=${speed}`;

    fetch(url)
      .then(r => r.text())
      .then(t => {
        document.getElementById('status').innerText = t;
      })
      .catch(err => {
        document.getElementById('status').innerText = 'Error sending update';
      });
  }

 // Definir padrão ativo
  setDir('left');
</script>
</body>
</html>
)rawliteral";

 // ======================= AJUDANTES ===============================

bool isHexChar(char c) {
  return (c >= '0' && c <= '9') ||
         (c >= 'a' && c <= 'f') ||
         (c >= 'A' && c <= 'F');
}

String urlDecode(const String &src) {
  String result;
  result.reserve(src.length());
  for (size_t i = 0; i < src.length(); i++) {
    char c = src[i];
    if (c == '+') {
      result += ' ';
    } else if (c == '%' && i + 2 < src.length()) {
      char h1 = src[i + 1];
      char h2 = src[i + 2];
      if (isHexChar(h1) && isHexChar(h2)) {
        char hex[3] = {h1, h2, 0};
        int val = (int)strtol(hex, nullptr, 16);
        result += (char)val;
        i += 2;
      } else {
        result += c;
      }
    } else {
      result += c;
    }
  }
  return result;
}

uint16_t colorFromHex(const String &hex) {
 // Espere "#RRGGBB" ou "RRGGBB"
  String c = hex;
  if (c.startsWith("#")) c.remove(0, 1);
  if (c.length() != 6) {
 // branco padrão
    return matrix.Color(255, 255, 255);
  }
  long value = strtol(c.c_str(), NULL, 16);
  uint8_t r = (value >> 16) & 0xFF;
  uint8_t g = (value >> 8) & 0xFF;
  uint8_t b = (value) & 0xFF;
  return matrix.Color(r, g, b);
}

void resetScrollPosition() {
  int textWidth  = scrollText.length() * 6; // fonte padrão ~6 px por caractere
  int textHeight = 8; // A fonte 5x7 se encaixa em 8

  switch (currentDir) {
    case DIR_LEFT:
 // Comece logo fora da borda direita.
      scrollX = MATRIX_WIDTH;
      scrollY = 0;
      break;

    case DIR_RIGHT:
 // Comece logo fora da borda esquerda.
      scrollX = -textWidth;
      scrollY = 0;
      break;

    case DIR_UP:
 // Rolagem vertical por letra, começando com o primeiro caractere abaixo da matriz
      vertCharIndex = 0;
      vertY = MATRIX_HEIGHT; // 8 → entra pela parte de baixo
      break;

    case DIR_DOWN:
 // Deslocamento vertical por letra, começando com o primeiro caractere acima da matriz
      vertCharIndex = 0;
      vertY = -textHeight; // -8 → entra de cima
      break;
  }
}

 // ======================= MANIPULADORES HTTP =========================

void handleRoot() {
  server.send_P(200, "text/html", MAIN_page);
}

void handleUpdate() {
  if (server.hasArg("text")) {
    String txt = urlDecode(server.arg("text"));
    if (txt.length() == 0) {
      scrollText = " ";
    } else {
      scrollText = txt;
    }
  }

  if (server.hasArg("color")) {
    String hex = server.arg("color");
    textColor = colorFromHex(hex);
  }

  if (server.hasArg("power")) {
    String p = server.arg("power");
    displayOn = (p == "1");
  }

  if (server.hasArg("dir")) {
    String d = server.arg("dir");
    if (d == "left")      currentDir = DIR_LEFT;
    else if (d == "right") currentDir = DIR_RIGHT;
    else if (d == "up")    currentDir = DIR_UP;
    else if (d == "down")  currentDir = DIR_DOWN;
  }

  if (server.hasArg("speed")) {
    String s = server.arg("speed");
    int val = s.toInt();

 // grampos de segurança simples
    if (val < 10)   val = 10; // muito rápido
    if (val > 1000) val = 1000; // 1 segundo max

    scrollInterval = (unsigned long)val;
 // Serial.print("Intervalo de rolagem definido como ");
 // Serial.print(scrollInterval);
 // Serial.println(" ms");
  }

  resetScrollPosition();
  server.send(200, "text/plain", "Updated");
}

 // ======================= LÓGICA DE DESLOCAMENTO ==========================

void drawScroll() {
  if (!displayOn) {
    matrix.fillScreen(0);
    matrix.show();
    return;
  }

  int textWidth  = scrollText.length() * 6;
  int textHeight = 8;

  matrix.fillScreen(0);
  matrix.setTextSize(1);
  matrix.setTextWrap(false);
  matrix.setTextColor(textColor);

 // -------- DESLOCAMENTO HORIZONTAL (ESQUERDA / DIREITA) --------
  if (currentDir == DIR_LEFT || currentDir == DIR_RIGHT) {
    matrix.setCursor(scrollX, 0);
    matrix.print(scrollText);
    matrix.show();

    if (currentDir == DIR_LEFT) {
      scrollX--;
      if (scrollX < -textWidth) {
        scrollX = MATRIX_WIDTH;
      }
    } else { // DIR_DIREITA
      scrollX++;
      if (scrollX > MATRIX_WIDTH) {
        scrollX = -textWidth;
      }
    }
    return;
  }

 // -------- ROLAGEM VERTICAL (CIMA / BAIXO) - POR LETRA --------

  if (scrollText.length() == 0) {
    matrix.show();
    return;
  }

 // Personagem atual
  char c = scrollText[vertCharIndex];

  int charWidth  = 6;
  int baseX = (MATRIX_WIDTH - charWidth) / 2; // centralizar horizontalmente

  matrix.setCursor(baseX, vertY);
  matrix.print(c);
  matrix.show();

  if (currentDir == DIR_UP) {
 // Mover o personagem para cima
    vertY--;
    if (vertY < -textHeight) {
 // Este caractere está totalmente aprovado, vá para o próximo.
      vertY = MATRIX_HEIGHT;
      vertCharIndex++;
      if (vertCharIndex >= scrollText.length()) {
        vertCharIndex = 0; // voltar ao primeiro caractere
      }
    }
  } else if (currentDir == DIR_DOWN) {
 // Mova o personagem para baixo
    vertY++;
    if (vertY > MATRIX_HEIGHT) {
 // Este caractere está totalmente aprovado, vá para o próximo.
      vertY = -textHeight;
      vertCharIndex++;
      if (vertCharIndex >= scrollText.length()) {
        vertCharIndex = 0; // retornar
      }
    }
  }
}

 // ======================= INICIALIZAÇÃO DO WIFI =============================

void startAPFallback() {
  Serial.println("Starting AP fallback...");
  WiFi.mode(WIFI_AP);
  bool apOk = WiFi.softAP(AP_SSID, AP_PASS);
  if (apOk) {
    Serial.print("AP started. SSID: ");
    Serial.print(AP_SSID);
    Serial.print("  IP: ");
    Serial.println(WiFi.softAPIP());
  } else {
    Serial.println("Failed to start AP.");
  }
}

void connectWiFi() {
  WiFi.mode(WIFI_STA);
  WiFi.begin(WIFI_SSID, WIFI_PASS);

  Serial.print("Connecting to WiFi ");
  Serial.print(WIFI_SSID);

  unsigned long startAttempt = millis();
  const unsigned long timeout = 10000; // 10s

  while (WiFi.status() != WL_CONNECTED && (millis() - startAttempt) < timeout) {
    Serial.print(".");
    delay(500);
  }
  Serial.println();

  if (WiFi.status() == WL_CONNECTED) {
    Serial.print("Connected. IP address: ");
    Serial.println(WiFi.localIP());
  } else {
    Serial.println("WiFi connect failed, starting AP fallback.");
    startAPFallback();
  }
}

 // ======================= CONFIGURAÇÃO E LOOP ==========================

void setup() {
  Serial.begin(115200);
  delay(500);

 // Inicialização da matriz
  matrix.begin();
  matrix.setRotation(MATRIX_ROTATION);
  matrix.setBrightness(BRIGHTNESS);
  matrix.fillScreen(0);
  matrix.show();

 // WiFi
  connectWiFi();

 // Rotas do servidor web
  server.on("/", handleRoot);
  server.on("/update", handleUpdate);
  server.onNotFound([]() {
    server.send(404, "text/plain", "Not found");
  });
  server.begin();
  Serial.println("HTTP server started.");

  resetScrollPosition();
}

void loop() {
  server.handleClient();

  unsigned long now = millis();
  if (now - lastScrollTime >= scrollInterval) {
    lastScrollTime = now;
    drawScroll();
  }
}

Coisas que você pode precisar

Recursos e referências

Arquivos📁

Arquivo Fritzing