Tabela de conteúdos

Relatório Final

Introdução

Este documento descreve a montagem e desempenho do robô HAL (grupo 7) autônomo desenvolvido por meio dos conhecimentos adquiridos durante o curso de Introdução à Robótica ministrado pelo Departamendo de Ciência da Computação da Universidade Federal de Minas Gerais (DCC-UFMG) no 2º semestre de 2011 em uma competição que marca o final do curso.

Na conclusão temos uma discussão sobre o desempenho do robô durante a competição.

Competição

A competição consiste em 2 robôs em lados opostos de um campo retangular na direção do maior lado deste. O campo contém uma cavidade na parte central na direção do menor lado deste dividindo o campo em 2 metades iguais. Uma rampa de aproximadamente 60 cm e inclinação de 18º une as 2 metades. Em cada metade temos: 4 paralelepípedos retos (torre) de aproximadamente 18 cm e um cubo (soldado) de lado aproximado de 6 cm sobre cada um destes (em um lado estes cubos são verdes enquanto que no outro, estes são azuis); um cubo (rei) sobre a superfície do campo em uma posição aleatória (coloração vermelha para um lado e amarela para o outro lado); uma luz polarizada (verticalmente para um lado e verticalmente para o outro) sobre a metade do lado menor do campo; uma luz de partida sobre a superfície do campo. Duas áreas nas laterais de cada metade delimitam uma região nomeada prisão.

A competição é composta por 2 rodadas iniciais e 3 chaves de 3 grupos cada. Na primeira rodada, cada grupo disputa com outro grupo de mesma chave em lados alternando os lados do campo. Na segunda rodada, cada grupo disputa com um grupo escolhido das demais chaves. A cada vitória, um grupo adquire 3 pontos, empate 1 ponto, derrota 0 pontos, -1 ponto se o robô não sair de sua base. A final é composta pelos 2 grupos que acumularem mais pontos em cada rodada. Uma disputa entre 2 grupos consiste em 1 minuto de ação entre os robôs (contados uma vez que a luz de partida em cada lado do campo seja acionada) e uma vez este tempo transcorrido, o grupo referênte ao robô de maior pontuação é declarado vencedor. A pontuação é feita contabilizando torre adversária derrubada (2 pontos), soldado adversário preso ao robô (1), soldado adversário na própria prisão (2), rei adversário preso ao robô (8), rei adversário na própria prisão (10), próprio rei fora do campo (-5), torre adversária na própria prisão (-3), torre adversária presa no robô (-1).

 Campo da Competição - Imagem por Matheus Caldas Santos

A apresentação feita para a competição: apresentacao.pdf

Estratégia

Uma vez definida e estudada a competição, uma boa estratégia deve fornecer a maior pontuação evitando conflitos com o robô oponente. Dessa forma imaginamos inicialmente que nosso robô pudesse disparar objetos contra as torres inimigas. Posteriormente imaginamos uma forma mais interessante de que o robô pudesse atravessar o de um lado para o outro sem utilizar a rampa. Por razões de complexidade e problemas observados durante as tentativas de desenvolver estas estratégias, escolhemos por fim uma abordagem mais simples:

  1. orientar-se pela luz polarizada;
  2. alinhar-se com o lado menor do quadrado e no sentido da rampa;
  3. andar para frente por 2,4 metros (suficientes para o próximo campo?);
  4. girar 90º e andar 60 cm repetindo este passo até que o tempo de 1 minuto da competição acabe;

Em qualquer situação anterior, temos as seguintes condições:

Montagem do Robô

O robô possui 2 motores Pololu (35:1 Metal Gearmotor 15.5Dx30L mm), 1 motor disponibilizado pelo kit lego dacta, 1 motor de especificações desconhecidas, 2 sensores de rotação (1 para cada motor), 2 sensores de toque na parte frontal inferior, 2 chaves optico reflexivas na parte inferior, 1 sensor de cor (led RGB + ldr), 1 sensor de luz polarizada, 1 sensor de partida, 1 sensor de obstrução de feixe e 1 HandyBoard.

Especificação

Altura: 21,0cm;
Comprimento: 30,0cm;
Largura: 19,0cm;
Eixo traseiro: 16,5cm;
Distância entre eixos: 13,0cm;
Diâmetro rodas traseiras: 8,0cm;
Diâmetro roda dianteira: 2,5cm.

