Manual

do

Maker

.

com

Pan e tilt com Arduino e ESP8266 I

Pan e tilt com Arduino e ESP8266 I

É muito divertido brincar com robótica, mas o difícil é encontrar uma razão ou projeto. Por exemplo, esse próprio pan e tilt. Qual a aplicação? - Eu pergunto, eu respondo. - É um ótimo brinquedo multifuncional! Você pode utilizá-lo para responder a comandos via WiFi, RF, IR, potenciômetros, joystick analógico e o mais legal - visão computacional!

Em sua aplicação mais simples ele é um cursor para câmeras de segurança e webcams, mas se por exemplo, você desejasse acompanhar a luz do sol para manter um painel solar sempre na direção correta, um pan tilt viria a calhar, bastando inserir um sensor de luminosidade digital ou um LDR mesmo. E esse pequeno pan tilt é excelente para testes de projetos maiores.

Em um projeto mais complexo, ele pode ser utilizado como sensor de presença, mas não com PIR e sim com visão computacional, utilizando um Raspberry ou um Onion Omega com OpenCV. Não sei em quanto tempo estarei apto a fazê-lo, mas o farei em algum momento, espero que você se inscreva em nosso newsletter e curta nossa fanpage.

Vou tentar me ater a apenas uma implementação simples por vez, para não sobrecarregar o artigo com texto e assim também motivar você a executá-lo tal como o farei.

Lista de materiais

Para essa primeira brincadeira você vai precisar de:

  • 1 Arduino ou 1 ESP8266
  • 1 pan tilt
  • 2 servo motores
  • 2 potenciômetros de 10K (opcional) ou
  • 1 joystick analógico (opcional)

Proposta do artigo

Vamos atacar duas frentes nesse artigo sobre pan e tilt de forma separada. A primeira será o controle digital via comunicação serial. Dessa forma utilizaremos o mesmo código para as próximas implementações, apenas acrescendo funcionalidades.

A segunda será o controle analógico utilizando um dos dois meios para controlar o pan e tilt; potenciômetro ou joystick analógico. Eu utilizarei um joystick analógico que tenho aqui há anos e nunca utilizei (espero que ainda funcione).

Em outros artigos incluirei mais funcionalidades e novos meios de controle, conforme descrevi no começo do artigo.

Implementação digital do pan e tilt com Arduino

Essa implementação é a mais simples. Para iniciarmos o projeto, primeiro é necessário considerar as necessidades; os requisitos.

Requisitos

As necessidades aqui são triviais; montar o pan e tilt (veja como montar no video disponível com o produto, caso tenha dúvidas, eu montei sem olhar nada, é muito fácil), e controlar 2 servos motor pelo terminal serial do arduino. Como em outro artigo faremos o controle remoto desse dispositivo, já vou considerar um protocolo para garantir a integridade da mensagem e a reação sem erros do Arduino que controla os servos do pan e tilt.

 Servo motor

Caso você conheça servo motor, pule essa parte, senão, leia.

Um servo motor é um tipo de motor de resposta rápida, um torque considerável para seu tamanho e de posicionamento preciso. Ele possui um controlador interno e seu movimento máximo é 180 graus. Isso significa que ele não tem giro contínuo. Isso significa também que você precisa achar o ponto mínimo e máximo. Para tal, basta encaixar uma das aletas e forçá-lo manualmente para a esquerda ou direita, então posicionar a aleta à 90 graus do corpo do motor, de maneira que forme um 'L'. Tendo identificado o posicionamento, será necessário posicioná-lo adequadamente de forma que o movimento seja ideal para cobrir a área desejada dentro dos 180 graus.

Para o servo da base superior, atente-se de que o máximo que você consegue movimentar é 90 graus, mais que isso já começa ficar de 'cabeça pra baixo' e se voltando a posição de assento, pode forçar o motor e quebrar, por isso identifique o limite antes de prendê-lo à base superior.

