Search Code

Dự án Ma trận LED RGB ESP32-S3 4 - Chấm nghiêng

Dự án Ma trận LED RGB ESP32-S3 4 - Chấm nghiêng

Dự án 4 – Chấm Nghiêng (Di Chuyển Chấm Bằng Cách Nghiêng ESP32-S3 RGB LED Matrix)

Dự án 4 giới thiệu cảm biến chuyển động được tích hợp trong mô-đun ESP32-S3 RGB LED Matrix. Thay vì tự động di chuyển chấm (như Dự án 1) hoặc cuộn chữ (Dự án 2 và 3), dự án này cho phép bạn điều khiển vị trí của chấm chỉ bằng cách nghiêng bo mạch. Chấm di chuyển mượt mà trên màn hình RGB 8×8 dựa trên số liệu trực tiếp từ gia tốc kế QMI8658C ở mặt sau của mô-đun.

Tất cả sáu dự án trong loạt bài này được trình diễn trong một video YouTube. Video tương tự cũng được nhúng trên trang này, để bạn có thể thấy chính xác cách chấm di chuyển trong thời gian thực khi bo mạch nghiêng. Mã hoàn chỉnh cho dự án này được tải tự động bên dưới bài viết, và các liên kết mua hàng liên kết cho mô-đun xuất hiện dưới phần mã.

Tổng Quan Về Mô-đun ESP32-S3 RGB LED Matrix

Dự án này sử dụng mô-đun ESP32-S3 RGB LED Matrix, bao gồm:

  • Vi điều khiển ESP32-S3 với Wi-Fi và Bluetooth
  • Ma trận LED RGB 8×8 (64 đèn LED có thể định địa chỉ riêng lẻ)
  • Gia tốc kế QMI8658C ở mặt sau để phát hiện độ nghiêng và chuyển động
  • Cổng USB để lập trình và cấp nguồn
  • Các nút Boot / Reset
  • Các chân GPIO có thể sử dụng để mở rộng trong tương lai

Cảm biến QMI8658C đọc các giá trị gia tốc và hướng theo trục X, YZ, cho phép chấm di chuyển lên/xuống/trái/phải dựa trên cách bo mạch được nghiêng.

Các Dự Án Được Trình Bày Trong Video (Mốc Thời Gian)

  • 00:00 – Giới thiệu
  • 02:01 – Cài đặt bo mạch ESP32
  • 03:32 – Cài đặt thư viện
  • 05:32 – Dự án 1: Chấm Di Chuyển
  • 11:11 – Dự án 2: Cuộn Chữ
  • 12:59 – Dự án 3: Văn Bản HTTP
  • 16:41Dự án 4: Chấm Nghiêng (dự án này)
  • 18:55 – Dự án 5: Mũi Tên Lên
  • 20:02 – Dự án 6: Trò Chơi Bắn Mục Tiêu

Xem phần trình diễn độ nghiêng trong video được khuyến khích cao, vì bạn có thể thấy cách chấm phản ứng mượt mà với hướng của bo mạch.

Cài Đặt Bo Mạch ESP32 Trong Arduino IDE

Nếu bạn đã hoàn thành bất kỳ dự án nào trước đó, hỗ trợ bo mạch đã được cài đặt sẵn. Nếu không, hãy làm theo các bước sau:

  1. Mở File > Preferences → Thêm URL ESP32 Boards.
  2. Đi tới Tools > Board > Boards Manager… và cài đặt ESP32.
  3. Chọn bo mạch ESP32-S3 của bạn trong Tools > Board.
  4. Chọn đúng cổng COM trong Tools > Port.

Cài Đặt Các Thư Viện Cần Thiết

Dự án này yêu cầu các thư viện sau:

  • Adafruit NeoMatrix
  • Adafruit NeoPixel
  • Adafruit GFX Library
  • QMI8658 (cảm biến chuyển động)

Cài đặt chúng trong Library Manager:

  1. Mở Sketch > Include Library > Manage Libraries….
  2. Tìm kiếm Adafruit NeoMatrix → Cài đặt.
  3. Cho phép cài đặt tự động Adafruit GFXAdafruit NeoPixel.
  4. Tìm kiếm QMI8658 theo tác giả được liệt kê → Cài đặt.

