Postagem em destaque

Controle PID de Potência em Corrente Alternada - Arduino e TRIAC - Parte I

Este post é o primeiro de uma série de seis que escrevi tratando de controle de potência e PID (controle proporcional, integral e derivativo...

sábado, 23 de maio de 2015

Usando o LeapMotion para controlar um robô meArm I

Abaixo, o vídeo dos meus primeiros resultados controlando o meArm a partir do sensor LeapMotion.


Esse sensor é semelhante ao Kinect, ou seja, detecta movimentos do nosso corpo (no caso, somente as mãos e braços) e constrói, usando modelos matemáticos bastante complexos, um modelo 3D que descreve cada parte do que está em seu "campo de visão". Essa detecção é feita com emissão e captação de radiação infravermelha.

Como a matemática por trás do troço é muito complexa, ele precisa do PC, ou seja, ainda não é possível ter uma plaquinha das que a gente usa (Arduino, RPi, Edison etc) para processar diretamente os sinais enviados pelo sensor.

Dito isso, se quisermos comandar algum device com o LM, devemos estabelecer a comunicação entre o PC onde o LM está conectado e o dispositivo a ser comandado.

No caso, a ideia é estabelecer uma maneira de comandar o meArm (pequeno robô visto no vídeo acima) de forma total, ou seja, todos os movimentos, a partir de movimentos captados pelo LM em uma ou duas mãos.

Como o robô pode ser controlado com um Arduino, pensei em ligar o Arduino no PC, captar os movimentos do meArm com uma aplicação em Python, processar esses comandos para facilitar o comando pelo Arduino e enviar pro Arduino via porta serial.

Os primeiros testes, me pareceu óbvio, seriam mais fáceis com a pinça, ou seja, fechando e abrindo a pinça do robô com movimentos em pinça do polegar contra o indicador.

Esse post vai tratar de como isso foi feito.

Primeiro, vamos analisar a aplicação Python:

###############################################################################
#                                                                             #
# This program tests the tweezers movement in meArm controlled by Leap Motion #
# You can see more in http://automatobr.blogspot.com.br                       #
#                                                                             #
# assismauro@hotmail.com                                                      #
#                                                                             #
###############################################################################
import sys
# Bring serial stuff
import serial
import thread, time

# Add path to LeapMotion libs directory 
sys.path.insert(0,"D:\Atrium\Projects\Arduino\LeapMotion\lib")

# import LM stuff
import Leap
from Leap import CircleGesture, KeyTapGesture, ScreenTapGesture, SwipeGesture

# initialize Arduino/PC communication. You must set COM port accordingly your Arduino connection
arduino = serial.Serial('COM45', 9600, timeout=.1)

# This listener is called anytime some data is available form LM
class SampleListener(Leap.Listener):

# some readable names to use later
    finger_names = ['Thumb', 'Index', 'Middle', 'Ring', 'Pinky']
    bone_names = ['Metacarpal', 'Proximal', 'Intermediate', 'Distal']

    def on_init(self, controller):
        print "Initialized"

    def on_connect(self, controller):
        print "Connected"

    def on_frame(self, controller):

        global arduino

        # Get the most recent frame, that contains data sent from LM engine
        frame = controller.frame()

        # Get hands
        if len(frame.hands) == 0:
            return
        
        hand = frame.hands[0] # only one hand

        # Get fingers: we only need Thumb and Index data, for a while
        for finger in hand.fingers:
            if (self.finger_names[finger.type()] == "Thumb") or (self.finger_names[finger.type()] == "Index"):
            # Get bones
                for b in range(0, 4):
                    bone = finger.bone(b)

                    d=0.0
                    i=0.0
                    tshMin = 10.0
                    tshMax = 100.0
                    # The idea is to process only distal bone data, that corresponds to te tip of our fingers.
                    # The LM coordinate system is a X,Y,Z based in the center of the sensor (see picture in my
                    # blog). As we need only the distance between tips, in a tweezers movement, I transform it
                    # in a "percent distance", that is, 0 corresponds to tweezer closed, and 100, tweezer
                    # oppened.
                    if (self.bone_names[bone.type] == "Distal"):
                        if (self.finger_names[finger.type()] == "Thumb"):
                            t=bone.next_joint[0]
                        else:
                            i=bone.next_joint[0]
                            d=abs(t-i)
                            o=100 if d >= tshMax else (0 if (d <= tshMin) else (d-tshMin)/tshMax*100.0)
                            print o
                            # here we send data to Arduino
                            toSend = str(o)+"\n"
                            arduino.write(toSend)
                            # and... that´s it.

def main():
    # Create a sample listener and controller
    listener = SampleListener()
    controller = Leap.Controller()
     
    # Have the sample listener receive events from the controller
    controller.add_listener(listener)

    # Keep this process running until Enter is pressed
    print "Press Enter to quit..."
    try:
        sys.stdin.readline()
    except KeyboardInterrupt:
        pass
    finally:
        # Remove the sample listener when done
        controller.remove_listener(listener)

if __name__ == "__main__":
    main()

O código acima, devidamente comentado, é responsável por enviar ao Arduino os comandos do motor da pinça (garra) do meArm.

Abaixo, o programa que roda no Arduino:


/*
  Program to command meArm gripper from LeapMotion (Arduino side)
  More about that: http://automatobr.blogspot.com.br
  assismauro@hotmail.com
*/

#include <Servo.h>
#include <SoftwareSerial.h>

// Debug communication, if you think is necessary
SoftwareSerial mySerial(7,8);

// meArm servo pins
int basePin = 11;
int shoulderPin = 10;
int elbowPin = 9;
int gripperPin = 6;

Servo base;
Servo shoulder;
Servo elbow;
Servo gripper;

void setup() {
  /*
  base.attach(basePin);
  shoulder.attach(shoulderPin);
  elbow.attach(elbowPin);
  */
  // Gripper test
  gripper.attach(gripperPin);
  Serial.begin(9600);
  mySerial.begin(9600);
  gripper.write(90);
  delay(500);
  gripper.write(120);
  delay(500);
  
}

// Stores the last cmd received
int cmdOld=-1;

// Threshold
int limiar = 5;

void loop() 
{
  String cmdStr = "";
  // Get command from Pyton PC app
  if(Serial.available())
     cmdStr=Serial.readStringUntil(10);
  
  if(cmdStr != "")
  {
     int cmd=cmdStr.toInt();
     //Check threshold
     if((cmd > 100) || (abs(cmd - cmdOld) < limiar))
        return;
     cmdOld=cmd;
     // Map LeapMotion command to gripper servo angle   
     int gripAngle=map(cmd,0,100,140,90);
     gripper.write(gripAngle);
     delay(30);
     // Send data to debug Arduino
     mySerial.print(cmd);
     mySerial.print("  -  ");
     mySerial.println(gripAngle);
  }
}

O programa é bem simples: recebe o comando que vem do Python e o trata, acionando o motor da garra.

Dois detalhes importantes:

1) Vc pode observar que tem a definição de uma segunda porta serial. Eu fiz isso para depurar o meu programa, porque como a porta serial que normalmente a gente usa para se comunicar com o Arduino está ocupada pelo próprio. Aí eu liguei um segundo Arduino a outro PC e conectei os dois criando uma porta serial usando os pinos 7 e 8. Aí deu para monitorar os comandos que estavam chegando enviando-os ao outro Arduino.