Sensores

Partida

Composto apenas de um grande LDR (em vermelho na figura) e ligado sobre uma porta analógica, a luz de partida sobre o sensor leva o valor lido pela porta analógica a praticamente zero.

partida.jpg

Rotação

Cada em direção a uma roda de 6 furos presa ao eixo de rotação dos motores Pololu que movimentam o robô e ligados em portas analógicas como mostrado em vermelho na figura a seguir.

encoder.jpg

Toque

Simples botão ao qual foi anexado uma pequena barra de alumínio fazendo uma alavanca em formato de concha e ligado a uma porta digital. Cada um foi montado na parte frontal do robô e em contato com a superfície de forma que esteja acionado enquanto este permanecer em contato com aquela.

toque.jpg

Chave Optico Reflexiva

Composto de 1 led infravermelho e um receptor infravermelho presos no interior de uma peça 2×1 lego e ligados em uma porta digital como mostrado em vermelho na figura.

linha.jpg

Luz Polarizada

Composto de 2 resistências variáveis por luminosidade envoltas de peças lego e um filtro de luz polarizada, cada um em uma orientação. As resistências foram ligadas em série sendo que entre elas foi adicionada uma saída para leitura do sinal analógico na HandyBoard como mostrado em branco na figura.

luz.jpg

Obstrução de Feixe

Composto de um led infravermelho e um receptor infravermelho presos em peças lego 2×1 diferentes como mostrado em vermelho na figura. Este conjunto foi ligado a uma porta digital.

beam.jpg

Cor

Composto de um led RGB e uma resistência variável por luminosidade ligada a uma porta analógica como indicado em branco na figura. A idéia é utilizar as cores verde, vermelho e azul em intervalos de tempos disjuntos e medir a resposta pela resistência. Pode ser também usado para indicar se há um objeto na frente do sensor acendendo e apagando o led em certa frequência e medindo a diferença do sinal da resistência. Uma grande diferênça entre estes sinais indica reflexão da luz do led em um objeto próximo à resistência.

cor.jpg

Motores

O robô contém 4 motores: 2 responsáveis pelo movimento do robô pelo campo e os demais responsáveis pela captura dos blocos (rei e soldados).

Movimento

Cada motor é ligado a um redutor helicoidal e cada um destes é conectado a uma engrenagem de 24 dentes. O eixo desta contém ainda uma engrenagem de 40 dentes que conecta a outra engrenagem de 24 dentes. Por fim, ao eixo desta última engrenagem, colocamos 2 rodas como indicado na figura. A redução total do motor à roda é de 5/72 (O mesmo que o produto das razões, i.e., (1/24)*(40/24).)

reducao.jpg

Captura de Bloco Superior

O motor é ligado diretamente a uma barra transversal que tende a empurrar blocos na parte superior da torre para dentro de uma região fechada do robô como indicado na figura. A haste que sustenta a barra gira livremente no sentido anti-horário impedindo que a barra desloque inicialmente no sentido horário mas permitindo qualquer movimento no sentido anti-horário.

cima.jpg

Captura de Bloco Inferior

O motor é ligado a duas engrenagens que transmitem o torque da posição fixa do motor até uma barra giratória ao mesmo tempo que fornece uma redução de 1/40. Outra barra fixa na parte posterior a barra giratória faz com que a barra giratória prenda um bloco que esteja entre estas duas.

baxo.jpg

Código

Controle PD

O controle PD é constituído de 1 função responsável pelo controle de velocidade entre as rodas de movimento do robô e 3 funções básicas: fwd, que recebe a distância a ser percorrida pelo robô como primeiro parâmetro e a diferença entre as velocidades das rodas direita e esquerda (números positivos indicam movimento curvilíneo no sentido anti-horário enquanto negativos indicam movimentos no sentido horário); bkw, que funciona da mesma forma que fwd com a única diferença de que o robô desloca-se para trás; e turn, que recebe um parâmetro que indica o ângulo que o robô deve virar em torno de seu eixo. Várias macros fazem conversões entre distâncias e ângulos para pulsos nos sensores de rotação e vice-versa.

Um estudo mais detalhado sobre o funcionamento deste controle pode ser encontrado nos trabalhos anteriores. Trabalho sobre controle PD

