Construa seu robô que desvia de obstáculos com Arduino 5

Robôs chamam muito a atenção, afinal são um dos poucos “adereços tecnológicos” que parecem ganhar vida quando em operação. E se o robô fosse capaz de desviar sozinho de obstáculos? Legal, né? É justamente o que você aprenderá a fazer aqui neste post!

Você aprenderá a fazer seu próprio robô autônomo, capaz de desviar sozinho de obstáculos e sair de labirintos.

Material necessário

Para fazer este projeto, você precisará de:

Visão geral do robô que desvia de obstáculos

Este projeto consiste de um robô de duas rodas capaz de, sozinho, desviar de obstáculos localizados a sua frente. Ou seja, o robô move-se e desvia de obstáculos por conta própria.

Os obstáculos são detectados a partir de um sensor de distância ultrassônico, capaz de informar a distância entre o robô e obstáculos à frente do mesmo, detectando quaisquer objetos em um range de 2 centímetros até 4 metros de distância do robô.

Desta forma, quando o robô está muito próximo de um obstáculo (neste projeto, muito próximo significa estar a 10 centímetros de distância do robô), este gira para esquerda ou direita até encontrar um caminho livre. Um caminho livre é um caminho sem obstáculos ou com obstáculos localizados a mais de 10 centímetros de distância. Uma vez encontrado um caminho livre, o robô segue em frente.

Para que lado girar quando localizar um obstáculo?

Em caso de o robô encontrar um obstáculo à frente, localizado a 10 ou menos centímetros de distância, este vai girar buscando um caminho livre, conforme já descrito neste post. Porém, como o robô pode decidir para que sentido deve girar (horário ou anti-horário)?

Neste caso, há duas estratégias possíveis:

  1. Girar sempre em um só sentido (horário ou anti-horário): esta estratégia é a mais simples e intuitiva possível.
    Porém, se o robô se encontra em um labirinto, por exemplo, ele pode demorar mais para desviar dos obstáculos, uma vez que achar o caminho livre girando somente para um dos sentidos (horário ou anti-horário) pode fazê-lo girar bastante até encontrá-lo.
    Ainda, dependendo da situação em que se encontra e dos obstáculos encontrados, nesta estratégia o robô pode achar como caminho livre o caminho de onde acabou de vir, causando assim uma espécie de “regressão”, como se o robô desse um passo atrás na solução do labirinto, por exemplo.
  2. Girar em sentido horário ou anti-horário, de forma alternada: aqui, a cada obstáculo encontrado, o robô alterna o sentido de giro. Por exemplo: se no obstáculo anterior ele girou em sentido horário, no próximo obstáculo girará no sentido anti-horário, e vice-versa. Desta forma, a chance do robô regredir na solução de um labirinto é minimizada e a chance de perder tempo girando para o lado errado também é menor.

Considerando as estratégias acima e consequências das mesmas, o robô deste projeto seguirá a estratégia de girar em sentidos alternados (estratégia 2).

Circuito esquemático

O circuito esquemático do robô pode ser visto na figura 1.

Circuito do robô
Figura 1 – Circuito esquemático do projeto

Importante: pelo fato dos motores exigirem muita corrente elétrica para funcionarem (em comparação a corrente exigida pelo Arduino Nano apenas), não se esqueça de remover o fio que liga +5 V ao Vin do Arduino Nano antes da programação do mesmo. Assim, não há o risco de um ou ambos motores ligarem e utilizarem a alimentação da USB do computador, o que pode levar a sérios danos ao computador.
Após a programação do Arduino Nano, pode-se ligar o fio que liga +5 V ao Vin do Arduino Nano novamente.

Além disso, como estamos alimentando o módulo de ponte H L298N com os 5 V fornecidos pelo powerbank e, ainda, este projeto não possui controle de velocidade de giro dos motores, é fundamental que:

  • Os jumpers ATIVA MA e ATIVA MB estejam presentes (colocar jumpers)
  • O jumper ATIVA 5 V esteja ausente (retirar jumper)

Para referências de quais são estes jumpers e onde se localizam no módulo, veja a figura 2.

Ponte H usada para o projeto
Figura 2 – Jumpers e pinos da Ponte H L298N

Montagem do robô que desvia de obstáculos

Para te ajudar a organizar os componentes e fios, segue abaixo algumas fotos do robô deste post já montado.

Parte da frente do robô que desvia de obstáculos
Figura 3 – Foto frontal do robô
Parte superior do robô que desvia de obstáculos
Figura 4 – Foto superior do robô
Parte superior com destaque nas ligações do robô que desvia de obstáculos
Figura 5 – Foto superior com destaque para as ligações
Robô que desvia de obstáculos montado
Figura 6 – Foto do robô montado

