Manual
do
Maker
.
com
É 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.
Para essa primeira brincadeira você vai precisar de:
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.
Essa implementação é a mais simples. Para iniciarmos o projeto, primeiro é necessário considerar as necessidades; os 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.
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.
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.
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.
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.
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.
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'.
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!
Autor do blog "Do bit Ao Byte / Manual do Maker".
Viciado em embarcados desde 2006.
LinuxUser 158.760, desde 1997.