.: HosiriS :.

Informatica e non solo

MeteArduino

Posted by hosiris su dicembre 1, 2011

Analisi iniziale
Il modulo meteo che verrà progettato dovrà soddisfare alcune semplici specifiche:

  • misurarazione di temperatura, pressione e umidità
  • inseririmento automatico dei dati misurati in un database
  • presentazione dei dati in maniera semplice ed intuitiva

La piattaforma di prototipazione che verrà utilizzata sarà Arduino con l’aggiunta di un piccola breadboard per le prime connessioni. La comunicazione tra Arduino e PC avverrà tramite USB.
Sono specifiche di massima, non restrittive, ma scritte solo per dare una linea guida al progetto.

Scelta dei sensori
Per la scelta dei sensori mi sono concentrato maggiormente sul costo degli stessi, in quanto per un progetto casalingo non è richiesta né una grande precisione né molta accuratezza.

Le soluzioni trovate sono le seguenti:

  • DS18B20: sensore di temperatura digitale
  • MPL115A1: sensore di pressione digitale
  • HIH4030: sensore di umidità

Di seguito analizzerò gli schemi di collegamento e la programmazione dei vari moduli.

Temperatura
Leggendo il datasheet del sensore DS18B20 si nota che per la connessione del sonsore ad Arduino è necessario utilizzare una resistenza di pull-up da 4.7kOhm. In figura è mostrato lo schema delle connessioni.

Questo sensore comunica tramite protocollo 1-Wire per cui sarà necessario determinare l’indirizzo a 64 bit che permette di riconoscerlo sul canale di comunicazione.
Scarichiamo la libreria OneWire dal seguente link e scompattiamola nella cartella “libraries” sotto la cartella d’installazione dell’SDK.
Apriamo un nuovo progetto nello SDK di Arduino e scriviamo il seguente programma:


#include 
OneWire ow(2);
void setup(void)
{
	Serial.begin(9600);
	lookUpSensors();
}
 
void lookUpSensors()
{
	byte address[8];
	int i=0;
	byte ok = 0, tmp = 0;

	Serial.println("--Ricerca avviata--");
 
	while (ow.search(address))
	{
		tmp = 0;
 
		if (address[0] == 0x10)
		{
			Serial.println("A questo indirizzo si trova una DS18S20 : ");
			tmp = 1;
		}
		else
		{
			if (address[0] == 0x28)
			{
				Serial.println("A questo indirizzo si trova una DS18B20 : ");
				tmp = 1;
			}
		}
 
		if (tmp == 1)
		{
			if (OneWire::crc8(address, 7) != address[7])
			{
				Serial.println(" (ma CRC8 non valido)");
			}
			else
			{
				for (i=0;i<8;i++)
				{
					Serial.print("0x");
					if (address[i] < 9)
					{
						Serial.print(";0");
					}
					Serial.print(address[i],HEX);
					if (i<7)
					{
						Serial.print(",");
					}
				}
				Serial.println(";");
				ok = 1;
			}
		}
	}
 
	if (ok == 0)
	{
		Serial.println("Non ho trovato sonde di temperatura");
	}
	Serial.println("--Ricerca terminata--");
}
 
void loop(void)
{
}

Carichiamo il codice ed eseguiamolo, aprendo il monitor leggeremo un codice (a meno di errori) che sarà proprio l’indirizzo del sensore, per cui copiamolo e memorizziamolo dove non può essere perso.
Dopo aver determinato l’indirizzo del sensore sul canale, possiamo passare alla lettura dei dati misurati attraverso il seguente codice:


#include 
OneWire  source(2);
 