O servo SG90 é alimentado por 5V, mas (pode ser uma estupidez minha) no datasheet não tem o consumo. Aliás, no documento de outro servo também não encontrei referências. Enfim, o Arduino dá conta de alimentar os dois, mas se tiver a possibilidade, sempre prefira alimentação externa.

Pra finalizar, existem sim servos de giro contínuo onde a velocidade pode ser controlada, mas não faz parte do escopo, portanto encerro esse tópico por aqui.

Kgf

O kgf ou, kilogram-force é uma unidade de medida de força em relação à gravidade influente na massa. Ela representa a força exercida em um deslocamento para o caso do servo. Mas a unidade oficial de força é o Newton, então a relação entre elas é 1kgf = 9,8N. Em alguns livros, a proporção está disposta como 10/1, portanto 1N = 0,1kgf considerando essa afirmação.

Newton

Apenas para exclarecer, Newton é a força exercida sobre um corpo com massa igual a 1 kg com indução de aceleração de 1 m/s² no sentido em que a força é exercida.

Características do servo

Agor que tudo está claro (ou não), podemos levar em conta que o torque do servo SG90 de 9 gramas é igual a 1.8 kgf·cm.

A velocidade dele é de 0.3s/180º, ou seja, 1/3 de segundo para ir de 0 à 179.

A tensão de operação vai de 4.8V à 5V.

Trabalha em temperaturas de 0º à 55º.

Se você pretender utilizá-lo com PIC, é bom saber (apesar de ser um padrão na maioria, senão em todos) que o periodo de PWM dele é de 20ms. No Arduino é transparente, mas se você o utilizar com PIC deve saber também que as posições principais são -90,0,90, onde -90 recebe um pulso de 1ms, 90 recebe um pulso de 2ms e o centro (que é a posição 0) recebe um pulso de 1,5ms. Sabendo disso, as variações já podem ser calculadas. Se pretende "mesmo" utilizá-lo com PIC, escrevi esse outro tutorial, que recomendo fortemente.

servo-wire-300x71.webp

Wiring

Quase não dá pra chamar de wiring. Se você tiver um shield com conexão para servo motor, é só um plug. De qualquer modo, disponibilizo o esquema com fritzing para que você sinta-se totalmente à vontade, caso seja iniciante. No caso, estou utilizando o Arduino UNO porque é o mais popular.

servo-wiring-01.webp

Código de controle do pan e tilt

Para esse primeiro exemplo, o código é muito simples, já escrevi algo parecido inclusive. Pode parecer até bobo movimentar os servos enviando comandos pelo monitor serial, mas é fundamental para as próximas implementações sem fio, onde enviaremos os mesmos comandos, mas por outro protocolo que não o serial. Para concluir, quero deixar claro que existem outros meios de codificar isso, quero escrever alguns códigos diferente para mostrar as variações, sem dizer qual é melhor ou pior.

Vou comentando no código e concluo logo após.

#include <Servo.h>

//pinos selecionados para os servos
#define PIN_UD 10
#define PIN_LR 9

//uma instancia para cada servo
Servo servoLeftRight;
Servo servoUpDown;

//posicao inicial do prato
int upDownInitialPos = 40;

//limites minimos; 40 para o prato, 0 para o eixo Y
int posLR = 40;
int posUD = 0;

//cria-se uma variavel para guardar o valor lido da serial
unsigned char LeftRightUpDown = '0';

void setup(){
    //inicializa os servos
    servoLeftRight.attach(PIN_LR); 
    servoUpDown.attach(PIN_UD);    
    
    Serial.begin(9600);   // inicializar porta serial

    servoUpDown.write(upDownInitialPos);
    delay(200);
    servoUpDown.detach();

    servoLeftRight.write(0);
    delay(200);
    servoLeftRight.detach();
}

