Components and supplies
DC MOTOR WHEELS
IR Sensors
Arduino Uno R4 Pro - By OSEPP
DC motor (generic)
ANGEEK L293D Driver Shield
40 colored female-female jumper wires
Tools and machines
drill, screwdriver, soldering iron
Apps and platforms
Arduino IDE 2.0 (beta)
Project description
Code
Line Follower Robot
cpp
Documentation
Chassis
Chassis 1.stl
Sensor mounting
Sensor mounting.stl
Comments
Only logged in users can leave comments
charlybrown1982
a month ago
Hi Lee, why is there a !analogueRead in line 41, while all other places you´re using a digitalRead?
profortnitegamer698
a month ago
Shut up weirdo ur not actually him bro get a life u ar so poop
mozziez
2 months ago
Congratulations on a wonderful project.
super_gamer_dude34
a month ago
not tat wodefull chut up
alnoorsmdscreen
2 months ago
Fantastic,GOOD Work, Keep it up bro https://alnoorsmd.com/
super_gamer_dude34
a month ago
not tat gret and like u not smat sut up
Building A Line Following Robot Using Arduino | Arduino Project Hub
nyxion
11 days ago
Lưu ý quan trọng trước khi bắt đầu: * Phần cứng: Code này giả định bạn đang sử dụng: * ESP8266/ESP32: (Vì code có sử dụng Blynk và WiFi). Bạn cần điều chỉnh các thư viện WiFi và chân kết nối nếu dùng board khác. * Mảng cảm biến: Code này được viết cho mảng 5 cảm biến (bạn có thể thay đổi số lượng cảm biến). * Động cơ có encoder (tùy chọn): Code có phần cho encoder, nhưng bạn có thể bỏ qua nếu không có. * Thư viện: Bạn cần cài đặt các thư viện sau (qua Library Manager trong Arduino IDE): * Blynk * PID_v1 * ESP8266WiFi (hoặc WiFi cho ESP32) * AFMotor (nếu bạn vẫn dùng driver L293D) * Điều chỉnh: Bạn cần phải điều chỉnh các thông số (chân cảm biến, chân động cơ, hệ số PID, tốc độ, v.v.) cho phù hợp với phần cứng cụ thể của bạn. * Mức độ phức tạp: Code này phức tạp hơn nhiều so với code ban đầu. Hãy đọc kỹ phần giải thích và đảm bảo bạn hiểu từng phần trước khi nạp code. * Chia nhỏ: Để dễ quản lý, bạn nên chia code thành các file .h (header file) và .cpp (source file). Ví dụ, bạn có thể tạo sensors.h và sensors.cpp để quản lý việc đọc và xử lý dữ liệu từ cảm biến, motor_control.h and motor_control.cpp để quản lý việc điều khiển động cơ, và pid_control.h và pid_control.cpp để tách biệt logic điều khiển PID. Code Nâng Cao (Giải Thích Chi Tiết) #define BLYNK_TEMPLATE_ID "TMPLxxxxxxx" // Thay bằng Template ID #define BLYNK_TEMPLATE_NAME "Line Follower PID" // Thay bằng Template Name #define BLYNK_AUTH_TOKEN "YourAuthToken" // Thay bằng Auth Token #include <ESP8266WiFi.h> // Hoặc <WiFi.h> cho ESP32 #include <BlynkSimpleEsp8266.h> // Hoặc <BlynkSimpleEsp32.h> #include <AFMotor.h> #include <PID_v1.h> // Cấu hình chân cảm biến (mảng 5 cảm biến) const int numSensors = 5; const int sensorPins[numSensors] = {A0, A1, A2, A3, A4}; // Thay đổi chân cho phù hợp // Cấu hình chân động cơ (giả sử L293D) AF_DCMotor motor1(1, MOTOR12_1KHZ); AF_DCMotor motor2(2, MOTOR12_1KHZ); AF_DCMotor motor3(3, MOTOR34_1KHZ); AF_DCMotor motor4(4, MOTOR34_1KHZ); // Biến cho PID double Setpoint, Input, Output; double Kp = 2, Ki = 5, Kd = 1; // Giá trị ban đầu (sẽ được điều chỉnh qua Blynk) PID myPID(&Input, &Output, &Setpoint, Kp, Ki, Kd, DIRECT); // Tốc độ cơ bản và giới hạn gia tốc int baseSpeed = 150; int maxAcceleration = 5; // Giới hạn gia tốc int currentLeftSpeed = 0; int currentRightSpeed = 0; int targetLeftSpeed = 0; int targetRightSpeed = 0; // Kết nối WiFi char ssid[] = "YourNetworkName"; char pass[] = "YourPassword"; // Biến cho lọc trung bình trượt const int numReadings = 10; int readings[numSensors][numReadings]; int readIndex = 0; int totals[numSensors] = {0}; int averages[numSensors] = {0}; // Biến cho encoder (tùy chọn) // Nếu bạn có encoder, hãy khai báo các chân encoder và biến đếm xung ở đây // Ví dụ: // #define encoderLeftPinA 2 // #define encoderLeftPinB 3 // #define encoderRightPinA 4 // #define encoderRightPinB 5 // volatile long encoderLeftCount = 0; // volatile long encoderRightCount = 0; // --- Blynk --- BLYNK_WRITE(V1) { // Kp Kp = param.asDouble(); myPID.SetTunings(Kp, Ki, Kd); } BLYNK_WRITE(V2) { // Ki Ki = param.asDouble(); myPID.SetTunings(Kp, Ki, Kd); } BLYNK_WRITE(V3) { // Kd Kd = param.asDouble(); myPID.SetTunings(Kp, Ki, Kd); } // Hàm đọc cảm biến (có lọc trung bình trượt) void readSensors() { for (int i = 0; i < numSensors; i++) { totals[i] = totals[i] - readings[i][readIndex]; readings[i][readIndex] = analogRead(sensorPins[i]); // Đọc analog (hoặc digitalRead) totals[i] = totals[i] + readings[i][readIndex]; averages[i] = totals[i] / numReadings; } readIndex = (readIndex + 1) % numReadings; } // Hàm tính toán Input (vị trí đường dò) // Dựa trên giá trị trung bình của các cảm biến double calculateInput() { // Phương pháp 1: Trọng tâm (Center of Gravity) - Phổ biến và hiệu quả int allSensors = 0; double weightedSum = 0; for (int i = 0; i < numSensors; i++) { // Giả sử ngưỡng để xác định "có đường" là 500 (cần điều chỉnh) if (averages[i] > 500) { // Thay 500 bằng ngưỡng phù hợp với cảm biến của bạn allSensors++; weightedSum += (double)averages[i] * (i - (numSensors - 1) / 2.0); // Cảm biến ở giữa có trọng số 0 } } if (allSensors == 0) { return 0;// không đọc được cảm biến nào } return weightedSum / allSensors; // Phương pháp 2: One-Hot Encoding (đơn giản hơn, ít chính xác hơn) // Chỉ sử dụng nếu bạn không muốn tính toán phức tạp /* if (averages[2] > 500) return 0; // Cảm biến giữa if (averages[1] > 500) return -1; // Cảm biến trái 1 if (averages[3] > 500) return 1; // Cảm biến phải 1 if (averages[0] > 500) return -2; // Cảm biến trái 2 if (averages[4] > 500) return 2; // Cảm biến phải 2 return 0; // Không cảm biến nào trên đường */ } // Hàm điều khiển động cơ (có giới hạn gia tốc) void setMotorSpeeds() { if (currentLeftSpeed < targetLeftSpeed) { currentLeftSpeed = min(currentLeftSpeed + maxAcceleration, targetLeftSpeed); } else if (currentLeftSpeed > targetLeftSpeed) { currentLeftSpeed = max(currentLeftSpeed - maxAcceleration, targetLeftSpeed); } if (currentRightSpeed < targetRightSpeed) { currentRightSpeed = min(currentRightSpeed + maxAcceleration, targetRightSpeed); } else if (currentRightSpeed > targetRightSpeed) { currentRightSpeed = max(currentRightSpeed - maxAcceleration, targetRightSpeed); } // Đặt tốc độ cho động cơ (sử dụng currentLeftSpeed và currentRightSpeed) if (currentLeftSpeed > 0) { motor1.run(FORWARD); motor4.run(FORWARD); } else{ motor1.run(BACKWARD); motor4.run(BACKWARD); } if (currentRightSpeed > 0) { motor2.run(FORWARD); motor3.run(FORWARD); }else{ motor2.run(BACKWARD); motor3.run(BACKWARD); } motor1.setSpeed(abs(currentLeftSpeed)); motor2.setSpeed(abs(currentRightSpeed)); motor3.setSpeed(abs(currentRightSpeed)); motor4.setSpeed(abs(currentLeftSpeed)); } // Hàm xử lý encoder (tùy chọn) // Nếu bạn có encoder, hãy viết các hàm ngắt (interrupt service routines - ISR) ở đây // để tăng biến đếm xung mỗi khi có tín hiệu từ encoder. // Ví dụ: /* void encoderLeftA() { if (digitalRead(encoderLeftPinB) == HIGH) { encoderLeftCount++; } else { encoderLeftCount--; } } void encoderLeftB() { if (digitalRead(encoderLeftPinA) == HIGH) { encoderLeftCount--; } else { encoderLeftCount++; } } // Tương tự cho encoderRightA và encoderRightB */ void setup() { Serial.begin(115200); Blynk.begin(BLYNK_AUTH_TOKEN, ssid, pass); // Khởi tạo chân cảm biến for (int i = 0; i < numSensors; i++) { pinMode(sensorPins[i], INPUT); // Khởi tạo mảng readings for (int j = 0; j < numReadings; j++) { readings[i][j] = 0; } } // Khởi tạo PID Setpoint = 0; // Giữa đường dò myPID.SetMode(AUTOMATIC); myPID.SetOutputLimits(-255, 255); // Giới hạn đầu ra cho phù hợp với setSpeed // Khởi tạo encoder (tùy chọn) // pinMode(encoderLeftPinA, INPUT_PULLUP); // pinMode(encoderLeftPinB, INPUT_PULLUP); // pinMode(encoderRightPinA, INPUT_PULLUP); // pinMode(encoderRightPinB, INPUT_PULLUP); // attachInterrupt(digitalPinToInterrupt(encoderLeftPinA), encoderLeftA, CHANGE); // attachInterrupt(digitalPinToInterrupt(encoderLeftPinB), encoderLeftB, CHANGE); // ... (tương tự cho encoder bên phải) } void stopRobot() { motor1.run(RELEASE); motor2.run(RELEASE); motor3.run(RELEASE); motor4.run(RELEASE); } void loop() { Blynk.run(); readSensors(); // Đọc và lọc giá trị cảm biến Input = calculateInput(); // Tính toán vị trí đường dò // Nếu không có cảm biến nào trên đường, dừng lại (hoặc thực hiện hành động tìm kiếm) bool allSensorsOff = true; for (int i = 0; i < numSensors; i++) { if (averages[i] > 500) { // Thay 500 bằng ngưỡng của bạn allSensorsOff = false; break; } } if (allSensorsOff) { stopRobot(); return; } myPID.Compute(); // Tính toán PID // Tính toán tốc độ mục tiêu cho mỗi bánh targetLeftSpeed = baseSpeed + Output; targetRightSpeed = baseSpeed - Output; setMotorSpeeds(); // Đặt tốc độ cho động cơ (có giới hạn gia tốc) // Gửi dữ liệu lên Blynk (tùy chọn) // Blynk.virtualWrite(V4, someValue); // Ví dụ: gửi giá trị Input lên V4 // ... gửi các giá trị khác nếu cần ... // In ra Serial Monitor để debug (tùy chọn) // Serial.print("Input: "); Serial.println(Input); //Serial.print("Output: "); Serial.println(Output); // Serial.print("Left Speed: "); Serial.println(currentLeftSpeed); //Serial.print("Right Speed: "); Serial.println(currentRightSpeed); } Giải Thích Chi Tiết * Cấu Hình: * #define: Định nghĩa các hằng số (chân cảm biến, số lượng cảm biến, chân động cơ, thông tin Blynk). * const int sensorPins[]: Mảng lưu trữ các chân kết nối với cảm biến. * Thư Viện: Bao gồm các thư viện cần thiết. * Biến: * Setpoint, Input, Output: Các biến cho PID. * Kp, Ki, Kd: Các hệ số PID (ban đầu). * baseSpeed: Tốc độ cơ bản khi đi thẳng. * maxAcceleration: Giới hạn gia tốc. * currentLeftSpeed, currentRightSpeed: Tốc độ hiện tại của bánh trái và phải. * targetLeftSpeed, targetRightSpeed: Tốc độ mục tiêu của bánh trái và phải. * readings, readIndex, totals, averages: Các biến cho lọc trung bình trượt. * (Tùy chọn) Các biến cho encoder (nếu có). * Blynk: * BLYNK_WRITE(V1), BLYNK_WRITE(V2), BLYNK_WRITE(V3): Các hàm callback để nhận giá trị Kp, Ki, Kd từ Blynk và cập nhật cho PID. * readSensors(): * Đọc giá trị từ tất cả các cảm biến. * Thực hiện lọc trung bình trượt cho mỗi cảm biến. * calculateInput(): * Tính toán vị trí của đường dò dựa trên giá trị đọc từ các cảm biến. * Phương pháp 1 (Trọng tâm): Tính vị trí trung bình có trọng số của đường dò. Phương pháp này cho kết quả chính xác hơn. * Phương pháp 2 (One-Hot Encoding): Xác định cảm biến nào đang ở trên đường và gán một giá trị tương ứng (ví dụ: -2, -1, 0, 1, 2). Phương pháp này đơn giản hơn nhưng kém chính xác. * setMotorSpeeds(): * Thực hiện giới hạn gia tốc: Tăng/giảm tốc độ dần dần cho đến khi đạt tốc độ mục tiêu. * Đặt tốc độ cho động cơ. * setup(): * Khởi tạo Serial, Blynk, chân cảm biến, PID, (tùy chọn) encoder. * loop(): * Blynk.run(): Xử lý các sự kiện Blynk. * readSensors(): Gọi hàm đọc cảm biến. * Input = calculateInput(): Tính toán vị trí đường dò. * Kiểm tra xem tất cả cảm biến có nằm ngoài đường không, nếu đúng thì dừng lại. * myPID.Compute(): Tính toán đầu ra PID. * Tính targetLeftSpeed và targetRightSpeed. * setMotorSpeeds(): Gọi hàm để đặt tốc độ (có giới hạn gia tốc). * (Tùy chọn) Gửi dữ liệu lên Blynk và/hoặc Serial Monitor để debug. Hướng Dẫn Mở Rộng: * Encoder: Nếu bạn có encoder, hãy: * Khai báo chân encoder và biến đếm xung. * Viết các hàm ngắt (ISR) để xử lý tín hiệu từ encoder và cập nhật biến đếm xung. * Sử dụng giá trị từ encoder để: * Điều khiển tốc độ chính xác hơn (PID vòng kín cho tốc độ). * Phát hiện trượt bánh. * Thực hiện các chuyển động chính xác. * Nhiều Cảm Biến Hơn: Nếu bạn có nhiều hơn 5 cảm biến, hãy điều chỉnh numSensors và mảng sensorPins. * Thay đổi cảm biến: bạn có thể thay đổi cảm biến analog bằng digital, nếu thay đổi bạn cần chỉnh sửa lại readSensors() và calculateInput().