domingo, 19 de junho de 2016

Integrando Raspberry PI com Arduíno via I²C

Olá, neste post iremos explicar como fazer a comunicação entre um Raspberry PI e 2 Arduínos, utilizando o protocolo I²C. Já havíamos explicado como fazer uma integração entre eles, utilizando o barramento serial (para ver o artigo, clique aqui). A vantagem de utilizarmos o I²C, como já explicado nesse artigo Barramento I²C, é que podemos ter no mesmo barramento até 128 dispositivos e via Serial, só podemos ter um dispositivo conectado e como o Raspberry tem poucas portas GPIO's, podemos expandir esse número utilizando um ou mais Arduínos integrados via I²C.

No post Barramento I²C, utilizamos um Arduíno UNO como Master, um Duemilanove e um Nano como Slavers. Neste post, vamos substituir o Arduíno UNO Master por um Raspberry PI.

Segue lista de materiais utilizados:

  • 1 Raspberry PI Rev B
  • 1 Arduíno Duemilanove
  • 1 Arduíno Nano

Na nossa montagem, o Raspberry irá imprimir uma mensagem enviada por cada Arduíno, similar ao post Barramento I²C:


Raspberry PI

Por default, os pinos I²C do Raspberry estão desabilitados. Segue abaixo os passos habilitá-los.

1 - Via console, acesse a ferramenta de configuração
$ sudo raspi-config


2 - Selecione Advanced Options



3 - Selecione A7 I2C


4 - Selecione Yes e depois OK



5 - Selecione Yes e depois OK



6 - Selecione Finish


7 - Desligue o Raspberry como o comando abaixo, desligue a força e faça conexões dos Arduínos.
$ sudo halt

Esquema de ligação

Segue abaixo a lista de pinos do Raspberry PI Rev B


Os pinos I²C são: 3 - SDA(Dados) e 5 - SCL (Clock). O esquema de ligação é exatamente o mesmo do post Barramento I²C.

Segue esquemático:

Apesar de o Raspberry Pi trabalhar a 3,3V e o Arduíno a 5V, como o Raspberry PI será o Master a tensão do barramento será definida por ele e por isso, não se faz necessário montar um circuito para limitar a tensão (se o arduíno for Master, ele pode queimar a porta do Raspberry). Além disso, o Raspberry PI já tem um resistor pull-up nos pinos utilizado no barramento I²C que dispensa o seu uso em outros dispositivos.

Programação dos Arduínos.

A biblioteca que utilizaremos no Raspberry só permite a leitura de um inteiro por vez (não é possível ler um buffer) e por isso, enviaremos a mensagem caractere a caractere, com o primeiro caractere de controle. Explicarei com mais detalhes mais a frente.

Código do Nano:
#include <Wire.h>

void setup()
{
  Wire.begin(0x50);//endereço de I2C               
  Wire.onRequest(requestEvent); 
  
  pinMode(13, OUTPUT);
}

void loop()
{
  digitalWrite(13, HIGH);   
  delay(500);              
  digitalWrite(13, LOW);   
  delay(500);
}

char * buff="*hello I'm Nano";
byte count = 0;
void requestEvent()
{
  Wire.write(buff[count]);
  if(count == 14)
    count = -1; 
  count ++;  
}

Código do Duemilanove:
#include <Wire.h>

void setup()
{
  Wire.begin(0x40);//endereço de I2C               
  Wire.onRequest(requestEvent); 
  
  pinMode(13, OUTPUT);
}

void loop()
{
  digitalWrite(13, HIGH);   
  delay(500);              
  digitalWrite(13, LOW);   
  delay(500);
}

char * buff="*hello I'm Due";
byte count = 0;
void requestEvent()
{
  Wire.write(buff[count]);
  if(count == 13)
    count = -1; 
  count ++;  
}

Programação do Raspberry PI.

Para confirmar se o módulo I²C está ativo, basta digitar a linha de comando abaixo:
$ lsmod | grep i2c

Devemos ter uma saída similar a essa:
pi@raspberrypi:~ $ lsmod | grep i2c
i2c_bcm2708             5988  0 
i2c_dev                 6386  0 

A saída acima indica que o módulo i2c_bmc2708 está ativo.