//Motor Control
#define PERIMETER (25.1327)
#define SPINCOUNTS (172.8)
#define PD_PGAIN 5.0
#define PD_DGAIN 1.0
#define MOTOR_POWER 80
#define MOTOR_MIN_POWER 40
#define CM2COUNTS(x) (int)(((float)(x)/47.0)*550.0)
#define COUNTS2CM(x) (int)(((float)(x)*47.0)/550.0)
#define ANGLE2COUNTS(x) (int)((float)(x)*SPINCOUNTS/180.0) - 5
#define COUNTS2ANGLE(x) (int)((float)COUNTS2CM(x)/0.067)
#define INF (-1)
 
int _setPower(int oldPower, int var){
 
    if(oldPower > 0){
        if((oldPower + var) > 100) return 100;
        else if((oldPower + var) < MOTOR_MIN_POWER) return MOTOR_MIN_POWER;
        else return oldPower + var;
    }
    else if(oldPower < 0){
        if((oldPower - var) < -100) return -100;
        else if((oldPower - var) > -MOTOR_MIN_POWER) return -MOTOR_MIN_POWER;
        else return oldPower - var;
    }
    else return 0;
}
 
void _pdControl(int counts, int power, int angle){
 
    int error, last_error;
    int derivative;
    int output, left, right;
 
    if(angle > (100-MOTOR_POWER)){
        left  = -power;
        right = power;
        angle = 0;
    }
    else if(angle < (MOTOR_POWER-100)){
        left  = power;
        right = -power;
        angle = 0;
    }
    else{
        left  = power - angle;
        right = power + angle;
    }
 
    motor(MOTOR_LEFT,  left);
    motor(MOTOR_RIGHT, right);
 
 
    last_error = 0;
    while( (COUNTS_LEFT < counts && COUNTS_RIGHT < counts) || (counts == INF)){
 
        sleep(0.3);
 
        last_error = error;
        error = VELOCITY_LEFT - VELOCITY_RIGHT + angle;
        derivative = error - last_error;
        output = (int)((PD_PGAIN*(float)error) - (PD_DGAIN*(float)derivative));
 
        left  = _setPower(left,  -output);
        right = _setPower(right, output);
 
        if(left && right){
            motor(MOTOR_LEFT,  left);
            motor(MOTOR_RIGHT, right);
        }
    }
    stop();
}
 
void stop(){
    off(MOTOR_LEFT);
    off(MOTOR_RIGHT);
}
 
void fwd(int distance, int angle){    
    int counts;
 
    if(angle > 100-MOTOR_POWER)      angle = 100-MOTOR_POWER;
    else if(angle < MOTOR_POWER-100) angle = MOTOR_POWER-100;
 
    if(distance != INF) counts = CM2COUNTS(distance);
 
    stop();
    _pdControl(counts, MOTOR_POWER, angle);
}
 
void bkw(int distance, int angle){
    int counts;
 
    if(angle > 100-MOTOR_POWER)      angle = 100-MOTOR_POWER;
    else if(angle < MOTOR_POWER-100) angle = MOTOR_POWER-100;
 
    if(distance != INF) counts = CM2COUNTS(distance);
 
    stop();
    _pdControl(counts, -MOTOR_POWER, angle);
}
 
void turn(int angle){
 
    stop();
    if(angle > 0)      _pdControl(ANGLE2COUNTS(angle),  MOTOR_POWER, 100);
    else if(angle < 0) _pdControl(-ANGLE2COUNTS(angle), MOTOR_POWER, -100);
}

Calibração e Orientação por luz Polarizada

Para orientação pela luz polarizada, o robô deve inicialmente (estado de calibração representado pela função calibrate_light) rodar em torno de seu eixo e a cada 20º ler o valor do sensor de luz polarizada. O menor ou maior valor, dependendo da orientação da luz, é então armazenado e uma vez a competição iniciada, o robô repete a mesma ação procurando o mesmo valor (com certa margem) no campo (função light_positioning).

#include "fencdr5.icb"
#include "fencdr6.icb"
#include "motor.ic"
 
//Inputs
#define COLOR (analog(2))
#define DIFF_EYE (analog(3))
#define STARTUP_SENSOR (analog(4))
 
