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, 20 de setembro de 2014

Comunicação Arduino-Raspberry Pi usando i²C - Parte I

Comecei a me interessar por automação "pelas mãos" do Arduino. De fato, me re-interessei, porque apesar de estar afastado da área de automação e robótica já há anos, eu sempre tive curiosidade sobre a área, e trabalhando com TI nunca estive realmente longe.

Depois veio o Raspberry Pi, que com sua capacidade de memória e interfaces incomparavelmente maiores em relação à plaquinha italiana me permitiram fazer projetos de certa forma mais complexos, envolvendo processamento de imagem, interface gráfica etc.

Acontece que o Arduino bate sem dó no Pi em um aspecto: aplicações de tempo real. Por aplicações de tempo real entenda-se programas que tenham que dar respostas muito rápidas (estou falando de milionésimos de segundo). Nesse caso o Pi "faia", porque como ele tem um sistema operacional, ele tem que dividir a sua capacidade de processamento entre várias tarefas simultãneas, ou seja: ele cuida do vídeo um bocadim, aí atende ao mouse do usuário, em seguida dá atenção à parte de segurança, depois volta ao vídeo etc. Se ao mesmo tempo em que ele tem que desemprenhar essas tarefas ele tem que cuidar de alguma coisa que exija uma atençao a intervalos de tempo muito precisos, a coisa "mela".

Vou dar um exemplo prático: suponhamos que vc desenvolva um website e hospede-o no seu Raspberry. Isso é molezinha, o bicho roda até Apache, dentre outros webservers. Mas vamos supor que vc queira que o usuário comande um servo motor a partir dess seu site (vocês verão como isso é possível nos próximos posts, aguardem). Nesse caso... ferrou. Um servo comum funciona da seguinte forma: se vc enviar pulsos para ele cuja largura (tempo em que o sinal fica "alto", ou seja, em um vamor positivo maior que uns 3V) seja de 1 ms (milissegundo), o servo vai para a posição 0°. Se vc passa o sinal para 1,5ms de largura, o servo movimenta-se até a posição 90°, permanencendo lá enquanto o pulso seguir mais ou menos o mesmo padrão. Se vc passa a largura do pulso para 2ms, ele vai para 180°.

Servo Motor Control Signal (PWM)
Ou seja, a variação máxima é da ordem de 1000 microssegundos. No Pi, ainda mais rodando como webserver, "no way".

Solução

E se ficássemos com os dois? Quer dizer, usássemos o Pi para hospedar o nosso site e, uma vez que tenha recebido comandos enviados pelo usuário através do site, ele entra em contato com o Arduino e diz a ele como posicionar o servo? Uma vez o servo em posição, basta ao Arduino informar que fez a tarefa e o Pi segue com a vida.

Bom, a ideia é boa, mas como fazer com que os dois "conversem"? Aí entra o protocolo I²C.

Em primeiro lugar "protocolo" é algo como um idioma, no sentido de uma "coisa" através da qual existe a comunicação, no caso a comunicação entre máquinas. Um desses protocolos é o I²C, que é largamente usado em automação, principalmente por ser muito simples e permitir a comunicação entre, em tese, milhares de equipamentos com apenas um par de fios, com uma velocidade mais que suficiente para a maioria dos nossos projetos de automação, mesmo os mais sofisticados. Mais sobre o assunto aqui.

Como implementar isso para a comunicação entre Arduino e RPi? Bom, vejamos com um exemplo prático. A ideia é enviar um número fornecido pelo usuário no Raspberry ao Arduino e o Arduino responder com esse número ao quadrado. Apesar da simplicidade nauseabunda, os conceitos estarão inteiramente explicados nessa aplicação, e outros exercícios virão em futuros posts.



Lado Arduino - Slave

Aqui a coisa é bem simples. Vc deve usar a biblioteca Wire, que já vem com o Arduino, e o "pograminha" fica assim:

// Add I²C lib
#include <Wire.h>

#define SLAVE_ADDRESS 0x04
int number = 0;

void setup() {
    // initialize i2c as slave
    Wire.begin(SLAVE_ADDRESS);

    // define callbacks for i2c communication
    Wire.onReceive(receiveData);
    Wire.onRequest(sendData);
}

