This tutorial is part of: ESP32-S3 RGB LED 点阵
这是一个很棒的项目,可以使用 ESP32-S3 RGB 矩阵模块进行创作,兼具趣味性和实用性。本文下方有其他相关视频的链接。
ESP32-S3 RGB LED矩陣項目3 - 手機文字
項目3 – 用手機控制矩陣文字(HTTP文字)
喺呢個項目入面,ESP32-S3 RGB LED矩陣會主持一個小型網頁,等你可以直接喺手機或者電腦度改滾動文字、顏色、方向同速度。你唔需要另外裝App – 淨係用瀏覽器就得。咁樣就令到呢個模組變成一個可以即時更新嘅微型Wi-Fi文字顯示器。
呢個系列嘅全部六個項目都會喺一條YouTube影片入面解釋同示範。同一條影片亦都嵌入咗喺呢一頁,所以你可以清楚睇到網頁介面係點樣,同埋文字點樣即時更新喺矩陣上面。呢個項目嘅完整原始碼會自動載入喺文章下面,你可以喺程式碼部分列出嘅聯盟商店購買ESP32-S3 RGB LED矩陣模組。
喺呢篇文章入面,我哋會集中講網絡邏輯點樣運作(家用Wi-Fi對比存取點模式),同埋你可以喺程式碼入面改邊啲設定嚟自訂行為。



ESP32-S3 RGB LED矩陣模組概覽
硬件同呢個系列其他項目一樣:一塊ESP32-S3微控制器板,背面內置一個8×8 RGB LED矩陣同一個QMI8658C動作感應器。USB-C接口用嚟供電同編程,邊緣嘅引腳仍然可以用嚟做其他IO。

- ESP32-S3 – 具備Wi-Fi同藍牙功能嘅微控制器。
- 8×8 RGB矩陣 – 64粒可定址RGB LED,用嚟顯示文字同圖形。
- QMI8658C加速度計 – 用喺傾斜同遊戲項目入面。
- USB接口 – 為板供電同從Arduino IDE上傳程式。
- 外露引腳 – 有需要時可以連接額外感應器或者執行器。
- Boot/Reset按鈕 – 用嚟上傳韌體同重新啟動。
對於項目3嚟講,最重要嘅功能係ESP32嘅Wi-Fi,佢令到塊板可以充當一個微型網頁伺服器,用嚟提供文字控制頁面。