//Scriviamo qui il codice che abbiamo salvato
byte sensor[8] = {0x28,0xCE,0x47,0x31,0x02,0x00,0x00,0xA2;
 
void setup(void) {
	Serial.begin(9600);
}
 
void writeTimeToScratchpad(byte* address){
	source.reset();
	source.select(address);
	source.write(0x44,1);
	delay(1000);
}
 
void readTimeFromScratchpad(byte* address, byte* data){
	source.reset();
	source.select(address);
	source.write(0xBE);
	for (byte i=0;i<9;i++){
		data[i] = source.read();
	}
}
 
float getTemperature(byte* address){
	byte data[12];
 
	writeTimeToScratchpad(address);
	readTimeFromScratchpad(address,data);
  
	return ((data[1] << 8 ) + data[0]) * 0.0625;
}
 
void loop(void) {
	float temp;
	temp = getTemperature(sensor);

	Serial.println(temp);
	delay(60000);
}

Carichiamo l’applicazione sulla EEPROM di Arduino ed eseguiamolo. Aprendo il monitor vedremo apparire, ad intervalli di 1 minuti i valori di temperatura misurati.

Pressione
Il sensore MPL115A1 comunica attraverso il protocollo SPI (Serial Protocol Interface) in cui Arduino rappresenta il master ed il sensore lo slave. Lo schema di connessione è presentato in figura

La configurazione per un Arduino Mega prevede l’uso delle seguenti connessioni:

SDN => 49
CSN => 53
SDO => 50
SDI => 51
SCK => 52
GND => 0
VDD => +5V

Dopo aver incluso la libreria passiamo a scrivere il codice necessario alla lattura dei dati:


...
#include 
// Definizione dei PIN
#define MPL115A1_ENABLE_PIN 49
#define MPL115A1_SELECT_PIN 53

// Maschera I/O SPI del MPL115A1
#define MPL115A1_READ_MASK	0x80
#define MPL115A1_WRITE_MASK 0x7F 

// Mappa degli indirizzi del registro
#define PRESH	 0x00		// 80
#define PRESL	 0x02		// 82
#define TEMPH	 0x04		// 84
#define TEMPL	 0x06		// 86

#define A0MSB	 0x08		// 88
#define A0LSB	 0x0A		// 8A
#define B1MSB	 0x0C		// 8C
#define B1LSB	 0x0E		// 8E
#define B2MSB	 0x10		// 90
#define B2LSB	 0x12		// 92
#define C12MSB	0x14		// 94
#define C12LSB	0x16		// 96
#define C11MSB	0x18		// 98
#define C11LSB	0x1A		// 9A
#define C22MSB	0x1C		// 9C
#define C22LSB	0x1E		// 9E

// Macro di conversione
#define FT_TO_M(x) ((long)((x)*(0.3048)))
#define KPA_TO_INHG(x) ((x)*(0.295333727))
#define KPA_TO_ATM(x) ((x)*(0.00986923267))
#define KPA_TO_MMHG(x) ((x)*(7.50061683))
#define KPA_TO_PSIA(x) ((x)*(0.145037738))
#define KPA_TO_KGCM2(x) ((x)*(0.0102))
#define INHG_TO_PSIA(x) ((x)*(0.49109778))
#define DEGC_TO_DEGF(x) ((x)*(9.0/5.0)+32)

...

float pressure_pKa = 0;
 
int P_SENSOR = 22;

...
 
void setup(void) {
	Serial.begin(9600);
	pinMode(P_SENSOR, INPUT);

	// Interfaccia SPI
	SPI.begin();
		
	// Inializzazione dei pin di chip select ed enable
	pinMode(MPL115A1_SELECT_PIN, OUTPUT);
	pinMode(MPL115A1_ENABLE_PIN, OUTPUT);
	
	digitalWrite(MPL115A1_ENABLE_PIN, LOW);
	digitalWrite(MPL115A1_SELECT_PIN, HIGH);
}
 
...
 
float calculatePressurekPa() {
	signed char sia0MSB, sia0LSB;
	signed char sib1MSB, sib1LSB;
	signed char sib2MSB, sib2LSB;
	signed char sic12MSB, sic12LSB;
	signed char sic11MSB, sic11LSB;
	signed char sic22MSB, sic22LSB;
	signed int sia0, sib1, sib2, sic12, sic11, sic22, siPcomp;
	float decPcomp;
	signed long lt1, lt2, lt3, si_c11x1, si_a11, si_c12x2;
	signed long si_a1, si_c22x2, si_a2, si_a1x1, si_y1, si_a2x2;
	unsigned int uiPadc, uiTadc;
	unsigned char uiPH, uiPL, uiTH, uiTL;
		
	writeRegister(0x24, 0x00);
	delay(2);
		
	// Lettura del valore di pressione
	uiPH = readRegister(PRESH);
	uiPL = readRegister(PRESL);
		
	uiPadc = (unsigned int) uiPH << 8;
	uiPadc += (unsigned int) uiPL & 0x00FF;
		
	// Impostiamo i coefficienti
	// a0
	sia0MSB = readRegister(A0MSB);
	sia0LSB = readRegister(A0LSB);
	sia0 = (signed int) sia0MSB << 8;
	sia0 += (signed int) sia0LSB & 0x00FF;
		
	// b1
	sib1MSB = readRegister(B1MSB);
	sib1LSB = readRegister(B1LSB);
	sib1 = (signed int) sib1MSB << 8;
	sib1 += (signed int) sib1LSB & 0x00FF;
		
	// b2
	sib2MSB = readRegister(B2MSB);
	sib2LSB = readRegister(B2LSB);
	sib2 = (signed int) sib2MSB << 8;
	sib2 += (signed int) sib2LSB & 0x00FF;
		
	// c12
	sic12MSB = readRegister(C12MSB);
	sic12LSB = readRegister(C12LSB);
	sic12 = (signed int) sic12MSB << 8;
	sic12 += (signed int) sic12LSB & 0x00FF;
		
	// c11
	sic11MSB = readRegister(C11MSB);
	sic11LSB = readRegister(C11LSB);
	sic11 = (signed int) sic11MSB << 8;
	sic11 += (signed int) sic11LSB & 0x00FF;
		
	// c22
	sic22MSB = readRegister(C22MSB);
	sic22LSB = readRegister(C22LSB);
	sic22 = (signed int) sic22MSB <> 6;
	uiTadc = uiTadc >> 6;
		
	// Step 1 c11x1 = c11 * Padc
	lt1 = (signed long) sic11;
	lt2 = (signed long) uiPadc;
	lt3 = lt1*lt2;
	si_c11x1 = (signed long) lt3;
		
	// Step 2 a11 = b1 + c11x1
	lt1 = ((signed long)sib1)<>14);
		
	// Step 3 c12x2 = c12 * Tadc
	lt1 = (signed long) sic12;
	lt2 = (signed long) uiTadc;
	lt3 = lt1*lt2;
	si_c12x2 = (signed long)lt3;
		
	// Step 4 a1 = a11 + c12x2
	lt1 = ((signed long)si_a11<>11;
		
	// Step 5 c22x2 = c22*Tadc
	lt1 = (signed long)sic22;
	lt2 = (signed long)uiTadc;
	lt3 = lt1 * lt2;
	si_c22x2 = (signed long)(lt3);
		
	// Step 6 a2 = b2 + c22x2
	lt1 = ((signed long)sib2<1);
	lt3 = lt1+lt2;
	si_a2 = ((signed long)lt3>>16);
		
	// Step 7 a1x1 = a1 * Padc
	lt1 = (signed long)si_a1;
	lt2 = (signed long)uiPadc;
	lt3 = lt1*lt2;
	si_a1x1 = (signed long)(lt3);
		
	// Step 8 y1 = a0 + a1x1
	lt1 = ((signed long)sia0<>10);
		
	// Step 9 a2x2 = a2 * Tadc
	lt1 = (signed long)si_a2;
	lt2 = (signed long)uiTadc;
	lt3 = lt1*lt2;
	si_a2x2 = (signed long)(lt3);
		
	// Step 10 pComp = y1 + a2x2
	lt1 = ((signed long)si_y1<<10);
	lt2 = (signed long)si_a2x2;
	lt3 = lt1+lt2;

	siPcomp = lt3/8192;

	// Conversione del valore digitale in un valore decimale
	decPcomp = ((65.0/1023.0)*siPcomp)+50;
		
	return decPcomp;
}

unsigned int readRegister(byte thisRegister) {
	byte result = 0;
	digitalWrite(MPL115A1_SELECT_PIN, LOW);
	SPI.transfer(thisRegister | MPL115A1_READ_MASK);
	result = SPI.transfer(0x00);
	digitalWrite(MPL115A1_SELECT_PIN, HIGH);
		
	return result;	
}

void writeRegister(byte thisRegister, byte thisValue) {
	digitalWrite(MPL115A1_SELECT_PIN, LOW);
	SPI.transfer(thisRegister & MPL115A1_WRITE_MASK);
	SPI.transfer(thisValue);
	digitalWrite(MPL115A1_SELECT_PIN, HIGH);
}

void loop()
{
...
	// Interroghiamo il sensore
	digitalWrite(MPL115A1_ENABLE_PIN, HIGH);
	delay(20);	// Attendiamo
		
	pressure_pKa = calculatePressurekPa();

	digitalWrite(MPL115A1_ENABLE_PIN, LOW);

	Serial.println(KPA_TO_ATM(pressure_pKa),3);
...
}

Umidità


...
#define RH_SENSOR 0
...
int RHAnalogValue;  // Lettura in uscita dal sensore HIH-4030
float RH;  // Valore di Umidita' Relativa espresso in percentuale
...
...
void loop()
{
	...
	RHAnalogValue = analogRead(RH_SENSOR);

	/**
	 * La funzione analogRead restituisce un valore tra 0 e 1023 proporzionale alla
         * tensione applicata sul pin
	 * per cui il valore di tensione e' dato da:	
	 * Vout=(analogRead/1023)*5
	 * Il valore di umidita' relativa e' invece dato da:
	 * RH = (Vout-0.8)/0.031
	 */
	RH = ((((float)RHAnalogValue/1023)*5)-0.8)/0.031;

	/**
	 * Dato che abbiamo anche il valore di temperatura, effettuiamo anche la compensazione per avere un valore di umidita' relativo preciso
	 */
	RH = RH/(1.0546-(0.00216*T));
	...
	Serial.print(RH,3);
}

Schema generale
In figura viene mostrato lo schema generale di connessioni di tutti i sensori con Arduino

Memorizzazione dei valori misurati
Il controllore si aspetta un segnale per poter leggere i dati dai sensori. La gestione della lettura e scrittura sulla porta USB sarà gestita tramite uno script python che farà uso della libreria PySerial.
Lo script si occuperà anche di memorizzare i dati su di un database MySql, che presenta la seguente struttura:


CREATE TABLE IF NOT EXISTS `datas` (
  `id` int(5) NOT NULL AUTO_INCREMENT,
  `date` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP,
  `temperature` decimal(7,5) DEFAULT NULL,
  `humidity` decimal(8,5) DEFAULT NULL,
  `pressure` decimal(8,5) DEFAULT NULL,
  PRIMARY KEY (`id`)
) ENGINE=InnoDB  DEFAULT CHARSET=utf8 ;

Di seguito, invece, viene presentato lo script che permetterà la comunicazione tra PC ed Arduino:

[language=python]
#!/usr/bin/env python

from serial import Serial
import MySQLdb as mysql
import datetime
import time
import sys

dbhost = 'localhost'
dbname = 'measures'
dbuser = sys.argv[2]
dbpass = sys.argv[3]
com = sys.argv[1]

s = Serial('/dev/ttyUSB0', 9600)
while True:
	try:
		if (com != ") :
			db = mysql.connect(dbhost, dbuser, dbpass, dbname)
			cursor = db.cursor()
			
			s.write(com)
			
			read = s.readline()			
			tmp = read.replace("rn", "")
			readed = tmp.split("/")
			print readed
			
			query = "INSERT INTO datas(temperature, humidity, pressure)
					 VALUES ('%f', '%f', '%f')" % 
					 (float(readed[1]), float(readed[0]), float(readed[2]))
			cursor.execute(query)
			db.commit()
			db.close()
			
			sys.exit(0)
	except KeyboardInterrupt:
		print "Bye"
		sys.exit(0)

L’utilizzo dello script python è semplice e prevede che vengano passati tre parametri: il comando da inviare alla piattaforma, il nome utente che ha permessi di scrittura sul database e la sua password:

$ ./getdata G user PaSsWoRd

Il comando di get è rappresentato dalla textit{G che segue il nome dello script nel comando.

Presentazione dei dati
Il livello di presentazione sarà gestito attraverso una semplice applicazione web che preleverà i dati dala database attraverso uno script PHP e creerà dei grafici tramite una libreria javascript.
Analizziamo il codice che preleva i dati dal database:


$dns = "mysql:dbname=".$dbname.";host=".$dbhost;
$conn = new PDO($dns, $dbuser, $dbpass);

$query = "	SELECT *
		FROM ".$datatable."
		ORDER BY date";
$results = $conn->query($query);
$lists = array();
foreach ($results as $result) {
	$lists[$result['date']] = array(
		'temperature' => $result['temperature'], 
		'humidity' => $result['humidity'], 
		'pressure' => $result['pressure']*101325
	);
}

$query = "	SELECT *
		FROM ".$datatable."
		WHERE date LIKE '".date('Y-m')."%'
		ORDER BY date";
$results = $conn->query($query);

$sum = 0;
$count = 0;
$temp = array();
$press = array();
$um = array();
$label = array();
$html = "";

$datas = array();
$Tsum = 0; $Tcount = 0;
$Hsum = 0; $Hcount = 0;
$Psum = 0; $Pcount = 0;
foreach ($results as $result) {
	$datas[$result['date']] = array(
		'temperature' => $result['temperature'], 
		'humidity' => $result['humidity'], 
		'pressure' => $result['pressure']*101325
	);
}

Lo script fa uso della libreria PDO per la gestione delle connessioni al database. Verranno prelevati solo i dati del mese corrente e saranno ordinati in una struttura con la seguente struttura:

array(
'AAAA-MM-DD' => array(
'temperature' => Tdata,
'humidity' => Hdata,
'pressure' => Pdata
)
)

La variabile così creata verrà poi elaborata da uno script js per popolare i dati dei grafici. Analizziamo le singole parti che comporranno la pagina web mostrata:


<div id="data">
	<div class="tempdata">
		<table cellpadding="0" cellspacing="0">
			<tr>
				<th>Data</th>
				<th>Temperature</th>
				<th>Humidity</th>
				<th>Pressure</th>
			</tr>
			<?php
			$pdatas = array_reverse($lists);
			foreach ($pdatas as $d=>$data) :
			?>
			<tr>
				<td><?php echo date('d-m-Y', strtotime($d)); ?></td>
				<td><?php echo $data['temperature']; ?></td>
				<td><?php echo $data['humidity']; ?></td>
				<td><?php echo $data['pressure']; ?></td>
			</tr>
			<?php
			endforeach;
			?>
		</table>
	</div>
</div>
	
<?php if (count($datas) != 0): ?>



<?php else: ?>
<div>No data registered in this week.</div>
<?php endif; ?>

Adesso che sono stati creati i canvas necessari a mostrare i grafici, possiamo scrivere il codice javascript per la generazione di questi ultimi:


<script>
	var t = new Bluff.Line('temperature', '1000x500');
	var h = new Bluff.Line('humidity', '1000x500');
	var p = new Bluff.Line('pressure', '1000x500');
	t.hide_dots = true;
	t.hide_legend = true;
	t.marker_font_size = 10;
	t.title = 'Temperature';
	t.tooltips = true;
	t.theme_greyscale();

	h.hide_dots = true;
	h.hide_legend = true;
	h.marker_font_size = 10;
	h.title = 'Humidity';
	h.tooltips = true;
	h.theme_greyscale();

	p.hide_dots = true;
	p.hide_legend = true;
	p.marker_font_size = 10;
	p.title = 'Pressure';
	p.tooltips = true;
	p.theme_greyscale();

	var tarray = new Array();
	var harray = new Array();
	var parray = new Array();
	var darray = new Array();
	<?php
	$i = 0;
	foreach ($datas as $d=>$data) {
		?>
		tarray[<?php echo $i;?>] = "<?php echo ($data['temperature'] != 0) ? $data['temperature'] : 15; ?>";
		harray[<?php echo $i;?>] = "<?php echo ($data['humidity'] != 0) ? $data['humidity'] : 0; ?>";
		parray[<?php echo $i;?>] = "<?php echo ($data['pressure'] != 0) ? $data['pressure'] : 100500; ?>";
		darray[<?php echo $i;?>] = "<?php echo date('d(H:i)', strtotime($d))?>";
		<?php
		$i++;
	
	?>

	t.data("Temp", tarray);
	t.minimum_value = 15;
	t.maximum_value = 35;
	t.y_axis_increment = 1;

	h.data("H", harray);
	h.minimum_value = 0;
	h.maximum_value = 100;
	h.y_axis_increment = 10;

	p.data("Pres", parray);
	p.minimum_value = 100500;
	p.maximum_value = 101500;
	p.y_axis_increment = 100;

	t.labels = darray;
	h.labels = darray;
	p.labels = darray;

	t.draw();
	h.draw();
	p.draw();
</script>

Infine, per migliorare lievemente l’estetica, applichiamo il seguente stile:


<style>
#data {
	border: 1px dotted #ccc;

.stat {
	font-size: 12px;
	width: 400px;
	margin: auto;

.stat p {
	margin: 0px;
	padding: 0px;

.tempdata {
	margin-top: 20px;
	height: 150px;
	overflow: auto;

.tempdata div {
	clear: both;

.tempdata table {
	font-size: 10px;
	text-align: left;

.tempdata table td, .tempdata table th {
	width: 100px;
	padding-left: 10px;
	padding-right: 10px;

.tempdata div p {
	margin: 0px 10px 0px 10px;
	float: left;

#temperature {
	margin: auto;

</style>

Adesso non rimane che collegare Arduino al PC e cominciare a memorizzare dei valori misurati e poi poterli visualizzare su di una pagina web:

Al seguente link sarà possibile scaricare i sorgenti.

Una Risposta to “MeteArduino”

  1. […] MeteArduino ::: HosiriS […]

Lascia un commento

Inserisci i tuoi dati qui sotto o clicca su un'icona per effettuare l'accesso:

Logo WordPress.com

Stai commentando usando il tuo account WordPress.com. Chiudi sessione / Modifica )

Foto Twitter

Stai commentando usando il tuo account Twitter. Chiudi sessione / Modifica )

Foto di Facebook

Stai commentando usando il tuo account Facebook. Chiudi sessione / Modifica )

Google+ photo

Stai commentando usando il tuo account Google+. Chiudi sessione / Modifica )

Connessione a %s...

 
%d blogger cliccano Mi Piace per questo: