التحكم في مصفوفة LED بحركة اليد باستخدام ESP32 وحساس MPU6050

هل يمكن تحريك مربع مضيء على مصفوفة LED بمجرد إمالة حساس صغير؟ في الحلقة الثالثة عشرة من سلسلة ESP32 من الصفر سنصنع مشروعًا تفاعليًا يجمع بين لوحة ESP32 وحساس الحركة MPU6050 ومصفوفة LED بحجم 8×8 مزودة بالشريحة MAX7219. سنبدأ بقراءة التسارع، ثم نحول إمالة الحساس إلى حركة ضوئية على المصفوفة.

فيديو المشروع

يمكنكم متابعة خطوات التجميع والبرمجة والتجربة العملية في الفيديو رقم 13 من سلسلة ESP32 من الصفر على قناة نادي تك – نادي الابتكار.

فكرة المشروع وكيف يعمل

نضع حساس GY-521 في اليد ونميله في اتجاهات مختلفة. تقرأ لوحة ESP32 قيم التسارع على المحورين X وY، ثم تحولها إلى إحداثيات على مصفوفة LED. نضيء أربعة مصابيح متجاورة لتشكيل مربع بحجم 2×2؛ يتحرك المربع حسب اتجاه الإمالة ومقدارها، ويعود إلى الوسط عندما نعيد الحساس إلى الوضع الأفقي.

يتضمن المشروع مرحلتين: الأولى لاختبار قراءة حساس الحركة عبر Serial Monitor، والثانية لإضافة مصفوفة MAX7219 وتحريك المربع المضيء مع المعايرة والفلترة.

المكونات المطلوبة

  • لوحة ESP32.
  • وحدة GY-521 التي تحتوي على حساس MPU6050.
  • مصفوفة LED بحجم 8×8 مع الشريحة MAX7219.
  • لوحتا تجارب Breadboard.
  • أسلاك توصيل Jumper Wires.
  • كابل USB وحاسوب مثبت عليه Arduino IDE.

ما هو حساس MPU6050؟

يجمع MPU6050 بين عنصرين داخل شريحة واحدة: Accelerometer لقياس التسارع على المحاور الثلاثة X وY وZ، وGyroscope لقياس السرعة الزاوية حول هذه المحاور. في هذا المشروع سنستخدم مقياس التسارع فقط، وسنستفيد من تأثير الجاذبية لمعرفة اتجاه إمالة الحساس.

عندما يكون الحساس أفقيًا وثابتًا، تكون مركبتا التسارع على X وY قريبتين من الصفر إذا كان محورا الحساس موازيين للأرض، بينما تظهر قيمة قريبة من 9.81 m/s² على المحور Z، وقد تكون الإشارة موجبة أو سالبة حسب اتجاه تركيب الحساس. عند الإمالة تتغير مركبات الجاذبية المقاسة على المحاور.

التجربة الأولى: توصيل GY-521 مع ESP32

يتواصل الحساس مع ESP32 باستخدام بروتوكول I2C عبر خطي البيانات SDA والتوقيت SCL. في التجربة نستخدم GPIO21 وGPIO22.

GY-521ESP32الوظيفة
VCC3.3Vتغذية الوحدة
GNDGNDالأرضي
SDAGPIO21بيانات I2C
SCLGPIO22توقيت I2C

ملاحظة: نستخدم 3.3V لتغذية وحدة GY-521 في هذا المشروع. تحقق من مواصفات وحدتك ومقاومات الرفع على خطوط I2C قبل توصيلها مع ESP32.

تثبيت مكتبة Adafruit MPU6050

افتح Arduino IDE ثم انتقل إلى Library Manager وابحث عن Adafruit MPU6050 وثبت المكتبة التي طورتها Adafruit. وافق على تثبيت المكتبات التابعة اللازمة إذا طلب منك البرنامج ذلك، ومنها Adafruit Unified Sensor وAdafruit BusIO حسب الحاجة.

قراءة التسارع عبر Serial Monitor

