بناء سيارة روبوتية متقدمة ذاتية القيادة يُتحكَّم فيها عبر البلوتوث باستخدام Arduino Mega ومستشعرات الموجات فوق الصوتية وAdafruit Motor Shield

يحتوي هذا المقال على روابط تسويق بالعمولة مُعلَّمة بعلامة *. بصفتي شريكًا في برنامج Amazon Associates، أربح من عمليات الشراء المؤهلة، دون أي تكلفة إضافية عليك. يساعد ذلك على إبقاء الشروحات مجانية.

يُحمَّل الفيديو من YouTube (Google)، وقد يضع ملفات تعريف الارتباط (Cookies). سياسة الخصوصية

ملخص سريع

تتجنب هذه السيارة الروبوتية العوائق من تلقاء نفسها. فهي تستخدم أربعة مستشعرات موجات فوق صوتية من نوع HC-SR04، واحدًا على كل جانب. ومن تطبيق Android يمكنك تحويلها إلى التحكم اليدوي عبر البلوتوث.

تعمل السيارة بلوحة Arduino Mega 2560. ويقود درع المحركات Adafruit Motor Shield V1.2 محركات التيار المستمر الأربعة، بينما تستقبل وحدة البلوتوث HC-05 الأوامر من الهاتف. ويتكون الهيكل من لوحين من الأكريليك (Plexiglas) بينهما إطار مطبوع بالطباعة ثلاثية الأبعاد (3D)، إضافة إلى حوامل مطبوعة للمحركات وحامل للوحة Arduino. أما تطبيق الهاتف فمبني باستخدام MIT App Inventor، وهي أداة مجانية تعمل في المتصفح وتبرمج فيها بتركيب الكتل بعضها ببعض بدلًا من كتابة الكود. ويوسّع هذا المشروع مشروعي السابق سيارة روبوتية يُتحكَّم فيها عبر البلوتوث باستخدام Arduino وHC-05 وAdafruit Motor Shield بإضافة كشف العوائق في الاتجاهات الأربعة كلها.

3D render of the robot car: white 3D printed frame with openings for the HC-SR04 sensors, the upper plate and four wheels

كيف تعمل السيارة الروبوتية

Arduino Mega هو المتحكم الرئيسي. ففي حلقة مستمرة يعالج أوامر البلوتوث، ويقرأ المستشعرات الأربعة في الوضع الذاتي، ويرسل الإشارات إلى درع المحركات. أستخدم Arduino Mega بدلًا من Arduino Uno لأنه يوفر أطرافًا رقمية حرة كافية للمستشعرات الأربعة (الأطراف من 22 إلى 29). والمنفذ التسلسلي العتادي (Hardware Serial) دائرة مدمجة (UART) ترسل البيانات التسلسلية وتستقبلها. ويملك Arduino Mega ثلاثة منافذ إضافية منها (من Serial1 إلى Serial3)، لذا يمكن أن تحصل وحدة HC-05 على منفذ خاص بها (انظر الملاحظة في الخطوة 4).

المكونات ووظائفها

وحدة البلوتوث HC-05 تتصل بالمنفذ التسلسلي العتادي في Arduino Mega عبر الطرف 0 (RX، الاستقبال) والطرف 1 (TX، الإرسال). وتتقاطع الأسلاك: يذهب الطرف TXD (الإرسال) في HC-05 إلى الطرف 0، ويذهب الطرف RXD (الاستقبال) في HC-05 إلى الطرف 1. وتستقبل الوحدة من تطبيق Android أوامر مكونة من حرف واحد: F (إلى الأمام)، وB (إلى الخلف)، وL (إلى اليسار)، وR (إلى اليمين)، وS (توقف)، وA (تشغيل الوضع الذاتي)، وM (إيقاف الوضع الذاتي).

Adafruit Motor Shield V1.2 يُركَّب فوق Arduino Mega. ويستخدم شريحتين لقيادة المحركات من نوع L293D (تحتوي كل منهما على جسرين من نوع H-bridge، وهي دوائر تستطيع تشغيل المحرك في الاتجاهين) للتحكم في ما يصل إلى أربعة محركات تيار مستمر. يقود المحركان M1 وM2 العجلات اليسرى، ويقود M3 وM4 العجلات اليمنى. كما ينقل الدرع تيار المحركات الذي كان سيُحمِّل أطراف Arduino Mega فوق طاقتها.

أربعة مستشعرات موجات فوق صوتية من نوع HC-SR04 مثبتة في الجدران الأمامية والخلفية واليسرى واليمنى للإطار المطبوع بتقنية الطباعة ثلاثية الأبعاد. يقيس كل مستشعر المسافة إلى أقرب عائق بإرسال نبضة فوق صوتية وقياس الزمن حتى عودة صداها. وفي الوضع الذاتي يقرأ Arduino Mega المستشعرات الأربعة كلها ويقرر هل يتقدم إلى الأمام أم ينعطف أم يرجع إلى الخلف. ويوضح شرح HC-SR04 الذي أعددته طريقة عمل القياس.

أوضاع التشغيل

وضع البلوتوث اليدوي: عندما تضغط زر اتجاه في تطبيق Android، يرسل التطبيق حرفًا واحدًا عبر البلوتوث. فيقرؤه Arduino Mega ويقود المحركات وفقًا له. وعند تحرير الزر يُرسَل الحرف S (توقف).

الوضع الذاتي: عندما تضبط مفتاح الوضع على Autonomous، يرسل التطبيق الحرف A. عندها يقرأ Arduino Mega المستشعرات الأربعة كلها في حلقة متكررة. فإذا لم يكن هناك أي شيء ضمن مسافة 20 cm أمام السيارة، تتقدم إلى الأمام. وتزداد سرعتها مع ازدياد المسافة الأمامية حتى 160 cm. وعندما يكون الطريق الأمامي مسدودًا، تقارن المسافتين اليسرى واليمنى وتنعطف نحو الجانب ذي المسافة الأكبر. وإذا كان الجانبان مسدودين أيضًا، ترجع إلى الخلف وهي تبحث عن منفذ. وأي أمر اتجاه أو توقف من التطبيق (F، B، L، R، S) ينهي الوضع الذاتي بمجرد أن ينهي برنامج Arduino (Sketch، وهو الاسم الذي يطلقه Arduino على البرنامج) حركته الحالية. وإعادة المفتاح إلى الوضع اليدوي ترسل الحرف M، فتتوقف السيارة وينتهي الوضع الذاتي كذلك.

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

