يمكن أن تكون برمجة PLC للتحكم في المشغل الخاص بك أحد المشاريع الأكثر صعوبة التي يجب القيام بها. إنه يتطلب التجربة والخطأ، والاختبار وكومة من الصبر؛ على الرغم من أن النتائج يمكن أن تكون وظيفية ومجزية بشكل لا يصدق.
الكود الموجود في هذا البرنامج التعليمي هو برنامج كامل لوظيفة المحرك المستجيب التسلسلي. سيسمح لك استخدام Serial Monitor مع PLC الخاص بك بربط نظام Arduino الخاص بك مع أجهزة الكمبيوتر أو الوحدات النمطية أو أنظمة التشغيل الآلي الأخرى.
سوف نقوم بمراجعة كل وظيفة وقسم من التعليمات البرمجية بينما نمضي قدمًا، لذلك يمكنك سحب وظائف محددة من هذا البرنامج التعليمي. يرجى ملاحظة أن بعض الوظائف تستدعي وظائف أخرى غير مضمنة في نفس مجموعة التعليمات البرمجية، لذلك ستحتاج إلى التحقق من التعليمات البرمجية الخاصة بك للتأكد من تنفيذها بشكل صحيح.
سيتم نشر الكود الكامل في نهاية هذه المقالة. إذا كنت ترغب في استخدام الكود الكامل، فانسخه والصقه في برنامج IDE الخاص بك.
نظرة عامة على البرنامج
عند التهيئة، سيعود هذا البرنامج إلى المنزل ويقوم بمعايرة مشغلاتك، ويبحث عن نهايات الحركة للوحدة. ثم يقوم بعد ذلك بنقل المشغل إلى النقطة الوسطى من السكتة الدماغية لانتظار المزيد من الأوامر. يمكنك بعد ذلك إدخال رقم في جهاز المراقبة التسلسلي (من جهاز الكمبيوتر الخاص بك) وسينتقل المشغل إلى هذا الموضع. لا يتم الخلط بين هذا البرنامج عند إيقاف التشغيل في نهاية الشوط، ويعمل مع أي مستشعر تأثير Hall أو مشغل مستشعر بصري.
قائمة الأجزاء
يتم تضمين المكونات التي تحمل علامة (*) في مجموعة اردوينو المبتدئة
- اردوينو بي ال سي (أونو* أو ميجا 2560)
- سائق المحرك (ارتفاع التيار AD-MD6321) - 1 لكل مشغل.
- مصدر طاقة 12 فولت للمحركات (يتجاوز سحب التيار التراكمي)
- 1 لوح تجارب، حسب الحاجة*
- أسلاك التوصيل لـ Arduino PLC - سيتطلب كل MD تيار عالي 6 اتصالات أنثى.
- سلك ذو دبوس واحد لـ PLCs* (M-F 10 قطعة/حزمة)
- طقم سلك العبور (م-م، 75 قطعة/باك; أطوال مختلفة)؛
- المحركات مع أجهزة استشعار ردود الفعل وأقواس تثبيت المحرك، حسب الحاجة. (مستشعر Hall Effect أو مشغلات المستشعر البصري فقط، لهذا الرمز)
وظائف
ملخص الوظائف
-
void setup()
: تهيئة الدبابيس، وبدء الاتصال التسلسلي، وتشغيل إجراءات التوجيه والمعايرة. -
void homingRoutine()
: يبدأ التوجيه، وينقل المشغل إلى التراجع الكامل، ويحدد الحد الأدنى من السكتة الدماغية. -
void calibrateActuator()
: يبدأ المعايرة، ويحرك المشغل إلى الامتداد الكامل، ويضبط الحد الأقصى للضربة. -
void loop()
: التحقق من الإدخال التسلسلي، وتحديث موضع الهدف، والتحكم في حركة المحرك. -
void movement()
: يتحكم في اتجاه حركة المحرك بناءً على موضع الهدف. -
void readSensor()
: يقرأ قيم المستشعر، ويحدث الموضع، ويطبع قراءات المستشعر. -
void stopMotor()
: يوقف حركة المحرك. -
bool isEndOfStroke()
: التحقق من نهاية شروط السكتة الدماغية ومعالجة المهلات.
المتغيرات
يجب استدعاء المتغيرات في التعليمات البرمجية الخاصة بك قبل استخدامها. أفضل ممارسات البرمجة هي تعيين المتغيرات الخاصة بك وتعيينها في الجزء العلوي من التعليمات البرمجية الخاصة بك، لتسهيل تعديل أو "ضبط" المتغيرات الخاصة بك.
هذه هي المتغيرات المستخدمة لهذا الكود، مع تعليق يشرح وظيفتها ونطاقها المحتمل.
//Actuator Specifications
int maxStroke; // Leave undefined, set during calibration
int minStroke; // Leave undefined, set during homing
// Input Variables (Pins) - Change to match your pinout
const int Xpin=10;
const int Rpin=11;
// Sensor Read Pin 1
const int sensorPin=3;
// Hall Effect Sensor Input (Sensor Read pin 2) - Change to match your pinout
const int sensorPin2=4;
int sensorCount2;
// Position Target Function Variables
int targetNumber;
int currentPosition;
int lastPosition=0;
// Motor Control Variables
bool active = false; // Actuator On / Off toggle flag
bool EOSFlag=false; // End of Stroke Flag
// Sensor Readings
int sensorValue;
int lastSensorValue = LOW;
int sensorValue2;
int lastSensorValue2 = LOW;
// Variables for Debounce and End of Stroke Sensing
const unsigned long motionTimeout = 2000;
// Adjust this value based on your requirements (in milliseconds) 2000=2s
const unsigned long CALIBRATION_TIMEOUT=3000;
// Adjust this value based on your requirements (in milliseconds) 3000=3s
unsigned long lastMotionTime = millis(); //Timer function for EOS function
// Position Variables
unsigned long pulseCount = 0;
int direction = 0; // Used for incremental movement count
// 0: Stopped, 1: Moving Forward, -1: Moving Backward
الإعداد/التهيئة
إعداد الفراغ () ؛ الوظيفة هي وظيفة Arduino الأساسية التي تقوم بتهيئة PLC، وتبدأ المراقبة التسلسلية، وتعين المدخلات والمخرجات كما تمت برمجتها.
تقوم وظيفة الإعداد هذه بتعيين 4 دبابيس كمدخلات أو مخرجات، ثم تقوم بتشغيل إجراءات التوجيه والمعايرة.
void setup() {
pinMode(Xpin, OUTPUT);
pinMode(Rpin, OUTPUT);
pinMode(sensorPin, INPUT_PULLUP);
pinMode(sensorPin2, INPUT_PULLUP);
Serial.begin(115200);
homingRoutine();
calibrateActuator();
}
روتين التوجيه
مقتطف الكود هذا هو وظيفة توجيه مشغلات الملاحظات الخاصة بك. سيقوم هذا الرمز بسحب المشغل الخاص بك حتى يتم اكتشاف نهاية الشوط، وسيعين لهذا الموضع قيمة "0" - ثم يقوم بتهيئة وظيفة المعايرة.
في البرنامج الكامل، يتم تشغيل وظيفتي التوجيه والمعايرة مرة واحدة، أثناء بدء التشغيل، ولا يتم استدعاؤهما مرة أخرى.
يستدعي مقتطف التعليمات البرمجية هذا الوظيفة (الوظائف) الإضافية التي لم يتم تضمينها في هذه المجموعة.
void homingRoutine() {
active=true;
Serial.println("Homing Initiated");
digitalWrite(Xpin, LOW);
digitalWrite(Rpin, HIGH);
while (!EOSFlag) {
direction=-1;
readSensor();
isEndOfStroke();
// Move actuator to full retraction
}
direction=0;
minStroke=currentPosition;
Serial.println("Homing Completed");
}
روتين المعايرة
بعد ذلك، لدينا روتين المعايرة الخاص بنا. ستعمل هذه الوظيفة على تمديد المشغل الخاص بك بالكامل بعد أن تقوم وظيفة التوجيه بسحب الوحدة بالكامل. الغرض من هذا الإجراء هو حساب نبضات المستشعر من أحد طرفي شوط المشغل إلى الطرف الآخر. يتم بعد ذلك حفظ هذه القيمة كمتغير maxStroke واستخدامها لتحديد الموضع. يمكن تعديل هذا الرمز لمعالجة ردود فعل مقياس الجهد عن طريق تعيين الجهد إلى الموضع، بدلاً من حساب النبضات.
يستدعي مقتطف التعليمات البرمجية هذا الوظيفة (الوظائف) الإضافية التي لم يتم تضمينها في هذه المجموعة.
void calibrateActuator() {
Serial.println("Calibration Initiated");
active = true;
// Reset variables
pulseCount = 0;
currentPosition = 0;
lastMotionTime=millis();
// Move actuator to full extension
digitalWrite(Xpin, HIGH);
digitalWrite(Rpin, LOW);
direction=1;
// Wait until the end of stroke is reached during calibration
while (!isEndOfStroke()) {
readSensor();
// Add a timeout condition to avoid infinite loop
if (millis() - lastMotionTime > motionTimeout) {
Serial.println("Calibration Timeout");
stopMotor();
maxStroke=currentPosition;
direction=0;
// Print the calibration results
Serial.print("Calibration Complete. Minimum Stroke: ");
Serial.print(minStroke);
Serial.print(" Maximum Stroke: ");
Serial.println(maxStroke);
targetNumber=((maxStroke+minStroke)/2);
break;
}
}
}
حلقة
وظيفة حلقة الفراغ () هي الجسم الرئيسي من كود اردوينو الخاص بك. سيتم تنفيذ أي شيء مكتوب داخل الدالة ()loop بشكل متكرر أثناء البرنامج.
ملاحظة المبرمجين: أنا شخصيا أجد أنه من الأسهل إبقاء وظيفة الحلقة بسيطة قدر الإمكان. هذا هو الجزء من الكود الذي سيتم الاتصال به معظم الوظائف الأخرى في الكود و لا أستطيع يتم رفعه دون بقية التعليمات البرمجية.
في وظيفة الحلقة هذه، يتحقق البرنامج من وجود جهاز مراقبة تسلسلي للإدخال، ويقوم بتحديث موضع الهدف باستخدام الأمر الخاص بك، ويستدعي وظيفة الحركة. معظم الرسائل التي يرسلها PLC إلى Serial Monitor تأتي من مقتطف الكود هذا.
هناك تم التعليق على سطر من التعليمات البرمجية أدناه - عند تضمينه، يقوم هذا السطر من التعليمات البرمجية بتعيين إدخال من 0-100 إلى المشغل. أي. المدخلات الخاصة بك هي الآن نسبة مئوية من شوط المشغل. يمكن أيضًا تعديل هذا الخط لاستخدام المدخلات الأخرى بتنسيق معين مماثل.
يستدعي مقتطف الكود هذا وظيفة (وظائف) إضافية غير مضمنة في هذه المجموعة.
void loop() {
if (!active && Serial.available() > 0) {
String serialInput = Serial.readStringUntil('\n');
Serial.print("Received: ");
Serial.println(serialInput);
if (serialInput.length() > 0) {
targetNumber = serialInput.toInt();
//targetNumber = map(targetNumber, 0, 100, minStroke, maxStroke);
/*If the above line is active, you will input a value between 0-100,
The program will use this input as a % of stroke.*/
Serial.print("Target number: ");
Serial.println(targetNumber);
EOSFlag = false;
}
// Clear the serial buffer
while (Serial.available()) {
Serial.read();
}
}
if (targetNumber != currentPosition) {
active = true;
movement();
}
/*
if (!active) {
Serial.println("Waiting for Input");
return;
} */
if (active && targetNumber == currentPosition) {
stopMotor();
Serial.println("Target Met");
}
}
حركة
تتحكم وظيفة الحركة في اتجاه حركة المشغل بناءً على موضع الهدف. هذه هي الوظيفة الوحيدة في هذا البرنامج التي تخبر سائق المحرك بتحريك المشغل.
يستدعي مقتطف الرمز هذا وظيفة (وظائف) إضافية غير مضمنة في هذه المجموعة.
void movement() {
if (targetNumber > currentPosition) {
digitalWrite(Xpin,HIGH);
digitalWrite(Rpin,LOW);
//Serial.println(" Extending");
direction = 1;
} else if (targetNumber < currentPosition) {
digitalWrite(Rpin,HIGH);
digitalWrite(Xpin,LOW);
direction = -1;
//Serial.println("Retracting");
} else if (targetNumber == currentPosition) {
stopMotor();
delay(10);
}
if(active) {
readSensor();
}
if (isEndOfStroke()) {
return; // Skip further movement actions
}
}
قراءة الاستشعار
تقوم وظيفة معالجة المستشعر بقراءة قيم المستشعر وتحديث موضعه وطباعة قراءات المستشعر على الشاشة التسلسلية. يمكن استخدام الأوامر التسلسلية لتصحيح الأخطاء، أو التعليق عليها للحصول على قراءات أقل انشغالًا.
يمكن استخدام مقتطف الكود هذا في أي برنامج وسيعطي قراءة دقيقة للنبضات من مستشعر التغذية المرتدة.
void readSensor() {
sensorValue = digitalRead(sensorPin);
if(lastSensorValue != sensorValue) {
lastSensorValue = sensorValue;
pulseCount = pulseCount + direction;
Serial.print("Sensor 1: ");
Serial.println(pulseCount);
}
sensorValue2 = digitalRead(sensorPin2);
if(lastSensorValue2 != sensorValue2) {
lastSensorValue2 = sensorValue2;
sensorCount2=sensorCount2+direction;
pulseCount = pulseCount + direction;
Serial.print("Sensor 2: ");
Serial.println(sensorCount2);
Serial.print("Current Position: ");
Serial.println(currentPosition);
}
currentPosition = pulseCount;
}
توقف المحرك وتعطيله
يوقف حركة المشغل ويعيد تمكين الوحدة من قراءة الشاشة التسلسلية لمزيد من الأوامر. هذا هو مقتطف الكود الوحيد في هذا البرنامج الذي سيوقف حركة المشغل.
يمكن استخدام مقتطف الكود هذا في أي برنامج مع أي مشغل، لأنه يقوم فقط بإيقاف تشغيل المحرك وتغيير الحالة "النشطة".
void stopMotor() {
if (active) {
active=false;
digitalWrite(Xpin,LOW);
digitalWrite(Rpin,LOW);
}
}
فحص نهاية السكتة الدماغية
يتحقق من نهاية شروط السكتة الدماغية ويتعامل مع المهلات. عندما يصل المشغل إلى نهاية الشوط ويتوقف عن الحركة، ستقوم هذه الوظيفة بتشغيل وإعادة ضبط المشغل لمدخلات الأوامر الجديدة، حتى لو لم يتم الوصول إلى الرقم المستهدف بعد.
يستدعي مقتطف التعليمات البرمجية هذا الوظيفة (الوظائف) الإضافية التي لم يتم تضمينها في هذه المجموعة.
bool isEndOfStroke() {
// Check if there is motion (changes in the pulse count)
if (active && (currentPosition != lastPosition)) {
lastMotionTime = millis(); // Update the time of the last motion
lastPosition = currentPosition;
EOSFlag=false;
}
// Check if there is no motion for the specified timeout
if (active && ((millis() - lastMotionTime) > motionTimeout)) {
if(EOSFlag!=true) {
Serial.print("Timeout - ");
Serial.println("At limit");
EOSFlag=true;
}
direction=0;
stopMotor();
return true;
}
return false;
}
الكود الكامل
//Actuator Specifications
int maxStroke;
int minStroke;
// Input Variables (Pins)
const int Xpin=10;
const int Rpin=11;
const int sensorPin=3;
// Hall Effect Sensor Input
const int sensorPin2=4;
int sensorCount2;
// Motor Function Variables
int targetNumber;
int currentPosition;
int lastPosition=0;
bool active = false;
bool EOSFlag=false;
// Sensor Readings
int sensorValue;
int lastSensorValue = LOW;
int sensorValue2;
int lastSensorValue2 = LOW;
// Variables for Debounce
const unsigned long motionTimeout = 2000; // Adjust this value based on your requirements (in milliseconds)
const unsigned long CALIBRATION_TIMEOUT=3000; // Adjust this value based on your requirements (in milliseconds)
unsigned long lastMotionTime = millis();
// Position Variables
unsigned long pulseCount = 0;
int direction = 0; // 0: Stopped, 1: Moving Forward, -1: Moving Backward
void setup() {
pinMode(Xpin, OUTPUT);
pinMode(Rpin, OUTPUT);
pinMode(sensorPin, INPUT_PULLUP);
pinMode(sensorPin2, INPUT_PULLUP);
Serial.begin(115200);
homingRoutine();
calibrateActuator();
}
void homingRoutine() {
active=true;
Serial.println("Homing Initiated");
digitalWrite(Xpin, LOW);
digitalWrite(Rpin, HIGH);
while (!EOSFlag) {
direction=-1;
readSensor();
isEndOfStroke();
// Move actuator to full retraction
}
direction=0;
minStroke=currentPosition;
Serial.println("Homing Completed");
}
void calibrateActuator() {
Serial.println("Calibration Initiated");
active = true;
// Reset variables
pulseCount = 0;
currentPosition = 0;
lastMotionTime=millis();
// Move actuator to full extension
digitalWrite(Xpin, HIGH);
digitalWrite(Rpin, LOW);
direction=1;
// Wait until the end of stroke is reached during calibration
while (!isEndOfStroke()) {
readSensor();
// Add a timeout condition to avoid infinite loop
if (millis() - lastMotionTime > motionTimeout) {
Serial.println("Calibration Timeout");
stopMotor();
maxStroke=currentPosition;
direction=0;
// Print the calibration results
Serial.print("Calibration Complete. Minimum Stroke: ");
Serial.print(minStroke);
Serial.print(" Maximum Stroke: ");
Serial.println(maxStroke);
targetNumber=((maxStroke+minStroke)/2);
break;
}
}
}
void loop() {
if (!active && Serial.available() > 0) {
String serialInput = Serial.readStringUntil('\n');
Serial.print("Received: ");
Serial.println(serialInput);
if (serialInput.length() > 0) {
targetNumber = serialInput.toInt();
Serial.print("Target number: ");
Serial.println(targetNumber);
EOSFlag = false;
}
// Clear the serial buffer
while (Serial.available()) {
Serial.read();
}
}
if (targetNumber != currentPosition) {
active = true;
movement();
}
/*
if (!active) {
Serial.println("Waiting for Input");
return;
} */
if (active && targetNumber == currentPosition) {
stopMotor();
Serial.println("Target Met");
}
}
void movement() {
if (targetNumber > currentPosition) {
digitalWrite(Xpin,HIGH);
digitalWrite(Rpin,LOW);
//Serial.println(" Extending");
direction = 1;
} else if (targetNumber < currentPosition) {
digitalWrite(Rpin,HIGH);
digitalWrite(Xpin,LOW);
direction = -1;
//Serial.println("Retracting");
} else if (targetNumber == currentPosition) {
stopMotor();
delay(10);
}
if(active) {
readSensor();
}
if (isEndOfStroke()) {
return; // Skip further movement actions
}
}
void readSensor() {
sensorValue = digitalRead(sensorPin);
if(lastSensorValue != sensorValue) {
lastSensorValue = sensorValue;
pulseCount = pulseCount + direction;
Serial.print("Sensor 1: ");
Serial.println(pulseCount);
}
sensorValue2 = digitalRead(sensorPin2);
if(lastSensorValue2 != sensorValue2) {
lastSensorValue2 = sensorValue2;
sensorCount2=sensorCount2+direction;
pulseCount = pulseCount + direction;
Serial.print("Sensor 2: ");
Serial.println(sensorCount2);
Serial.print("Current Position: ");
Serial.println(currentPosition);
}
currentPosition = pulseCount;
}
void stopMotor() {
if (active) {
active=false;
digitalWrite(Xpin,LOW);
digitalWrite(Rpin,LOW);
}
}
bool isEndOfStroke() {
// Check if there is motion (changes in the pulse count)
if (active && (currentPosition != lastPosition)) {
lastMotionTime = millis(); // Update the time of the last motion
lastPosition = currentPosition;
EOSFlag=false;
}
// Check if there is no motion for the specified timeout
if (active && ((millis() - lastMotionTime) > motionTimeout)) {
if(EOSFlag!=true) {
Serial.print("Timeout - ");
Serial.println("At limit");
EOSFlag=true;
}
direction=0;
stopMotor();
return true;
}
return false;
}