Realizziamo una Smart Sveglia Bluetooth con Arduino

Se anche voi, come me, da quando avete comprato uno smartphone non spegnete più il cellulare perché altrimenti la sveglia non funziona, allora questo post fa al caso vostro. In questo articolo vi porterò a riappropriarvi del tasto ‘Spegni’ del vostro cellulare realizzando una sveglia intelligente configurabile da Smartphone tramite bluetooth Low Energy e aprendo così una finestra sul mondo dei dispositivi Bluetooth Smart. Di seguito affronteremo insieme il cammino nel mondo del Real Time Clock (RTC), EEPROM, Bluetooth comprendendone le problematiche e le potenzialità, per poi concentrarci sulla realizzazione di un semplice sistema di allarme temporizzato impostabile da remoto tramite BLE.

Premesso che la 'mission' di questo articolo non è la realizzazione di un prototipo di allarme bluetooth, ma il 'proof of concept', con cui spero di aprire la mente dei lettori, iniziamo a parlare di questo progetto entrando subito nel vivo ed elencando i componenti utilizzati nella Smart Sveglia.

Componenti utilizzati

  • Arduino UNO;
  • DS1302 (RTC);
  • 24LC256 (EEPROM);
  • CR2032 (BATTERIA 3V);
  • HM-10 (MODULO BLUETOOTH LE);
  • Display LCD 16x2;
  • Trimmer 10k;
  • Quarzo 32.768 kHz;
  • Buzzer x1;
  • Resistori: 2.2kOhm x1, 470Ohm x1, 47Ohm x1;
  • LED x1;

Il Real Time Clock

Ogni nostra azione quotidiana è scandita dall’inesorabile incedere del tempo.

Questa necessità si riflette allo stesso modo nei circuiti digitali che eseguono comandi a ritmo di clock. L’oscillatore che regola il tempo di esecuzione dei comandi macchina dei microcontrollori è spesso esterno per ragioni di stabilità e dimensioni ed è in grado di scandire intervalli temporali dell’ordine dei nanosecondi con una eccellente risoluzione.

Tuttavia, in assenza di un riferimento temporale che leghi l’intervallo temporale scandito dall’oscillatore al tempo coordinato universale (UTC), è impossibile sfruttare un oscillatore come se fosse un vero e proprio orologio.

In questo contesto il real-time clock trova la sua ragion d’essere in quanto capace di portare il tempo anche quando il dispositivo all’interno del quale è integrato è spento.

Tutto ciò è possibile sfruttando un doppio circuito di alimentazione costituito dall’alimentazione primaria e dalla batteria tampone in grado di alimentare l’integrato, che consuma pochi nanoWatt, per diversi anni.

Solitamente la batteria tampone è una piccola cella al litio ma sistemi più recenti tendono ad utilizzare dei supercapacitori, in quanto questi ultimi possono essere integrati nel processo di assemblaggio del PCB.

Il primo dispositivo commerciale dotato di RTC fu il PC/AT di IBM nel 1984 che adottava il chip MC146818 di Motorola.
Attualmente i chip più utilizzati allo scopo tra gli hobbisti sono sicuramente i DS1302 e i DS1307 (in vendita su Conrad).
Non è un caso che questi microcontrollori richiedano un quarzo esterno con frequenza di 32.768 kHz, che, essendo una potenza di 2 (2^15), è più semplice da manipolare con dei contatori digitali.
Nella Smart Sveglia ho utilizzato un DS1302, già dotato di quarzo da 32.768kHz e batteria al litio CR2023 da 3V.

image credits: playzone.ch

I pin digitali di Arduino 5, 6, 7 sono stati collegati ai pin SLCK (Serial Clock), I/O e CE (Chip Enable) per implementare l’interfaccia 3 Wire.

image credits: datasheets

Se non si utilizzano le linee digitali di Arduino, già dotate di resistori di pull-up, è necessario aggiungerli in modo da preservare l’integrità della trasmissione.
A questo punto, per testare il chip, è stata utilizzata una libreria per Arduino aggiunta su GitHub da Matt Sparks  che crea un oggetto DS1302 di cui possono essere richiamati metodi di lettura o scrittura.

#include < DS1302.h >
#include < stdio.h >