المكوّنالكميةالغرضملاحظات
Arduino Mega 25601المتحكم الرئيسي: يشغّل البرنامج ويقرأ المستشعراتأطراف رقمية حرة كافية (من 22 إلى 29) للمستشعرات الأربعة، إضافة إلى المنافذ من Serial1 إلى Serial3. استخدمتُ لوحة متوافقة مع Mega تعمل بالطريقة نفسها
Adafruit Motor Shield V1.21يقود محركات التيار المستمر الأربعة عبر مرابط التوصيل من M1 إلى M4يُركَّب مباشرة فوق أطراف التوصيل في Arduino Mega (صفوف مقابس الأطراف الممتدة على حواف اللوحة)
وحدة البلوتوث HC-051اتصال لاسلكي بتطبيق Androidتتصل بالمنفذ Serial (الطرف 0 والطرف 1)؛ ورمز الاقتران الافتراضي (PIN) هو 1234
مستشعر الموجات فوق الصوتية HC-SR044كشف العوائق في الأمام والخلف واليسار واليمينيحتاج كل منها إلى طرفين: Trig (الإطلاق) يبدأ نبضة فوق صوتية، وEcho يبقى في الحالة HIGH (عند 5 V) حتى يعود الصدى
محرك تيار مستمر بعلبة تروس4يقود العجلات الأربعمحركات تيار مستمر قياسية بعلبة تروس تعمل من 3 إلى 6 V
لوح أكريليك (Plexiglas)2اللوح السفلي (يحمل المحركات وحامل Arduino) واللوح العلوي (يغلق الإطار من الأعلى)200 × 150 mm، بسماكة 3 mm لكل منهما
الإطار وحوامل المحركات وحامل Arduino المطبوعة بتقنية الطباعة ثلاثية الأبعاد1 مجموعةيحمل الإطار المستشعرات الأربعة؛ أما حوامل المحركات وحامل اللوحة فتثبّت المحركات وArduino Megaملفات STL (ملفات للطباعة ثلاثية الأبعاد) في متجر OmArTronics، انظر أدناه
مقاومتان 1 kΩ و2 kΩ1 من كل نوعمقسم جهد لخط RXD في HC-05انظر الملاحظة أسفل جدول توصيلات HC-05
أسلاك التوصيل (Jumper)متنوعةجميع توصيلات الإشارة والطاقةذكر-ذكر (قابس في الطرفين) وذكر-أنثى (قابس ومقبس)
حزمة البطاريات1تغذي المحركات بالطاقة (وكذلك Arduino Mega، انظر توصيلات الطاقة)من 6 إلى 7.4 V لمحركات 3 إلى 6 V؛ وصّلها بمربط طاقة المحركات في الدرع (EXT_PWR)
مفتاح الطاقة1يوصل البطارية ويفصلهاملحوم في السلك الموجب للبطارية
هاتف ذكي يعمل بنظام Android1يشغّل تطبيق التحكم المصمم بأداة MIT App InventorAndroid مزوّد بالبلوتوث
العجلات4تُركَّب على محاور المحركات ذات علب التروسعجلات قياسية للسيارات الروبوتية مقاس 65 mm
براغٍ وصواميل وأعمدة تباعدمتنوعةالتجميع الميكانيكييُنصح بقطع تثبيت M3 (براغٍ مترية بسنّ قطره 3 mm)، إضافة إلى حشوات ملولبة حرارية للقطع المطبوعة؛ والفواصل (Standoffs) أعمدة تباعد ترفع اللوحة فوق اللوح

متجر OmArTronics: ملفات STL متاحة للتنزيل المجاني: إطار الهيكل، واللوحان السفلي والعلوي، وحامل Arduino، وحاملان للمحركات، وغطاء فتحة الوصول إلى منفذ USB (7 ملفات). أما الكود ومخطط التوصيل وقائمة القطع فموجودة في هذه الصفحة.

الخطوة 1: بناء هيكل سيارتك الروبوتية

إذا كانت الطباعة ثلاثية الأبعاد (3D) جديدة عليك، فإن دليل المبتدئين للطباعة ثلاثية الأبعاد (3D) الذي أعددته يغطي الأساسيات.

  • قصّ ألواح الأكريليك: قصّ لوحين بمقاس 200 × 150 mm من صفيحة أكريليك بسماكة 3 mm، أحدهما للوح السفلي والآخر للوح العلوي. اشتريتُ ألواح الأكريليك وقصصتها بهذا المقاس وثقبتُ فتحات التثبيت قبل التجميع.
  • اطبع الإطار وحوامل المحركات بطابعة ثلاثية الأبعاد: اطبع الإطار وأربعة حوامل للمحركات. تحتوي مجموعة ملفات STL على ملفي حامل متناظرين كالمرآة (motor_holder1 و motor_holder2)؛ اطبع كل واحد منهما مرتين. ضع الحوامل بحيث تستقر المحركات بشكل متناظر على الجانبين.
  • اطبع حامل Arduino بطابعة ثلاثية الأبعاد: اطبع حامل Arduino من مجموعة ملفات STL نفسها. واترك مسافة كافية لدرع المحركات الذي يُركَّب فوق Arduino Mega.
  • اضغط الحشوات الملولبة في مكانها: سخّنتُ الحشوات الملولبة وضغطتها في جميع القطع المطبوعة، لكي تثبت البراغي دون صواميل إضافية.

الخطوة 2: تركيب المحركات

  • ثبّت المحركات: ثبّت كل محرك تيار مستمر على حامله المطبوع بتقنية الطباعة ثلاثية الأبعاد بالبراغي.
  • ركّب المحركات على الهيكل: ثبّت حوامل المحركات على لوح الأكريليك السفلي وأحكم شد البراغي. واضبط محاذاة الزوج الأيسر (M1 وM2) والزوج الأيمن (M3 وM4) لكي تسير السيارة في خط مستقيم.