2) Outro pronto é a variável limiar.  Ela serve para suavizar os movimentos do meArm, porque os comandos recebidos variam muito. Isso evita a "tremedeira" da garra.

É isso.


Relógio de Tempo Real - RTC: o que é e quando é necessário usar

Um amigo, o Rodrigo, está montando no sítio um sistema de irrigação automática controlado por Arduino. Conversando sobre o projeto, sugeri a ele que comprasse um RTC, já que saber a hora do dia e mesmo o dia da semana é importante nesse tipo de projeto.

Aqui no blog tem um post que ensina inclusive como montar um RTC. Acontece que, como é parte de um outro projeto, acabou que não se falou muito do RTC. Principalmente não se falou das aplicações do bicho, o que é um pecado tendo em vista a importância do dito.

 Agora vai:
Image result for bateria de relógioRTC (Real Time Clock) é um dispositivo que armazena a data e a hora de maneira precisa ao longo do tempo. É um relógio, onde vc indica a data/hora inicial e ele passa a contar o tempo a partir daí. Ele tem uma bateria para manter a data/hora de maneira que, se o equipamento a que está conectado ficar sem alimentação, ao ser religado a hora está certa.

Esse dispositivo existe dentro de muitos aparelhos, como relógios digitais, despertadores, fornos de microondas etc. A bateria que eles usam, aquela que parece uma moeda (essa aí do lado), até tem o apelido de "bateria de relógio" por isso.

