This tutorial is part of: ESP32-S3 RGB LED 点阵
这是一个很棒的项目,可以使用 ESP32-S3 RGB 矩阵模块进行创作,兼具趣味性和实用性。本文下方有其他相关视频的链接。
ESP32-S3 RGB LED矩陣項目5 - 箭嘴永遠向上
項目5 – 箭嘴永遠向上(用QMI8658C嘅方向指示器)
項目5用QMI8658C動作感應器嚟偵測ESP32-S3 RGB LED矩陣嘅方向,並且永遠顯示一個相對於地心吸力指向UP嘅箭嘴。無論你點樣旋轉塊板——USB邊向上、OUSB邊向上、“15”邊或者“34”邊——個箭嘴都會自動轉向,指向物理上向上嘅方向。
呢個係一個強勁嘅示範,展示咗用板上加速計嚟做實時方向感應。呢個模組嘅全部六個項目都會喺一條YouTube影片入面示範,條片亦都嵌入咗喺呢一頁。項目5嘅完整程式碼會自動載入喺文章下面,而聯盟連結會出現喺程式碼部分下面。

ESP32-S3 RGB LED矩陣模組概覽
ESP32-S3 RGB LED矩陣模組包含幾個令到呢個項目可以實現嘅組件:

- ESP32-S3微控制器 — 提供Wi-Fi、BLE,同埋執行LED/IMU邏輯。
- 8×8 RGB LED矩陣 — 以四種方向嘅任何一種顯示箭嘴。
- QMI8658C加速計 — 偵測傾斜、移動同方向。
- USB-C接口 用嚟供電同喺Arduino IDE入面編程。
- Reset同Boot按鈕 用嚟上載草圖。
- GPIO引腳 喺板周圍可用,用嚟做其他項目。
箭嘴方向完全由加速計讀數決定。當塊板旋轉時,QMI8658C會感應到新嘅X/Y/Z數值,而草圖會選擇應該畫邊個箭嘴圖案(↑、↓、←、→)。

影片入面涵蓋嘅項目(時間戳)
- 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:目標遊戲
條片清楚顯示咗箭嘴點樣根據塊板嘅轉動而即時改變方向。強烈建議睇吓呢一段。