Código-fonte do robô que desvia de obstáculos

Segue abaixo o código-fonte do projeto. Leia atentamente seus comentários para total compreensão do mesmo.

#include <Ultrasonic.h>

/* Definições dos GPIOs para leitura do sensor ultrasonico */
#define GPIO_TRIGGER     12
#define GPIO_ECHO        11

/* Definições de operação do sensor ultrasônico */
#define DISTANCIA_MINIMA_CM                10.0 //cm
#define TEMPO_ENTRE_LEITURAS_DE_DISTANCIA  250  //ms

/* Definições para controle dos dois motores */
#define IN_1      3
#define IN_2      4
#define IN_3      5
#define IN_4      6

/* Definições dos motores a serem controlados */
#define MOTOR_A                      0x00
#define MOTOR_B                      0x01

/* Definições das ações dos motores */
#define ACAO_FREIO                   0x00
#define ACAO_MOVIMENTO_ANTI_HORARIO  0x01
#define ACAO_MOVIMENTO_HORARIO       0x02
#define ACAO_PONTO_MORTO             0x03

/* Definições de sentido de giro (em caso de obstáculo) */
#define SENTIDO_GIRO_ANTI_HORARIO    0x00
#define SENTIDO_GIRO_HORARIO         0x01

/* Definições do desvio de objetos */
#define ESTADO_AGUARDA_OBSTACULO     0x00
#define ESTADO_GIRANDO               0x01

/* Variáveis e objetos globais */
Ultrasonic ultrasonic(GPIO_TRIGGER, GPIO_ECHO);
char ultimo_lado_que_girou = SENTIDO_GIRO_ANTI_HORARIO;
char estado_desvio_obstaculos = ESTADO_AGUARDA_OBSTACULO;

/* Protótipos */
void configura_gpios_controle_motor(void);
void controla_motor(char motor, char acao);
float le_distancia_sensor_ultrasonico(void);
void maquina_estados_desvio_obstaculos(float distancia_obstaculo);

/* Função: configura GPIOs de controle do L298N como output
 * Parâmetros: nenhum
 * Retorno: nenhum
 */
void configura_gpios_controle_motor(void)
{
    pinMode(IN_1, OUTPUT);
    pinMode(IN_2, OUTPUT);
    pinMode(IN_3, OUTPUT);
    pinMode(IN_4, OUTPUT);
}

/* Função: controle um motor (freia, movimento anti-horário, movimento horário
 *         ou ponto morto)
 * Parâmetros: motor a ser controlado e ação desejada
 * Retorno: nenhum
 */
void controla_motor(char motor, char acao)
{
    int gpio_1_motor = 0;
    int gpio_2_motor = 0;

    /* seleciona os GPIOs de acordo com o motor desejado */
    switch(motor)
    {
        case MOTOR_A:
            gpio_1_motor = IN_1;
            gpio_2_motor = IN_2;
            break;
    
        case MOTOR_B:
            gpio_1_motor = IN_3;
            gpio_2_motor = IN_4;
            break;

        default:
            /* Motor inválido. Nada mais deve ser feito nesta função */
            return;            
    }

    /* Controla o motor conforme ação desejada */
    switch(acao)
    {
        case ACAO_FREIO:
            digitalWrite(gpio_1_motor, HIGH);
            digitalWrite(gpio_2_motor, HIGH);
            break;

        case ACAO_MOVIMENTO_ANTI_HORARIO:
            digitalWrite(gpio_1_motor, LOW);
            digitalWrite(gpio_2_motor, HIGH);
            break;

        case ACAO_MOVIMENTO_HORARIO:
            digitalWrite(gpio_1_motor, HIGH);
            digitalWrite(gpio_2_motor, LOW);
            break;

        case ACAO_PONTO_MORTO:
            digitalWrite(gpio_1_motor, LOW);
            digitalWrite(gpio_2_motor, LOW);
            break;

        default:
            /* Ação inválida. Nada mais deve ser feito nesta função */
            return;                                                            
    }    
}

/* Função: faz leitura da distância (em centímetros) de obstáculo a frente do robô
 * Parâmetros: nenhum
 * Retorno: distância (cm)
 */
float le_distancia_sensor_ultrasonico(void)
{
    float distancia_cm = 0.0;
    long microsec = 0;
    
    microsec = ultrasonic.timing();
    distancia_cm = ultrasonic.convert(microsec, Ultrasonic::CM);
    return distancia_cm;
}