影片入面涵蓋嘅項目(時間戳)
呢個系列嘅一條影片涵蓋晒全部六個項目。方便快速參考:
- 00:00 – 簡介
- 02:01 – 安裝ESP32板支援
- 03:32 – 安裝程式庫
- 05:32 – 項目1:移動點
- 11:11 – 項目2:文字滾動
- 12:59 – 項目3:HTTP文字(呢個項目)
- 16:41 – 項目4:傾斜點
- 18:55 – 項目5:向上箭嘴
- 20:02 – 項目6:靶心遊戲
建議你睇呢篇文章嘅時候,同時睇返影片入面嘅HTTP文字部分。影片會展示ESP32點樣生成網頁,同埋改文字、顏色同速度點樣即時反映喺LED矩陣上面。
喺Arduino IDE安裝ESP32板支援
如果你已經完成咗項目1或者2,板嘅設定已經做好,可以跳過呢個部分。否則,跟住以下步驟喺Arduino IDE度做:
- 打開
File > Preferences,將ESP32板嘅URL加入「Additional Boards Manager URLs」。 - 去
Tools > Board > Boards Manager…,搜尋ESP32,然後安裝官方ESP32套件。 - 喺
Tools > Board度選擇正確嘅ESP32-S3 RGB矩陣板。 - 用USB連接模組,然後喺
Tools > Port度選擇正確嘅序列埠。
如果冇正確嘅ESP32板支援同正確嘅埠,網頁伺服器程式就冇辦法上傳。
安裝NeoMatrix同所需程式庫
呢個項目用嘅程式庫同之前文字滾動項目一樣:
Adafruit NeoMatrixAdafruit NeoPixelAdafruit GFX Library
透過程式庫管理員安裝:
- 打開
Sketch > Include Library > Manage Libraries…。 - 搜尋
Adafruit NeoMatrix,然後撳Install。 - 接受相依程式庫嘅安裝(
Adafruit GFX同Adafruit NeoPixel)。
安裝完成之後,你應該會喺File > Examples度見到NeoMatrix同NeoPixel嘅範例程式。
項目3入面嘅兩種Wi-Fi模式
呢個項目最重要嘅概念係ESP32可以喺兩種唔同模式下運作:
- Station(STA)模式 – ESP32連接你現有嘅家用Wi-Fi網絡。
- Access Point(AP)模式 – 如果家用Wi-Fi唔可用,ESP32會建立自己嘅Wi-Fi網絡。
兩種模式都用同一個網頁介面:一個由ESP32自己提供嘅HTML頁面,你可以喺上面改文字、顏色、滾動方向同速度。
模式1 – 連接家用Wi-Fi(Station模式)
喺Station模式入面,ESP32會加入你嘅家居或者辦公室Wi-Fi網絡。呢個係喺路由器可用時嘅首選模式,因為:
- 你嘅手機同電腦已經連接咗同一個Wi-Fi網絡。
- 你可以將瀏覽器指向ESP32嘅IP地址,然後喺嗰個網絡上任何裝置控制文字。
喺程式嘅設定部分,你需要提供你嘅Wi-Fi SSID同密碼:
// 家居 Wi-Fi 憑證(Station 模式)
const char* WIFI_SSID = "YourHomeWiFi";
const char* WIFI_PASS = "YourHomePassword";
喺開發板啟動之後,佢會嘗試連接 WIFI_SSID。如果成功,程式會將分配嘅 IP 地址打印到 Serial Monitor,例如:
Connected to WiFi
IP address: 192.168.1.16
要控制文字:
- 確保你嘅手機或電腦連接咗同一個 Wi-Fi 網絡(例如
YourHomeWiFi)。 - 打開瀏覽器並輸入打印出嚟嘅 IP 地址,例如
http://192.168.1.16/。 - 控制頁面會出現,容許你輸入文字、揀顏色、選擇方向,同埋調整滾動速度。
模式 2 – 獨立存取點(AP 模式)
如果 ESP32 連接唔到你嘅家居 Wi-Fi(密碼錯誤、網絡唔可用,或者你喺戶外使用模組),程式會自動切換到存取點模式。喺 AP 模式,ESP32 本身會變成一個 Wi-Fi 熱點,帶有自己嘅網絡名稱同密碼。
喺呢個項目入面,AP 設定係固定嘅:
// Access Point (AP) credentials (fallback mode)
const char* AP_SSID = "ESP32";
const char* AP_PASS = "password";
當 Station 模式失敗,模組會切換到 AP 模式,並開始廣播一個叫 ESP32 嘅 Wi-Fi 網絡。要控制矩陣:
- 喺你嘅手機或電腦上,打開 Wi-Fi 設定並連接到 ESP32 網絡。
- 輸入密碼 password(同程式入面定義嘅一樣)。
- 連接之後,打開瀏覽器並前往
http://192.168.4.1/(ESP32 AP 模式嘅默認 IP)。 - 相同嘅控制頁面會出現,容許你更改文字、顏色、速度同方向。
呢種後備行為令到項目喺任何地方都有用:喺屋企、實驗室,或者冇路由器嘅演示環境。
項目 3 – 程式入面嘅主要設定
完整嘅 HTTP Text sketch 由網站喺呢篇文章下面載入。呢度我哋只記錄你最可能編輯嘅重要配置選項。
Wi-Fi 同存取點設定
喺 sketch 嘅頂部,你會搵到 Wi-Fi 配置部分。只更改 Station(家居 Wi-Fi)憑證;AP 設定通常保持默認值:
// ---------- Wi-Fi SETTINGS ----------
// Home Wi-Fi (Station mode)
const char* WIFI_SSID = "YourHomeWiFi"; // 喺度輸入你嘅路由器 SSID
const char* WIFI_PASS = "YourHomePassword"; // 喺度輸入你嘅路由器密碼
// Fallback Access Point (AP mode)
const char* AP_SSID = "ESP32"; // 固定 AP 名稱
const char* AP_PASS = "password"; // 固定 AP 密碼
行為:
- 如果
WIFI_SSID同WIFI_PASS正確,而且網絡可用 → ESP32 會作為普通 Wi-Fi 裝置連接(Station 模式)。 - 如果連接喺超時後失敗 → ESP32 會使用
AP_SSID同AP_PASS啟動自己嘅 AP。
矩陣引腳、尺寸同亮度
呢啲設定同之前嘅項目一樣:
// Matrix configuration
const int MATRIX_PIN = 14; // RGB 矩陣數據引腳
const int MATRIX_WIDTH = 8;
const int MATRIX_HEIGHT = 8;
// Overall display brightness (0–255)
uint8_t matrixBrightness = 40; // 根據你嘅環境調整
將 MATRIX_PIN 保持喺 14 以配合呢塊板。如果你需要更多光,可以增加 matrixBrightness,但較低嘅值喺近距離觀看時會更舒適。
默認文字同滾動設定
當開發板啟動時,佢會顯示一個初始訊息,直到你打開網頁並輸入新嘅文字。你可以更改配置入面嘅默認文字:
// Default message shown at startup
String currentText = "Robojax"; // 之後可以從網頁 UI 覆寫
其餘嘅滾動行為由一組由網頁界面更新嘅變數控制:
// Scroll delay in milliseconds (lower = faster)
int scrollDelayMs = 50;
// Scroll direction: 0=left, 1=right, 2=up, 3=down
int scrollDirection = 0; // 默認:向左滾動
網頁會根據滑桿同按鈕選擇發送新值。從 Arduino 方面,你只需要知道:
- 減少
scrollDelayMs會令文字移動得更快。 - 增加
scrollDelayMs會令佢移動得更慢。 - 更改
scrollDirection會喺左、右、上或下滾動模式之間切換。
文字顏色(從網頁控制)
文字顏色由三個 0–255 嘅值(紅、綠、藍)控制。每當你喺網頁上揀新顏色時,佢哋就會更新:
// Current text color (R, G, B)
uint8_t textRed = 255;
uint8_t textGreen = 255;
uint8_t textBlue = 255;
當你喺瀏覽器揀顏色並撳 Apply,ESP32 會解析 RGB 值並更新呢三個變數;文字會即刻喺矩陣上改變顏色。喺影片入面,呢種行為用多種顏色變化嚟示範,包括紅色、綠色同藍色嘅例子。
總結
項目 3 將你嘅 ESP32-S3 RGB LED 矩陣變成一個完全無線嘅文字顯示器,你可以用任何有網頁瀏覽器嘅裝置嚟控制。呢個 sketch 設計得好穩健:
- 佢首先會嘗試用你設定嘅SSID同密碼連接你屋企嘅Wi-Fi。
- 如果咁樣失敗,佢會自動變成一個名稱係
ESP32、密碼係password嘅存取點。 - 喺兩種模式之下,喺瀏覽器打開正確嘅IP地址都會顯示同一個控制頁面,用嚟控制文字、顏色、方向同速度。
完整嘅HTTP文字代碼喺呢篇文章下面有得睇(會喺網站上自動載入)。如果想睇逐步詳細講解同文字即時更新嘅現場示範,記得睇影片入面Project 3嘅部分。如果你想自己整呢個項目,都可以用下面代碼列出嘅聯盟連結購買ESP32-S3 RGB LED矩陣模組。
This tutorial is part of: ESP32-S3 RGB LED 点阵
- ESP32-S3 RGB LED矩陣項目1-基本點
- ESP32-S3 RGB LED矩陣項目2 - 滾動文字
- ESP32-S3 RGB LED矩陣項目 4 - 傾斜點
- ESP32-S3 RGB LED矩陣項目5 - 箭嘴永遠向上
- ESP32-S3 RGB LED矩陣項目6 - Cible遊戲
- ESP32-S3 RGB LED矩陣 Wi-Fi + NTP時鐘項目 -1 基本時鐘
- ESP32-S3 RGB LED矩陣網絡時鐘項目 - 2個時鐘多色時間及日期顯示
- ESP32-S3 RGB LED矩陣互聯網時鐘項目 - 3種夜間顏色連日期
- ESP32-S3 RGB LED矩陣網絡時鐘項目 - 5 彩虹色
- ESP32-S3 RGB LED矩陣網絡時鐘項目 - 4種隨機顏色
- ESP32-S3 RGB LED Matrix test for RGB, GRB setting
/*
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')">▲</button>
<div></div>
<button class="arrow-btn" id="btnLeft" onclick="setDir('left')">◀</button>
<div></div>
<button class="arrow-btn" id="btnRight" onclick="setDir('right')">▶</button>
<div></div>
<button class="arrow-btn" id="btnDown" onclick="setDir('down')">▼</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();
}
}
你可能需要嘅嘢
-
亞馬遜
-
易贝
-
阿里巴巴速卖通Purchase ESP32-S3 RGB Matrix from AliExpresss.click.aliexpress.com
-
阿里巴巴速卖通Purchase ESP32-S3 RGB Matrix from AliExpress (2)s.click.aliexpress.com
資源與參考資料
-
內部🎨 Color picker Toolrobojax.com
文件📁
Fritzing 文件
-
esp32-S3-supermini-tht fritzing part
esp32-S3-supermini-tht.fzpz0.02 MB