يحتوي هذا المقال على روابط تسويق بالعمولة مُعلَّمة بعلامة *. بصفتي شريكًا في برنامج Amazon Associates، أربح من عمليات الشراء المؤهلة، دون أي تكلفة إضافية عليك. يساعد ذلك على إبقاء الشروحات مجانية.
📋 ملخص سريع
OmObiArm مركبة جوالة (Rover) بأربع عجلات تعلوها ذراع روبوتية بستة محركات سيرفو. للذراع خمسة مفاصل إضافة إلى القابض. تتحكم لوحة Arduino Mega 2560 في الاثنين معًا: في محركات التيار المستمر الأربعة عبر Adafruit Motor Shield V1.2، وفي محركات السيرفو عبر لوحة قيادة السيرفو PCA9685. ويرسل تطبيق Android مبني في MIT App Inventor الأوامر عبر وحدة البلوتوث HC-05.
يجمع OmObiArm بين اثنين من مشاريعي السابقة: OmObi، المركبة الجوالة التي يُتحكَّم فيها عبر البلوتوث، و OmArm، الذراع الروبوتية ذات التحكم عبر البلوتوث. تُثبَّت الذراع على اللوح العلوي للمركبة، وتشغّل لوحة Arduino Mega واحدة الاثنين معًا. تتحرك المركبة إلى الأمام والخلف وتدور في مكانها (التوجيه الانزلاقي Skid Steering: تدور العجلات اليسرى واليمنى في اتجاهين متعاكسين). تحرّك ستة منزلقات في التطبيق مفاصل الذراع، ويسجّل زر Save أوضاع الذراع، ويعيد زر Play تشغيلها. في الاختبار في نهاية الفيديو، حفظت عدة أوضاع ثم بدأت التشغيل، فرفع الروبوت جسمًا ونقله إلى مكان آخر.

كيف يعمل OmObiArm
لوحة Arduino Mega 2560 هي وحدة التحكم المركزية. تستقبل الأوامر من الهاتف عبر الوحدة HC-05 على أطراف المنفذ التسلسلي العتادي (RX0 وTX0، الطرفان 0 و1). وبحسب الأمر، تقود المركبة أو تحرّك الذراع.
للمركبة، تستخدم لوحة Arduino درع المحركات Adafruit Motor Shield V1.2. يُركَّب الدرع مباشرة فوق Arduino Mega ويقود محركات التيار المستمر الأربعة عبر شرائح L293D. كل شريحة L293D جسر H مزدوج (H-bridge: دائرة ترانزستورات تتيح تشغيل محرك التيار المستمر في الاتجاهين). شرحت طريقة عمل الدرع في درس سيارة الروبوت المتحكَّم فيها عبر البلوتوث باستخدام Adafruit Motor Shield.
التحكم في الذراع والاتصال اللاسلكي
للذراع، تتواصل لوحة Arduino مع لوحة قيادة السيرفو PCA9685 ذات 16 قناة عبر I2C (ناقل بسلكين: SDA ينقل البيانات وSCL ينقل إشارة الساعة). تولّد PCA9685 إشارات PWM (تعديل عرض النبضة: طول النبضة يحدد زاوية السيرفو) لستة محركات سيرفو على القنوات 0 و4 و8 و9 و12 و13. القنوات غير متتالية؛ فالمصفوفة servoChannels في الكود تربط محركات السيرفو من 1 إلى 6 بهذه القنوات. تحرّك ثلاثة محركات سيرفو MG996R القاعدة والكتف والكوع، وهي التي تتحمل الحمل الأكبر، وتحرّك ثلاثة محركات سيرفو مصغّرة SG90 المعصم والقابض.
تحصل محركات السيرفو على طاقتها من محوّل خافض للجهد LM2596 (Buck Converter: وحدة DC-DC صغيرة قابلة للضبط لخفض الجهد) يخفض جهد بطارية LiPo (ليثيوم بوليمر، انظر 4.2) من 7.4 V إلى 6 V. وبذلك لا يمر تيار السيرفو أبدًا عبر منظم الجهد في لوحة Arduino. ولربط الروبوتين كهربائيًا، أضفت LM2596 وPCA9685 إلى إلكترونيات المركبة. لأساسيات السيرفو وPCA9685، راجع ما شرحته في دليل التحكم بمحرك السيرفو مع Arduino.
يرسل تطبيق Android، المبني باستخدام MIT App Inventor، أوامر نصية قصيرة عبر البلوتوث. ويسرد جدول مرجع الأوامر في الخطوة 5 جميع هذه الأوامر.
المكونات
تحتاج إلى القطع المطبوعة بالطابعة ثلاثية الأبعاد (3D) لمركبة OmObi وذراع OmArm، إضافة إلى الإلكترونيات والمحركات وأدوات التثبيت المذكورة أدناه.
نظرة عامة
| الفئة | المكونات |
|---|---|
| وحدة التحكم | Arduino Mega 2560 |
| وحدة قيادة المحركات | Adafruit Motor Shield V1.2 (L293D) |
| لوحة قيادة السيرفو | لوحة قيادة السيرفو PCA9685 بـ 16 قناة عبر I2C |
| بلوتوث | وحدة HC-05 |
| المحركات | 4 محركات تيار مستمر بعلب تروس، 4 عجلات |
| محركات السيرفو | 3 محركات MG996R (عزم دوران عالٍ)، 3 محركات SG90 (مصغّرة) |
| الطاقة | بطارية LiPo بجهد 7.4 V من نوع 2S (5300 mAh)، ومحوّل خافض للجهد LM2596 مضبوط على 6 V، ومفتاح طاقة |
| القطع المطبوعة بالطابعة ثلاثية الأبعاد (3D) | الهيكل، واللوح العلوي، ووصلات الذراع، والقابض مع التروس والجُلَب، ومحور المفصل، والأغطية، وحامل Arduino (من تصميمَي OmObi وOmArm) |
| الملحقات | أسلاك التوصيل (Jumper)، ومقاومات 1 kΩ و2 kΩ (مقسم الجهد، انظر 4.5)، وبراغي M3 وM2، وحشوات ملولبة حرارية M3، ونابض شد، ومحمل كروي 6806ZZ (30x42x7 mm)، وأذرع السيرفو (Horns: رافعات صغيرة تُثبَّت بالبراغي على أعمدة محركات السيرفو) |
قائمة المواد الكاملة (BOM)
القطع التي تحمل علامة “طباعة ثلاثية الأبعاد (3D)” متوفرة بصيغة ملفات STL في متجر OmArTronics. الأرقام هي أرقام البنود في قائمة القطع الخاصة بنموذج CAD.
القطع المطبوعة بالطابعة ثلاثية الأبعاد (3D)
| الرقم | الكمية | الجزء | ملاحظات |
|---|---|---|---|
| 1 | 1 | القاعدة السفلية (الهيكل) | طباعة ثلاثية الأبعاد (3D). الجزء السفلي من الهيكل، يحمل المحركات والإلكترونيات |
| 29 | 1 | إطار الهيكل | طباعة ثلاثية الأبعاد (3D). إطار الهيكل |
| 31 | 1 | قاعدة سيارة الروبوت | قاعدة مطبوعة بالطابعة ثلاثية الأبعاد (3D) |
| 30 | 3 | الغطاء | أغطية مطبوعة بالطابعة ثلاثية الأبعاد (3D) |
| 15 | 1 | وصلة قاعدة الذراع | طباعة ثلاثية الأبعاد (3D). تربط الذراع بالمركبة |
| 10 | 1 | قاعدة الذراع العلوية | طباعة ثلاثية الأبعاد (3D). الجزء العلوي من قاعدة الذراع |
| 3 | 1 | وصلة الذراع 1 | طباعة ثلاثية الأبعاد (3D) |
| 14 | 1 | وصلة الذراع 2 | طباعة ثلاثية الأبعاد (3D) |
| 13 | 1 | وصلة الذراع 3 | طباعة ثلاثية الأبعاد (3D) |
| 9 | 4 | الوصلة | قطع وصلات مطبوعة بالطابعة ثلاثية الأبعاد (3D) |
| 8 | 1 | مجموعة القابض | مجموعة القابض الكاملة مطبوعة بالطابعة ثلاثية الأبعاد (3D) |
| 4 | 1 | إصبع القابض الأيمن | طباعة ثلاثية الأبعاد (3D) |
| 6 | 1 | ترس القابض الأيمن | طباعة ثلاثية الأبعاد (3D). الترس الأيمن لآلية القابض |
| 7 | 1 | ترس القابض الأيسر | طباعة ثلاثية الأبعاد (3D). الترس الأيسر لآلية القابض |
| 5 | 2 | جُلبة القابض | طباعة ثلاثية الأبعاد (3D). جُلبة لمحور مفصل القابض |
| 18 | 1 | محور المفصل | طباعة ثلاثية الأبعاد (3D). محور يثبّت المفاصل في الذراع |
الإلكترونيات
| الرقم | الكمية | الجزء | ملاحظات |
|---|---|---|---|
| 32 | 1 | Arduino Mega 2560 | وحدة التحكم |
| 33 | 1 | وحدة قيادة المحركات L293D (Adafruit Motor Shield V1.2) | درع محركات بجسر H مزدوج لمحركات التيار المستمر الأربعة |
| 43 | 1 | لوحة قيادة السيرفو PCA9685 | لوحة قيادة سيرفو بـ 16 قناة عبر I2C |
| 40 | 1 | وحدة البلوتوث HC-05 | الاتصال اللاسلكي بالهاتف |
| 42 | 1 | محوّل خافض للجهد LM2596 | يخفض جهد البطارية إلى 6 V لمحركات السيرفو |
| 34 | 1 | بطارية LiPo 2S بجهد 7.4 V، بسعة 5300 mAh | مزود الطاقة الرئيسي |
| 36 | 1 | المفتاح | مفتاح الطاقة الرئيسي |
المحركات ومحركات السيرفو والعجلات
| الرقم | الكمية | الجزء | ملاحظات |
|---|---|---|---|
| 44 | 4 | محرك تيار مستمر بعلبة تروس | نظام دفع المركبة |
| 26 | 4 | ترس المحرك | ترس بلاستيكي لعمود المحرك |
| 28 | 4 | عجلة عربة 4WD | عجلة بلاستيكية |
| 27 | 4 | إطار 4WD | إطار مطاطي |
| 12 | 3 | سيرفو MG996R | القاعدة، الكتف، الكوع |
| 11 | 3 | سيرفو مصغّر SG90 | ميل المعصم (Pitch)، دوران المعصم (Roll)، القابض |
| 20 | 3 | ذراع سيرفو MG955 (Horn) | أذرع السيرفو (Horns) لمحركات MG996R (القطعة في نموذج CAD مسمّاة باسم MG995، الذي يستخدم ذراع السيرفو نفسه ذا 25 سنًّا) |
| 21 | 3 | ذراع سيرفو SG90 (Horn) | أذرع (Horns) لمحركات SG90 |
أدوات التثبيت والمحامل والقطع الصغيرة
| الرقم | الكمية | الجزء | ملاحظات |
|---|---|---|---|
| 2 | 1 | JIS B 1521 6806, 30x42x7 mm | محمل كروي ذو أخدود عميق لدوران قاعدة الذراع |
| 17 | 1 | نابض شد | يوفّر الشد لآلية الوصلات |
| 35 | 16 | حشوات M3 | حشوات ملولبة حرارية للقطع المطبوعة |
| 16 | 7 | AS 1420-1973 M3x16 | برغي برأس أسطواني وتجويف سداسي |
| 37 | 16 | AS 1420-1973 M3x6 | براغي مترية ISO برأس أسطواني وتجويف سداسي |
| 38 | 4 | ANSI B18.3.1M M3x0.5×30 | برغي متري برأس أسطواني وتجويف سداسي مُشكَّل بالسحب (Broached) |
| 39 | 4 | ANSI B18.3.1M M3x0.5×25 | برغي متري برأس أسطواني وتجويف سداسي مُشكَّل بالسحب (Broached) |
| 24 | 2 | DIN 7985 (Z) M3x6-Z | براغي برأس أسطواني مرتفع (Cheese Head) وتجويف صليبي، النوع Z |
| 25 | 3 | DIN 7985 (Z) M2x6-Z | براغي برأس أسطواني مرتفع (Cheese Head) وتجويف صليبي، النوع Z |
| 19 | 1 | ANSI B18.2.4.2M M3x0.5 | صامولة سداسية (لولب متري قياسي) |
| 22 | 8 | ANSI B18.6.4 No. 3-28 x 1/2 | برغي ذاتي اللولبة برأس مقبّب عريض (Truss Head) وتجويف صليبي، النوع AB، النوع I |
| 23 | 16 | ANSI B18.6.4 No. 2-32 x 3/8 | برغي ذاتي اللولبة برأس مقبّب عريض (Truss Head) وتجويف صليبي، النوع AB، النوع I |
| 41 | 4 | ANSI B18.6.4 No. 0-48 x 1/4 | برغي ذاتي اللولبة برأس مقبّب عريض (Truss Head) وتجويف صليبي، النوع AB، النوع I |
الخطوة 1: تصميم النموذج ثلاثي الأبعاد (3D)
يجمع OmObiArm بين تصميمَي مركبة OmObi وذراع OmArm. في Autodesk Inventor عدّلت قاعدة OmArm ودمجتها مع اللوح العلوي لمركبة OmObi، فاللوح العلوي هو أيضًا قاعدة الذراع. ولكابلات السيرفو صنعت فتحة في القاعدة، فتمر الكابلات نزولًا إلى داخل المركبة. إذا كنت جديدًا على التصميم ثلاثي الأبعاد (3D) للروبوتات، فابدأ بدرسي مقدمة إلى الطباعة ثلاثية الأبعاد (3D) وAutodesk Inventor. يوضح الرسم الهندسي أدناه الأبعاد الرئيسية ومناظر المقاطع للتجميع.