الخطوة 3: تركيب Arduino Mega ودرع المحركات

  • ثبّت حامل Arduino: ثبّت حامل Arduino المطبوع بتقنية الطباعة ثلاثية الأبعاد على اللوح السفلي بالبراغي.
  • ركّب Arduino Mega: ضع Arduino Mega في الحامل وثبّته بالبراغي.
  • ثبّت درع المحركات: ضع Adafruit Motor Shield فوق Arduino Mega، وتحقق من محاذاة جميع أطراف التوصيل، ثم اضغطه إلى الأسفل بثبات ولكن بحذر.

الخطوة 4: توصيل المكونات

إذا كانت وحدات البلوتوث جديدة عليك، فإن درسي بعنوان الشرح الكامل لوحدة البلوتوث HC-05/HC-06 يغطي الأساسيات بالتفصيل.

توصيلات المحركات

المحركطرف درع المحركاتالجانب
المحرك الأمامي الأيسرM1يسار
المحرك الخلفي الأيسرM2يسار
المحرك الأمامي الأيمنM3يمين
المحرك الخلفي الأيمنM4يمين

توصيلات وحدة البلوتوث HC-05

طرف HC-05طرف Arduino Megaملاحظات
VCC5 Vتغذية الوحدة بالطاقة
GNDGNDأرضي مشترك
TXDRX (الطرف 0)إرسال HC-05 إلى استقبال Arduino Mega
RXDTX (الطرف 1)إرسال Arduino Mega إلى استقبال HC-05 (عبر مقسم جهد، انظر الملاحظة)

مقسم الجهد على خط RXD: يتوقع الطرف RXD في HC-05 إشارات بجهد 3.3 V، لكن Arduino Mega يرسل إشارات بجهد 5 V. لذا ضع مقسم جهد في هذا الخط: مقاومة 1 kΩ من الطرف TX في Arduino Mega (الطرف 1) إلى RXD في HC-05، ومقاومة 2 kΩ من RXD في HC-05 إلى الأرضي (GND). يخفض هذا الإشارة إلى نحو 3.3 V. يوصل مخطط التوصيل الخاص بي أدناه الطرف RXD مباشرة، لكن أضف المقسم رغم ذلك لتبقي جهد الطرف RXD عند 3.3 V.

الطرفان التسلسليان 0 و1: تستخدم وحدة HC-05 المنفذ التسلسلي العتادي Serial في Arduino Mega على الطرف 0 (RX) والطرف 1 (TX). ويغطي درع المحركات هذين الطرفين، لذا يوصل مخطط التوصيل الخاص بي أسلاك HC-05 بالطرفين 0 و1 في صف أطراف التوصيل الرقمية على الدرع، وهذا الصف يمررهما إلى Arduino Mega. ويستخدم اتصال USB الطرفين نفسيهما، لذا افصل سلكي TXD وRXD الخاصين بوحدة HC-05 قبل رفع برنامج إلى اللوحة، ثم أعد توصيلهما بعد ذلك. والبديل هو Serial1: وصّل TXD في HC-05 بالطرف 19 (RX1) وRXD في HC-05 بالطرف 18 (TX1)، عبر مقسم الجهد أيضًا. واستبدل كل استدعاء لـ Serial في البرنامج (Serial.begin وSerial.available وSerial.read) بـ Serial1. وعندها يمكن أن تبقى الوحدة موصولة أثناء الرفع.

توصيلات مستشعرات الموجات فوق الصوتية

المستشعرالموضعطرف Trigطرف Echo
Sensor_Fالأمامالطرف 23الطرف 22
Sensor_Bالخلفالطرف 25الطرف 24
Sensor_Lيسارالطرف 27الطرف 26
Sensor_Rيمينالطرف 29الطرف 28

يحتاج كل مستشعر HC-SR04 أيضًا إلى جهد 5 V على الطرف VCC وإلى توصيل بالأرضي GND.

توصيلات الطاقة

وصّل القطبين الموجب والسالب لحزمة البطاريات بمربط طاقة المحركات (EXT_PWR) في Adafruit Motor Shield. ويحتوي الدرع على وصلة PWR (Jumper)، وهي قابس صغير يصل بين طرفين. وعند تركيب هذه الوصلة تغذي البطارية نفسها Arduino Mega أيضًا عبر الطرف VIN (مدخل الجهد غير المنظم)، الذي يحتاج إلى 7 V على الأقل. وحزمة 6 V (4 × AA) منخفضة جدًا لهذا الغرض. انزع الوصلة عندما يكون لـ Arduino Mega مصدر طاقة خاص به (USB أو بطارية ثانية). والدرع المركّب فوق اللوحة يشترك أصلًا في الأرضي GND مع Arduino Mega.

يجب أن توفر الحزمة 2 A على الأقل للمحركات الأربعة. واختر الجهد بما يناسب المحركات. تفقد شرائح القيادة L293D على الدرع نحو 2.6 V عند 0.6 A (القيم النموذجية في ورقة البيانات: 1.4 V على الجانب العلوي و1.2 V على الجانب السفلي). لذلك تعمل محركات التيار المستمر ذات علب التروس المصممة لجهد من 3 إلى 6 V جيدًا على جهد من 6 إلى 7.4 V. أما عند 12 V فقد ترتفع حرارتها بشكل مفرط. والحزم المناسبة هي خلايا قلوية 4 × AA (6 V، للمحركات فقط) أو بطارية LiPo من نوع 2S (ليثيوم بوليمر، خليتان على التوالي، 7.4 V). ولا تستطيع بطارية 9 V (PP3) توفير تيار أربعة محركات تحت الحمل.

ثبّتُّ حزمة البطاريات على اللوح السفلي بشريط لاصق مزدوج الوجه، ولحمتُ مفتاح تشغيل في سلكها الموجب. ويوجد المفتاح في اللوح العلوي.

Wiring diagram: Arduino Mega, Adafruit Motor Shield, four HC-SR04 sensors, HC-05 module, four DC motors, two battery holders and a power switch

