جدول المحتويات:

روبوت ذاتي التوازن من ماجيكبيت: 6 خطوات
روبوت ذاتي التوازن من ماجيكبيت: 6 خطوات

فيديو: روبوت ذاتي التوازن من ماجيكبيت: 6 خطوات

فيديو: روبوت ذاتي التوازن من ماجيكبيت: 6 خطوات
فيديو: 10 ساعات من خافيير ميلي في المقابلات والمناقشات. 2024, يوليو
Anonim

يوضح هذا البرنامج التعليمي كيفية إنشاء روبوت ذاتي التوازن باستخدام لوحة تطوير Magicbit. نحن نستخدم Magicbit كلوحة تطوير في هذا المشروع الذي يعتمد على ESP32. لذلك يمكن استخدام أي لوحة تطوير ESP32 في هذا المشروع.

اللوازم:

  • ماجيكبيت
  • سائق محرك مزدوج H- جسر L298
  • منظم خطي (7805)
  • بطارية ليبو 7.4 فولت 700 مللي أمبير
  • وحدة القياس بالقصور الذاتي (IMU) (6 درجات من الحرية)
  • محركات تروس 3V-6V DC

الخطوة 1: القصة

قصة
قصة
قصة
قصة

مرحبًا يا رفاق ، سنتعرف اليوم في هذا البرنامج التعليمي على أشياء معقدة بعض الشيء. هذا يتعلق بموازنة الروبوت الذاتي باستخدام Magicbit مع Arduino IDE. لذا لنبدأ.

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

الخطوة الثانية: النظرية والمنهجية

النظرية والمنهجية
النظرية والمنهجية

لموازنة الروبوت ، نحصل أولاً على بيانات من بعض أجهزة الاستشعار لقياس زاوية الروبوت إلى المستوى الرأسي. لهذا الغرض استخدمنا MPU6050. بعد الحصول على البيانات من المستشعر نحسب الميل إلى المستوى العمودي. إذا كان الروبوت في وضع مستقيم ومتوازن ، فإن زاوية الميل هي صفر. إذا لم يكن الأمر كذلك ، فإن زاوية الإمالة تكون قيمة موجبة أو سالبة. إذا كان الروبوت مائلاً إلى الجانب الأمامي ، فيجب أن يتحرك الروبوت إلى الاتجاه الأمامي. أيضًا إذا كان الروبوت يميل إلى الجانب العكسي ، فيجب أن يتحرك الروبوت إلى الاتجاه المعاكس. إذا كانت زاوية الميل هذه عالية ، فيجب أن تكون سرعة الاستجابة عالية. على العكس من ذلك ، تكون زاوية الميل منخفضة ثم سرعة التفاعل يجب أن تكون منخفضة. للتحكم في هذه العملية ، استخدمنا نظرية محددة تسمى PID. PID هو نظام تحكم يستخدم للتحكم في العديد من العمليات. PID تعني 3 عمليات.

  • ع- نسبي
  • أنا- متكامل
  • د- المشتق

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

الأول هو مضاعفة الخطأ من الكسب الرقمي. عادة ما يسمى هذا الكسب Kp

P = خطأ * Kp

الثاني هو إنشاء تكامل الخطأ في المجال الزمني وضربه من بعض الكسب. هذا المكسب يسمى Ki

أنا = متكامل (خطأ) * كي

الثالث هو مشتق من الخطأ في المجال الزمني وضربه في قدر من الربح. هذا المكسب يسمى Kd

D = (d (error) / dt) * kd

بعد إضافة العمليات أعلاه نحصل على الناتج النهائي

الإخراج = P + I + D.

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

www.arrow.com/ar/research-and-events/articles/pid-controller-basics-and-tutorial-pid-implementation-in-arduino

يكون خرج وظيفة PID محددًا لنطاق 0-255 (دقة 8 بت PWM) وسيغذي ذلك المحركات كإشارة PWM.

الخطوة 3: إعداد الجهاز

إعداد الأجهزة
إعداد الأجهزة

