مضى على الشبكة و يوم من العطاء.

[ مشروع ] عمل روبوت ال Line follower

أبو المعاليأبو المعالي is verified member.

.:: اداري سابق ::.
.:: اداري سابق ::.

السمعة:

792867124.gif


موضوعنا اليوم موضوع بسيط إن شاء الله و لن يكون شرح كامل بكل التفاصيل بل وضع قدمك على الطريق

بداية ال Line follower هي مسابقة فكرتها أن تقوم بعمل روبوت يتحرك وحده من دون تدخل بشري و الهدف أن ينهي مسار مرسوم بلون أسود بأسرع وقت و أن تحاول عدم الخروج من المسار
1748807170225.webp


فأنت بالمسابقة تعتمد على عدد من القطع و الأمور لعمل الروبوت و أهمها :

1748807370774.webp
1748807396001.webp
1748807413784.webp

  1. [B]الماتورات :[/B] ففي المسابقة العزم في الماتورات ليس مهم بل المهم هو السرعة, لذلك في حالتنا و بعد تجربة اخترنا هذا النوع من الماتورات من Temu التي كانت سرعتها 1360 و بالحقيقة كانت مناسبة جدا في وسط البيئة التي كنا نتنافس بها
  2. المتحكم : نحن كما قلنا فإن الهدف الإنهاء بأقرب وقت لذلك تحتاج لمتحكم سريع يمكنه أن يرسل و يستقبل بسرعة لذلك لم نتجه لل Arduino علما أن هناك الكثير من المتاسبقين الذين استخدموه و لكننا فضلنا أن لا نواجه مشاكل بسبب نوع المتحكم لذلك اتجهنا لل ESP32
  3. ال driver : قمنا بإعتماد ال driver التقليدي L298N, علما أنه يمكنك من باب الإحتياط إن كان الماتورين يسحبا أكثر مما يعطي ال driver الواحد أن تضع 2 drivers (لكل ماتور ال driver الخاص به)
    1748808006786.webp
  4. البطارية : بالواقع و بناء على الماتورات التي اخترناها و ال driver فنحتاج بطارية 12 فولت
    و لكن ملاحظة مهمة جدا : ال ESP32 تعمل على 3.3 فولت فقط و إن زادت عن هذا المقدار فستحترق و تتلف, لذلك لا تنسى أن تضع منظم جهد 3.3v

    يمكنك أن تقوم بعمل السيركت من خلال منظم الجهد و المكثفات أو أن تقوم بشراء هذه القطعة الجاهزة التي تحتوي على مكثفات من نوع SMD داخلها
  5. حساسات ال IR : بالطبع الأمر لا يمكن تحديده بنوع حساس معين, فروبوتنا و لعدم توافر الحساسات في السوق بسبب المسابقة و زيادة الطلب قمنا باللجوء لحساس يعتبر سيء نسبيا و هناك كان الكثير من الحساسات التي كانت أفضل منا و لكننا فزنا و الحمد لله و فضله
    فنقطة حساسات ال IR اختيارية لك حسب التوافر و الميزانية و لكن لا تنسى أن الإبداع في البرمجة أهم من قوة الحساسات لديك
    الحساس الذي استخدمناه : https://mikroelectron.com/product/5-channel-ir-sensor-tcrt5000-line-track-follower-module
    و هو الحساس الذي تم بنأ الكود عليه
  6. العجلات : ربما هذه النقطة ليست ذا فرق كبير جدا و لكن صديق لنا -الله يجزيه الخير- قام بتجهيز تصميم لها على برنامج solidwork للعجلات و حتى ل cover ليقوم بتثبيت الماتور في التصميم المصنوع من مادة ال الأكرليك ثم قصها بالليزر و قام أيضا بعمل قالب و شراء مادة من السيلكون السائل و صبها بالقالب كما هي و بعد حوالي 10 ساعات أو اقل بقليل تكون قد تحولت للحالة الصلبة و يمكنك إستخدامها ك rubber للعجلات بدلا من شراء rubber جاهز بتكلفة كبيرة نسبيا
    ملاحظة مهمة : كل التصاميم ستجدها إن شاء الله بالمرفقات