void loop() {
    delay(100);
}

// callback for received data
void receiveData(int byteCount){

    while(Wire.available()) {
        number = Wire.read();
     }
}

// callback for sending data
void sendData(){
    Wire.write(number*2);
}

O programa realmente é simples, o que talvez exista aí de diferentes são as callbacks.

Uma função callback é uma função definida para ser chamada por "alguém" que não seja a gente, ou seja, o programador. Perguntaria vc: "Como assim cê fala, tio???". Respondo eu: veja o exemplo acima. Nessa caso, as funções receiveData() e sendData() são chamadas não pelo nosso programa, mas pela biblioteca Wire respectivamente quando dados são enviados pelo master (app Raspberry) ou são solicitados por ele. Sacaste? Isso dá ao Arduino o papel um pouco politicamente incorreto de slave, ou seja, quem decide quando enviar os dados ou quando recevber alguma resposta é o master, no caso o RPi.



Lado RPi - Master

Preparar o Pi para seu papel é um pouquinho mais enrolado, mas um pouquinho só mesmo.
Primeiramente temos que tirar a comunicação I²C da "blacklist", que é uma lista de protocolos bloqueados no ambiente Linux. Para isso, altere o arquivo raspi-blacklist.conf:

sudo nano /etc/modprobe.d/raspi-blacklist.conf

"Comente" a linha referente ao protocolo I²C no arquivo, colocando um # na primeira coluna. Isso irá "desativar a desativação" do protocolo, ou seja, torná-lo ativo. A linha deve ficar assim:
#blacklist i2c-bcm2708

Em seguida, adicione a linha a seguir ao arquivo /etc/modules
i2c-dev

Agora, é tempo de instalar as bibliotecas necessárias para o uso do I²C no RPi:
$ sudo apt-get install i2c-tools

Habilitando o I²C, usuário pi:
$ sudo adduser pi i2c

Agora, reinicie o seu Raspberry:
$ sudo reboot

Depois do reboot, se vc der o comando:
ll /dev/i2c*

... deverá ver as portas abertas pelo I²C no Pi. Se a sua placa for a primeira revisão, vc verá algo como:

/dev/i2c-0

Outro teste poderá ser feito se vc rodar:
$ i2cdetect -y 1 

Nesse caso, a saída será algo como:

     0  1  2  3  4  5  6  7  8  9  a  b  c  d  e  f
00:          -- -- -- -- -- -- -- -- -- -- -- -- -- 
10: -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- 
20: -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- 
30: -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- 
40: -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- 
50: -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- 
60: -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- 
70: -- -- -- -- -- -- -- --

Se houvesse algum device I²C conectado, veríamos o endereço dele ocupado acima. Vamos ver isso daqui a pouco.

"And last but not least..." vamos instalar a lib do Python para uso do protocolo:
sudo apt-get install python-smbus

Depois das configurações e downloads, devemos rodar o programa Python que conversa com aquele do Arduino lá de cima. Eis o bicho:

import smbus
import time
# for RPI version 1, use "bus = smbus.SMBus(0)"
bus = smbus.SMBus(0)

# This is the address we setup in the Arduino Program
address = 0x04

def writeNumber(value):
    bus.write_byte(address, value)
    # bus.write_byte_data(address, 0, value)
    return -1

def readNumber():
    number = bus.read_byte(address)
    # number = bus.read_byte_data(address, 1)
    return number

while True:
    var = input("Enter a number: ")
    if not var:
        continue

    writeNumber(var)
    print "I just sent to Arduino: ", var
    # sleep one second
    time.sleep(1)

    number = readNumber()
    print "Arduino sent to me back: ", number
    print


Comparando os dois programas, vc pode ver que a variável address tem o mesmo valor no Arduino e no RPi, indicando que eles vão se comunicar usando esse endereço.

Em seguida o sistema fica num loop eterno (while True:), onde ele vai ler um valor fornecido pelo usuário para em seguida coletar e exibir o valor respondido pelo Arduino.

Conecatando eletricamente Arduino & Pi

Bom, essa... fica prá amanhã.

Abracadabraço,

Mauro