Search Code

Proyek Matriks LED RGB ESP32-S3 3 - Teks dari ponsel

Proyek Matriks LED RGB ESP32-S3 3 - Teks dari ponsel

Proyek 3 – Kontrol Teks Matriks dari Ponsel Anda (HTTP Text)

Pada proyek ini, Matriks LED RGB ESP32-S3 menghosting halaman web kecil sehingga Anda dapat mengubah teks berjalan, warna, arah, dan kecepatan langsung dari ponsel atau komputer Anda. Anda tidak memerlukan aplikasi terpisah – cukup browser web. Ini menjadikan modul ini tampilan teks Wi-Fi kecil yang dapat Anda perbarui secara real time.

Keenam proyek dalam seri ini dijelaskan dan didemonstrasikan dalam satu video YouTube. Video yang sama disematkan di halaman ini, sehingga Anda dapat melihat dengan tepat bagaimana antarmuka web terlihat dan bagaimana teks diperbarui secara instan di matriks. Kode sumber lengkap untuk proyek ini dimuat secara otomatis di bawah artikel, dan Anda dapat membeli modul Matriks LED RGB ESP32-S3 dari toko afiliasi yang tercantum di bawah bagian kode.

Dalam artikel ini kami fokus pada cara kerja logika jaringan (Wi-Fi rumah vs titik akses) dan pengaturan apa yang dapat Anda ubah dalam kode untuk menyesuaikan perilaku.

Ikhtisar Modul Matriks LED RGB ESP32-S3

Perangkat kerasnya sama dengan semua proyek lain dalam seri ini: papan mikrokontroler ESP32-S3 dengan matriks LED RGB 8×8 bawaan dan sensor gerak QMI8658C di bagian belakang. Port USB-C digunakan untuk daya dan pemrograman, dan pin di sekitar tepi masih tersedia untuk IO lainnya.:contentReference[oaicite:0]{index=0}

  • ESP32-S3 – mikrokontroler yang mampu Wi-Fi dan Bluetooth.
  • Matriks RGB 8×8 – 64 LED RGB yang dapat dialamatkan untuk teks dan grafis.
  • Akselerometer QMI8658C – digunakan dalam proyek kemiringan dan permainan.
  • Port USB – memberi daya pada papan dan mengunggah sketsa dari Arduino IDE.
  • Pin yang terbuka – memungkinkan sensor atau aktuator tambahan jika diperlukan.
  • Tombol Boot/Reset – untuk unggahan firmware dan memulai ulang.

Untuk Proyek 3, fitur terpenting adalah Wi-Fi ESP32, yang memungkinkan papan bertindak sebagai server web kecil untuk halaman kontrol teks.:contentReference[oaicite:1]{index=1}

Proyek yang Dicakup dalam Video (Stempel Waktu)

Satu video untuk seri ini mencakup keenam proyek. Untuk referensi cepat:

  • 00:00 – Pendahuluan
  • 02:01 – Menginstal papan ESP32
  • 03:32 – Menginstal pustaka
  • 05:32 – Proyek 1: Titik Bergerak
  • 11:11 – Proyek 2: Gulir Teks
  • 12:59Proyek 3: HTTP Text (proyek ini)
  • 16:41 – Proyek 4: Titik Kemiringan
  • 18:55 – Proyek 5: Panah ke Atas
  • 20:02 – Proyek 6: Permainan Target

Anda disarankan untuk menonton bagian HTTP Text dalam video sambil mengerjakan artikel ini. Video menunjukkan bagaimana halaman web dihasilkan oleh ESP32 dan bagaimana perubahan teks, warna, dan kecepatan tercermin secara instan pada matriks LED.:contentReference[oaicite:2]{index=2}

Menginstal Papan ESP32 di Arduino IDE

Jika Anda telah menyelesaikan Proyek 1 atau 2, pengaturan papan sudah selesai dan Anda dapat melewati bagian ini. Jika tidak, ikuti langkah-langkah berikut di Arduino IDE:

  1. Buka File > Preferences dan tambahkan URL papan ESP32 ke “Additional Boards Manager URLs”.
  2. Buka Tools > Board > Boards Manager…, cari ESP32, dan instal paket ESP32 resmi.
  3. Pilih papan Matriks RGB ESP32-S3 yang benar dari Tools > Board.
  4. Hubungkan modul melalui USB dan pilih port serial yang benar di bawah Tools > Port.

Tanpa dukungan papan ESP32 yang tepat dan port yang benar, sketsa server web tidak akan terunggah.

Menginstal NeoMatrix dan Pustaka yang Diperlukan

Proyek ini menggunakan pustaka yang sama dengan proyek gulir teks sebelumnya:

  • Adafruit NeoMatrix
  • Adafruit NeoPixel
  • Adafruit GFX Library