#define kCePin 5 // DS1302 - Chip Enable
#define kIoPin 6 // DS1302 - Input/Output
#define kSclkPin 7 // DS1302 - Serial Clock
DS1302 rtc(kCePin, kIoPin, kSclkPin);

void printTime() 
{ 
Time t = rtc.time(); 
char buf[50]; 
snprintf(buf, sizeof(buf), "%04d-%02d-%02d %02d:%02d:%02d",t.yr, t.mon,
t.date, t.hr, t.min, t.sec); 
Serial.println(buf); 
}

void setup() 
{ 
Serial.begin(9600); 
rtc.writeProtect(false); //Abilita la scrittura sul chip dando un 
valore LOW al pin CE 
rtc.halt(false); // Mette in sleep in clock se posto a TRUE 
Time t(2014, 5, 5, 1, 38, 50, Time::kSunday); 
rtc.time(t); 
}

void loop() 
{ 
printTime(); 
delay(1000); 
}

Con questo semplice sketch è possibile settare l’ora e il giorno di riferimento per il DS1302, monitorare il tempo e visualizzarlo ogni secondo sulla porta seriale.

La chiave di volta di questo sketch sono le righe

Time t(2014, 5, 5, 1, 38, 50, Time::kSunday);
rtc.time(t);

con le quali vengono impostate ora e data corrente sull’RTC. Chiaramente questa operazione andrebbe eseguita una tantum e non ogni qualvolta viene programmato il microcontrollore.

E’ pertanto opportuno impostare un flag di controllo sul primo utilizzo del controllore in cui impostare l’orario corrente sul DS1302.

L’orario attuale viene letto attraverso il metodo rtc.time() richiamato nella funzione printTime().

 

La EEPROM

Per la realizzazione della Smart Sveglia una EEPROM esterna è del tutto NON necessaria. 

Se ne potrebbe tranquillamente fare a meno ed utilizzare quella interna ad Arduino che ha una capacità di 512 bytes; tuttavia, ho ritenuto opportuno utilizzarne una esterna per completezza di trattazione, in quanto la ridotta memoria può risultare in un limite alle potenzialità della sveglia, ad esempio se si vogliono memorizzare più sveglie o informazioni.

Una EEPROM (Electrically Erasable Programmable Read Only Memory) è una memoria di tipo non volatile che può essere scritta oppure letta.

Solitamente, per la sua capacità ridotta, è utilizzata per memorizzare impostazioni di configurazione. E’ sconsigliato utilizzarla come se fosse una RAM per via del limitato tempo di vita medio legato al numero di cicli di scrittura (tipicamente 100000).

Il componente che ho scelto per la Smart Sveglia è il chip 24LC256 prodotto da Microchip, realizzato in tecnologia CMOS ed in vendita su Conrad.

image credits: Microchip Datasheet

Il chip in questione è in grado di memorizzare fino a 32 kBytes (64 volte in più di quella interna ad Arduino), ha tempi di latenza massimi in scrittura pari a 5 ms (un dettaglio assolutamente non trascurabile) e la sua memoria è divisa in pagine da 64 bytes ciascuna (ovvero con una gestione ottimizzata di codice e risorse si potrebbe pensare di scrivere una intera pagina con un solo ciclo riducendo, così, i tempi di latenza).

Il datasheet garantisce inoltre il componente per oltre un milione di cicli di scrittura e la corretta conservazione dei dati per oltre 200 anni!

image credits: Microchip Datasheet

Il protocollo per accedere alla EEPROM è lI2C (Inter Integrated Circuit) supportato nativamente da Arduino, che richiede soltanto 2 pin: SCL (Serial Clock Line), poiché è un bus sincrono, e SDA (Serial DAta line).

I pin A0, A1 e A2 servono ad impostare l’indirizzo del chip all’interno del bus.

L’I2C infatti ha 7 bit di indirizzo e può pertanto supportare fino a 128 nodi. 

Vi sono, però, 16 indirizzi protetti e non accessibili, pertanto il numero massimo di dispositivi che possono essere connessi sul bus è di 112.

Tuttavia, il 24LC256 possiede soltanto 3 bit di indirizzamento, infatti gli altri 4 bit (quelli più significativi) vengono considerati (di default) pari a 1010.