يعرض المخطط حاملين من نوع 4 × AAA موصولين على التوالي ومعهما العلامة 6V-12V. ومع محركات من 3 إلى 6 V، التزم بجهد من 6 إلى 7.4 V كما هو موضح في قسم توصيلات الطاقة.

الخطوة 5: برمجة Arduino Mega للقيادة الذاتية والتحكم عبر البلوتوث

يتولى البرنامج أوامر البلوتوث اليدوية وتجنب العوائق في الوضع الذاتي معًا. وإذا لم تكتب برنامج Arduino من قبل، فابدأ بدرسي أساسيات برمجة Arduino: دليل شامل للمبتدئين. وستجد البرنامج وملفات التطبيق أيضًا في مستودع GitHub.

برنامج Arduino الكامل

يحتاج البرنامج إلى مكتبة AFMotor من Adafruit الخاصة بالدرع V1. ثبّتها في Arduino IDE (المحرر المجاني الذي يترجم البرامج ويرفعها إلى اللوحة) من القائمة Sketch > Include Library > Manage Libraries: وابحث عن “Adafruit Motor Shield library” (وليس مكتبة V2). انسخ البرنامج إلى Arduino IDE، واضغط Verify، ثم Upload لرفعه إلى Arduino Mega. وافصل سلكي TXD وRXD الخاصين بوحدة HC-05 قبل الرفع.

/**
 * Author: Omar Draidrya
 * Date: 2024/07/20
 * This sketch controls an advanced autonomous and Bluetooth-controlled robot car using Arduino Mega,
 * four HC-SR04 ultrasonic sensors, HC-05 Bluetooth module, and Adafruit Motor Shield V1.
 * The robot supports manual Bluetooth control (Forward, Backward, Left, Right, Stop) and
 * autonomous obstacle-avoidance navigation with front, rear, left, and right distance sensing.
 */
#include <AFMotor.h>
// Motor setup
AF_DCMotor motor1(1);
AF_DCMotor motor2(2);
AF_DCMotor motor3(3);
AF_DCMotor motor4(4);
// Ultrasonic sensor pins
const int trigF = 23;
const int echoF = 22;
const int trigL = 27;
const int echoL = 26;
const int trigR = 29;
const int echoR = 28;
const int trigB = 25;
const int echoB = 24;
char command;
bool autonomousModeEnabled = false; // Variable to track autonomous mode
// Function prototypes
void handleCommand(char command);
void forward(int speed = 160);
void backward(int speed = 160);
void turnLeft(int speed = 160);
void turnRight(int speed = 160);
void stop();
void autonomousMode();
int getDistance(int trigPin, int echoPin);
void setup() {
  Serial.begin(9600);
  pinMode(trigF, OUTPUT);
  pinMode(echoF, INPUT);
  pinMode(trigL, OUTPUT);
  pinMode(echoL, INPUT);
  pinMode(trigR, OUTPUT);
  pinMode(echoR, INPUT);
  pinMode(trigB, OUTPUT);
  pinMode(echoB, INPUT);
}
void loop() {
  if (Serial.available() > 0) {
    command = Serial.read();
    handleCommand(command);
  }
  if (autonomousModeEnabled) {
    autonomousMode();
  }
}
void handleCommand(char command) {
  if (command == 'F') {
    forward();
    autonomousModeEnabled = false; // Disable autonomous mode
  } else if (command == 'B') {
    backward();
    autonomousModeEnabled = false; // Disable autonomous mode
  } else if (command == 'L') {
    turnLeft();
    autonomousModeEnabled = false; // Disable autonomous mode
  } else if (command == 'R') {
    turnRight();
    autonomousModeEnabled = false; // Disable autonomous mode
  } else if (command == 'S') {
    stop();
    autonomousModeEnabled = false; // Disable autonomous mode
  } else if (command == 'A') {
    autonomousModeEnabled = true; // Enable autonomous mode
  } else if (command == 'M') {
    stop();
    autonomousModeEnabled = false; // Mode switch OFF: leave autonomous mode
  }
}
void forward(int speed) {
  motor1.setSpeed(speed);
  motor2.setSpeed(speed);
  motor3.setSpeed(speed);
  motor4.setSpeed(speed);
  motor1.run(FORWARD);
  motor2.run(FORWARD);
  motor3.run(FORWARD);
  motor4.run(FORWARD);
}
void backward(int speed) {
  motor1.setSpeed(speed);
  motor2.setSpeed(speed);
  motor3.setSpeed(speed);
  motor4.setSpeed(speed);
  motor1.run(BACKWARD);
  motor2.run(BACKWARD);
  motor3.run(BACKWARD);
  motor4.run(BACKWARD);
}
void turnLeft(int speed) {
  motor1.setSpeed(speed);
  motor2.setSpeed(speed);
  motor3.setSpeed(speed);
  motor4.setSpeed(speed);
  motor1.run(BACKWARD);
  motor2.run(BACKWARD);
  motor3.run(FORWARD);
  motor4.run(FORWARD);
}
void turnRight(int speed) {
  motor1.setSpeed(speed);
  motor2.setSpeed(speed);
  motor3.setSpeed(speed);
  motor4.setSpeed(speed);
  motor1.run(FORWARD);
  motor2.run(FORWARD);
  motor3.run(BACKWARD);
  motor4.run(BACKWARD);
}
void stop() {
  motor1.run(RELEASE);
  motor2.run(RELEASE);
  motor3.run(RELEASE);
  motor4.run(RELEASE);
}
void autonomousMode() {
  int distanceF = getDistance(trigF, echoF);
  int distanceL = getDistance(trigL, echoL);
  int distanceR = getDistance(trigR, echoR);
  int distanceB = getDistance(trigB, echoB);
  // Check for an obstacle in front
  if (distanceF < 20) {
    stop();
    delay(100); // Short pause to make sure the robot has stopped
    // Obstacle avoidance
    if (distanceR > distanceL && distanceR > 20) {
      turnRight(180);
      delay(300);
    } else if (distanceL > distanceR && distanceL > 20) {
      turnLeft(180);
      delay(300);
    } else if (distanceB > 20) {
      backward(160);
      while (distanceB > 20) {
        distanceB = getDistance(trigB, echoB);
        distanceL = getDistance(trigL, echoL);
        distanceR = getDistance(trigR, echoR);
        if (distanceR > 20 || distanceL > 20) {
          stop();
          delay(100);
          if (distanceR > distanceL) {
            turnRight(180);
          } else {
            turnLeft(180);
          }
          delay(300);
          break;
        }
        delay(100);
      }
      stop();
    } else {
      stop();
    }
  } else {
    adjustDirection(distanceL, distanceR);
    forward(constrain(map(distanceF, 20, 160, 64, 160), 64, 160));
  }
}
void adjustDirection(int distanceL, int distanceR) {
  if (distanceL < 20) {
    turnRight(180);
    delay(100);
    forward(160);
  } else if (distanceR < 20) {
    turnLeft(180);
    delay(100);
    forward(160);
  }
}
int getDistance(int trigPin, int echoPin) {
  digitalWrite(trigPin, LOW);
  delayMicroseconds(2);
  digitalWrite(trigPin, HIGH);
  delayMicroseconds(10);
  digitalWrite(trigPin, LOW);
  long duration = pulseIn(echoPin, HIGH, 30000); // Wait at most 30 ms for the echo
  if (duration == 0) return 400; // No echo: treat as a free path
  int distance = duration * 0.034 / 2;
  return distance;
}

