Search Code

ESP32-S3 RGB LED矩陣項目5 - 箭嘴永遠向上

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板

如果你已經完成咗之前任何一個項目,塊板嘅設定已經做好咗。否則:

  1. File > Preferences → 加入ESP32板URL
  2. Tools > Board > Boards Manager… → 安裝“ESP32”
  3. Tools > Board下面選擇ESP32-S3板
  4. Tools > Port下面選擇正確嘅USB COM埠

安裝所需程式庫

項目5用到:

  • Adafruit NeoMatrix
  • Adafruit NeoPixel
  • Adafruit GFX
  • QMI8658(動作感應器)
  1. Sketch > Include Library > Manage Libraries…
  2. 搜尋:NeoMatrix → 安裝
  3. 安裝依賴項目:NeoPixel + GFX
  4. 搜尋並安裝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 模組嘅聯盟連結會喺代碼部分下面出現。

圖片

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 attached with buzzer to pin6 and GND
ESP32 S3 Matrix attached with buzzer to pin6 and GND
ESP32 S3 Matrix displaying red heart
ESP32 S3 Matrix displaying red heart
ESP32 S3 Matrix displaying green heart
ESP32 S3 Matrix displaying green heart
ESP32 S3 Matrix displaying rainbow heart 3
ESP32 S3 Matrix displaying rainbow heart 3
ESP32-S3_RGB_8x8_matrix1
ESP32-S3_RGB_8x8_matrix1
ESP32-S3_RGB_8x8_matrix-2
ESP32-S3_RGB_8x8_matrix-2
ESP32-S3-Mtrix - Alway Up
ESP32-S3-Mtrix - Alway Up
803-ESP32-S3 RGB LED Matrix Project 5 - Arrow always up
語言: C++
/* 
  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);
}

你可能需要嘅嘢

資源與參考資料

文件📁

Fritzing 文件