Così facendo, il numero massimo di EEPROM che possono essere aggiunte al bus viene limitato a 2^3=8.
Il pin WP (Write Protection) se posto a valore logico alto, in questo caso Vcc, impedisce la scrittura, preservando i dati precedentemente memorizzati, e, pertanto, nell’esempio in questione è stato posto a massa.

Analogamente sono stati posti a massa i pin A0, A1, A2, assegnando, così, al chip indirizzo 1010000 sul bus I2C.

I pin SCL e SDA sono stati collegati ai pin analogici di Arduino 4 e 5, come suggerito dalla documentazione della libreria Wire.h di Arduino.

In questo modo si può evitare di inserire resistori di pull-up poiché già presenti sulla linea analogica di Arduino e abilitati attraverso la libreria suddetta.

Un’eccellente sketch per controllare la EEPROM si può trovare sul forum di Arduino utilizzando la libreria EEPROM.h, tuttavia ho sfruttato un altro riferimento che utilizza la libreria Wire.h.

#include < Wire.h >

void writeEEPROM(int deviceaddress, unsigned int eeaddress, byte data )
{
  Wire.beginTransmission(deviceaddress);
  Wire.send((int)(eeaddress >> 8)); // MSB
  Wire.send((int)(eeaddress & 0xFF)); // LSB
  Wire.send(data);
  Wire.endTransmission();
  delay(5);
}
 
byte readEEPROM(int deviceaddress, unsigned int eeaddress )
{
  byte rdata = 0xFF;
  Wire.beginTransmission(deviceaddress);
  Wire.send((int)(eeaddress >> 8)); // MSB
  Wire.send((int)(eeaddress & 0xFF)); // LSB
  Wire.endTransmission();
  Wire.requestFrom(deviceaddress,1);
  if (Wire.available()) rdata = Wire.receive();
  return rdata;
}

Attraverso queste due funzioni è possibile leggere o scrivere sulla EEPROM caratterizzata da un indirizzo I2C univoco ‘deviceaddress’ un byte ‘data’ nella locazione di memoria della EEPROM ‘eeaddress’.

E’ stato inserito un delay di 5ms nella funzione writeEEPROM poiché questo è il tempo di latenza massima garantito dal datasheet per la scrittura in memoria.

#define eeprom1 0x50    //INDIRIZZO DEL 24LC256
void setup(void)
{
  Serial.begin(9600);
  Wire.begin();
  unsigned int address = 0;
  writeEEPROM(eeprom1, address, ‘A’);
  Serial.print(readEEPROM(eeprom1, address), DEC);
}

image credits: Microchip Datasheet

Nel codice mostrato sopra, il codice HEX 0x50 corrisponde alla eeprom indirizzata con i bit A0, A1, A2 a massa, infatti

1 0 1 0 A2 A1 A0
0 1 0 0 0 0

0*(2^0)+0*(2^1)+0*(2^2)+0*(2^3)+1*(2^4)+0*(2^5)+1*(2^6)=16+64=80 in decimale che corrisponde a 50 in esadecimale.

A questo punto occorre definire anche l’indirizzo della locazione di memoria (la variabile 'address') in cui si vuole andare a scrivere il byte da salvare in memoria (in questo caso la lettera ‘A’).
Chiaramente essendo la EEPROM in questione da 32K l’indirizzo che possiamo scegliere può variare tra 0 e 31999.
Con le funzioni writeEEPROM e readEEPROM si può salvare/leggere in memoria un singolo byte e questo è sicuramente poco pratico per manipolare delle stringhe.

Si può pensare allora di scrivere delle routine per richiamare ciclicamente queste funzioni.

Quelle mostrate di seguito richiedono come parametri la dimensione delle aree di memoria da leggere (in numero di byte) e l'indirizzo da cui iniziare a leggere, oltre che quello della EEPROM sul bus I2C. 

void writeEEPROMstring(int deviceAddress, unsigned int startingEepromAddress,
unsigned int memorySize,char *string)
{
for(int i=0;i < memorySize;i++)
writeEEPROM(deviceAddress,startingEepromAddress+i,string[i]);
}