//Color
#define START_COLOR poke(0x1009,0x3c)
#define COLOR_R poke(0x1008,1<<5)
#define COLOR_G poke(0x1008,1<<4)
#define COLOR_B poke(0x1008,1<<3)
#define COLOR_ALL poke(0x1008,7<<3)
#define COLOR_OFF poke(0x1008,0)
#define OBJECT_LIMIAR (30)
#define EX_TIME (1.0)
#define LIGHT_GREEN 0
#define LIGHT_BLUE 1
#define THRESHOLD 15
 
//Global variables
persistent int light_ref=-1;
persistent int light_color=-1;
 
//Functions
 
void calibrate_light(){
    int i, diff, menor=DIFF_EYE, maior=DIFF_EYE, div = 255/2;
 
    while(1){
        if(knob() > 1*div && knob() <= 2*div){
            printf("Green Light!\n");
            light_color = LIGHT_GREEN;
        }
        if(knob() > 0*div && knob() <= 1*div){
            printf("Blue Light!\n");
            light_color = LIGHT_BLUE;
        }
        if( start_button() ) {
            break;
        }
        sleep(0.2);
    }
 
    for(i=0; i<40; i++){
        sleep(0.2);        
        diff = DIFF_EYE;         
        if(diff < menor) menor = diff;        
        if(diff > maior) maior = diff; 
        turn(20, LEFT);        
    }    
 
    if(light_color == LIGHT_BLUE) light_ref = menor;
    else light_ref = maior;
}
 
void light_positioning()
{  
    int diff, menor, maior, i, j;
 
    while(1){
        menor=DIFF_EYE;
        maior=DIFF_EYE;
        for(i=0; i<30; i++){
 
            diff = DIFF_EYE; 
            printf("Ref: %d Diff %d\n", light_ref, diff);
 
            if(light_color == LIGHT_BLUE && diff <= light_ref+THRESHOLD) return;
            if(light_color == LIGHT_GREEN && diff >= light_ref-THRESHOLD) return;  
 
            if(diff < menor) menor = diff;        
            if(diff > maior) maior = diff;
 
            turn(20, LEFT);
            sleep(0.2);
        }
 
        if(light_color == LIGHT_BLUE) light_ref = menor;
        else light_ref = maior;
    }
    alloff();
}

Abordagem Reativa

A função competition define o que o robô deve fazer enquanto que as funções modificadas de movimento (_fwd e _fwd2) descrevem como o robô deve reagir em determinada situação.

void _fwd(int dist){
    pid_motor = start_process(fwd(dist,0));
    COUNTS_LEFT  = 0;
    COUNTS_RIGHT = 0;
    while((COUNTS_LEFT < CM2COUNTS(dist) && COUNTS_RIGHT < CM2COUNTS(dist))){
        if(TOUCH_LEFT == 0){
            int left = COUNTS_LEFT;
            int right = COUNTS_RIGHT;
            stop_motor();
            if(TOUCH_LEFT == 0 && LINE_LEFT == WHITE && LINE_RIGHT == WHITE){
                COUNTS_LEFT  = 0;
                COUNTS_RIGHT = 0;
                bkw(5,0);
                COUNTS_LEFT  = 0;
                COUNTS_RIGHT = 0;
                turn(-10);
            }
            COUNTS_LEFT = left;
            COUNTS_RIGHT = right;
            pid_motor = start_process(fwd(dist,0));
        }
        if(TOUCH_RIGHT == 0){
            int left = COUNTS_LEFT;
            int right = COUNTS_RIGHT;
            stop_motor();
            if(TOUCH_RIGHT == 0 && LINE_LEFT == WHITE && LINE_RIGHT == WHITE){
                COUNTS_LEFT  = 0;
                COUNTS_RIGHT = 0;
                bkw(5,0);
                COUNTS_LEFT  = 0;
                COUNTS_RIGHT = 0;
                turn(10);
            }
            pid_motor = start_process(fwd(dist,0));
        }
        if(BREAK_BEAM == 0){
            int left = COUNTS_LEFT;
            int right = COUNTS_RIGHT;
            stop_motor();
            COUNTS_LEFT  = 0;
            COUNTS_RIGHT = 0;
            bkw(10,10);
            COUNTS_LEFT  = 0;
            COUNTS_RIGHT = 0;
            fwd(10,10);
            pid_motor = start_process(fwd(dist,0));
        }
    }
    stop_motor();
}
 