E para que a gente precisa disso no Arduino? Basicamente para três funções:

1) Quando precisamos que a data/hora seja mantida correta entre desligamentos do Arduino.

Quando a gente desliga o Arduino, o "pograminha" permanece em sua memória, mas ele para de rodar. Assim, se vc tiver um contador de tempo no seu programa (a função millis() é usada para esse fim), ele começará do zero quando for religado. Aí entra o RTC, obviamente.

2) Para manter a data/hora de um equipamento ao longo de muitos dias, ou meses.

A função millis() funciona assim: a cada milésimo de segundo o Arduino incrementa um contador interno de uma unidade. Assim, quando acessamos millis() a função retorna esse número.

Acontece que o maior número inteiro que o Arduino armazena é o que é conhecido no C como unsigned long, que é um número de 32 bits, ou seja 4 bytes.

Isso significa que o maior número inteiro que o Arduino consegue armazenar é 232, ou seja, 4.294.967.296 milissegundos. Parece muito, mas:

Um dia tem 3600 s/h * 1000 ms/s * 24h/d = 86.400.000 ms/dia.

Ou seja, o Arduino tem como contar tempo durante 4.294.967.296 ms/(86.400.000 ms/dia), ou seja, 49.7 dias, ou menos de dois meses, o que é pouco para sistemas que funcionarão durante anos.

E o que acontece quando chega no fim, quer dizer, se a gente vai acessando a função millis() e passam-se os tais quase 50 dias? "Tio, O Arduino explode???? ", perguntaria vc, aflito. Não, a variável simplesmente zera e começa a contar de novo.

3) Quando precisamos de uma medida do tempo precisa.

A função millis() é de uma precisão bastante razoável quando queremos contar o tempo em alguns segundos. Acontece que tem algumas funcionalidades do Arduino que distorcem essa função, ou seja, fazem com que ela passe a dar valores menos precisos. Uma dessas funções são as interrupções, que já foram discutidas aqui. Quando o programa principal "congela" para executar uma interrupção ele também congela o incremento da variável do millis() fazendo que a função perca em precisão, "aumentando o tamanho" de um segundo.

Bom, essas são as três razões principais pelas quais vc deve usar um RTC quando for precisar de uma medida de tempo. No mais, vc pode ir de millis() mesmo que estará de bom tamanho.

O funcionamento

O RTC mais simples é baseado em um chip chamado DS1307. Na realidade é uma família de chips, todos com código semelhante a esse, que é um chip especializado, quer dizer, feito para esse fim.

Ele basicamente armazena o valor do tempo que, uma vez inicializado, vai sendo incrementado com muita precisão pelo chip. É necessária ainda uma bateria tipo relógio, aquela da foto aí em cima, para manter a data/hora, e também uma alimentação de 3 a 5V para que possamos nos comunicar com o chip, de maneira a preservar a bateria que fica com o fim exclusivo de manter a contagem de tempo.