String readEEPROMstring(int deviceAddress, unsigned int startingEepromAddress,
unsigned int memorySize)
{
char string[memorySize+1];
for(int i=0;i < memorySize;i++)
string[i]=charValue(readEEPROM(deviceAddress,startingEepromAddress+i));
}
string[memorySize]='\0';
return string;
}

char charValue(int integer)
{
return char(integer-0);
}

La routine writeEEPROMstring, tuttavia, non è ottimizzata per la scrittura in memoria a pagine e pertanto il tempo di latenza per la scrittura di una stringa è pari a 5ms*lunghezza_stringa.

 

IL BLUETOOTH HM-10

Il modulo bluetooth HM-10, prodotto da JNHuaMao Technology Company, utilizza il chip di Texas Instruments CC2540 e implementa lo stack protocollare del Bluetooth 4.0 Low Energy e, pertanto, si sposa bene con l’applicazione da Smart Sveglia per via del basso consumo energetico.

Nonostante questo chip sia dotato di 34 pin, nel corso di quest’articolo mi concentrerò soltanto su quelli per il funzionamento minimale di questo dispositivo, ovvero i pin di alimentazione, quelli per l’UART, il pin di RESET e quello per il LED (utile in fase di debugging ma non necessario).

 

image credits: Microduino

Per l’UART (Universal Asynchronous Receiver-Transmitter) sono necessari, in questo esempio, soltanto i pin UART_TX e UART_RX, mentre i pin UART_CTS e UART_RTS possono essere lasciati floating.

Attenzionel’HM-10, a differenza del resto della circuiteria trattata in questo articolo, DEVE essere alimentato a 3.3V.

In questo esempio ho collegato i pin 1 e 2 dell’HM-10 rispettivamente ai pin 10 e 11 di Arduino, ho omesso il condensatore anti-disturbi tra alimentazione e massa, connesso il pin 24 a un LED verde tramite un resistore da 47 Ohm e il pin 23 alla tensione 3.3V di Arduino tramite un resistore da 3.3 kOhm, con in parallelo un interruttore di tipo Normally Open (NO).

Una volta effettuate le connessioni, il LED lampeggerà in modo continuato, per evidenziare che la periferica HM-10 è in modalità Advertising, fino a quando un Bluetooth Master non effettua l’accoppiamento (pairing).

A questo punto il LED manterrà la sua luce fissa per l'intera durata della connessione al Master.

Per implementare la UART mantenendo le funzionalità della porta seriale per il debugging è consigliabile non utilizzare i pin 0 e 1 di Arduino, ma la seriale emulata (in questo esempio dai pin 10 e 11) dalla libreria SoftwareSerial.h.

#include < SoftwareSerial.h >

#define kRxSwSer 10 // RX Software Serial - Bluetooth Comm
#define kTxSwSer 11 // TX Software Serial - Bluetooth Comm

SoftwareSerial mySerial(kRxSwSer, kTxSwSer);

void setup()
{
Serial.begin(9600); //INIZIALIZZO LA SERIALE
while(!Serial){;}
mySerial.begin(9600); //INIZIALIZZO LA SERIALE EMULATA
}

void loop()
{
char buffer[100];
int k=0;
while (mySerial.available()){
buffer[k]=mySerial.read();
k++;
}
if(k!=0){
buffer[k]='\0';
}
Serial.print(buffer);
}

Con questo codice è possibile stampare a video, tramite la seriale, i dati che si ricevono sull’UART tramite la seriale emulata dai pin 10 e 11.

 

Il Display LCD 16x2

Ogni sveglia che si rispetti, oltre a svolgere la funzione di allarme, mostra all’utente l’orario, per questo ho deciso di inserire un display LCD alfanumerico nel progetto.

Il display utilizzato è diviso in 16 colonne e 2 righe (16x2) ed è basato sul noto controller HITACHI HD44780.

Questo genere di display monta un'interfaccia di comunicazione standard a 16 pin e può essere comandato sia in modalità 8 bit (utilizza 8 pin per la comunicazione) sia4 bit (utilizza 4 pin per la comunicazione ma è più lenta poichè ogni messaggio è diviso in due blocchi).

image credits: 3.bp