شرح تفصيلي للكود

المكتبة وإعداد المحركات

يستخدم البرنامج مكتبة AFMotor.h من Adafruit الخاصة بالدرع V1 (للتثبيت: انظر قسم برنامج Arduino الكامل). توفر المكتبة الصنف AF_DCMotor؛ ويمثل كل كائن AF_DCMotor مربط محرك واحدًا (من M1 إلى M4). يتحكم motor1 و motor2 في العجلات اليسرى، ويتحكم motor3 و motor4 في العجلات اليمنى.

تعيين أطراف مستشعرات الموجات فوق الصوتية

تحدد ثمانية ثوابت للأطراف توصيلات Trig و Echo لمستشعرات HC-SR04 الأربعة. الأمامي: trigF و echoF على الطرفين 23 و22. الخلفي: trigB و echoB على الطرفين 25 و24. الأيسر: trigL و echoL على الطرفين 27 و26. الأيمن: trigR و echoR على الطرفين 29 و28. وتضبط الدالة setup() جميع أطراف Trig كمخارج OUTPUT وجميع أطراف Echo كمداخل INPUT.

معالجة أوامر البلوتوث

في كل دورة من loop() يفحص البرنامج Serial.available() بحثًا عن بيانات بلوتوث واردة. وعند وصول حرف تستدعي handleCommand() دالة الحركة المطابقة: يستدعي F الدالة forward()، وB الدالة backward()، وL الدالة turnLeft()، وR الدالة turnRight()، وS الدالة stop(). ويضبط كل أمر من هذه الأوامر أيضًا autonomousModeEnabled = false، فتغادر السيارة الوضع الذاتي بمجرد أن تنهي حركتها الحالية. أما A فيضبط autonomousModeEnabled = true. والأمر M، الذي يُرسَل عندما تعيد التطبيق إلى الوضع اليدوي، يوقف السيارة ويضبط autonomousModeEnabled = false.

دوال الحركة في الوضع اليدوي

تضبط الدوال forward() و backward() و turnLeft() و turnRight() سرعة المحركات الأربعة كلها ثم تشغلها في الاتجاه المطلوب. ويعتمد الانعطاف على القيادة التفاضلية (Differential Drive): فليس للسيارة عجلات توجيه، وهي تنعطف بتدوير العجلات اليسرى واليمنى في اتجاهين متعاكسين. تدير turnLeft() المحركات اليسرى إلى الخلف والمحركات اليمنى إلى الأمام، وتفعل turnRight() العكس. وتستدعي الدالة stop() التعليمة run(RELEASE) للمحركات الأربعة كلها، فينقطع تيار المحركات. وتقبل دوال الحركة الأربع قيمة سرعة اختيارية من 0 إلى 255 (القيمة الافتراضية 160). وتحدد هذه القيمة إشارة PWM (تعديل عرض النبضة): إذ يُوصَل جهد المحرك ويُفصَل مرات كثيرة في الثانية. وكلما ارتفعت القيمة بقي الجهد موصولًا لجزء أكبر من كل دورة، فيدور المحرك أسرع.

الوضع الذاتي ومنطق العائق الأمامي

تعمل الدالة autonomousMode() ما دامت قيمة autonomousModeEnabled هي true. وهي تقرأ المسافات من المستشعرات الأربعة كلها. فإذا كانت المسافة الأمامية أقل من 20 cm، تتوقف السيارة وتبدأ تجنب العائق. ثم تقارن المسافتين اليسرى واليمنى. فإذا كانت المسافة اليمنى أكبر وتزيد على 20 cm، تنعطف السيارة يمينًا لمدة 300 ms؛ وإذا كانت المسافة اليسرى أكبر وتزيد على 20 cm، تنعطف يسارًا. وإذا كان الجانبان مسدودين، ترجع إلى الخلف.

منطق التصحيح الجانبي

تعمل الدالة adjustDirection() عندما لا يوجد عائق أمامي. فإذا كانت المسافة اليسرى أقل من 20 cm (أي أن السيارة تنحرف نحو جدار)، تنعطف السيارة يمينًا لمدة 100 ms ثم تواصل سيرها. وينطبق الأمر نفسه على الجانب الآخر. وبهذا تبقى السيارة في المنتصف تقريبًا بين العوائق.

منطق الرجوع إلى الخلف والخروج من المأزق

عندما تكون الجهات الأمامية واليسرى واليمنى كلها مسدودة بينما الخلف خالٍ (المسافة أكبر من 20 cm)، ترجع السيارة إلى الخلف. وفي أثناء هذه المناورة يواصل البرنامج قراءة المستشعرين الأيسر والأيمن. وبمجرد أن تتجاوز المسافة اليسرى أو اليمنى 20 cm، تتوقف السيارة وتنعطف نحو الجانب ذي المسافة الأكبر وتخرج من حلقة الرجوع. وإذا انسد الخلف أولًا، تتوقف السيارة.