Diz o fabricante que o consumo de chip é da ordem de pico Ampère, quer dizer, 10-9A. Segundo os caras, a bateria consegue manter a coisa precisa por mais de 6 anos (!). Parece um adaptador de tomadas que tem lá em casa que diz que funciona em mais de 200 países, ou seja, difícil de comprovar. Na África do Sul, por exemplo, não funciona.

Mas.. tergiverso, voltemos ao que interessa.

"Bom,tio, e cumé que usamos essa bagaça?"

Piece of cake:

arduino to RTCAcima esquema de um break RTC ligado ao Arduino. Break é quando a gente tem um dispositivo que já vem mais "mastigado", ou seja, devidamente soldado e com pinos para que a gente o conecte ao mundo externo. "É a mesma coisa que shield então né?" Não, nénão. Shield é o break que se encaixa em cima do Arduino, ou seja, todo shield é um break mas nem todo break é shield, como é o caso do RTC.

 As conexões principais, ou seja, as que são obrigatórias para o RTC funcionar são:

GND e Vcc: alimentação, positivo e negativo, fios azul e vermelhor acima.
SDA e SCL: comunicação entre o Arduino e o break. É por esses fios que os dados vão e vem entre os dois dispositivos, num protocolo chamado i2c, que já foi usado em outros projetos aqui do blog.

O Arduino se comunica via i2c usando as portas A4 e A5 analógicas, que devem ser conectadas aos pinos SDA e SCL do Arduino.

Além dessas conexões podem haver outras. Existem breaks RTC que vem com um sensor de temperatura, tipo LM35 ou outro semelhante. Nesse caso vc o conecta a uma entrada/saída digital do Arduino, conforme mostrado na conexão DS/pino 2 acima.

Existe também um meio de vc saber se a bateria está ou não ok, através do pino BAT indicado acima.

Conectado o break, faça o upload do "pograminha" abaixo. Eu fiz o download o modifiquei apenas um pouco, então vai com o crédito do sujeito que fez. A vantagem dessa implementação é que a única biblioteca que ela usa é a Wire.h, que já vem quando a gente instala o Arduino. Existem algumas libs prontas que facilitam (RTCLib é uma delas), então fica a seu gosto decidir como usar.

Basicamente ele faz duas coisas:

1) Permite que vc acerte o relógio

2) Uma vez acertado, ele passa a mostrar a hora correta (essa foi a minha alteração).

Para verificar se está tudo ok com o break, rode o programa e forneça a data/hora corretas. Em seguida, desligue o Arduino e ligue novamente. Quando o programa perguntar se que vc quer atualizar data/hora, responda que não. Se ele voltar a indicar a hora correta, vc é o feliz proprietário de um RTC que funciona!

Abracadabraço,

Mauro

///////////////////////////////////////////

// RTC data and time setter              //
//                                       //
// This sample program allows the user   //
// to set the date and time of an RTC    //
// using I2C.                            //
//                                       //
// Codes by:                             //
// eGizmo Mechatronix Central            //
// Taft, Manila, Philippines             //
// http://www.egizmo.com                 //
// April 15, 2013                        //
///////////////////////////////////////////

#include <Wire.h>

const int DS1307 = 0x68; // Address of DS1307 see data sheets

const char* days[] =
{"Sunday", "Monday", "Tuesday", "Wednesday", "Thursday", "Friday", "Saturday"};

const char* months[] =
{"January", "February", "March", "April", "May", "June", "July", "August","September", "October", "November", "December"};

// Initializes all values:
byte second = 0;
byte minute = 0;
byte hour = 0;
byte weekday = 0;
byte monthday = 0;
byte month = 0;
byte year = 0;