Seguendo le indicazioni riportate sul datasheet risulta davvero semplice connettere il display ad un microcontrollore, in particolare, nel caso in esame, ho utilizzato la modalità di funzionamento a 4 bit, lasciando i pin D0, D1, D2, D3 floating.

Il pin RW (Read Write) è stato collegato a massa poiché il display viene, in questo esempio, utilizzato soltanto in scrittura e non in lettura.

I pin RS (Register Select) ed E (Enable) del display sono stati collegati rispettivamente ai pin 13 e 12 di Arduino.

I pin D4, D5, D6, D7 del display sono stati collegati rispettivamente ai pin 9, 8, 3, 2 di Arduino.

Il pin Vo del display è usato per la regolazione del contrasto ed è stato connesso al terminale centrale di un trimmer da 10 kOhm posto tra Vcc e GND.

Infine, i pin A e K dell’LCD, utilizzati per la retroilluminazione, sono stati connessi rispettivamente a Vcc tramite un resistore da 470 Ohm e a GND.

Attenzione: Senza il resistore, la retroilluminazione del display si brucerebbe. 

Questa è realizzata in tecnologia LED e, pertanto, fissa la tensione ai suoi capi, in questo caso a 4.2 V.
Se Vcc=5V, allora, la corrente destinata alla retroilluminazione, per la legge di Ohm, è pari a (5-4.2)/470= 1.8 mA un valore sufficientemente lontano da quello massimo tollerato dai LED, di certo poco accurato, ma rappresenta una soluzione precauzionale da attuare qualora non si possegga il datasheet dell'LCD.

Il controller HD44780 richiede una inizializzazione abbastanza complessa con tempi di latenza ben precisi, fortunatamente la libreria LiquidCrystal.h di Arduino svolge questo lavoro per noi.

#include  < LiquidCrystal.h >

#define kRSLCD 13//LCD RS
#define kELCD   12//LCD E
#define kD4LCD 9 //LCD D4
#define kD5LCD 8 //LCD D5
#define kD6LCD 3 //LCD D6
#define kD7LCD 2 //LCD D7

LiquidCrystal lcd(kRSLCD, kELCD, kD4LCD, kD5LCD, kD6LCD, kD7LCD);

void setup()
{
lcd.begin(16, 2);
delay(1000);
lcd.setCursor(0, 0);
lcd.print(“EOS DISPLAY”);
}

In questo esempio, dopo aver creato un oggetto 'lcd' della classe LiquidCrystal e averlo inizializzato come un display con 16 colonne e 2 righe, tramite il metodo lcd.begin(16, 2), è possibile visualizzare il testo “EOS DISPLAY”.

Il metodo richiamato nel codice setCursor(x,y) prevede due parametri e y che indicano rispettivamente la riga e la colonna del display a partire dalle quali iniziare a scrivere il testo. 

 

L'app iOS

Partendo dal precedente articolo sul Bluetooth Low Energy e i dispositivi Apple, andiamo a modificare il codice già esistente, predisposto per il BC127, per connettere un iPhone al modulo HM-10.

Nella classe BTViewController và fatta una piccola modifica poiché la periferica HM-10 ha un solo servizio con un'unica caratteristica che, a differenza del BC127, prevede una modalità di scrittura senza risposta (CBCharacteristicWriteWithoutResponse).

Pertanto andiamo a sostituire CBCharacteristicWriteWithResponse con CBCharacteristicWriteWithoutResponse.

Il codice Xcode allegato all'articolo è già ottimizzato per l'HM-10.

Nello storyboard ho trasformato l'unico ViewController finora presente nel rootViewController di un NavigationController e aggiunto un segue con il nome BTAlarmViewController collegato al pulsante SVEGLIA.

Ho inserito quindi da interfaccia grafica un oggetto UIDatePicker e un oggetto UIButton cui ho associato il metodo enableAction nell'header di BTAlarmViewController riportato di seguito.

 

Header

//
//  BTAlarmViewController.h
//  coreBluetoothiPhone
//
//  Created by Luigi D'Acunto on 01/05/14.
//  Copyright (c) 2014 Luigi D'Acunto. All rights reserved.
//

#import < UIKit/UIKit.h >
#import < CoreBluetooth/CoreBluetooth.h >