الخطوة 2: الطباعة ثلاثية الأبعاد (3D) للقطع
اطبع كل القطع التي تحمل علامة “طباعة ثلاثية الأبعاد (3D)” في قائمة المواد (BOM). يناسب خيط الطباعة PLA معظم القطع. إعدادات الطابعة واختيار المادة مشروحة في درس المقدمة المذكور رابطه في الخطوة 1.
الخطوة 3: تجميع OmObiArm
3.1 تركيب المحركات والعجلات
يحتوي الهيكل على حشوات ملولبة حرارية M3 (جُلَب معدنية ذات لولب داخلي تغرسها في البلاستيك بالحرارة). عندما بنيت مركبة OmObi سخّنتها وضغطتها داخل القطع المطبوعة، فصارت البراغي تُثبَّت في لولب معدني. ثبّت كل محرك من محركات التيار المستمر الأربعة على حوامل المحركات المطبوعة بالبراغي والصواميل، ثم ركّب عجلة على عمود كل محرك.
3.2 تركيب Arduino Mega ودرع المحركات وPCA9685 وLM2596 وHC-05
ثبّت حامل Arduino المطبوع على الهيكل بالبراغي، ثم ثبّت Arduino Mega في الحامل. ركّب Adafruit Motor Shield فوق أطراف التوصيل في Arduino؛ فهو لا يحتاج إلى أسلاك إضافية. تأكد من أن كل طرف (Pin) مستقر في مقبسه قبل أن تضغط الدرع إلى الأسفل. ثبّت PCA9685 وLM2596 وHC-05 على الهيكل بأربطة بلاستيكية (Zip Ties) أو بشريط لاصق مزدوج الوجه، متقاربة بما يكفي لتبقى الأسلاك قصيرة.
3.3 تثبيت اللوح العلوي
ثبّت اللوح العلوي الذي يحمل الذراع على الهيكل بالبراغي بإحكام، حتى لا تهتز الذراع أثناء سير المركبة.
3.4 تجميع الذراع
تُجمَّع الذراع كما هو موضح في دليل بناء OmArm. مقاسات البراغي في هذا القسم مطابقة لأدلة OmArm الخاصة بي. أما قائمة قطع CAD أعلاه فتستخدم براغي قياسية من مكتبة Inventor، لذلك لن تجد هذه المقاسات فيها.
الخطوة الفرعية 1: تثبيت السيرفو على القاعدة. ثبّت أول محرك سيرفو MG996R على قاعدة الذراع بالبراغي المرفقة مع السيرفو، أو ببراغي ذاتية اللولبة M3x12 (براغي تشق لولبها بنفسها في البلاستيك). أضف المحمل الكروي 6806ZZ (30x42x7 mm) لدوران القاعدة؛ فهو يقلل الاحتكاك لتدور القاعدة بسلاسة. ثم ثبّت ذراع السيرفو (Horn) على الجزء العلوي الدوّار ببراغي ذاتية اللولبة M2x12.
الخطوة الفرعية 2: توسيط محركات السيرفو. اضبط كل سيرفو على وضعه المركزي قبل تركيب ذراعه (Horn). ولهذا الغرض، وصّل السيرفو بـ PCA9685 كما في الخطوة 4، وارفع برنامج Arduino (Sketch) من الخطوة 5، وشغّل الطاقة مرة واحدة: تحرّك الدالة setup() كل محرك سيرفو إلى قيمة PWM 375 (90°). ثم ركّب ذراع السيرفو بحيث تكون الوصلة في وضعها الأوسط، وثبّته ببرغي الذراع. أما ذراع السيرفو المركّب بزاوية منحرفة فيدفع المفصل نحو مصدّه الميكانيكي عند التشغيل التالي.
الخطوة الفرعية 3: تركيب الوصلات. ثبّت كل سيرفو على وصلة الذراع الخاصة به بالبراغي، واربط الوصلات عبر أذرع السيرفو. تأكد من أن المفاصل تتحرك بسلاسة وأنها مثبتة بإحكام.
الخطوة الفرعية 4: تجميع القابض. ثبّت القابض على الوصلة الأخيرة ببراغي M3x20. تأكد من أنه مثبت بإحكام على الذراع. ستختبر فتحه وإغلاقه بالتطبيق في 7.3.
3.5 الفحص الميكانيكي
قبل توصيل بقية الروبوت، أدر كل عجلة باليد وتأكد من أنها لا تحتك بالهيكل، وتأكد من عدم وجود رأس برغي أو كابل يعيق أي مفصل في الذراع.
الخطوة 4: التوصيلات
قد يؤدي التوصيل الخاطئ إلى إتلاف اللوحات. وصّل كل مجموعة أدناه وقارنها بمخطط التوصيل في نهاية هذه الخطوة.
4.1 توصيل محركات التيار المستمر بدرع المحركات
| المحرك | طرف درع المحركات | الجانب |
|---|---|---|
| المحرك الأمامي الأيسر | M1 | يسار |
| المحرك الخلفي الأيسر | M2 | يسار |
| المحرك الأمامي الأيمن | M3 | يمين |
| المحرك الخلفي الأيمن | M4 | يمين |
وصّل المحركين الأيسرين بالمخرجين M1 وM2، والمحركين الأيمنين بالمخرجين M3 وM4. تحدد قطبية كل محرك اتجاه دورانه؛ وستتحقق منها في 7.2.
4.2 البطارية ومسار الطاقة الرئيسي
أستخدم بطارية LiPo من نوع 2S بجهد 7.4 V (خليتان على التوالي، الجهد الاسمي لكل منهما 3.7 V) بسعة 5300 mAh وموصل EC3. استخدم حزمة بطاريات لا تقل سعتها عن ذلك. الحَمْ على مدخل الدائرة القابسَ المناسب لبطاريتك: EC3 مثل قابسي (قابس بطرفين شائع في بطاريات نماذج RC)، أو XT60، أو قابس T. ضع مفتاح الطاقة بين البطارية والدائرة.
اشحن حزمة البطاريات فقط بشاحن موازنة مخصص لبطاريات LiPo (Balance Charger). يشحن هذا الشاحن كل خلية إلى الجهد نفسه عبر قابس الموازنة الصغير في الحزمة. لا تترك الحزمة أبدًا تُشحن دون مراقبة. توقف عن القيادة عندما ينخفض جهد الحزمة إلى نحو 6.6 V (أي 3.3 V لكل خلية)؛ فتحت 3.0 V لكل خلية قد تتلف الخلايا تلفًا دائمًا. يُنبّهك منبّه جهد LiPo موصول بقابس الموازنة في الوقت المناسب. إذا خزّنت الحزمة لأكثر من بضعة أيام، فاشحنها أو فرّغها إلى نحو 7.6 V (أي 3.8 V لكل خلية)، وهو جهد التخزين؛ ومعظم شواحن الموازنة فيها وضع تخزين لهذا الغرض.
تغذي البطارية طرف EXT_PWR في درع المحركات (طرف التوصيل اللولبي لتغذية المحركات)، وتغذي LM2596 بالتوازي. وعند تركيب وصلة PWR (Jumper) على الدرع، يمرّر الدرع جهد البطارية إلى الطرف VIN في Arduino (مدخل التغذية الخارجية)، ويُنتج منظم الجهد الخاص بلوحة Arduino منه 5 V. ركّب هذه الوصلة: ففي مخطط التوصيل هي مسار الطاقة الوحيد للوحة Arduino بعد فصل كابل USB.
4.3 محوّل خفض الجهد LM2596
| طرف LM2596 | التوصيل |
|---|---|
| IN+ | القطب الموجب للبطارية (7.4 V، عبر المفتاح) |
| IN− | أرضي البطارية |
| OUT+ | طرف التوصيل اللولبي V+ في PCA9685 (تغذية السيرفو، مضبوطة على 6 V) |
| OUT− | طرف GND اللولبي في PCA9685 |
قبل توصيل PCA9685، أدر المقاومة المتغيرة (Potentiometer: مقاومة صغيرة قابلة للضبط ببرغي في أعلاها) على LM2596 حتى يُظهر جهاز القياس المتعدد 6 V تمامًا عند OUT+. لحمت أسلاك المحوّل أولًا. ومع البطارية المشحونة كان جهد الدخل نحو 8 V، فأدرت البرغي وأنا أقيس الخرج حتى ظهرت القيمة 6 V. يغذي خط 6 V هذا طرف السيرفو في PCA9685 ومحركات السيرفو الستة كلها.
يوفّر LM2596 نحو 2 إلى 3 A من دون مشتت حراري، وقد يسحب محرك MG996R واحد نحو 2.5 A عند التوقف (Stall). إذا أُعيد تشغيل Arduino أو ارتعشت محركات السيرفو عندما تتحرك عدة مفاصل في وقت واحد، فاستبدل الوحدة بمحوّل خافض للجهد XL4016 (حتى 8 A). تبقى التوصيلات وضبط الجهد على 6 V كما هي.
4.4 لوحة قيادة السيرفو PCA9685 ومحركات السيرفو
| طرف PCA9685 | التوصيل |
|---|---|
| V+ (طرف توصيل لولبي، تغذية السيرفو) | LM2596 OUT+ (6 V) |
| GND (طرف توصيل لولبي) | LM2596 OUT− |
| SDA | Arduino Mega SDA (الطرف 20) |
| SCL | Arduino Mega SCL (الطرف 21) |
| VCC (طرف تغذية المنطق) | Arduino 5 V |
| GND (أرضي المنطق) | Arduino GND |
لا توصل مصدر 6 V أبدًا بطرف المنطق VCC؛ فشريحة PCA9685 مصممة لجهد 5.5 V كحد أقصى. ولا تغذِّ محركات السيرفو من الطرف 5 V في Arduino أيضًا، فستة محركات سيرفو تسحب تيارًا أكبر مما تستطيع اللوحة توفيره، ما يسبب هبوطًا في الجهد واهتزاز السيرفو وإعادة تشغيل Arduino.
ركّب كل سيرفو على قناته بحيث تطابق أطراف الإشارة وV+ وGND العلامات المطبوعة على اللوحة. يستخدم الكود هذه القنوات:
| Servo | المفصل | قناة PCA9685 | النوع |
|---|---|---|---|
| السيرفو 1 | دوران القاعدة | 0 | MG996R |
| السيرفو 2 | الكتف | 4 | MG996R |
| السيرفو 3 | الكوع | 8 | MG996R |
| السيرفو 4 | ميل المعصم | 9 | SG90 |
| السيرفو 5 | دوران المعصم | 12 | SG90 |
| السيرفو 6 | القابض | 13 | SG90 |
4.5 وحدة البلوتوث HC-05
| طرف HC-05 | طرف Arduino Mega |
|---|---|
| VCC | 5 V |
| GND | GND |
| TXD | RX0 (الطرف 0) |
| RXD | TX0 (الطرف 1) |
يعمل الطرف RXD في HC-05 بمنطق 3.3 V، بينما يُخرج الطرف TX0 في Mega جهد 5 V. في الروبوت الذي بنيته يتصل RXD مباشرة بالطرف 1 كما في مخطط التوصيل، وقد عمل الروبوت بهذه الطريقة. لكن مقسم الجهد (مقاومتان تقسمان الجهد) هو الخيار الأكثر أمانًا: فمع مقاومة 1 kΩ من TX0 إلى RXD ومقاومة 2 kΩ من RXD إلى GND، يصل إلى RXD جهد 5 V × 2 kΩ / 3 kΩ ≈ 3.3 V.
الطرفان 0 و1: تتصل وحدة HC-05 بالمنفذ التسلسلي العتادي Serial0 في Mega (الطرفان 0 و1). ويستخدم اتصال USB الطرفين نفسيهما عند رفع برنامج Arduino (Sketch). افصل HC-05، أو على الأقل سلكي TXD وRXD، قبل كل عملية رفع؛ فإذا بقيت الوحدة موصولة يفشل الرفع بخطأ في الاتصال. ثم أعد توصيلها بعد ذلك. البديل هو Serial1 (الطرفان 18 و19): وصّل TXD بـ RX1 وRXD بـ TX1 (مع مقسم الجهد). وفي البرنامج أضف Serial1.begin(9600) إلى setup() واقرأ الأوامر من Serial1 بدلًا من Serial. يبقى Serial على اتصال USB، فيعمل الرفع والشاشة التسلسلية (Serial Monitor: نافذة Arduino IDE التي تعرض النص الذي ترسله اللوحة) حتى والوحدة موصولة. شرحتُ التوصيل والاقتران خطوة بخطوة في درس وحدة البلوتوث HC-05/HC-06.
4.6 اختبار توزيع الطاقة
قبل التشغيل لأول مرة، تحقق بجهاز القياس المتعدد في وضع فحص الاستمرارية (Continuity) من عدم وجود دارة قصر بين القطبين الموجب والسالب للبطارية. في هذا الوضع يُصدر الجهاز صفيرًا عند اتصال نقطتين. ثم شغّل الطاقة وقِس: 5 V عند Arduino وHC-05، و6 V عند طرف V+ في PCA9685، وجهد البطارية عند طرف EXT_PWR في الدرع. تحصل المحركات على جهد أقل قليلًا، لأن شرائح القيادة L293D تفقد جزءًا من الجهد.

