terça-feira, 9 de março de 2010

Bússola Digital com sensor de temperatura e umidade utilizando o Program-Me da Globalcode.


Neste post descrevo como é simples conectar sensores tipo bússola, temperatura e umidade ao Program-ME utilizando a interface I2C, e construir uma interessante bússola digital com display LCD gráfico.

O chip ATMEGA utilizado em todos os Arduinos possui uma interface muito interessante chamada I2C . Essa interface permite a conexão de diversos sensores, memórias EEPROM e inclusive outros Arduinos utilizando apenas dois fios além do terra (GND). Essa interface funciona como um barramento onde cada dispositivo tem um endereço e o dispositivo mestre, neste caso o ATMEGA, controla o acesso aos demais dispositivos denominados escravos.

Para aprender mais sobre esta interface recomendo os links abaixo:

http://www.arduino.cc/playground/Learning/I2C

http://www.nxp.com/acrobat_download2/literature/9398/39340011.pdf (especificação da interface)

Neste projeto utilizei dois sensores, um magnético que utiliza o chip HMC6352 da Honeywell e um sensor de temperatura e umidade que utiliza o chip SHT-15 da Sensirion, sendo que ambos são compatíveis com a interface I2C, apesar de que o SHT-15 não é endereçável por esta interface, porém pode conviver com os outros dispositivos no barramento I2C desde que isso seja gerenciado pelo software.




Para exibir as informações dos sensores foi utilizado um display LCD gráfico de 128x64 pixels em conjunto com a biblioteca KS0108.

O interessante da biblioteca KS0108, é que podemos transferir imagens para o display com apenas uma linha de comando. Para isso, a imagem .BMP deve ser convertida por um sketch de Processing chamado glcdBitmap em um arquivo hexadecimal que será utilizado pela biblioteca para identificar quais são os pixels do display que devem ou não ser ativados.
Aí é só incluir o arquivo gerado no seu programa e utilizar quando necessário.

Existe um programa muito simples chamado FASTLCD que ajuda muito na construção das telas gráficas. Na figura abaixo temos o layout da tela deste projeto sendo construída no FASTLCD.



Após construir a tela no FASTLCD devemos salvar o arquivo .BMP no diretório “..\hardware\libraries\ks0108\Processing\glcdBitmap\data” que fica dentro do diretório da IDE do Arduino.

Com o arquivo BMP salvo no diretório acima, abrimos na IDE de Processing o arquivo “..\hardware\libraries\ks0108\Processing\glcdBitmap\glcdBitmap.pde”



Então é só editar a linha abaixo colocando o nome do seu arquivo .BMP no lugar de “ArduinoIcon.bmp”

String sourceImage = "ArduinoIcon.bmp"; // mude esta linha para o nome do seu BMP

Ao executar este Processing, será gerado um arquivo nomedoseuBMP.h no diretório “..\hardware\libraries\ks0108\Processing\glcdBitmap” correspondente a imagem.

Então basta no início do seu programa incluir a figura com o comando do exemplo abaixo:

#include "Layout_bussola.h"

E depois para exibir a figura no display basta dar o seguinte comando:

GLCD.DrawBitmap(Layout_bussola, 0,0, BLACK); //desenha layout básico do display para bússola.

IMPORTANTE: Caso apresente falha na compilação do seu programa, será necessário copiar o arquivo .h do diretório “..\hardware\libraries\ks0108\Processing\glcdBitmap” para o diretório “..\hardware\libraries\ks0108”

O método GLCD.DrawBitmap permite especificar a partir de qual pixel será carregada a imagem, então para alterar as direções da bússola, criei 8 bitmaps diferentes (figuras abaixo) com resolução 64x64, ou seja, somente a metade direita do display e carrego elas a partir do pixel (64,0) conforme o valor recebido do sensor magnético HMC6352.




Com isso o trecho do programa que atualiza a indicação da bússola no display fica bem simples como pode ser observado na listagem do programa.



// Bussola com medidor de temperatura e humidade utilizando PROGRAM-ME

#include
#include // biblioteca para display ITM12864
#include "SystemFont5x7.h" // definicao de fontes pequenas (5x7)Arial
#include "Layout_bussola.h" // bitmap de layout inicial do display
#include "N.h" // bitmap bussola Norte
#include "NE.h" // bitmap bussola Nordeste
#include "L.h" // bitmap bussola Leste
#include "SU.h" // bitmap bussola Sudeste
#include "S.h" // bitmap bussola Sul
#include "SO.h" // bitmap bussola Sudoeste
#include "O.h" // bitmap bussola Oeste
#include "NO.h" // bitmap bussola Noroeste
#include "logo_globalcode.h" // bitmap logo Globalcode

int EnderecoBussola = 0x42 >> 1; // datasheet informa 0x42, biblioteca wire só utiliza 7 bits fazemos um shift para ficar com MSB
int angulo = 0; //variavel para armazenamento do angulo medido
int temperatureCommand = B00000011; // comando para leitura de temperatura
int humidityCommand = B00000101; // comando para leitura de umidade
int clockPin = 13; // porta usada para clock da interface com sensor de Temperaruta e umidade
int dataPin = 12; // porta usada para dados da interface com sensor de Temperaruta e umidade
int ack; // acknowledgment para erros
long val;
float temperature; //variavel para armazenamento da leitura de temperatura
float humidity; //variavel para armazenamento da leitura de umidade

void setup() {
Wire.begin();
Serial.begin(9600); // configura serial em 9600
GLCD.Init(NON_INVERTED); // inicializa a library no modo "non inverted" de escrita de pixels
GLCD.ClearScreen(); // limpa a tela do display
GLCD.DrawBitmap(logo_globalcode, 0,0, BLACK); //desenha logo da Globalcode no display
delay (4000); //mantem logo globalcode no display por 4 segundos
GLCD.ClearScreen(); // limpa a tela do display
GLCD.DrawBitmap(Layout_bussola, 0,0, BLACK); //desenha layout básico do display para bussola
}

void loop() {
// Bussola
Wire.beginTransmission(EnderecoBussola); //inicia comunicação I2C com a bussola
Wire.send('A'); // envia comando para bussola executar mediçao do angulo
Wire.endTransmission();
delay(10); // aguardar pelo menos 6 milisegundos antes de efetuar leitura do angulo
Wire.requestFrom(EnderecoBussola, 2); // executa leitura do angulo com 2 bytes
if(2 <= Wire.available()) { // se já estiverem disponiveis os 2 bytes
angulo = Wire.receive(); // recebe o byte mais significativo
angulo = angulo << 8; // desloca o byte mais significativo para sua posicao (MSB)
angulo += Wire.receive(); // recebe o byte menos significativo adicionando ao byte anterior
angulo = int ((double)angulo / 10); // divide por dez para converter de decimos de grau para graus
Serial.println(angulo); // mostra o valor no serial monitor
GLCD.FillRect(16,55,19,7, WHITE); //apaga valor de angulo anterior para evitar sobreposicao
GLCD.SelectFont(System5x7); // seleciona fontes 5x7
GLCD.GotoXY(16,63); // posiciona o cursor na cord x=16, y=63 para escrever o valor do angulo obtido da bussola
GLCD.PrintNumber(angulo); // escreve o valor do angulo no display
if (angulo >= 68 and angulo <= 112) {
GLCD.DrawBitmap(L, 64,0, BLACK); //desenha bussola na posicao LESTE
}
if (angulo >= 113 and angulo <= 157) {
GLCD.DrawBitmap(SU, 64,0, BLACK); //desenha bussola na posicao SUDESTE
}
if (angulo >= 158 and angulo <= 202) {
GLCD.DrawBitmap(S, 64,0, BLACK); //desenha bussola na posicao SUL
}
if (angulo >= 203 and angulo <= 247) {
GLCD.DrawBitmap(SO, 64,0, BLACK); //desenha bussola na posicao SUDOESTE
}
if (angulo >= 248 and angulo <= 292) {
GLCD.DrawBitmap(O, 64,0, BLACK); //desenha bussola na posicao OESTE
}
if (angulo >= 293 and angulo <= 337) {
GLCD.DrawBitmap(NO, 64,0, BLACK); //desenha bussola na posicao NOROESTE
}
if ((angulo >= 338 and angulo <= 360) or (angulo >= 0 and angulo <= 22)) {
GLCD.DrawBitmap(N, 64,0, BLACK); //desenha bussola na posicao NORTE
}
if (angulo >= 23 and angulo <= 67) {
GLCD.DrawBitmap(NE, 64,0, BLACK); //desenha bussola na posicao NORDESTE
}
}

// Temperatura e umidade

// le valor da temperatura e converte para graus centigrados
sendCommandSHT(temperatureCommand, dataPin, clockPin);//chama rotina de leitura solicitando temperatura
waitForResultSHT(dataPin);// aguarda resultado das leituras
val = getData16SHT(dataPin, clockPin);//lê os dois bytes correspondentes a temperatura
skipCrcSHT(dataPin, clockPin);// ignora os dados de CRC
temperature = (float) (-40.1 + 0.01 * val);//calcula a temperatura
Serial.print("temperature: ");
Serial.print(temperature,DEC);//envia valor da temperatura para o serial monitor
GLCD.FillRect(16,9,18,13, WHITE);//limpa campo da umidade no display antes de atualizar
GLCD.SelectFont(System5x7); // seleciona fonte 5x7
GLCD.GotoXY(16,11); // posiciona o cursor na cord x=16, y=11 para escrever o valor da temperatura
GLCD.PrintNumber(temperature); // escreve o valor da temperatura no display

// le valor da umidade
sendCommandSHT(humidityCommand, dataPin, clockPin);//chama rotina de leitura solicitando umidade
waitForResultSHT(dataPin);// aguarda resultado das leituras
val = getData16SHT(dataPin, clockPin);//lê os dois bytes correspondentes a umidade
skipCrcSHT(dataPin, clockPin);// ignora os dados de CRC
humidity = -4.0 + 0.0405 * val + -0.0000028 * val * val;//calcula a umidade
Serial.print("humidity: ");
Serial.print((long)humidity, DEC);//envia valor da umidade para o serial monitor
GLCD.FillRect(16,33,10,7, WHITE);//limpa campo da umidade antes de atualizar
GLCD.SelectFont(System5x7); // seleciona fonte 5x7
GLCD.GotoXY(16,33); // posiciona o cursor na cord x=13, y=39 para escrever o valor da umidade
GLCD.PrintNumber(humidity); // escreve o valor da umidade no display

delay(100); // aguarda 100 ms antes de executar nova leitura
}


// rotina para deslocar bits das leituras do sensor SHT15
int shiftIn(int dataPin, int clockPin, int numBits) {
int ret = 0;
for (int i=0; i< numBits; ++i) {
digitalWrite(clockPin, HIGH);
//delay(10); not needed :)
ret = ret*2 + digitalRead(dataPin);
digitalWrite(clockPin, LOW);
}
return(ret);
}

// rotina para envio de comandos ao sensor SHT15
void sendCommandSHT(int command, int dataPin, int clockPin) {
int ack;
// inicia transmissao
pinMode(dataPin, OUTPUT);
pinMode(clockPin, OUTPUT);
digitalWrite(dataPin, HIGH);
digitalWrite(clockPin, HIGH);
digitalWrite(dataPin, LOW);
digitalWrite(clockPin, LOW);
digitalWrite(clockPin, HIGH);
digitalWrite(dataPin, HIGH);
digitalWrite(clockPin, LOW);

// envia comando (os 3 bits mais significativos sao o endereco que deve ser 000 os 5 bits menos significativos sao o comando)
shiftOut(dataPin, clockPin, MSBFIRST, command);

// verifica se foi recebido ack
digitalWrite(clockPin, HIGH);
pinMode(dataPin, INPUT);
ack = digitalRead(dataPin);
if (ack != LOW)
Serial.println("ACK error 0");
digitalWrite(clockPin, LOW);
ack = digitalRead(dataPin);
if (ack != HIGH)
Serial.println("ACK error 1");
}

// rotina para aguardar resultado da leitura solicitada ao SHT15
void waitForResultSHT(int dataPin) {
int ack;
pinMode(dataPin, INPUT);
for(int i=0; i<100; ++i) {
delay(10);
ack = digitalRead(dataPin);
if (ack == LOW)
break;
}
if (ack == HIGH)
Serial.println("ACK error 2");
}

// rotina que obtem dados da leitura executada pelo SHT15
long getData16SHT(int dataPin, int clockPin) {
long val;
//obtem byte mais significativo
pinMode(dataPin, INPUT);
pinMode(clockPin, OUTPUT);
val = shiftIn(dataPin, clockPin, 8);
Serial.println(val);
val *= 256; // this is equivalent to val << 8;
Serial.println(val);

// envia sinal ACK para sensor SHT15
pinMode(dataPin, OUTPUT);
digitalWrite(dataPin, HIGH);
digitalWrite(dataPin, LOW);
digitalWrite(clockPin, HIGH);
digitalWrite(clockPin, LOW);

// obtem byte menos significativo
pinMode(dataPin, INPUT);
val |= shiftIn(dataPin, clockPin, 8);
return val;
}

// rotina para ignorar dados de CRC vindos do sensor SHT15
void skipCrcSHT(int dataPin, int clockPin) {
pinMode(dataPin, OUTPUT);
pinMode(clockPin, OUTPUT);
digitalWrite(dataPin, HIGH);
digitalWrite(clockPin, HIGH);
digitalWrite(clockPin, LOW);
}






Na foto abaixo temos todo o conjunto montado em um protoboard e mais abaixo temos dois vídeos demonstrando o funcionamento do circuito.



Obs: As bússolas apontam sempre para o Norte, porém neste projeto configurei de forma que seja indicado pela seta o rumo que estamos seguindo e o NORTE fica fixo à frente. Para operar no modo padrão, bastaria criar as imagens com o norte acompanhando a ponta da seta e rotacionar tudo no sentido oposto ao que está sendo feito. É possível também conectar uma chave em uma das portas do Program-ME e com isso seria possível selecionar dois modos de operação da bússola.

O próximo desafio é adaptar a biblioteca KS0108 para trabalhar com display serial, assim não ocuparemos tantas portas do Program-Me só para o display.


Ache outros vídeos como este em Globalcode Program-ME



Ache outros vídeos como este em Globalcode Program-ME

8 comentários:

  1. Olá,

    Porque não disponibilizam o sketch? Sem o sketch uma boa idéia não pode ser totalmente aprendida.
    Acredito que a idéia de blog é compartilhar informações portanto...

    ResponderExcluir
  2. Bom dia , estaremos disponibilizando em breve.

    Estamos vendo qual é a melhor forma de fazer isso.

    Agradeço a sugestão

    atenciosamente

    José Luiz

    ResponderExcluir
  3. José Luiz, o post está sensacional. Muito legal mesmo.

    []s
    Yara

    ResponderExcluir
  4. Onde voce comprou o sensor SHT15? Qto custou?



    Armando. armando.camara@gmail.com

    ResponderExcluir
  5. Armando, desculpe a demora em responder mas não tinha visto sua pergunta. O sensor foi comprado na SparkFun por US40.

    atenciosamente

    José Luiz

    ResponderExcluir
  6. Bom dia, meu nome é Expedito e estou terminando o curso de Mecatronica. O meu TCC e controlar a navegabilidade de um barco, já tinha encontrado esta bússola, o hoje encontrei seu blog, verifique seu projeto e é a mesma ideia. Eu não entendo muito de bússola conheço de programação. Você poderia me ajudar com alguma informação sobre esta bússola?

    ResponderExcluir
  7. Boa tarde.
    Gostaria de parabeniza-lo pelo projeto e pelo tutorial.
    Estou precisando de desenvolver algo semelhante e gostaria de saber onde você comprou o HCM6352.
    Obrigada.

    ResponderExcluir
  8. Dê uma revisada no link : http://www.arduino.cc/playground/Learning/I2C

    Não achei o esquema deste projeto...

    Os dois sensores foram comprados na SparkFun ?

    Parabéns pelo post.

    ResponderExcluir