دالة قياس المسافة

ترسل الدالة getDistance() نبضة مدتها 10 µs على طرف Trig، وتقيس زمن الصدى على طرف Echo باستخدام pulseIn(). ثم تحوّل زمن الصدى إلى سنتيمترات بالمعادلة distance = duration * 0.034 / 2. فالصوت يقطع نحو 0.034 cm في الميكروثانية (343 m/s)، والنبضة تقطع المسافة إلى العائق ذهابًا وإيابًا، لذا تُقسَم النتيجة على 2. ويستدعي البرنامج getDistance() لكل من المستشعرات الأربعة كلما احتاج إلى قراءة مسافة. ومن دون مهلة زمنية (Timeout) تنتظر pulseIn() صدى لمدة تصل إلى ثانية واحدة، وتعيد 0 إذا لم يصل، فيعامل البرنامج ذلك على أنه عائق على بعد 0 cm. ويحدد الوسيط الثالث في pulseIn(echoPin, HIGH, 30000) مدة الانتظار بـ 30 ms (أي مدى يقارب 5 m)، و if (duration == 0) return 400; يعدّ غياب الصدى مسارًا خاليًا.

منطق تحويل المسافة إلى سرعة

عندما يكون الطريق أمام السيارة خاليًا، يضبط الكود سرعة التقدم باستخدام constrain(map(distanceF, 20, 160, 64, 160), 64, 160). تحوّل الدالة map() المسافة الأمامية (من 20 إلى 160 cm) إلى سرعة (من 64 إلى 160). تسير السيارة ببطء عندما يكون العائق قريبًا، وبالسرعة الافتراضية 160 عندما يكون الطريق أمامها خاليًا لمسافة 160 cm أو أكثر. لا تحدّ الدالة map() وحدها من قيمة خرجها: فابتداءً من 300 cm تُعيد 256 أو أكثر، فتتجاوز القيمة حدها في setSpeed() وتلتف إلى سرعة منخفضة، وبذلك تتباطأ السيارة في المساحات المفتوحة. أما الدالة constrain() فتحصر القيمة في المدى من 64 إلى 160. ويُبقي الحد الأدنى 64 المحركات دائرة: فتحت هذه القيمة يكون متوسط الجهد أضعف من أن يتغلب على الاحتكاك، فيتوقف المحرك عن الدوران.

الخطوة 6: بناء تطبيق Android للتحكم عبر البلوتوث

التطبيق مبني في MIT App Inventor، ويرسل الأوامر المكوّنة من حرف واحد إلى وحدة HC-05 عبر البلوتوث.

إعداد المشروع

  • أنشئ حسابًا وسجّل الدخول: انتقل إلى MIT App Inventor وسجّل الدخول بحساب Google الخاص بك.
  • أنشئ مشروعًا جديدًا: انقر على “Start New Project” (بدء مشروع جديد) وسمِّه “RobotCarControl”.

مكونات واجهة المستخدم المطلوبة

  • ListPicker1: اضبط النص على “Select Bluetooth Device”. يتيح لك هذا المكوّن اختيار وحدة HC-05 المقترنة من قائمة أجهزة البلوتوث.
  • BluetoothClient1: أضفه من لوحة Connectivity. وهو مكوّن غير مرئي يتولى اتصال البلوتوث التسلسلي.
  • Label1: تسمية نصية (Label) تعرض حالة الاتصال (“Connected” أو “Not Connected”). هذه التسمية يجب أن تكون موجودة في الواجهة لأن الكتل (Blocks) تشير إليها.
  • ButtonForward, ButtonBackward, ButtonLeft, ButtonRight: أربعة أزرار للاتجاهات. اضبط ألوانها لتمييزها بصريًا (مثلًا الأخضر للأمام والأحمر للخلف).
  • ModeSwitch: مكوّن Switch يبدّل بين الوضع اليدوي ووضع القيادة الذاتية. عند تشغيله (ON) يرسل “A” لتفعيل وضع القيادة الذاتية. وعند إيقافه (OFF) يرسل “M”، فيستجيب برنامج Arduino داخل handleCommand() بالسطر else if (command == 'M') { stop(); autonomousModeEnabled = false; }. من دون هذا الفرع، تواصل السيارة القيادة الذاتية إلى أن تضغط على أحد أزرار الاتجاه.

كيف يعمل اتصال البلوتوث عبر ListPicker

عند النقر على ListPicker، يملأ الحدث BeforePicking القائمة بأجهزة البلوتوث المقترنة من BluetoothClient1.AddressesAndNames. وبعد اختيار جهاز، يحاول الحدث AfterPicking الاتصال به عبر BluetoothClient1.Connect. إذا نجح الاتصال، تعرض Label1 النص “Connected”؛ وإلا فتعرض “Not Connected”. في Android 12 والإصدارات الأحدث، يحتاج التطبيق إلى إذن الأجهزة المجاورة (Nearby devices) لقراءة هذه القائمة. إذا بقيت القائمة فارغة، فامنح الإذن من الإعدادات > التطبيقات > RobotCarControl > الأذونات، أو أضف كتلة Screen1.Initialize تستدعي Screen1.AskForPermission مع BluetoothConnect.

كيف تعمل أوامر TouchDown وTouchUp

يستخدم كل زر اتجاه الحدث TouchDown لإرسال أمر الحركة (F أو B أو L أو R) عند الضغط عليه، والحدث TouchUp لإرسال “S” (توقف) عند تحريره. تتحرك السيارة ما دمت تضغط على الزر، وتتوقف عندما تتركه.

برمجة الكتل

أضف هذه الكتل في محرر الكتل (Blocks editor) في MIT App Inventor.

when ListPicker1.BeforePicking
  set ListPicker1.Elements to BluetoothClient1.AddressesAndNames
when ListPicker1.AfterPicking
  if call BluetoothClient1.Connect address ListPicker1.Selection
    then set Label1.Text to "Connected"
    else set Label1.Text to "Not Connected"
when ButtonForward.TouchDown
  if BluetoothClient1.IsConnected
    call BluetoothClient1.SendText text "F"