Instal melalui Library Manager:

  1. Buka Sketch > Include Library > Manage Libraries….
  2. Cari Adafruit NeoMatrix dan klik Install.
  3. Terima instalasi dependensi (Adafruit GFX dan Adafruit NeoPixel).

Setelah terinstal, Anda akan melihat contoh sketsa NeoMatrix dan NeoPixel di bawah File > Examples.

Dua Mode Wi-Fi dalam Proyek 3

Konsep terpenting dalam proyek ini adalah ESP32 dapat bekerja dalam dua mode berbeda:

  1. Mode Station (STA) – ESP32 terhubung ke jaringan Wi-Fi rumah Anda yang sudah ada.
  2. Mode Access Point (AP) – ESP32 membuat jaringan Wi-Fi sendiri jika Wi-Fi rumah tidak tersedia.

Kedua mode menggunakan antarmuka web yang sama: halaman HTML yang disajikan dari ESP32 itu sendiri, di mana Anda dapat mengubah teks, warna, arah gulir, dan kecepatan.:contentReference[oaicite:3]{index=3}

Mode 1 – Terhubung ke Wi-Fi Rumah (Mode Station)

Dalam mode Station, ESP32 bergabung dengan jaringan Wi-Fi rumah atau kantor Anda. Ini adalah mode yang disukai setiap kali router Anda tersedia karena:

  • Ponsel dan komputer Anda sudah terhubung ke jaringan Wi-Fi yang sama.
  • Anda dapat mengarahkan browser ke alamat IP ESP32 dan mengontrol teks dari perangkat mana pun di jaringan tersebut.

Di bagian pengaturan sketsa, Anda memberikan SSID dan kata sandi Wi-Fi Anda:

// Kredensial Wi-Fi rumah (Mode Station) const char* WIFI_SSID = "YourHomeWiFi"; const char* WIFI_PASS = "YourHomePassword";

Setelah board melakukan boot, ia mencoba terhubung ke WIFI_SSID. Jika berhasil, kode mencetak alamat IP yang ditetapkan ke Serial Monitor, misalnya:


Connected to WiFi
IP address: 192.168.1.16

Untuk mengontrol teks:

  1. Pastikan ponsel atau PC Anda terhubung ke jaringan Wi-Fi yang sama (misalnya, YourHomeWiFi).
  2. Buka browser dan masukkan alamat IP yang dicetak, seperti http://192.168.1.16/.
  3. Halaman kontrol akan muncul, memungkinkan Anda mengetik teks, memilih warna, memilih arah, dan menyesuaikan kecepatan gulir.

Mode 2 – Access Point Mandiri (Mode AP)

Jika ESP32 tidak dapat terhubung ke Wi-Fi rumah Anda (salah kata sandi, jaringan tidak tersedia, atau Anda menggunakan modul di luar ruangan), sketsa secara otomatis beralih ke mode Access Point. Dalam mode AP, ESP32 itu sendiri menjadi hotspot Wi-Fi dengan nama jaringan dan kata sandi sendiri.

Dalam proyek ini, pengaturan AP ditetapkan sebagai:


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

Ketika mode Station gagal, modul beralih ke mode AP dan mulai menyiarkan jaringan Wi-Fi bernama ESP32. Untuk mengontrol matriks:

  1. Di ponsel atau komputer Anda, buka pengaturan Wi-Fi dan sambungkan ke jaringan ESP32.
  2. Masukkan kata sandi password (seperti yang didefinisikan dalam kode).
  3. Setelah terhubung, buka browser dan pergi ke http://192.168.4.1/ (IP default untuk mode AP ESP32).
  4. Halaman kontrol yang sama muncul, memungkinkan Anda mengubah teks, warna, kecepatan, dan arah.

Perilaku cadangan ini membuat proyek berguna di mana saja: di rumah, di laboratorium, atau di lingkungan demo di mana tidak ada router yang tersedia.

Proyek 3 – Pengaturan Utama dalam Kode

Sketsa HTTP Text lengkap dimuat di bawah artikel ini oleh situs web. Di sini kami hanya mendokumentasikan opsi konfigurasi paling penting yang mungkin Anda edit.

Pengaturan Wi-Fi dan Access Point

Di bagian atas sketsa, Anda akan menemukan bagian konfigurasi Wi-Fi. Hanya ubah kredensial Station (Wi-Fi rumah); pengaturan AP biasanya dipertahankan sebagai default:


// ---------- PENGATURAN WI-FI ----------

// Wi-Fi rumah (Mode Station)
const char* WIFI_SSID = "YourHomeWiFi";      // letakkan SSID router Anda di sini
const char* WIFI_PASS = "YourHomePassword";  // letakkan kata sandi router Anda di sini