و أظن بأن هذه أهم القطع الرئيسية و لكن لا تنسى أن تضع fuse للحماية في حالة ال short -لا سمح الله- و لكي لا تتلف لديك القطع على ما أتذكر فيوز حوالي 1A مناسب (لست متأكد قم بالرجوع لل datasheet الخاصة بال ESP32 أوالمتحكم الخاص بك و انظر ما أعلى قيمه يسحبها في حالتك و زد عليها حوالي نصف أمبير)
و لقد ركزت على نقطة "في حالتك" لأن ال ESP32 ربما تصل ل1 أو 2 أمبير إن كنت تستخدم ال wifi و ال bluetooth و لكننا لن نستخدمها في مشروعنا لذلك لن نهتم بها و سنركز على السحب في الحالات العادية فق

قبل توصيل القطع تأكد دائما من التوصيل الكهربائي و الفولتيات من خلال ال Multimeter

هذه أهم الأمور الخاصة بالروبوت و تصميمه و لكن هناك ملاحظة على الروبوت بالصورة

يفضل أن تقوم بتقصير أكثر لأن أحد أسباب المشاكل في المسابقة كانت أنه طويل نسبيا و بنفس الوقت لا تجعله قصير كثيرا


الجانب البرمجي
في البرمجة اعتمدنا تقنية أو خوارزمية تسمى ال PID و بالطبع هي موضوع كبير و إن شاء الله أن نتمكن من شرحه مستقبلا
و لكن بعد بناء الكود الأساسي يجب أن تقوم بالتجربة و معرفة الأخطاء و تعديل سرعة الماتورات أو قيم ال P و I و D حسب النتائج التي تراها

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

هذه بعض المصادر لفهم ما هي ال PID :



و هذا هو الكود النهائي الذي استخدمناه لروبوتنا (لا تستخدمه نفسه و لكن تعلم منه و طور و حسن عليه و ناقشني بتلك التحسينات لنستفيد جميعا)
C++:
#define IR_L_2 32
  #define IR_L_1 33
  #define IR_O_1 25
  #define IR_R_1 26
  #define IR_R_2 27

  const int sensorPins[5] = {IR_L_2 , IR_L_1 , IR_O_1 , IR_R_1 , IR_R_2};

  #define Push_Button 22

  /*************** تعريف دبابيس التحكم بالمحركات ***************/
  // ------ محرك الجانب الأيمن ------
  #define EN_A 2  // للمحرك الأيمن (التحكم بالسرعة) PWM دبوس تمكين
  #define IN_1 5  // دبوس التحكم بالاتجاه 3 للمحرك الأيمن
  #define IN_2 4  // دبوس التحكم بالاتجاه 4 للمحرك الأيمن

  // ------ محرك الجانب الأيسر ------
#define EN_B 15  // للمحرك الأيسر (التحكم بالسرعة) PWM دبوس تمكين
#define IN_3 18  // دبوس التحكم بالاتجاه 1 للمحرك الأيسر
#define IN_4 19  // دبوس التحكم بالاتجاه 2 للمحرك الأيسر

/*************** ESP32 المتقدمة لـ PWM إعدادات ***************/
#define PWM_Channel_R 0  // رقم 0 للمحرك الأيمن PWM قناة
#define PWM_Channel_L 1  // رقم 1 للمحرك الأيسر PWM قناة
#define Frequency 6000   // (5 كيلوهرتز) PWM تردد إشارة
#define Resolution 8     // (تساوي 8 بت = قيم من 0 إلى 255) PWM دقة

double error = 0;
double lastError = 0;
double totalError = 0;
double integral = 0;
double output = 0;

double weightedSum = 0;
int sum = 0;

int R_Speed = 0;
int L_Speed = 0;

int total_Sensor = 10;
uint8_t i = 0;  // يمكن أن يأخذ قيمًا موجبة من 0 إلى 255 فقط (uint8_t) تـعـريـف مُـتـغـيـر من نـوع
bool Run = 0;
unsigned long Print_1 = 0;
int Values[8];

int sensorValues[5];


// ━━━━━━━ إعدادات PID ━━━━━━━
double Kp = 65;      // معامل التناسب
double Ki = 0.0002;  // معامل التكامل
double Kd = 93;       // معامل التفاضل

int baseSpeed = 160;  // السرعة الأساسية (0-255)
int maxSpeed = 193;   // أقصى سرعة

// أوزان الحساسات
int sensorWeights[5] = { -4, -2, 0, 2, 4 };

int skip = 0;

// Forward Declarations
void Stop_motors();
void calculateError();
void PID();
void Move_motors();
void Print();
void readSensors();