void moveTo(char pos){
  //servoLeftRight
  Serial.print(pos);
  if (pos == 'l' || pos == 'r'){
    //se digitado 'r' e a posicao for menor que o maximo...
    if (pos == 'r' && posLR < 169){
        servoLeftRight.attach(PIN_LR);
        delay(15);
        posLR += 10;
        Serial.println(servoLeftRight.read());
    }
    else if (pos == 'l' && posLR >9){
        servoLeftRight.attach(PIN_LR);
        delay(15);
        posLR -=10;
        Serial.println(servoLeftRight.read());
    }
    else {
          //posicao = 40;
          servoLeftRight.attach(PIN_LR);
          delay(15);
    }

    servoLeftRight.write(posLR);
    delay(150);
    servoLeftRight.detach();
  }
  else if (pos == 'u' || pos == 'd'){
      if (pos == 'u' && posUD >49){        
          posUD -= 10;
          servoUpDown.attach(PIN_UD);
          delay(100);
          Serial.println(servoUpDown.read());
      }
      else if (pos == 'd' && posUD <149){
          posUD +=10;
          servoUpDown.attach(PIN_UD);
          delay(100);
          Serial.println(servoUpDown.read());
      }
      else {
          //posicao = 40;
          servoUpDown.attach(PIN_UD);
          delay(150);
      }
    
    servoUpDown.write(posUD);
    delay(20);
    servoUpDown.detach();
  }
    pos = 'z';
    //pausa de 0.015s
    delay(15);
}

void loop(){
    //Faz-se uma leitura serial:
    if (Serial.available() > 0){
        LeftRightUpDown = Serial.read();
        moveTo(LeftRightUpDown);
    }
}

Como você pode ver no primeiro video (a seguir), funciona bem e o legal é que você pode enviar uma série de comandos como 'lluuurrrdddd' e toda a sequência será executada. Vou até fazer uma brincadeira com isso em outro artigo.

https://www.youtube.com/watch?feature=player\_detailpage&v=SLrk7MczhxU

De ruim, ficam alguns pontos interessantes, que considero 'efeitos colaterais' do modo que foi programado, e explico.

O movimento de uma posição leva de 1ms à 2ms dentro de 20ms de comprimento de pulso. Quanto maior o deslocamento, obviamente maior o tempo para que o servo motor avance até a posição pretendida. No código acima, eu desconecto o servo após movimentá-lo para a posição; se isso ocorrer em um intervalo menor do que o suficiente para que o servo tenha atingido a posição pretendida, ele não chegará ao ponto-alvo. Nesse caso, pode-se gerar um loop para movimentar o servo em passos menores, de forma que também possam ser reduzidos os delays (maneira burra, mas simples), ou então pode-se verificar se o servo atingiu a posição antes de desconectá-lo. Isso funcionaria bem. Algo como:

- pega posição atual
- subtrai a diferença da posição pretendida
- enquanto NÃO atingir a posição, avança

Isso pode ser feito dentro de uma função utilizando também millis, de modo que não haja travamento do fluxo com o delay. Para pegar a posição atual do servo, simplesmente utiliza-se o método:

variavel = servo.read();

Em outros exemplos o código variará, porque pretendo escrever códigos diferentes pra mostrar mais recursos.

Fora a parte funcional, o código está ruim porque o mesmo valor é verificado em duas condicionais:

else if (pos == 'u' || pos == 'd'){
      if (pos == 'u' && posUD >49)...

Não importa se está funcionando, a questão é que se você está gerando duas condicionais para validar um mesmo atributo, significa que está errado. Esse código está ruim nesse aspecto também, porém o escrevi 'in flow' para exemplificar esse artigo, entenda que isso é uma 'prova de conceito'.

Implementação analógica do pan e tilt com joystick

Cada implementação tem suas fronteiras, mas não é necessário a eximia para funcionar a contento. Tanto que não escrevi nenhum código especialista nesse artigo, apenas imaginei o que queria e escrevi de qualquer jeito, sem considerar consumo, tempos, timers, etc.

A implementação com o joystick também é curiosa e a exibo em seguida. Utilizei o joystick shield para controlar o pan e tilt porque o tenho aqui há tempos, mas utilizei apenas o joystick, os botões não tem funcionalidade, apesar de que já deixei codificado, caso você também tenha um desses e deseje utilizar os botões. Vamos ao código:

#include <Servo.h>

//pinos selecionados para os servos
#define PIN_UD 10
#define PIN_LR 9

//uma instancia para cada servo
Servo servoLeftRight;
Servo servoUpDown;

struct joyshield
{
    char sel      = 2;         //pino do select (apertando o joystick)
    int leftRight = 504;       //valor inicial do joystick
    int upDown    = 511;       //valor inicial do joystick
    int button[4] = {3,4,5,6}; //pinos digital dos 4 botoes
    
    int result    = 505;       //ponto central medio

    int lastPosLR = 0;         //memoria de ultimo estado, para desligar/ligar o servo 1
    int lastPosUD = 0;         //memoria de ultimo estado, para desligar/ligar o servo 2
};
 
struct joyshield joystick;

void controller(int LR, int UD){
    //LR
    if ((LR < 500 || LR > 508) && LR != joystick.lastPosLR){
        servoLeftRight.attach(PIN_LR);
        delay(20);
        servoLeftRight.write(LR);
        delay(300);
        servoLeftRight.detach();
        joystick.lastPosLR = LR;
    }
    //UD
    if ((UD <504 || UD > 512) && UD != joystick.lastPosUD){
        servoUpDown.attach(PIN_UD);
        delay(20);
        servoUpDown.write(UD);
        delay(300);
        servoUpDown.detach();
        joystick.lastPosUD = UD;
    }
}

void setup(void){
    controller(joystick.leftRight,joystick.upDown);
    
    pinMode(joystick.sel, INPUT);      
    digitalWrite(joystick.sel, HIGH);

    for (int i=0;i<4;i++){
        joystick.button[i] = i+3;
        pinMode(joystick.button[i],INPUT);
        digitalWrite(joystick.button[i],HIGH);
    }
  
    Serial.begin(9600);
}

void loop(void){
    joystick.leftRight = map(analogRead(0),0,1023,40,150);
    Serial.print("Left/Right: ");
    Serial.println(joystick.leftRight);

    joystick.upDown = map(analogRead(1),0,1023,0,180);
    Serial.print("Up/Down: ");
    Serial.println(joystick.upDown);

    Serial.print("Select: ");
    Serial.println(digitalRead(joystick.sel));
    
    for (int i=0;i<4;i++){
        Serial.print("Button D");
        Serial.print(joystick.button[i]);
        Serial.print(": ");
        Serial.println(digitalRead(joystick.button[i])); 
    }

    controller(joystick.leftRight,joystick.upDown);
    delay(10);
}

Perceba que foi utilizado 2 pinos analógicos, sendo A0 e A1. Se você não estiver utilizando o shield, terá flexibilidade para escolher qualquer um dos analógicos.

Criei uma estrutura para organizar as variáveis pertencentes ao joystick, dessa forma fica mais fácil entender o código ao olhar. Dentro dessa estrutura, criei 2 variáveis para guardar o valor de leitura. No próximo loop, se o valor da leitura guardado for diferente do valor de leitura atual, ele religa o servo motor, muda a posição e desliga o servo. Esse tempo de da função controller() pode variar, fiz assim sem testar mas você pode experimentar valores menores ou maiores se desejar.

O video do joystick com pan e tilt.

Por fim, apelo aos leitores que na aquisição de produtos para execução dos artigos, que o façam nos links citados no post. A Fulltronic é nossa parceira e está patrocinando um "monte" de novidades pra gente e comprar com eles garantirá mais artigos e novos materiais. E se você desejar adquirir algum produto deles que não tiver tutorial, fiquem tranquilos, basta solicitar ao vendendor e eu escreverei o artigo. Além disso, você conta com suporte na fanpage Manual do Maker, inbox também comigo no facebook e principalmente através dos nossos grupos.

Inscreva-se no nosso canal Manual do Maker Brasil no YouTube.

Próximo post a caminho!

Nome do Autor

Djames Suhanko

Autor do blog "Do bit Ao Byte / Manual do Maker".

Viciado em embarcados desde 2006.
LinuxUser 158.760, desde 1997.