الخطوة 5: برمجة Arduino
يقرأ البرنامج أوامر البلوتوث، ويقود محركات المركبة، ويحرّك محركات السيرفو خطوة بخطوة، ويتولى حفظ الأوضاع وتشغيلها. الكود الكامل وملف مشروع MIT App Inventor موجودان في مستودع GitHub. إذا كنت جديدًا على Arduino، فاقرأ درسي أساسيات برمجة Arduino أولًا. افصل على الأقل سلكي TXD وRXD في HC-05 قبل الرفع (انظر 4.5). ولرفع الكود نزعت غطاء منفذ USB المطبوع ووصلت الروبوت بحاسوبي.
كود Arduino الكامل
#include <Wire.h>
#include <Adafruit_PWMServoDriver.h>
#include <AFMotor.h>
// Constants and definitions
const int numServos = 6; // Number of servos
const int maxConfigurations = 10; // Maximum number of storable poses
const int stepDelay = 10; // Delay between each step to slow down the servo movement
const int stepSize = 1; // PWM units per step
// Servo channels on the PCA9685
const int servoChannels[numServos] = {0, 4, 8, 9, 12, 13};
// Storage structures
int savedConfigurations[maxConfigurations][numServos];
int currentServoPositions[numServos] = {375, 375, 375, 375, 375, 375}; // Default positions
int configCount = 0; // Counter for stored poses
bool isPlaying = false; // Status indicating if poses are being played
bool loopPlayback = false; // Status indicating if poses should be played in a loop
bool stopPlaying = false; // Status indicating if playback should be stopped
int currentPoseIndex = 0; // Index of the current pose during playback
// Motor instances for the rover
AF_DCMotor motor1(1);
AF_DCMotor motor2(2);
AF_DCMotor motor3(3);
AF_DCMotor motor4(4);
Adafruit_PWMServoDriver pwm = Adafruit_PWMServoDriver();
String input = ""; // Holds incoming Bluetooth data
void setup() {
Serial.begin(9600); // Serial communication for Bluetooth
// Initialize motors
motor1.setSpeed(255);
motor2.setSpeed(255);
motor3.setSpeed(255);
motor4.setSpeed(255);
// Initialize servo controller
pwm.begin();
pwm.setPWMFreq(50); // Set frequency to 50 Hz for servos
// Initialize servo positions
for (int i = 0; i < numServos; i++) {
pwm.setPWM(servoChannels[i], 0, currentServoPositions[i]);
}
Serial.println("Bluetooth Robot Controller ready. Waiting for commands...");
}
void loop() {
// Process incoming commands
if (Serial.available()) {
input = Serial.readStringUntil('\n'); // Read data from Bluetooth
input.trim();
Serial.println("Received command: " + input);
processCommand(input);
}
// Check if robot arm is playing poses
if (isPlaying && !stopPlaying) {
if (loopPlayback) {
playPosesInLoop();
} else {
playNextPose();
}
}
}
void processCommand(String command) {
// If playing, ignore all rover commands
if (isPlaying) {
if (command == "s") { // Allow stop playback command
stopPlayingPoses();
} else {
Serial.println("Playback in progress. Ignoring command: " + command);
}
return;
}
// Control rover movement
if (command == "F") {
forward();
} else if (command == "B") {
backward();
} else if (command == "L") {
turnLeft();
} else if (command == "R") {
turnRight();
} else if (command == "S") {
stopMotors(); // Stop motors immediately on "S" command
// Control robotic arm
} else if (command.startsWith("1,")) {
processServoCommand(command, 0); // Control Servo 1
} else if (command.startsWith("2,")) {
processServoCommand(command, 1); // Control Servo 2
} else if (command.startsWith("3,")) {
processServoCommand(command, 2); // Control Servo 3
} else if (command.startsWith("4,")) {
processServoCommand(command, 3); // Control Servo 4
} else if (command.startsWith("5,")) {
processServoCommand(command, 4); // Control Servo 5
} else if (command.startsWith("6,")) {
processServoCommand(command, 5); // Control Servo 6
// Save, play, reset poses
} else if (command == "v") {
saveCurrentPose();
} else if (command == "l") {
startPlayingPoses();
} else if (command == "r") {
resetPoses();
} else if (command == "s") {
stopPlayingPoses();
} else if (command == "o") {
loopPlayback = true;
Serial.println("Loop playback enabled.");
} else if (command == "f") {
loopPlayback = false;
Serial.println("Loop playback disabled.");
} else {
Serial.println("Unknown command: " + command);
}
}
void processServoCommand(String command, int servoIndex) {
int commaIndex = command.indexOf(',');
if (commaIndex > 0) {
int position = command.substring(commaIndex + 1).toInt();
int pwmValue = map(position, 0, 180, 150, 600);
moveToPositionSmoothly(servoIndex, pwmValue);
}
}
التحكم في المحركات وحركة السيرفو
void forward() {
motor1.run(FORWARD);
motor2.run(FORWARD);
motor3.run(FORWARD);
motor4.run(FORWARD);
Serial.println("Moving forward");
}
void backward() {
motor1.run(BACKWARD);
motor2.run(BACKWARD);
motor3.run(BACKWARD);
motor4.run(BACKWARD);
Serial.println("Moving backward");
}
void turnLeft() {
motor1.run(BACKWARD);
motor2.run(BACKWARD);
motor3.run(FORWARD);
motor4.run(FORWARD);
Serial.println("Turning left");
}
void turnRight() {
motor1.run(FORWARD);
motor2.run(FORWARD);
motor3.run(BACKWARD);
motor4.run(BACKWARD);
Serial.println("Turning right");
}
void stopMotors() {
motor1.run(RELEASE);
motor2.run(RELEASE);
motor3.run(RELEASE);
motor4.run(RELEASE);
Serial.println("Motors stopped");
}
// Arm servo control
void moveToPositionSmoothly(int servoIndex, int targetPwmValue) {
int currentPwmValue = currentServoPositions[servoIndex];
if (targetPwmValue > currentPwmValue) {
for (int pos = currentPwmValue; pos <= targetPwmValue; pos += stepSize) {
pwm.setPWM(servoChannels[servoIndex], 0, pos);
delay(stepDelay);
}
} else {
for (int pos = currentPwmValue; pos >= targetPwmValue; pos -= stepSize) {
pwm.setPWM(servoChannels[servoIndex], 0, pos);
delay(stepDelay);
}
}
currentServoPositions[servoIndex] = targetPwmValue;
Serial.println("Servo " + String(servoIndex + 1) + " set to position: " + String(targetPwmValue));
}
دوال الأوضاع
void saveCurrentPose() {
if (configCount < maxConfigurations) {
for (int i = 0; i < numServos; i++) {
savedConfigurations[configCount][i] = currentServoPositions[i];
}
configCount++;
Serial.println("Pose saved. Total poses: " + String(configCount));
} else {
Serial.println("Memory full. Cannot save pose.");
}
}
void startPlayingPoses() {
if (configCount > 0) {
stopMotors(); // Ensure rover is stopped during playback
isPlaying = true;
stopPlaying = false;
currentPoseIndex = 0;
Serial.println("Starting pose playback");
} else {
Serial.println("No poses saved");
}
}
void playNextPose() {
if (currentPoseIndex < configCount) {
for (int i = 0; i < numServos; i++) {
moveToPositionSmoothly(i, savedConfigurations[currentPoseIndex][i]);
}
delay(1000);
currentPoseIndex++;
} else {
isPlaying = false;
Serial.println("Pose playback finished");
}
}
void playPosesInLoop() {
for (int i = 0; i < configCount; i++) {
if (stopPlaying) break; // Never true here: loop() reads no commands while this runs
for (int j = 0; j < numServos; j++) {
moveToPositionSmoothly(j, savedConfigurations[i][j]);
}
delay(1000);
}
}
void stopPlayingPoses() {
stopPlaying = true;
isPlaying = false;
Serial.println("Pose playback stopped");
}
void resetPoses() {
configCount = 0;
isPlaying = false;
loopPlayback = false;
currentPoseIndex = 0;
Serial.println("All poses reset");
}
شرح الكود: المكتبات
#include <Wire.h> #include <Adafruit_PWMServoDriver.h> #include <AFMotor.h>
يحتاج البرنامج إلى ثلاث مكتبات. تتولى Wire.h اتصال I2C مع PCA9685. وتتحكم Adafruit_PWMServoDriver.h في محركات السيرفو عبر PCA9685. أما AFMotor.h فهي مكتبة Adafruit Motor Shield لمحركات التيار المستمر الموصولة بجسور H في شرائح L293D. ثبّت المكتبات الثلاث في Arduino IDE قبل الترجمة (Compile).
شرح الكود: قنوات السيرفو والمتغيرات
const int numServos = 6;
const int maxConfigurations = 10;
const int stepDelay = 10;
const int stepSize = 1;
const int servoChannels[numServos] = {0, 4, 8, 9, 12, 13};
int savedConfigurations[maxConfigurations][numServos];
int currentServoPositions[numServos] = {375, 375, 375, 375, 375, 375};
int configCount = 0;
bool isPlaying = false;
bool loopPlayback = false;
bool stopPlaying = false;
int currentPoseIndex = 0;
قيمة numServos هي 6 لأن الذراع تحتوي على ستة محركات سيرفو. ويحدّد maxConfigurations الحد الأقصى لعدد الوضعيات المحفوظة بـ 10. تُخزَّن الوضعيات في ذاكرة SRAM (ذاكرة العمل التي تُمسح عند انقطاع الطاقة)، لذا تضيع عند إطفاء الروبوت. ويحدّد stepDelay (10 ms) وstepSize (1 وحدة PWM) سرعة حركة محركات السيرفو: وحدة PWM واحدة كل 10 ms. وتربط المصفوفة servoChannels محركات السيرفو من 1 إلى 6 بقنوات PCA9685 ذات الأرقام 0 و4 و8 و9 و12 و13. وتخزّن المصفوفة currentServoPositions الموضع الحالي لكل محرك سيرفو. أما متغيرات الحالة (flags) isPlaying وloopPlayback وstopPlaying فتتحكم في تشغيل الوضعيات المحفوظة.
تخزّن المصفوفة قيم PCA9685 الخام وليس الدرجات. عند تردد 50 Hz تقسم شريحة PCA9685 كل دورة مدتها 20 ms إلى 4096 خطوة. والقيمة هي عدد الخطوات التي تبقى فيها النبضة في المستوى المرتفع: 150 تعادل نحو 0.7 ms، و600 نحو 2.9 ms. أما 375 فهي منتصف هذا المدى وتقابل 90°.
شرح الكود: كائنات المحركات ولوحة قيادة السيرفو
AF_DCMotor motor1(1); AF_DCMotor motor2(2); AF_DCMotor motor3(3); AF_DCMotor motor4(4); Adafruit_PWMServoDriver pwm = Adafruit_PWMServoDriver();
تُنشأ أربعة كائنات لمحركات التيار المستمر، واحد لكل مخرج من مخارج المحركات M1 إلى M4 في درع المحركات. والرقم بين القوسين هو رقم المخرج. ويستخدم الكائن pwm من النوع Adafruit_PWMServoDriver عنوان I2C الافتراضي 0x40 لشريحة PCA9685. وإذا أضفت لوحة PCA9685 ثانية، فمرّر عنوانها هنا، مثل Adafruit_PWMServoDriver(0x41). ويُضبط العنوان عبر وصلات اللحام (Solder Jumpers) من A0 إلى A5 على اللوحة.
شرح الكود: setup()
void setup() {
Serial.begin(9600);
motor1.setSpeed(255);
motor2.setSpeed(255);
motor3.setSpeed(255);
motor4.setSpeed(255);
pwm.begin();
pwm.setPWMFreq(50);
for (int i = 0; i < numServos; i++) {
pwm.setPWM(servoChannels[i], 0, currentServoPositions[i]);
}
}
تبدأ الدالة setup() تشغيل المنفذ التسلسلي بسرعة 9600 baud (عدد البتات في الثانية على الخط التسلسلي)، وهي السرعة الافتراضية لوحدة HC-05. ثم تضبط المحركات الأربعة على السرعة القصوى (255 من 255)، وتشغّل PCA9685 بتردد PWM قدره 50 Hz، وهو التردد المعتاد لمحركات السيرفو المستخدمة في مشاريع الهواة. بعد ذلك ترسل محركات السيرفو الستة مباشرة إلى قيمة PWM 375 (90°). هذه الحركة الأولى غير منعّمة، لذا تقفز الذراع عند التشغيل إلى وضعيتها المركزية بأقصى سرعة للسيرفو. أبعِد يديك وأي أجسام عن مدى وصول الذراع عند تشغيلها.
شرح الكود: loop()
void loop() {
if (Serial.available()) {
input = Serial.readStringUntil('\n');
input.trim();
processCommand(input);
}
if (isPlaying && !stopPlaying) {
if (loopPlayback) {
playPosesInLoop();
} else {
playNextPose();
}
}
}
تتحقق الدالة loop() من وصول بيانات على المنفذ التسلسلي. وعند وجود بيانات، تقرأ readStringUntil(‘\n’) أمرًا كاملًا واحدًا. يحدد حرف السطر الجديد (\n) نهاية الأمر، لذلك يجب أن ينتهي كل أمر يرسله التطبيق بـ \n. وبعد أن تزيل trim() المسافات وفواصل الأسطر، يُمرَّر الأمر إلى processCommand(). أما النصف الثاني من loop() فيتحقق مما إذا كان تشغيل الوضعيات نشطًا، وإن كان كذلك يستدعي playPosesInLoop() أو playNextPose().
شرح الكود: processCommand()
void processCommand(String command) {
if (isPlaying) {
if (command == "s") { stopPlayingPoses(); }
return;
}
if (command == "F") forward();
else if (command == "B") backward();
else if (command == "L") turnLeft();
else if (command == "R") turnRight();
else if (command == "S") stopMotors();
else if (command.startsWith("1,")) processServoCommand(command, 0);
else if (command.startsWith("2,")) processServoCommand(command, 1);
else if (command.startsWith("3,")) processServoCommand(command, 2);
else if (command.startsWith("4,")) processServoCommand(command, 3);
else if (command.startsWith("5,")) processServoCommand(command, 4);
else if (command.startsWith("6,")) processServoCommand(command, 5);
else if (command == "v") saveCurrentPose();
else if (command == "l") startPlayingPoses();
else if (command == "r") resetPoses();
else if (command == "s") stopPlayingPoses();
else if (command == "o") { loopPlayback = true; }
else if (command == "f") { loopPlayback = false; }
}
تحدد الدالة processCommand() الدالة التي تعالج كل أمر. وأثناء تشغيل الوضعيات (عندما تكون قيمة isPlaying هي true)، لا تقبل إلا الحرف الصغير s لإيقاف التشغيل وتتجاهل كل الأوامر الأخرى. وبذلك لا تصل أوامر القيادة إلى المحركات أثناء تنفيذ سلسلة الوضعيات، فلا يمكن للمركبة أن تنطلق بينما تعمل الذراع. وفي الحالات الأخرى تستدعي الدالة المطابقة للأمر. تقرأ processServoCommand() الرقم الذي يلي الفاصلة، وتحوّل المدى 0–180° إلى مدى PCA9685 150–600، ثم تستدعي دالة الحركة السلسة.
مرجع الأوامر
الأوامر حساسة لحالة الأحرف: الحرف الكبير S يوقف المحركات، والحرف الصغير s يوقف تشغيل الوضعيات. وأمر التشغيل هو الحرف L الصغير (l)، وليس الرقم 1. وينتهي كل أمر بحرف سطر جديد (\n).
| الأمر | الإجراء | المجموعة |
|---|---|---|
| F | التحرك إلى الأمام | المركبة |
| B | التحرك إلى الخلف | المركبة |
| L | الدوران يسارًا في المكان | المركبة |
| R | الدوران يمينًا في المكان | المركبة |
| S | إيقاف المحركات الأربعة كلها (يرسله التطبيق عندما تحرر زر القيادة) | المركبة |
| 1,angle | تحريك السيرفو 1 (القاعدة) إلى الزاوية angle (0–180) | الذراع |
| 2,angle | تحريك السيرفو 2 (الكتف) إلى الزاوية angle | الذراع |
| 3,angle | تحريك السيرفو 3 (المرفق) إلى الزاوية angle | الذراع |
| 4,angle | تحريك السيرفو 4 (ميل المعصم) إلى الزاوية angle | الذراع |
| 5,angle | تحريك السيرفو 5 (دوران المعصم) إلى الزاوية angle | الذراع |
| 6,angle | تحريك السيرفو 6 (القابض) إلى الزاوية angle | الذراع |
| v | حفظ وضعية الذراع الحالية | الوضعيات |
| l | تشغيل الوضعيات المحفوظة | الوضعيات |
| r | إعادة ضبط (مسح) جميع الوضعيات المحفوظة | الوضعيات |
| s | إيقاف تشغيل الوضعيات | الوضعيات |
| o | تفعيل وضع التكرار | الوضعيات |
| f | تعطيل وضع التكرار | الوضعيات |
شرح الكود: دوال محركات المركبة
void forward() {
motor1.run(FORWARD); motor2.run(FORWARD);
motor3.run(FORWARD); motor4.run(FORWARD);
}
void backward() {
motor1.run(BACKWARD); motor2.run(BACKWARD);
motor3.run(BACKWARD); motor4.run(BACKWARD);
}
void turnLeft() {
motor1.run(BACKWARD); motor2.run(BACKWARD);
motor3.run(FORWARD); motor4.run(FORWARD);
}
void turnRight() {
motor1.run(FORWARD); motor2.run(FORWARD);
motor3.run(BACKWARD); motor4.run(BACKWARD);
}
void stopMotors() {
motor1.run(RELEASE); motor2.run(RELEASE);
motor3.run(RELEASE); motor4.run(RELEASE);
}
تضبط كل دالة المحركات الأربعة دفعة واحدة. في الحركة إلى الأمام وإلى الخلف تدور كل المحركات في الاتجاه نفسه. أما عند الالتفاف فيدور المحركان الأيسران في اتجاه والمحركان الأيمنان في الاتجاه المعاكس، فتدور المركبة في مكانها (التوجيه بالانزلاق، Skid Steering). تستخدم stopMotors() القيمة RELEASE التي تقطع الطاقة عن المحركات. والسرعة مثبتة على 255 في setup()؛ اخفض هذه القيمة لقيادة أبطأ وأكثر تحكمًا. ولأساسيات التحكم في المحركات عبر جسر H (H-bridge)، راجع شرح وحدة قيادة المحركات L298N.
شرح الكود: moveToPositionSmoothly()
void moveToPositionSmoothly(int servoIndex, int targetPwmValue) {
int currentPwmValue = currentServoPositions[servoIndex];
if (targetPwmValue > currentPwmValue) {
for (int pos = currentPwmValue; pos <= targetPwmValue; pos += stepSize) {
pwm.setPWM(servoChannels[servoIndex], 0, pos);
delay(stepDelay);
}
} else {
for (int pos = currentPwmValue; pos >= targetPwmValue; pos -= stepSize) {
pwm.setPWM(servoChannels[servoIndex], 0, pos);
delay(stepDelay);
}
}
currentServoPositions[servoIndex] = targetPwmValue;
}
القفز مباشرة إلى الموضع المستهدف يسبب حركة متقطعة وعنيفة وقد يتلف تروس السيرفو. لذلك تمرّ هذه الدالة على قيم PWM وحدةً تلو الأخرى، مع تأخير 10 ms بين كل خطوة والتي تليها، صعودًا أو نزولًا بحسب موقع الهدف. وفي النهاية تخزّن الموضع الجديد في currentServoPositions.
حلقة for حاجبة (Blocking)، أي أنها تشغل Arduino بالكامل حتى تنتهي. فالمسح الكامل من 150 إلى 600 يستغرق 450 خطوة × 10 ms = 4.5 s، ولا تعالج Arduino خلال هذا الوقت أي أوامر جديدة، ولا حتى S (إيقاف المحركات). تنتظر الأوامر في المخزن المؤقت التسلسلي (Serial Buffer)، وهو ذاكرة صغيرة للأحرف الواردة. فإذا كانت المركبة تسير وأنت تحرّك أحد أشرطة التمرير (Slider)، فستواصل السير حتى يصل السيرفو إلى موضعه. أما النسخة غير الحاجبة (Non-blocking) فتحرّك كل سيرفو خطوة واحدة في كل دورة من loop()، وتضبط توقيت الخطوات باستخدام millis() (عدد الميلي ثانية منذ بدء تشغيل Arduino).
شرح الكود: دوال الوضعيات
void saveCurrentPose() {
if (configCount < maxConfigurations) {
for (int i = 0; i < numServos; i++) {
savedConfigurations[configCount][i] = currentServoPositions[i];
}
configCount++;
}
}
void startPlayingPoses() {
if (configCount > 0) {
stopMotors();
isPlaying = true;
stopPlaying = false;
currentPoseIndex = 0;
}
}
void playNextPose() {
if (currentPoseIndex < configCount) {
for (int i = 0; i < numServos; i++) {
moveToPositionSmoothly(i, savedConfigurations[currentPoseIndex][i]);
}
delay(1000);
currentPoseIndex++;
} else {
isPlaying = false;
}
}
void playPosesInLoop() {
for (int i = 0; i < configCount; i++) {
if (stopPlaying) break;
for (int j = 0; j < numServos; j++) {
moveToPositionSmoothly(j, savedConfigurations[i][j]);
}
delay(1000);
}
}
void resetPoses() {
configCount = 0;
isPlaying = false;
loopPlayback = false;
currentPoseIndex = 0;
}
تنسخ saveCurrentPose() قيم PWM الست الحالية إلى المصفوفة savedConfigurations وتزيد configCount بمقدار واحد، حتى 10 وضعيات كحد أقصى. أما startPlayingPoses() فتوقف المحركات وتفعّل isPlaying وتعيد currentPoseIndex إلى 0.
تحرّك playNextPose() محركات السيرفو الستة إلى القيم المخزنة لوضعية واحدة، وتنتظر ثانية واحدة، ثم تنتقل إلى الوضعية التالية. وبعد آخر وضعية ينتهي التشغيل تلقائيًا. أما playPosesInLoop() فتمر على كل الوضعيات المحفوظة في استدعاء واحد، وتعيد loop() استدعاءها ما دامت قيمة isPlaying هي true. وتمسح resetPoses() الوضعيات وكل متغيرات حالة التشغيل.
في وضع التكرار، لا يسري أمر الإيقاف إلا بعد أن تُنهي الذراع سلسلة الوضعيات المحفوظة كاملة. فأثناء تنفيذ playPosesInLoop() لا تقرأ Arduino أي أوامر جديدة، لذا لا يرصد فحص stopPlaying داخل الحلقة أمر الإيقاف أبدًا. ومع عدة وضعيات قد يستغرق ذلك وقتًا طويلًا. أما عند تعطيل وضع التكرار، فيسري الإيقاف بين وضعيتين.
الخطوة 6: تطبيق OmObiArmControl
OmObiArmControl هو تطبيق Android الخاص بالروبوت، وقد بُني باستخدام MIT App Inventor. ويوجد ملف المشروع .aia (صيغة المشروع التي تستوردها إلى MIT App Inventor) في مستودع GitHub.