@interface BTAlarmViewController : UIViewController
@property (weak, nonatomic) IBOutlet UIDatePicker *datePick;
@property (weak, nonatomic) IBOutlet UIButton *enableButton;
- (IBAction)enableAction:(id)sender;

@property (strong, nonatomic) CBCentralManager *myCentralManger;
@property (strong, nonatomic) CBPeripheral *myPeripheralBLE;
@property (strong, nonatomic) CBCharacteristic *myCharacteristic;
@property (strong,nonatomic)  NSString *deviceType;
@end

Le proprietà relative al Bluetooth vengono importate dal ViewController trattato in precedenza.

Nel file di implementazione, invece, ho gestito la possibilità di abilitare/disabilitare l'allarme lavorando sul toggle del tasto ENABLE.

Ho aggiunto, inoltre, alla stringa che viene inviata dall'iPhone un carattere di inizio (*) e fine (;) comando per poter discriminare dalla periferica bluetooth la lunghezza del messaggio in arrivo.

Come evidenziato dal mokup la stringa viene inviata all'HM-10 soltanto se viene premuto il pulsante ENABLE/DISABLE che, oltre a concatenare le stringhe di data e ora in formato yyyyMMddHHmm, aggiunge un flag booleano, che indica se l'allarme è attivo o meno, e il payload (*;).

La stringa finale inviata dal telefono all'HM-10 e, che dovrà essere elaborata da Arduino, risulta allora:

 

*yyyyMMddHHmmE;

 

Implementation

//
//  BTAlarmViewController.m
//  coreBluetoothiPhone
//
//  Created by Luigi D'Acunto on 01/05/14.
//  Copyright (c) 2014 Luigi D'Acunto. All rights reserved.
//

#import "BTAlarmViewController.h"

@interface BTAlarmViewController ()

@end

@implementation BTAlarmViewController

- (id)initWithNibName:(NSString *)nibNameOrNil bundle:(NSBundle *)nibBundleOrNil
{
    self = [super initWithNibName:nibNameOrNil bundle:nibBundleOrNil];
    if (self) {
      
    }
    return self;
}

- (void)viewDidLoad
{
    [super viewDidLoad];

    NSDate *currentTime = [NSDate date];
    NSDateFormatter *dateFormatter = [[NSDateFormatter alloc] init];
    [dateFormatter setDateFormat:@"ddMMyyyy	hh:mm"];
    NSString *min = [dateFormatter stringFromDate: currentTime];

    NSString *max = @"22042024 12:00";
    NSDateFormatter *dateFormat = [[NSDateFormatter alloc] init];
    [dateFormat setDateFormat:@"ddMMyyyy hh:mm"];
    NSDate *datemin = [dateFormat dateFromString:min];
    NSDate *datemax = [dateFormat dateFromString:max];
    self.datePick.maximumDate = datemax;
    self.datePick.minimumDate = datemin;
}

- (IBAction)enableAction:(id)sender {
    if([self.enableButton.titleLabel.text isEqualToString:@"ENABLE"]){
        [self.enableButton setTitle:@"DISABLE" forState:UIControlStateNormal];
        [self sendMessage:1];
    }
    else if([self.enableButton.titleLabel.text isEqualToString:@"DISABLE"]){
        [self.enableButton setTitle:@"ENABLE" forState:UIControlStateNormal];
        [self sendMessage:0];
    }
}
-(void)sendMessage:(NSUInteger) enable{
    
     NSDateFormatter *dateFormatter = [[NSDateFormatter alloc] init];
    [dateFormatter setDateFormat:@"yyyyMMddHHmm"];
    NSString *dateString = [dateFormatter stringFromDate: self.datePick.date];
    NSString *stringSS = [NSString stringWithFormat:@"*%@%d;",dateString,enable];
    NSLog(@"%@",stringSS);
    
    NSData* data = [stringSS dataUsingEncoding:NSUTF8StringEncoding];
    if([self.deviceType isEqualToString:@"BC127"])
    {
        [self.myPeripheralBLE writeValue:data forCharacteristic:self.myCharacteristic 
type:CBCharacteristicWriteWithResponse];
        NSLog(@"Send String To BC127");
    }
    else if([self.deviceType isEqualToString:@"HM10"])
    {
        [self.myPeripheralBLE writeValue:data forCharacteristic:self.myCharacteristic 
type:CBCharacteristicWriteWithoutResponse];
        NSLog(@"Send String To HM10");
    }
}
@end

 