Agora, vamos instalar a biblioteca para o I²C. Para mais detalhes sobre ela, clique aqui.
$ sudo apt-get install libi2c-dev

O Raspberry vem com um programa para detectar o endereço dos dispositivos conectados no barramento.

Basta digitar o comando abaixo:
$ i2cdetect -y 1

Se seu Raspberry PI for Rev 1, digite:
$ i2cdetect -y 0

Essa diferença acontece, pois houve uma alteração nos pinos utilizados pelo I²C entre Rev 1 e 2.

A saída deve ser de acordo com texto abaixo:
pi@raspberrypi:~ $ i2cdetect -y 1
     0  1  2  3  4  5  6  7  8  9  a  b  c  d  e  f
00:          -- -- -- -- -- -- -- -- -- -- -- -- -- 
10: -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- 
20: -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- 
30: -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- 
40: 40 -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- 
50: 50 -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- 
60: -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- 
70: -- -- -- -- -- -- -- --                         

0x40 e 0x50 são os Arduínos conetados no barramento.

Agora vamos a codificação.

Makefile - Crie o arquivo de acordo com o texto abaixo:
#
# Makefile:
#################################################################################


#DEBUG  = -g -O0
DEBUG   = -O3
CC      = gcc
INCLUDE = -I/usr/local/include
CFLAGS  = $(DEBUG) -Wall $(INCLUDE) -Winline -pipe

LDFLAGS = -L/usr/local/lib
LDLIBS    = -lwiringPi -lwiringPiDev -lpthread -lm

# Should not alter anything below this line
###############################################################################

SRC     =       i2cTest.cpp                                             \

Crie um arquivo chamado i2cTest.cpp, de acordo com o texto abaixo:
#include <stdio.h>
#include <wiringPi.h>
#include <wiringPiI2C.h>

void readI2CString(int fd, char*buffer,int length,char charController)
{
 for(;;)//aguardando caractere de controle para guardar a mensagem
  if(wiringPiI2CRead(fd)==charController)
   break;

 for(int i = 0; i < length; i++)//lendo mensagem
 {
  buffer[i]=wiringPiI2CRead(fd);
 }

}

int main (int argc, char *argv[])
{
        int fd1,fd2;//identificadores de cada dispositivo
 char buffer[20];//buffer para armazenar a mensagem
 wiringPiSetup ();//inicializando biblioteca
        fd1=wiringPiI2CSetup (0x40);//conectando com dispositivo 40
 fd2=wiringPiI2CSetup (0x50);//conectando com dispositivo 50
        if(fd1==-1||fd2==-1)
        {
                printf("Can't setup the I2C devices\n");
                return -1;
        }
        else
        {
  for (;;)
                {
   readI2CString(fd1,buffer,13,'*');//Lendo mensagem do dispositivo 40
   buffer[13] = '\0';
   printf("%s\n",buffer);

   readI2CString(fd2,buffer,14,'*');//Lendo mensagem do dispositivo 50
   buffer[14] = '\0';
   printf("%s\n",buffer);

                }
        }
        return 0;
}

Para compilar, basta executar a linha de comando abaixo:
$ make i2cTest

Para executar o programa, basta digitar o comando:
$ sudo ./i2cTest

A saída deverá ser essa:



Entendimento do código

Esse método abaixo, irá chamar o método wiringPiI2CRead da biblioteca wiringPiI2C que retorna um inteiro (valor lido). Como queremos imprimir uma String, se simplesmente imprimíssemos os caracteres no console, poderíamos imprimir llo I'm Duehe ou o I'm Duehello, ou outra combinação, pois o arduíno irá enviar sequencialmente e infinitamente a sequencia de caracteres. Assim, adicionei um caractere de controle para indicar que a mensagem iniciará no próximo caractere e o código irá esperar que venha esse caractere para começar a gravar a mensagem.

void readI2CString(int fd, char*buffer,int length,char charController)
{
 for(;;)//aguardando caractere de controle para guardar a mensagem
  if(wiringPiI2CRead(fd)==charController)
   break;

 for(int i = 0; i < length; i++)//lendo mensagem
 {
  buffer[i]=wiringPiI2CRead(fd);
 }

}