استخدام التطبيق
- الاتصال: أقرِن وحدة HC-05 بهاتفك من إعدادات البلوتوث قبل فتح التطبيق (رمز PIN هو 1234، أو 0000 في بعض الوحدات). وفي التطبيق، اضغط على “Select Device” واختر HC-05 من قائمة الأجهزة المقترنة. يتحول لون النص المجاور للزر إلى الأخضر وتظهر عبارة “Connected”. أما اللون الأحمر مع “Not Connected” فيعني أن الاتصال فشل.
- حرّك الذراع: ستة أشرطة تمرير، واحد لكل سيرفو من القاعدة حتى القابض. يرسل كل شريط قيمته إلى السيرفو المطابق عند تحريكه.
- القيادة: تحرّك أزرار الأسهم الأربعة المركبة إلى الأمام والخلف واليسار واليمين. ولا تتحرك المركبة إلا ما دمت تضغط على الزر. وعند رفع إصبعك يرسل التطبيق S فتتوقف المحركات، لذا لا يوجد زر إيقاف منفصل للقيادة.
- الوضعيات: يحفظ زر “Save” موضع الذراع الحالي كوضعية، حتى 10 وضعيات. ويعيد “Play” تشغيلها بالترتيب. ويكرر مفتاح LoopPlayback السلسلة حتى توقفها. وينهي “Stop” التشغيل؛ وفي وضع التكرار تُكمل الذراع أولًا الدورة الحالية عبر كل الوضعيات. ويمسح “Reset” كل الوضعيات المحفوظة. وفي الكود، تُخزَّن الوضعيات المحفوظة في المصفوفة savedConfigurations.
- الحالة: يعدّ PoseCount، الموجود بجوار مفتاح LoopPlayback، عدد مرات ضغطك على Save. ولا تحتفظ Arduino إلا بأول 10 وضعيات، لذا قد يكون العدد المعروض أكبر من عدد الوضعيات المخزنة فعلًا.
اتصال البلوتوث
استخدم مكوّن ListPicker. في حدث BeforePicking الخاص به، املأ القائمة بأجهزة البلوتوث المقترنة. وفي AfterPicking، اتصل بالجهاز المختار واضبط نص الحالة على “Connected” باللون الأخضر، أو على “Not Connected” باللون الأحمر إذا فشل الاتصال. وتجد شرح كتل البلوتوث (Blocks) بالتفصيل في شرح وحدة البلوتوث HC-05.
أشرطة تمرير الذراع
أضف ستة مكوّنات Slider، واحدًا لكل سيرفو، مع MinValue بقيمة 0 وMaxValue بقيمة 180. وفي حدث PositionChanged لكل شريط تمرير، أرسل نص الأمر عبر البلوتوث: يرسل Slider 1 الأمر 1,value\n، ويرسل Slider 2 الأمر 2,value\n، وهكذا حتى Slider 6.

