Table of Contents

Line Follower

Introducere

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ă:

Descriere generală

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.

Design Hardware

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

Software Design

1. Motivarea alegerii bibliotecilor folosite

Pentru acest proiect am optat pentru o abordare minimalistă, folosind doar Arduino.h ca bibliotecă principală. Această alegere a fost motivată de:

2. Elementul de noutate al proiectului

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.

3. Justificarea utilizării funcționalităților din laborator

Funcționalitățile implementate sunt direct derivate din conceptele de laborator:

Control PID

Controlul motoarelor

Senzoristica digitală

4. Scheletul proiectului și interacțiunea dintre funcționalități

Arhitectura sistemului:

┌─────────────────┐    ┌──────────────┐    ┌─────────────────┐
│  Senzori Linie  │───▶│ Algoritm PID │───▶│   Motoare DC    │
│   (5 senzori)   │    │              │    │ (Stânga/Dreapta)│
└─────────────────┘    └──────────────┘    └─────────────────┘

Fluxul de execuție:

  1. Inițializare (setup()):
    • Configurarea pin-ilor pentru motoare și senzori
    • Setarea orientării motoarelor
    • Inițializarea comunicației seriale
  1. Bucla principală (loop()):
    • Citirea valorilor de la senzori
    • Calculul erorii de poziție
    • Aplicarea algoritmului PID
    • Ajustarea vitezei motoarelor

Validarea funcționalităților:

5. Mediul de dezvoltare utilizat

Pentru acest proiect am utilizat CLion IDE în combinație cu PlatformIO, o alegere motivată de următoarele avantaje:

CLion IDE:

PlatformIO:

Configurarea proiectului:

; platformio.ini
[env:uno]
platform = atmelavr
board = uno
framework = arduino
monitor_speed = 9600

Această combinație oferă:

6. Calibrarea elementelor de senzoristica

Strategia de calibrare implementată:

  1. Maparea pozițiilor: Fiecare combinație de senzori este mapată la o valoare specifică:
    • Centru (00100): valoare 0
    • Stânga extremă (10000): valoare -6
    • Dreapta extremă (00001): valoare +6
  1. Modificatorul nearLineModifier (0.50):
    • Reduce sensibilitatea pentru pozițiile apropiate de centru
    • Evită corecțiile bruște pentru deviații mici
  1. Tratarea cazurilor speciale:
    • Intersecții (11111, 01110): Păstrarea ultimei valori valide
    • Curbe strânse (11100, 00111): Valori extreme pentru rotire rapidă

Procesul de calibrare:

// Testare individuală a senzorilor
lineSensorModule.SerialLineAnalyzer("every sensor");
 
// Monitorizare completă
lineSensorModule.SerialLineAnalyzer("all");

7. Optimizări implementate

7.1 Filtrul Low-Pass pe eroare

7.2 Limitarea integratorului

7.3 Optimizarea structurilor

7.4 Maparea eficientă a puterii

7.5 Detecția inteligentă a lipsei liniei

Concluzii

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()));
}

Download

linefollower.zip

Rezultate Obţinute