void setup() {
  Wire.begin();
  Serial.begin(9600);
  delay(2000); // This delay allows the MCU to read the current date and time.

  Serial.print("The current date and time is: ");
  printTime();
  Serial.println("Please change to newline ending the settings on the lower right of the Serial Monitor");
  Serial.println("Would you like to set the date and time now? Y/N");

  while (!Serial.available()) delay(10);

  if (Serial.read() == 'y' || Serial.read() == 'Y')
  // This set of functions allows the user to change the date and time
  {
    Serial.read();
    setTime();
    Serial.print("The current date and time is now: ");
    printTime();
  }
  Serial.println("Thank you.");
}


// Continuous function for converting bytes to decimals and vice versa
void loop() {
  readTime();
  printTime();
  delay(1000);
}

byte decToBcd(byte val) {
  return ((val/10*16) + (val%10));
}

byte bcdToDec(byte val) {
  return ((val/16*10) + (val%16));
}

void setTime() {
  Serial.print("Please enter the current year, 00-99. - ");
  year = readByte();
  Serial.println(year);
  Serial.print("Please enter the current month, 1-12. - ");
  month = readByte();
  Serial.println(months[month-1]);
  Serial.print("Please enter the current day of the month, 1-31. - ");
  monthday = readByte();
  Serial.println(monthday);
  Serial.println("Please enter the current day of the week, 1-7.");
  Serial.print("1 Sun | 2 Mon | 3 Tues | 4 Weds | 5 Thu | 6 Fri | 7 Sat - ");
  weekday = readByte();
  Serial.println(days[weekday-1]);
  Serial.print("Please enter the current hour in 24hr format, 0-23. - ");
  hour = readByte();
  Serial.println(hour);
  Serial.print("Please enter the current minute, 0-59. - ");
  minute = readByte();
  Serial.println(minute);
  second = 0;
  Serial.println("The data has been entered.");

  // The following codes transmits the data to the RTC
  Wire.beginTransmission(DS1307);
  Wire.write(byte(0));
  Wire.write(decToBcd(second));
  Wire.write(decToBcd(minute));
  Wire.write(decToBcd(hour));
  Wire.write(decToBcd(weekday));
  Wire.write(decToBcd(monthday));
  Wire.write(decToBcd(month));
  Wire.write(decToBcd(year));
  Wire.write(byte(0));
  Wire.endTransmission();
  // Ends transmission of data
}



byte readByte() {
  while (!Serial.available()) delay(10);
  byte reading = 0;
  byte incomingByte = Serial.read();
  while (incomingByte != '\n') {
    if (incomingByte >= '0' && incomingByte <= '9')
      reading = reading * 10 + (incomingByte - '0');
    else;
    incomingByte = Serial.read();
  }

  Serial.flush();
  return reading;
}


void printTime() {
  char buffer[3];
  const char* AMPM = 0;
  readTime();
  Serial.print(days[weekday-1]);
  Serial.print(" ");
  Serial.print(months[month-1]);
  Serial.print(" ");
  Serial.print(monthday);
  Serial.print(", 20");
  Serial.print(year);
  Serial.print(" ");
  if (hour > 12) {
    hour -= 12;
    AMPM = " PM";
  }
  else AMPM = " AM";
  Serial.print(hour);
  Serial.print(":");
  sprintf(buffer, "%02d", minute);
  Serial.print(buffer);
  Serial.print(":");
  sprintf(buffer, "%02d", second);
  Serial.print(buffer);
  Serial.println(AMPM);
}


void readTime() {
  Wire.beginTransmission(DS1307);
  Wire.write(byte(0));
  Wire.endTransmission();
  Wire.requestFrom(DS1307, 7);
  second = bcdToDec(Wire.read());
  minute = bcdToDec(Wire.read());
  hour = bcdToDec(Wire.read());
  weekday = bcdToDec(Wire.read());
  monthday = bcdToDec(Wire.read());
  month = bcdToDec(Wire.read());
  year = bcdToDec(Wire.read());
}