MA QUANTO MI CONSUMI !


torna alla Home Page


LA TEORIA

Si tratta di un misuratore di consumo elettrico casalingo, che rileva la potenza assorbita contando gli impulsi luminosi emessi dal contatore Enel.
Infatti, secondo le specifiche del contatore, viene emesso un lampeggio per ogni watt consumato, quindi la misura del tempo tra due impulsi può essere usato per stimare il consumo istantaneo, mentre il conteggio degli impulsi nell'ora o nella giornata daranno il consumo in wattora o il totale giornaliero.

Fino a qui nulla di nuovo, anzi esistono su internet contatori simili a badilate, vuoi con Arduino, che altre MCU, oppure dispositivi commerciali fatti e finiti... e allora perchè rifare una cosa già fatta ?
La ragione si trova nella realizzazione, che ho voluto spingere oltre il semplice visualizzatore di consumo, aggiungendo gadget e ideuzze che potrebbero tornare utili in altri progetti.

Ecco di cosa è composto il progettino, e i relativi componenti principali:

TRASMETTITORE

  • Sensore: LDR affacciato al led di segnalazione sul contatore ENEL (il cappuccio al termine della spirale nella foto);
  • Elaborazione: MCU ATTiny13;
  • Trasmettitore: Modulo ASK 433 Mhz in kit con ricevitore (Seeed Studio);
  • Alimentazione: Batteria ricaricabile Ni-Cd da 6v
  • Case: box plastico 10x10x5 cm.


RICEVITORE

  • Ricevitore: modulo ASK 433 Mhz in kit con trasmettitore (Seeed Studio);
  • Elaborazione: MCU ATMega368, dotato di Bootloader Arduino;
  • Display:LCD 20x4 righe, blu con retroilluminazione;
  • Interfaccia: ricevitore infrarosso per telecomando TSOP1732;
  • Clock: RTC DS1307 con batteria tampone CR2032;
  • Memoria: EEPROM 24LC16 da 2KByte;
  • Sensore Touch: chip di prossimità IQS127D  (Sure Electronics)
  • Case: Box alluminio e LCD ricavati da un LCD Smartie Asset (Sure Electronics);
  • Alimentazione: tramite connettore USB;
  • Telecomando: remote da macchina fotografica

SOFTWARE

Appena acceso il ricevitore, visualizza sul display a caratteri giganti il consumo istantaneo, calcolato come 3.600.000 diviso il tempo in millisecondi tra due impulsi ricevuti.
Inoltre , ogni inpulso ricevuto viene accumulato separatamente in una locazione di memoria associata al minuto corrente, all'ora corrente e al giorno corrente, usando come valore assoluto il dato ritornato dall'RTC. 

Facendo un esempio, un impulso ricevuto il il giorno 17 agosto, alle 13:31 viene accumulato nella posizione 31 per i minuti, nella posizione 13 per le ore e nella posizione 17 per il giorno. Ovviamente le posizioni di memoria vengono distanziate in modo opportuno per non sovrapporre i conteggi dei minuti/ore/gioni, e hanno una lunghezza di 60 locazioni per i minuti (di tipo Byte), 24 per le ore (tipo Integer) e 31 per i giorni (tipo integer).
Essendo un buffer circolare, al cambio del minuto,ora o giorno, la nuova locazione di memoria viene azzerata prima di essere di nuovo accumulata.

Tramite i bottoni SX/DX del telecomando si può passare alla visualizzazione storica, visualizzata tramite un istogramma che rappresenta gli ultimi 15 minuti, 15 ore o 15 giorni e la cui scala verticale può essere cambiata con i tasti SU/GIU del telcomando, modificando il valore massimo secondo una potenza di 2 (massimo = 32,64,128,256,1.024, 2.048, 4.096, 8.192, 16.536, 32.768 Watt) consentendo di zoomare l'istogramma in modo agevole.

Occorre precisare che il lavoro di riconoscimento del segnale in arrivo dal ricevitore è stato effettuato grazie ad un post scovato nel forum di Arduino (DCDW: Dirt Cheap Dumb Wireless) , da cui ho estirpato letteralmente il codice necessario e a cui rendo un doveroso grazie. Tramite quell'implementazione sono in grado di discriminare la pernacchia che genera il trasmettitore e conteggiarla come watt consumato.

Il bottone centrale del telecomando richiama il menu di setup, con cui si può regolare data e ora, eseguire il dump della memora su porta seriale, azzerare tutta la memoria, o richiamare l'”about” per verificare la versione de firmware.
Una funzione accessoria è quella della luce del display che si può attivare/disattivare usando un altro bottone del telecomando, o anche toccando il case grazie al touch sensor. In caso di inattività  si spegne dopo 90 secondi.