Aqui, a aplicação ficará em loop, lendo as mensagens de cada um dos Arduínos.
for (;;)
                {
   readI2CString(fd1,buffer,13,'*');//Lendo mensagem do dispositivo 40
   buffer[13] = '\0';
   printf("%s\n",buffer);

   readI2CString(fd2,buffer,14,'*');//Lendo mensagem do dispositivo 50
   buffer[14] = '\0';
   printf("%s\n",buffer);

                }

O Arduíno Duemilanove, que está com o endereço 0x40, irá enviar o caractere de controle '*' e a mensagem hello I'm Due, caractere a caractere (13 caracteres excluindo o de controle)

char * buff="*hello I'm Due";
byte count = 0;
void requestEvent()
{
  Wire.write(buff[count]);
  if(count == 13)
    count = -1; 
  count ++;  
}

O Arduíno Nano, que está com o endereço 0x50, irá enviar o caractere de controle '*' e a mensagem hello I'm Nano, caractere a caractere (14 caracteres excluindo o de controle)

char * buff="*hello I'm Nano";
byte count = 0;
void requestEvent()
{
  Wire.write(buff[count]);
  if(count == 14)
    count = -1; 
  count ++;  
}

E por isso, no loop do Raspberry, ele esperará 13 caracteres do Duemianove e 14 do Nano.


domingo, 12 de junho de 2016

Utilizando EEPROM I²C Com Arduíno

Olá, neste post iremos explicar como utilizar uma memória externa EEPROM com o barramento I²C e com um Arduíno. Para mais informações sobre o I²C, consulte o post anterior (clique aqui).

A EEPROM(Electrically-Erasable Programmable Read-Only) é uma memória que permite escrita e gravação e seus dados não são apagados quando não há energia. As memórias flash(pen driver, SD e outros), são uma evolução desse tipo de memória. A EEPROM pode ser utilizada para armazenar algumas informações do seu programa que precisam ser salvas quando não houver energia. Para mais informações sobre esse tipo de memória, consulte aqui.

Os micro-controladores que estão presentes nas mais diversas versões do Arduíno, têm diferentes quantidades de memória EEPROM disponíveis.

  • ATmega328 - 1KB (1024 bytes)
  • ATmega168 e ATmega8 - 512 bytes
  • ATmega1280 e ATmega2560 - 4 KB (4096 bytes)
É possível acessar a EEPROM dos micro-controladores e há uma biblioteca disponível para isso na própria IDE do Arduíno. A documentação pode ser encontrada aqui.

Mas, neste post, iremos focar no uso de EEPROMs externas que utilizam o barramento I²C para leitura e escrita.

Existem no mercado diversas marcas e modelos de memória, com os mais diversos tamanhos e para cada uma delas, há um protocolo definido para se comunicar e por isso, é difícil ter um código genérico de acesso. Esse exemplo funcionará para as EEPROMs do tipo 24C16,que são bem comuns. 

EEPROM 24C16WP

Essa memória tem 16 Kbits (2 Kbytes) de armazenamento, permitindo a leitura e escrita byte-a-byte ou em blocos (páginas). Aqui, iremos abordar a leitura e escrita byte-a-byte.

Montagem

Segue a lista de materiais utilizados:
  • 1 Memória EEPROM 24C16WP
  • 1 Arduíno Uno
  • 1 Protoboard
  • Alguns fios

Segue abaixo os pinos da memória:
Pinagem 24C16WP

Os pinos 6 (SCL) e 5 (SDA), são utilizados para o barramento I²C e devem ser conectados aos pinos analógicos do Arduíno A4 (SDA - Data ) e A5 (SCL - Clock)

O endereçamento I²C da memória inicia em 0x50 e vai até 0x57. Os pinos NC são utilizados para o ajustar esse endereçamento se aterrados(GND) ou ligados ao VCC.

Exemplo:
  • NC - 0, NC - 0, NC - 0 -> Endereço da memória 0x50
  • NC - 0, NC - 0, NC - 1 -> Endereço da memória 0x51
  • NC - 0, NC - 1, NC - 0 -> Endereço da memória 0x52

Essa configuração é interessante, pois podemos colocar até 8 memórias no mesmo barramento.

O pino 7(WC), é utilizado para indicar se a memória está protegida contra gravação ou não. Se "0" - ligado ao GND, a memória pode ser escrita. Se "1" - ligada ao VCC, protegida contra gravação.

