Ce face
Robotul meu linefollower este un dispozitiv autonom care urmărește linii trasate pe suprafețe prin intermediul unei matrice de 5 senzori infraroșu. Folosind placa Arduino UNO ca unitate centrală de procesare, robotul detectează linia de sub el și controlează două motoare DC prin intermediul driverului L298N pentru a se menține pe traseu. Construcția sa robustă, cu șasiul imprimat 3D personalizat, îi conferă durabilitate, în timp ce utilizarea chederului lipit pe roți asigură o aderență sporită la suprafață, prevenind alunecarea pe parcursul traseelor.
Care este scopul lui
Scopul principal al acestui robot este să demonstreze principiile de bază ale roboticii autonome și să ofere o platformă educațională pentru înțelegerea controlului de mișcare bazat pe feedback-ul senzorilor. Robotul este proiectat să parcurgă cu precizie trasee complexe la viteze optime, fiind capabil să navigheze prin curbe, intersecții și segmente drepte fără intervenție umană.
Care a fost ideea de la care am pornit
Ideea inițială a proiectului a pornit de la dorința mea de a crea un robot accesibil ca nivel de complexitate, dar suficient de performant pentru a fi competitiv. Am ales configurația cu 5 senzori IR pentru a avea un echilibru între precizie și simplitate, iar pentru alimentare am implementat soluția cu două baterii de 9V conectate pentru a asigura o capacitate energetică sporită și autonomie extinsă. Șasiul imprimat 3D l-am proiectat special pentru acest robot, optimizând distribuția greutății și poziționarea componentelor pentru stabilitate maximă, iar adăugarea chederului pe roți a venit ca soluție inovatoare pentru îmbunătățirea tracțiunii pe orice tip de suprafață.
De ce cred că este util pentru alții și pentru mine
Pentru mine, acest proiect reprezintă o oportunitate excelentă de a aplica practic cunoștințele de programare, electronică și design 3D într-un sistem integrat. Procesul de optimizare a codului pentru a face robotul mai eficient și mai precis mi-a dezvoltat abilitățile de rezolvare a problemelor și gândirea analitică.
Pentru alții, robotul meu oferă:
Arduino UNO
Descriere: Microcontroler bazat pe ATmega328P care servește ca unitate centrală de procesare. Interacțiune: Primește date de la senzorii IR prin conexiuni GPIO (D2-D4, D7, D12), procesează informația și transmite comenzi de direcție prin GPIO (D8-D11) și viteză prin PWM (D5, D6) către driverul de motor. Comunică cu PC-ul prin UART (D0, D1) pentru debugging. Primește alimentare de 5V de la driverul L298N.
Driver Motor L298N
Descriere: Driver dual H-bridge pentru controlul independent al celor două motoare DC. Interacțiune: Primește semnale de control logic (IN1-IN4) și PWM (ENA, ENB) de la Arduino pentru a controla direcția și viteza motoarelor. Furnizează tensiune de 5V către Arduino și transformă semnalele logice în curenți de putere pentru motoare prin ieșirile OUT1-OUT4. Este alimentat cu 12V de la baterii.
Matrice IR cu 5 Canale
Descriere: Set de 5 senzori infraroșu pentru detectarea liniei negre pe fundal deschis. Interacțiune: Trimite semnale digitale către Arduino (OUT1-OUT5) indicând poziția liniei față de robot. Primește alimentare de 5V de la Arduino.
Motoare DC (2)
Descriere: Motoare de curent continuu pentru propulsia robotului. Interacțiune: Primesc semnale de alimentare și control de la driverul L298N prin ieșirile OUT1-OUT4, transformând semnalele electrice în mișcare mecanică. Sunt echipate cu chedere lipite pe roți pentru aderență îmbunătățită. Baterii 2x9V Descriere: Sursă de alimentare pentru întregul sistem, oferind autonomie extinsă. Interacțiune: Furnizează 12V către driverul L298N pentru alimentarea motoarelor și 5V pentru circuitele logice.
PC
Descriere: Computer utilizat pentru debugging și monitorizare. Interacțiune: Comunică cu Arduino prin conexiune UART (serial) pentru a primi date de diagnostic și a trimite comenzi de test.
Șasiu Imprimat 3D
Descriere: Structură fizică a robotului realizată prin imprimare 3D. Interacțiune: Integrează toate modulele electronic într-un design compact și robust, permițând poziționarea optimă a senzorilor IR față de suprafață. Sistemul funcționează în buclă închisă: senzorii IR detectează poziția liniei, Arduino procesează aceste date și calculează corecțiile necesare, apoi trimite comenzi către driverul motor pentru a ajusta direcția și viteza robotului, menținându-l astfel pe traseul dorit.
Listă de Componente
Arduino UNO
Driver de Motor DC L298N
Motor DC (x2)
Matrice IR cu 5 Canale
Baterie de 9V (x2)
Mufă Universală Tip Baril (tată)
Detalii de Cablare
Arduino UNO
Driver de Motor DC L298N
Motoare DC
Matrice IR cu 5 Canale
Baterii de 9V
Pentru acest proiect am optat pentru o abordare minimalistă, folosind doar Arduino.h ca bibliotecă principală. Această alegere a fost motivată de:
Principalul element inovator al proiectului constă în implementarea unui filtru low-pass pe eroarea PID:
error = error * 0.4 + input * 0.6;
Această abordare oferă:
Un alt aspect inovator este sistemul de detectare a cazurilor speciale pentru diferite configurații ale senzorilor, cu tratare separată pentru intersecții și pierderea liniei.
Funcționalitățile implementate sunt direct derivate din conceptele de laborator:
!digitalRead()
pentru adaptarea la tipul de senzor utilizat┌─────────────────┐ ┌──────────────┐ ┌─────────────────┐ │ Senzori Linie │───▶│ Algoritm PID │───▶│ Motoare DC │ │ (5 senzori) │ │ │ │ (Stânga/Dreapta)│ └─────────────────┘ └──────────────┘ └─────────────────┘
SerialLineAnalyzer()
permite monitorizarea în timp realPentru acest proiect am utilizat CLion IDE în combinație cu PlatformIO, o alegere motivată de următoarele avantaje:
; platformio.ini [env:uno] platform = atmelavr board = uno framework = arduino monitor_speed = 9600
Această combinație oferă:
// Testare individuală a senzorilor lineSensorModule.SerialLineAnalyzer("every sensor"); // Monitorizare completă lineSensorModule.SerialLineAnalyzer("all");
PID()
error = error * 0.4 + input * 0.6;
errorInt = constrain(error + errorInt, -20, 20);
motor
și lineSensor
setPower()
din structura motormap()
și constrain()
noDetection()
Proiectul implementează un sistem robust de urmărire a liniei cu:
Rezultatul este un robot stabil și predictibil, capabil să urmărească trasee complexe cu precizie ridicată.
#include <Arduino.h> double error; double lastError; double errorDiff; double errorInt; #define KP 17.00 #define KD 600.00 #define KI 0.00 #define baseSpeed 40.00 #define nearLineModifier 0.50 struct motor { int motorControllerForwardPIN{}; int motorControllerBackwardPIN{}; int motorControllerSpeedPIN{}; int internalPower{}; int mappedPower{}; bool internalOrientation = false; void setOrientation(const bool orientation) { internalOrientation = orientation; } void setPins(const int forwardPIN, const int backwardPIN, const int speedPWMPIN) { motorControllerForwardPIN = forwardPIN; motorControllerBackwardPIN = backwardPIN; motorControllerSpeedPIN = speedPWMPIN; pinMode(motorControllerForwardPIN, OUTPUT); pinMode(motorControllerBackwardPIN, OUTPUT); pinMode(motorControllerSpeedPIN, OUTPUT); } void setPower(int power) { power = constrain(power, -100, 100); mappedPower = power; if (internalOrientation) power *= -1; internalPower = static_cast<int>(map(abs(power), 0, 100, 0, 255)); if (power < 0) { digitalWrite(motorControllerForwardPIN, LOW); digitalWrite(motorControllerBackwardPIN, HIGH); } else { digitalWrite(motorControllerBackwardPIN, LOW); digitalWrite(motorControllerForwardPIN, HIGH); } analogWrite(motorControllerSpeedPIN, internalPower); } }; motor Right; motor Left; struct lineSensor { bool leftValue = false; bool leftCenterValue = false; bool centerValue = false; bool rightCenterValue = false; bool rightValue = false; double globalLineValue = 0; String lineOutput; int lastGlobalLineValue = 0; int leftSensorPIN = 0; int leftCenterSensorPIN = 0; int centerSensorPIN = 0; int rightCenterSensorPIN = 0; int rightSensorPIN = 0; void setPins(const int left, const int leftCenter, const int center, const int rightCenter, const int right) { leftSensorPIN = left; leftCenterSensorPIN = leftCenter; centerSensorPIN = center; rightCenterSensorPIN = rightCenter; rightSensorPIN = right; pinMode(leftSensorPIN, INPUT); pinMode(leftCenterSensorPIN, INPUT); pinMode(centerSensorPIN, INPUT); pinMode(rightCenterSensorPIN, INPUT); pinMode(rightSensorPIN, INPUT); } void LineValueAnalyzer() { if (!noDetection()) lastGlobalLineValue = static_cast<int>(globalLineValue); leftValue = !digitalRead(leftSensorPIN); leftCenterValue = !digitalRead(leftCenterSensorPIN); centerValue = !digitalRead(centerSensorPIN); rightCenterValue = !digitalRead(rightCenterSensorPIN); rightValue = !digitalRead(rightSensorPIN); lineOutput = leftValue + static_cast<String>(leftCenterValue) + static_cast<String>(centerValue) + static_cast< String>(rightCenterValue) + static_cast<String>(rightValue); if (lineOutput == "10000") { globalLineValue = -6; // -5 } else if (lineOutput == "11000") { globalLineValue = -5; // -3 } else if (lineOutput == "01000") { globalLineValue = -2 * nearLineModifier; } else if (lineOutput == "01100") { globalLineValue = -1 * nearLineModifier; } else if (lineOutput == "00100") { globalLineValue = 0; } else if (lineOutput == "00110") { globalLineValue = 1 * nearLineModifier; } else if (lineOutput == "00010") { globalLineValue = 2 * nearLineModifier; } else if (lineOutput == "00011") { globalLineValue = 5; // 3 } else if (lineOutput == "00001") { globalLineValue = 6; // 5 } else if (lineOutput == "11100" || lineOutput == "11110" || lineOutput == "10100" || lineOutput == "10010") { globalLineValue = -6; } else if (lineOutput == "00111" || lineOutput == "01111" || lineOutput == "00101" || lineOutput == "01001") { globalLineValue = 6; } else if (lineOutput == "11111" || lineOutput == "01110" || lineOutput == "01010") { globalLineValue = lastGlobalLineValue; } } bool noDetection() { leftValue = digitalRead(leftSensorPIN); leftCenterValue = digitalRead(leftCenterSensorPIN); centerValue = digitalRead(centerSensorPIN); rightCenterValue = digitalRead(rightCenterSensorPIN); rightValue = digitalRead(rightSensorPIN); if (leftValue == 1 && leftCenterValue == 1 && centerValue == 1 && rightCenterValue == 1 && rightValue == 1) return true; return false; } int returnLineValue() { if (noDetection()) { Serial.println(lastGlobalLineValue); return lastGlobalLineValue; } Serial.println(globalLineValue); return static_cast<int>(globalLineValue); } void SerialLineAnalyzer(const String &command) { LineValueAnalyzer(); if (command == "every sensor") { Serial.print(leftValue); Serial.print(" "); Serial.print(leftCenterValue); Serial.print(" "); Serial.print(centerValue); Serial.print(" "); Serial.print(rightCenterValue); Serial.print(" "); Serial.print(rightValue); Serial.print("\n"); } if (command == "all") { Serial.print(leftValue); Serial.print(" "); Serial.print(leftCenterValue); Serial.print(" "); Serial.print(centerValue); Serial.print(" "); Serial.print(rightCenterValue); Serial.print(" "); Serial.print(rightValue); Serial.print(" "); Serial.print(globalLineValue); Serial.print(" "); } if (command == "value") { Serial.print(globalLineValue); Serial.print("\n"); } } }; lineSensor lineSensorModule; void setup() { Serial.begin(9600); Right.setOrientation(false); Left.setOrientation(true); Right.setPins(4, 5, 3); Left.setPins(7, 8, 6); lineSensorModule.setPins(9, 10, 11, 12, 13); } double output; double PID(int input) { error = error * 0.4 + input * 0.6; errorDiff = error - lastError; errorInt = constrain(error + errorInt, -20, 20); output = KP * error + KD * errorDiff + KI * errorInt; lastError = error; return output; } void loop() { lineSensorModule.LineValueAnalyzer(); Left.setPower(baseSpeed - PID(lineSensorModule.returnLineValue())); Right.setPower(baseSpeed + PID(lineSensorModule.returnLineValue())); }