أزرار القيادة
أضف أربعة مكوّنات Button للحركة إلى الأمام والخلف واليسار واليمين. وفي حدث TouchDown لكل زر (عند وضع الإصبع على الزر)، أرسل F\n أو B\n أو L\n أو R\n. وفي حدث TouchUp (عند رفع الإصبع)، أرسل S\n. وبهذه الطريقة يعمل التطبيق الموجود في المستودع.

أزرار الوضعيات ومفتاح التكرار (Loop)
أضف أزرارًا لـ Save (v\n) وPlay (l\n) وReset (r\n) وStop (s\n)، إضافة إلى مكوّن Switch لوضع التكرار يرسل o\n عند تشغيله وf\n عند إيقافه.
نصوص الحالة
أضف مكوّنات Label لعرض حالة الاتصال وعدد الوضعيات. وحدّثها عندما يتصل التطبيق أو يحفظ وضعية أو يعيد الضبط.

الخطوة 7: الاختبار والمعايرة
ضع المركبة على سطح مستوٍ، وشغّلها والذراع في وضعية مرتخية، واختبر كل نظام فرعي على حدة قبل تشغيل كل شيء معًا. عند التشغيل تقفز الذراع إلى وضعيتها المركزية بأقصى سرعة للسيرفو، لذا أبعِد يديك عنها.
7.1 فحص الاتصال
أقرِن الوحدة واتصل بها كما هو موضح في الخطوة 6. في إصدار ZS-040 الشائع من وحدة HC-05، يومض LED بسرعة أثناء انتظار الاتصال، ثم يتحول إلى وميض مزدوج كل ثانيتين بعد الاتصال. وإذا فشل الاتصال، فتأكد من وصول الطاقة إلى HC-05 ومن تفعيل البلوتوث في هاتفك.
7.2 قيادة المركبة
اختبر كل اتجاه على حدة: الأمام، والخلف، واليسار، واليمين. إذا دار أحد المحركات في الاتجاه الخاطئ، فبدّل سلكيه عند مخرج درع المحركات؛ ولا حاجة إلى تغيير الكود. وتأكد من توقف المحركات الأربعة كلها عند تحرير الزر.
7.3 معايرة السيرفو
حرّك كل شريط تمرير ببطء، واحدًا تلو الآخر، وتأكد من أن السيرفو المطابق يتحرك. إذا لم يستجب أحد محركات السيرفو، فافحص توصيلاته وتأكد من أن قناته تطابق الكود (0، 4، 8، 9، 12، 13). وإذا كان يرتجف أو يطنّ دون أن يتحرك، فافحص جهد 6 V القادم من LM2596. وإذا وصل السيرفو إلى حدّه الميكانيكي قبل أن يبلغ شريط التمرير 0 أو 180، فقيّد مداه. يطبّق سطر map() في processServoCommand() المدى نفسه على محركات السيرفو الستة كلها. ولتقييد سيرفو واحد فقط، اضبط MinValue وMaxValue لشريط التمرير الخاص به في App Inventor (مثلًا 20 و160)، أو خصّص لكل سيرفو قيمة PWM دنيا وقصوى خاصة به في الكود.
7.4 حفظ الوضعيات وتشغيلها
حرّك الذراع إلى موضع ما واضغط Save. ثم حرّكها إلى موضع ثانٍ واضغط Save مرة أخرى. اضغط Play: يجب أن تعيد الذراع تنفيذ الوضعيتين بالترتيب. فعّل وضع التكرار وابدأ التشغيل: تتنقل الذراع بين الوضعيات باستمرار حتى تضغط Stop، ويسري الإيقاف بعد انتهاء الدورة الحالية. وأخيرًا اضغط Reset ثم Play: لن يحدث شيء، لأنه لا توجد أي وضعية محفوظة.
7.5 المركبة والذراع معًا
قُد المركبة أثناء تحريك أشرطة تمرير الذراع، وراقب جهد البطارية. إذا أصبح الروبوت بطيئًا، أو ارتجفت محركات السيرفو، أو أعادت Arduino تشغيل نفسها، فالسبب المرجح هو مزود الطاقة (راجع قسم استكشاف الأخطاء وإصلاحها). وفي هذه الحالة استخدم بطارية بسعة أعلى أو اخفض سرعة المحركات في setup().
استكشاف الأخطاء وإصلاحها
يقترن HC-05 لكن الروبوت لا يستجيب: تأكد من توصيل TXD بـ RX0 (الطرف 0) وRXD بـ TX0 (الطرف 1)، ومن أن برنامج Arduino يستخدم 9600 baud. ولرؤية مخرجات التصحيح في الشاشة التسلسلية (Serial Monitor)، افصل HC-05 (أو انقله إلى Serial1، راجع 4.5) ثم افتح الشاشة التسلسلية. يجب أن يطبع البرنامج “Bluetooth Robot Controller ready. Waiting for commands…”.
فشل رفع برنامج Arduino: لا تزال وحدة HC-05 موصولة بالطرفين 0 و1. افصل على الأقل سلكي TXD وRXD الخاصين بها (راجع 4.5).
محركات السيرفو ترتجف أو تطنّ أو تتوقف عن الحركة: قِس جهد خرج LM2596؛ يجب أن يكون 6 V. وحدة LM2596 مصنّفة لتيار ذروة 3 A ونحو 2 A بشكل مستمر دون مشتت حراري. ومحرك MG996R واحد متوقف تحت الحمل (Stall) يسحب ما يصل إلى نحو 2.5 A عند 6 V. فإذا انخفض الجهد عندما ترتفع الذراع، فاستخدم وحدة خفض جهد (Step-down) مصنّفة لتيار 5 A على الأقل. وقد ينتج الارتجاف أيضًا عن نقص الطاقة عندما تعمل عدة محركات سيرفو في الوقت نفسه. وافحص أيضًا مقابس السيرفو على PCA9685.
المركبة تتحرك لكن الذراع لا تستجيب: افحص SDA وSCL على الطرفين 20 و21، وجهد 6 V عند طرف V+ في PCA9685. يجب أن يكون عنوان I2C هو 0x40؛ شغّل برنامج Arduino لفحص I2C (برنامج اختبار قصير يسرد كل جهاز على ناقل I2C) للتأكد من أن Arduino تتعرّف على PCA9685.
الذراع تتحرك لكن المركبة لا تستجيب: افحص أسلاك المحركات عند M1 إلى M4، وتأكد من أن درع المحركات مثبّت بالكامل على أطراف التوصيل في Arduino، وافحص جهد البطارية عند طرف EXT_PWR.
الوضعيات لا تُحفظ أو لا تُشغَّل: تأكد من أن التطبيق يرسل v للحفظ وl للتشغيل (حرف L صغير). تعرض الشاشة التسلسلية (Serial Monitor) عدد الوضعيات بعد كل عملية حفظ (راجع البند الأول لمعرفة طريقة توصيلها). وبعد 10 وضعيات محفوظة يتجاهل البرنامج أي عملية حفظ إضافية.
التشغيل المتكرر لا يتوقف: يجب أن يرسل التطبيق الحرف الصغير s. وفي وضع التكرار لا يسري الإيقاف إلا بعد انتهاء الدورة الحالية عبر كل الوضعيات المحفوظة (راجع شرح دوال الوضعيات).
انخفاض الجهد والسلوك غير المنتظم تحت الحمل: لا تستطيع البطارية أو وحدة LM2596 توفير تيار كافٍ للمحركات ومحركات السيرفو في الوقت نفسه. اشحن بطارية LiPo بالكامل. وإذا لم يُجدِ ذلك، فغذِّ المحركات ومحركات السيرفو من بطاريتين منفصلتين.
المحركات تدور في الاتجاه الخاطئ: بدّل سلكي ذلك المحرك عند مخرج الدرع. ولا حاجة إلى تغيير الكود.
الأسئلة الشائعة
يتسع البرنامج لذاكرة Uno: فعند ترجمته لهذه اللوحة يستخدم نحو 1.1 KB من أصل 2 KB من ذاكرة SRAM للمتغيرات العامة. وعلى Uno، وصّل SDA وSCL الخاصين بـ PCA9685 بالطرفين A4 وA5. وتبقى وحدة HC-05 على الطرفين 0 و1، لأن Uno لا تحتوي على Serial1. أما Mega فتحتوي على 8 KB من SRAM وأطراف حرة أكثر، وتتيح لك نقل HC-05 إلى Serial1 (راجع 4.5).
يشغل Adafruit Motor Shield عددًا من أطراف PWM في Arduino. أما PCA9685 فلا تحتاج إلا إلى طرفي I2C (SDA وSCL)، وتوفّر 16 قناة PWM. ويتلقى خط الطاقة V+ فيها جهد 6 V المنفصل القادم من LM2596، فلا يمر تيار السيرفو عبر Arduino أبدًا.
تستخدم وحدة HC-05 تقنية Bluetooth Classic مع ملف المنفذ التسلسلي Serial Port Profile (SPP: بلوتوث يعمل كأنه كابل تسلسلي)، وهو ما لا تتيحه أجهزة iPhone لتطبيقات الطرف الثالث. ولنظام iOS، استبدل HC-05 بوحدة BLE (Bluetooth Low Energy)، مثل HM-10 أو ESP32، وعدّل برنامج Arduino والتطبيق.
المصادر والمشاريع التالية
ملفات المشروع
- مستودع GitHub: برنامج Arduino وملف مشروع MIT App Inventor (.aia).
- متجر OmArTronics: ملفات الطباعة وملفات CAD (STL للطباعة، وSTP لفتح النموذج في برنامج CAD) للهيكل والصفيحة العلوية ووصلات الذراع والقابض. أما الكود ومخطط التوصيل وقائمة القطع فموجودة في هذه الصفحة.
شروحات ذات صلة
- OmObi: بناء سيارة روبوتية يُتحكَّم بها عبر البلوتوث: المركبة التي بُني عليها OmObiArm.
- OmArm: ذراع روبوتية بست درجات حرية (6-DOF) تصنعها بنفسك مع تحكم عبر البلوتوث: الذراع التي يحملها OmObiArm. يعني DOF درجات الحرية (مفاصل مستقلة)؛ والاسم يحتسب محركات السيرفو الستة كلها، بما فيها سيرفو القابض.
- ذراع روبوتية بست درجات حرية (6-DOF) اصنعها بنفسك: الطباعة ثلاثية الأبعاد (3D) والتوصيل والبرمجة: النسخة الأقدم من الذراع، التي يُتحكَّم بها بمقاومات متغيرة (Potentiometer).
- شرح كامل لوحدة البلوتوث HC-05/HC-06 مع Arduino: التوصيلات والإقران وكتل البلوتوث في App Inventor.
- دليل التحكم في محركات السيرفو مع Arduino: SG90 وPWM وPCA9685: أساسيات محركات السيرفو وشريحة PCA9685.
- أساسيات برمجة Arduino: دليل شامل للمبتدئين: ابدأ من هنا إن لم يسبق لك برمجة Arduino.
- سيارة روبوتية ذاتية القيادة متقدمة باستخدام Arduino Mega ومستشعرات الموجات فوق الصوتية: درع المحركات نفسه ولوحة Arduino Mega نفسها، مع تفادي العوائق.
- وحدة قيادة المحركات L298N مع Arduino: دليل التحكم بمحركات التيار المستمر: التحكم في المحركات عبر جسر H (H-bridge) باستخدام لوحة قيادة منفصلة.
الترقيات التالية
- تفادي العوائق: أضف أربعة مستشعرات موجات فوق صوتية من نوع HC-SR04 لتتمكن المركبة من التنقل ذاتيًا بينما تعمل الذراع. راجع شرح مستشعر الموجات فوق الصوتية HC-SR04 و مشروع رادار Arduino.
- تخزين الوضعيات في EEPROM: خزّن الوضعيات في ذاكرة EEPROM (ذاكرة تحتفظ بمحتواها دون طاقة)، كي لا تضيع عند فصل الطاقة.
- التحكم في السرعة: استبدل السرعة الثابتة 255 بشريط تمرير في التطبيق، ومرّر قيمته إلى setSpeed().
- الكاميرا والرؤية الحاسوبية: أضف Raspberry Pi أو ESP32-CAM لاكتشاف الأجسام، فلوحة Mega وحدها لا تستطيع معالجة الصور.
- واي فاي (Wi-Fi): استبدل HC-05 بلوحة ESP32 للتحكم من متصفح الويب أو من نظام أتمتة منزلية.
مشروع الذراع التالي، OmArm Zero، يخطو هذه الخطوة وينتقل من Arduino إلى ESP32. وتتحكم فيه من المتصفح عبر واي فاي (Wi-Fi)، وتضيف الأجزاء اللاحقة ROS 2 وMoveIt 2 وكاميرا.
مقارنة بين كل أذرعي الروبوتية: كيف تبني ذراعًا روبوتية بست محاور في المنزل يشرح ماذا يعني ست محاور، ويضع التصاميم الأربعة في جدول واحد، ويخبرك أيها تبني أولًا.