نستدعي المكتبات المطلوبة وننشئ كائنًا للتحكم في MPU6050. داخل setup() نبدأ الاتصال التسلسلي بسرعة 115200، ثم نهيئ I2C عبر Wire.begin(21, 22)، ونتحقق من وجود الحساس باستخدام mpu.begin().

داخل loop() نستخدم getEvent() للحصول على قراءات التسارع على المحاور X وY وZ، ثم نطبع القيم في Serial Monitor كل 300 ملي ثانية تقريبًا. عند إمالة الحساس نلاحظ تغير قيم المحاور وفق اتجاه الإمالة.

كود التجربة الأولى: قراءة الحساس

#include <Wire.h>
#include <Adafruit_MPU6050.h>
#include <Adafruit_Sensor.h>

Adafruit_MPU6050 mpu;

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

  Wire.begin(21, 22);

  if (!mpu.begin()) {
    Serial.println("MPU6050 non detecte !");
    while (true) {
      delay(100);
    }
  }

  Serial.println("MPU6050 detecte !");
  mpu.setAccelerometerRange(MPU6050_RANGE_2_G);
}

void loop() {
  sensors_event_t a, g, temp;
  mpu.getEvent(&a, &g, &temp);

  Serial.print("X: ");
  Serial.print(a.acceleration.x, 2);

  Serial.print(" | Y: ");
  Serial.print(a.acceleration.y, 2);

  Serial.print(" | Z: ");
  Serial.println(a.acceleration.z, 2);

  delay(300);
}

التجربة الثانية: إضافة مصفوفة LED 8×8

بعد نجاح اختبار الحساس نحتفظ بتوصيلاته السابقة ونضيف مصفوفة LED التي تضم 64 مصباحًا وتستخدم الشريحة MAX7219 للتحكم في الإضاءة. هذه هي المصفوفة التي تعرفنا عليها سابقًا في الحلقة السادسة من السلسلة.

MAX7219ESP32الوظيفة
VCC5Vتغذية المصفوفة في تجربة الفيديو
GNDGNDأرضي مشترك
DINGPIO23إدخال البيانات
CLKGPIO18الساعة
CSGPIO5اختيار الشريحة

تنبيه تقني: بعض وحدات MAX7219 التي تعمل بتغذية 5V قد لا تضمن مواصفاتها التعرف على إشارة HIGH الصادرة من ESP32 بجهد 3.3V. إذا ظهرت مشكلات في الاتصال أو أردت تصميمًا موثوقًا، فاستخدم محول مستويات منطقية مناسبًا بين ESP32 وخطوط DIN وCLK وCS، وراجع مواصفات الوحدة. احرص على توحيد GND.

مكتبة MD_MAX72xx للتحكم في المصفوفة

سنستخدم مكتبة MD_MAX72xx للتحكم في مصابيح المصفوفة. ثبتها من Library Manager إذا لم تكن مثبتة. يجب أيضًا اختيار نوع العتاد المناسب للمصفوفة في إعدادات الكود لأن ترتيب توصيل المصابيح قد يختلف بين وحدات MAX7219.

كيف نرسم مربعًا مضيئًا بحجم 2×2؟

نحدد متغيرين posX وposY لموضع المربع. تقوم دالة drawSquare() بإطفاء الرسم السابق ثم تشغيل أربعة مصابيح متجاورة لتشكيل مربع 2×2 في الموضع الجديد.

بما أن عرض المصفوفة ثمانية مصابيح، فإن أول مصباح في مربع عرضه اثنان يمكن أن يبدأ فقط عند إحداثي بين 0 و6 على كل محور؛ بهذه الطريقة لا يتجاوز المربع حدود الشاشة.

تحويل الإمالة إلى إحداثيات

تقوم دالة tiltToPosition() بتحويل قيمة التسارع إلى إحداثي مناسب من 0 إلى 6. كلما زادت الإمالة في أحد الاتجاهات تغير موضع المربع أكثر، ويمكن الجمع بين المحورين X وY لتحريكه بشكل قطري.

المعايرة عند بدء التشغيل