// Access Point cadangan (Mode AP)
const char* AP_SSID = "ESP32";               // nama AP tetap
const char* AP_PASS = "password";            // kata sandi AP tetap

Perilaku:

  • Jika WIFI_SSID dan WIFI_PASS benar dan jaringan tersedia → ESP32 terhubung sebagai perangkat Wi-Fi normal (Mode Station).
  • Jika koneksi gagal setelah waktu tunggu → ESP32 memulai AP sendiri menggunakan AP_SSID dan AP_PASS.

Pin Matriks, Ukuran, dan Kecerahan

Pengaturan ini sama dengan proyek sebelumnya:


// Konfigurasi matriks
const int MATRIX_PIN    = 14;   // pin data matriks RGB
const int MATRIX_WIDTH  = 8;
const int MATRIX_HEIGHT = 8;

// Kecerahan tampilan keseluruhan (0–255)
uint8_t matrixBrightness = 40;  // sesuaikan untuk lingkungan Anda

Pertahankan MATRIX_PIN pada 14 untuk board ini. Anda dapat meningkatkan matrixBrightness jika membutuhkan lebih banyak cahaya, tetapi nilai yang lebih rendah lebih nyaman untuk melihat dari dekat.

Teks Default dan Pengaturan Gulir

Ketika board dimulai, ia menampilkan pesan awal sampai Anda membuka halaman web dan mengetik yang baru. Anda dapat mengubah teks default dalam konfigurasi:


// Pesan default yang ditampilkan saat startup
String currentText = "Robojax";   // ditimpa dari UI web nanti

Sisa perilaku gulir dikendalikan oleh serangkaian variabel yang diperbarui oleh antarmuka web:


// Penundaan gulir dalam milidetik (lebih rendah = lebih cepat)
int scrollDelayMs = 50;

// Arah gulir: 0=kiri, 1=kanan, 2=atas, 3=bawah
int scrollDirection = 0;   // default: gulir ke kiri

Halaman web mengirim nilai baru berdasarkan pilihan slider dan tombol. Dari sisi Arduino, Anda hanya perlu tahu bahwa:

  • Mengurangi scrollDelayMs membuat teks bergerak lebih cepat.
  • Meningkatkan scrollDelayMs membuatnya bergerak lebih lambat.
  • Mengubah scrollDirection beralih antara mode gulir kiri, kanan, atas, atau bawah.

Warna Teks (Dikendalikan dari Halaman Web)

Warna teks dikendalikan oleh tiga nilai 0–255 (merah, hijau, biru). Nilai-nilai ini diperbarui setiap kali Anda memilih warna baru di halaman web:


// Warna teks saat ini (R, G, B)
uint8_t textRed   = 255;
uint8_t textGreen = 255;
uint8_t textBlue  = 255;

Ketika Anda memilih warna di browser dan klik Terapkan, ESP32 mengurai nilai RGB dan memperbarui tiga variabel ini; teks segera berubah warna di matriks. Dalam video, perilaku ini didemonstrasikan dengan beberapa perubahan warna, termasuk contoh merah, hijau, dan biru.

Ringkasan

Proyek 3 mengubah ESP32-S3 RGB LED Matrix Anda menjadi tampilan teks sepenuhnya nirkabel yang dapat Anda kontrol menggunakan perangkat apa pun dengan browser web. Sketsa dirancang untuk menjadi kuat:

  • Pertama-tama, perangkat mencoba terhubung ke Wi-Fi rumah Anda menggunakan SSID dan kata sandi yang telah Anda konfigurasi.
  • Jika gagal, perangkat secara otomatis menjadi titik akses dengan nama ESP32 dan kata sandi password.
  • Dalam kedua mode, membuka alamat IP yang benar di browser akan menampilkan halaman kontrol yang sama untuk teks, warna, arah, dan kecepatan.

Kode HTTP Text lengkap tersedia di bawah artikel ini (dimuat secara otomatis di situs web). Untuk panduan langkah demi langkah yang lengkap dan demonstrasi langsung tentang bagaimana teks diperbarui secara real time, pastikan untuk menonton bagian Project 3 dari video tersebut. Jika Anda ingin membangun proyek ini sendiri, Anda juga dapat membeli modul ESP32-S3 RGB LED Matrix menggunakan tautan afiliasi yang tercantum di bawah kode.