مرحبًا يا فريق OmArTronics plus،
أرجو أن تكونوا بخير!
معكم Ashley من JLCMC، وهي علامة تجارية فرعية تابعة لـ JLCPCB. نعمل على بناء خدمة عالمية متكاملة توفّر كل شيء في مكان واحد، ولدينا آلاف عديدة من المكونات الميكانيكية التي تناسب مختلف سيناريوهات الاستخدام والاحتياجات.
لقد أعجبنا نهجك العملي ومشاريعك الإبداعية التي تصنعها بنفسك (DIY)، ولا سيما عملك على الأذرع الروبوتية. ويتوافق تركيزك على الحلول العملية والمبتكرة تمامًا مع مهمتنا في JLCMC المتمثلة في توفير مكونات ميكانيكية عالية الجودة. ونعتقد أن التعاون سيعود بالفائدة على الطرفين، إذ يتطابق اهتمام جمهورك بالروبوتات والأتمتة إلى حد كبير مع الحلول التي نقدمها. وستتيح لنا الشراكة معك تقديم قطعنا الدقيقة إلى مجتمع متمرس تقنيًا يتطلع إلى تقنيات ذكية وفعالة.
يسعدنا أن نزوّدك بمنتجاتنا لاستخدامها. وإذا كنت مهتمًا، أو احتجت إلى تحديث مكوناتك، أو كانت لديك أي أسئلة أخرى، فلا تتردد في التواصل معنا!
شكرًا على وقتك، وأتطلع إلى سماع ردك!
مع أطيب التحيات،
Ashely
you are very creative and brave
i from Egypt and i love your app because i will Build a robot to my school and its our my frist robot
thank you
Odai
Thank you for your kind words, Odai
I wish you lots of success building your first robot for school!