/* Função: maquina de estado responsavel por controlar o desvio de obstáculos
 * Parâmetros: distância de obstáculo a frente
 * Retorno: nenhum
 */
void maquina_estados_desvio_obstaculos(float distancia_obstaculo)
{
    switch(estado_desvio_obstaculos)
    {
        case ESTADO_AGUARDA_OBSTACULO:
            if (distancia_obstaculo <= DISTANCIA_MINIMA_CM)
            {
                /* Obstáculo encontrado. O robô deve girar para
                   desviar dele */
                Serial.println("[MOVIMENTO] Obstaculo encontrado!");   
                
                /* Alterna sentido de giro para se livrar de obstáculos
                   (para otimizar o desvio de obstáculos) */
                if (ultimo_lado_que_girou == SENTIDO_GIRO_ANTI_HORARIO)
                    ultimo_lado_que_girou = SENTIDO_GIRO_HORARIO;
                else
                    ultimo_lado_que_girou = SENTIDO_GIRO_ANTI_HORARIO;
                    
                estado_desvio_obstaculos = ESTADO_GIRANDO; 
            }
            else
            {
                Serial.println("[MOVIMENTO] Sem obstaculos a frente");
                
                /* Se não há obstáculos, continua em frente */
                controla_motor(MOTOR_A, ACAO_MOVIMENTO_HORARIO);
                controla_motor(MOTOR_B, ACAO_MOVIMENTO_HORARIO);
            }
            
            break;

        case ESTADO_GIRANDO: 
            if (distancia_obstaculo > DISTANCIA_MINIMA_CM)
            {
                /* Não há mais obstáculo a frente do robô */   
                estado_desvio_obstaculos = ESTADO_AGUARDA_OBSTACULO; 
            }
            else
            {
                if (ultimo_lado_que_girou == SENTIDO_GIRO_ANTI_HORARIO)
                {
                    controla_motor(MOTOR_A, ACAO_MOVIMENTO_ANTI_HORARIO);
                    controla_motor(MOTOR_B, ACAO_MOVIMENTO_HORARIO);
                    Serial.println("[MOVIMENTO] Girando no sentido anti-horario...");
                }
                else
                {
                    controla_motor(MOTOR_A, ACAO_MOVIMENTO_HORARIO);
                    controla_motor(MOTOR_B, ACAO_MOVIMENTO_ANTI_HORARIO);
                    Serial.println("[MOVIMENTO] Girando no sentido horario...");
                }
            }
            
            break;
    }
}

void setup() 
{
    Serial.begin(115200);
    
    /* Configura GPIOs de controle do L298N como output e coloca motor em condição de freio */
    configura_gpios_controle_motor();    
    controla_motor(MOTOR_A, ACAO_FREIO);
    controla_motor(MOTOR_B, ACAO_FREIO);
}

void loop() 
{
    float distancia_a_frente = 0.0;

    distancia_a_frente = le_distancia_sensor_ultrasonico();
    Serial.print("* Distancia lida: ");
    Serial.print(distancia_a_frente);
    Serial.println("cm");

    /* Verifica se há obstáculo a frente */
    maquina_estados_desvio_obstaculos(distancia_a_frente);

    delay(TEMPO_ENTRE_LEITURAS_DE_DISTANCIA);
}

Recapitulando: o robô vai em frente até encontrar um obstáculo, localizado a 10 ou menos centímetros a sua frente. Após isso, gira em sentido horário ou anti-horário (sentidos alternados, conforme estratégia adotada) até que consiga um caminho livre (sem obstáculos ou com obstáculo a mais de 10 centímetros a frente). Os giros em sentidos alternados visam aumenta a eficiência nos desvios de obstáculos.

Gostou deste post sobre como construir seu próprio robô que desvia de obstáculos? Deixe seu comentário logo abaixo.

Faça seu comentário

Acesse sua conta e participe

5 Comentários

  1. Esse projeto serve para arduíno uno

    1. Olá Valdinei.

      Funciona, porém, será necessário efetuar algumas mudanças tanto no código quanto no esquema elétrico.

      Att.
      Vitor Mattos.
      Suporte Técnico MakerHero.

  2. ola pessoal, estou tendo esse erro ao compilsr
    slgum pode me ajudar ?

    exit status 1
    ‘unsigned int Ultrasonic::timing()’ is private within this context

  3. Outra forma de programação interessante, pois tenho uma versão com uso de servo, na qual realiza a leitura de distânçia.

  4. microsec = ultrasonic.timing();
    nessa linha de programação esta dando este erro

    unsigned int Ultrasonic::timing()’ is private within this context