Il dump della memoria dei consumi si può effettuare tramite porta seriale, che va collegata estraendo la schedina dal contenitore e collegando i pin esposti della porta seriale al convertitore (già usato nel precedente progetto dell'orologio), e quindi al pc.

IL TRASMETTITORE


Scendendo nei dettagli (qui sopra lo schema elettrico), il trasmettitore si occupa di inviare una "pernacchia" nell'etere ogni volta che viene rilevato un flash dal contatore. 

Dopo avere fatto un pò di esperimenti, e provato a usare addirittura un arduino intero solo per fare questa semplice operazione, mi sono rivolto ad un più economico ATTiny13, che con il suo K di memoria e la capacità di essere riprogrammato come l'arduino, ben si adatta al ruolo.

La fotoresisrtenza LDR rileva l'impuslo ed è regolata in sensibilità dal potenziometro collegato in serie. 

Ogni impulso abilita il timer in modalità CTC, e per 1000 volte alterna l'uscita del pin PB2 tra 0 e 1, fornendo così un'onda quadra di circa 3KHz che serve a modulare il segnale in uscita dal trasmettitore.

Per aumentare la potenza del trasmettitore, la relativa alimentazione è stata presa direttamente dalla batteria che eroga poco più di 6V, mentre l'ATTiny è alimentato da un regolatore 7805 che mantiene la tensione a 5V stabilizzati.

Come da specifiche, il trasmettitore richiede uno spezzone fi filo di circa 17cm che fungerà da antenna.

Per poter riporgrammare agevolmente l'ATTiny senza estrarlo ho messo un connettore ICSP a 6 poli, a cui si può collegare un USBTinyISP per la riprogrammazione in-circuit.

Il sorgente si trova a questo link


IL SIMULATORE

Per poter procedere nei test, senza dover collegare fisicamente il contatore ENEL, ho realizzato un piccolo lampeggiatore, a frequenza variabile, che emette un breve impulso luminoso con un tempo variabile. 

Non c'è scelta, un buon vecchio 555 fa al caso nostro, l'unica accortezza è assicurarsi che il duty cycle sia molto stretto, diciamo l'1% per avere un flash molto breve ad ogni impuso.

Un potenziometro consente di variare a piacimento il tempo tra due impulsi e verificare quindi la relativa lettura sul ricevitore.

In base ai componenti usati ottengo un impulso luminoso al secondo fino a uno ogni 7-8 secondi, più che sufficienti per fare le prove del caso e verificare la bontà dei numeri.



QUALCHE EXTRA

Per avere una memoria storica dei consumi, ho aggiunto la registrazione dei consumi tramite una EEPROM 24LC16, che con i suoi 2 Kbyte a disposizione fornisce uno spazio più che sufficente per lo scopo.

L'indirizzamento della EEPROM avviene portando a massa i pin 2,3,4 nelle varie combinazioni: nel nostro caso tutti i tre pin a massa l'indirizzo corrisponde a 0x50.
La libreria Wire consente il dialogo con la EEPROM im modo agevole, e ho rilasciato due funzioni di lettura e scrittura rispettivamente per gestire il typo byte o integer.

Un altro extra divertente è stata l'applicazione di un sensore touch, usando un chip dedicato, preso da Sure Eectronics: con un paio di resistenze e un condensatore si ottiene il sensore completo, l'unica scocciatura è che si tratta di un componente SMD, ma essendo fornito con la propria breakoutboard (un francobollo su cui saldare l'SMD, con contattti in uscita DIL) l'unica accortezza è avere un saldatore con punta molto sottile per poter saldare i 6 pin, e cinque minuti di tranquillità totale.

Ho collegato il contatto caldo del sensore al case in alluminio, rendendo quindi tutto il contenitore sensibile al tocco, che viene interpretato come ON/OFF della retroilluminazione.


 

IL RICEVITORE

Come per il progetto dell'orologio che avevo realizzato un pò di tempo fa, anche per questo lavoro ho preferito  fare tutto su basetta millefori, è semplice da usare e consente ripensamenti "in corsa" a differenza di un circuito stampato che obbliga a un lavoro di preparazione e pianificazione non indifferente.

Per iniziare ho preso il case del LCD Smartie Asset (preso da SURE su EBay), ne ho ricavato l'LCD e ho determinato le misure della basetta interna che avrebbe rimpiazzato l'originale.

Ho avuto qualche problemino con la presa mini USB, che ho dovuto recuperare da un vecchio telefonino, e incollandola con l'attak nella posizione esatta come il circuito originale.

Il resto del lavoro è stato quello di seguire pazientemente lo schema elettrico, che sembra complicato ma si può agevolmente individuare l'LCD, il modulo RTC, il ricevitore infrarosso, eccetera: ognuno fondamentealmente collegato all'alimentazione e ai relativi pin dell'arduino.

Giusto per condimento ho messo nelle vicinanze di ogni elemento un condensatore di livellamento da un centinaio di nanofarad, come da buona creanza di ogni tecnico.

Come per l'orologio realizzato a suo tempo, ho mantenuto i pin TX, RX e Reset come contatti di uscita (Programmer Port) , che vengono usati per la riprogrammazione o per il download dei consumi su porta seriale.


IL VIDEO E IL CODICE

/*
*********************************************
PowerDuino
ARDUINO WIRELESS POWER METER
By Marco Nicolato
2011/08/19 - V.1.0
*********************************************
*/

// vvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvv
#include <WProgram.h>
#include <Wire.h>
#include <DS1307.h>
// ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^


// vvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvv
// menu definition
#define NUM_MENU_ITEM 4
#define MENU_X 1 // 0-39
#define MENU_Y 0 // 0-4

char menu_items[NUM_MENU_ITEM][20]={
"DATA & ORA",
"DUMP MEMORIA",
"AZZERARE MEMORIA",
"ABOUT"};

void (*menu_funcs[NUM_MENU_ITEM])(void) = {
datetime,
dumpMemory,
resetMemory,
about
};
char current_menu_item;
// ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^

// vvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvv
// Current display mode
#define DISPLAY_CURRENT 0 // Instant consumption
#define DISPLAY_MM 1 // Historical - last 15 mins
#define DISPLAY_HH 2 // Historical - last 15 hours
#define DISPLAY_DT 3 // Historical - last 15 hours
#define DISPLAY_DEBUG 4 // Debug Mode
#define DISPLAY_ITEMS 3 // How many modes
int displayType = DISPLAY_CURRENT;
// ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^


// vvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvv
// initialize the LCD library with the numbers of the interface pins
#include <LiquidCrystal.h>
#define LCD_PIN_D4 4
#define LCD_PIN_D5 5
#define LCD_PIN_D6 6
#define LCD_PIN_D7 7
#define LCD_PIN_RS 8
#define LCD_PIN_EN 9
#define LCD_PIN_LIGHT 10
#define LCD_ROWS 4
#define LCD_COLS 20
LiquidCrystal lcd(LCD_PIN_RS, LCD_PIN_EN, LCD_PIN_D4, LCD_PIN_D5, LCD_PIN_D6, LCD_PIN_D7);
boolean lcdLight = true;
#define LCD_LIGHTONTIME 60000UL // light on for 60 sec
unsigned long lcd_lightUntil = 0; // light on until time
// ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^

// vvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvv
#include <IRremote.h>
int RECV_PIN = 3;
IRrecv irrecv(RECV_PIN);
decode_results results;

// Remote IR keys
#define UP_KEY 1401766589
#define LEFT_KEY 1401782909
#define CENTER_KEY 1401812999
#define DOWN_KEY 1401799229
#define RIGHT_KEY 1401750269
#define LIGHT_KEY 1401807389
#define RMT_1 1401758429
#define RMT_2 1401774749
#define RMT_3 1401807389
// ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^

// vvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvv
// Control LED
#define PIN_LED 13
// ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^

// vvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvv
// DCDW data link on digital pin 2
#define PIN_RX 2
#define DCDW_MINFREQ 3000
#define DCDW_MAXFREQ 3500
#define TIMEOUT 40000
// ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^


// vvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvv
// Touch sensor from SURE
#define PIN_PROX_NEAR 12
#define PIN_PROX_TOUCH 11
#define PROX_DEBOUNCE 800 // MS to debounce proximity signal
int touch = 0;
int proximity = 0;
// ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^


// vvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvv
int instPower = 0;
int minPower = 0;

// date & time vars
int yy;
int mt;
int dt;
int hh;
int mm;
int ss;
int oldMM;
int oldHH;
int oldDT;
// Days in each month array
byte giorni[13]={31,31,28,31,30,31,30,31,31,30,31,30,31};


// vvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvv
// Memory location start banks
#define EEPROM_ADDR 0x50
#define EEPROM_BASE_MM 0
#define EEPROM_BASE_HH 60
#define EEPROM_BASE_DT 110

#define BUFFER_LEN 15
// zoom factor
int zoomFactor[4];
// ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^



// vvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvv
// Array index into parts of big numbers. Numbers consist of 9 custom characters in 3 lines
#define B 0xFF // The character for a completely filled box
#define A 0x20 // The character for blank
// 0 1 2 3 4 5 6 7 8 9
char bn1[]={B,2,1, 2,1,A, 2,2,1, 2,2,1, 0,A,B, B,2,2, B,2,2, 2,2,B, B,2,1, B,2,1};
char bn2[]={B,A,B, A,B,A ,0,6,5, A,2,1, 5,6,B, 2,2,1, B,6,7, A,0,5, B,6,B, 5,6,B};
char bn3[]={4,3,B, 3,B,3, B,3,3, 4,3,B, A,A,B, 4,3,B, 4,3,B, A,B,A, 4,3,B, 3,3,B};
// ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^


//******************************************
void setup() {
//******************************************

Serial.begin(9600); // start up the serial communications USART

pinMode(LCD_PIN_LIGHT, OUTPUT);
pinMode(PIN_LED, OUTPUT);

pinMode(PIN_RX, INPUT); // set RX pin to input
digitalWrite(PIN_RX, LOW); // no pullup

pinMode(PIN_PROX_NEAR, INPUT);
pinMode(PIN_PROX_TOUCH, INPUT);
digitalWrite(PIN_PROX_NEAR, HIGH); // PULLUP
digitalWrite(PIN_PROX_TOUCH, HIGH);

// Start the IR receiver
irrecv.enableIRIn();

// set up the LCD's number of columns and rows:
lcd.begin(LCD_COLS, LCD_ROWS);

// Remap big number segments as default
mapNumbers();

// ASSIGN ZOOM FACTOR (power of 2)
zoomFactor[DISPLAY_MM] = 5; // max=32
zoomFactor[DISPLAY_HH] = 12; // max=4096
zoomFactor[DISPLAY_DT] = 15; // max = 32768

// splash screen
digitalWrite(LCD_PIN_LIGHT, HIGH);
about0();
delay(3000);
lcd.clear();

// set current indexes
readTime();
oldMM = mm;
oldHH = hh;
oldDT = dt;

lcd_lightUntil = millis() + LCD_LIGHTONTIME;
}


//******************************************
void loop() {
//******************************************
int prevMM;
int value;
static long oldMillis=0;
unsigned long key = readKey();

// vvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvv
// touch and debounce
touch = digitalRead(PIN_PROX_TOUCH);
proximity = digitalRead(PIN_PROX_NEAR);
if ( (touch==0) && ( (oldMillis + PROX_DEBOUNCE) < millis()) ){

if( lcd_lightUntil > millis() )
lcd_lightUntil = 0;
else
lcd_lightUntil = millis() + LCD_LIGHTONTIME;

oldMillis = millis();
}
// ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^

// vvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvv
// Verify remote key pressed
switch(key){

case CENTER_KEY:
menu();
break;

case LIGHT_KEY:
// swap lcd light
if( lcd_lightUntil > millis() )
lcd_lightUntil = 0;
else
lcd_lightUntil = millis() + LCD_LIGHTONTIME;
break;

case LEFT_KEY:
lcd.clear();
lcd_lightUntil = millis() + LCD_LIGHTONTIME;
displayType--;
if(displayType < 0)
displayType = DISPLAY_ITEMS;
if(displayType == 0)
mapNumbers();
else
mapSymbols();
break;

case RIGHT_KEY:
lcd.clear();
lcd_lightUntil = millis() + LCD_LIGHTONTIME;
displayType++;
if(displayType > DISPLAY_ITEMS)
displayType = 0;
if(displayType == 0)
mapNumbers();
else
mapSymbols();
break;

// increaze zoom
case UP_KEY:
lcd_lightUntil = millis() + LCD_LIGHTONTIME;
zoomFactor[displayType] = (zoomFactor[displayType] - 1);
zoomFactor[displayType] = constrain(zoomFactor[displayType], 5, 15);
break;

// decreaze zoom
case DOWN_KEY:
lcd_lightUntil = millis() + LCD_LIGHTONTIME;
zoomFactor[displayType] = (zoomFactor[displayType] + 1);
zoomFactor[displayType] = constrain(zoomFactor[displayType], 5, 15);
break;

// fill memory with some sample data
case RMT_1:
fillMemory();
break;

}
// ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^

// LIGHT UP LCD ?
digitalWrite(LCD_PIN_LIGHT, ( lcd_lightUntil > millis() ));

// READ PULSE FROM POWER COUNTER
static unsigned long prevMs=0;
int freq;
unsigned long deltaMs;
unsigned long curMs;

// Reads RX pulse freq
curMs = millis();
freq = countFrequency (PIN_RX, 1);
deltaMs = curMs - prevMs;

// if received a valid blink, compute it
if ((freq > DCDW_MINFREQ && freq < DCDW_MAXFREQ) || (deltaMs > TIMEOUT)) {

if (deltaMs > TIMEOUT)
instPower = 0;
else {
digitalWrite(PIN_LED, HIGH);
bufferMM(oldMM, bufferMM(oldMM) + 1); // increase each mon/hour/day location
bufferHH(oldHH, bufferHH(oldHH) + 1);
bufferDT(oldDT, bufferDT(oldDT) + 1);
instPower = (3600000 / deltaMs);
}

// Compute last minute power
prevMM = oldMM -1;
if (prevMM < 0)
prevMM = 59;
minPower = bufferMM(prevMM) * 60;

// Save old ms
prevMs = curMs;
delay(300);
digitalWrite(PIN_LED, LOW);
}

// show on LCD
showDisplay(instPower, minPower);

// Every minute change moves on next buffer slot
if (oldMM != mm)
bufferMM(mm, 0); // clear before

// Every hour change moves on next buffer slot
if (oldHH != hh)
bufferHH(hh, 0); // clear before

// Every day changes moves on next buffer slot
if (oldDT != dt)
bufferDT(dt, 0); // clear before

oldDT = dt;
oldHH = hh;
oldMM = mm;

}


//****************************************************
void showDisplay(int instPower, int minPower) {
//****************************************************
int th,hu,te,un;
int nPower;
int i;
char k=' ';
unsigned int z;
int idx;
unsigned int zoom;

// update display every 100ms
static unsigned long prevMs=0;
if (prevMs + 100 > millis())
return;
prevMs =millis();

// Show clock in the corner
showClock();

// What to display ?
switch(displayType){

// ---------------------------------------
case DISPLAY_CURRENT:
// show last min power
lcd.setCursor(0,0);
lcd.print("Min = ");
lcd.print(minPower);
lcd.print(" ");

lcd.setCursor(17,1);
lcd.print(" ");
lcd.setCursor(17,2);
lcd.print("Pot");
lcd.setCursor(17,3);
lcd.print("IST");

// show inst power
if (instPower < 9999){
th = (instPower / 1000);
hu = (instPower - (th * 1000)) / 100;
te = (instPower - (th * 1000) - (hu * 100) ) / 10;
un = (instPower % 10);
printOneNumber(0, th);
printOneNumber(4, hu);
printOneNumber(8, te);
printOneNumber(12, un);
}
break;

// ---------------------------------------
case DISPLAY_MM:
lcd.setCursor(15,1);
lcd.print("M= ");
lcd.setCursor(17,1);
z = (1 << zoomFactor[DISPLAY_MM]);
if (z>999){
z=z/1000;
k='K';
}
lcd.print(z,DEC);
lcd.print(k);
lcd.setCursor(15,2);
lcd.print("ult15");
lcd.setCursor(17,3);
lcd.print("min");
zoom = ( 1 << zoomFactor[DISPLAY_MM]);
for(idx=0; idx < BUFFER_LEN; idx++){
i=(oldMM - idx - 1);
if (i<0)
i = i + 60;
nPower = map(bufferMM(i), 0, zoom, 0, 32);
bar(idx, nPower);
}
break;

// ---------------------------------------
case DISPLAY_HH:
lcd.setCursor(15,1);
lcd.print("M= ");
lcd.setCursor(17,1);
z = (1 << zoomFactor[DISPLAY_HH]);
if (z>999){
z=z/1000;
k='K';
}
lcd.print(z,DEC);
lcd.print(k);

lcd.setCursor(15,2);
lcd.print("ult15");
lcd.setCursor(17,3);
lcd.print("ore");
zoom = ( 1 << zoomFactor[DISPLAY_HH]);
for(idx=0; idx < BUFFER_LEN; idx++){
i=(oldHH - idx - 1);
if (i<0)
i = i + 24;
nPower = map(bufferHH(i), 0, zoom, 0, 32);
bar(idx, nPower);
}
break;

// ---------------------------------------
case DISPLAY_DT:
lcd.setCursor(15,1);
lcd.print("M= ");
lcd.setCursor(17,1);
z = (1 << zoomFactor[DISPLAY_DT]);
if (z>999){
z=z/1000;
k='K';
}
lcd.print(z,DEC);
lcd.print(k);

lcd.setCursor(15,2);
lcd.print("ult15");
lcd.setCursor(17,3);
lcd.print("gio");
zoom = ( 1 << zoomFactor[DISPLAY_DT]);
for(idx=0; idx < BUFFER_LEN; idx++){
i=(oldDT - idx - 1);
// Verify how many days in the month
if (i<0)
i += giorni[mt-1];

nPower = map(bufferDT(i), 0, zoom, 0, 32);
bar(idx, nPower);
}
break;

// ---------------------------------------

// ---------------------------------------
// for debug only: show prox / touch
case DISPLAY_DEBUG:
lcd.setCursor(0,1);
lcd.print("touch=");
lcd.print(touch);
lcd.setCursor(0,2);
lcd.print("proximity=");
lcd.print(proximity);
break;
}
}


//****************************************************
void bar(int nColumn, int nValue){
//****************************************************

// How many full bars
int full = (nValue / 8);
// mette le barre piene
for (int n=1; n <= full; n++){
lcd.setCursor(nColumn, 4-n);
lcd.write(255);
}

// Then remaining bitmap symbol
if (full < 4) {
lcd.setCursor(nColumn, 4-full-1);
lcd.write(nValue - (full * 8));
// Clean the rest
for (int n=0; n<4-full-1; n++){
lcd.setCursor(nColumn, n);
lcd.write(0x20);
}
}
}

//****************************************************
void printOneNumber(byte value, byte number){
//****************************************************
// functions for printing one large digits. Value is the column number
// and number is the number to print.

lcd.setCursor(value,1); // Printing the first row of the large number
lcd.write(bn1[number*3]);
lcd.write(bn1[number*3+1]);
lcd.write(bn1[number*3+2]);

lcd.setCursor(value,2); // Printing the second row of the large number
lcd.write(bn2[number*3]);
lcd.write(bn2[number*3+1]);
lcd.write(bn2[number*3+2]);

lcd.setCursor(value,3); // Printing the third row of the large number
lcd.write(bn3[number*3]);
lcd.write(bn3[number*3+1]);
lcd.write(bn3[number*3+2]);
}



//******************************************
void menu(void){
//******************************************
unsigned long key = 0;
lcdLight = true;
init_MENU();

while (key!= CENTER_KEY) {
switch(key){
case UP_KEY:
// current item to normal display
lcd.setCursor(MENU_X-1, MENU_Y + current_menu_item);
lcd.print(" ");
current_menu_item -=1;
if(current_menu_item <0) current_menu_item = NUM_MENU_ITEM -1;
// next item to highlight display
lcd.setCursor(MENU_X-1, MENU_Y + current_menu_item);
lcd.print(">");
break;
case DOWN_KEY:
// current item to normal display
lcd.setCursor(MENU_X-1, MENU_Y + current_menu_item);
lcd.print(" ");
current_menu_item +=1;
if(current_menu_item >(NUM_MENU_ITEM-1)) current_menu_item = 0;
// next item to highlight display
lcd.setCursor(MENU_X-1, MENU_Y + current_menu_item);
lcd.print(">");
break;
case LEFT_KEY:
init_MENU();
break;
case RIGHT_KEY:
lcd.clear();
(*menu_funcs[current_menu_item])();
init_MENU();
break;
case LIGHT_KEY:
lcdLight = !lcdLight;
}
digitalWrite(LCD_PIN_LIGHT, lcdLight); // LIGHT UP LCD
key = readKey();
}
lcd.clear();
}


/* menu functions */
//******************************************
void init_MENU(void){
//******************************************
byte i;
lcd.clear();
lcd.setCursor(MENU_X-1, MENU_Y );
lcd.print(">");
current_menu_item = 0;

for (i=0; i<NUM_MENU_ITEM; i++){
lcd.setCursor(MENU_X, MENU_Y+i );
lcd.print(menu_items[i]);
}


}

// waiting for center key press
//******************************************
void waitfor_OKkey() {
//******************************************
unsigned long key = 0;
while (key!= CENTER_KEY)
key = readKey();

}

//******************************************
void datetime() {
//******************************************
lcd.clear();
adjustTime();
}


//******************************************
void dumpMemory() {
//******************************************
// Dump all historical memory to serial out
lcd.setCursor(0,0);
lcd.print("Dump memoria ? NO SI");
char menu_items[2][21]={
" ^^ ",
" ^^"
};

int x;
int y;
int idx = 0;
int chg = 0;
unsigned long key = 0;

while (key!= CENTER_KEY) {
switch(key){
case LEFT_KEY:
idx--;
if (idx<0)
idx=1;
break;
case RIGHT_KEY:
idx++;
if (idx>1)
idx=0;
break;
}
lcd.setCursor(14,1);
lcd.print(menu_items[idx]);
key = readKey();
}

// CONFIRM DUMP MEMORY ?
if(idx == 1) {
lcd.setCursor(0,3);
lcd.print("Attendere...");

// Leap year ?
if ( !(yy%4) && ( (yy%100) || !(yy%400) ) )
giorni[2] = 29;

Serial.println("");
Serial.println("Dump memoria Power Meter (Watt)");
Serial.println("-------------------------------");
Serial.print("Data corrente: ");
Serial.print(yy);
Serial.print("/");
Serial.print(mt);
Serial.print("/");
Serial.print(dt);
Serial.print(" ");
Serial.print(hh);
Serial.print(":");
Serial.print(mm);
Serial.print(":");
Serial.println(ss);


Serial.println("");
Serial.println("Ore:Minuti");
Serial.println("^^^^^^^^^^");
for(idx=0; idx < 60; idx++){
x=(mm - idx);
y = hh;
if (x<0){
x = x + 60;
y = y - 1;
}
if(y<0)
y=11;
printTtwoDigit(y);
Serial.print(":");
printTtwoDigit(x);
Serial.print("=");
Serial.println(bufferMM(x), DEC);
}


Serial.println("");
Serial.println("Giorno/Ore");
Serial.println("^^^^^^^^^^");
for(idx=0; idx < 24; idx++){
x=(hh - idx);
y=dt;
if (x<0){
x = x + 24;
y = y - 1;
}
if (y<1)
dt=giorni[mt-1];
Serial.print(y);
Serial.print("/");
printTtwoDigit(x);
Serial.print("=");
Serial.println(bufferHH(x), DEC);
}


Serial.println("");
Serial.println("Mese/Giorno");
Serial.println("^^^^^^^^^^^");
for(idx=0; idx < 31; idx++){
x=(dt - idx);
y=mt;
// Verify last day of the month
if (x < 1){
x = x + giorni[mt-1];
y = y - 1;
if (y < 1)
y = 12;

}

if (x == dt && idx > 0)
break;

Serial.print(x);
Serial.print("/");
Serial.print(y);
Serial.print("=");
Serial.println(bufferDT(x), DEC);
}

lcd.clear();
lcd.setCursor(0,0);
lcd.print("Dump terminato.");
lcd.setCursor(0,3);
lcd.print("(premi un tasto)");
waitfor_OKkey();

}
}


//******************************************
void printTtwoDigit(int num){
//******************************************
if (num<10)
Serial.print("0");
Serial.print(num);
}

//******************************************
void about(){
//******************************************
about0();
waitfor_OKkey();
lcd.clear();
}

//******************************************
void about0(){
//******************************************
lcd.clear();
lcd.setCursor(0,0);
lcd.print("Wireless");
lcd.setCursor(0,1);
lcd.print("LCD PowerMeter v.01");
lcd.setCursor(0,3);
lcd.print("by M. Nicolato");
}


//****************************************************
void readTime() {
//****************************************************
// get date & time
yy = RTC.get(DS1307_YR,true);
mt = RTC.get(DS1307_MTH,false);
dt = RTC.get(DS1307_DATE,false);
hh = RTC.get(DS1307_HR,false);
mm = RTC.get(DS1307_MIN,false);
ss = RTC.get(DS1307_SEC,false);
}

//****************************************************
void showClock() {
//****************************************************
readTime();
lcd.setCursor(15, 0);
if (hh < 10) lcd.print("0");
lcd.print(hh);
lcd.print(":");
if (mm < 10) lcd.print("0");
lcd.print(mm);
}


//****************************************************
void showTime() {
//****************************************************
readTime();
// set string date
lcd.setCursor(0, 0);

if (yy < 10) lcd.print("0");
lcd.print(yy);
lcd.print("-");
if (mt < 10) lcd.print("0");
lcd.print(mt);
lcd.print("-");
if (dt < 10) lcd.print("0");
lcd.print(dt);
lcd.print(" ");
if (hh < 10) lcd.print("0");
lcd.print(hh);
lcd.print(":");
if (mm < 10) lcd.print("0");
lcd.print(mm);
lcd.print(":");
if (ss < 10) lcd.print("0");
lcd.print(ss);

}


//******************************************
unsigned long readKey() {
//******************************************
// read from IR remote
unsigned long rcv = 0;
if (irrecv.decode(&results)) {
rcv=results.value;
irrecv.resume(); // Receive the next value
}
return rcv;
}

//******************************************
void resetMemory() {
//******************************************
// reset memory buffer
lcd.setCursor(0,0);
lcd.print("Azzerare NO SI");
lcd.setCursor(0,1);
lcd.print("memoria ? ");
char menu_items[2][21]={
" ^^ ",
" ^^"
};

int idx = 0;
int chg = 0;
unsigned long key = 0;
while (key!= CENTER_KEY) {
switch(key){
case LEFT_KEY:
idx--;
if (idx<0)
idx=1;
break;
case RIGHT_KEY:
idx++;
if (idx>1)
idx=0;
break;
}
lcd.setCursor(14,1);
lcd.print(menu_items[idx]);
key = readKey();
}

// CONFIRM TO ERASE MEMORY ?
if(idx == 1) {
lcd.setCursor(0,3);
lcd.print("Attendere...");

for (int m = 0; m < 60; m++)
bufferMM(m, 0);
lcd.print(".");

for (int m = 0; m < 24; m++)
bufferHH(m, 0);
lcd.print(".");

for (int m = 0; m < 32; m++)
bufferDT(m, 0);
lcd.print(".");

lcd.clear();
lcd.setCursor(0,0);
lcd.print("Memoria cancellata.");
lcd.setCursor(0,3);
lcd.print("(premi un tasto)");
waitfor_OKkey();

}
}


//****************************************************
void adjustTime() {
//****************************************************
char menu_items[5][18]={
"^^^^ ",
" ^^ ",
" ^^ ",
" ^^ ",
" ^^ ",
};

int idx = 0;
int chg = 0;
unsigned long key = 0;
while (key!= CENTER_KEY) {
showTime();
chg = 0;
switch(key){
case LEFT_KEY:
idx--;
if (idx<0)
idx=4;
break;
case RIGHT_KEY:
idx++;
if (idx>4)
idx=0;
break;
case UP_KEY:
chg = +1;
break;
case DOWN_KEY:
chg = -1;
break;
}

// Change ?
if (chg != 0) {
RTC.stop();
switch (idx) {
case 0: // anno
RTC.set(DS1307_YR, (yy+chg-2000));
break;
case 1: // mese
RTC.set(DS1307_MTH, (mt+chg));
break;
case 2: // giorno
RTC.set(DS1307_DATE, (dt+chg));
break;
case 3: // ora
RTC.set(DS1307_HR, (hh+chg));
break;
case 4: // minuti
RTC.set(DS1307_MIN, (mm+chg));
break;
}
RTC.start();
}

lcd.setCursor(0,1);
lcd.print(menu_items[idx]);
key = readKey();
}
}


// Read consumption for specific minute at <address>
//******************************************
byte bufferMM(int address) {
//******************************************
return readMemoryByte((EEPROM_BASE_MM + address));
}

// Read consumption for specific hour at <address>
//******************************************
unsigned int bufferHH(int address) {
//******************************************
return readMemoryInt((EEPROM_BASE_HH + (address * 2)));
}

// Read consumption for specific day at <address>
//******************************************
unsigned int bufferDT(int address) {
//******************************************
return readMemoryInt((EEPROM_BASE_DT + (address * 2)));
}



// Write consumption for specific minute at <address>
//******************************************
void bufferMM(int address, byte data) {
//******************************************
writeMemoryByte(EEPROM_BASE_MM + address, data);

}

// Write consumption for specific hour at <address>
//******************************************
void bufferHH(int address, unsigned int data) {
//******************************************
writeMemoryInt((EEPROM_BASE_HH + (address * 2)), data);

}

// Write consumption for specific day at <address>
//******************************************
void bufferDT(int address, unsigned int data) {
//******************************************
writeMemoryInt((EEPROM_BASE_DT + (address * 2)), data);

}



// Write one byte at <address>
//******************************************
void writeMemoryByte(int nAddress, byte nByte) {
//******************************************
int bank = ((nAddress >> 8) & 7);
// Ask for EEPROM
Wire.beginTransmission(EEPROM_ADDR);
Wire.send(0);
Wire.endTransmission();
//Write the data
Wire.beginTransmission(EEPROM_ADDR + bank);
Wire.send((byte) (nAddress & 255));
Wire.send(nByte);
Wire.endTransmission();
delay(15);
}


// Get one byte from <address>
//******************************************
byte readMemoryByte(int address) {
//******************************************
byte b;
int bank = ((address >> 8) & 7);

// Ask for EEPROM
Wire.beginTransmission(EEPROM_ADDR);
Wire.send(0);
Wire.endTransmission();

// Set addrress
Wire.beginTransmission(EEPROM_ADDR + bank);
Wire.send(address & 255);
Wire.endTransmission();

// Ask 1 byte
Wire.beginTransmission(EEPROM_ADDR + bank);
Wire.requestFrom(EEPROM_ADDR + bank, 1);

//Wait till we get all the bytes
if (Wire.available()) {
b = Wire.receive();
Wire.endTransmission();
return(b);
}
}

// Write one integer (2 byte) at <address>
//******************************************
void writeMemoryInt(int address, unsigned int nInt) {
//******************************************
writeMemoryByte(address, (byte) (nInt >> 8) & 0xFF); // MSB
writeMemoryByte(address + 1, (byte) nInt & 0xFF); // LSB
}

// read one integer (2 byte) from <address>
//******************************************
unsigned int readMemoryInt(int address) {
//******************************************
return ( readMemoryByte(address) << 8 ) + readMemoryByte(address+1);
}


//******************************************
long countFrequency (int pin, int precision) {
//******************************************
// counts frequency of an incoming pulse train
//
// pin any digital input pin (int)
// precision number of significant digits, 2, 3 or 4 (int)
// returns: frequency in Hz (long)
//
// written for Dirt Cheep Dumb Wireless sensor telemetry boards
// using OOK radio links (SparkFun etc) which output noise on no RF in
// so this looks for consistent pulse width as criterion against noise
// returns negative numbers for all errors such as noise
//
int pulses; // total number of pulses to time
int pulse; // pulse count
unsigned long timeout= 10000; // microsecs for pulseIn() to wait before giving up
unsigned long t0; // start time hack
unsigned long time; // delay in millisec
unsigned long needTime = 1; // delay needed for precision in millisec
unsigned long duration; // length of one pulse
unsigned long totDuration; // total duration of first ten pulses
unsigned long minDuration; // minimum length of a valid pulse
unsigned long maxDuration; // maximum length of a valid pulse
constrain (precision, 1, 5); // keepin it sane
for (int i = 1; i < precision; i++) {
needTime = needTime * 10;
} // millisecs needed
long frequency = 0; // nothin' yet
totDuration = 0; // clear this to start
for(pulse = 0; pulse < 10; pulse += 1) // count 10 incoming pulses
{
duration = pulseIn(pin, LOW, timeout); // wait for one complete pulse cycle
if (duration == 0) {
return -(long)pulse;
} // if pulse timout then abort, return neg of bad pulse
totDuration += duration;
}
maxDuration = totDuration / 7;
minDuration = totDuration / 14;
// now we have a range of average pulse durations
// so start counting "pulses"pulses, increasing "pulses by 10x each time
// until a string of pulses meets the minimum total time for accuracy
for (pulses = 10; pulses < 100000; pulses = pulses * 10)
{
frequency = 0; // nothin' yet
t0 = millis(); // start time
for(pulse = 0; pulse < pulses; pulse += 1) // count incoming pulses
{
duration = pulseIn(pin, LOW, timeout); // wait for one complete pulse cycle
if (duration == 0) {
return -(long)pulse;
} // pulse timout: abort, return neg of bad pulse
if (duration > maxDuration) // pulse too long: abort, return error code
{
return -(long)duration;
}
if (duration < minDuration)
{
return -(long)duration;
}
}
time = millis()-t0; // got all pulses, so time = now - start

if (time > needTime) // if time is enough for precision we are done
{
frequency = 1000 * (unsigned long)pulses / time; // frequency in Hz
return (long)frequency;
}
}
return -9999;
}



//******************************************
void mapNumbers(){
//******************************************
// big numbers bitmap
byte cc[8][8] = {
{ // Custom Character 0
B00000,
B00111,
B01111,
B11111,
B11111,
B11111,
B11111,
B11111
},
{ // Custom Character 1
B11100,
B11110,
B11111,
B11111,
B11111,
B11111,
B11111,
B11111
},
{ // Custom Character 2
B11111,
B11111,
B11111,
B11111,
B11111,
B00000,
B00000,
B00000
},
{ // Custom Character 3
B00000,
B00000,
B00000,
B11111,
B11111,
B11111,
B11111,
B11111
},
{ // Custom Character 4
B11111,
B11111,
B11111,
B11111,
B11111,
B11111,
B01111,
B00111
},
{ // Custom Character 5
B11111,
B11111,
B11111,
B11111,
B11111,
B11111,
B00000,
B00000
},
{ // Custom Character 6
B00000,
B11111,
B11111,
B11111,
B11111,
B11111,
B00000,
B00000
},
{ // Custom Character 7
B00000,
B11100,
B11110,
B11111,
B11111,
B11111,
B11111,
B11111
}
};

for(int i=0; i<8; i++)
lcd.createChar(i,cc[i]);
}



//******************************************
void mapSymbols(){
//******************************************
// bars bitmap
byte cc[8][8] = {
{ // Custom Character 0
B00000,
B00000,
B00000,
B00000,
B00000,
B00000,
B00000,
B00000
},
{ // Custom Character 1
B00000,
B00000,
B00000,
B00000,
B00000,
B00000,
B00000,
B11111
},
{ // Custom Character 2
B00000,
B00000,
B00000,
B00000,
B00000,
B00000,
B11111,
B11111
},
{ // Custom Character 3
B00000,
B00000,
B00000,
B00000,
B00000,
B11111,
B11111,
B11111
},
{ // Custom Character 4
B00000,
B00000,
B00000,
B00000,
B11111,
B11111,
B11111,
B11111
},
{ // Custom Character 5
B00000,
B00000,
B00000,
B11111,
B11111,
B11111,
B11111,
B11111
},
{ // Custom Character 6
B00000,
B00000,
B11111,
B11111,
B11111,
B11111,
B11111,
B11111
},
{ // Custom Character 7
B00000,
B11111,
B11111,
B11111,
B11111,
B11111,
B11111,
B11111
}
};

for(int i=0; i<8; i++)
lcd.createChar(i,cc[i]);
}


MESSA IN STRADA

Dopo aver assemblato e testato il tutto ho applicato il trasmettitore al contatore ENEL, appoggiandolo nella parte superiore e tenendolo in posizione grazie ad un velcro 3M molto forte., come si vede dal video.

Ho quindi applicato il sensore LDR al primo LED del contatore, usando la pasta adesiva Bluetak (o in alternativa si può usare del nastro adesivo, anche se un po' coatto) avendo cura di isolare dalla  luce esterna il sensore per non generare falsi segnali.

Ho regolato il trimmer nel trasmettitore per impostare la soglia ottimale di sensibilità della LDR, facendo in modo di ripetere il segnale del LED del contatore ENEL indipendentemente dalle condizioni di illuminazione esterna, che possono falsare le letture anche in modo sensibile.

A questo punto il lavoro è terminato, il ricevitore mostrerà il consumo istantaneo e memorizzerà il consumo storico senza nessun'altro intervento. L'autonomia del trasmettitore con la batteria NiCd usata è di circa 30-40 giorni, più che sufficienti per analizzare i propri consumi in un range temporale significativo.

Le foto a maggiore risoluzione sono su PiCasa, a questo link

FINE DELLA STORIA

Che dire ? Mi sono divertito con una bella applicazione tipicamente Arduinica, ho provato l'ebbrezza delle saldature SMD, ho imparato come interagire con una EEPROM tramite libreria Wire, e la realizzazione finale ha un look quasi commerciale.

Inutile dire che dopo averla provata per un po', stò già riguardando il tutto con l'occhio cinico da “mò ti smonto e ti uso per qualche altra idea”, ma per il momento l'idea non c'è ancora... per il momento!

Leggero' con piacere i vostri commenti, se vorrete scrivermi al seguente indirizzo: mnicolato@hotmail.com

Aggiornato il 20 agosto 2011