when ButtonBackward.TouchDown
  if BluetoothClient1.IsConnected
    call BluetoothClient1.SendText text "B"
when ButtonLeft.TouchDown
  if BluetoothClient1.IsConnected
    call BluetoothClient1.SendText text "L"
when ButtonRight.TouchDown
  if BluetoothClient1.IsConnected
    call BluetoothClient1.SendText text "R"
when ButtonForward.TouchUp
  if BluetoothClient1.IsConnected
    call BluetoothClient1.SendText text "S"
when ButtonBackward.TouchUp
  if BluetoothClient1.IsConnected
    call BluetoothClient1.SendText text "S"
when ButtonLeft.TouchUp
  if BluetoothClient1.IsConnected
    call BluetoothClient1.SendText text "S"
when ButtonRight.TouchUp
  if BluetoothClient1.IsConnected
    call BluetoothClient1.SendText text "S"
when ModeSwitch.Changed
  if ModeSwitch.On
    call BluetoothClient1.SendText text "A"
  else
    call BluetoothClient1.SendText text "M"
MIT App Inventor block programming interface showing Bluetooth connection, directional button commands, and mode switch logic for the robot car control app

الخطوة 7: اختبار السيارة الروبوتية وضبطها

  • اقرن جهاز البلوتوث: شغّل البلوتوث في هاتف Android واقرنه بوحدة HC-05. رمز PIN الافتراضي للإقران هو 1234 (أو 0000 في بعض الوحدات).
  • ثبّت التطبيق: في App Inventor، اختر Build > Android App (.apk)، ثم افتح رابط التنزيل أو رمز QR على هاتفك وثبّت ملف APK (ملف تطبيق Android). وللاختبارات السريعة، يشغّل تطبيق MIT AI2 Companion المشروع دون تثبيته (Connect > AI Companion). في تجربة القيادة التي تظهر في الفيديو، استخدمتُ ملف APK مثبّتًا على هاتفي. ستجد ملف APK الجاهز وملف مشروع App Inventor (.aia) أيضًا في مستودع GitHub.
  • اتصل عبر التطبيق: افتح تطبيق RobotCarControl، وانقر على ListPicker واختر وحدة HC-05 الخاصة بك. إذا نجح الاتصال، يتغير نص Label1 إلى “Connected”.
  • اختبر التحكم اليدوي قبل وضع القيادة الذاتية: يجب أن يحرّك كل زر (F، B، L، R) السيارة في الاتجاه الصحيح، وأن يوقفها تحرير الزر (S).
  • تحقق من اتجاه دوران المحركات: إذا دار أحد المحركات في الاتجاه الخاطئ، فبدّل سلكيه عند طرف التوصيل (Terminal) في درع المحركات. عند الضغط على زر التقدم للأمام، يجب أن تدور العجلات الأربع كلها إلى الأمام.
  • تحقق من كل مستشعر موجات فوق صوتية على حدة: أضف تعليمات Serial.println() مؤقتة تطبع مسافة كل مستشعر على الشاشة التسلسلية (Serial Monitor، أي النافذة في Arduino IDE التي تعرض البيانات التي ترسلها اللوحة عبر USB). يجب أن تعرض المستشعرات الأمامي والخلفي والأيسر والأيمن قيمًا معقولة، وأن تتفاعل مع جسم يوضع أمامها.
  • اختبر وضع القيادة الذاتية: اضبط مفتاح الوضع على القيادة الذاتية. ضع عائقًا أمام السيارة وراقب ما إذا كانت تتوقف وتنعطف مبتعدةً عنه.

اضبط القيم بهذا الترتيب:

  • حدّ مسافة العائق: يستخدم الكود 20 cm حدًّا، أي المسافة التي تُعدّ القراءة دونها عائقًا. إذا تفاعلت سيارتك متأخرةً جدًا (فتصطدم) أو مبكرًا جدًا (فتتوقف بعيدًا عن العوائق)، فغيّر القيمة 20 في autonomousMode() وadjustDirection() وفي استدعاء map(). أو عرّف ثابتًا واحدًا مثل const int SAFE_DIST = 20; واستخدمه في المواضع الثلاثة كلها.
  • قيم السرعة: السرعة الافتراضية هي 160 (من أصل 255). إذا كانت محركاتك سريعة جدًا أو بطيئة جدًا، فغيّر السرعة الافتراضية ومدى الدالة map(). وقد تحتاج السيارة الأثقل أو المحركات الأضعف إلى حد أدنى أعلى للسرعة.
  • توقيت الانعطاف: تُحدَّد مدة الانعطاف بالتعليمة delay(300) بعد أوامر الانعطاف. إذا لم تنعطف السيارة بما يكفي (أو انعطفت أكثر من اللازم)، فزِد هذا التأخير أو أنقصه.
  • القيم التي أستخدمها: يحتوي برنامج Arduino أعلاه على القيم التي تسير بها سيارتي في الفيديو: الحد 20 cm، وسرعة التقدم من 64 إلى 160، والانعطاف بسرعة 180 لمدة 300 ms. ابدأ بهذه القيم وغيّر قيمة واحدة في كل مرة.

استكشاف الأخطاء وإصلاحها