قبل تشغيل الحركة نضع الحساس في وضع أفقي وثابت. يأخذ البرنامج عدة قراءات لتحديد قيم مرجعية، ثم يطرحها من القياسات الجديدة. يساعد ذلك على جعل الموضع الأفقي يقابل وسط المصفوفة تقريبًا.

الفلترة والمنطقة المحايدة

تتغير قراءات الحساس قليلًا حتى عند تثبيته. لذلك يستخدم المشروع فلترة بسيطة لتقليل الاهتزازات، بالإضافة إلى منطقة محايدة Dead Zone تمنع انتقال المربع بسبب التغيرات الصغيرة جدًا حول الوضع المرجعي.

تحديث الرسم عند تغير الموضع

داخل loop() نقرأ تسارع X وY، ونطرح قيم المعايرة، ثم نطبق الفلترة ونحول النتائج إلى إحداثيات. إذا تغير موضع المربع نحدث مصفوفة LED، وإلا نبقي الرسم كما هو.

الكود الكامل لمشروع MPU6050 مع MAX7219


#include <Wire.h>
#include <Adafruit_MPU6050.h>
#include <Adafruit_Sensor.h>
#include <MD_MAX72xx.h>
#include <SPI.h>
#include <math.h>

// ===== GY-521 / MPU6050 =====
Adafruit_MPU6050 mpu;

#define SDA_PIN 21
#define SCL_PIN 22

// ===== MAX7219 / LED Matrix =====
#define HARDWARE_TYPE MD_MAX72XX::FC16_HW
#define MAX_DEVICES 1

#define DIN_PIN 23
#define CLK_PIN 18
#define CS_PIN  5

// SPI logiciel : DIN, CLK, CS
MD_MAX72XX matrix = MD_MAX72XX(
  HARDWARE_TYPE,
  DIN_PIN,
  CLK_PIN,
  CS_PIN,
  MAX_DEVICES
);

// ===== Position du carre 2x2 =====
int posX = 3;
int posY = 3;

// ===== Filtrage =====
float filteredX = 0;
float filteredY = 0;

// ===== Calibration =====
float offsetX = 0;
float offsetY = 0;

// ===== Reglages =====
const float alpha = 0.20;
const float deadZone = 0.50;
const float maxTilt = 7.0;

// Changer en true pour inverser un axe
const bool invertX = false;
const bool invertY = false;

// ===== Affichage du carre =====
void drawSquare(int x, int y) {
  matrix.clear();

  for (int row = y; row < y + 2; row++) {
    for (int col = x; col < x + 2; col++) {
      matrix.setPoint(row, col, true);
    }
  }
}

// ===== Inclinaison vers position 0..6 =====
int tiltToPosition(float value) {
  if (fabs(value) < deadZone) {
    value = 0;
  }

  value = constrain(value, -maxTilt, maxTilt);

  int position = round(3.0 + value * 3.0 / maxTilt);

  return constrain(position, 0, 6);
}

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

  // Initialisation de la matrice
  matrix.begin();
  matrix.control(MD_MAX72XX::INTENSITY, 4);
  matrix.clear();

  // Initialisation du MPU6050
  Wire.begin(SDA_PIN, SCL_PIN);

  if (!mpu.begin()) {
    Serial.println("MPU6050 not found!");
    while (true) {
      delay(100);
    }
  }

  mpu.setAccelerometerRange(MPU6050_RANGE_2_G);

  // Garder le capteur a plat
  Serial.println("Keep the sensor flat...");
  delay(1500);

  // Calibration de la position horizontale
  sensors_event_t a, g, temp;

  for (int i = 0; i < 30; i++) {
    mpu.getEvent(&a, &g, &temp);

    offsetX += a.acceleration.x;
    offsetY += a.acceleration.y;

    delay(20);
  }

  offsetX /= 30.0;
  offsetY /= 30.0;

  Serial.println("Calibration done!");

  drawSquare(posX, posY);
}