La Smart Sveglia

Mettendo insieme i vari frammenti di codice (più qualche funzione extra) e di hardware riportati in questo articolo e, aggiungendo un piccolo buzzer piezoelettrico tra il pin 4 di Arduino e GND, si può procedere alla realizzazione della Smart Sveglia.

Il codice che ho scritto prevede la creazione di una struttura nominata alarmType

typedef struct
{
int year;
int mon;
int day;
int hh;
int mm;
int ss;
boolean enabled;
} alarmType;

che contiene le informazioni su un orario di allarme e un flag booleano che indica se la la sveglia è stata abilitata o meno.

Definisco poi indirizzi e locazioni di memoria della EEPROM e inizializzo una variabile di tipo alarmType.

//INDIRIZZI LOCAZIONI MEMORIA EEPROM
#define ADD_FIRSTTIME 0
#define ADD_DATE 1
#define ADD_TIME 11

//DIMENSIONI LOCAZIONI MEMORIA EEPROM (aggiungere sempre +1!)
#define ADD_DATE_SIZE 11
#define ADD_TIME_SIZE 9

#define kBuzzer 4 // BUZZER

alarmType alarm;

Nel blocco di setup viene controllato un flag memorizzato nella EEPROM nella locazione di memoria ad indirizzo 0 (ADD_FIRSTTIME).

Tale flag, se posto pari a 0, indica che quello in corso è il primo utilizzo della EEPROM e, solo in questo caso, si procede ad impostare l’orario corrente sul RTC manualmente e, eventualmente, a salvare un allarme di test disabilitato sulla EEPROM.

Viene inoltre impostato il pin relativo al Buzzer come uscita, e posto a valore logico basso.

...... INSERIRE IN SETUP ......
pinMode(kBuzzer, OUTPUT);
digitalWrite(kBuzzer, LOW);

if(readEEPROM(EEPROM1, ADD_FIRSTTIME)==1){ //NOT FIRST TIME
Serial.print("Not First Time");
readAlarmFromEEPROM();
}
else{ //FIRST TIME
Serial.print(" First Time");
writeEEPROM(EEPROM1, ADD_FIRSTTIME,1);
Time t(2014, 5, 1, 17, 56, 0, Time::kMonday);
rtc.time(t);
delay(200);
saveTestAlarmToEEPROM();
delay(200);
}
}
...... INSERIRE IN SETUP ......

void saveTestAlarmToEEPROM()
{
alarm.year = 2014;
alarm.mon = 4;
alarm.day = 30;
alarm.hh = 12;
alarm.mm = 46;
alarm.ss = 40;
alarm.enabled = 0;
alarmSet(alarm);
}

void alarmSet (alarmType al)
{
String dateString = int2string(al.year)+int2string(al.mon)+int2string(al.day);
String timeString = int2string(al.hh)+int2string(al.mm)+int2string(al.ss);

char date[40];
char time[40];

dateString.toCharArray(date,dateString.length()+1);
timeString.toCharArray(time,timeString.length()+1);

writeEEPROMstring(EEPROM1,ADD_DATE,ADD_DATE_SIZE,date);
writeEEPROMstring(EEPROM1,ADD_TIME,ADD_TIME_SIZE,time);
}

void readAlarmFromEEPROM ()
{
String time = readEEPROMstring(EEPROM1,ADD_TIME,ADD_TIME_SIZE);
String date = readEEPROMstring(EEPROM1,ADD_DATE,ADD_DATE_SIZE);

alarm.year = date.substring(0,4).toInt();
alarm.mon = date.substring(4,6).toInt();
alarm.day = date.substring(6,8).toInt();
alarm.hh = time.substring(0,2).toInt();
alarm.mm = time.substring(2,4).toInt();
alarm.ss = time.substring(4,6).toInt();
}

Nel Loop, invece, la funzione sprtinf(cmdBuffer,receiveCommand(cmdBuffer)) inserisce, se presente, un comando nell'array di char cmdBuffer, una volta privato del payload dalla funzione depackCmd(char*cmd).