المشكلةالسبب المحتملالحل
تقترن HC-05 لكن السيارة لا تتحركالتطبيق لا يرسل الأوامر، أو التوصيلات غير صحيحةتأكد من أن التطبيق يعرض “Connected”. أعد توصيل سلكَي TXD وRXD الخاصين بوحدة HC-05 إذا كنت قد فصلتهما أثناء الرفع. وإذا لم تتحرك السيارة بعد ذلك، فافحص توصيلات درع المحركات والبطارية.
فشل الرفع مع ظهور خطأ في Serialسلكا TXD وRXD من وحدة HC-05 يشغلان الطرفين 0 و1افصل سلكَي TXD وRXD الخاصين بوحدة HC-05 قبل الرفع. أعد توصيلهما بعد انتهاء الرفع.
السيارة تتحرك في الاتجاه الخاطئأسلاك المحرك موصّلة بالعكسبدّل سلكَي المحرك المعني عند طرف التوصيل في الدرع، أو بدّل كائنات المحركات في الكود.
مستشعر الموجات فوق الصوتية يعطي قراءات غير مستقرةتوصيلات غير محكمة، أو تشويش كهربائي، أو أن المستشعر يواجه سطحًا لينًا أو مائلًاثبّت جميع أسلاك التوصيل (Jumper) جيدًا. أضف تأخيرًا قصيرًا بين قراءات المستشعرات. اختبر باستخدام سطح مستوٍ وصلب.
السيارة تتوقف مرارًا على نحو غير متوقعالمستشعر الأمامي يقرأ مسافات قصيرة خاطئة، أو لا يعود أي صدى، فتُعيد pulseIn() القيمة 0 وتُبلغ getDistance() عن 0 cmافحص سلكَي Trig وEcho بحثًا عن أي ارتخاء. ثبّت المستشعر بإحكام حتى لا يهتز. أبقِ على مهلة pulseIn() (Timeout) وعلى فحص duration == 0 الموجودين في برنامج Arduino.
وضع القيادة الذاتية يعلق في زاويةالاتجاهات الأربعة كلها مسدودة ضمن مسافة 20 cmيتوقف برنامج Arduino عندما تُسدّ الاتجاهات الأربعة كلها. أضف حركة للخروج: اجعل السيارة تستدير في مكانها باستخدام turnLeft(180) مع تأخير أطول من 300 ms، ثم أعد القياس. كما أن خفض الحد يتيح للسيارة الخروج من الأماكن الأضيق.
المحركات ضعيفة، أو يُعاد تشغيل Arduino Mega عند بدء دوران المحركاتجهد البطارية ينخفض تحت الحملاستخدم بطاريات جديدة أو أكبر سعة. غذِّ المحركات من طرف EXT_PWR في الدرع، وغذِّ Arduino Mega من USB أو من مزود طاقة خاص به (وانزع وصلة الـ Jumper المسمّاة PWR).
السيارة تنعطف في الاتجاه الخاطئ في وضع القيادة الذاتيةالمستشعران الأيسر والأيمن متبادلان في التوصيلات أو في الكودتأكد من أن Sensor_L مركّب على اليسار وSensor_R على اليمين، وأن أرقام الأطراف تطابق توزيع التوصيلات.

إذا تصرفت سيارتك بشكل مختلف، فاترك تعليقًا تذكر فيه نوع البطارية وقراءات المستشعرات الأربعة من الشاشة التسلسلية (Serial Monitor).

الأسئلة الشائعة

هل يمكنني استخدام Arduino Uno بدلاً من Arduino Mega؟

ممكن، ولكن مع بعض التنازلات. عند تشغيل أربعة محركات تيار مستمر، يستخدم Adafruit Motor Shield V1 الأطراف 3 و4 و5 و6 و7 و8 و11 و12، وتشغل وحدة HC-05 الطرفين 0 و1. ويتبقى بذلك الأطراف 2 و9 و10 و13 وA0 إلى A5 لخطوط Trig وEcho الثمانية (تعمل الأطراف التناظرية أيضًا كأطراف رقمية). ويجب أن تغيّر أرقام الأطراف في برنامج Arduino لتتوافق مع ذلك. لا يحتوي Arduino Uno على منفذ تسلسلي عتادي ثانٍ، لذا يجب فصل HC-05 عند كل عملية رفع. أما Arduino Mega فيوفر أطرافًا حرة كافية، ويمكن أن تبقى HC-05 موصولة أثناء الرفع عند توصيلها بـ Serial1 (انظر الخطوة 4).

ما الفرق بين Adafruit Motor Shield V1 وV2؟

يستخدم الإصدار V1 المكتبة AFMotor.h: يضبط مسجل الإزاحة 74HC595 اتجاهات المحركات، وتزوّد شريحتا القيادة L293D المحركات بالتيار. ومسجل الإزاحة (Shift Register) شريحة تحوّل عددًا قليلًا من أطراف Arduino إلى ثمانية مخارج. أما الإصدار V2 فيستخدم I2C (ناقل بسلكين) والمكتبة Adafruit_MotorShield. يعتمد هذا الشرح على V1؛ وإذا كان لديك V2 فعليك تغيير المكتبة وكود إعداد المحركات.

هل يمكنني استخدام وحدة HC-06 بدلاً من HC-05؟

نعم. تعمل HC-06 في الوضع التابع (Slave) فقط: فهي تنتظر أن يتصل بها جهاز آخر ولا تستطيع بدء الاتصال بنفسها. وهذا كافٍ هنا لأن الهاتف هو الذي يفتح الاتصال. وتبقى التوصيلات والكود كما هي.

لماذا لا تسير سيارتي في خط مستقيم؟

يؤدي تفاوت سرعات المحركات، أو اختلاف احتكاك العجلات، أو الفروق الصغيرة بين المحركين الأيسر والأيمن إلى انحراف السيارة. امنح الجانب الأسرع سرعة أقل قليلًا: تستخدم الدالة forward() قيمة واحدة للمحركات الأربعة كلها، لذا أضف قيمة ثانية للمحركين M3 وM4. وللسير في خط مستقيم بدقة تحتاج إلى مشفرات للعجلات (مستشعرات تعدّ دورات العجلات) وإلى تحكم PID، وهو حلقة تغذية راجعة تصحح سرعة المحرك بناءً على الخطأ المقيس.

المصادر والمشاريع التالية

شروحات OmArTronics ذات الصلة

أفكار للتطوير

  • أضف مستشعر موجات فوق صوتية أماميًا مثبتًا على محرك سيرفو للمسح بزاوية 180 درجة (راجع دليل رادار Arduino باستخدام HC-SR04 ومحرك سيرفو الخاص بي).
  • أضف مستشعرات تتبع الخط للتنقل على مسار محدد (راجع شرح روبوت تتبع الخط باستخدام مستشعرات KY-033 الخاص بي).
  • أضف شاشة OLED أو LCD تعرض قراءات المستشعرات وحالة الاتصال.
  • أضف مشفرات للعجلات وتحكم PID في المحركات لموازنة السرعات والسير في خط مستقيم (راجع الأسئلة الشائعة أعلاه).

أضف تعليق