void loop() {
  sensors_event_t a, g, temp;
  mpu.getEvent(&a, &g, &temp);

  float ax = a.acceleration.x - offsetX;
  float ay = a.acceleration.y - offsetY;

  // Filtrage des mesures
  filteredX = alpha * ax
            + (1.0 - alpha) * filteredX;

  filteredY = alpha * ay
            + (1.0 - alpha) * filteredY;

  float tiltX = invertX ? -filteredX : filteredX;
  float tiltY = invertY ? -filteredY : filteredY;

  int newX = tiltToPosition(tiltX);
  int newY = tiltToPosition(tiltY);

  // Actualiser uniquement si la position change
  if (newX != posX || newY != posY) {
    posX = newX;
    posY = newY;

    drawSquare(posX, posY);
  }

  Serial.print("X: ");
  Serial.print(filteredX, 2);

  Serial.print(" | Y: ");
  Serial.print(filteredY, 2);

  Serial.print(" | LED X: ");
  Serial.print(posX);

  Serial.print(" | LED Y: ");
  Serial.println(posY);

  delay(50);
}

اختبار المشروع عمليًا

  • ضع حساس GY-521 أفقيًا وثابتًا عند تشغيل اللوحة لنجاح المعايرة.
  • انتظر انتهاء المعايرة وظهور مربع مكون من أربعة مصابيح قرب الوسط.
  • أمل الحساس في اتجاه معين ولاحظ حركة المربع.
  • أمل الحساس في الاتجاه المعاكس وتأكد من انتقال المربع إلى الجهة الأخرى.
  • جرّب الإمالة على المحور الثاني أو المحورين معًا لمشاهدة الحركة القطرية.
  • أعد الحساس إلى الوضع الأفقي ولاحظ عودة المربع إلى الوسط.

مشكلات شائعة وحلولها

المشكلةما الذي يجب التحقق منه؟
لا تظهر قراءات MPU6050تأكد من 3.3V وGND وSDA على GPIO21 وSCL على GPIO22، ومن تثبيت المكتبة.
المصفوفة لا تضيءتحقق من التغذية والأرضي ومنافذ DIN وCLK وCS ونوع العتاد المحدد في المكتبة.
إضاءة غير مستقرةراجع توافق مستويات المنطق 3.3V/5V وجودة التوصيلات والتغذية.
المربع لا يعود إلى الوسطأعد التشغيل مع تثبيت الحساس أفقيًا أثناء المعايرة.
حركة معكوسةراجع اتجاه تركيب الحساس وتعيين المحورين X وY في البرنامج.
اهتزاز المربع وهو ثابتراجع قيم الفلترة والمنطقة المحايدة Dead Zone.
المربع يخرج عن الحدوداحصر الإحداثيات بين 0 و6 عند رسم مربع 2×2.

ماذا تعلمنا في هذه الحلقة؟

  • التعرف على مقياس التسارع والجيروسكوب داخل MPU6050.
  • توصيل حساس GY-521 بلوحة ESP32 عبر I2C.
  • قراءة التسارع على X وY وZ باستخدام Serial Monitor.
  • توصيل مصفوفة LED 8×8 المزودة بشريحة MAX7219.
  • استخدام مكتبة MD_MAX72xx لرسم مربع مضيء.
  • تحويل قيم التسارع إلى إحداثيات داخل حدود المصفوفة.
  • استخدام المعايرة والفلترة والمنطقة المحايدة لتحسين استقرار الحركة.

أفكار لتطوير المشروع

يمكن الاستفادة من نفس المبدأ لإنشاء لعبة تعتمد على الإمالة، أو التحكم في روبوت، أو تصميم واجهة تفاعلية تستجيب لحركة اليد. كما يمكن تطوير البرنامج لإضافة مؤثرات ضوئية أو تغيير سرعة الحركة حسب زاوية الإمالة.

الخلاصة

نجحنا في استخدام حساس MPU6050 مع ESP32 للتحكم في مربع مضيء على مصفوفة LED 8×8. بدأنا بفهم قراءات التسارع واختبارها، ثم ربطناها بحركة مرئية مع المعايرة والفلترة. تابعوا سلسلة ESP32 من الصفر على قناة نادي تك – نادي الابتكار للمزيد من مشاريع الإلكترونيات والبرمجة.

موضوعات ذات صلة