Robô Seguidor de Linha com IA no Arduino Nano
Componentes e Materiais
| Componente | Quantidade |
|---|---|
| Arduino Nano | 1 |
| Transceptor Bluetooth RF Sem Fio Seeed Master/Slave | 1 |
| Módulo Sensor IR Infravermelho Reflexivo Fotoelétrico (Amazon Web Services) | 1 |
| Servo de Rotação Contínua Sony | 1 |
| Bateria Android 4,8V | 1 |
Aplicativos e Serviços Online
- Arduino IDE
Sobre o Projeto
Após desenvolver o Robô Pick & Place baseado em Arduino, a ideia foi desenvolver um robô seguidor de linha baseado em Inteligência Artificial (IA). Desenvolver um seguidor de linha simples era bom para iniciante, bastava adicionar sensores infravermelhos em vez de servomotores no projeto recente. "A ideia era testar minhas habilidades em nível avançado".
Embora fosse tarefa difícil, pesquisas online ajudaram muito.
Por que Robô Seguidor de Linha Baseado em IA?
Quando se trata de IA, é tecnologia recente com potencial para mudar o ambiente mundial e a vida humana efetivamente. Um sonho que pode se tornar real colocando inteligência em nossos robôs para entender problemas reais e resolvê-los adequadamente - esse sonho só se torna realidade através da Inteligência Artificial, por isso todos falam sobre ela.
- Pode ser usado em linhas de montagem/produção.
- Ônibus de coleta de passageiros.
Embora meu protótipo tenha um pouco de inteligência, é apenas o começo para mover meus projetos rumo à IA. Aqui explico passo a passo o desenvolvimento de robô seguidor de linha usando IA em linguagem simples. Por falta de recursos, não construirei o robô, mas explico bem como você pode construí-lo fazendo pesquisas. Tentarei fornecer códigos precisos que funcionem no Arduino IDE, para que você desenvolva seu primeiro robô IA facilmente.
Apoie-me no Patreon: https://bit.ly/31NfQ6A
"Encontra as taxas mais baratas de equipamentos nos EUA na descrição deste vídeo, clique no vídeo."
Uso de Sensores Infravermelhos
Nosso projeto inclui 7 sensores infravermelhos conforme diagrama:
- 5 para controle PID
- 1 para detecção esquerda
- 1 para detecção direita
Papel dos 5 Sensores de Controle PID: Geram saída digital alta ou baixa (1, 0). O sensor centralizado em relação à linha preta produz HIGH (1). Resultados possíveis:
- 10000, 11000, 01000, 01100, 00100, 00110, 00010, 00011, 00001, 00000, 11111
Outros dois sensores (esquerda e direita): saída analógica alta ou baixa.
Para armazenar valores dos 5 sensores, criamos array:
int LFSensor[5]={1,1,1,1,1};
Para sensores esquerda/direita, usamos inteiros:
int farleft_sensor=0;
Como temos 5 sensores para armazenar caminho esquerdo/direito em array:
LFSensor[0] = digitalRead(lineFollowSensor0);
LFSensor[1] = digitalRead(lineFollowSensor1);
LFSensor[2] = digitalRead(lineFollowSensor2);
LFSensor[3] = digitalRead(lineFollowSensor3);
LFSensor[4] = digitalRead(lineFollowSensor4);
farRightSensor = analogRead(farRightSensorPin);
farLeftSensor = analogRead(farLeftSensorPin);
REGRA DA MÃO ESQUERDA EM LABIRINTOS
Assista este vídeo para entender a regra da mão esquerda em labirintos.
Em resumo, a Regra da Mão Esquerda:
- Coloque sua mão esquerda na parede.
- Comece caminhando para frente.
- Em cada interseção, mantenha a mão esquerda tocando a parede à esquerda.
- Eventualmente chegará ao fim do labirinto. Provavelmente não pelo caminho mais curto, mas chegará.
A chave é identificar as interseções, definindo qual caminho seguir baseado nas regras. Em labirinto 2D, existem 8 tipos de interseções:
Ações possíveis nas interseções:
- Cruzamento: Ir à esquerda, direita ou reto
- T: Ir à esquerda ou direita
- Somente Direita: Ir à direita
- Somente Esquerda: Ir à esquerda
- Reto ou Esquerda: Ir à esquerda ou reto
- Reto ou Direita: Ir à direita ou reto
- Beco Sem Saída: Voltar (meia-volta)
- Fim do Labirinto: Parar
Aplicando a "Regra da Mão Esquerda", ações reduzem a uma opção cada:
- Cruzamento: Ir à esquerda
- T: Ir à esquerda
- Somente Direita: Ir à direita
- Somente Esquerda: Ir à esquerda
- Reto ou Esquerda: Ir à esquerda
- Reto ou Direita: Ir reto
- Beco Sem Saída: Voltar (meia-volta)
- Fim do Labirinto: Parar
Quase lá! "Mantenha a calma!"
Quando robô atinge "Beco Sem Saída" ou "Fim do Labirinto", é fácil identificá-los (já implementamos essas ações no Robô Seguidor de Linha, lembra?). O problema é quando encontra uma "LINHA" - pode ser "Cruzamento" (1) ou "T" (2). Também em "CURVA ESQUERDA/DIREITA", pode ser curva simples (opções 3 ou 4) ou opções de ir reto (5 ou 6). Para descobrir exatamente o tipo de interseção, passo adicional: robô deve andar uma "polada extra" e ver o que vem a seguir.
Fluxo de ações possíveis:
- BECO SEM SAÍDA: Voltar (meia-volta)
- LINHA:
- Andar uma polada extra
- Se há linha: É "Cruzamento" → Ir à ESQUERDA
- Se não há linha: É "T" → Ir à ESQUERDA
- Se há outra linha: É "Fim do Labirinto" → PARAR
- CURVA DIREITA:
- Andar uma polada extra
- Se há linha: É "Reto ou Direita" → Ir RETO
- Se não há linha: É "Somente Direita" → Ir à DIREITA
- CURVA ESQUERDA:
- Andar uma polada extra
- Se há linha: É "Reto ou Esquerda" → Ir à ESQUERDA
- Se não há linha: É "Somente Esquerda" → Ir à ESQUERDA
Nota: No caso de "CURVA ESQUERDA", pode pular o teste, pois irá à esquerda de qualquer forma. Deixei explicação genérica para clareza. No código real, pulo este teste.
Alguns problemas que podem surgir:
Tópico Relacionado: RESOLVENDO PROBLEMA DE DRIVER ARDUINO
Assista este vídeo para resolver problema de driver Arduino.
Código
RoBot_Maze_Solve_2 (Principal)
/*------------------------------------------------------------------
7 sensors Smart Robot - Maze Solver and Line Follower with programable PID controller via BT
==> Basic movement based on Nano Mouse Robot, developed by Michael Backus (https://www.akrobotnerd.com/ )
==> Line follow based on https://samvrit.tk/tutorials/pid-control-arduino-line-follower-robot/?ckattempt=1
==> CREDIT to Patrick McCabe for the path Solving Code, visit patrickmccabemakes.com!!
Marcelo Jose Rovai - 23 April, 2016 - Visit: https://mjrobot.org
-------------------------------------------------------------------*/
#include <Servo.h>
#include "robotDefines.h"
String command;
String device;
#include <SoftwareSerial.h>
SoftwareSerial BT1(10, 11); // Pin 10 Rx, Pin 11 Tx
void setup() {
Serial.begin(9600);
BT1.begin(9600);
pinMode(ledPin, OUTPUT);
pinMode(buttonPin, INPUT_PULLUP);
pinMode(lineFollowSensor0, INPUT);
pinMode(lineFollowSensor1, INPUT);
pinMode(lineFollowSensor2, INPUT);
pinMode(lineFollowSensor3, INPUT);
pinMode(lineFollowSensor4, INPUT);
leftServo.attach(5);
rightServo.attach(3);
BT1.print("check the PID constants to be sent to Robot");
Serial.print("check the PID constants to be sent to Robot");
BT1.println('\n');
while (digitalRead(buttonPin) && !mode) {
checkBTcmd();
manualCmd();
command = "";
}
checkPIDvalues();
mode = STOPPED;
status = 0;
}
void loop() {
ledBlink(1);
BT1.println("Start First Pass");
Serial.println("Start First Pass");
readLFSsensors();
mazeSolve();
ledBlink(2);
BT1.println("End First Pass");
Serial.println("End First Pass");
while (digitalRead(buttonPin) && !mode) {
checkBTcmd();
manualCmd();
command = "";
}
BT1.println("Starting 2nd Pass");
Serial.println("Starting 2nd Pass");
pathIndex = 0;
status = 0;
mazeOptimization();
ledBlink(3);
BT1.println("End 2nd Pass");
Serial.println("End 2nd Pass");
while (digitalRead(buttonPin) && !mode) {
checkBTcmd();
manualCmd();
command = "";
}
mode = STOPPED;
status = 0;
pathIndex = 0;
pathLength = 0;
}
Funções Principais
void mazeSolve(void) {
while (!status) {
readLFSsensors();
switch (mode) {
case NO_LINE: motorStop(); goAndTurn(LEFT, 180); recIntersection('B'); break;
case CONT_LINE: runExtraInch(); readLFSsensors();
if (mode != CONT_LINE) {goAndTurn(LEFT, 90); recIntersection('L');} else mazeEnd(); break;
case RIGHT_TURN: runExtraInch(); readLFSsensors();
if (mode == NO_LINE) {goAndTurn(RIGHT, 90); recIntersection('R');} else recIntersection('S'); break;
case LEFT_TURN: goAndTurn(LEFT, 90); recIntersection('L'); break;
case FOLLOWING_LINE: followingLine(); break;
}
}
}
void recIntersection(char direction) {
path[pathLength] = direction;
pathLength++;
simplifyPath();
}
void mazeEnd(void) {
motorStop();
BT1.print("The End ==> Path: ");
for(int i=0;i "); Serial.println(pathLength);
status = 1; mode = STOPPED;
}
void followingLine(void) { calculatePID(); motorPIDcontrol(); }
void simplifyPath() {
if(pathLength < 3 || path[pathLength-2] != 'B') return;
int totalAngle = 0;
for(int i=1;i<=3;i++) {
switch(path[pathLength-i]) {
case 'R': totalAngle += 90; break;
case 'L': totalAngle += 270; break;
case 'B': totalAngle += 180; break;
}
}
totalAngle = totalAngle % 360;
switch(totalAngle) {
case 0: path[pathLength-3] = 'S'; break;
case 90: path[pathLength-3] = 'R'; break;
case 180: path[pathLength-3] = 'B'; break;
case 270: path[pathLength-3] = 'L'; break;
}
pathLength -= 2;
}
void mazeOptimization(void) {
while (!status) {
readLFSsensors();
switch (mode) {
case FOLLOWING_LINE: followingLine(); break;
case CONT_LINE:
if (pathIndex >= pathLength) mazeEnd();
else {mazeTurn(path[pathIndex]); pathIndex++;} break;
case LEFT_TURN: case RIGHT_TURN:
if (pathIndex >= pathLength) mazeEnd();
else {mazeTurn(path[pathIndex]); pathIndex++;} break;
}
}
}
void mazeTurn(char dir) {
switch(dir) {
case 'L': goAndTurn(LEFT, 90); break;
case 'R': goAndTurn(RIGHT, 90); break;
case 'B': goAndTurn(RIGHT, 800); break;
case 'S': runExtraInch(); break;
}
}
Funções Gerais
void ledBlink(int times) {
for (int i = 0; i < times; i++) {
digitalWrite(ledPin, HIGH); delay(500);
digitalWrite(ledPin, LOW); delay(500);
}
}
void checkBTcmd() {
while (BT1.available()) {
delay(10);
char c = BT1.read();
device += c;
}
if (device.length() > 0) {
Serial.print("Command received from BT ==> "); Serial.println(device);
command = device; device = ""; BT1.flush();
}
}
void manualCmd() {
switch (command[0]) {
case 'g': mode = FOLLOWING_LINE; break;
case 's': motorStop(); break;
case 'f': motorForward(); break;
case 'r': motorTurn(RIGHT, 30); motorStop(); break;
case 'l': motorTurn(LEFT, 30); motorStop(); break;
case 'b': motorBackward(); break;
case 'p': Kp = command[2]; break;
case 'i': Ki = command[2]; break;
case 'd': Kd = command[2]; break;
}
}
void sendBTdata(int data) {
digitalWrite(ledPin, HIGH);
BT1.print("Data from Arduino"); BT1.print(data); BT1.print(" xx"); BT1.println('\n');
digitalWrite(ledPin, LOW);
}
void calculatePID() {
P = error; I = I + error; D = error - previousError;
PIDvalue = (Kp*P) + (Ki*I) + (Kd*D);
previousError = error;
}
void checkPIDvalues() {
BT1.print("PID: "); BT1.print(Kp); BT1.print(" - "); BT1.print(Ki); BT1.print(" - "); BT1.println(Kd);
Serial.print("PID: "); Serial.print(Kp); Serial.print(" - "); Serial.print(Ki); Serial.print(" - "); Serial.println(Kd);
}
void testLineFollowSensors() {
int LFS0 = digitalRead(lineFollowSensor0);
int LFS1 = digitalRead(lineFollowSensor1);
int LFS2 = digitalRead(lineFollowSensor2);
int LFS3 = digitalRead(lineFollowSensor3);
int LFS4 = digitalRead(lineFollowSensor4);
Serial.print("LFS: L 0 1 2 3 4 R ==> ");
Serial.print(LFS0); Serial.print(" "); Serial.print(LFS1); Serial.print(" ");
Serial.print(LFS2); Serial.print(" "); Serial.print(LFS3); Serial.print(" "); Serial.print(LFS4);
Serial.print(" ==> P: "); Serial.print(P); Serial.print(" I: "); Serial.print(I);
Serial.print(" D: "); Serial.print(D); Serial.print(" PID: "); Serial.println(PIDvalue);
}
Funções de Sensores
void readLFSsensors() {
LFSensor[0] = digitalRead(lineFollowSensor0);
LFSensor[1] = digitalRead(lineFollowSensor1);
LFSensor[2] = digitalRead(lineFollowSensor2);
LFSensor[3] = digitalRead(lineFollowSensor3);
LFSensor[4] = digitalRead(lineFollowSensor4);
farRightSensor = analogRead(farRightSensorPin);
farLeftSensor = analogRead(farLeftSensorPin);
if ((LFSensor[0]==1)&&(LFSensor[1]==1)&&(LFSensor[2]==1)&&(LFSensor[3]==1)&&(LFSensor[4]==1)) {mode = CONT_LINE; error = 0;}
else if((LFSensor[0]==0)&&(farRightSensor < THRESHOLD)) {mode = RIGHT_TURN; error = 0;}
else if((farLeftSensor < THRESHOLD)&&(LFSensor[4]==0)) {mode = LEFT_TURN; error = 0;}
else if((LFSensor[0]==0)&&(LFSensor[1]==0)&&(LFSensor[2]==0)&&(LFSensor[3]==0)&&(LFSensor[4]==0)) {mode = NO_LINE; error = 0;}
else if((LFSensor[0]==0)&&(LFSensor[1]==0)&&(LFSensor[2]==0)&&(LFSensor[3]==0)&&(LFSensor[4]==1)) {mode = FOLLOWING_LINE; error = 4;}
else if((LFSensor[0]==0)&&(LFSensor[1]==0)&&(LFSensor[2]==0)&&(LFSensor[3]==1)&&(LFSensor[4]==1)) {mode = FOLLOWING_LINE; error = 3;}
else if((LFSensor[0]==0)&&(LFSensor[1]==0)&&(LFSensor[2]==0)&&(LFSensor[3]==1)&&(LFSensor[4]==0)) {mode = FOLLOWING_LINE; error = 2;}
else if((LFSensor[0]==0)&&(LFSensor[1]==0)&&(LFSensor[2]==1)&&(LFSensor[3]==1)&&(LFSensor[4]==0)) {mode = FOLLOWING_LINE; error = 1;}
else if((LFSensor[0]==0)&&(LFSensor[1]==0)&&(LFSensor[2]==1)&&(LFSensor[3]==0)&&(LFSensor[4]==0)) {mode = FOLLOWING_LINE; error = 0;}
else if((LFSensor[0]==0)&&(LFSensor[1]==1)&&(LFSensor[2]==1)&&(LFSensor[3]==0)&&(LFSensor[4]==0)) {mode = FOLLOWING_LINE; error = -1;}
else if((LFSensor[0]==0)&&(LFSensor[1]==1)&&(LFSensor[2]==0)&&(LFSensor[3]==0)&&(LFSensor[4]==0)) {mode = FOLLOWING_LINE; error = -2;}
else if((LFSensor[0]==1)&&(LFSensor[1]==1)&&(LFSensor[2]==0)&&(LFSensor[3]==0)&&(LFSensor[4]==0)) {mode = FOLLOWING_LINE; error = -3;}
else if((LFSensor[0]==1)&&(LFSensor[1]==0)&&(LFSensor[2]==0)&&(LFSensor[3]==0)&&(LFSensor[4]==0)) {mode = FOLLOWING_LINE; error = -4;}
Serial.print(farLeftSensor); Serial.print(" <== LEFT RIGH==> "); Serial.print(farRightSensor);
Serial.print(" mode: "); Serial.print(mode); Serial.print(" error:"); Serial.println(error);
}
void readLateralSensors(void) { }
RobotDefines.h
int mode = 0;
#define STOPPED 0
#define FOLLOWING_LINE 1
#define NO_LINE 2
#define CONT_LINE 3
#define POS_LINE 4
#define RIGHT_TURN 5
#define LEFT_TURN 6
const int power = 250;
const int iniMotorPower = 250;
const int adj = 0;
float adjTurn = 8;
int extraInch = 200;
int adjGoAndTurn = 800;
const int ledPin = 13;
const int buttonPin = 9;
const int lineFollowSensor0 = 12;
const int lineFollowSensor1 = 18;
const int lineFollowSensor2 = 17;
const int lineFollowSensor3 = 16;
const int lineFollowSensor4 = 19;
const int farRightSensorPin = 0; // A0
const int farLeftSensorPin = 1; // A1
const int THRESHOLD = 150;
int farRightSensor = 0;
int farLeftSensor = 0;
int LFSensor[5] = {0,0,0,0,0};
float Kp=50, Ki=0, Kd=0;
float error=0, P=0, I=0, D=0, PIDvalue=0;
float previousError=0, previousI=0;
#define RIGHT 1
#define LEFT -1
Servo leftServo; Servo rightServo;
unsigned char dir;
char path[100] = "";
unsigned char pathLength = 0;
int pathIndex = 0;
unsigned int status = 0;
Funções de Motor
void motorStop() {
leftServo.writeMicroseconds(1500);
rightServo.writeMicroseconds(1500);
delay(200);
}
void motorForward() {
leftServo.writeMicroseconds(1500 - (power+adj));
rightServo.writeMicroseconds(1500 + power);
}
void motorBackward() {
leftServo.writeMicroseconds(1500 + power);
rightServo.writeMicroseconds(1500 - power);
}
void motorFwTime(unsigned int time) { motorForward(); delay(time); motorStop(); }
void motorBwTime(unsigned int time) { motorBackward(); delay(time); motorStop(); }
void motorTurn(int direction, int degrees) {
leftServo.writeMicroseconds(1500 - (iniMotorPower+adj)*direction);
rightServo.writeMicroseconds(1500 - iniMotorPower*direction);
delay(round(adjTurn*degrees+20));
motorStop();
}
void motorPIDcontrol() {
int leftMotorSpeed = 1500 - (iniMotorPower+adj) - PIDvalue;
int rightMotorSpeed = 1500 + iniMotorPower - PIDvalue;
constrain(leftMotorSpeed, 1000, 2000);
constrain(rightMotorSpeed, 1000, 2000);
leftServo.writeMicroseconds(leftMotorSpeed);
rightServo.writeMicroseconds(rightMotorSpeed);
}
void runExtraInch(void) { motorPIDcontrol(); delay(extraInch); motorStop(); }
void goAndTurn(int direction, int degrees) {
motorPIDcontrol(); delay(adjGoAndTurn); motorTurn(direction, degrees);
}
Esquemáticos
Esquemático do circuito disponível na seção de esquemáticos do projeto original.
Processo de manufatura
- Construa um Robô que Evita Paredes com Raspberry Pi – Guia Passo a Passo
- Robô Móvel Evita-Obstáculos Baseado em Visão no Raspberry Pi
- Robô Empunhando Pincel da Star Automation Brilha na NPE2018
- Wittmann Battenfeld Lança Linha de Robôs WX153 para Automação de Injeção de Alta Força
- Bosch Apresenta Solução de Manufatura Impulsionada por IA na Hannover Messe
- FANUC Expande Linha SCARA: Seis Modelos, Cargas de 3–20 kg, Alcance de 400–1100 mm
- Inteligência Artificial no Varejo: Benefícios Comprovados, Não Moda Passageira
- IA Capacita Robôs a Identificar 90+ Objetos pelo Tato
- Robótica Impulsionada por IA: Máquinas Inteligentes de Ponta
- IA: Benefícios, Riscos e Impacto na Indústria