====== 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). {{:cursos:introrobotica:2011-2:grupo07:campo-legenda.png| Campo da Competição - Imagem por Matheus Caldas Santos}} A apresentação feita para a competição: {{:cursos:introrobotica:2011-2:grupo07: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: - orientar-se pela luz polarizada; - alinhar-se com o lado menor do quadrado e no sentido da rampa; - andar para frente por 2,4 metros (suficientes para o próximo campo?); - 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: * se uma parte do robô elevar-se muito do chão, se este não estiver sobre a faixa preta do campo, volte um pouco, vire e vire um pouco no sentido contrário a parte que se elevou; * se algo for detectado na parte frontal do robô, acione mecanismos para tentar capturar o bloco se estiver no estado 4 ou desvie do objeto caso contrário; ===== 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. |
//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ô.
{{ :cursos:introrobotica:2011-2:grupo07:ponte.jpg?500 }}
==== 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.