喺Arduino IDE入面安裝ESP32板
如果你已經完成咗之前任何一個項目,塊板嘅設定已經做好咗。否則:
File > Preferences→ 加入ESP32板URLTools > Board > Boards Manager…→ 安裝“ESP32”- 喺
Tools > Board下面選擇ESP32-S3板 - 喺
Tools > Port下面選擇正確嘅USB COM埠
安裝所需程式庫
項目5用到:
Adafruit NeoMatrixAdafruit NeoPixelAdafruit GFXQMI8658(動作感應器)
Sketch > Include Library > Manage Libraries…- 搜尋:NeoMatrix → 安裝
- 安裝依賴項目:NeoPixel + GFX
- 搜尋並安裝QMI8658(按作者)
項目5點樣運作
QMI8658C量度X、Y同Z軸上面嘅重力。通過比較呢啲數值,草圖會判斷塊板邊個物理面係向上:
- USB邊向上
- OUSB邊向上(USB嘅對面)
- “15”邊向上
- “34”邊向上
每個方向都對應8×8矩陣上面一個唔同嘅箭嘴圖案。呢個對應關係跟隨你之前除錯環節入面確認嘅方向邏輯。塊板嘅旋轉會被持續讀取,而箭嘴會喺向上嘅面一改變嘅時候即刻更新。
項目5 – 程式碼設定(箭嘴永遠向上)
下面係嚟自設定區域、用戶可以調整嘅數值。完整嘅項目程式碼會自動出現喺文章下面。
矩陣設定
// 矩陣設定
const int MATRIX_PIN = 14; // 呢個模組固定用呢個
const int MATRIX_WIDTH = 8;
const int MATRIX_HEIGHT = 8;
// 建議方向:左上角原點,漸進模式
// (實際建構子喺下面載入嘅程式碼入面)
呢個項目用NEO_MATRIX_PROGRESSIVE排列,確保箭嘴根據實際移動正確指向。
亮度
uint8_t matrixBrightness = 40; // 0–255
你可以提高呢個數值嚟應付更光嘅環境。室內使用嘅話,30–60會比較舒服。
箭嘴顏色
// 箭嘴顏色
uint8_t arrowRed = 255;
uint8_t arrowGreen = 0;
uint8_t arrowBlue = 0;
改變呢啲數值嚟修改箭嘴嘅顏色。例如:
- 綠色箭嘴:
(0, 255, 0) - 藍色箭嘴:
(0, 0, 255) - 白色箭嘴:
(255, 255, 255)
靈敏度同平滑處理
為咗避免震動,程式碼包含咗平滑同門檻邏輯。喺設定入面你可能會見到類似咁嘅嘢:
// 靈敏度/平滑調整
float tiltThreshold = 0.30f; // 如果箭嘴太容易改變就調整呢個
- 如果你嘅箭咀太容易反轉 → 增加 閾值。
- 如果箭咀轉得太慢 → 減少 閾值。
箭咀圖案
呢個草圖包含以下箭咀位圖圖案:
- ↑ 上
- ↓ 下
- ← 左
- → 右
你唔需要修改呢啲,但如果你想用唔同風格,都可以改代碼入面嘅形狀。
總結
項目 5 示範咗 ESP32-S3 RGB LED Matrix 同 QMI8658C 加速度計點樣一齊運作,偵測方向並顯示一個永遠指向上方嘅箭咀。呢個項目建基於 Tilt Dot(項目 4),並為你準備好項目 6 嘅最終互動遊戲。
完整嘅「箭咀永遠向上」草圖喺呢篇文章下面提供(自動載入)。強烈建議睇返對應嘅影片部分,睇吓箭咀對電路板旋轉嘅即時反應。如果你想喺屋企整呢個項目,ESP32-S3 RGB LED Matrix 模組嘅聯盟連結會喺代碼部分下面出現。
圖片
This tutorial is part of: ESP32-S3 RGB LED 点阵
- ESP32-S3 RGB LED矩陣項目1-基本點
- ESP32-S3 RGB LED矩陣項目2 - 滾動文字
- ESP32-S3 RGB LED矩陣項目3 - 手機文字
- ESP32-S3 RGB LED矩陣項目 4 - 傾斜點
- 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 5: Arrow Always Up – ESP32-S3 RGB LED Matrix (Waveshare)
This sketch reads tilt from the QMI8658C IMU and smoothly moves a dot
on the 8×8 RGB LED matrix based on board orientation.
▶️ Video Tutorial:
https://youtu.be/JKLuYrRcLMI
📚⬇️ Resources & Code Page:
https://robojax.com/RJT833
QMI8658_RGB_2
*/
#include <Arduino.h>
#include <math.h>
#include <Adafruit_GFX.h>
#include <Adafruit_NeoMatrix.h>
#include <Adafruit_NeoPixel.h>
#include <QMI8658.h> // by Lahav Gahali
// -------- LED MATRIX SETUP --------
#define MATRIX_PIN 14
#define MATRIX_WIDTH 8
#define MATRIX_HEIGHT 8
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
);
// -------- QMI8658 IMU SETUP --------
QMI8658 imu;
QMI8658_Data imuData;
// -------- USER SETTINGS --------
// true -> arrow points to opposite side
// USB↔OUSB, 34↔15
// false -> arrow points to the same side that is UP
bool useOppositeMapping = false;
// Arrow color (0–255 each)
uint8_t dotRed = 0;
uint8_t dotGreen = 150;
uint8_t dotBlue = 0;
// Board sides
enum Side {
SIDE_CENTER = 0,
SIDE_USB,
SIDE_OUSB,
SIDE_15,
SIDE_34
};
// Direction for arrow drawing
enum ArrowDir {
ARROW_CENTER,
ARROW_UP,
ARROW_DOWN,
ARROW_LEFT,
ARROW_RIGHT
};
bool isFlat = false;
const char* sideName(Side s) {
switch (s) {
case SIDE_CENTER: return "CENTER";
case SIDE_USB: return "USB";
case SIDE_OUSB: return "OUSB";
case SIDE_15: return "15";
case SIDE_34: return "34";
default: return "?";
}
}
// -------- ARROW DRAWING (YOUR CODE, UNCHANGED) --------
// Draw a simple arrow on 8x8 matrix pointing in the given direction
void drawArrow(ArrowDir dir, uint16_t color) {
matrix.fillScreen(0);
switch (dir) {
case ARROW_UP:
// Tip
matrix.drawPixel(3, 0, color);
matrix.drawPixel(4, 0, color);
// Second row
matrix.drawPixel(2, 1, color);
matrix.drawPixel(3, 1, color);
matrix.drawPixel(4, 1, color);
matrix.drawPixel(5, 1, color);
// Shaft
matrix.drawLine(3, 2, 3, 6, color);
matrix.drawLine(4, 2, 4, 6, color);
break;
case ARROW_DOWN:
// Tip
matrix.drawPixel(3, 7, color);
matrix.drawPixel(4, 7, color);
// Row above tip
matrix.drawPixel(2, 6, color);
matrix.drawPixel(3, 6, color);
matrix.drawPixel(4, 6, color);
matrix.drawPixel(5, 6, color);
// Shaft
matrix.drawLine(3, 1, 3, 5, color);
matrix.drawLine(4, 1, 4, 5, color);
break;
case ARROW_LEFT:
// Tip
matrix.drawPixel(0, 3, color);
matrix.drawPixel(0, 4, color);
// Column after tip
matrix.drawPixel(1, 2, color);
matrix.drawPixel(1, 3, color);
matrix.drawPixel(1, 4, color);
matrix.drawPixel(1, 5, color);
// Shaft
matrix.drawLine(2, 3, 6, 3, color);
matrix.drawLine(2, 4, 6, 4, color);
break;
case ARROW_RIGHT:
// Tip
matrix.drawPixel(7, 3, color);
matrix.drawPixel(7, 4, color);
// Column before tip
matrix.drawPixel(6, 2, color);
matrix.drawPixel(6, 3, color);
matrix.drawPixel(6, 4, color);
matrix.drawPixel(6, 5, color);
// Shaft
matrix.drawLine(1, 3, 5, 3, color);
matrix.drawLine(1, 4, 5, 4, color);
break;
case ARROW_CENTER:
default:
// Simple plus in the center
matrix.drawLine(3, 3, 4, 3, color);
matrix.drawLine(3, 4, 4, 4, color);
matrix.drawLine(3, 3, 3, 4, color);
matrix.drawLine(4, 3, 4, 4, color);
break;
}
matrix.show();
}
// -------- IMU → SIDE DETECTION --------
// We calibrated earlier:
// +X = USB, -X = OUSB
// +Y = 15, -Y = 34 (after your correction)
Side detectSideUp(float ax_g, float ay_g, float az_g) {
// Flat detection
const float flatThreshXY = 0.15f;
const float flatThreshZ = 0.15f;
if (fabs(ax_g) < flatThreshXY &&
fabs(ay_g) < flatThreshXY &&
fabs(az_g - 1.0f) < flatThreshZ) {
isFlat = true;
return SIDE_CENTER;
}
isFlat = false;
// Thresholds to say "this axis is really tilted"
const float tiltThreshY = 0.5f;
const float tiltThreshX = 0.5f;
// Prefer Y axis for 15 / 34
if (fabs(ay_g) >= tiltThreshY) {
if (ay_g > 0) {
return SIDE_34; // +Y = 34 up
} else {
return SIDE_15; // -Y = 15 up
}
}
// Otherwise, use X axis for USB / OUSB
if (fabs(ax_g) >= tiltThreshX) {
if (ax_g > 0) {
return SIDE_USB; // +X = USB up
} else {
return SIDE_OUSB; // -X = OUSB up
}
}
// Not clearly tilted → treat as center
return SIDE_CENTER;
}
// Map from UP side to where the arrow should point
Side arrowSideFromUpSide(Side upSide) {
if (!useOppositeMapping) {
// Arrow shows the side that is UP
return upSide;
}
// Arrow shows the opposite side
switch (upSide) {
case SIDE_USB: return SIDE_OUSB;
case SIDE_OUSB: return SIDE_USB;
case SIDE_15: return SIDE_34;
case SIDE_34: return SIDE_15;
case SIDE_CENTER:
default: return SIDE_CENTER;
}
}
// Convert SIDE to ArrowDir
ArrowDir arrowDirFromSide(Side s) {
switch (s) {
case SIDE_USB: return ARROW_UP;
case SIDE_OUSB: return ARROW_DOWN;
case SIDE_15: return ARROW_LEFT;
case SIDE_34: return ARROW_RIGHT;
case SIDE_CENTER:
default: return ARROW_CENTER;
}
}
// ---------------- SETUP & LOOP ----------------
void setup() {
Serial.begin(115200);
delay(500);
matrix.begin();
matrix.setBrightness(20);
matrix.fillScreen(0);
matrix.show();
// IMU: SDA=11, SCL=12 on ESP32-S3-Matrix
if (!imu.begin(11, 12)) {
Serial.println("Failed to initialize QMI8658!");
while (1) { delay(1000); }
}
imu.setAccelUnit_mg(true);
imu.setGyroUnit_dps(true);
imu.setDisplayPrecision(4);
Serial.print("QMI8658 initialized. useOppositeMapping = ");
Serial.println(useOppositeMapping ? "TRUE" : "FALSE");
}
void loop() {
if (!imu.readSensorData(imuData)) {
return;
}
float ax_g = imuData.accelX / 1000.0f;
float ay_g = imuData.accelY / 1000.0f;
float az_g = imuData.accelZ / 1000.0f;
Side upSide = detectSideUp(ax_g, ay_g, az_g);
Side arrowSide = arrowSideFromUpSide(upSide);
ArrowDir dir = arrowDirFromSide(arrowSide);
uint16_t color = matrix.Color(dotRed, dotGreen, dotBlue);
drawArrow(dir, color);
// Debug
Serial.print("AX="); Serial.print(ax_g, 3);
Serial.print(" AY="); Serial.print(ay_g, 3);
Serial.print(" AZ="); Serial.print(az_g, 3);
Serial.print(" | UP="); Serial.print(sideName(upSide));
Serial.print(" | ARROW="); Serial.println(sideName(arrowSide));
delay(80);
}
你可能需要嘅嘢
-
亞馬遜
-
易贝
-
阿里巴巴速卖通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