الآن هذا جزء من إعداد الأجهزة. يعتمد تصميم الروبوت عليك. عندما تصمم جسم الإنسان الآلي ، عليك أن تفكر في تناظره حول المحور الرأسي الذي يقع في محور المحرك. حزمة البطارية الموجودة أدناه. لذلك فإن الروبوت سهل التوازن. في تصميمنا نقوم بإصلاح لوحة Magicbit عموديًا على الجسم. استخدمنا اثنين من محركات التروس بجهد 12 فولت. ولكن يمكنك استخدام أي نوع من محركات التروس. هذا يعتمد على أبعاد الروبوت الخاص بك.

عندما نناقش الدائرة ، يتم تشغيلها بواسطة بطارية ليبو 7.4 فولت. تستخدم Magicbit 5V للتشغيل. لذلك استخدمنا منظم 7805 لتنظيم جهد البطارية إلى 5 فولت. في الإصدارات الأحدث من Magicbit لا يحتاج هذا المنظم. لأنه يعمل حتى 12 فولت. نقوم بتوريد 7.4 فولت مباشرة لسائق المحرك.

قم بتوصيل جميع المكونات وفقًا للرسم البياني أدناه.

الخطوة 4: إعداد البرنامج

في الكود استخدمنا مكتبة PID لحساب إخراج PID.

انتقل إلى الرابط التالي لتنزيله.

www.arduinolibraries.info/libraries/pid

قم بتنزيل أحدث إصدار منه.

للحصول على قراءات مستشعر أفضل ، استخدمنا مكتبة DMP. DMP تعني عملية الحركة الرقمية. هذه ميزة مضمنة في MPU6050. تحتوي هذه الرقاقة على وحدة معالجة حركة مدمجة. لذلك يتطلب الأمر القراءة والتحليل. بعد أن يولد مخرجات دقيقة صامتة للميكروكونترولر (في هذه الحالة Magicbit (ESP32)). ولكن هناك الكثير من الأعمال في جانب الميكروكونترولر لأخذ تلك القراءات وحساب الزاوية. لذلك ببساطة استخدمنا مكتبة MPU6050 DMP. قم بتنزيله عن طريق goint إلى الرابط التالي.

github.com/ElectronicCats/mpu6050

لتثبيت المكتبات ، في قائمة Arduino ، انتقل إلى أدوات-> تضمين مكتبة-> مكتبة add.zip وحدد ملف المكتبة الذي قمت بتنزيله.

في الكود ، يجب عليك تغيير زاوية الضبط بشكل صحيح. تختلف قيم ثابت PID من إنسان آلي إلى إنسان آلي. لذا عند ضبط ذلك ، قم أولاً بتعيين قيم Ki و Kd إلى الصفر ثم قم بزيادة Kp حتى تحصل على سرعة رد فعل أفضل. المزيد من Kp يسبب المزيد من التجاوزات. ثم قم بزيادة قيمة Kd. قم بزيادته دائمًا بكمية قليلة جدًا. هذه القيمة منخفضة بشكل عام عن القيم الأخرى. الآن قم بزيادة Ki حتى تحصل على استقرار جيد جدًا.

حدد منفذ COM الصحيح واكتب اللوحة. قم بتحميل الكود. الآن يمكنك اللعب مع الروبوت الخاص بك.

الخطوة 5: المخططات

المخططات
المخططات

الخطوة 6: الكود

#يشمل