void setup() {
  Serial.begin(115200);  // تهيئة الاتصال التسلسلي لعرض النتائج (سرعة 115200 باود)

  for (int i = 0; i < 5; i++) {
    pinMode(sensorPins[i], INPUT);
  }

  pinMode(Push_Button, INPUT_PULLUP);

  // ------ تهيئة دبابيس التحكم بالاتجاه كمخرجات ------
  pinMode(IN_3, OUTPUT);  // تهيئة IN3 كمخرج
  pinMode(IN_4, OUTPUT);  // تهيئة IN4 كمخرج
  pinMode(IN_1, OUTPUT);  // تهيئة IN1 كمخرج
  pinMode(IN_2, OUTPUT);  // تهيئة IN2 كمخرج

  // ------ المتقدمة PWM تهيئة قنوات ------
  ledcSetup(PWM_Channel_R, Frequency, Resolution);  // للمحرك الأيمن مع التردد والدقة المحددة PWM إعداد قناة
  ledcSetup(PWM_Channel_L, Frequency, Resolution);  // للمحرك الأيسر مع التردد والدقة المحددة PWM إعداد قناة
  // ------ PWM ربط دبابيس التمكين مع قنوات ------
  ledcAttachPin(EN_B, PWM_Channel_R);  // بالقناة 0 ENB ربط
  ledcAttachPin(EN_A, PWM_Channel_L);  // بالقناة 1 ENA ربط

  Stop_motors();  // إيقاف كلا المحركين عند بداية التشغيل
}

void loop() {

  if (digitalRead(Push_Button) == LOW) {
    Run = 1;
    Serial.println("Run");
    delay(1000);
  }

  if (Run == 1) {
    readSensors();
    calculateError();
    PID();
    Move_motors();
    Print();
    //delay(1000);
  }
}

void readSensors() {
  for (int i = 0; i < 5; i++) {
    sensorValues[i] = digitalRead(sensorPins[i]);
  }
  sensorValues[0] = ! sensorValues[0];
  sensorValues[1] = ! sensorValues[1];
  sensorValues[2] = ! sensorValues[2];
  sensorValues[3] = ! sensorValues[3];
}


void calculateError() {
  weightedSum = 0;
  sum = 0;

  for (int i = 0; i < 5; i++) {
    weightedSum += sensorValues[i] * sensorWeights[i];
    sum += sensorValues[i];
  }

  if (sensorValues[0] == 0 &&
      sensorValues[1] == 0 &&
      sensorValues[2] == 0 &&
      sensorValues[3] == 0 &&
      sensorValues[4] == 0 ){
        skip = 1;
        Serial.print("wwwwwwww");

      if (R_Speed > L_Speed){
      // يمين   
      digitalWrite(IN_3, LOW);
      digitalWrite(IN_4, HIGH);
      digitalWrite(IN_1, HIGH);
      digitalWrite(IN_2, LOW);
      ledcWrite(PWM_Channel_R , 165);
      ledcWrite(PWM_Channel_L , 165);
      Serial.print("rrrrrrrrrrrrrrr");

      }
      if (R_Speed < L_Speed){
       // يسار
      digitalWrite(IN_3, HIGH);
      digitalWrite(IN_4, LOW);
      digitalWrite(IN_1, LOW);
      digitalWrite(IN_2, HIGH);
      ledcWrite(PWM_Channel_R , 165);
      ledcWrite(PWM_Channel_L , 165);
      Serial.print("lllllllllllll");

      }
        return;
  } else if (sensorValues[2] == 1){
    L_Speed = 170;
    R_Speed = 170;
    skip = 1;
  }
  else if (sum == 0) {  // إذا فقد الخط، استخدم آخر قيمة خطأ
    error = lastError;
    skip = 0;
  } else {
    error = weightedSum / sum;
    skip = 0;
  }
}

void PID() {
  if (skip == 1){
   return;
  }



  int derivative = error - lastError;
  totalError += error;
  output = (Kp * error) + (Ki * totalError) + (Kd * derivative);
  lastError = error;  // حفظ الخطأ السابق


  // تحديث السرعات
  R_Speed = baseSpeed + output;
  L_Speed = baseSpeed - output;

  L_Speed = constrain(L_Speed, 0, maxSpeed);
  R_Speed = constrain(R_Speed, 0, maxSpeed);
}

//  تحريك المواتير
void Move_motors() {
  // التحكم بالاتجاه للأمام
  digitalWrite(IN_3, LOW);
  digitalWrite(IN_4, HIGH);
  digitalWrite(IN_1, LOW);
  digitalWrite(IN_2, HIGH);
  // تحديد السرعات
  ledcWrite(PWM_Channel_R, R_Speed);
  ledcWrite(PWM_Channel_L, L_Speed);
}