Cách Dự Án 4 Hoạt Động

Cảm biến QMI8658C liên tục cung cấp dữ liệu gia tốc dọc theo các trục X, Y và Z. Đối với dự án này, chúng ta chỉ sử dụng trục X và Y để quyết định:

  • Khoảng cách di chuyển của chấm sang trái hoặc phải (trục X)
  • Khoảng cách di chuyển của chấm lên hoặc xuống (trục Y)

Các giá trị cảm biến được ánh xạ vào phạm vi tọa độ từ 0 đến 7 (cho ma trận LED 8×8). Vị trí chấm được cập nhật nhiều lần mỗi giây, tạo hiệu ứng trượt mượt mà khi bạn nghiêng mô-đun.

Dự án 4 – Cài Đặt Mã (Chấm Nghiêng)

Dưới đây là các cài đặt người dùng có thể chỉnh sửa được tìm thấy gần đầu mã của dự án. Bản phác thảo đầy đủ xuất hiện tự động bên dưới bài viết.

Chân và Kích Thước Ma Trận


// Cấu hình ma trận RGB 8×8
const int MATRIX_PIN    = 14;   // chân cố định cho bo mạch này
const int MATRIX_WIDTH  = 8;
const int MATRIX_HEIGHT = 8;

Giữ nguyên MATRIX_PIN ở giá trị 14. Nó được nối cứng với ma trận trên bo mạch.

Độ Sáng


// Độ sáng tổng thể (0–255)
uint8_t matrixBrightness = 40;

Tăng nếu cần, nhưng tránh độ sáng quá cao khi nhìn ở khoảng cách gần.

Màu Chấm


// Màu chấm (R, G, B)
uint8_t dotRed   = 0;
uint8_t dotGreen = 200;   // xanh lá nhạt (mặc định)
uint8_t dotBlue  = 0;

Thay đổi các giá trị này để tạo bất kỳ màu nào. Ví dụ:

  • Đỏ: (255, 0, 0)
  • Vàng: (255, 255, 0)
  • Trắng: (255, 255, 255)

Độ Nhạy Chuyển Động

Để tránh các bước nhảy quá lớn, các giá trị gia tốc kế thường được giới hạn hoặc chia tỷ lệ. Một cài đặt điển hình trông như:


// Mức độ ảnh hưởng của độ nghiêng đến chuyển động
float sensitivity = 4.0f;   // lớn hơn = di chuyển nhanh hơn trên màn hình

Nếu chấm di chuyển quá chậm → tăng giá trị. Nếu chấm di chuyển quá đột ngột → giảm giá trị.

Tốc Độ Cập Nhật (Tần Suất Làm Mới)

Bạn có thể thêm một khoảng trễ nhỏ giữa các lần cập nhật để chuyển động mượt mà hơn:


// Độ trễ giữa các lần cập nhật vị trí (ms)
int refreshDelayMs = 20;   // thấp hơn = phản hồi mượt và nhanh hơn

Các giá trị từ 10–30 ms sẽ cho cảm giác rất nhạy bén.

Tóm tắt

Dự án 4 đưa cảm biến gia tốc QMI8658C tích hợp trên ESP32-S3 vào hoạt động bằng cách cho phép bạn điều khiển ma trận LED bằng chuyển động vật lý. Một cú nghiêng nhỏ của bo mạch sẽ di chuyển chấm theo cùng hướng, biến dự án này thành bước đệm hoàn hảo cho các dự án nâng cao hơn như “Mũi tên lên” và “Trò chơi bắn mục tiêu”.

Mã nguồn đầy đủ của Tilt Dot được tải bên dưới bài viết này (tự động). Để hiểu rõ nhất, hãy xem phần trình diễn độ nghiêng trong video, nơi bạn có thể thấy chấm di chuyển mượt mà như thế nào khi bo mạch được xoay. Liên kết mua mô-đun ESP32-S3 RGB LED Matrix được bao gồm dưới phần mã nguồn.