#include "I2Cdev.h" #include "MPU6050_6Axis_MotionApps20.h" #if I2CDEV_IMPLEMENTATION == I2CDEV_ARDUINO_WIRE #include "Wire.h" #endif MPU6050 mpu؛ منطقي dmpReady = خطأ ؛ // تعيين صحيح إذا كان DMP init ناجحًا uint8_t mpuIntStatus ؛ // يحمل بايت حالة المقاطعة الفعلية من MPU uint8_t devStatus ؛ // حالة الإرجاع بعد كل عملية جهاز (0 = نجاح ،! 0 = خطأ) uint16_t packetSize ؛ // حجم حزمة DMP المتوقع (الافتراضي 42 بايت) uint16_t fifoCount ؛ // عدد البايتات الموجودة حاليًا في FIFO uint8_t fifoBuffer [64] ؛ // FIFO المخزن المؤقت Quaternion q ؛ // [w، x، y، z] حاوية رباعية الجاذبية VectorFloat؛ // [x، y، z] عوامة ناقلات الجاذبية ypr [3]؛ // [الانعراج ، الملعب ، التدحرج] الانحراف / الحاوية / لفة الحاوية وناقل الجاذبية double originalSetpoint = 172.5 ؛ نقطة الضبط المزدوجة = originalSetpoint ؛ double MovingAngleOffset = 0.1 ؛ إدخال مزدوج ، خرج ؛ int moveState = 0 ؛ double Kp = 23؛ // set P first double Kd = 0.8؛ // هذه القيمة بشكل عام صغيرة مزدوجة Ki = 300 ؛ // يجب أن تكون هذه القيمة عالية لتحسين الاستقرار PID pid (& input، & output، & setpoint، Kp، Ki، Kd ، DIRECT) ؛ // pid تهيئة int motL1 = 26 ؛ // 4 دبابيس لمحرك المحرك int motL2 = 2 ؛ int motR1 = 27 ؛ int motR2 = 4 ؛ متغير منطقي mpuInterrupt = خطأ ؛ // يشير إلى ما إذا كان طرف MPU المقاطعة قد أصبح فارغًا dmpDataReady () {mpuInterrupt = true؛ } إعداد باطل () {ledcSetup (0، 20000، 8)؛ // pwm setup ledcSetup (1، 20000، 8) ؛ إعداد ledc (2 ، 20000 ، 8) ؛ إعداد ledc (3 ، 20000 ، 8) ؛ ledcAttachPin (motL1 ، 0) ؛ // pinmode للمحركات ledcAttachPin (motL2 ، 1) ؛ ledcAttachPin (motR1 ، 2) ؛ ledcAttachPin (motR2 ، 3) ؛ // انضم إلى ناقل I2C (مكتبة I2Cdev لا تفعل ذلك تلقائيًا) #if I2CDEV_IMPLEMENTATION == I2CDEV_ARDUINO_WIRE Wire.begin () ؛ Wire.setClock (400000) ؛ // ساعة 400 كيلو هرتز I2C. علق على هذا السطر إذا واجهت صعوبات في الترجمة #elif I2CDEV_IMPLEMENTATION == I2CDEV_BUILTIN_FASTWIRE Fastwire:: setup (400، true)؛ #endif Serial.println (F ("تهيئة أجهزة I2C …"))؛ pinMode (14 ، الإدخال) ؛ // تهيئة الاتصال التسلسلي // (تم اختيار 115200 لأنه مطلوب لإخراج Teapot Demo ، لكن الأمر متروك لك حقًا اعتمادًا على مشروعك) Serial.begin (9600) ؛ بينما (! المسلسل) ؛ // انتظر تعداد ليوناردو ، ويستمر الآخرون على الفور // تهيئة الجهاز Serial.println (F ("تهيئة أجهزة I2C …")) ؛ mpu.initialize () ، // تحقق من الاتصال Serial.println (F ("اختبار اتصالات الجهاز …")) ؛ Serial.println (mpu.testConnection ()؟ F ("اتصال MPU6050 ناجح"): F ("فشل اتصال MPU6050")) ؛ // تحميل وتكوين DMP Serial.println (F ("تهيئة DMP …")) ؛ devStatus = mpu.dmpInitialize () ، // قم بتوفير تعويضات الدوران الخاصة بك هنا ، والتي تم تحجيمها من أجل الحد الأدنى من الحساسية mpu.setXGyroOffset (220) ؛ mpu.setYGyroOffset (76) ، mpu.setZGyroOffset (-85) ، mpu.setZAccelOffset (1788) ؛ // 1688 افتراضيًا للمصنع لشريحة الاختبار الخاصة بي // تأكد من أنها تعمل (تُرجع 0 إذا كان الأمر كذلك) إذا (devStatus == 0) {// قم بتشغيل DMP ، والآن أصبح Serial.println جاهزًا (F ("تمكين DMP … ")) ؛ mpu.setDMPEnabled (صحيح) ، // تمكين الكشف عن مقاطعة Arduino Serial.println (F ("تمكين اكتشاف المقاطعة (المقاطعة الخارجية لـ Arduino 0) …")) ؛ attachInterrupt (14، dmpDataReady، RISING) ؛ mpuIntStatus = mpu.getIntStatus () ، // قم بتعيين علامة DMP Ready الخاصة بنا حتى تعرف وظيفة الحلقة الرئيسية () أنه لا بأس من استخدامها Serial.println (F ("DMP جاهز! انتظار المقاطعة الأولى …")) ؛ dmpReady = صحيح ؛ // احصل على حجم حزمة DMP المتوقع لحزمة المقارنة اللاحقة = mpu.dmpGetFIFOPacketSize () ؛ // إعداد PID pid. SetMode (تلقائي) ؛ pid. SetSampleTime (10) ، pid. SetOutputLimits (-255، 255) ، } آخر {// ERROR! // 1 = فشل تحميل الذاكرة الأولية // 2 = فشلت تحديثات تكوين DMP // (إذا كان سيتم كسرها ، فعادة ما يكون الرمز 1) Serial.print (F ("فشل تهيئة DMP (الرمز")) ؛ Serial. طباعة (devStatus) ؛ Serial.println (F (")")) ؛ }} void loop () {// إذا فشلت البرمجة ، لا تحاول أن تفعل أي شيء إذا عادت (! dmpReady)؛ // انتظر مقاطعة MPU أو تتوفر حزمة (حزم) إضافية أثناء (! mpuInterrupt && fifoCount <packetSize) {pid. Compute ()؛ // تُستخدم هذه الفترة الزمنية لتحميل البيانات ، لذا يمكنك استخدامها في العمليات الحسابية الأخرى motorSpeed (انتاج)؛ } // إعادة تعيين إشارة المقاطعة والحصول على INT_STATUS بايت mpuInterrupt = false ؛ mpuIntStatus = mpu.getIntStatus () ، // الحصول على عدد FIFO الحالي fifoCount = mpu.getFIFOCount () ؛ // تحقق من التدفق الزائد (يجب ألا يحدث هذا أبدًا ما لم يكن كودنا غير فعال للغاية) إذا ((mpuIntStatus & 0x10) || fifoCount == 1024) {// إعادة تعيين حتى نتمكن من متابعة mpu.resetFIFO () بشكل نظيف ؛ Serial.println (F ("FIFO overflow!")) ؛ // خلاف ذلك ، تحقق من المقاطعة الجاهزة لبيانات DMP (يجب أن يحدث هذا بشكل متكرر)} وإلا إذا (mpuIntStatus & 0x02) {// انتظر طول البيانات المتاح الصحيح ، يجب أن يكون انتظارًا قصيرًا جدًا أثناء (حزمة fifoCount 1 متوفرة // (هذا يتيح لنا قراءة المزيد على الفور دون انتظار المقاطعة) fifoCount - = packetSize؛ mpu.dmpGetQuaternion (& q، fifoBuffer)؛ mpu.dmpGetGravity (& gravity، & q)؛ mpu.dmpGetYawPitchRoll (ypr، & q، & gravity)؛ #if LOG_INPUT Serial. طباعة ("ypr / t") ؛ Serial.print (ypr [0] * 180 / M_PI) ؛ // زوايا أويلر Serial.print ("\ t") ؛ Serial.print (ypr [1] * 180 / M_PI) ؛ Serial.print ("\ t")؛ Serial.println (ypr [2] * 180 / M_PI)؛ #endif input = ypr [1] * 180 / M_PI + 180؛}} void motorSpeed (int PWM) {float L1 ، L2 ، R1 ، R2 ؛ إذا (PWM> = 0) {// الاتجاه الأمامي L2 = 0 ؛ L1 = abs (PWM) ؛ R2 = 0 ؛ R1 = abs (PWM) ؛ إذا (L1> = 255) { L1 = R1 = 255 ؛}} آخر {// اتجاه خلفي L1 = 0 ؛ L2 = abs (PWM) ؛ R1 = 0 ؛ R2 = abs (PWM) ؛ إذا (L2> = 255) {L2 = R2 = 255 ؛ }} // محرك أقراص ledcWrite (0، L1)؛ ledcWrite (1، L2)؛ ledcWrite (2، R1 * 0.97)؛ // 0.97 تمثل حقيقة السرعة أو ، لأن المحرك الأيمن لديه سرعة عالية من المحرك الأيسر ، لذلك نقوم بتقليله حتى تتساوى سرعات المحرك مع ledcWrite (3 ، R2 * 0.97) ؛

}

موصى به: