Sensor de temperatura on-line com Arduino, DS18B20 e DS1307

D.I.Y.  |    09/02/2011   |   10857 hits   |   J. Ricardo Simões Rodrigues

Neste projeto publicamos em uma aplicação disponível na internet os dados referentes às leituras da temperatura de um ambiente. Para tanto, faz-se uso das facilidades do computador físico Arduino e alguns módulos amplamente discutidos e documentados na rede.

Sensor de temperatura on-line com Arduino, DS18B20 e DS1307

Diagrama do sensor de temperatura on-line com Arduino, DS18B20 e DS1307

Arduino Duemilanove

Arduino, segundo a Wikipedia, é um computador físico baseado numa simples plataforma de hardware livre, projetada com um microcontrolador de placa única, com suporte de entrada/saída embutido e uma linguagem de programação padrão, na qual tem origem em Wiring, e é essencialmente C/C++. O objetivo do projeto é criar ferramentas que são acessíveis, com baixo custo, flexíveis e fáceis de se usar por artistas e amadores. Principalmente para aqueles que não teriam alcance aos controladores mais sofisticados e de ferramentas mais complicadas.

Pode ser usado para o desenvolvimento de independentes objetos interativos, ou ainda para ser conectado a um computador hospedeiro. Uma típica placa Arduino é composta por um controlador, algumas linhas de E/S digital e analógica, além de uma interface serial ou USB, para interligar-se ao hospedeiro, que é usado para programá-la e interação em tempo real. Ela em si não possui qualquer recurso de rede, porém é comum combinar um ou mais Arduinos deste modo, usando extensões apropriadas chamadas de shield. A interface do hospedeiro é simples, podendo ser escrita em várias linguagens.

São vários modelos disponíveis, veja mais em http://arduino.cc/en/Main/Hardware.

Arduino 2009

Uma placa Arduino 2009

O Arduino Duemilanove ("2009") (http://arduino.cc/en/Main/ArduinoBoardDuemilanove) é uma placa microcontroladora baseada no ATmega168 ou ATmega328. Possui 14 entradas e saídas digitais (pinos), das qusia 6 podem ser usadas como PWM, 6 entradas analógicas, um cristal oscilador de 16 MHz, uma conexão USB, tomada de força ou bateria. Basta conectar a placa ao um computador pela USB ou a um adaptador de energia e o aparelho começa a funcionar.

Adiante uma tabela com as principais características do Arduino 2009 usado neste projeto.

Microcontrolador

Atmega168 ou ATmega328

Voltagem de operação

5V

Voltagem de entrada (recomendações)

7-12V

Voltagem de entrada (limites)

6-20V

Pinos de I/O digital

14 (6 dispõe de saída PWM)

Pinos de entrada analógica

6

Corrente de I/O por pino

40 mA

Memória flash

16 KB (ATmega168) ou32 KB (Atmega328), sendo 2 KB usados pelo bootloader

SRAM

1 KB (ATmega168) ou 2 KB (ATmega328)

EEPROM

512 bytes (ATmega168) ou 1 KB (ATmega328)

Clock

16 MHz

DS1307

Diagrama do DS1307

Diagrama do DS1307

O DS1307 (http://www.maxim-ic.com/datasheet/index.mvp/id/2688) é um relógio de tempo real serial (real-time clock – RTC) com 56 bytes de NV SRAM de baixíssimo consumo (500nA quando alimentado com bateria). Dados são transferidos por via serial por um barramento bidirecional I²C. O relógio fornece informações de segundos, minutos, horas, dia, data, mês e ano, podendo ser no formato de 24 ou 12 horas com calendário válido até 2100. Possui um sensor que detecta a ausência de fornecimento de alimentação e muda automaticamente para a bateria, mantendo as informações tempo íntegras.

DS18B20
O DS18B20 (http://www.maxim-ic.com/datasheet/index.mvp/id/2812) é um termômetro digital que fornece medições de temperatura em graus Celcius com 9 a 12 bits de resolução. Ele se comunica pelo barramento 1-Wire® (requer apenas uma linha de dados e respectivo terra) com um microcontrolador e é alimentado com voltagens de 3.0V a 5.5V. Opera de -55°C a +125°C com acurácia de ±0.5°C no intervalo de -10°C a +85°C. Cada DS18B20 possui um código de 64 bits que permite vários deles conviverem em um mesmo barramento.

Tanto o termômetro quanto o relógio podem ser encontrados já montados em pequenos blocos que podem ser facilmente ligados ao Arduino. A vantagem é que o CI já vem com todos os componentes necessários para seu funcionamento, alguns deles, às vezes difíceis de se encontrar no varejo. Perde-se em personalização, por outro lado. Para facilitar podemos utilizar um shield que ajuda fornecendo conexões com três linhas já agrupadas: dados, alimentação e terra.

Módulo DS1307 para Arduino

Módulo DS1307 para Arduino

Módulo DS18b207 para Arduino

Módulo DS18b207 para Arduino

Módulo DS18b207 ligado ao Shield V4

Módulo DS18b207 ligado ao Shield V4

Arduino Shield V4

Um Arduino Shield V4

Se for construir o seu, um guia está em http://lusorobotica.com/index.php?topic=681.0.

A aplicação Arduino

A maior parte do código referente ao RTC1307 foi copiada de http://wiring.org.co/learning/libraries/realtimeclock.html.

Saliento que a parte referente ao RTC1307 é dispensável. A informação de tempo poderia ser obtida no sistema ao qual o Arduino está ligado. Mas como pretendia testar o RTC1307 e posteriormente ligar o Arduino diretamente à rede, já deixei o RTC1307 devidamente plugado.

Já a biblioteca para facilitar o acesso ao DS18B20 está disponível em http://milesburton.com/index.php/Dallas_Temperature_Control_Library.

{codecitation class="brush: cpp; " width="500px"}

#include <Wire.h>
#include <OneWire.h>
#include <DallasTemperature.h>

//RTC1307
int clockAddress = 0x68; //I2C address
int command = 0;
long previousMillis = 0;
byte second, minute, hour, dayOfWeek, dayOfMonth, month, year;
byte test;

//Dallas Temperature IC Control
#define ONE_WIRE_BUS 2
OneWire oneWire(ONE_WIRE_BUS);
DallasTemperature sensors(&oneWire);


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

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

void setDateDs1307()
{
second = (byte) ((Serial.read() - 48) * 10 + (Serial.read() - 48));
minute = (byte) ((Serial.read() - 48) *10 + (Serial.read() - 48));
hour = (byte) ((Serial.read() - 48) *10 + (Serial.read() - 48));
dayOfWeek = (byte) (Serial.read() - 48);
dayOfMonth = (byte) ((Serial.read() - 48) *10 + (Serial.read() - 48));
month = (byte) ((Serial.read() - 48) *10 + (Serial.read() - 48));
year= (byte) ((Serial.read() - 48) *10 + (Serial.read() - 48));
Wire.beginTransmission(clockAddress);
Wire.send(0x00);
Wire.send(decToBcd(second));
Wire.send(decToBcd(minute));
Wire.send(decToBcd(hour));
Wire.send(decToBcd(dayOfWeek));
Wire.send(decToBcd(dayOfMonth));
Wire.send(decToBcd(month));
Wire.send(decToBcd(year));
Wire.endTransmission();
}

void getDateDs1307() {
Wire.beginTransmission(clockAddress);
Wire.send(0x00);
Wire.endTransmission();
Wire.requestFrom(clockAddress, 7);
second = bcdToDec(Wire.receive() & 0x7f);
minute = bcdToDec(Wire.receive());
hour = bcdToDec(Wire.receive() & 0x3f);
dayOfWeek = bcdToDec(Wire.receive());
dayOfMonth = bcdToDec(Wire.receive());
month = bcdToDec(Wire.receive());
year = bcdToDec(Wire.receive());
Serial.print(hour, DEC);
Serial.print(":");
Serial.print(minute, DEC);
Serial.print(":");
Serial.print(second, DEC);
Serial.print(" ");
Serial.print(dayOfMonth, DEC);
Serial.print("/");
Serial.print(month, DEC);
Serial.print("/");
Serial.print(year, DEC);
Serial.println(" ");

}

void getDateMysql() {
Wire.beginTransmission(clockAddress);
Wire.send(0x00);
Wire.endTransmission();
Wire.requestFrom(clockAddress, 7);
second = bcdToDec(Wire.receive() & 0x7f);
minute = bcdToDec(Wire.receive());
hour = bcdToDec(Wire.receive() & 0x3f);
dayOfWeek = bcdToDec(Wire.receive());
dayOfMonth = bcdToDec(Wire.receive());
month = bcdToDec(Wire.receive());
year = bcdToDec(Wire.receive());

Serial.print((2000 + year), DEC);
Serial.print("-");
Serial.print(month, DEC);
Serial.print("-");
Serial.print(dayOfMonth, DEC);
Serial.print(" ");
Serial.print(hour, DEC);
Serial.print(":");
Serial.print(minute, DEC);
Serial.print(":");
Serial.print(second, DEC);
}

void setup() {
Wire.begin();
Serial.begin(9600);
}

void loop() {
if (Serial.available()) {
command = Serial.read();
if (command == 86) { //"V" Set Date
setDateDs1307();
getDateDs1307();
Serial.println(" ");
}
else if (command == 81) {//"Q" RTC1307 Memory Functions
delay(100);
if (Serial.available()) {
command = Serial.read();
if (command == 49) {
Wire.beginTransmission(clockAddress);
Wire.send(0x08);
for (int i = 1; i <= 27; i++) {
Wire.send(0xff);
delay(100);
}
Wire.endTransmission();
getDateDs1307();
Serial.println(": RTC1307 Initialized Memory");
}
else if (command == 50) {//"2" RTC1307 Memory Dump
getDateDs1307();
Serial.println(": RTC 1307 Dump Begin");
Wire.beginTransmission(clockAddress);
Wire.send(0x00);
Wire.endTransmission();
Wire.requestFrom(clockAddress, 64);
for (int i = 1; i <= 64; i++) {
test = Wire.receive();
Serial.print(i);
Serial.print(":");
Serial.println(test, DEC);
}
Serial.println(" RTC1307 Dump end");
}
else if (command == 51) {//"3" Show Time
getDateDs1307();
}
}
}else if (command == 80) { //"P" Retorna data e temperatura
sensors.requestTemperatures();
delay(500);
sensors.requestTemperatures();
Serial.print("temp1|");
Serial.print(sensors.getTempCByIndex(0));
Serial.print("#datetime|");
getDateMysql();
Serial.println("");
}
}
delay(100);
command = 0;
}

{/codecitation}

O código acima, uma vez compilado, possui 10150 bytes, sobrando ainda 4186 bytes de espaço no Atmega168.

Para colocarmos on-line os dados obtidos via Arduino serão necessários três scripts adiante descritos

A aplicação PERL sensor.pl

Esse script acessa o Arduino, recebe os dados e envia a um servidor remoto.

Por enquanto estou utilizando o sensor ligado via USB a um servidor que executa o Ubuntu Server 10.04. O equipamento é constituído basicamente uma placa mini-itx D510MO (Intel Atom CPU D510 @ 1.66GHz, 4 cores, http://www.intel.com/products/desktop/motherboards/D510MO/D510MO-overview.htm) com 1GB de RAM e 1TB de disco e que funciona como NAS, servidor SAMBA e DLNA.

Aguardo a chegada de um shield ethernet para ligar o Arduino diretamente na rede.

Saliento que fiz testes também com PHP funcionando normalmente por uns dois dias interruptamente. Pretendo voltar a fazer testes com PHP posteriormente. O básico da implementação em PHP era o seguinte trecho:

{codecitation class="brush: php; " width="500px"}

$ser = fopen("/dev/ttyUSB0","r+");
stream_set_timeout($ser, 5);
if (!$ser){
echo "Erro";
}
fputs($ser,'P' . "\n");
$buffer="";
$info = stream_get_meta_data($ser);
print_r($info);
while (!feof($ser)) {
$buffer .= fgets($ser);
}

{/codecitation}

Notem que estava fazendo o acesso direto ao dispositivo /dev/ttyUSB0. Porém há uma classe muito interessante para abstrair o acesso à interface serial, tanto em Linux quando em Windows. A classe está em http://www.phpclasses.org/package/3679-PHP-Communicate-with-a-serial-port.html.

{codecitation class="brush: perl; " width="500px"}

use Device::SerialPort;
use LWP::UserAgent;
my $ua = new LWP::UserAgent;

my $port = Device::SerialPort->new("/dev/ttyUSB0");
$port->databits(8);
$port->baudrate(9600);
$port->parity("none");
$port->stopbits(1);

my $count = 1;
my $char = "";
while ($count) {
$char = $port->lookfor();
if ($char) {
print " " . $char . " \n";
$count = 0;
} else {
#P é o comando que faz o Arduino mandar os dados para a Serial
my $count_out = $port->write("P\n");
}
}

my $response = $ua->post('http://servidor/sensor.php', { data => $char} );

my $content = $response->content;

print $content;

{/codecitation}

Esse pequeno script apenas acessa a serial, manda o comando “P”, recebe os dados de data e temperatura do Arduino e faz um POST para a URL http://seuservidor/sensor.php.

Coloquei esse script para ser executados a cada cinco minutos via CRON.

A aplicação PHP sensor.php

Recebe os dados do script sensor.pl via POST e insere os mesmos em uma tabela de um banco de dados mySQL.

O esquema SQL do DB é o seguinte:

{codecitation class="brush: sql; " width="500px"}

--
-- Estrutura da tabela `arduino_sensor`
--
CREATE TABLE IF NOT EXISTS `arduino_sensor` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`name` varchar(45) NOT NULL,
`descr` text NOT NULL,
`type` int(11) NOT NULL,
PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
-- --------------------------------------------------------
--
-- Estrutura da tabela `arduino_sensor_type`
--
CREATE TABLE IF NOT EXISTS `arduino_sensor_type` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`name` varchar(45) NOT NULL,
`descr` text NOT NULL,
`un` int(11) NOT NULL,
PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8

-- --------------------------------------------------------
--
-- Estrutura da tabela `arduino_sensor_un`
--
CREATE TABLE IF NOT EXISTS `arduino_sensor_un` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`name` varchar(45) NOT NULL,
`desc` text NOT NULL,
`symbol` varchar(15) NOT NULL,
`type` varchar(15) NOT NULL,
PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
-- --------------------------------------------------------
--
-- Estrutura da tabela `arduino_sensor_value`
--
CREATE TABLE IF NOT EXISTS `arduino_sensor_value` (
`id` bigint(20) NOT NULL AUTO_INCREMENT,
`timestamp` datetime NOT NULL,
`sensor` int(11) NOT NULL,
`value` float NOT NULL,
PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;

{/codecitation}

O script PHP é o que segue. Aqui é utilizada a biblioteca PEAR para abstração do acesso ao DB.

{codecitation class="brush: php; " width="500px"}

set_include_path(implode(PATH_SEPARATOR, array(
realpath('/../PEAR'),
get_include_path()
)));
require_once 'DB.php';
$user = 'usuario';
$pass = 'senha';
$host = 'servidormysql';
$db_name = 'nomedodb';
error_reporting(1);
$dsn = "mysql://$user:$pass@$host/$db_name";
$db = DB::connect($dsn);
if (DB::isError($db)) {
die ($db->getMessage());
};
if(isset($_POST)){
$res = explode ("#", $_POST['data']);
print_r($_POST);
$sth = $db->prepare("INSERT INTO arduino_sensor_value"
. " (timestamp, sensor, value)"
. " VALUES (?, ?, ?)");
$temp=explode ("|", $res[0]);
$time=explode ("|", $res[1]);
$data = array (
array ($time[1], 1, $temp[1])
);

$res = $db->executeMultiple($sth, $data);
if (DB::isError($res)) {
die($res->getMessage());
}
}

{/codecitation}

Pronto, agora o projeto está on-line. É só aguardar o CRON fazer seu trabalho e popular o DB com as leituras do Arduino. Obviamente, devemos criar uma interface para exibição dos dados, preferencialmente num belo gráfico mostrando a evolução das temperaturas no dia, médias, máximas e mínimas e etc.

Adiante, uma listagem dos módulos usados no projeto e preços de aquisição:

Módulo

Onde

Preço com frete

Arduino Duemilanove 2009 AVR ATmega168-20PU USB board

eBay

U$19.50

Arduino DS18B20 digital temperature sensor module Shiel

eBay

US $8.50

DS1307 RTC Module Real Time Clock For AVR Arduino PIC

eBay

US $7.95

Arduino Sensor Shield digital analog module & servos V4

eBay

US $8.50

Abaixo alguns dados plotados a partir das temperaturas coletadas.

Última edição concluída em 16/02/2011 por J. Ricardo Simões Rodrigues.