// يقاف المواتير
void Stop_motors() {
  digitalWrite(IN_3, LOW);
  digitalWrite(IN_4, LOW);
  digitalWrite(IN_1, LOW);
  digitalWrite(IN_2, LOW);
  ledcWrite(PWM_Channel_R, 0);
  ledcWrite(PWM_Channel_L, 0);
}

// طباعة
void Print() {
  if (millis() - Print_1 > 1000) {
    Serial.print("\t");
    for (i = 0; i < 5; i++) {
      Serial.print(sensorValues[i]);  // عرض قيمة الحساس الحالي
      Serial.print("\t");             // (Tab) إضافة تباعد بين القيم
    }
    Serial.println();  // إنهاء السطر بعد عرض جميع القيم
    Serial.println();

    Serial.print("   Error= ");
    Serial.print(error);
    Serial.print("   Output= ");
    Serial.print(output);
    Serial.print("   Left= ");
    Serial.print(L_Speed);
    Serial.print("   Right= ");
    Serial.println(R_Speed);

    Serial.println();
    //delay(1000);  // تأخير 100 مللي ثانية قبل القراءة التالية
    Print_1 = millis();
  }
}

و لماذا تمت برمجته ب C++ و ليس Python علما أن ال ESP يمكن برمجتها ب Python خلافا لل Arduino الذي لا يقبل إلا C++ .. بكل بساطة لأن C++ أسرع في التعامل مع المتحكم من Python
بكل بساطة و أترك المجال لمن لديه خلفية تقنية أكبر مني بالإجابة على هذا السؤال و توضيحه بشكل أكبر
لأن ال C++ هي "compiled" language لذلك يتم ترجمة كل الأوامر دفعة واحده للغة الألة (صفر و واحد) و يتم القيام بهذه العملية قبل رفع ال sketch للمتحكم .. خلافا ل Python التي تعتبر "interpreted" language ما يعني أن الأسطر تترجم و ترسل للمتحكم أو المعالج سطر سطر


ربما تتساءل عن النتيجة التي حصلنا عليها و أظن بأني اخربتك أننا فزنا, بالواقع حصلنا على المركز الثالث و لله الحمد علما أن روبوتنا هو الذي أنهى الحلبة بأقصر وقت بناء على حديث أحد الحكام معنا و لكن لبعض الأخطاء (مثل خروج الروبوت عن الحلبة و اعادتنا له تم خصم عدد من النقاط)
و من أفضل الأمور بالمسابقة هي العمل الجماعي, فنحن كفريق طلاب بالجامعة انضممنا بأكثر من مجموعة و الحمد لله كان التعاون و العمل الجماعي بيننا كبير و لله الحمد و فوز أحدنا كان فوز لنا جميعا, فصحيح كنا بمنافسة و لكن الفوز و الخسارة لم تكن إلا بإرادة الله و ليس لنا أن نعترض على إرادة الله لذلك نسعى بكل ما لدينا و نرضى بالنتيجة و هذه أراها قاعدة في كل المسابقات أحببت التنويه لها لأننا جميعا كفريق واحد و حتى المنافسين من الفرق و الجامعات الأخرى كلنا مسلمين فلا نجعل مسابقة تفيد روح الإخوة بيننا و حتى اخواننا في العروبة من الديانات الأخرى (بشرط أن لا يعتدوا علينا أو على دينا)
1748811585260.webp


و في الختام أتمنى أن لا يتم إستخدام الكود و هذه النصائح من دون تفكير copy and past .. بل ابحث و تحقق من أين قلت أنك أن ال ESP32 إن ادخلت لها أكثر من 3.3v ستعمل فذهب لجوجل و اكتب ESP32 datasheet و اقرأ و تعلم
دائما تعلم التفكير و البحث, لا تأخذ المعلومة على طبق من ذهب
أتمنى أن يكون في هذا الشرح الإفادة لإخواننا المسلمين و المسلمات و لا تنسوا الدعاء لغزة و لكل العالم الإسلامي
و إن احتجت أو فريقك لمساعدة تعلم ما عليك فعله أن تبحث و تتعلم لتحلها و تسهر اليالي و إن لم تعلم فأرسل لي رسالة على هذا المنتدى الكريم و سأسهر معك الليالي لحلها إن شاء الله ( :
ستجدون كل التصاميم مضغوطة بصغة .rar في المرفقات
و بالتوفيق
 

المرفقات

آخر المشاركات

عودة
أعلى