Este trabalho tem como objetivo a caracterização de sensores de luz, assim como a utilização dos mesmos para a distinção de cores. Duas aplicações foram feitas a partir disso, uma para fazer o robô seguir uma linha preta em superfície branca, e outra para fazer com que ele distingua blocos de cores diferentes.
Primeiramente, escolhemos o uso de sensores infra-vermelhos para a realização da tarefa, pois são menos susceptíveis à interferência de outras luzes visíveis (o espectro é menor). Assim, dois sensores foram acoplados na parte de baixo do carrinho, alinhados, de modo que um ficasse do lado direito e o outro do lado esquerdo. A utilização de dois sensores foi escolhida para que fosse possível detectar por qual dos lados o carrinho estava se desviando da linha preta.
Para a identificação de cores, a escolha do sensor resistivo(LDR) foi feita, pois ele é melhor para distinguir em um espectro mais amplo de cores. Planejamos a utilização de um LED RGB (o mesmo LED muda para verde, azul e vermelho), porém devido à alguns problemas, acabamos fazendo a construção e a implementação apenas com um LED branco.
Pensnado nos objetivos do trabalho, testamos dois sensores diferentes: um sensor IR (Foto 1) e um sensor RGB (Foto 2). O primeiro sensor é composto por um par emissor/receptor infravermelho. Já o segundo, sensor é feito utilizando um LDR e um LED RGB. Ambos sensores são ativos, ou seja, emitem luz e medem o seu reflexo. Consideramos que essa é a melhor maneira de lidar com a influência externa que tende a variar bastante ao longo do tempo. Dessa forma, o primeiro sensor emite um feixe de luz Infravermelha e aguarda por sua reflexão em uma superfície monocromática. Não é esperado que o sensor tenha um bom desempenho bem para a identificação de cores, pois a luz emitida está em uma frequência definida e não abrange o espectro cromático completo. Por esse motivo, esperamos que o sensor RGB atue bem para a identificação de cores pois ele ira emitir 3 cores que estão distantes umas das outras no espectro, e comparando as taxas de reflexão para cada uma dessas cores podemos descobrir com uma maior exatidão qual a cor identificada pelo sensor.
Nosso principal objetivo com o teste das cores é avaliar a possibilidade de descrever a cor de um objeto utilizando apenas um corte para o valor lido pelo LDR após
Realizamos a caracterização dos dois sensores em ambientes escuros (caixa de papelão). Isso foi viável porque nossos sensores eram ativos, portanto não dependiam de luz externa. Foram utilizada peças de lego para realizar os testes. Isso pode provocar algumas discrepâncias pois a superfície delas é bastante reflexiva.
Observações pertinentes Verificamos que os leds vermelho e azul separa bem as cores analisadas em dois conjuntos de cores: quentes e frias, de acordo com a posição que elas estão no espectro luminoso. Essa capacidade de separação das cores não foi tão clara no caso do led verde pois ele se encontra mais no centro do espectro de luz visível. Outra conclusão interessante, é a incapacidade do sensor IR de detectar cores. Esse sensor detecta apenas a cor preta.
Realizamos a caracterização dos dois sensores em um ambiente aberto (incidencia de luz solar indireta e de luz incandescente diretamente). Em seguida analisaremos o impacto disso aos experimentos.
Observações pertinentes Como era de se esperar, o erro aumentou ao realizar os experimentos em ambiente não controlado.
O objetivo desse teste era testar os sensores nas condições que ele será posto a prova na competição. Por isso colocamos o robô para andar a uma velocidade constante e baixa e medimos novamente os experimentos.
Observações pertinentes Mais uma vez a precisão caiu, embora ainda não chega a ser uma grande preocupação para os valores lidos.
Todos os testes até esse momento foram calculados a uma distancia fixa de 1 cm do objeto até o conjunto do sensor. No entanto, avaliamos objetos a distância. É muito difícil conseguir traçar uma correlação do valor lido pela distancia pois quanto mais distante do objeto, o sensor lerá apenas o valor do ambiente. Isso acontece porque a quantidade de luz refletida pelo objeto será apenas uma pequena fração da quantidade de luz absorvida pelo sensor. Quando a distancia for suficientemente longa (pelos nossos testes mais de 6 cm já é o suficiente), o sensor passa a ser insencível a luz refletida pelo objeto. Portanto, não acreditamos que é possivel estimar distancias nem obter leituras de objetos a longa distância.
1. Usarei 15 para MAX_LIGHT e 230 para MIN_LIGHT
2. Sim, a função normalize opera como esperado, produzindo valores de 0 a 100
3. Um valor menor que 0 ou maior que 100 pode ocorrer se o valor de luz obtido não estiver entre os limites definidos (nesse caso, 15 e 230). Assim, a função corrige esse casos para a potência de 0 ou 100.
Robô seguindo linha Robô procurando linha
A implementação do algoritmo de seguir linha foi feita com o uso de multi-threads. Duas Threads foram criadas: uma para monitorar os sensores e a outra para atuar devidamente nos motores.
Para a filtração de ruídos, foi utilizada uma média de 10 valores em todas as leituras de sensores. Foi implementada uma média em que os valores discrepantes eram retirados, porém notou-se que isso consumia muito tempo de execução e acabava prejudicando o desempenho do robô, em vez de melhorar. Além disso, se uma medida for prejudicada por valores discrepantes, poucos milésimos de segundo depois será calculada outra medida, não prejudicando o fluxo em geral. Por esses motivos, adotou-se a média simples.
O algoritmo funciona com base em um autômato finito de 4 estados, descritos a seguir:
-Estado 0(00): nenhum sensor vê a linha
-Estado 1(01): somente o sensor da direita vê a linha
-Estado 2(10): somente o sensor da esquerda vê a linha
-Estado 3(11): ambos os sensores vêem a linha
O número e o significado de cada estado foram escolhidos propositalmente. Nota-se acima que a representação do número, em binário, indica os sensores que vêem a linha (sendo 1 = vê e 0 = não vê).
O estado inicial do autômato no começo do programa é o 0.
O fluxo do programa pode ser dividido em 3 partes: calibração, monitorar sensores e seguir linha. Abaixo, falarei de cada uma delas.
A calibração é executada no início do programa. O carrinho é colocado em cima da linha preta para calcular para cada um de seus sensores o valor de luz infra-vermelha refletida pela linha. Desse valor, é decrescido 10% para a constituição do UpperBound, por motivos que serão explicados logo abaixo, relacionados à histerese.
Em seguida, o carrinho é colocado em cima da superfície branca, para que seus sensores calculem o valor de luz infra-vermelha refletida pelo fundo branco. Esse valor é acrescido de 20% e é utilizado como LowerBound. Após a calibração, as duas partes seguintes passam a ser executadas, cada uma em uma thread.
Essa é a parte que controla e seta o valor da máquina de estados. Ela faz a leitura dos sensores e determina se cada um deles está enxergando a linha preta ou o fundo branco, para então alterar o estado (que é variável global do programa) para o correspondente.
Como só existe como possibilidades enxergar ou não enxergar a linha, foi implementada a tática de histerese, utilizando 2 limites já comentados: UpperBound e LowerBound.
O funcionamento é o seguinte:
-Se, no estado atual, a linha não é detectada, então ela só será detectada quando o valor lido for abaixo de LowerBound.
-Se, no estado atual, a linha é detectada, então ela só não será detectada quando o valor lido for acima de UpperBound.
Ou seja: se não houver grandes alterações nas leituras dos sensores, os estados se mantém por “inércia”. É uma ótima técnica para evitar ruídos, pois utilizando apenas 1 limite, se o valor lido está próximo desse limite, o estado pode ser facilmente alterado por causa de um ruído.
Essa etapa utiliza as informações de estado obtidas por “Monitorar Sensores” e executa as ações a partir delas. O objetivo é sempre levar o carrinho ao estado 3. As ações são citadas abaixo:
-Se estado é 3: coloca os motores em movimento para frente (ambos com a mesma potência).
-Se estado é 2: coloca ambas os motores em movimento para frente, porém com a potência do direito bem maior, para corrigir o erro e voltar para a linha.
-Se estado é 1: coloca ambas os motores em movimento para frente, porém com a potência do esquerdo bem maior, para corrigir o erro e voltar para a linha.
-Se estado é 0: o robô vira 90 graus para a esquerda procurando a linha. Se não encontrar, então vira 360 graus para a direita (assim, se ele acabou de sair da linha, certamente a achará). Se mesmo assim o robô não achar a linha, então ele se considera no modo “perdido” e começa a procurar a linha, fazendo uma espiral quadrada (quadrados com os lados cada vez maiores, acrescidos por uma unidade de comprimento).
O programa começa com uma calibração, que calibra um vetor de cores, no caso: preto, vermelho, azul, verde, amarelo e marrom. A calibração pede que o usuário coloque um objeto daquela cor em frente ao sensor, e então lê e registra o valor, após a confirmação da pessoa. O valor registrado é acrescido de 5 (valor arbitrário, porém foi determinado que ele não causaria interferência na próxima cor), pois é utilizado como limite superior.
Após a calibração, o programa começa a funciona e detecta a cor do objeto próximo ao sensor. Esse processo é feito do seguinte modo:
-Verifica se o valor lido é menor do que o limite superior do preto. Se sim, a cor é preta.
-Caso contrário, verifica a condição para a próxima cor (no caso, marrom). Se for menor é marrom, se não, vai para a próxima cor.
-Quando chegar na última cor, que no caso é o amarelo, verifica se é menor. Se sim é amarelo, se não é branco (por isso, não é necessário estabelecer limite para o branco).
Do mesmo modo que o segue linha, a leitura do sensor é feita sempre filtrando-se a média
#define TEMPO_MAXIMO_ROTACAO 1.6 #define LIGHT_SENSOR_DIR 6 #define LIGHT_SENSOR_ESQ 4 #define COLOR_SENSOR 2 /* estado 0 -> 00 -> nenhum sensor ve a linha estado 1 -> 01 -> somente o da direita ve a linha estado 2 -> 10 -> somente o da esquerda ve a linha estado 3 -> 11 -> ambos os sensores veem a linha */ int estado; float UpperBoundEsq, UpperBoundDir, LowerBoundEsq, LowerBoundDir, ColorBound=80.0; int main(){ int pidSensors; int pidMotors; estado=0; calibration(); printf("\n\nColoque o carro na linha e de start"); start_press(); printf("\n\nExecutando..."); pidSensors = start_process(MonitorSensors(),5); pidMotors = start_process(FollowLine(0,1),5); stop_press(); kill_process(pidSensors); kill_process(pidMotors); } void calibration(){ printf("\n\nColoque o carro na linha e aperte start"); while(!start_button()); while(start_button()); printf("\n\nLendo valor da linha..."); LowerBoundDir = getMediaLightValue(LIGHT_SENSOR_DIR); LowerBoundEsq = getMediaLightValue(LIGHT_SENSOR_ESQ); //aumenta ligeiramente o valor do limite inferior LowerBoundDir = LowerBoundDir*1.2; LowerBoundEsq = LowerBoundEsq*1.2; beep(); printf("\n\nEsq: %f Dir: %f", LowerBoundEsq, LowerBoundDir); sleep(5.0); printf("\n\nColoque o carro fora da linha e aperte start"); while(!start_button()); while(start_button()); printf("\n\nLendo valor fora da linha..."); UpperBoundDir = getMediaLightValue(LIGHT_SENSOR_DIR); UpperBoundEsq = getMediaLightValue(LIGHT_SENSOR_ESQ); //reduz ligeiramente o valor do limite superior UpperBoundDir = UpperBoundDir*0.9; UpperBoundEsq = UpperBoundEsq*0.9; beep(); printf("\n\nEsq: %f Dir: %f", UpperBoundEsq, UpperBoundDir); sleep(5.0); printf("\n\nCalibracao concluida!"); sleep(2.0); } float getLightValue(int sensor){ return 255.0 - (float)analog(sensor); } float getMediaLightValue(int sensor){ float valor=0.0; int i; for(i = 1; i <= 10; i++){ valor += getLightValue(sensor); } return valor / 10.0; } //funcao nao usada devido à grande tempo de processamento /* float getMediaValue(int sensor){ float valor=0.0, valorAtual; int i; for(i = 1; i<=20; i++){ valorAtual = 255.0-(float)analog(sensor); //valor += valorAtual; //a partir de 4 valores consistentes(20%), comeca a retirar valores discrepantes if(i>4){ //se o erro entre o valor atual e a media maior que 10% while(valor/(float)(i-1) - valorAtual > valor/(float)((i-1)*10) || valor/(float)(i-1) - valorAtual < -valor/(float)((i-1)*10)){ valorAtual = 255.0 - (float)analog(sensor); } } else if(i>1){ //se o erro entre o valor atual e a media maior que 10% if(valor/(float)(i-1) - valorAtual > valor/(float)((i-1)*10) || valor/(float)(i-1) - valorAtual < -valor/(float)((i-1)*10)){ //elimina medies e comeca de novo i=1; valorAtual = 0.0; valor = 0.0; } } valor += valorAtual; } valor = valor/20.0; return valor; }*/ int MonitorSensors(){ float valueDir, valueEsq; while(1){ valueDir = getMediaLightValue(LIGHT_SENSOR_DIR); valueEsq = getMediaLightValue(LIGHT_SENSOR_ESQ); //condicoes com histerese if((valueDir > UpperBoundDir || valueDir > LowerBoundDir && (estado == 0 || estado == 2)) && (valueEsq > UpperBoundEsq || valueEsq > LowerBoundEsq && estado < 2)){ //ambos nao enxergam a linha preta estado = 0; } else if((valueDir <= LowerBoundDir || valueDir < UpperBoundDir && (estado == 1 || estado == 3)) && (valueEsq > UpperBoundEsq || valueEsq > LowerBoundEsq && estado <2)){ //somente o da direita ve a linha estado = 1; } else if((valueDir > UpperBoundDir || valueDir > LowerBoundDir && (estado == 2 || estado == 0) ) && (valueEsq <= LowerBoundEsq || valueEsq < UpperBoundEsq && estado >1)){ //somente o da esquerda ve a linha estado = 2; } else if((valueDir <= LowerBoundDir || valueDir < UpperBoundDir && (estado == 1 || estado == 3)) && (valueEsq <= LowerBoundEsq || valueEsq < UpperBoundEsq && estado > 1)){ //ambos enxergam a linha preta estado = 3; } } } int FollowLine(int motorEsq, int motorDir){ int valorDir, valorEsq; int ultimo0 = 0; int i,c; while(1){ //maquina de estados switch(estado){ case 3: valorDir = 20; valorEsq = 20; break; case 2: valorDir = 20; valorEsq = 5; break; case 1: valorDir = 5; valorEsq = 20; break; default: reset_system_time(); motor(motorDir,15); motor(motorEsq,-15); beep(); while(estado == 0 && seconds() < TEMPO_MAXIMO_ROTACAO); ao(); if(estado == 0){ beep(); motor(motorDir,-15); motor(motorEsq,15); reset_system_time(); while(estado == 0 && seconds() < 4.0*TEMPO_MAXIMO_ROTACAO); ao(); if(estado == 0){ beep(); printf("\n\nProcurando linha..."); c=1; //procura fazendo quadrados while(estado == 0){ for(i=0;i<3;i++){ motor(motorEsq, 20); motor(motorDir, 20); reset_system_time(); while(estado == 0 && seconds() < (float)c*0.5); if(estado != 0){ ao(); break; } //rotacao motor(motorDir,15); motor(motorEsq,-15); reset_system_time(); while(estado == 0 && seconds() < TEMPO_MAXIMO_ROTACAO); if(estado != 0){ ao(); break; } } c++; } printf("\n\nLinha achada!"); beep(); } } ultimo0 = 1; } if(ultimo0 != 1){ motor(motorEsq,valorEsq); motor(motorDir,valorDir); } ultimo0 = 0; } }
#define COLOR_SENSOR 2 /*descomentados para quando a calibração não for utilizada*/ //#define GREENBOUND 120.0 //#define REDBOUND 90.0 //#define BLUEBOUND 110.0 //#define YELLOWBOUND 140.0 //#define BROWNBOUND 65.0 //#define BLACKBOUND 53.0 int GREENBOUND, REDBOUND, BLUEBOUND, YELLOWBOUND, BROWNBOUND, BLACKBOUND; int main(){ int valor; calibrationColors(COLOR_SENSOR); printf("\n\nposicione o sensor e aperte start"); while(1){ start_press(); valor = getMedia(COLOR_SENSOR); if(valor < BLACKBOUND){ //black printf("\n\nPreto!"); } if(valor >= BLACKBOUND && valor < BROWNBOUND){ //marrom printf("\n\nMarrom!"); } if(valor >= BROWNBOUND && valor < REDBOUND){ //red printf("\n\nVermelho!"); } if(valor >= REDBOUND && valor < BLUEBOUND){ //blue printf("\n\nAzul!"); } if(valor >= BLUEBOUND && valor < GREENBOUND){ //green printf("\n\nVerde!"); } if(valor >= GREENBOUND && valor < YELLOWBOUND ){ //yellow printf("\n\nAmarelo!"); } if(valor >= YELLOWBOUND){ //white printf("\n\nBranco!"); } } } int getMedia(int sensor){ int valor=0; int i; for(i = 1; i <= 10; i++){ valor += getLightValue(sensor); } return valor / 10; } int getLightValue(int sensor){ return 255 - (int)analog(sensor); } void calibrationColors(int port){ int valor; int repetir = 0; int i; char cores[6][]; cores[0] = "preto"; cores[1] = "vermelho"; cores[2] = "azul"; cores[3] = "verde"; cores[4] = "amarelo"; cores[5] = "marrom"; for(i=0; i< 6;i++){ do{ printf("\n\nColoque objeto %s e de start",cores[i]); start_press(); valor = getMedia(port)+5; printf("\n\nvalor: %f . Aceitar?"); while(1){ if(start_button()){ while(start_button()); switch(i){ case 0: BLACKBOUND = valor; break; case 1: REDBOUND = valor; break; case 2: BLUEBOUND = valor; break; case 3: GREENBOUND = valor; break; case 4: YELLOWBOUND = valor; break; case 5: BROWNBOUND = valor; break; } repetir = 0; break; } else if(stop_button()){ while(stop_button()); repetir = 1; break; } } }while(repetir == 1); } }