void _fwd2(int dist){
    pid_motor = start_process(fwd(dist,0));
    COUNTS_LEFT  = 0;
    COUNTS_RIGHT = 0;
    while((COUNTS_LEFT < CM2COUNTS(dist) && COUNTS_RIGHT < CM2COUNTS(dist))){
        if(TOUCH_LEFT == 0){
            int left = COUNTS_LEFT;
            int right = COUNTS_RIGHT;
            stop_motor();
            if(TOUCH_LEFT == 0 && LINE_LEFT == WHITE && LINE_RIGHT == WHITE){
                COUNTS_LEFT  = 0;
                COUNTS_RIGHT = 0;
                bkw(5,0);
                COUNTS_LEFT  = 0;
                COUNTS_RIGHT = 0;
                turn(-10);
            }
            COUNTS_LEFT = left;
            COUNTS_RIGHT = right;
            pid_motor = start_process(fwd(dist,0));
        }
        if(TOUCH_RIGHT == 0){
            int left = COUNTS_LEFT;
            int right = COUNTS_RIGHT;
            stop_motor();
            if(TOUCH_RIGHT == 0 && LINE_LEFT == WHITE && LINE_RIGHT == WHITE){
                COUNTS_LEFT  = 0;
                COUNTS_RIGHT = 0;
                bkw(5,0);
                COUNTS_LEFT  = 0;
                COUNTS_RIGHT = 0;
                turn(10);
            }
            pid_motor = start_process(fwd(dist,0));
        }
        if(BREAK_BEAM == 0){
            motor(4,80);
        }
    }
    stop_motor();
}
 
void _competition()
{
    //Part 1 - Wait for startup and orientation
    wait_startup();
    light_positioning();
 
    //Inside Square
    COUNTS_LEFT  = 0;
    COUNTS_RIGHT = 0;
    bkw(5,0);
    COUNTS_LEFT  = 0;
    COUNTS_RIGHT = 0;
    bkw(5,MOTOR_POWER);
    COUNTS_LEFT  = 0;
    COUNTS_RIGHT = 0;
    bkw(5,-MOTOR_POWER);
    _fwd(120);
    motor(0,30);
    _fwd2(120);
    while(1){
        _fwd2(60);
        turn(180);
    }
}

Abordagens Alternativas

Algumas abordagens inicialmente propostas foram estudadas antes da competição e por diversos problemas não puderam ser implementadas.

Ponte

Uma delas consistia em seguir as linhas laterais do campo até um dos lados da cavidade e soltar uma ponte evitando dessa forma colisões com outros robôs que optaram por passar pela rampa. O problema surgiu com o meio em que a ponte era solta sobre a cavidade: muitas vezes resultou em um mal posicionamento impossibilitando a travessia do robô. A ponte consistia em grandes chapas de lego com bordas laterais que evitavam que o robô caisse. Uma linha presa a uma extremidade era enrolada na barra giratória reponsável por prender blocos inferiores. Uma vez o motor acionado, a barra giratória desenrolava a linha baixando a ponte sobre a cavidade. Barras verticais sobre a parte inferior da ponte foram usadas para que esta caisse adequadamente sobre a cavidade. Apesar de efetivas, estas barras faziam com que o robô ultrapassasse o limite de 30 cm de comprimento do robô.

ponte.jpg

Seguir Linha até o outro campo

Embora o leitor possa pensar que nossa abordagem reativa foi muito simples, pensamos inicialmente em seguir as linhas de um lado do campo até o outro. O problema que surgiu, e consumiu muito de nosso tempo, foi tratar os casos em que o robô deveria fazer curvas sobre cruzamentos de linhas. Uma vez que dispunhamos de 2 sensores para esta tarefa (3 a princípio, mas o led queimou e não havia tempo para refazê-lo) e consideramos os 2 sensores sempre sobre a linha, não havia bons mecanismos para determinar o tempo exato em que o robô deveria seguir outra linha do cruzamento.

O código original até o momento em que o desenvolvíamos está disponível.

/*
000 - Move forward
001 - Moving off the line to the right
010 - Not used
011 - Slightly off the line to the right
100 - Moving off the line to the left
101 - Centered over the line
110 - Slightly off the line to the left
111 - No line - Overshoot
*/
 