Hình ảnh

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
802-ESP32-S3 RGB LED Matrix Project 4 - Tilt dot
Ngôn ngữ: C++
/* 
  Project 4: Tilt Dot – 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/RJT829

  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  -> dot on opposite side: USB↔OUSB, 34↔15
// false -> dot on same side as UP
bool useOppositeMapping = false;

// Dot color (0–255 each)
uint8_t dotRed   = 0;
uint8_t dotGreen = 100;
uint8_t dotBlue  = 0;

// Board sides
enum Side {
  SIDE_CENTER = 0,
  SIDE_USB,
  SIDE_OUSB,
  SIDE_15,
  SIDE_34
};

// Smooth dot position (in pixel coordinates, but kept as float for easing)
float dotPosX = 3.0f;   // start at center
float dotPosY = 3.0f;

// Smoothing factor: smaller = slower movement (0.1 very slow, 0.5 faster)
const float dotSmooth = 0.25f;


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 "?";
  }
}

// Detect which side is UP using calibrated axes:
// +X = USB, -X = OUSB, +Y = 34, -Y = 15
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;

  // 1) Prefer Y axis for 15 / 34 if it's clearly tilted
  if (fabs(ay_g) >= tiltThreshY) {
    if (ay_g > 0) {
      return SIDE_15;   // +Y = 34 up
    } else {
      return SIDE_34;   // -Y = 15 up
    }
  }

  // 2) Otherwise, check 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
    }
  }

  // 3) If nothing is strongly tilted, just call it CENTER
  return SIDE_CENTER;
}

// Map from UP side to where the dot should go
Side dotSideFromUpSide(Side upSide) {
  switch (upSide) {
    case SIDE_USB:
      return useOppositeMapping ? SIDE_OUSB : SIDE_USB;

    case SIDE_OUSB:
      return useOppositeMapping ? SIDE_USB : SIDE_OUSB;

    case SIDE_34:
      return useOppositeMapping ? SIDE_15 : SIDE_34;

    case SIDE_15:
      return useOppositeMapping ? SIDE_34 : SIDE_15;

    case SIDE_CENTER:
    default:
      return SIDE_CENTER;
  }
}

// Convert dot side to matrix coordinates
void getDotPixel(Side dotSide, int &px, int &py) {
  // Matrix (0,0) = top-left
  // top center:    (3,0) → USB
  // bottom center: (3,7) → OUSB
  // left center:   (0,3) → 15
  // right center:  (7,3) → 34
  // center:        (3,3)

  switch (dotSide) {
    case SIDE_USB:   px = 3; py = 0; break;
    case SIDE_OUSB:  px = 3; py = 7; break;
    case SIDE_15:    px = 0; py = 3; break;
    case SIDE_34:    px = 7; py = 3; break;
    case SIDE_CENTER:
    default:         px = 3; py = 3; break;
  }
}

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

  matrix.begin();
  matrix.setBrightness(20);
  matrix.fillScreen(0);
  matrix.show();

  // IMU: SDA=11, SCL=12 for 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 dotSide = dotSideFromUpSide(upSide);

  int targetX, targetY;
  getDotPixel(dotSide, targetX, targetY);

  // --- Smooth movement toward target ---
  dotPosX += (targetX - dotPosX) * dotSmooth;
  dotPosY += (targetY - dotPosY) * dotSmooth;

  // Convert to integer pixel coordinates
  int px = (int)round(dotPosX);
  int py = (int)round(dotPosY);

  // Clamp just in case
  if (px < 0) px = 0;
  if (px > 7) px = 7;
  if (py < 0) py = 0;
  if (py > 7) py = 7;

  // --- Draw dot ---
  matrix.fillScreen(0);
  uint16_t color = matrix.Color(dotRed, dotGreen, dotBlue);
  matrix.drawPixel(px, py, color);
  matrix.show();

  // 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(" | DOT="); Serial.print(sideName(dotSide));
  Serial.print(" | px=");  Serial.print(px);
  Serial.print(" py=");    Serial.println(py);

  delay(80);
}



Nghĩ bạn có thể cần

Tài nguyên & tài liệu tham khảo

Tập tin📁

Tập tin Fritzing