Il comando, che non prevede alcun tipo di eccezione, è atteso nel formato specificato nell'app iOS (yyyyMMddHHmmE).

Una volta ricevuto, questo viene scomposto in sottostringhe yyyy MM dd HH mm E dalla funzione executeCommand(char *cmd), che, convertite in interi, vanno a popolare la variabile globale alarm.

La struct alarm viene, al ciclo successivo, utilizzata dalla funzione shouldPlayAnAlarm(alarmType *alarm) che compara i suoi campi con data e ora correnti lette dal RTC e, qualora risultassero uguali e il flag alarm.enabled fosse pari a true, comunicherebbe ad Arduino di attivare il Buzzer.

void loop()
{
delay(1000);
if(shouldPlayAnAlarm(alarm)==true){
Serial.print("\nALARM!\n");
lcd.setCursor(0, 0);
lcd.print("ALARM!");
digitalWrite(kBuzzer, HIGH);
}
else{
digitalWrite(kBuzzer, LOW);
lcd.setCursor(0, 0);
lcd.print(printTime());
Serial.print(printTime());
}

char cmdBuffer[20];
sprintf(cmdBuffer,receiveCommand(cmdBuffer));
}

int shouldPlayAnAlarm(alarmType al)
{
Time t = rtc.time();
if(t.yr==al.year&&t.mon==al.mon&&t.date==al.day&&t.hr==al.hh&&t.min==al.mm)
if(al.enabled ==true)
return 1;
return 0;
}

void executeCommand(char *cmd)
{
char strTemp[20];
strTemp[0]=cmd[0];
strTemp[1]=cmd[1];
strTemp[2]=cmd[2];
strTemp[3]=cmd[3];
strTemp[4]='\0';
alarm.year = atoi(strTemp);
strTemp[0]=cmd[4];
strTemp[1]=cmd[5];
strTemp[2]='\0';
alarm.mon = atoi(strTemp);
strTemp[0]=cmd[6];
strTemp[1]=cmd[7];
strTemp[2]='\0';
alarm.day = atoi(strTemp);
strTemp[0]=cmd[8];
strTemp[1]=cmd[9];
strTemp[2]='\0';
alarm.hh = atoi(strTemp);
strTemp[0]=cmd[10];
strTemp[1]=cmd[11];
strTemp[2]='\0';
alarm.mm = atoi(strTemp);
strTemp[0]=cmd[12];
strTemp[1]='\0';
if(atoi(strTemp)==1)
alarm.enabled=1;
else
alarm.enabled=0;

}
char* receiveCommand(char *buffer)
{
int k=0;
while (mySerial.available()){
buffer[k]=mySerial.read();
k++;
}
if(k!=0)
{
buffer[k]='\0';
executeCommand(depackCmd(buffer));
return "OK";
}
else
return NULL;
}

char* depackCmd(char* cmd)
{
char strTemp[20];
int k=1,endFound=0;
for(int i=0;i if(cmd[i]=='*'){
k--;
}
else{
strTemp[k]=cmd[i];
if(strTemp[k]==';'){
strTemp[k]='\0';
endFound=1;
break;
}
k++;
}
}
if(endFound==0)
Serial.print("Missing End");
return strTemp;
}

Conclusioni

Abbiamo realizzato insieme una Smart Sveglia programmabile da remoto tramite Bluetooth Low Energy da Smartphone iOS. Lo scopo di questo piccolo progetto non è quello di realizzare un prodotto finito e funzionante bensì di aprire la mente ai lettori riguardo potenzialità e problematiche legate alla scrittura su una EEPROM e alle esigenze di sincronizzazione tra orario UTC e Arduino.

Scarica subito una copia gratis

8 Commenti

  1. Avatar photo 20000leghe 15 Maggio 2014
  2. Avatar photo Luigi.D'Acunto 15 Maggio 2014
  3. Avatar photo Boris L. 21 Maggio 2014
  4. Avatar photo Luigi.D'Acunto 21 Maggio 2014
  5. Avatar photo gfranco78 28 Settembre 2014
  6. Avatar photo 20000leghe 28 Settembre 2014

Scrivi un commento

Seguici anche sul tuo Social Network preferito!

Send this to a friend