Para o nosso exemplo, utlizaremos os pinos abaixo:

EEPROM Arduíno
6(SCL) A5(SCL)
5(SDA) A4(SDA)
8(VCC) VCC
4(VSS) GND
7(WC) GND

Segue abaixo do esquemático:

Esquema feito no programa Fritzing
Vamos ao programa:


#include ‹Wire.h›    
 
#define i2cAddress 0x50 //Endereço da memoria
 
void setup(void)
{
  Serial.begin(115200);
  Wire.begin();  //inicializando I2C

  int sizeBuf = 21;
  char buf[sizeBuf] = "teste gravando eeprom";
  
  Serial.print("Gravando... \n");
  for(int i = 0; i < sizeBuf; i++)
  {
    writeI2CEEPROM(i2cAddress,i,buf[i]);
  }
  
  Serial.print("Lendo... \n");
  for(int i = 0; i < sizeBuf; i++)
  {
    Serial.print((char)readI2CEEPROM(i2cAddress,i));  
  }
  Serial.print("\nFim... \n");
}
 
void loop(){}

void writeI2CEEPROM(int i2cAddr, byte memoryAddress, byte data)
{
  Wire.beginTransmission(i2cAddr);//iniciando barramento para o endereco I2C da memoria
  Wire.write(memoryAddress);//informando endereco da memoria 
  Wire.write(data);//dados
  Wire.endTransmission();//finalizando transmissao do emissor
  delay(5);
}

byte readI2CEEPROM(int i2cAddr, byte memoryAddress)
{
   byte data = 0xFF;
 
   Wire.beginTransmission(i2cAddr);//iniciando barramento para o endereco I2C da memoria
   Wire.write(memoryAddress);//informando endereco da memoria
   Wire.endTransmission();//finalizando transmissao do emissor
   Wire.requestFrom(i2cAddr,1);//avisando a memoria que espera-se dado (1 byte)
   if (Wire.available()){
     data = Wire.read();
  }
  return data;
}


No código acima, iremos escrever na memória a frase "teste gravando eeprom" e na sequência, os valores serão recuperados.

Vamos explicar alguns pontos do código.

void writeI2CEEPROM(int i2cAddr, byte memoryAddress, byte data)
{
  Wire.beginTransmission(i2cAddr);//iniciando barramento para o endereco I2C da memoria
  Wire.write(memoryAddress);//informando endereco da memoria 
  Wire.write(data);//dados
  Wire.endTransmission();//finalizando transmissao do emissor
  delay(5);
}

O método writeI2CEEPROM tem três parâmetros:

  • i2cAddr - Endereço I²C da memória
  • memoryAddress - endereço da memória (0 até 255)
  • data - byte que será gravado

Como já citado anteriormente, essa memória suporta até 2 Kbytes e por isso, ela terá 256 endereços para escrita e leitura (de 0 atá 255). Para escrever na memória, é necessário iniciar a transmissão, indicar o endereço da memória, enviar o byte e finalizar a transmissão. Esse protocolo, está definido no datasheet da memória, que pode ser consultado aqui.

byte readI2CEEPROM(int i2cAddr, byte memoryAddress)
{
   byte data = 0xFF;
 
   Wire.beginTransmission(i2cAddr);//iniciando barramento para o endereco I2C da memoria
   Wire.write(memoryAddress);//informando endereco da memoria
   Wire.endTransmission();//finalizando transmissao do emissor
   Wire.requestFrom(i2cAddr,1);//avisando a memoria que espera-se dado (1 byte)
   if (Wire.available()){
     data = Wire.read();
  }
  return data;
}

O método readI2CEEPROM tem dois parâmetros e retorna o byte gravado.
  • i2cAddr - Endereço I²C da memória
  • memoryAddress - endereço da memória (0 até 255)
Assim como na escrita, o protocolo de leitura está definido no datasheet da memória mas, de maneira resumida temos: inicio da transmissão, envia-se o endereço, finalização a transmissão, requisita-se a resposta e faz-se a leitura do valor se estiver disponível.

Segue a saída desse programa:

Para comprovar a a gravação, você pode comentar o trecho que faz a gravação. Ficando assim: