Curso Robótica – #6 – Controle Remoto com 1 Lanterna
Quando o Arduino ainda não era popular, muitos iniciantes estavam à procura de guias descrevendo a construção de robots sem micro controladores.
Uma escolha popular era o lightheart , ou robot, que estava indo para a luz. Na prática, isso significava a possibilidade de controle remoto usando uma lanterna!
Devido à popularidade dos amantes da luz, não pude ignorar este projeto enquanto escrevia um curso de construção de robots. Claro, não vamos desistir do microcontrolador, também desta vez o coração do nosso veículo será o Arduino UNO!
Sensores para fontes de luz
Para que o robot possa seguir a fonte de luz mais forte, são necessários dois sensores. Um deles deve ser anexado no lado esquerdo do robot e o outro à direita.
A comparação de leituras ajudará a detectar a direção da queda da luz mais forte.
Naturalmente, as fotoresistências funcionarão melhor como simples sensores de iluminação. Esses elementos mudam a sua resistência dependendo da quantidade de luz que incide sobre eles.
Existem sensores analógicos prontos com fotoresistências, para que não precise de se preocupar em combinar o valor da resistência com o divisor de tensão, etc.
Conexão de sensores
Os sensores que usaremos para construir a luz são visíveis na figura abaixo. Dependendo do momento de comprar o conjunto, eles podem diferir ligeiramente em forma ou cor. Com certeza eles terão um fotoresistência!
Placas de sensor devem ser anexadas à frente do robot. Pode fazer isso em várias configurações. Para um efeito melhor, no entanto, vale a pena colocá-los relativamente distantes. Durante testes adicionais, a seguinte configuração será usada (em duas altas distâncias):
Condutores devem ter cuidado,
para não perder-se na roda e não bloquear seus movimentos.
Os sensores são conectados aos soquetes que usamos durante o episódio anterior do curso , ou seja, ADC_L e ADC_R. Ao conectar a ficha, certifique-se de fazer isso na direção certa. É melhor verificar se o cabo do terra (GND) vai para o lugar certo na placa.
Durante este projeto, usaremos os sensores na forma mais simples, ou seja, sem um jumper (entre o díodo e a fotoresistência). No entanto, pode ser assumido por um momento para o teste. Se o todo estiver conectado corretamente, o LED ficará azul:
Lembre-se que durante as experiências posteriores descritas
neste artigo, o jumper entre o díodo e o fotoresistência foi removido!
Teste de sensor via UART
Vamos começar com um programa simples que irá verificar o funcionamento dos sensores. Claro, contamos com esboços que foram criados anteriormente. Os sensores estão conectados aos mesmos pinos que usamos para anexar os extremos. Adicionamos novas definições para que os sensores (e seus sites) não corram mal:
#define R_LIGHT_SENSOR A0 #define L_LIGHT_SENSOR A1
Desta vez, as entradas serão lidas em analógico, portanto, NÃO as declaramos como entradas INPUT_PULLUP. O esboço básico do programa deve ser assim:
#define L_PWM 5 #define L_DIR 4 #define R_PWM 6 #define R_DIR 9 #define PWM_MAX 165 #define R_LIGHT_SENSOR A0 #define L_LIGHT_SENSOR A1 #define BUZZER 10 void setup() { //Konfiguracja pinow od mostka H pinMode(L_DIR, OUTPUT); pinMode(R_DIR, OUTPUT); pinMode(L_PWM, OUTPUT); pinMode(R_PWM, OUTPUT); //Konfiguracja pozostalych elementow pinMode(BUZZER, OUTPUT); digitalWrite(BUZZER, 0); //Wylaczenie buzzera } void loop() { } void leftMotor(int V) { if (V > 0) { //Jesli predkosc jest wieksza od 0 (dodatnia) V = map(V, 0, 100, 0, PWM_MAX); digitalWrite(L_DIR, 0); //Kierunek: do przodu analogWrite(L_PWM, V); //Ustawienie predkosci } else { V = abs(V); //Funkcja abs() zwroci wartosc V bez znaku V = map(V, 0, 100, 0, PWM_MAX); digitalWrite(L_DIR, 1); //Kierunek: do tyłu analogWrite(L_PWM, V); //Ustawienie predkosci } } void rightMotor(int V) { if (V > 0) { //Jesli predkosc jest wieksza od 0 (dodatnia) V = map(V, 0, 100, 0, PWM_MAX); digitalWrite(R_DIR, 0); //Kierunek: do przodu analogWrite(R_PWM, V); //Ustawienie predkosci } else { V = abs(V); //Funkcja abs() zwroci wartosc V bez znaku V = map(V, 0, 100, 0, PWM_MAX); digitalWrite(R_DIR, 1); //Kierunek: do tyłu analogWrite(R_PWM, V); //Ustawienie predkosci } } void stopMotors() { analogWrite(L_PWM, 0); //Wylaczenie silnika lewego analogWrite(R_PWM, 0); //Wylaczenie silnika prawego }
No começo vale a pena verificar se os sensores funcionam corretamente. Usaremos a comunicação UART para enviar informações sobre o status atual dos sensores para o computador a cada 250 ms:
#define L_PWM 5 #define L_DIR 4 #define R_PWM 6 #define R_DIR 9 #define PWM_MAX 165 #define R_LIGHT_SENSOR A0 #define L_LIGHT_SENSOR A1 #define BUZZER 10 void setup() { //Konfiguracja pinow od mostka H pinMode(L_DIR, OUTPUT); pinMode(R_DIR, OUTPUT); pinMode(L_PWM, OUTPUT); pinMode(R_PWM, OUTPUT); //Konfiguracja pozostalych elementow pinMode(BUZZER, OUTPUT); digitalWrite(BUZZER, 0); //Wylaczenie buzzera Serial.begin(9600); } void loop() { int odczytLewy = analogRead(L_LIGHT_SENSOR); int odczytPrawy = analogRead(R_LIGHT_SENSOR); Serial.print("Lewa strona: "); Serial.print(odczytLewy); Serial.print(" | "); Serial.print("Prawa strona: "); Serial.println(odczytPrawy); delay(250); } void leftMotor(int V) { if (V > 0) { //Jesli predkosc jest wieksza od 0 (dodatnia) V = map(V, 0, 100, 0, PWM_MAX); digitalWrite(L_DIR, 0); //Kierunek: do przodu analogWrite(L_PWM, V); //Ustawienie predkosci } else { V = abs(V); //Funkcja abs() zwroci wartosc V bez znaku V = map(V, 0, 100, 0, PWM_MAX); digitalWrite(L_DIR, 1); //Kierunek: do tyłu analogWrite(L_PWM, V); //Ustawienie predkosci } } void rightMotor(int V) { if (V > 0) { //Jesli predkosc jest wieksza od 0 (dodatnia) V = map(V, 0, 100, 0, PWM_MAX); digitalWrite(R_DIR, 0); //Kierunek: do przodu analogWrite(R_PWM, V); //Ustawienie predkosci } else { V = abs(V); //Funkcja abs() zwroci wartosc V bez znaku V = map(V, 0, 100, 0, PWM_MAX); digitalWrite(R_DIR, 1); //Kierunek: do tyłu analogWrite(R_PWM, V); //Ustawienie predkosci } } void stopMotors() { analogWrite(L_PWM, 0); //Wylaczenie silnika lewego analogWrite(R_PWM, 0); //Wylaczenie silnika prawego }
Após sua ativação, as leituras atuais devem aparecer no monitor da porta de serie. Quanto mais escuro o sensor (cobrindo com a mão), mais valor será indicado.
Sabemos que um amante da luz… deve ir onde está mais, isto é, onde o sensor mostra um valor menor. É claro, pode comparar os valores acima, mas de acordo com essa visão, eles serão difíceis de ler o tempo todo.
Será muito mais fácil se calcularmos a diferença nos valores lidos. O novo loop () deve ficar assim:
void loop() { int odczytLewy = analogRead(L_LIGHT_SENSOR); int odczytPrawy = analogRead(R_LIGHT_SENSOR); Serial.print("Roznica: "); Serial.println(odczytLewy - odczytPrawy); delay(250); }
Agora as leituras serão mais fáceis de interpretar. Se o resultado for positivo, significa que no lado direito do robô está mais brilhante. Se o resultado for negativo, então viramos à esquerda. Além disso, quanto maior o número (ignorando o sinal), maior será a diferença entre os sensores, ou seja, você pode girar mais rápido.
É fácil associá-lo a um eixo numérico no qual os valores negativos estão à esquerda e os positivos à direita. Quando a nossa diferença é de cerca de 0, o robot pode ir em frente, e quando a diferença se desvia de 0, a resposta do veículo deve ser mais forte.
Siga a luz “no lugar”
No começo, é melhor começar com o teste no lugar. Deixe o robot girar em direção a uma fonte de luz forte. Essa é a maneira mais fácil de detectar possíveis erros de conversão de páginas.
Uma implementação exemplar dessa tarefa pode ser vista abaixo. Além de medir o valor, no loop (), contamos a diferença e, dependendo do seu valor, viramos para a esquerda ou para a direita:
#define L_PWM 5 #define L_DIR 4 #define R_PWM 6 #define R_DIR 9 #define PWM_MAX 165 #define R_LIGHT_SENSOR A0 #define L_LIGHT_SENSOR A1 #define BUZZER 10 void setup() { //Konfiguracja pinow od mostka H pinMode(L_DIR, OUTPUT); pinMode(R_DIR, OUTPUT); pinMode(L_PWM, OUTPUT); pinMode(R_PWM, OUTPUT); //Konfiguracja pozostalych elementow pinMode(BUZZER, OUTPUT); digitalWrite(BUZZER, 0); //Wylaczenie buzzera } void loop() { int odczytLewy = analogRead(L_LIGHT_SENSOR); int odczytPrawy = analogRead(R_LIGHT_SENSOR); int roznica = odczytLewy - odczytPrawy; if (roznica > 0) { //Jesli obliczona roznica jest wieksza od zera leftMotor(30); //To skręcamy w prawo rightMotor(-30); } else { //Jesli obliczona roznica jest mniejsza od zera leftMotor(-30); //To skręcamy w lewo rightMotor(30); } } void leftMotor(int V) { if (V > 0) { //Jesli predkosc jest wieksza od 0 (dodatnia) V = map(V, 0, 100, 0, PWM_MAX); digitalWrite(L_DIR, 0); //Kierunek: do przodu analogWrite(L_PWM, V); //Ustawienie predkosci } else { V = abs(V); //Funkcja abs() zwroci wartosc V bez znaku V = map(V, 0, 100, 0, PWM_MAX); digitalWrite(L_DIR, 1); //Kierunek: do tyłu analogWrite(L_PWM, V); //Ustawienie predkosci } } void rightMotor(int V) { if (V > 0) { //Jesli predkosc jest wieksza od 0 (dodatnia) V = map(V, 0, 100, 0, PWM_MAX); digitalWrite(R_DIR, 0); //Kierunek: do przodu analogWrite(R_PWM, V); //Ustawienie predkosci } else { V = abs(V); //Funkcja abs() zwroci wartosc V bez znaku V = map(V, 0, 100, 0, PWM_MAX); digitalWrite(R_DIR, 1); //Kierunek: do tyłu analogWrite(R_PWM, V); //Ustawienie predkosci } } void stopMotors() { analogWrite(L_PWM, 0); //Wylaczenie silnika lewego analogWrite(R_PWM, 0); //Wylaczenie silnika prawego }
É hora de pegar na sua lanterna, telefone com uma lanterna ou outra fonte de holofotes e começar a testar! Um exemplo do efeito deste programa é visível abaixo:
Versão 1: Um programa simples de Światłoluba
Agora é suficiente expandir um pouco a condição para que o robot siga a própria luz. Como foi marcado na reta numérica, para valores próximos a zero, pode ir direto.
#define L_PWM 5 #define L_DIR 4 #define R_PWM 6 #define R_DIR 9 #define PWM_MAX 165 #define R_LIGHT_SENSOR A0 #define L_LIGHT_SENSOR A1 #define BUZZER 10 void setup() { //Konfiguracja pinow od mostka H pinMode(L_DIR, OUTPUT); pinMode(R_DIR, OUTPUT); pinMode(L_PWM, OUTPUT); pinMode(R_PWM, OUTPUT); //Konfiguracja pozostalych elementow pinMode(BUZZER, OUTPUT); digitalWrite(BUZZER, 0); //Wylaczenie buzzera } void loop() { int odczytLewy = analogRead(L_LIGHT_SENSOR); int odczytPrawy = analogRead(R_LIGHT_SENSOR); int roznica = odczytLewy - odczytPrawy; if (roznica > 50) { //Jesli obliczona roznica jest wieksza leftMotor(30); //To skręcamy w prawo rightMotor(-30); } else if (roznica < -50){ //Jesli obliczona roznica jest mniejsza leftMotor(-30); //To skręcamy w lewo rightMotor(30); } else { //W pozostałych przypadkach jedziemy prosto leftMotor(30); rightMotor(30); } } void leftMotor(int V) { if (V > 0) { //Jesli predkosc jest wieksza od 0 (dodatnia) V = map(V, 0, 100, 0, PWM_MAX); digitalWrite(L_DIR, 0); //Kierunek: do przodu analogWrite(L_PWM, V); //Ustawienie predkosci } else { V = abs(V); //Funkcja abs() zwroci wartosc V bez znaku V = map(V, 0, 100, 0, PWM_MAX); digitalWrite(L_DIR, 1); //Kierunek: do tyłu analogWrite(L_PWM, V); //Ustawienie predkosci } } void rightMotor(int V) { if (V > 0) { //Jesli predkosc jest wieksza od 0 (dodatnia) V = map(V, 0, 100, 0, PWM_MAX); digitalWrite(R_DIR, 0); //Kierunek: do przodu analogWrite(R_PWM, V); //Ustawienie predkosci } else { V = abs(V); //Funkcja abs() zwroci wartosc V bez znaku V = map(V, 0, 100, 0, PWM_MAX); digitalWrite(R_DIR, 1); //Kierunek: do tyłu analogWrite(R_PWM, V); //Ustawienie predkosci } } void stopMotors() { analogWrite(L_PWM, 0); //Wylaczenie silnika lewego analogWrite(R_PWM, 0); //Wylaczenie silnika prawego }
A operação do robot é visível abaixo:
Tarefa Adicional 6.1
Acrescente um procedimento de partida no qual o robot pisca rapidamente com um díodo conectado ao pino 13, e somente depois da iluminação próxima a um dos sensores começa a andar 3 vezes com uma campainha e só inicia. Como parte da solução de tarefas, além do código, coloque seus vídeos nos comentários!
Versão 2: Passeio suave de Światłoluba
O código acima foi simples, mas o robot não se comportou bem. Ele reagiu a mudanças na luz com revoluções repentinas. Naturalmente, o programa pode ser estendido com condições adicionais que introduzem vários estados intermediários. Isso deve facilitar o passeio do robot.
Nesta fase do curso, podemos fazê-lo definitivamente melhor!
Os valores extremos da diferença da variável aparecem raramente. Claro, isso também depende das condições em que o robot é testado. Numa sala muito escura e com uma lanterna muito forte, certamente alcançaremos valores extremos com mais frequência.
No meu caso, a diferença costuma flutuar dentro do limite de +/- 300.
Então eu adicionei duas definições que definem os valores admissíveis da variável:
#define ROZNICA_MIN -300 #define ROZNICA_MAX 300
Depois de ampliar a lanterna muito perto do robot, era possível exceder esses intervalos. Para evitar problemas, é uma boa ideia proteger-se contra essas condições apropriadas. Logo após calcular o valor da variável, a diferença foi adicionada para verificar se não excede o intervalo aceito:
if (roznica < ROZNICA_MIN) { //Jeśli roznica mniejsza od dopuszczalnej roznica = ROZNICA_MIN; //To ustaw najmniejszą dopuszczalną wartość } else if (roznica > ROZNICA_MAX) { //Jeśli różnica większa od dopuszczalnej roznica = ROZNICA_MAX; //To ustaw największą dopuszczalną wartość }
Graças a isso, em casos extremos, quando a variável assume um valor fora do intervalo (por exemplo, 350), ela será definida como 300 segura. Esse mecanismo também será útil durante os testes do robot.
Como já foi dito, o valor da diferença de variável nos informa sobre a “força” com a qual o robot deve começar a girar. Portanto, é melhor usar essas informações e traduzi-las diretamente em revoluções do motor. A função de mapa () funcionará perfeitamente aqui!
//Przeliczenie odczytow z czujnikow na zmiane predkosci silnikow int zmianaPredkosci = map(roznica, ROZNICA_MIN, ROZNICA_MAX, -40, 40);
Com esta linha em mudança de velocidade serão armazenados valor de -40 a 40. O valor deste já pode ser traduzido em motores de velocidade. Agora basta adicionar a alteração calculada à velocidade de um dos mecanismos e subtrair do outro:
//Dodajemy lub odejmujemy wyliczoną zmianę od prędkosci bazowej leftMotor(30+zmianaPredkosci); rightMotor(30-zmianaPredkosci);
O “30” inserido aqui é a velocidade na qual o robot irá direto (quando as leituras dos sensores estão próximas umas das outras). Tanto o “30” e o intervalo de -40 a 40 eu escolhi durante os testes.
Ao escolher os parâmetros de velocidade, lembre-se de que, no caso extremo,
a sua soma não excede 100 (porque 100% é a velocidade máxima dos motores).
Aqui, nós usaremos maximamente 30 + 40 = 70% da velocidade do motor.
Todo o código do programa está disponível abaixo:
#define L_PWM 5 #define L_DIR 4 #define R_PWM 6 #define R_DIR 9 #define PWM_MAX 165 #define R_LIGHT_SENSOR A0 #define L_LIGHT_SENSOR A1 #define BUZZER 10 #define ROZNICA_MIN -300 #define ROZNICA_MAX 300 void setup() { //Konfiguracja pinow od mostka H pinMode(L_DIR, OUTPUT); pinMode(R_DIR, OUTPUT); pinMode(L_PWM, OUTPUT); pinMode(R_PWM, OUTPUT); //Konfiguracja pozostalych elementow pinMode(BUZZER, OUTPUT); digitalWrite(BUZZER, 0); //Wylaczenie buzzera } void loop() { int odczytLewy = analogRead(L_LIGHT_SENSOR); int odczytPrawy = analogRead(R_LIGHT_SENSOR); int roznica = odczytLewy - odczytPrawy; if (roznica < ROZNICA_MIN) { //Jeśli roznica mniejsza od dopuszczalnej roznica = ROZNICA_MIN; //To ustaw najmniejszą dopuszczalną wartość } else if (roznica > ROZNICA_MAX) { //Jeśli różnica większa od dopuszczalnej roznica = ROZNICA_MAX; //To ustaw największą dopuszczalną wartość } //Przeliczenie odczytow z czujnikow na zmiane predkosci silnikow int zmianaPredkosci = map(roznica, ROZNICA_MIN, ROZNICA_MAX, -40, 40); //Dodajemy lub odejmujemy wyliczoną zmianę od prędkosci bazowej leftMotor(30+zmianaPredkosci); rightMotor(30-zmianaPredkosci); } void leftMotor(int V) { if (V > 0) { //Jesli predkosc jest wieksza od 0 (dodatnia) V = map(V, 0, 100, 0, PWM_MAX); digitalWrite(L_DIR, 0); //Kierunek: do przodu analogWrite(L_PWM, V); //Ustawienie predkosci } else { V = abs(V); //Funkcja abs() zwroci wartosc V bez znaku V = map(V, 0, 100, 0, PWM_MAX); digitalWrite(L_DIR, 1); //Kierunek: do tyłu analogWrite(L_PWM, V); //Ustawienie predkosci } } void rightMotor(int V) { if (V > 0) { //Jesli predkosc jest wieksza od 0 (dodatnia) V = map(V, 0, 100, 0, PWM_MAX); digitalWrite(R_DIR, 0); //Kierunek: do przodu analogWrite(R_PWM, V); //Ustawienie predkosci } else { V = abs(V); //Funkcja abs() zwroci wartosc V bez znaku V = map(V, 0, 100, 0, PWM_MAX); digitalWrite(R_DIR, 1); //Kierunek: do tyłu analogWrite(R_PWM, V); //Ustawienie predkosci } } void stopMotors() { analogWrite(L_PWM, 0); //Wylaczenie silnika lewego analogWrite(R_PWM, 0); //Wylaczenie silnika prawego }
A operação do sistema na prática é apresentada no vídeo a seguir. Como você pode ver, o comportamento do robot é agora muito mais suave. O escalonamento apropriado da diferença nas leituras do sensor permite escolher a “força de direção” com a qual o robot deve reagir.
Tarefa Adicional 6.2
Verifique o efeito que as alterações têm no comportamento do robot:
- diferença de intervalo variável (no artigo foi de -300 a 300),
- faixa de velocidade adicionada/subtraída aos motores (no artigo era de -40 a 40).
Como e por que a velocidade do motor é calculada?
Talvez nem todos saibam imediatamente por que acrescentei o valor calculado a um mecanismo e subtrai-o do outro. É melhor considerar isso num exemplo. Suponha que a luz definitivamente mais forte esteja no lado direito do robot, o que fez a diferença variável assumir o valor de 300.
Após o reescalonamento, a variável mudança de velocidade será 40. Se adicionarmos agora à velocidade do motor esquerdo (30 + 40 = 70) e subtrairmos da velocidade do motor direito (30-40 = -10), o robot girará para a direita – exatamente onde deveria.
Da mesma forma para a situação oposta (luz forte à esquerda) na diferença variável, temos -300, que após o dimensionamento nos dará -40. Adicionando este valor à velocidade do motor esquerdo, obtemos agora 30 + (-40) = -10. No entanto, para o motor direito 30 – (-40) = 70, o robot irá virar para a esquerda.
A propósito, vale ressaltar que a solução utilizada aqui
é um controlador proporcional simplificado.
Sumário
Se tudo correu bem, cada um dos leitores já tem o robot mais popular, uma boa lâmpada! Acho que toda a gente vai admitir que o controle remoto de um veículo com uma lanterna é uma solução incomum.
No próximo artigo, veremos os populares robots LineFollower que podem navegar por uma rota especialmente preparada sem a ajuda de ninguém! Por enquanto, como parte da tarefa do trabalho de casa, encorajo a todos a ajustar a sua luz, escolher novas velocidades e gravar um pequeno vídeo sobre como dirigir com o seu projeto!