#include "motor.ic"
 
//Sensors digital inputs
#define LINEFOLLOW_LEFT (digital(10))
#define LINEFOLLOW_CENTER (digital(9))
#define LINEFOLLOW_RIGHT (digital(13))
 
//Timeout timers
#define NOLINE_TIMEOUT x
#define INLINE_TIMEOUT x
 
#define OVERSHOOT 0
#define ERROR -1
 
//Global variables
int lineflag; //Contains a representation of the 3 sensors
int overshoot; //A flag that indicates if we have lost the line at a turn
int lastlineflag; //Store the las representation of the 3 sensors
 
//Functions
int linefollow()
{
    int pid_motor;
    lastlineflag=-1;
 
    while(1){
        lineflag = readlinesensors();
        switch(lineflag){
            case 000: //Move forward
               printf("000\n");
               if(lastlineflag != lineflag){
                  stop();
                  fwd(1000, 0);               
               }
               break;
            case 001: //Moving off the line to the right
               printf("001\n");
               if(lastlineflag != lineflag){
                  stop();
                  motor(MOTOR_LEFT, 30);   
                  motor(MOTOR_RIGHT, 80);
                  while(readlinesensors()!=000);
                  motor(MOTOR_LEFT, 80);   
                  motor(MOTOR_RIGHT, 20);
                  sleep(0.2);
               }
               break;
            case 010: //Not used
               printf("Error 010\n");
               stop();
               return ERROR;
            case 011: //Slightly off the line to the right
              printf("011\n");
              if(lastlineflag != lineflag){
                 stop();
                 fwd(1000, 20);               
              }
              break;
            case 100: //Moving off the line to the left
              printf("100\n");
              if(lastlineflag != lineflag){
                  stop();
                  motor(MOTOR_LEFT, 80);   
                  motor(MOTOR_RIGHT, 20);
                  while(readlinesensors()!=000);
                  motor(MOTOR_LEFT, 30);   
                  motor(MOTOR_RIGHT, 80);
                  sleep(0.2);               
              }
              break;
            case 101: //Not Used (Error)
               printf("Overshoot\n");
               stop();
               return OVERSHOOT;
            case 110: //Slightly off the line to the left
               printf("110\n");
               if(lastlineflag != lineflag){
                  stop();
                  fwd(1000, -20);               
               }
               break;
            case 111: //No line - Overshoot
               printf("Overshoot\n");
               stop();
               return OVERSHOOT;
        }
        lastlineflag = lineflag;
        sleep(0.2);
    }
}
 
void fwdToLine(){
    fwd(100, 0);
    while(LINEFOLLOW_LEFT==1 || LINEFOLLOW_RIGHT==1) 
      sleep(0.1);
    stop();
}
 
void fwdToOutLine(){
    fwd(100, 0);
    while(LINEFOLLOW_LEFT==0 || LINEFOLLOW_RIGHT==0) 
      sleep(0.1);
    stop();
}
 
int readlinesensors(){
    return LINEFOLLOW_LEFT*100 + LINEFOLLOW_CENTER*10 + LINEFOLLOW_RIGHT;
}

Até o momento em que pudemos desenvolver esta abordagem, o robô seguia até antes da rampa. A sequência de comandos é:

    //Part 1 - Wait for startup and orientation
    //wait_startup();
    //light_positioning();
 
    //Part 2 - Go to the black line and turn right
    fwdToLine();
    turn(95, RIGHT);
 
    //Part 3 - Go to the second black line and turn left
    fwdToLine();
    fwdToOutLine();
    fwdToLine();
    turn(50, LEFT);
    fwdToLine();
 
    //Part 4 - Follow the line until overshoot
    linefollow(); //E preciso garantir que chegou ao fim linha, podemos fazer isso por tempo. 
 
    //Part 5 - Go to the bridge
    turn(90, LEFT);
    fwd(45, 0);

Conclusão

O robô apresentou um comportamento não determinístico e bastante confuso durante a competição sem se aproximar muito de seu objetivo (alcançar o campo inimigo e tentar derrubar as torres) e as vezes derrubando as próprias torres resultando em um baixo desempenho e aproveitamento de suas funções.