Gambar

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
Bahasa: C++
/*
  Project 4: HTTP Text Scroll – ESP32-S3 RGB LED Matrix (Waveshare)

  - Connects to your home WiFi (station mode, with AP fallback).
  - Serves a web page where you can set:
      * Text
      * Color
      * Display ON/OFF
      * Scroll direction (Left / Right / Up / Down)
      * Scroll delay (speed)
  - Supports 8×8 RGB LED matrix using Adafruit_NeoMatrix.

  ▶️ Video Tutorial:
  https://youtu.be/JKLuYrRcLMI

  📄 Resources & Code Page:
  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>

// ======================= WIFI SETTINGS =========================
// Home WiFi (change these to your router credentials)
const char* WIFI_SSID = "Biseem";
const char* WIFI_PASS = "wan9&Jang~";

// Fallback Access Point
const char* AP_SSID = "ESP32";
const char* AP_PASS = "password";

// ======================= MATRIX SETTINGS =======================

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

// 0, 1, 2, or 3 – adjust if text orientation is wrong with USB up
#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
);

// ======================= SCROLL STATE ==========================

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

String   scrollText  = "Robojax";
uint16_t textColor   = 0xFFFF;  // default white
bool     displayOn   = true;
ScrollDir currentDir = DIR_LEFT;

// 1 step every this many ms (lower = faster)
unsigned long scrollInterval = 80;
unsigned long lastScrollTime = 0;

// Horizontal scroll position
int scrollX = MATRIX_WIDTH;
int scrollY = 0;              // top row

// State for vertical per-letter scroll
int vertCharIndex = 0;        // which character of the string
int vertY         = MATRIX_HEIGHT;    // vertical position of that character

// ======================= WEB SERVER ============================

WebServer server(80);

// ======================= HTML PAGE ============================

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';
      });
  }

  // Set default active
  setDir('left');
</script>
</body>
</html>
)rawliteral";

// ======================= HELPERS ===============================

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) {
  // Expect "#RRGGBB" or "RRGGBB"
  String c = hex;
  if (c.startsWith("#")) c.remove(0, 1);
  if (c.length() != 6) {
    // default white
    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;  // default font ~6 px per char
  int textHeight = 8;                        // 5x7 font fits in 8

  switch (currentDir) {
    case DIR_LEFT:
      // Start from just outside the right edge
      scrollX = MATRIX_WIDTH;
      scrollY = 0;
      break;

    case DIR_RIGHT:
      // Start from just outside the left edge
      scrollX = -textWidth;
      scrollY = 0;
      break;

    case DIR_UP:
      // Per-letter vertical scroll, starting with first character below matrix
      vertCharIndex = 0;
      vertY = MATRIX_HEIGHT;   // 8 → enters from bottom
      break;

    case DIR_DOWN:
      // Per-letter vertical scroll, starting with first character above matrix
      vertCharIndex = 0;
      vertY = -textHeight;     // -8 → enters from top
      break;
  }
}

// ======================= HTTP HANDLERS =========================

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();

    // simple safety clamp
    if (val < 10)   val = 10;    // very fast
    if (val > 1000) val = 1000;  // 1 second max

    scrollInterval = (unsigned long)val;
    // Serial.print("Scroll interval set to ");
    // Serial.print(scrollInterval);
    // Serial.println(" ms");
  }

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

// ======================= SCROLL LOGIC ==========================

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);

  // -------- HORIZONTAL SCROLL (LEFT / RIGHT) --------
  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_RIGHT
      scrollX++;
      if (scrollX > MATRIX_WIDTH) {
        scrollX = -textWidth;
      }
    }
    return;
  }

  // -------- VERTICAL SCROLL (UP / DOWN) – PER LETTER --------

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

  // Current character
  char c = scrollText[vertCharIndex];

  int charWidth  = 6;
  int baseX = (MATRIX_WIDTH - charWidth) / 2;  // center horizontally

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

  if (currentDir == DIR_UP) {
    // Move character upwards
    vertY--;
    if (vertY < -textHeight) {
      // This character is fully passed, go to next
      vertY = MATRIX_HEIGHT;
      vertCharIndex++;
      if (vertCharIndex >= scrollText.length()) {
        vertCharIndex = 0; // loop back to first character
      }
    }
  } else if (currentDir == DIR_DOWN) {
    // Move character downwards
    vertY++;
    if (vertY > MATRIX_HEIGHT) {
      // This character is fully passed, go to next
      vertY = -textHeight;
      vertCharIndex++;
      if (vertCharIndex >= scrollText.length()) {
        vertCharIndex = 0; // loop back
      }
    }
  }
}

// ======================= WIFI INIT =============================

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();
  }
}

// ======================= SETUP & LOOP ==========================

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

  // Matrix init
  matrix.begin();
  matrix.setRotation(MATRIX_ROTATION);
  matrix.setBrightness(BRIGHTNESS);
  matrix.fillScreen(0);
  matrix.show();

  // WiFi
  connectWiFi();

  // Web server routes
  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();
  }
}

Hal-hal yang mungkin Anda butuhkan

Sumber daya & referensi

Berkas📁

File Fritzing