Esplora E-book
Categorie
Esplora Audiolibri
Categorie
Esplora Riviste
Categorie
Esplora Documenti
Categorie
Maik Schmidt
© Apogeo - IF - Idee editoriali Feltrinelli s.r.l.
Socio Unico Giangiacomo Feltrinelli Editore s.r.l.
Risorse online
Sul sito dell’editore inglese, The Pragmatic Programmers,
potete scaricare un archivio con il codice relativo a tutti gli esempi
(https://pragprog.com/titles/msard2/source_code). I file
sono organizzati in cartelle e nel corso del libro la loro posizione
viene ricordata nelle didascalie dei listati presentati, quindi, per
esempio, la didascalia Welcome/HelloWorld/HelloWorld.ino
14/517
Starter Pack
Sono molti i negozi online che vendono componenti elettronici
e kit relativi ai microcontrollori Arduino, per esempio Maker Shed
(http://makershed.com) e Adafruit (http://adafruit.com).
Questi siti offrono diverse soluzioni appositamente studiate per
iniziare la realizzazione di progetti completi (Starter Pack): si rac-
comanda di acquistare almeno una di queste soluzioni.
L’offerta migliore e più economica nel momento in cui è stato
scritto questo libro era costituita dal kit Adafruit Experimentation
16/517
Kit for Arduino (ID prodotto 170), che contiene molti dei com-
ponenti necessari per realizzare i progetti qui presentati e altro
materiale utile. Controllate il contenuto del kit che avete scelto; in
generale, comunque, dovrete acquistare separatamente i compon-
enti indicati di seguito.
• Sensore Parallax PING))).
• Accelerometro ADXL335 Breakout Board.
• Header standard a 6 pin da 0,1 pollici.
• Controller Nintendo Nunchuk.
• Un sensore passivo a infrarossi.
• Un led a infrarossi.
• Un ricevitore a infrarossi.
• Un Ethernet shield.
• Una scheda per prototipi (Proto shield).
• Un cavo RCA (video composite).
Tutti i negozi aggiornano costantemente l’offerta di starter
pack, pertanto si suggerisce di verificare con attenzione le ultime
novità presenti nei cataloghi online.
Benvenuti in Arduino
Cosa serve
1.0.x solo ai casi in cui sono necessarie librerie che non funzion-
ano con la 1.6.x. Le istruzioni di seguito si riferiscono alla versione
1.6.0.
Hello, World!
Per iniziare a familiarizzare con le funzioni principali dell’IDE
provate a realizzare un semplice programma che fa lampeggiare
un led (Light-Emitting Diode). Un led è una sorgente di luce effi-
ciente ed economica, già presente anche su ogni scheda Arduino,
dove un led segnala l’alimentazione elettrica della scheda, mentre
altri due lampeggiano per segnalare la trasmissione o la ricezione
di dati attraverso la connessione seriale (Figura 1.12).
Welcome/HelloWorld/HelloWorld.ino
Riga 1 const unsigned int LED_PIN = 13;
- const unsigned int PAUSE = 500;
-
- void setup() {
5 pinMode(LED_PIN, OUTPUT);
- }
-
- void loop() {
- digitalWrite(LED_PIN, HIGH);
10 delay(PAUSE);
- digitalWrite(LED_PIN, LOW);
- delay(PAUSE);
- }
Conviene studiare il funzionamento del programma esamin-
ando il significato delle singole istruzioni. Nelle prime due righe si
definiscono le costanti unsigned int tramite la parola chiave
const. LED_PIN indica il pin digitale IO che si sta utilizzando,
mentre PAUSE imposta l’intervallo di tempo (in millisecondi) di
accensione del led.
Ogni programma Arduino richiede la presenza di una funzione
setup, che in questo primo progetto ha inizio alla riga 4. Di
seguito è riportata la sintassi tipica che definisce una funzione:
<tipo del valore restituito> <nome funzione> '('
<elenco di parametri> ')'
In questo programma la funzione è denominata setup e il
valore restituito è di tipo void in quanto non viene restituito
alcun valore. L’istruzione setup non si aspetta alcun argomento e
48/517
Funzioni di Arduino
Arduino chiama la funzione setup quando viene collegata l’ali-
mentazione elettrica; nel precedente esempio Hello, World! è
stata impiegata la medesima funzione per inizializzare la scheda e
l’hardware collegato a questa. Nel programma si utilizza il metodo
pinMode per configurare il pin 13 come pin di output. In questo
modo il pin è in grado di fornire la corrente necessaria per
accendere il led. Lo stato di default di un pin è INPUT; le parole
INPUT e OUTPUT sono costanti predefinite, come si può vedere
consultando la documentazione ufficiale del progetto
(http://arduino.cc/en/Tutorial/DigitalPins).
Un’altra funzione obbligatoria è chiamata loop e nel primo pro-
getto di esempio ha inizio alla riga 8. Questa funzione contiene la
logica principale del programma e viene utilizzata da Arduino per
creare un loop infinito. La logica di questo programma deve in
primo luogo accendere il led collegato al pin 13. Per effettuare
questa operazione si utilizza il metodo digitalWrite passando
come parametri il numero del pin e la costante HIGH. In questo
modo il pin fornisce in uscita 5 V fino alla modifica successiva e il
led collegato al pin si accende.
A questo punto il programma chiama il metodo delay e attende
500 millisecondi senza eseguire alcuna operazione. Durante
questo intervallo di tempo il pin 13 rimane in stato HIGH e il led è
sempre acceso. Il led si spegne quando si imposta di nuovo a LOW
lo stato del pin utilizzando ancora una volta il metodo digit-
alWrite. Il programma attende ancora per 500 millisecondi
prima di concludere la funzione loop. Il programma Arduino
52/517
Compilare e caricare i
programmi
Prima di compilare e caricare un programma è necessario con-
figurare due impostazioni dell’IDE, che riguardano il tipo di
scheda Arduino che si vuole impiegare e la porta seriale di col-
legamento tra scheda e computer.
A partire dalla versione 1.6.0, l’IDE cerca di identificare auto-
maticamente la scheda connessa al computer. Questo meccan-
ismo funziona abbastanza bene, ma non sempre. Per questo è
utile saper identificare manualmente la scheda Arduino e la porta
seriale utilizzate.
È facile identificare il tipo di scheda Arduino, dato che questa
informazione è stampata sulla scheda. I tipi più diffusi sono
chiamati Uno, Duemilanove, Diecimila, Nano, Mega, Mini, NG,
BT, LilyPad, Pro oppure Pro Mini. In alcuni casi dovete anche
verificare il tipo di microcontrollore installato sulla scheda, che in
genere corrisponde a un componente ATmega328. Il modello di
microcontrollore è stampato sul corpo del componente. Identific-
ate il tipo esatto di Arduino a disposizione e selezionate il modello
corrispondente nel menu Tools > Board.
A questo punto dovete scegliere la porta seriale di collegamento
tra Arduino e computer dal menu Tools > Serial Port. In OS X il
nome delle porta seriale inizia solitamente con /dev/tty.usbserial
53/517
Esercizi
• Generate diverse modalità di accensione/spegnimento
del led impostando più intervalli di pausa e modificate
57/517
Cosa serve
Di seguito sono indicati i componenti necessari per svolgere gli
esempi di questo capitolo.
1. Una scheda Arduino, per esempio il modello Uno,
Duemilanove o Diecimila.
2. Un cavo USB per collegare la scheda Arduino al
computer.
3. Un led (facoltativo).
4. Un software di monitoraggio delle comunicazioni seri-
ali (terminale), per esempio PuTTY (in ambiente Win-
dows) oppure il comando screen in Linux e OS X
(facoltativo).
61/517
Le preferenze dell’IDE
Nei progetti più semplici le impostazioni di default dell’IDE
sono più che adeguate, ma presto o tardi si avrà la necessità di
65/517
ATTENZIONE
Ricordate che l’IDE aggiorna alcune preferenze solo quando si chi-
ude l’applicazione. Dovete pertanto interrompere l’esecuzione
dell’IDE prima di modificare le preferenze agendo direttamente sul
file preferences.txt.
Welcome/LedSwitch/LedSwitch.ino
Riga 1 const unsigned int LED_PIN = 13;
- const unsigned int BAUD_RATE = 9600;
-
- void setup() {
5 pinMode(LED_PIN, OUTPUT);
- Serial.begin(BAUD_RATE);
- }
-
- void loop() {
10 if (Serial.available() > 0) {
- int command = Serial.read();
- if (command == '1') {
- digitalWrite(LED_PIN, HIGH);
- Serial.println("LED on");
15 } else if (command == '2') {
- digitalWrite(LED_PIN, LOW);
- Serial.println("LED off");
- } else {
- Serial.print("Unknown command: ");
20 Serial.println(command);
- }
- }
- }
Analogamente agli esempi già incontrati, anche in questo pro-
gramma si definisce una costante per indicare il pin ove risulta
collegato il led e si imposta la sua modalità OUTPUT tramite la fun-
zione setup. Alla riga 6 si inizializza la porta seriale utilizzando la
funzione begin della classe Serial, impostando un baud rate di
9600. (Il baud rate verrà definito nell’Appendice C.) Queste
istruzioni permettono al programma di inviare e ricevere dati
attraverso la porta seriale.
72/517
Serial.println(command, DEC);
Serial.println(command, HEX);
Serial.println(command, OCT);
Serial.println(command, BIN);
Serial.write(command);
Serial.println();
L’invio della lettera A produce un output simile al seguente:
Unknown command: 65
41
101
1000001
A
L’impostazione dell’indicatore di formato determina il tipo di
conversione che Serial.println effettua per rappresentare il
byte di dati. L’opzione DEC visualizza il byte come numero decim-
ale, HEX lo converte in numero esadecimale e così via. È interess-
ante osservare che questa conversione modifica in genere la
lunghezza dei dati trasmessi; per esempio, la rappresentazione
binaria del singolo byte 65 richiede la trasmissione di 7 byte, in
quanto contiene sette caratteri.
Osservate inoltre come sia necessario utilizzare Serial.write,
anziché Serial.println, per generare la rappresentazione del
carattere corrispondente al valore del comando. Le versioni pre-
cedenti di Arduino disponevano di un modificatore BYTE a tale
scopo, ma questo è stato rimosso in Arduino 1.0.
75/517
Sistemi di numerazione
Il nostro sistema di numerazione quotidiano ha base 10 per una
ragione evoluzionistica; è probabile che se avessimo quattro dita
per ogni mano conteremmo in base 8 e avremmo inventato i com-
puter molti secoli prima.
Da migliaia di anni siamo abituati a utilizzare sistemi di
numerazione posizionali e rappresentiamo numeri quali 4711 in
base alla convenzione indicata di seguito:
4×103 + 7×102 + 1×101 + 1×100
Esercizi
• Aggiungete nuovi comandi al programma di questo
capitolo; il comando 3 potrebbe far lampeggiare il led
per un certo intervallo di tempo.
• Provate a rendere più comprensibili i comandi del pro-
gramma; per esempio, al posto di 1 impostate il
comando “on”, mentre al posto di 2 configurate il
comando “off”.
Se avete problemi nel risolvere questi esercizi consultate le
indicazioni fornite nel Capitolo 4 a proposito delle librerie di
programma.
Parte II
Il dado binario
Cosa serve
1. Una breadboard di piccole dimensioni.
2. Tre led (per svolgere gli esercizi in fondo al capitolo è
necessario avere a disposizione un numero maggiore
di led).
3. Due resistori da 10kΩ (si veda l’Appendice A per
saperne di più sui resistori).
4. Tre resistori da 1kΩ.
5. Due pulsanti.
6. Cavi di lunghezza diversa.
7. Una scheda Arduino, per esempio un modello Arduino
Uno, Duemilanove o Diecimila.
8. Un cavo USB per collegare la scheda Arduino al
computer.
87/517
BinaryDice/Blink/Blink.ino
const unsigned int LED_PIN = 12;
const unsigned int PAUSE = 500;
void setup() {
pinMode(LED_PIN, OUTPUT);
}
void loop() {
digitalWrite(LED_PIN, HIGH);
delay(PAUSE);
digitalWrite(LED_PIN, LOW);
delay(PAUSE);
}
99/517
Un dado binario richiede solo tre led per indicare il valore deci-
male da 1 a 6. Questo valore è convertito nel sistema di
numerazione binario e ogni bit di valore 1 corrisponde a un led
acceso. Di seguito è riportato uno schema che mostra la
101/517
BinaryDice/BinaryDice.ino
Riga 1 const unsigned int LED_BIT0 = 12;
- const unsigned int LED_BIT1 = 11;
- const unsigned int LED_BIT2 = 10;
-
5 void setup() {
- pinMode(LED_BIT0, OUTPUT);
- pinMode(LED_BIT1, OUTPUT);
- pinMode(LED_BIT2, OUTPUT);
-
10 randomSeed(analogRead(A0));
- long result = random(1, 7);
- output_result(result);
- }
-
15 void loop() {
- }
-
- void output_result(const long result) {
- digitalWrite(LED_BIT0, result & B001);
103/517
BinaryDice/SimpleButton/SimpleButton.ino
const unsigned int BUTTON_PIN = 7;
const unsigned int LED_PIN = 13;
void setup() {
pinMode(LED_PIN, OUTPUT);
pinMode(BUTTON_PIN, INPUT);
}
void loop() {
const int BUTTON_STATE = digitalRead(BUTTON_PIN);
if (BUTTON_STATE == HIGH)
digitalWrite(LED_PIN, HIGH);
else
digitalWrite(LED_PIN, LOW);
}
Collegate il pulsante al pin 7 e il led al pin 13, poi inizializzate i
pin in base alle indicazioni della funzione setup. La funzione
loop rileva lo stato attuale del pin collegato al pulsante: se il pin è
HIGH si accende il led, altrimenti il led rimane spento.
Caricate il programma su Arduino e osservate che il led si
accende ogni volta che tenete premuto il pulsante, mentre si
spegne non appena lo rilasciate. Tutto sembra pronto per control-
lare il funzionamento del dado binario tramite il pulsante. Prima
109/517
BinaryDice/UnreliableSwitch/UnreliableSwitch.ino
Riga 1 const unsigned int BUTTON_PIN = 7;
- const unsigned int LED_PIN = 13;
-
- void setup() {
5 pinMode(LED_PIN, OUTPUT);
- pinMode(BUTTON_PIN, INPUT);
- }
- int led_state = LOW;
- void loop() {
10 const int CURRENT_BUTTON_STATE =
digitalRead(BUTTON_PIN);
- if (CURRENT_BUTTON_STATE == HIGH) {
- if (led_state == LOW)
- led_state = HIGH;
- else
15 led_state = LOW;
- digitalWrite(LED_PIN, led_state);
- }
- }
Le prime istruzioni inizializzano le costanti relative ai pin della
scheda, mentre la funzione setup imposta le modalità di utilizzo
dei pin. Alla riga 8 si definisce la variabile globale led_state per
memorizzare lo stato attuale del led, che deve risultare LOW
quando il led è spento e HIGH quando è acceso. La funzione loop
verifica lo stato attuale del pulsante. Se premete il pulsante lo
stato diventa HIGH e il programma commuta il contenuto di
110/517
BinaryDice/MoreReliableSwitch/MoreReliableSwitch.ino
const unsigned int BUTTON_PIN = 7;
const unsigned int LED_PIN = 13;
void setup() {
111/517
pinMode(LED_PIN, OUTPUT);
pinMode(BUTTON_PIN, INPUT);
}
int old_button_state = LOW;
int led_state = LOW;
void loop() {
const int CURRENT_BUTTON_STATE =
digitalRead(BUTTON_PIN);
if (CURRENT_BUTTON_STATE != old_button_state &&
CURRENT_BUTTON_STATE == HIGH) {
if (led_state == LOW)
led_state = HIGH;
else
led_state = LOW;
digitalWrite(LED_PIN, led_state);
}
old_button_state = CURRENT_BUTTON_STATE;
}
Dopo aver inizializzato i pin relativi a pulsante e led il pro-
gramma dichiara due nuove variabili: old_button_state mem-
orizza lo stato precedente del pulsante, mentre led_state mem-
orizza lo stato attuale del led. Entrambe queste variabili possono
assumere il valore HIGH oppure LOW.
La funzione loop deve anche in questo caso leggere lo stato cor-
rente del pulsante; ora non si deve limitare a verificare se si trova
in stato HIGH, deve anche verificare se lo stato corrente è cambiato
rispetto all’ultima rilevazione effettuata. Solo quando entrambe le
condizioni sono vere il programma commuta lo stato del led.
Questa soluzione non accende e spegne più volte il led nell’inter-
vallo di tempo in cui il pulsante rimane premuto. Alla fine del
112/517
BinaryDice/DebounceButton/DebounceButton.ino
Riga 1 const unsigned int BUTTON_PIN = 7;
- const unsigned int LED_PIN = 13;
- void setup() {
- pinMode(LED_PIN, OUTPUT);
5 pinMode(BUTTON_PIN, INPUT);
- }
-
- int old_button_state = LOW;
113/517
Inserimento di un pulsante
esterno
Ora che sapete come lavorare con i pulsanti, non dovete più
abusare del pulsante Reset della scheda Arduino per controllare il
dado: potete invece aggiungere un pulsante esterno. Nella Figura
3.10 potete osservare le modifiche da apportare al circuito; in
effetti, non dovete modificare i componenti già collegati al cir-
cuito, ma aggiungere nuovi componenti esterni. Collegate in
primo luogo un pulsante sulla breadboard e collegate un suo ter-
minale al pin 7, poi collegate il pulsante a massa attraverso un
resistore da 10kΩ e aggiungete un piccolo cavo per collegare
questo resistore a 5 V. Dopo aver sistemato l’hardware potete
digitare le istruzioni software riportate di seguito.
BinaryDice/DiceWithButton/DiceWithButton.ino
const unsigned int LED_BIT0 = 12;
const unsigned int LED_BIT1 = 11;
const unsigned int LED_BIT2 = 10;
const unsigned int BUTTON_PIN = 7;
115/517
void setup() {
pinMode(LED_BIT0, OUTPUT);
pinMode(LED_BIT1, OUTPUT);
pinMode(LED_BIT2, OUTPUT);
pinMode(BUTTON_PIN, INPUT);
randomSeed(analogRead(A0));
}
int current_value = 0;
int old_value = 0;
void loop() {
current_value = digitalRead(BUTTON_PIN);
if (current_value != old_value && current_value ==
HIGH) {
output_result(random(1, 7));
delay(50);
}
old_value = current_value;
}
BinaryDice/DiceGame/DiceGame.ino
Riga 1 #include <Bounce2.h>
- const unsigned int LED_BIT0 = 12;
- const unsigned int LED_BIT1 = 11;
- const unsigned int LED_BIT2 = 10;
5 const unsigned int GUESS_BUTTON_PIN = 5;
- const unsigned int START_BUTTON_PIN = 7;
- const unsigned int BAUD_RATE = 9600;
- const unsigned int DEBOUNCE_DELAY = 20;
-
120/517
10 int guess = 0;
- Bounce start_button;
- Bounce guess_button;
-
- void setup() {
15 pinMode(LED_BIT0, OUTPUT);
- pinMode(LED_BIT1, OUTPUT);
- pinMode(LED_BIT2, OUTPUT);
- pinMode(START_BUTTON_PIN, INPUT);
- pinMode(GUESS_BUTTON_PIN, INPUT);
20 start_button.attach(START_BUTTON_PIN);
- start_button.interval(DEBOUNCE_DELAY);
- guess_button.attach(GUESS_BUTTON_PIN);
- guess_button.interval(DEBOUNCE_DELAY);
- randomSeed(analogRead(A0));
25 Serial.begin(BAUD_RATE);
- }
-
- void loop() {
- handle_guess_button();
30 handle_start_button();
- }
-
- void handle_guess_button() {
- if (guess_button.update()) {
35 if (guess_button.read() == HIGH) {
- guess = (guess % 6) + 1;
- output_result(guess);
- Serial.print("Guess: ");
- Serial.println(guess);
40 }
- }
- }
-
- void handle_start_button() {
45 if (start_button.update()) {
121/517
- if (start_button.read() == HIGH) {
- const int result = random(1, 7);
- output_result(result);
- Serial.print("Result: ");
50 Serial.println(result);
- if (guess > 0) {
- if (result == guess) {
- Serial.println("You win!");
- hooray();
55 } else {
- Serial.println("You lose!");
- }
- }
- delay(2000);
60 guess = 0;
- }
- }
- }
- void output_result(const long result) {
65 digitalWrite(LED_BIT0, result & B001);
- digitalWrite(LED_BIT1, result & B010);
- digitalWrite(LED_BIT2, result & B100);
- }
-
70 void hooray() {
- for (unsigned int i = 0; i < 3; i++) {
- output_result(7);
- delay(500);
- output_result(0);
75 delay(500);
- }
- }
In effetti il programma è piuttosto lungo ma si tratta perlopiù di
istruzioni già note e le parti nuove sono abbastanza semplici. Alla
prima riga si include la libreria Bounce2 che verrà impiegata più
122/517
Esercizi
• Il dado binario può funzionare bene quando giocate a
Monopoli con i vostri amici, ma la maggior parte delle
persone sembra preferire i più convenzionali dadi
decimali. Provate a trasformare il dado da binario a
decimale utilizzando sei led. Disponete i led così come
li vedete sulla faccia di un dado qualsiasi.
• I resistori da 1kΩ impiegati in questo capitolo per pro-
teggere i led hanno un valore di resistenza piuttosto
elevato. Leggete l’Appendice A e sostituite questi com-
ponenti con resistori che abbiano una resistenza più
bassa, per esempio 470Ω. Si nota la differenza di
luminosità dei led?
127/517
Libreria per la
generazione di un codice
Morse
Cosa serve
1. Una scheda Arduino, per esempio un modello Arduino
Uno, Duemilanove o Diecimila.
2. Un cavo USB per collegare la scheda Arduino al
computer.
3. Un altoparlante o un buzzer (facoltativi).
Realizzare un generatore di
codice Morse
L’elemento principale della libreria del generatore è costituito
dalla classe C++ chiamata Telegraph. In questo paragrafo verrà
definita la sua interfaccia a partire dallo schema di principio
indicato di seguito.
TelegraphLibrary/TelegraphLibrary.ino
void setup() {
}
void loop() {
}
Si tratta ovviamente di un programma Arduino ridotto ai min-
imi termini, che non svolge alcuna operazione a eccezione della
dichiarazione delle funzioni obbligatorie, ancora prive di
istruzioni. Questa tecnica di sviluppo consente però di compilare
il lavoro progressivamente verificando di volta in volta la presenza
di errori sintattici. Salvate il programma con il nome Tele-
graphLibrary, in modo che l’IDE possa creare una cartella
chiamata TelegraphLibrary e vi includa il file Tele-
graphLibrary.ino. Tutti i file e le directory richiesti dalla libreria
saranno memorizzati nella cartella TelegraphLibrary.
A questo punto aprite una nuova scheda e indicate il nome di
file telegraph.h. Proprio così, avete appena creato un file header
132/517
TelegraphLibrary/telegraph.h
#ifndef __TELEGRAPH_H__
#define __TELEGRAPH_H__
class Telegraph {
public:
Telegraph(const int output_pin, const int
dit_length);
void send_message(const char* message);
private:
void dit();
void dah();
void output_code(const char* code);
void output_symbol(const int length);
int _output_pin;
int _dit_length;
int _dah_length;
};
#endif
Ricordate sempre che la programmazione orientata agli oggetti
non è più una esclusiva delle CPU di grandi capacità! In questo
caso il programma descrive l’interfaccia della classe Telegraph da
impiegare nel vostro primo progetto importante, a condizione
ovviamente che siate interessati alla trasmissione di informazioni
in codice Morse.
La prima riga del programma imposta un meccanismo di esclu-
sione della doppia inclusione; in altre parole, il corpo del file
133/517
TelegraphLibrary/telegraph.cpp
#include <ctype.h>
#include <Arduino.h>
#include "telegraph.h"
"--.." // Z
};
TelegraphLibrary/telegraph.cpp
Telegraph::Telegraph(const int output_pin, const int
dit_length) {
_output_pin = output_pin;
_dit_length = dit_length;
_dah_length = dit_length * 3;
pinMode(_output_pin, OUTPUT);
}
Il costruttore richiede due argomenti: il numero del pin ove
inviare il codice Morse e la lunghezza di un punto espresso in mil-
lisecondi. Il programma memorizza questi valori in variabili
istanza corrispondenti, calcola la durata di una linea e imposta il
pin di comunicazione come pin di output.
Avete probabilmente notato che le variabili istanza di tipo
privato hanno un nome che inizia con un underscore (_), una con-
venzione apprezzata ma non imposta da C++ o dall’IDE Arduino.
137/517
TelegraphLibrary/telegraph.cpp
void Telegraph::output_code(const char* code) {
const unsigned int code_length = strlen(code);
void Telegraph::dit() {
Serial.print(".");
output_symbol(_dit_length);
}
void Telegraph::dah() {
Serial.print("-");
output_symbol(_dah_length);
}
void Telegraph::output_symbol(const int length) {
digitalWrite(_output_pin, HIGH);
delay(length);
138/517
digitalWrite(_output_pin, LOW);
}
La funzione output_code accetta una sequenza in codice Morse
costituita da punti e linee e la converte in chiamate delle funzioni
dit e dah. I metodi dit e dah inviano un punto e una linea alla
porta seriale e lasciano il resto dell’elaborazione al metodo out-
put_symbol, a cui passano la lunghezza del simbolo in codice
Morse da trasmettere. La funzione output_symbol imposta il pin
di output nello stato HIGH per tutta la lunghezza del simbolo,
dopodiché viene ripristinato lo stato LOW. Le operazioni vengono
effettuate in base allo schema di temporizzazione del codice
Morse; manca solo l’implementazione del metodo send_message,
riportata di seguito.
TelegraphLibrary/telegraph.cpp
Riga 1 void Telegraph::send_message(const char*
message) {
- for (unsigned int i = 0; i <
strlen(message); i++) {
- const char current_char =
toupper(message[i]);
- if (isalpha(current_char)) {
5 output_code(LETTERS[current_char -
'A']);
- delay(_dah_length);
- } else if (isdigit(current_char)) {
- output_code(DIGITS[current_char - '0']);
- delay(_dah_length);
10 } else if (current_char == ' ') {
- Serial.print(" ");
- delay(_dit_length * 7);
139/517
- }
- }
15 Serial.println();
- }
Questa funzione trasmette un carattere del messaggio alla volta,
all’interno di un ciclo di istruzioni. Alla riga 3 il carattere iniziale è
convertito in una lettera maiuscola, dato che le minuscole non
sono riconosciute dal codice Morse (questo è il motivo per cui non
è possibile implementare un client per chat da trasmettere in
codice Morse). A questo punto il programma verifica se il car-
attere attuale è costituito da una lettera ricavata dalla funzione
isalpha del linguaggio C. In questo caso si utilizza il carattere per
stabilire la rappresentazione in codice Morse memorizzata
nell’array LETTERS. Per eseguire questa operazione si adotta un
vecchio stratagemma: nella tabella ASCII tutte le lettere (e le
cifre) compaiono in modo sequenziale, ovvero si ha A=65, B=66 e
così via. Il carattere attuale può pertanto essere trasformato
nell’indice dell’array LETTERS sottraendo 65 ('A') dal valore del
codice ASCII. Il valore corrente del codice Morse è poi trasferito al
metodo output_symbol e si ritarda in questo modo il programma
per un tempo pari alla durata di una linea.
L’algoritmo prevede un funzionamento analogo per la trasmis-
sione di cifre. Occorre semplicemente ricavare l’indice della array
DIGITS e non dell’array LETTERS e poi sottrarre il valore ASCII del
carattere '0'.
Alla riga 10 il programma verifica la presenza di un carattere
vuoto; in questo caso si deve inviare alla porta seriale un carattere
vuoto e attendere per un tempo pari a sette punti. Tutti gli altri
140/517
TelegraphLibrary/examples/HelloWorld/HelloWorld.ino
#include "telegraph.h"
const unsigned int BAUD_RATE = 9600;
const unsigned int OUTPUT_PIN = 13;
const unsigned int DIT_LENGTH = 200;
void setup() {
Serial.begin(BAUD_RATE);
}
void loop() {
telegraph.send_message("Hello, world!");
delay(5000);
}
142/517
TelegraphLibrary/examples/MorseCodeGenerator/MorseCodeGener-
ator.ino
#include "telegraph.h"
char message_text[MAX_MESSAGE_LEN];
int index = 0;
void setup() {
Serial.begin(BAUD_RATE);
}
void loop() {
if (Serial.available() > 0) {
int current_char = Serial.read();
if (current_char == NEWLINE || index ==
MAX_MESSAGE_LEN - 1) {
message_text[index] = 0;
index = 0;
telegraph.send_message(message_text);
} else {
message_text[index++] = current_char;
}
}
}
Anche in questo caso si include il file header della classe Tele-
graph e come al solito si definiscono alcune costanti: OUTPUT_PIN
stabilisce il pin di collegamento del led, mentre DIT_LENGTH con-
tiene la durata di un punto espressa in millisecondi. La costante
NEWLINE è impostata con il valore ASCII che corrisponde al car-
attere newline; questo carattere è necessario per stabilire la fine
del messaggio da trasmettere. Infine, la costante
MAX_MESSAGE_LEN stabilisce la lunghezza massima dei messaggi
che il programma consente di trasmettere in codice Morse.
A questo punto si definiscono tre variabili globali: mes-
sage_text è il buffer di caratteri da riempire con i dati ricevuti
dalla porta seriale, index tiene traccia della posizione attuale nel
buffer e telegraph è l’oggetto Telegraph da impiegare per
145/517
TelegraphLibrary/keywords.txt
# Sintassi a colori della libreria Telegraph
Telegraph KEYWORD1
send_message KEYWORD2
Le righe vuote e le righe che iniziano con un carattere # conten-
gono commenti e vengono ignorate dal compilatore. Le altre righe
contengono il nome di uno dei membri della libreria e il tipo di
membro. Separate i due parametri con un carattere di tab-
ulazione. Le classi sono di tipo KEYWORD1, mentre le funzioni sono
di tipo KEYWORD2. Per quanto riguarda le costanti impostate il tipo
LITERAL1.
Attivate la sintassi a colori per la libreria Telegraph copiando il
file keywords.txt nella cartella libraries/Telegraph e riavviate
l’IDE. A questo punto il nome della classe Telegraph compare in
arancione, mentre send_message è in marrone.
Prima di terminare la pubblicazione della libreria dovete
effettuare le operazioni descritte di seguito.
• Memorizzate gli esempi di programma in una cartella
chiamata examples e copiate questa cartella nella dir-
ectory libraries/Telegraph. Ogni programma deve
avere una cartella in questa directory.
148/517
Esercizi
• Il codice Morse non solo supporta lettere e numeri, ma
definisce anche simboli di uso comune, per esempio le
virgole. Modificate la classe Telegraph in modo che il
programma possa interpretare l’intero codice Morse.
152/517
led_device->output_symbol(200);
speaker_device->output_symbol(200);
Il resto del programma è lasciato come esercizio da
svolgere per conto vostro.
155/517
Cosa serve
1. Un sensore a ultrasuoni Parallax PING))).
2. Un sensore di temperatura Analog Devices TMP36.
3. Una breadboard.
4. Cavi di collegamento.
5. Una scheda Arduino, per esempio un modello Uno,
Duemilanove o Diecimila.
6. Un cavo USB per collegare Arduino al computer.
158/517
InputDevices/Ultrasonic/Simple/Simple.ino
Riga 1 const unsigned int PING_SENSOR_IO_PIN = 7;
- const unsigned int BAUD_RATE = 9600;
-
- void setup() {
5 Serial.begin(BAUD_RATE);
- }
-
- void loop() {
- pinMode(PING_SENSOR_IO_PIN, OUTPUT);
10 digitalWrite(PING_SENSOR_IO_PIN, LOW);
- delayMicroseconds(2);
-
- digitalWrite(PING_SENSOR_IO_PIN, HIGH);
- delayMicroseconds(5);
15 digitalWrite(PING_SENSOR_IO_PIN, LOW);
-
- pinMode(PING_SENSOR_IO_PIN, INPUT);
- const unsigned long duration =
pulseIn(PING_SENSOR_IO_PIN, HIGH);
- if (duration == 0) {
20 Serial.println("Warning: We did not get a
pulse from sensor.");
- } else {
- Serial.print("Distance to nearest
object:");
-
Serial.print(microseconds_to_cm(duration));
- Serial.println(" cm");
25 }
-
165/517
- delay(100);
- }
-
30 unsigned long microseconds_to_cm(const
unsigned long microseconds) {
- return microseconds / 29 / 2;
- }
Il programma definisce in primo luogo una costante relativa al
pin di IO cui è collegato il sensore PING))). Per collegare il
sensore a un altro pin digitale dovete semplicemente modificare la
prima riga del programma. Il metodo setup imposta a 9600 il
baud rate della porta seriale, in quanto i dati del sensore dov-
ranno essere visualizzati nel monitor seriale.
Il funzionamento concreto del sensore ha luogo nel ciclo loop
che implementa il protocollo di funzionamento del componente
PING))). In base alle sue specifiche (http://www.parallax.com/
downloads/ping-ultrasonic-distance-sensor-product-
guide), si può controllare il funzionamento del sensore tramite
una serie di impulsi; anche i risultati della misura corrispondono
a impulsi a larghezza variabile.
Le righe da 9 a 11 impostano il pin di segnale del sensore allo
stato LOW per 2 microsecondi, in modo da configurare lo stato
iniziale della misura e garantire la presenza di impulsi HIGH puliti
da impiegare nei passi successivi della misurazione. Ricordate che
i segnali elettronici sono sempre soggetti a picchi di tensione
dovuti all’alimentazione elettrica.
A questo punto si può avviare la misurazione vera e propria. Le
righe da 13 a 15 impostano il pin di segnale del sensore allo stato
HIGH per 5 microsecondi e avviano in questo modo una nuova
166/517
Aumentare la sensibilità
utilizzando numeri a virgola
mobile
Le specifiche del sensore PING))) dichiarano un’accuratezza
delle misure per distanze variabili tra 2 centimetri e 3 metri.
Questi valori dipendono dalla lunghezza dell’impulso generato dal
sensore: la durata minima dell’impulso è di 115 microsecondi,
mentre la sua lunghezza massima è di 18,5 millisecondi. La pro-
cedura descritta per questo progetto non sfrutta completamente
la precisione del sensore, dato che i calcoli vengono effettuati
riportando risultati interi. In questo modo è possibile ricavare
misure con l’accuratezza di un centimetro. Per ottenere una sens-
ibilità della misura nell’ordine del millimetro occorre impiegare
valori numerici a virgola mobile.
In generale l’adozione di valori interi è dettata dalle scarse
risorse di memoria e dalle prestazioni della CPU della scheda
Arduino, tenendo conto che i calcoli a virgola mobile richiedono
un certo dispendio di risorse hardware. A volte, però, è utile sfrut-
tare i valori a virgola mobile e Arduino supporta calcoli di questo
genere. Perfezionate il progetto con il programma qui riportato.
InputDevices/Ultrasonic/Float/Float.ino
Riga 1 const unsigned int PING_SENSOR_IO_PIN = 7;
- const unsigned int BAUD_RATE = 9600;
171/517
- }
-
35 void output_distance(const unsigned long
duration) {
- Serial.print("Distance to nearest object:
");
- Serial.print(microseconds_to_cm(duration));
- Serial.println(" cm");
- }
Questo programma non è molto diverso da quello precedente.
In primo luogo si imposta il valore 29,155 per indicare il tempo in
microsecondi impiegato dal segnale per percorrere la distanza di
un centimetro. Il calcolo della distanza tiene conto dello spazio
che esiste tra il sensore e il circuito che contiene il componente.
Il collegamento del sensore alla breadboard introduce in genere
un piccolo gap dovuto alla distanza tra sensore e bordo della
breadboard. Questo gap è definito alla riga 5 del programma e
viene ripreso nel calcolo successivo della distanza. Il gap è misur-
ato in centimetri e il suo valore è moltiplicato per due, dato che il
segnale sonoro percorre lo stesso gap in avanti e all’indietro.
Il metodo loop risulta ora più chiaro, in quanto le operazioni
principali del programma sono state riportate in funzioni distinte
tra loro. La logica di controllo del sensore è impostata dal metodo
measure_distance, mentre il metodo output_distance elabora i
valori di output da inviare alla porta seriale. Le modifiche più sig-
nificative di questa versione del programma riguardano la fun-
zione microseconds_to_cm, che restituisce un valore a virgola
mobile ed esegue la sottrazione tra misura del sensore e gap. La
173/517
Aumentare la precisione
utilizzando un sensore di
temperatura
Il supporto di numeri a virgola mobile è un miglioramento del
programma, anche se l’aumento di precisione è legato essenzial-
mente al dato inviato in output; si può ottenere un risultato simile
anche modificando i calcoli che producono i risultati numerici.
Per ora aumenteremo la sensibilità delle misure senza ricorrere
ad artifici software, ma impiegando un nuovo componente hard-
ware, ovvero un sensore di temperatura.
Nei paragrafi precedenti si è partiti dall’ipotesi che il suono
viaggia nell’aria a una velocità di 343 m/s, un valore che non è
particolarmente accurato in quanto la velocità del suono non è
costante e dipende, tra l’altro, dalla temperatura dell’aria. Tras-
curare la temperatura può comportare un errore abbastanza sig-
nificativo, che può raggiungere il 12% rispetto al valore esatto. Di
seguito è riportata la relazione matematica che lega la velocità
effettiva del suono (C) e la temperatura dell’aria (t):
C = 331,5 + (0,6 * t)
InputDevices/Temperature/SensorTest/SensorTest.ino
Riga 1 const unsigned int TEMP_SENSOR_PIN = A0;
- const float SUPPLY_VOLTAGE = 5.0;
- const unsigned int BAUD_RATE = 9600;
-
5 void setup() {
- Serial.begin(BAUD_RATE);
- }
-
- void loop() {
10 const float tempC = get_temperature();
- const float tempF = (tempC * 9.0 / 5.0) +
32.0;
- Serial.print(tempC);
- Serial.print(" C, ");
- Serial.print(tempF);
15 Serial.println(" F");
- delay(1000);
- }
-
- const float get_temperature() {
20 const int sensor_voltage =
analogRead(TEMP_SENSOR_PIN);
- const float voltage = sensor_voltage *
SUPPLY_VOLTAGE / 1024;
- return (voltage * 1000 - 500) / 10;
- }
Le prime due righe del programma impostano le costanti relat-
ive al pin analogico cui è collegato il sensore e la tensione di ali-
mentazione fornita dalla scheda Arduino. Le istruzioni successive
sono il consueto metodo setup seguito da un metodo loop che
riporta in output la temperatura corrente una volta al secondo. La
logica di funzionamento del sensore è incapsulata nel metodo
178/517
InputDevices/Ultrasonic/PreciseSensor/PreciseSensor.ino
Riga 1 const unsigned int TEMP_SENSOR_PIN = A0;
- const float SUPPLY_VOLTAGE = 5.0;
- const unsigned int PING_SENSOR_IO_PIN = 7;
- const float SENSOR_GAP = 0.2;
5 const unsigned int BAUD_RATE = 9600;
- float current_temperature = 0.0;
183/517
2129,1071
2129,1063
2129,1063
2129,1063
L’output è costituito da un elenco di valori separati da virgole
nel quale il primo valore di ogni coppia rappresenta la temper-
atura rilevata in gradi Celsius mentre il secondo è la distanza tra
Arduino e l’oggetto più vicino, misurata in centimetri. Entrambi i
valori devono essere divisi per 100 per ottenere il risultato della
misura effettuata dai sensori.
Il progetto include ora due sensori, uno dei quali è collegato a
un pin digitale e l’altro a uno analogico. Nel prossimo paragrafo
vedrete come trasferire i dati dei sensori su un computer e come
impiegare questi dati per realizzare applicazioni che tengano
conto delle rilevazioni eseguite nell’ambiente esterno alla scheda
di controllo.
Creazione di un cruscotto
In questo paragrafo, invece di stampare i dati dei nostri sensori
digitali e analogici su una porta seriale, simuleremo una piccola
parte del cruscotto di una moderna automobile. La maggior parte
delle automobili di oggi mostra la temperatura attuale, e molte
dispongono anche di sensori di parcheggio che avvisano quando
ci si avvicina troppo a un altro oggetto.
Nella mia vettura, il sistema di controllo dei sensori di parcheg-
gio è costituito da una coppia di led arancione e rosso. Se non ci
sono oggetti vicino all’auto, tutti i led sono spenti, ma non appena
la distanza tra la vettura e un potenziale ostacolo si riduce ecces-
sivamente, si accende il primo led arancione. Man mano che la
distanza diminuisce, si accendono sempre più led; quando la dis-
tanza raggiunge un limite critico, tutti i led sono accesi e l’auto
emette un fastidioso segnale acustico.
Questa è proprio l’applicazione che andremo a realizzare:
mostra la temperatura corrente, e con l’accensione di una prima
luce rossa segnala che è presente un oggetto molto vicino al
sensore di distanza.
188/517
InputDevices/Dashboard/manifest.json
{
"manifest_version": 2,
"name": "Dashboard Demo",
"version": "1",
"permissions": [ "serial" ],
"app": {
"background": {
"scripts": ["background.js"]
}
},
"minimum_chrome_version": "33"
}
Definisce tutte le metainformazioni necessarie e dichiara che
l’app Chrome deve accedere alla porta seriale. Anche il file back-
ground.js non è particolarmente interessante.
189/517
InputDevices/Dashboard/background.js
chrome.app.runtime.onLaunched.addListener(function() {
chrome.app.window.create('main.html', {
id: 'main',
bounds: { width: 600, height: 300 }
});
});
Apre una nuova finestra e visualizza il file main.html.
InputDevices/Dashboard/main.html
Riga 1 <!DOCTYPE html>
- <html lang="en">
- <head>
- <meta charset="utf-8"/>
5 <link rel="stylesheet" type="text/css"
href="css/dashboard.css"/>
- <title>Dashboard Demo</title>
- </head>
- <body>
- <div id="dashboard">
10 <div id="distance-display">
- <p>
- <span id="d1">●</span>
- <span id="d2">●</span>
- <span id="d3">●</span>
15 <span id="d4">●</span>
- <span id="d5">●</span>
- <span id="d6">●</span>
- <span id="d7">●</span>
- <span id="d8">●</span>
20 </p>
- </div>
- <div id="temperature-display">
- <p><span id="temperature"></span>
190/517
℃</p>
- </div>
25 </div>
- <script
src="js/serial_device.js"></script>
- <script src="js/dashboard.js"></script>
- </body>
- </html>
Per creare l’interfaccia utente del cruscotto, ci serve solo un po’
di codice HTML di base. L’intero display relativo ai sensori di
parcheggio è definito nelle righe da 12 a 19. Ogni led è rapp-
resentato da un elemento <span> contenente il carattere Unicode
(●) per il pallino pieno. Ogni elemento <span> riceve un
ID univoco, che ci permetterà in seguito di fare riferimento ai sin-
goli led.
Il display della temperatura è ancora più semplice, visto che è
costituito da un singolo elemento <span>. Abbiamo aggiunto il
carattere Unicode per il simbolo dei gradi Celsius (℃) per
ottenere un risultato più professionale. Aggiungiamo anche un po’
di codice CSS per rendere il cruscotto ancora più accattivante.
InputDevices/Dashboard/css/dashboard.css
body {
font-size: 50px;
background: black;
color: white;
}
#distance-display,
#temperature-display {
191/517
text-align: center;
}
Il foglio di stile aumenta la dimensione del font e imposta il
colore di sfondo a nero e il colore del testo a bianco; inoltre,
centra il display dei led e il display della temperatura.
È il momento di dare vita al cruscotto con il codice JavaScript.
InputDevices/Dashboard/js/dashboard.js
Riga 1 var arduino = new SerialDevice("/dev/
tty.usbmodem24321", 9600);
-
- arduino.onConnect.addListener(function() {
- console.log("Connected to: " +
arduino.path);
5 });
-
- arduino.onReadLine.addListener(function(line)
{
- console.log("Read line: " + line);
- var attr = line.split(",");
10 if (attr.length == 2) {
- var temperature =
Math.round(parseInt(attr[0]) / 100.0 * 10) / 10;
- var distance = parseInt(attr[1]) / 100.0;
- updateUI(temperature, distance);
- }
15 });
-
- var lights = {
- d1: [35.0, "orange"],
- d2: [30.0, "orange"],
20 d3: [25.0, "orange"],
- d4: [20.0, "orange"],
- d5: [15.0, "orange"],
192/517
Esercizi
• Costruite un sistema automatico di allarme anti-intru-
sione che visualizza un cartello di “Stop” ogni volta che
qualcuno si avvicina al vostro computer (potete tro-
vare un cartello di questo genere all’indirizzo
http://en.wikipedia.org/wiki/
File:Stop_sign_MUTCD.svg). Fate in modo che
l’applicazione risulti il più possibile precisa; per esem-
pio, prevedete un ritardo di attivazione per evitare che
196/517
Cosa serve
1. Una scheda Arduino Proto Shield (opzionale).
2. Un accelerometro ADXL335.
3. Un pulsante.
4. Un resistore da 10kΩ.
5. Cavi di collegamento su breadboard.
6. Una mini breadboard (se non utilizzate un Proto
Shield).
7. Una scheda Arduino, per esempio un modello Arduino
Uno, Duemilanove o Diecimila.
8. Un cavo USB per collegare la scheda Arduino al
computer.
9. Un connettore standard da 0,1 pollici a 6 pin.
199/517
Cablaggio dell’accelerometro
Esistono in commercio molti accelerometri che si contraddis-
tinguono fondamentalmente per il numero di assi spaziali sup-
portati; in genere si trovano componenti che riconoscono due o
tre assi spaziali. Nel progetto di questo capitolo verrà impiegato
l’accelerometro ADXL335 di Analog Devices, semplice da cablare
e da reperire (http://www.analog.com/en/sensors/inertial-
sensors/adxl335/products/product.html). Analog Devices
propone anche altri accelerometri, chiamati per esempio
200/517
Mettere in funzione
l’accelerometro
Una strategia decisamente pragmatica che permette di cono-
scere un nuovo dispositivo di controllo consiste nel metterlo in
funzione e osservare i dati che fornisce. Di seguito è riportato un
programma che legge i valori di input dell’accelerometro relativi
ai tre assi spaziali e li riporta in output sulla porta seriale.
MotionSensor/SensorTest/SensorTest.ino
const unsigned int X_AXIS_PIN = 2;
const unsigned int Y_AXIS_PIN = 1;
const unsigned int Z_AXIS_PIN = 0;
const unsigned int BAUD_RATE = 9600;
void setup() {
Serial.begin(BAUD_RATE);
}
void loop() {
Serial.print(analogRead(X_AXIS_PIN));
Serial.print(" ");
Serial.print(analogRead(Y_AXIS_PIN));
Serial.print(" ");
Serial.println(analogRead(Z_AXIS_PIN));
delay(100);
}
Questo semplice programma di prova definisce una funzione
setup che imposta tre costanti cui corrispondono i tre pin analo-
gici della scheda e inizializza la porta seriale. È interessante osser-
vare che i pin analogici non sono stati impostati a INPUT in modo
esplicito, dato che questa è la loro impostazione di default.
205/517
Individuazione e
ottimizzazione dei valori limite
La misura delle grandezze fisiche è ben lungi dall’essere per-
fetta, in particolare quando si fa riferimento alle rilevazioni
effettuate da svariati tipi di sensori, compresi gli accelerometri.
Questi componenti generano sempre dati compresi tra un minimo
e un massimo, ma tali valori possono variare nel tempo. I dati ril-
evati possono inoltre manifestare una certa oscillazione anche
quando si riferiscono a una posizione fissa, evidenziando una
serie di “vibrazioni” (jitter) che modificano i valori di output
anche quando non muovete il sensore. Dovete anche tenere conto
che le misure possono cambiare di valore in modo non sempre
206/517
MotionSensor/SensorValues/SensorValues.ino
const unsigned int X_AXIS_PIN = A2;
const unsigned int Y_AXIS_PIN = A1;
const unsigned int Z_AXIS_PIN = A0;
const unsigned int BAUD_RATE = 9600;
void setup() {
Serial.begin(BAUD_RATE);
min_x = min_y = min_z = 1000;
max_x = max_y = max_z = -1000;
}
void loop() {
const int x = analogRead(X_AXIS_PIN);
const int y = analogRead(Y_AXIS_PIN);
const int z = analogRead(Z_AXIS_PIN);
Serial.print("x(");
207/517
Serial.print(min_x);
Serial.print("/");
Serial.print(max_x);
Serial.print("), y(");
Serial.print(min_y);
Serial.print("/");
Serial.print(max_y);
Serial.print("), z(");
Serial.print(min_z);
Serial.print("/");
Serial.print(max_z);
Serial.println(")");
}
Questo programma dichiara le variabili che contengono il
valore minimo e massimo per i tre assi e le inizializza impostando
numeri (-1000 e 1000) che sono sicuramente esterni all’intervallo
di misura del sensore. La funzione loop misura con continuità
l’accelerazione lungo i tre assi e regola di conseguenza i rispettivi
valori minimi e massimi.
Compilate e caricate il programma, poi spostate la breadboard
su cui è montato il sensore in tutte le direzioni, facendola anche
ruotare lungo i tre assi. Traslate il sensore lentamente, poi in
modo più rapido, inclinatelo prima piano e poi in modo più
deciso. Fate attenzione che durante i diversi movimenti la bread-
board non perda incidentalmente qualche connessione.
Proseguite le prove fino a quando ottenete valori stabili sia per
il minimo sia per il massimo. Dovreste visualizzare un output
simile a questo:
x(247/649), y(253/647), z(278/658)
208/517
MotionSensor/Buffering/Buffering.ino
Riga 1 const unsigned int X_AXIS_PIN = 2;
- const unsigned int Y_AXIS_PIN = 1;
- const unsigned int Z_AXIS_PIN = 0;
- const unsigned int NUM_AXES = 3;
5 const unsigned int PINS[NUM_AXES] = {
- X_AXIS_PIN, Y_AXIS_PIN, Z_AXIS_PIN
- };
- const unsigned int BUFFER_SIZE = 16;
- const unsigned int BAUD_RATE = 9600;
10 int buffer[NUM_AXES][BUFFER_SIZE];
- int buffer_pos[NUM_AXES] = { 0 };
-
- void setup() {
- Serial.begin(BAUD_RATE);
15 }
-
- int get_axis(const int axis) {
- delay(1);
- buffer[axis][buffer_pos[axis]] =
analogRead(PINS[axis]);
20 buffer_pos[axis] = (buffer_pos[axis] + 1) %
BUFFER_SIZE;
- long sum = 0;
209/517
MotionSensor/Controller/Controller.ino
#include <Bounce2.h>
const unsigned int BUTTON_PIN = 7;
const unsigned int X_AXIS_PIN = A2;
const unsigned int Y_AXIS_PIN = A1;
const unsigned int Z_AXIS_PIN = A0;
const unsigned int NUM_AXES = 3;
const unsigned int PINS[NUM_AXES] = {
X_AXIS_PIN, Y_AXIS_PIN, Z_AXIS_PIN
214/517
};
Bounce button;
void setup() {
Serial.begin(BAUD_RATE);
pinMode(BUTTON_PIN, INPUT);
button.attach(BUTTON_PIN);
button.interval(20);
}
long sum = 0;
for (unsigned int i = 0; i < BUFFER_SIZE; i++)
sum += buffer[axis][i];
return round(sum / BUFFER_SIZE);
}
int get_x() { return get_axis(0); }
int get_y() { return get_axis(1); }
int get_z() { return get_axis(2); }
void loop() {
Serial.print(get_x());
Serial.print(" ");
215/517
Serial.print(get_y());
Serial.print(" ");
Serial.print(get_z());
Serial.print(" ");
if (button.update()) {
button_pressed = button.read() == HIGH;
}
Serial.println(button_pressed == HIGH ? "1" : "0");
delay(10);
}
La funzione antirimbalzo del pulsante è implementata dalla
classe Bounce, come è stato illustrato nel Capitolo 3. Le altre
istruzioni del programma dovrebbero risultare abbastanza chiare;
è interessante notare che il baud rate è impostato a 38400 per
garantire un trasferimento sufficientemente veloce dei dati del
game controller.
Compilate e caricate il codice, aprite il terminale seriale e
provate a giocare con il controller. Muovete il sensore, premete il
pulsante e osservate l’output visualizzato dal programma, che
dovrebbe essere simile al seguente:
324 365 396 0
325 364 397 0
325 364 397 1
325 364 397 0
325 365 397 0
325 365 397 1
326 364 397 0
Siete riusciti a costruire un dispositivo che potrete utilizzare per
numerosi progetti, per esempio per controllare robot, labirinti e
così via. Tuttavia, poiché il suo fine primo è il controllo dei giochi,
216/517
Esercizi
• Per comprendere meglio i dati generati dall’accelero-
metro, dovreste scrivere qualche programma che si
concentri su un asse specifico. Per esempio, realizzate
uno sketch che generi in output solo il valore corrente
dell’asse X; accendete un led quando il valore dell’asse
X è superiore a una soglia prestabilita, altrimenti
spegnetelo.
Capitolo 7
BrowserGame/GameController/js/game_controller.js
var GameController = function(path, threshold) {
this.arduino = new SerialDevice(path);
this.threshold = threshold || 325;
this.moveLeft = false;
this.moveRight = false;
221/517
this.buttonPressed = false;
this.boundOnReadLine = this.onReadLine.bind(this);
this.arduino.onReadLine.addListener(this.boundOnReadLine)
this.arduino.connect();
}
Questa funzione definisce diverse proprietà. In primo luogo
crea una proprietà arduino e la inizializza con un nuovo oggetto
SerialDevice. La proprietà successiva, invece, definisce una
soglia per l’asse x del game controller. Per verificare se un utente
ha inclinato il game controller a sinistra o a destra, dobbiamo
conoscere il ”punto di riposo” del controller. Invece di cercare il
punto di riposo esatto, aggiungeremo una certa tolleranza mem-
orizzandone il valore in threshold.
Le tre proprietà seguenti sono tutti flag booleani che rapp-
resentano lo stato attuale del controller: se moveLeft è true, sig-
nifica che l’utente ha spostato il controller a sinistra. Alla fine,
aggiungiamo il listener onReadLine all’oggetto SerialDevice e
utilizziamo il solito trucco basato su bind.
Il listener onReadLine interpreta i dati ottenuti da Arduino.
BrowserGame/GameController/js/game_controller.js
GameController.prototype.onReadLine = function(line) {
const TOLERANCE = 5;
var attr = line.trim().split(' ');
if (attr.length == 4) {
this.moveRight = false;
this.moveLeft = false;
var x = parseInt(attr[0]);
if (x <= this.threshold - TOLERANCE) {
this.moveLeft = true;
222/517
BrowserGame/Arduinoid/main.html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8"/>
<link rel="stylesheet" type="text/css" href="css/
arduinoid.css"/>
<title>Arduinoid</title>
</head>
<body>
<div id="game">
<div id="playfield">
<div id="paddle"></div>
<div id="ball"></div>
<div id="winner" class="message">
<p>You win!</p>
</div>
<div id="game_over" class="message">
<p>Game Over</p>
</div>
</div>
<div id="stats">
<div>Lives: <span id="lives"/></div>
225/517
BrowserGame/Arduinoid/js/arduinoid.js
const MAX_LIVES = 5;
var GameStates = {
RUNNING: 'running',
PAUSED: 'paused',
LOST: 'lost',
WON: 'won'
}
var Game = {
lives: MAX_LIVES,
score: 0,
state: GameStates.PAUSED,
227/517
paddle: {
speed: 15,
width: $("#paddle").width(),
height: $("#paddle").height()
},
playfield: {
width: $("#playfield").width(),
height: $("#playfield").height(),
rows: 4,
columns: 10
},
ball: {
diameter: $("#ball").width(),
vx: 5 + Math.random() * 5,
vy: -10
},
BrowserGame/Arduinoid/js/arduinoid.js
Riga 1 function initGame() {
- Game.state = GameStates.PAUSED;
- Game.lives = MAX_LIVES;
- Game.score = 0;
5 resetMovingObjects();
- updateStatistics();
- drawPlayfield();
- }
-
10 function resetMovingObjects() {
- $("#paddle").css("left",
(Game.playfield.width - Game.paddle.width) / 2);
- $("#ball").css("left",
(Game.playfield.width - Game.ball.diameter) / 2);
- $("#ball").css("top",
parseInt($("#paddle").css("top")) -
Game.paddle.height);
- }
15
- function updateStatistics() {
- $('#lives').text(Game.lives);
- $('#score').text(Game.score);
- }
20
- function drawPlayfield() {
- var colors = ['blue', 'green', 'red',
'yellow'];
- var $playfield = $('#playfield');
230/517
- $playfield.children('.row').remove();
25
- for (var row = 0; row <
Game.playfield.rows; row++) {
- var $row = $('<div class="row"></div>');
- $row.appendTo($playfield);
- for (var col = 0; col <
Game.playfield.columns; col++) {
30 var $block = $("<div
class='block'></div>");
- $block.css("background", 'url("img/' +
colors[row] + '.png")');
- $block.appendTo($row);
- }
- }
35 }
initGame inizializza il gioco, impostando alcune proprietà
dell’oggetto Game direttamente ai loro valori predefiniti. Chiama
quindi diverse funzioni per inizializzare gli oggetti specifici del
gioco. resetMovingObjects imposta le posizioni della pallina e
della barra ai loro valori di default. La barra appare al centro della
parte inferiore del campo la pallina si trova sopra la barra.
updateStatistics copia il numero di vite e il punteggio cor-
rente nella pagina HTML. Viene utilizzato il metodo text di
jQuery per impostare gli elementi di testo specificati dagli ID
lives e score. I valori predefiniti possono naturalmente essere
copiati anche nella funzione initGame, ma questa funzione sarà
chiamata in seguito, nel corso del gioco.
La funzione drawPlayfield disegna i mattoncini che il gioc-
atore deve colpire con la pallina. In pratica crea quattro elementi
<div> con l’attributo class impostato su row; all’interno di ogni
231/517
elemento row crea quindi dieci elementi <div> della classe block.
Per ottenere questo risultato, alla riga 24 vengono per prima cosa
rimossi tutti gli elementi row che potrebbero già esistere. Ancora
una volta sfruttiamo jQuery a nostro vantaggio. Il metodo chil-
dren restituisce tutti i figli dell’elemento playfield che hanno la
classe row, poi il metodo remove li elimina tutti dalla pagina
HTML.
Osservate che anche i nomi delle variabili possono contenere il
carattere $ in JavaScript: qui lo usiamo per denominare le variab-
ili come $playfield che fanno riferimento agli oggetti jQuery.
Con due cicli for annidati creiamo quindi i mattoncini. Qui
viene utilizzata di nuovo l’onnipotente funzione $ per creare tutti
gli elementi <div> necessari. Passando una stringa contenente
codice HTML a $, il metodo crea l’elemento richiesto. Alla riga 27
creiamo una nuova fila, che alla riga successiva viene inserita
nella pagina HTML corrente.
Nel ciclo for successivo ripetiamo l’operazione per i matton-
cini. Oltre a creare gli elementi <div>, impostiamo la loro propri-
età background su un’immagine che cambia in base alla fila di
mattoncini. Le immagini sono sfumate per rendere più colorati i
mattoncini.
Ora che il gioco è stato inizializzato, possiamo implementare il
ciclo di gioco che viene chiamato per ogni fotogramma.
BrowserGame/Arduinoid/js/arduinoid.js
function gameLoop() {
switch (Game.state) {
case GameStates.PAUSED:
232/517
if (Game.controller.buttonPressed) {
Game.state = GameStates.RUNNING;
}
break;
case GameStates.RUNNING:
movePaddle();
moveBall();
checkCollisions();
updateStatistics();
break;
case GameStates.WON:
handleMessageState("winner");
break;
case GameStates.LOST:
handleMessageState("game_over");
break;
default:
break;
}
}
function handleMessageState(message) {
$("#" + message).show();
if (Game.controller.buttonPressed) {
$("#" + message).hide();
initGame();
}
}
La funzione gameLoop è sorprendentemente semplice, perché
verifica solamente lo stato attuale del gioco e quindi delega i
compiti di conseguenza. Se il gioco è attualmente in pausa, con-
trolla se il giocatore ha premuto il pulsante del game controller, e
in questo caso cambia lo stato del gioco in GameStates.RUNNING.
233/517
BrowserGame/Arduinoid/js/arduinoid.js
Riga 1 function moveBall() {
- var ball_pos = $("#ball").position();
- var ball_x = ball_pos.left;
- var ball_y = ball_pos.top;
5 var next_x_pos = ball_x + Game.ball.vx;
- var next_y_pos = ball_y + Game.ball.vy;
-
- if (next_x_pos <= 0) {
- Game.ball.vx *= -1;
10 next_x_pos = 1;
- } else if (next_x_pos >=
Game.playfield.width - Game.ball.diameter) {
- Game.ball.vx *= -1;
- next_x_pos = Game.playfield.width -
Game.ball.diameter - 1;
- }
234/517
15
- var paddle_y = $("#paddle").position().top;
- if (next_y_pos <= 0) {
- Game.ball.vy *= -1;
- next_y_pos = 1;
20 } else if (next_y_pos + Game.ball.diameter
>= paddle_y) {
- var paddle_x =
$("#paddle").position().left;
- if (next_x_pos >= paddle_x &&
- next_x_pos <= paddle_x +
Game.paddle.width)
- {
25 Game.ball.vy *= -1;
- next_y_pos = paddle_y -
Game.ball.diameter;
- }
- }
-
30 $("#ball").css({ "left" : next_x_pos, "top"
: next_y_pos });
- }
-
- function movePaddle() {
- if (Game.controller.moveLeft) {
35 var paddle_x =
$("#paddle").position().left;
- if (paddle_x - Game.paddle.speed >= 0) {
- $("#paddle").css("left", paddle_x -
Game.paddle.speed);
- } else {
- $("#paddle").css("left", 0);
40 }
- }
-
- if (Game.controller.moveRight) {
235/517
- var paddle_x =
$("#paddle").position().left;
45 var next_pos = paddle_x +
Game.paddle.width + Game.paddle.speed;
- if (next_pos < Game.playfield.width) {
- $("#paddle").css("left", paddle_x +
Game.paddle.speed);
- }
- }
50 }
Il metodo più utile di jQuery per spostare gli oggetti è posi-
tion, che restituisce un oggetto contenente gli attributi left e
top correnti dell’elemento HTML. In CSS, questi attributi spe-
cificano le coordinate x e y dell’oggetto sullo schermo. Alle righe
da 2 a 4 della funzione moveBall viene utilizzata la funzione pos-
ition per determinare le coordinate su schermo attuali della pal-
lina. Nelle due righe successive si calcola invece la nuova posiz-
ione della pallina aggiungendo le velocità correnti per entrambe le
direzioni.
A seguire, si verifica se la nuova posizione della pallina
potrebbe essere esterna allo schermo, e in tal caso si limitano le
coordinate ai confini dello schermo. Alle righe da 8 a 14 ci
assicuriamo che la coordinata x della pallina sia maggiore di zero
e minore della larghezza del campo. Se la pallina colpisce il con-
fine destro o sinistro del campo, moltiplichiamo vx per -1 al fine
di cambiarne la direzione.
Lo stesso accade alle righe da 16 a 28 per la coordinata y della
pallina: quando la pallina colpisce il confine superiore del campo,
moltiplichiamo vy per -1. Il campo di gioco non ha un confine
236/517
BrowserGame/Arduinoid/js/arduinoid.js
Riga 1 function checkCollisions() {
- if (ballDropped()) {
- Game.lives = Game.lives - 1;
237/517
- if (Game.lives == 0) {
5 Game.state = GameStates.LOST;
- } else {
- Game.state = GameStates.PAUSED;
- resetMovingObjects();
- }
10 }
- if (!checkBlockCollision()) {
- Game.state = GameStates.WON;
- }
- }
15
- function ballDropped() {
- var ball_y = $("#ball").position().top;
- var paddle_y = $("#paddle").position().top;
- return ball_y + Game.ball.diameter >
paddle_y + Game.paddle.height;
20 }
-
- function inXRange(ball_left, block_left,
block_width) {
- return (ball_left + Game.ball.diameter >=
block_left) &&
- (ball_left <= block_left +
block_width);
25 }
-
- function inYRange(ball_top, block_top,
block_height) {
- return (ball_top + Game.ball.diameter >=
block_top) &&
- (ball_top <= block_top +
block_height);
30 }
-
- function checkBlockCollision() {
238/517
- var block_width =
$(".block").first().width();
- var block_height =
$(".block").first().height();
35 var ball_left = $("#ball").position().left;
- var ball_top = $("#ball").position().top;
- var blocks_left = false;
- $(".block").each(function() {
- if ($(this).css("visibility") ==
"visible") {
40 blocks_left = true;
- var block_top = $(this).position().top;
- var block_left =
$(this).position().left;
- var in_x = inXRange(ball_left,
block_left, block_width);
- var in_y = inYRange(ball_top,
block_top, block_height);
45 if (in_x && in_y) {
- Game.score += 10;
- $(this).css("visibility", "hidden");
- if (in_x) {
- Game.ball.vy *= -1;
50 }
- if (in_y) {
- Game.ball.vx *= -1;
- }
- }
55 }
- });
- return blocks_left;
- }
La funzione checkCollisions verifica innanzitutto se il gioc-
atore ha lasciato cadere la pallina, caso in cui viene diminuito il
numero di vite. Occorre quindi controllare se il giocatore ha
239/517
BrowserGame/Arduinoid/js/arduinoid.js
$(function() {
initGame();
setInterval(gameLoop, 30);
});
Ricorriamo all’ennesima variante della funzione $ di jQuery,
passandole una funzione anonima che viene chiamata non appena
la pagina HTML è stata caricata interamente. In questa funzione
inizializziamo il gioco e ci assicuriamo che la funzione gameLoop
sia chiamata ogni 30 millisecondi.
Il gioco è completo: fate qualche partita, ve la meritate!
Esercizi
• Create un mouse per computer utilizzando l’accelero-
metro ADXL335. Dovrebbe funzionare senza appoggio
e dovrebbe emettere l’accelerazione attuale intorno
agli assi x e y; dovrebbe inoltre disporre di un pulsante
destro e di un pulsante sinistro. Scrivete un’app di
Chrome (o magari del codice nel linguaggio di pro-
grammazione che preferite) per controllare il punt-
atore del mouse sullo schermo.
Generazione di segnali
video con Arduino
Cosa serve
1. Un cavo RCA.
2. Un resistore da 470Ω.
3. Un resistore da 1kΩ.
4. Alcuni cavi.
5. Un sensore di temperatura TMP36 di Analog Devices.
6. Una scheda Arduino, per esempio Uno, Duemilanove
o Diecimila.
7. Un cavo USB per collegare la scheda Arduino al
computer.
Creazione di un convertitore
digitale-analogico (DAC)
La scheda Arduino non supporta nativamente i segnali di out-
put analogica, quindi dobbiamo trovare un modo per aggirare
questo limite. La soluzione è ricorrere a un convertitore digitale-
analogico (DAC, http://it.wikipedia.org/wiki/Conver-
titore_digitale-analogico). Come suggerito dal nome, tale
circuito trasforma un input digitale in un’uscita analogica. Potete
trovare i DAC in numerosi dispositivi consumer, per esempio nel
vostro lettore MP3, che trasforma la musica digitale in un’onda
sonora analogica.
248/517
0V 0V 0,0 V SYNC
0V 5V 0,3 V Nero
5V 0V 0,65 V Grigio
5V 5V 0,95 V Bianco
Video/TvThermometer/TvThermometer.ino
#include <TVout.h>
#include <fontALL.h>
#include "thermometer.h"
Video/TvThermometer/TvThermometer.ino
void setup() {
TV.begin(PAL, SCREEN_WIDTH, SCREEN_HEIGHT);
TV.bitmap(0, 1, thermometer);
TV.select_font(font4x6);
TV.set_cursor(20, 4);
TV.print("40");
TV.set_cursor(20, 24);
TV.print("30");
TV.set_cursor(20, 44);
TV.print("20");
TV.set_cursor(20, 64);
TV.print("10");
}
Inizialmente viene chiamato il metodo begin dell’oggetto TV.
Lo standard della TV analogica viene impostato su PAL; inoltre,
vengono configurate larghezza e altezza dello schermo. Se il
vostro televisore preferisce NTSC, dovete semplicemente
sostituire il primo argomento con NTSC. A seguire, chiamiamo il
metodo bitmap per disegnare l’immagine del termometro sullo
schermo. La sua posizione X è 0, mentre la sua posizione Y è 1.
264/517
Video/TvThermometer/TvThermometer.ino
Riga 1 void loop() {
- unsigned long current_millis = millis();
- if (abs(current_millis - last_measurement)
>= 1000) {
- current_temperature = get_temperature();
5 last_measurement = current_millis;
- int y_pos = mapfloat(
- current_temperature, MIN_TEMP, MAX_TEMP,
SCALE_Y_MAX, SCALE_Y_MIN);
- TV.draw_rect(
- SCALE_X_MIN, SCALE_Y_MIN, SCALE_WIDTH,
SCALE_HEIGHT, BLACK, BLACK);
10 TV.draw_rect(
- SCALE_X_MIN, y_pos, SCALE_WIDTH,
SCALE_Y_MAX - y_pos, WHITE, WHITE);
- TV.select_font(font6x8);
- TV.set_cursor(53, 1);
265/517
- TV.print("Current");
15 TV.set_cursor(40, 11);
- TV.print("Temperature:");
- TV.select_font(font8x8);
- TV.set_cursor(50, 25);
- TV.print(current_temperature, 1);
20 TV.print(" C");
- TV.draw_circle(88, 27, 1, WHITE);
- }
- }
-
25 const float mapfloat(
- float x, float in_min, float in_max, float
out_min, float out_max)
- {
- return (x - in_min) * (out_max - out_min) /
(in_max - in_min) + out_min;
- }
30
- const float get_temperature() {
- const int sensor_voltage =
analogRead(TEMP_SENSOR_PIN);
- const float voltage = sensor_voltage *
SUPPLY_VOLTAGE / 1024;
- return (voltage * 1000 - 500) / 10;
35 }
Nel codice ci assicuriamo di misurare la temperatura attuale
solo una volta al secondo. Ogni volta che si determina la temper-
atura attuale, alla riga 6 viene calcolata la nuova posizione Y
superiore della scala del termometro. La funzione map_float
associa la temperatura corrente a un valore sulla scala del
termometro.
266/517
Video/TvThermometer/thermometer.h
#ifndef THERMOMETER_H
#define THERMOMETER_H
extern const unsigned char thermometer[];
#endif
Il contenuto è piuttosto deludente: il file, infatti, dichiara unica-
mente una variabile chiamata thermometer. Questa variabile è un
array di valori di carattere senza segno, mentre la parola chiave
extern comunica al compilatore che vogliamo solo dichiarare la
variabile: in pratica, è necessaria la possibilità di farvi riferimento
nel programma, ma occorre ancora definirla per allocarle la
memoria.
La variabile thermometer viene in realtà definita in thermo-
meter.cpp (alcune righe sono state eliminate per ragioni di
concisione).
Video/TvThermometer/thermometer.cpp
Riga 1 #include <Arduino.h>
- #include <avr/pgmspace.h>
- #include "thermometer.h"
- PROGMEM const unsigned char thermometer[] = {
5 20, 94,
- B00000000, B11110000, B00000000,
- B00000001, B00001000, B00000000,
- B00000010, B00000100, B00000000,
- B00000010, B00000100, B00000000,
10 B00000010, B00000100, B00000000,
- B00000010, B00000111, B10000000, // 40.0
- B00000010, B00000100, B00000000,
- B00000010, B00000100, B00000000,
- B00000010, B00000100, B00000000,
268/517
Video/img2cpp.rb
Riga 1 require 'RMagick'
- include Magick
-
- image = Image::read(ARGV[0]).first
5
- puts '#include "thermometer.h"'
- puts 'PROGMEM const unsigned char
thermometer[] = {'
- puts " #{image.columns}, #{image.rows},"
-
10 (0..image.rows).each do |y|
273/517
anche tutti i giunti saldati sul cavo RCA modificato, e nel dubbio
rinforzate le saldature.
Esercizi
• Usate la libreria TVout per visualizzare qualche altro
dato del sensore. Potete provare a realizzare i sensori
di parcheggio del Capitolo 5 utilizzando la libreria
TVout.
• Modificate il termometro sul televisore in modo che
passi tra gradi Celsius e gradi Fahrenheit ogni pochi
secondi. Non cambiate solo il testo, ma anche la
grafica.
Capitolo 9
Giocherellare con il
controller Wii Nunchuk
Cosa serve
• Una scheda Arduino, per esempio un modello Arduino
Uno, Duemilanove o Diecimila.
278/517
La classe Nunchuk
Di seguito è riportata l’interfaccia della classe Nunchuk e la
parte principale della sua implementazione.
284/517
Tinkering/NunchukDemo/nunchuk.h
Riga 1 #ifndef __NUNCHUK_H__
- #define __NUNCHUK_H__
- #define NUNCHUK_BUFFER_SIZE 6
-
5 class Nunchuk {
- public:
- void initialize();
- bool update();
-
10 int joystick_x() const { return _buffer[0]; }
- int joystick_y() const { return _buffer[1]; }
-
- int x_acceleration() const {
- return ((int)(_buffer[2]) << 2) |
((_buffer[5] >> 2) & 0x03);
15 }
-
- int y_acceleration() const {
- return ((int)(_buffer[3]) << 2) |
((_buffer[5] >> 4) & 0x03);
- }
20
- int z_acceleration() const {
- return ((int)(_buffer[4]) << 2) |
((_buffer[5] >> 6) & 0x03);
- }
- bool z_button() const { return !(_buffer[5]
& 0x01); }
25 bool c_button() const { return !(_buffer[5]
& 0x02); }
-
- private:
- void request_data();
- char decode_byte(const char);
285/517
30
- unsigned char _buffer[NUNCHUK_BUFFER_SIZE];
- };
-
- #endif
Questa piccola classe C++ contiene tutto quello che serve per
utilizzare un controller Nunchuk in un progetto Arduino. Le
prime istruzioni della classe definiscono un meccanismo che evita
la doppia inclusione: tramite #ifndef si controlla l’esistenza di
una macro preprocessor chiamata __NUNCHUK_H__. Si imposta la
macro, se questa operazione non è ancora stata eseguita, e si
prosegue con la dichiarazione della classe Nunchuk; in caso con-
trario la funzione preprocessor salta la dichiarazione. In questo
modo potete includere il file header più volte nella stessa
applicazione.
La riga 3 genera una costante che riguarda la dimensione
dell’array necessario per memorizzare i dati restituiti da Nun-
chuk. L’array è definito alla riga 31 e in questo progetto la cost-
ante è impostata dalla funzione preprocessor e non tramite la
parola chiave const, dato che C++ prevede che le costanti degli
array siano note in fase di compilazione.
A questo punto ha inizio la dichiarazione della classe Nunchuk.
Per avviare il canale di comunicazione tra Arduino e Nunchuk si
deve chiamare una volta il metodo initialize, poi si chiama
update ogni volta che Nunchuk deve inviare dati. Questi metodi
saranno implementati nei prossimi paragrafi.
Il programma include metodi pubblici che impostano tutti gli
attributi restituiti da Nunchuk: posizione x e y del joystick
286/517
Tinkering/NunchukDemo/nunchuk.cpp
Riga 1 #include <Arduino.h>
- #include <Wire.h>
- #include "nunchuk.h"
- #define NUNCHUK_DEVICE_ID 0x52
5
- void Nunchuk::initialize() {
- Wire.begin();
- Wire.beginTransmission(NUNCHUK_DEVICE_ID);
- Wire.write((byte)0x40);
10 Wire.write((byte)0x00);
- Wire.endTransmission();
- update();
- }
-
15 bool Nunchuk::update() {
- delay(1);
- Wire.requestFrom(NUNCHUK_DEVICE_ID,
NUNCHUK_BUFFER_SIZE);
- int byte_counter = 0;
- while (Wire.available() && byte_counter <
NUNCHUK_BUFFER_SIZE)
20 _buffer[byte_counter++] =
287/517
decode_byte(Wire.read());
- request_data();
- return byte_counter == NUNCHUK_BUFFER_SIZE;
- }
-
25 void Nunchuk::request_data() {
- Wire.beginTransmission(NUNCHUK_DEVICE_ID);
- Wire.write((byte)0x00);
- Wire.endTransmission();
- }
30
- char Nunchuk::decode_byte(const char b) {
- return (b ^ 0x17) + 0x17;
- }
Dopo aver incluso tutte le librerie necessarie, il programma
definisce la costante NUNCHUK_DEVICE_ID. Il protocollo I2C è di
tipo master/slave; in questo progetto Arduino è l’elemento master
e Nunchuk è lo slave. Il controller Nunchuk registra se stesso sul
bus dati utilizzando un ID particolare (0x52), in modo da potervi
fare riferimento quando necessario.
Il metodo initialize imposta la connessione tra Arduino e
Nunchuk inviando un segnale di handshake. Alla riga 7 si chiama
il metodo begin della classe Wire, in modo che Arduino si unisca
al protocollo I2C come master; se passate a begin un ID il dispos-
itivo corrispondente si unisce alle trasmissioni I2C come elemento
slave. A questo punto ha inizio una nuova trasmissione verso il
dispositivo identificato da NUNCHUCK_DEVICE_ID, nel nostro caso
il Nunchuk.
La trasmissione invia due byte (0x40 e 0x00) a Nunchuk,
dopodiché si conclude. Questa comunicazione determina l’intera
procedura di handshake, poi il programma chiede a Nunchuk lo
288/517
Tinkering/NunchukDemo/NunchukDemo.ino
#include <Wire.h>
#include "nunchuk.h"
void setup() {
Serial.begin(BAUD_RATE);
nunchuk.initialize();
}
void loop() {
if (nunchuk.update()) {
Serial.print(nunchuk.joystick_x());
290/517
Serial.print(" ");
Serial.print(nunchuk.joystick_y());
Serial.print(" ");
Serial.print(nunchuk.x_acceleration());
Serial.print(" ");
Serial.print(nunchuk.y_acceleration());
Serial.print(" ");
Serial.print(nunchuk.z_acceleration());
Serial.print(" ");
Serial.print(nunchuk.z_button());
Serial.print(" ");
Serial.println(nunchuk.c_button());
}
}
Queste istruzioni non presentano novità di sorta: si definisce un
oggetto globale Nunchuk e lo si inizializza nella funzione setup. In
loop si chiama update per richiedere lo stato corrente del con-
troller e trasmettere in output tutti gli attributi tramite la porta
seriale.
Compilate e caricate il programma, poi aprite il serial monitor e
provate a giocherellare con Nunchuk. Muovete il joystick, spostate
il controller e premete i pulsanti. Dovreste osservare dati simili ai
seguenti:
46 109 428 394 651 1 1
49 132 414 380 656 1 0
46 161 415 390 651 1 0
46 184 429 377 648 1 0
53 199 404 337 654 1 0
53 201 406 359 643 1 0
A questo punto avete collegato correttamente un controller
Nunchuk alla scheda Arduino. Non è ancora un progetto
291/517
Creazione di un videogioco
TVout e la libreria Nunchuk sono tutto ciò che serve per
scrivere videogiochi divertenti per la console. In questo paragrafo
realizzeremo Pragduino, un gioco semplice che illustra la maggior
parte delle competenze che dovete avere per scrivere giochi più
complessi.
294/517
Tinkering/Pragduino/Pragduino.ino
#include <Wire.h>
#include <TVout.h>
#include <fontALL.h>
#include "nunchuk.h"
Tinkering/Pragduino/Pragduino.ino
Riga 1 TVout tv;
- Nunchuk nunchuk;
-
- boolean up, down, left, right, c_button,
z_button;
5 int chx, chy;
- int chvx, chvy;
- int target_x, target_y, target_r;
- unsigned int target_count;
- unsigned int hits;
10 unsigned long target_creation;
-
- enum GameState {
- INTRO, STARTING, RUNNING, DONE
- };
15
- GameState state;
Nella programmazione di giochi spesso occorre gestire numer-
ose variabili globali, anche in un gioco semplice come questo. Per
prima cosa vengono definite un’istanza di TVout denominata tv e
un’istanza di Nunchuk chiamata nunchuk. A seguire, vengono def-
inite le variabili booleane per tutte le proprietà di Nunchuk a cui
siamo interessati. Tutte funzionano allo stesso modo: per esem-
pio, up viene impostata su true se l’utente spinge lo stick analo-
gico di Nunchuk verso l’alto.
297/517
Tinkering/Pragduino/Pragduino.ino
void init_game() {
up = down = left = right = c_button = z_button =
false;
chx = WIDTH / 2;
chy = HEIGHT / 2;
chvx = 1;
chvy = 1;
state = INTRO;
target_count = 0;
hits = 0;
create_target();
}
void create_target() {
target_r = random(7, 11);
target_x = random(target_r, WIDTH - target_r);
target_y = random(target_r, HEIGHT - target_r);
target_count++;
target_creation = millis();
}
La funzione init_game imposta la maggior parte delle variabili
globali su valori costanti. create_target è un po’ più interess-
ante: crea infatti un nuovo bersaglio di dimensione casuale in una
posizione casuale. Lo useremo più avanti nel ciclo di gioco per
creare un nuovo bersaglio. La funzione si assicura che il bersaglio
rientri nei limiti dello schermo e usa la funzione millis per
determinare l’ora di creazione del bersaglio.
299/517
Tinkering/Pragduino/Pragduino.ino
Riga 1 void setup() {
- randomSeed(analogRead(A0));
- tv.begin(PAL, WIDTH, HEIGHT);
- nunchuk.initialize();
5 init_game();
- }
-
- void loop() {
- check_controls();
10 switch (state) {
- case INTRO: intro(); break;
- case STARTING: start_game(); break;
- case RUNNING: update_game(); break;
- case DONE: game_over(); break;
15 }
- tv.delay_frame(1);
- }
-
- void check_controls() {
20 up = down = left = right = c_button =
z_button = false;
- if (nunchuk.update())
- {
- if (nunchuk.joystick_x() < 70)
- left = true;
25 if (nunchuk.joystick_x() > 150)
- right = true;
- if (nunchuk.joystick_y() > 150)
- up = true;
- if (nunchuk.joystick_y() < 70)
300/517
30 down = true;
- c_button = nunchuk.c_button();
- z_button = nunchuk.z_button();
- }
- }
setup inizializza il generatore di numeri casuali utilizzando un
disturbo dal pin analogico A0, a seguire inizializza lo schermo e
Nunchuk, infine chiama init_game per impostare tutte le variab-
ili globali su valori plausibili.
La funzione loop chiama check_controls per leggere lo stato
attuale di Nunchuk, poi verifica lo stato corrente del gioco in
un’istruzione switch e delega il suo lavoro alla funzione
responsabile della gestione dello stato attuale.
Alla riga 16 loop chiama una funzione della libreria TVout che
non abbiamo ancora esaminato. delay_frame attende l’inizio del
successivo intervallo di soppressione verticale, vale a dire il
momento in cui il fascio di elettroni del televisore si sposta dal
fondo dello schermo alla parte superiore. L’obiettivo è attendere
l’inizio del fotogramma successivo, pertanto passiamo 1 a
delay_frame. Questa operazione è necessaria per prevenire lo
sfarfallio, in quanto garantisce che la creazione della grafica del
gioco in memoria resti sincronizzata con l’output effettivo sullo
schermo.
Tinkering/Pragduino/Pragduino.ino
Riga 1 void intro() {
- tv.select_font(font8x8);
- tv.printPGM(28, 20, PSTR("Pragduino"));
- tv.select_font(font6x8);
5 tv.printPGM(16, 40, PSTR("A Pragmatic
Game"));
- tv.select_font(font4x6);
- tv.printPGM(18, 74, PSTR("Press Z-Button to
Start"));
- if (z_button) {
- state = STARTING;
10 z_button = false;
- delay(200);
- }
- }
-
15 void start_game() {
- tv.clear_screen();
- tv.select_font(font8x8);
- tv.printPGM(40, 44, PSTR("READY?"));
- if (z_button) {
20 init_game();
- state = RUNNING;
- }
- }
-
25 void game_over() {
- tv.clear_screen();
- tv.select_font(font8x8);
- tv.printPGM(28, 38, PSTR("Game Over"));
- int x = (WIDTH - 7 * 8) / 2;
30 if (hits > 9)
- x = (WIDTH - 8 * 8) / 2;
- tv.printPGM(x, 50, PSTR("Hits: "));
302/517
Tinkering/Pragduino/Pragduino.ino
void move_crosshairs() {
if (left) chx -= chvx;
if (right) chx += chvx;
if (up) chy -= chvy;
if (down) chy += chvy;
chx = CH_LEN + 1;
if (chx >= WIDTH - CH_LEN)
chx = WIDTH - CH_LEN - 1;
if (chy <= CH_LEN)
chy = CH_LEN + 1;
if (chy >= HEIGHT - CH_LEN)
chy = HEIGHT - CH_LEN - 1;
}
void draw_crosshairs() {
tv.draw_row(chy, chx - CH_LEN, chx - 1, WHITE);
tv.draw_row(chy, chx + 1, chx + CH_LEN, WHITE);
tv.draw_column(chx, chy - CH_LEN, chy - 1, WHITE);
tv.draw_column(chx, chy + 1, chy + CH_LEN, WHITE);
}
move_crosshairs controlla tutte le variabili globali relative allo
stato attuale di Nunchuk. Aggiorna la posizione del mirino in base
ai valori delle variabili e si assicura che il mirino resti entro i con-
fini dello schermo.
La funzione draw_crosshairs disegna il mirino sullo schermo.
Invece di ricorrere a una bitmap, impieghiamo due nuovi metodi
di TVout: draw_row genera una linea orizzontale, che utilizzeremo
per le due linee orizzontali del mirino; draw_column, allo stesso
modo, consente di disegnare le due linee verticali. Il pixel nel
punto di incrocio rimarrà vuoto per rendere il mirino più
realistico.
Forse vi state chiedendo perché non abbiamo usato una bit-
map: il problema di queste immagini è che non assumono un
aspetto gradevole quando si sovrappongono. Anche se la bitmap
ha l’aspetto di un mirino, è pur sempre un quadrato: se spostiamo
305/517
Tinkering/Pragduino/Pragduino.ino
bool target_hit() {
if (z_button)
return (target_x - chx) * (target_x - chx) +
(target_y - chy) * (target_y - chy) <
target_r * target_r;
return false;
}
void check_target() {
if (target_hit()) {
hits++;
create_target();
}
Esercizi
• Riscrivete il gioco implementato nel Capitolo 7 in
modo da supportare il controller Nunchuk. Il gioco
deve supportare tanto il joystick analogico quanto
l’accelerometro. Riuscite a commutare tra joystick e
accelerometro utilizzando i pulsanti del controller
Nunchuk?
• Elaborare la Nintendo Wii Motion è un po’ più com-
plicato
(http://randomhacksofboredom.blogspot.com/
2009/07/motion-plus-and-nunchuckto-
getheron.html) ma può essere una strada intrigante
per migliorare significativamente le vostre abilità di
progetto.
• La libreria TVout dispone di un supporto audio basil-
are. Collegate un buzzer piezo al pin digitale 11 e
utilizzate le funzioni tone e noTone per aggiungere
effetti audio alla console per videogiochi. Aggiun-
getene qualcuno anche a Pragduino.
Capitolo 10
Cosa serve
1. Una scheda Ethernet shield per Arduino.
2. Un sensore di temperatura TMP36.
3. Cavi di collegamento su breadboard.
4. Una breadboard.
5. Una scheda Arduino, per esempio un modello Arduino
Uno, Duemilanove o Diecimila.
6. Un cavo USB per collegare la scheda Arduino al
computer.
310/517
Ethernet/TwitterTemperature/TwitterTemperature.ino
Riga 1 #define CELSIUS
- const unsigned int TEMP_SENSOR_PIN = 0;
- const unsigned int BAUD_RATE = 9600;
- const float SUPPLY_VOLTAGE = 5.0;
5 void setup() {
- Serial.begin(BAUD_RATE);
- }
-
- void loop() {
10 const int sensor_voltage =
analogRead(TEMP_SENSOR_PIN);
- const float voltage = sensor_voltage *
SUPPLY_VOLTAGE / 1024;
- const float celsius = (voltage * 1000 - 500)
/ 10;
- #ifdef CELSIUS
- Serial.print(celsius);
15 Serial.println(" C");
- #else
- Serial.print(9.0 / 5.0 * celsius + 32.0);
- Serial.println(" F");
- #endif
20 delay(5000);
- }
Registrazione di
un’applicazione Twitter
Prima di scrivere il programma dovete registrarvi sul sito Twit-
ter per ottenere le chiavi API e le vostre credenziali di accesso
OAuth (http://it.wikipedia.org/wiki/OAuth). Il protocollo
aperto OAuth permette alle applicazioni di utilizzare risorse di
un’altra applicazione. In questo progetto dovete acquisire il priv-
ilegio che permetta alla vostra applicazione di aggiornare i vostri
messaggi di Twitter senza richiedere ogni volta l’immissione di
nome utente e password per l’accesso a questo social network.
Per ottenere tutte le informazioni necessarie, create una nuova
applicazione nella sezione per le applicazioni del sito web di Twit-
ter (https://apps.twitter.com). Effettuate il login e fate clic
sul pulsante Create New App, poi compilate il form che potete
osservare nella Figura 10.3.
Dopo aver creato la nuova applicazione visualizzate la scheda
Permissions e impostate il livello di accesso dell’applicazione su
Read & Write. Passate quindi alla scheda API Keys e fate clic sul
pulsante Create My Access Token. Potrebbe essere necessario
qualche istante: aggiornate la pagina finché non è disponibile il
token di accesso.
316/517
Ethernet/TweetTemperature/TweetTemperature.pde
import processing.serial.*;
final float MAX_WORKING_TEMP = 32.0;
final int LINE_FEED = 10;
final int BAUD_RATE = 9600;
final int FONT_SIZE = 32;
final int WIDTH = 320;
final int HEIGHT = 240;
final String API_KEY = "<YOUR API KEY>";
final String API_SECRET = "<YOUR API SECRET>";
319/517
Serial _arduinoPort;
float _temperature;
boolean _isCelsius;
PFont _font;
void setup() {
size(WIDTH, HEIGHT);
_font = createFont("Arial", FONT_SIZE, true);
println(Serial.list());
_arduinoPort = new Serial(this, Serial.list()[0],
BAUD_RATE);
_arduinoPort.clear();
_arduinoPort.bufferUntil(LINE_FEED);
_arduinoPort.readStringUntil(LINE_FEED);
}
void draw() {
background(255);
fill(0);
textFont(_font, FONT_SIZE);
textAlign(CENTER, CENTER);
if (_isCelsius)
text(_temperature + " \u2103", WIDTH / 2, HEIGHT
/ 2);
else
text(_temperature + " \u2109", WIDTH / 2, HEIGHT /
2);
}
Anche in questo programma si importano le librerie della
comunicazione seriale con Arduino e si definiscono alcune cost-
anti che saranno impiegate più avanti, la maggior parte delle quali
320/517
Ethernet/TweetTemperature/TweetTemperature.pde
void serialEvent(Serial port) {
final String arduinoData =
port.readStringUntil(LINE_FEED);
if (arduinoData != null) {
final String[] data = split(trim(arduinoData), '
');
if (data.length == 2 &&
(data[1].equals("C") || data[1].equals("F")))
{
_isCelsius = data[1].equals("C");
_temperature = float(data[0]);
322/517
if (Float.isNaN(_temperature))
return;
println(_temperature);
int sleepTime = 5 * 60 * 1000;
if (_temperature > MAX_WORKING_TEMP) {
tweetAlarm();
sleepTime = 120 * 60 * 1000;
}
try {
Thread.sleep(sleepTime);
}
catch(InterruptedException ignoreMe) {}
}
}
}
void tweetAlarm() {
ConfigurationBuilder cb = new
ConfigurationBuilder();
cb.setDebugEnabled(true)
.setOAuthConsumerKey(API_KEY)
.setOAuthConsumerSecret(API_SECRET)
.setOAuthAccessToken(ACCESS_TOKEN)
.setOAuthAccessTokenSecret(ACCESS_TOKEN_SECRET);
TwitterFactory tf = new TwitterFactory(cb.build());
Twitter twitter = tf.getInstance();
try {
Status status = twitter.updateStatus(
"Someone, please, take me to the beach!"
);
println(
"Successfully updated status to '" +
status.getText() + "'."
);
}
catch (TwitterException e) {
e.printStackTrace();
323/517
}
}
Ogni volta che arrivano nuovi dati sulla porta seriale, l’ambi-
ente di runtime Processing chiama il metodo serialEvent, che
tenta di leggere una riga di testo e verifica se questa contiene un
numero decimale seguito da uno spazio vuoto e da un carattere C
o F. Questa forma di validazione dei dati assicura che è stato letto
un data set relativo a una misura di temperatura corretta.
Il dato validato è convertito in un oggetto float e il programma
verifica se il suo valore è maggiore di MAX_WORKING_TEMP (nes-
suno dovrebbe lavorare quando fa così caldo!). In caso affermat-
ivo si chiama il metodo tweetAlarm e si invia un messaggio a
Twitter con una richiesta di aiuto che verrà letta da chi legge i
vostri tweet (follower); a questo punto il programma attende due
ore prima di eseguire una nuova misurazione della temperatura.
Se la temperatura è più bassa si attendono solo cinque minuti
prima di una nuova misurazione.
Il metodo tweetAlarm aggiorna lo stato Twitter in modo molto
semplice. Aderendo alle convenzioni tipiche del linguaggio Java il
programma crea una nuova istanza Twitter tramite Twitter-
Factory, che richiede un oggetto ConfigurationBuilder iniz-
ializzato con le credenziali dell’applicazione Twitter. Infine, si
chiama la funzione updateStatus. Se tutte queste operazioni
vanno a buon fine, il programma visualizza a console un messag-
gio che segnala l’avvenuto successo. Se al contrario si manifesta
un problema, il metodo updateStatus genera un’eccezione e il
324/517
Ethernet/TimeServer/TimeServer.ino
Riga 1 #include <SPI.h>
- #include <Ethernet.h>
- const unsigned int BAUD_RATE = 9600;
- const unsigned int DAYTIME_PORT = 13;
5
- byte mac[] = { 0xDE, 0xAD, 0xBE, 0xEF, 0xFE,
0xED };
- IPAddress my_ip(192, 168, 2, 120);
- IPAddress time_server(192, 43, 244, 18); //
time.nist.gov
- EthernetClient client;
10
- void setup() {
329/517
- Serial.begin(BAUD_RATE);
- Ethernet.begin(mac, my_ip);
- }
15
- void loop() {
- delay(1000);
- Serial.print("Connecting...");
-
20 if (client.connect(time_server,
DAYTIME_PORT) <= 0) {
- Serial.println("connection failed.");
- } else {
- Serial.println("connected.");
- delay(300);
25 while (client.available()) {
- char c = client.read();
- Serial.print(c);
- }
-
30 Serial.println("Disconnecting.");
- client.stop();
- }
- }
Il programma include in primo luogo la libreria Ethernet e
definisce una costante per la alla porta del servizio Daytime; è
necessario includere anche la libreria SPI, dato che la libreria Eth-
ernet dipende da questa. Le istruzioni definiscono poi alcune vari-
abili globali.
mac contiene l’indirizzo MAC della scheda Ethernet shield.
Questo indirizzo è un codice numerico di 48 bit che identifica in
modo univoco un dispositivo di rete
(http://it.wikipedia.org/wiki/Indirizzo_MAC). In genere è
il produttore della scheda che imposta questo codice
330/517
Ethernet/TimeServerDnsDhcp/TimeServerDnsDhcp.ino
Riga 1 #include <SPI.h>
- #include <Ethernet.h>
-
- const unsigned int BAUD_RATE = 9600;
5 const unsigned int DAYTIME_PORT = 13;
-
- byte mac[] = { 0xDE, 0xAD, 0xBE, 0xEF, 0xFE,
0xED };
- char* time_server = "time.nist.gov";
- EthernetClient client;
10
- void setup() {
- Serial.begin(BAUD_RATE);
- if (Ethernet.begin(mac) == 0) {
- for (;;) {
15 Serial.println("Could not obtain an IP
336/517
- Serial.print(".");
50 }
- Serial.println();
- }
Questo programma svolge le stesse operazioni di quello nel
paragrafo precedente, ma non contiene indirizzi IP espliciti. A
parte questa caratteristica, non differisce molto dalla versione ori-
ginale. La prima differenza è alla riga 8, dove la variabile
time_server non viene più dichiarata come oggetto IPAddress
ma come stringa. La stringa contiene il nome del server a cui
connettersi.
Alla riga 13 non si passa più il proprio indirizzo IP al metodo
begin di Ethernet. Questa volta begin prova a ottenere un indi-
rizzo IP univoco utilizzando un server DHCP della rete locale: se
non riesce nel suo compito, viene avviato un ciclo infinito che
visualizza un messaggio d’errore ogni secondo. Diversamente,
viene visualizzato l’indirizzo IP ottenuto utilizzando una piccola
funzione helper chiamata print_ip_address.
Alla fine, alla riga 27, passiamo la stringa time_server al met-
odo connect. In effetti la riga non è stata modificata: è cambiato
solo il tipo della variabile time_server. Se connect ottiene una
stringa e non un oggetto IPAddress, prova a cercare con DNS
l’indirizzo IP appartenente al nome del server salvato nella
stringa.
Eseguite il programma per vedere un output simile al seguente:
We've got the following IP address: 192.168.2.113
Connecting...connected.
338/517
Esercizi
• Consultate il Web per trovare altri progetti che
utilizzano le schede Ethernet e realizzatene almeno
uno; un progetto molto ambizioso, per esempio, prova
a implementare un browser web completo in Arduino
(http://hackaday.io/project/3116-pip-arduino-
web-browser).
• Registrate un account per i servizi Xively, Temboo o
altri servizi IoT. Seguite i relativi tutorial e create
almeno un’applicazione Arduino.
• Provate almeno un’altra tecnologia di rete (Bluetooth,
Wi-Fi o XBee) con la vostra scheda Arduino.
Capitolo 11
Creazione di un sistema
d’allarme con notifiche via
e-mail
Cosa serve
1. Una scheda Ethernet shield per Arduino.
2. Un sensore di movimento PIR (Passive Infrared
Sensor).
3. Cavi di collegamento su breadboard.
4. Una breadboard.
5. Una scheda Arduino, per esempio un modello Arduino
Uno, Duemilanove o Diecimila.
6. Un cavo USB per collegare la scheda Arduino al
computer.
344/517
250-SIZE 52428800
250-8BITMIME
250-PIPELINING
250-AUTH CRAM-MD5 PLAIN LOGIN
250-STARTTLS
250 HELP
AUTH LOGIN
334 VXNlcm5hbWU6
bm90bXl1c2VybmFtZQ==
334 UGFzc3dvcmQ6
bm90bXlwYXNzd29yZA==
235 Authentication succeeded
MAIL FROM:<arduino@example.com>
250 OK
RCPT TO:<info@example.com>
250 Accepted <info@example.com>
DATA
354 Enter message, ending with "." on a line by itself
from:arduino@example.com
to:info@example.com
subject:This is a test
Really, it is a test!
.
250 OK id=1WrAQ9-4gfLuZ-5U
QUIT
221 smtpcorp.com closing connection
Connection closed by foreign host.
Questa sessione di comunicazione è simile a quella svolta in
precedenza con il servizio Daytime, anche se in questo caso le fasi
della trasmissione sembrano molto più complesse. Ricordate che
non potete scrivere i comandi in lettere maiuscole. La connes-
sione qui avviene con la porta 2525, che non è la porta SMTP
348/517
maik> nslookup
> set type=mx
> gmail.com
Server: 192.168.2.1
Address: 192.168.2.1#53
Non-authoritative answer:
gmail.com mail exchanger = 5
gmail-smtp-in.l.google.com.
gmail.com mail exchanger = 10
alt1.gmail-smtp-in.l.google.com.
gmail.com mail exchanger = 20 alt2.gmail-smtp-in.
> exit
Questo comando restituisce un elenco di tutti i server di scam-
bio della posta (MX) che appartengono al dominio gmail.com
(sostituitelo con il dominio del vostro provider di posta elettron-
ica) e che sono disponibili sulla rete. In questo caso appartengono
tutti a Google Mail, quindi non potete utilizzarli per inviare mes-
saggi da Arduino in quanto Google Mail impone una connessione
crittografata.
Se il vostro provider di posta elettronica è meno restrittivo,
potete provare a inviare un messaggio senza autenticazione né
crittografia. Aprite una connessione alla porta SMTP standard 25
e sostituite di conseguenza il nome del server smtp.example.com
e tutti gli indirizzi e-mail nella seguente sessione telnet:
maik> telnet smtp.example.com 25
Trying 93.184.216.119...
Connected to smtp.example.com.
Escape character is '^]'.
220 mx.example.com ESMTP q43si10820020eeh.100
HELO
250 mx.example.com at your service
351/517
Ethernet/Email/email.h
#ifndef __EMAIL__H_
#define __EMAIL__H_
class Email {
String _from, _to, _subject, _body;
public:
Email(
const String& from,
const String& to,
const String& subject,
const String& body
) : _from(from), _to(to), _subject(subject),
_body(body) {}
};
#endif
Questa classe incapsula i quattro attributi fondamentali di un
messaggio di posta, ovvero gli indirizzi di mittente e destinatario,
l’oggetto e il corpo del messaggio. Questi attributi sono mem-
orizzati in oggetti nel formato String.
Alt, fermi… una classe String? Proprio così! L’IDE Arduino
propone una classe String (http://arduino.cc/en/Refer-
ence/StringObject), che non è così ricca di funzioni come le
classi omonime dei linguaggi C++ o Java, ma che rappresenta
comunque una soluzione migliore rispetto all’utilizzo di più punt-
atori char. L’utilizzo di questa classe verrà spiegato nei prossimi
paragrafi.
Le altre istruzioni della classe Email dovrebbero risultare
abbastanza chiare. Il costruttore di questa classe inizializza le
variabili di istanza e sono presenti metodi che permettono di
ricavare ogni attributo. A questo punto ci occorre la classe Smt-
pService che invia gli oggetti Email.
Ethernet/Email/smtp_service.h
Riga 1 #ifndef __SMTP_SERVICE__H_
- #define __SMTP_SERVICE__H_
-
- #include "email.h"
5
- class SmtpService {
- boolean _use_auth;
- IPAddress _smtp_server;
- unsigned int _port;
354/517
10 String _username;
- String _password;
-
- void read_response(EthernetClient& client) {
- delay(4000);
15 while (client.available()) {
- const char c = client.read();
- Serial.print(c);
- }
- }
20
- void send_line(EthernetClient& client, String
line) {
- const unsigned int MAX_LINE = 256;
- char buffer[MAX_LINE];
- line.toCharArray(buffer, MAX_LINE);
25 Serial.println(buffer);
- client.println(buffer);
- read_response(client);
- }
-
30 public:
-
- SmtpService(
- const IPAddress& smtp_server,
- const unsigned int port) : _use_auth(false),
35
_smtp_server(smtp_server),
- _port(port) { }
-
- SmtpService(
- const IPAddress& smtp_server,
40 const unsigned int port,
- const String& username,
- const String& password) :
_use_auth(true),
355/517
-
_smtp_server(smtp_server),
- _port(port),
45
_username(username),
-
_password(password) { }
-
- void send_email(const Email& email) {
- EthernetClient client;
50 Serial.print("Connecting...");
-
- if (client.connect(_smtp_server, _port) <=
0) {
- Serial.println("connection failed.");
- } else {
55 Serial.println("connected.");
- read_response(client);
- if (!_use_auth) {
- Serial.println("Using no
authentication.");
- send_line(client, "helo");
60 }
- else {
- Serial.println("Using
authentication.");
- send_line(client, "ehlo");
- send_line(client, "auth login");
65 send_line(client, _username);
- send_line(client, _password);
- }
- send_line(
- client,
70 "mail from: <" + email.getFrom() + ">"
- );
- send_line(
356/517
- client,
- "rcpt to: <" + email.getTo() + ">"
75 );
- send_line(client, "data");
- send_line(client, "from: " +
email.getFrom());
- send_line(client, "to: " +
email.getTo());
- send_line(client, "subject: " +
email.getSubject());
80 send_line(client, "");
- send_line(client, email.getBody());
- send_line(client, ".");
- send_line(client, "quit");
- client.println("Disconnecting.");
85 client.stop();
- }
- }
- };
-
90 #endif
Si tratta in effetti di un numero consistente di istruzioni, ma le
esamineremo una alla volta. In primo luogo la classe SmtpSer-
vice incapsula l’indirizzo IP e la porta del server SMTP. Questi
attributi sono sempre obbligatori; inoltre, memorizziamo un
nome utente e una password nel caso qualcuno intenda utilizzare
una connessione autenticata.
La comunicazione con il server SMTP richiede di leggere le sue
risposte, e questa operazione è eseguita dal metodo privato
read_response definito a partire dalla riga 13. Il programma
attende per quattro secondi (in genere i server SMTP sono molto
impegnati, dato che devono inviare molti messaggi spam), poi
357/517
Ethernet/Email/Email.ino
Riga 1 #include <SPI.h>
- #include <Ethernet.h>
- #include "smtp_service.h"
-
5 const unsigned int SMTP_PORT = 2525;
- const unsigned int BAUD_RATE = 9600;
- const String USERNAME =
"bm90bXl1c2VybmFtZQ=="; // Codifica Base64.
- const String PASSWORD =
"bm90bXlwYXNzd29yZA=="; // Codifica Base64.
-
10 byte mac[] = { 0xDE, 0xAD, 0xBE, 0xEF, 0xFE,
0xED };
- IPAddress my_ip(192, 168, 2, 120);
-
- // Inserire di seguito l'indirizzo IP del
server SMTP
- IPAddress smtp_server(0, 0, 0, 0);
15
- SmtpService smtp_service(smtp_server,
SMTP_PORT, USERNAME, PASSWORD);
359/517
-
- void setup() {
- Ethernet.begin(mac, my_ip);
20 Serial.begin(BAUD_RATE);
- delay(1000);
- Email email(
- "arduino@example.com",
- "info@example.net",
25 "Yet another subject",
- " Yet another body"
- );
- smtp_service.send_email(email);
- }
30
- void loop() {}
Niente di nuovo. Il programma definisce una serie di costanti
per la porta SMTP, l’indirizzo MAC, il nome utente, la password e
altro ancora, poi crea un’istanza SmtpService. La funzione setup
inizializza la scheda Ethernet shield e la porta seriale, poi attende
per un secondo in modo da stabilizzare la configurazione del sis-
tema. Alla riga 22 si crea un nuovo oggetto Email da passare al
metodo send_email. Nella Figura 11.2 è possibile vedere il pro-
gramma in azione (autenticazione compresa).
Ora sapete come inviare messaggi di posta elettronica
utilizzando una scheda Arduino; per realizzare il sistema di
allarme anti-intrusione dovrete imparare a rilevare il movimento
in una stanza.
360/517
Ethernet/MotionDetector/MotionDetector.ino
Riga 1 const unsigned int PIR_INPUT_PIN = 2;
- const unsigned int BAUD_RATE = 9600;
-
- class PassiveInfraredSensor {
5 int _input_pin;
-
364/517
- public:
- PassiveInfraredSensor(const int input_pin) {
- _input_pin = input_pin;
10 pinMode(_input_pin, INPUT);
- }
- const bool motion_detected() const {
- return digitalRead(_input_pin) == HIGH;
- }
15 };
-
- PassiveInfraredSensor pir(PIR_INPUT_PIN);
-
- void setup() {
20 Serial.begin(BAUD_RATE);
- }
-
- void loop() {
- if (pir.motion_detected()) {
25 Serial.println("Motion detected");
- } else {
- Serial.println("No motion detected");
- }
- delay(200);
30 }
La costante PIR_INPUT_PIN definisce il pin digitale di collega-
mento con il sensore PIR. Alla riga 4 inizia invece la definizione
della classe PassiveInfraredSensor, che incapsula tutti gli ele-
menti software che hanno a che fare con i sensori PIR.
La variabile membro _input_pin memorizza il numero del pin
digitale ove risulta collegato il sensore, poi il programma definisce
un costruttore che accetta come argomento il numero di pin e lo
assegna alla variabile membro. Non resta che definire il metodo
motion_detected, che restituisce true se è stato rilevato un
365/517
Assemblare il progetto
Avendo a disposizione le classi PassiveInfraredSensor e
SmtpService diventa abbastanza semplice realizzare un sistema
di allarme anti-intrusione con notifiche via e-mail. Collegate il
sensore PIR alla Ethernet shield come mostrato nella Figura 11.6 e
inserite il codice che segue nell’IDE di Arduino.
Ethernet/BurglarAlarm/burglar_alarm.h
Riga 1 #ifndef __BURGLAR_ALARM_H__
- #define __BURGLAR_ALARM_H__
- #include "pir_sensor.h"
- #include "smtp_service.h"
5
- class BurglarAlarm {
- PassiveInfraredSensor _pir_sensor;
- SmtpService _smtp_service;
- void send_alarm() {
367/517
10 Email email(
- "arduino@example.com",
- "info@example.net",
- "Intruder Alert!",
- "Someone's moving in your living room!"
15 );
- _smtp_service.send_email(email);
- }
-
- public:
20 BurglarAlarm(
- const PassiveInfraredSensor& pir_sensor,
- const SmtpService& smtp_service) :
- _pir_sensor(pir_sensor),
- _smtp_service(smtp_service)
25 {
- }
-
-
- void check() {
30 Serial.println("Checking");
- if (_pir_sensor.motion_detected()) {
- Serial.println("Intruder detected!");
- send_alarm();
- }
35 }
- };
- #endif
Questo programma definisce una classe BurglarAlarm che
aggrega il codice scritto nei paragrafi precedenti. Il programma
incapsula un’istanza SmtpService e un oggetto PassiveIn-
fraredSensor. Il metodo più complesso di questa classe è
send_alarm, che permette di inviare un messaggio e-mail
preconfezionato.
368/517
Ethernet/BurglarAlarm/BurglarAlarm.ino
#include <SPI.h>
#include <Ethernet.h>
#include "burglar_alarm.h"
void setup() {
Ethernet.begin(mac, my_ip);
Serial.begin(BAUD_RATE);
369/517
delay(20 * 1000);
}
void loop() {
burglar_alarm.check();
delay(3000);
}
In primo luogo si includono tutte le librerie richieste dal pro-
getto, poi si definiscono le costanti relative al pin del sensore PIR
e all’indirizzo MAC. A questo punto si impostano gli oggetti Smt-
pService e PassiveInfraredSensor, che vanno utilizzati per
definire un’istanza BurglarAlarm. Vengono passati anche nome
utente e password in quanto viene utilizzata una connessione
SMTP autenticata per l’invio dei messaggi e-mail; se utilizzate una
connessione non autenticata, potete tranquillamente rimuovere
tutte le occorrenze dei parametri USERNAME e PASSWORD.
Il metodo setup configura la porta seriale e la scheda Ethernet
shield. Il programma aggiunge una pausa di 20 secondi, un tempo
sufficiente per lasciare la stanza prima che il sistema di allarme
inizi a funzionare.
La funzione loop deve semplicemente delegare le operazioni di
controllo al metodo check della classe BurglarAlarm. Nella
Figura 11.7 potete vedere ciò che succede quando il sistema di
allarme rileva la presenza di un intruso.
370/517
Esercizi
• Costruite un progetto simile a quello dell’allarme anti-
intrusione ma utilizzate un altro tipo di sensore.
Potete trovare suggerimenti utili all’indirizzo
372/517
http://www.tigoe.net/pcomp/code/category/
arduinowiring/873.
• Aggiungete l’indicazione del giorno e dell’ora nel mes-
saggio di posta elettronica trasmesso dall’allarme anti-
intrusione. Potete ottenere i dati che vi interessano
impiegando un servizio Daytime.
• Aggiungete il supporto per DHCP e DNS all’allarme
anti-intrusione.
• Aggiungete il supporto per Base64 all’allarme anti-
intrusione, in modo da non dover codificare manual-
mente il nome utente e la password. A tale scopo è
disponibile una libreria Base64
(https://github.com/adamvr/arduino-base64).
• Aggiungete al progetto un buzzer piezo che riproduce
un segnale acustico quando viene rilevata
un’intrusione.
• Procuratevi una fotocamera seriale TTL
(https://learn.adafruit.com/ttl-serial-cam-
era/overview) e allegate ai messaggi e-mail le foto
dell’intrusione. Questo esercizio è piuttosto
complesso: dovete infatti imparare come controllare la
fotocamera e come inviare allegati e-mail.
Capitolo 12
Telecomando universale
Cosa serve
1. Uno o più telecomandi a infrarossi, per esempio del
televisore o del lettore DVD.
2. Una scheda Ethernet shield per Arduino.
3. Un ricevitore a infrarossi, per esempio PNA4602 o
TSOP38238.
4. Un resistore da 100Ω.
5. Un led a infrarossi.
6. Cavi di collegamento su breadboard.
7. Una breadboard.
375/517
Il telecomando a infrarossi
Il controllo remoto o senza fili di un dispositivo elettronico, per
esempio un televisore, si basa sulla presenza di un trasmettitore e
di un ricevitore. Il ricevitore è in genere assemblato all’interno
dell’apparecchiatura elettronica, mentre il trasmettitore fa parte
del telecomando. La trasmissione dei segnali può impiegare svari-
ate tecnologie wireless, per esempio Bluetooth o Wi-Fi, ma la
maggior parte dei telecomandi si affida a comunicazioni che
utilizzano la luce a infrarossi.
La trasmissione di segnali a infrarossi è una tecnica che
presenta molti vantaggi. Questo tipo di segnale è invisibile
all’occhio umano, pertanto non disturba chi utilizza il tele-
comando. Può essere generato a poco prezzo con led a infrarossi,
facilmente integrabili nei circuiti elettronici. In sintesi, è una
soluzione ottima soprattutto nel controllo dei dispositivi che si
trovano solitamente in un ambiente domestico.
La tecnologia dell’infrarosso presenta peraltro una serie di
svantaggi. Il segnale non riesce ad attraversare le porte o le pareti,
e la distanza tra telecomando e dispositivo da controllare deve
essere abbastanza ridotta. È ancora più importante sottolineare
che il segnale a infrarossi è soggetto a interferenze con altre sor-
genti luminose. Le distorsioni provocate da altre sorgenti lumin-
ose possono essere ridotte al minimo producendo un segnale a
infrarossi modulato; ciò significa che il led viene acceso e spento a
una certa frequenza, in genere compresa tra 36 kHz e 40 kHz.
La modulazione del segnale è solo uno dei problemi che com-
plicano la realizzazione di un telecomando a infrarossi veramente
affidabile. Il problema più significativo è che i produttori di
377/517
RemoteControl/InfraredDumper/InfraredDumper.ino
Riga 1 #include <IRremote.h>
-
381/517
- Serial.println("SANYO");
40 } else if (protocol == MITSUBISHI) {
- Serial.println("MITSUBISHI");
- } else if (protocol == SAMSUNG) {
- Serial.println("SAMSUNG");
- } else if (protocol == LG) {
45 Serial.println("LG");
- }
- Serial.print("Value: ");
- Serial.print(results->value, HEX);
- Serial.print(" (");
50 Serial.print(results->bits, DEC);
- Serial.println(" bits)");
- }
-
- void loop() {
55 if (ir_receiver.decode(&results)) {
- dump(&results);
- ir_receiver.resume();
- }
- }
In primo luogo si definisce un oggetto IRrecv chiamato
ir_receiver che legge i dati presenti sul pin 11. Il programma
definisce anche un oggetto decode_result che memorizza gli
attributi dei segnali a infrarossi rilevati in input. Il metodo setup
inizializza la porta seriale, mentre il ricevitore a infrarossi è iniz-
ializzato dal metodo enableIRIn.
A questo punto si definisce il metodo dump che imposta il form-
ato e trasmette in output sulla porta seriale il contenuto di un
oggetto decode_result. Questo oggetto corrisponde a uno dei
tipi di dati fondamentali della libreria IRremote e incapsula una
serie di informazioni, per esempio il tipo di protocollo, la
383/517
Clonazione di un telecomando
Ora che conoscete il protocollo e i codici di controllo impiegati
dal telecomando, potete passare alla sua clonazione. Avete solo
bisogno di un led a infrarossi, un componente non molto diverso
dai led impiegati nei capitoli precedenti; l’unica differenza è data
dal fatto che questo led emette luce “invisibile”. Nella Figura 12.4
potete vedere lo schema di cablaggio di un led a infrarossi con il
pin 3 di Arduino; la scelta del pin dipende dalla libreria software
utilizzata; in questo progetto si aspetta la connessione tra pin 3 e
385/517
RemoteControl/TVRemote/TVRemote.ino
Riga 1 #include <IRremote.h>
-
- class TvRemote {
-
5 enum {
- CMD_LEN = 32, GUIDE =
0xE0E0F20D,
- POWER = 0xE0E040BF, TOOLS =
0xE0E0D22D,
- SOURCE = 0xE0E0807F, INFO =
0xE0E0F807,
- HDMI = 0xE0E0D12E, OPTIONS =
0xE0E016E9,
10 ONE = 0xE0E020DF, UP_K =
0xE0E006F9,
- TWO = 0xE0E0A05F, LEFT_K =
0xE0E0A659,
- THREE = 0xE0E0609F, RIGHT_K =
0xE0E046B9,
- FOUR = 0xE0E010EF, DOWN_K =
0xE0E08679,
- FIVE = 0xE0E0906F, RETURN =
0xE0E01AE5,
15 SIX = 0xE0E050AF, EXIT =
0xE0E0B44B,
- SEVEN = 0xE0E030CF, A =
0xE0E036C9,
- EIGHT = 0xE0E0B04F, B =
387/517
0xE0E028D7,
- NINE = 0xE0E0708F, C =
0xE0E0A857,
- TXT = 0xE0E034CB, D =
0xE0E06897,
20 ZERO = 0xE0E08877, PIP =
0xE0E004FB,
- PRE_CH = 0xE0E0C837, SEARCH =
0xE0E0CE31,
- VOL_UP = 0xE0E0E01F, DUAL =
0xE0E000FF,
- VOL_DOWN = 0xE0E0D02F, USB_HUB =
0xE0E025DA,
- MUTE = 0xE0E0F00F, P_SIZE =
0xE0E07C83,
25 CH_LIST = 0xE0E0D629, SUBTITLE =
0xE0E0A45B,
- PROG_UP = 0xE0E048B7, REWIND =
0xE0E0A25D,
- PROG_DOWN = 0xE0E008F7, PAUSE =
0xE0E052AD,
- MENU = 0xE0E058A7, FORWARD =
0xE0E012ED,
- SMART_TV = 0xE0E09E61, RECORD =
0xE0E0926D,
30 PLAY = 0xE0E0E21D, STOP =
0xE0E0629D
- };
-
- IRsend tv;
-
35 void send_command(const long command) {
- tv.sendSAMSUNG(command, CMD_LEN);
- }
-
- public:
388/517
40
- void guide() { send_command(GUIDE); }
- void power() { send_command(POWER); }
- void tools() { send_command(TOOLS); }
- void source() { send_command(SOURCE); }
45 void info() { send_command(INFO); }
- void hdmi() { send_command(HDMI); }
- void zero() { send_command(ZERO); }
- void one() { send_command(ONE); }
- void two() { send_command(TWO); }
50 void three() { send_command(THREE); }
- void four() { send_command(FOUR); }
- void five() { send_command(FIVE); }
- void six() { send_command(SIX); }
- void seven() { send_command(SEVEN); }
55 void eight() { send_command(EIGHT); }
- void nine() { send_command(NINE); }
- void up() { send_command(UP_K); }
- void left() { send_command(LEFT_K); }
- void right() { send_command(RIGHT_K); }
60 void down() { send_command(DOWN_K); }
- void ret() { send_command(RETURN); }
- void exit() { send_command(EXIT); }
- void a() { send_command(A); }
- void b() { send_command(B); }
65 void c() { send_command(C); }
- void d() { send_command(D); }
- void txt() { send_command(TXT); }
- void pip() { send_command(PIP); }
- void pre_ch() { send_command(PRE_CH); }
70 void search() { send_command(SEARCH); }
- void vol_up() { send_command(VOL_UP); }
- void vol_down() { send_command(VOL_DOWN); }
- void dual() { send_command(DUAL); }
- void usb_hub() { send_command(USB_HUB); }
75 void mute() { send_command(MUTE); }
389/517
RemoteControl/TvRemote/TvRemote.ino
Riga 1 const unsigned int BAUD_RATE = 9600;
-
- TvRemote tv;
- String command = "";
5 boolean input_available = false;
-
- void setup() {
- Serial.begin(BAUD_RATE);
- }
10
- void serialEvent() {
- while (Serial.available()) {
- const char c = Serial.read();
- if (c == '\n')
15 input_available = true;
- else
- command += c;
- }
- }
20
- void loop() {
- if (input_available) {
- Serial.print("Received command: ");
- Serial.println(command);
25 if (command == "guide")
tv.guide();
- else if (command == "power")
tv.power();
391/517
- }
- }
Nelle righe da 3 a 5 vengono definiti un oggetto TvRemote glob-
ale chiamato tv, una stringa chiamata command che contiene il
comando corrente e un flag booleano chiamato
input_available, che risulta true quando il programma riceve
un nuovo comando. Come di consueto, nella funzione setup viene
inizializzata la porta seriale.
La scheda Arduino chiama la funzione serialEvent definita
alla riga 11 all’arrivo di nuovi dati sulla porta seriale (ulteriori
informazioni su serialEvent sono disponibili nell’Appendice C).
Questi dati vengono accodati alla variabile command; quando si
incontra un carattere newline, inoltre, il flag input_available
viene impostato su true, affinché la funzione loop possa determ-
inare se è stato ricevuto un nuovo comando e quale.
Il metodo loop attende i comandi: all’arrivo di un nuovo
comando verifica se lo stesso è supportato e, in questo caso, invia
il codice di controllo corrispondente. In caso contrario, invece,
viene visualizzato un messaggio d’errore.
Compilate e caricate il programma per controllare un televisore
a piacere, in questo caso un televisore Samsung, tramite il mon-
itor seriale, il che rappresenta già un risultato significativo.
L’interfaccia è ancora un po’ scomoda per chi non è abituato a
maneggiare circuiti elettronici; nel prossimo paragrafo vedrete
come realizzarne una molto più intuitiva.
394/517
Controllo a distanza di
dispositivi a infrarossi dal
browser
Avete già realizzato diversi progetti controllabili tramite il mon-
itor seriale. Chi è solito programmare in campo informatico può
ritenere l’interfaccia comoda e pratica da usare, ma è sufficiente
mostrare uno di questi progetti a un amico poco abituato a man-
ovrare congegni tecnologici per suscitare perplessità e perché
questi reclami l’esigenza di un’interfaccia molto più intuitiva e
usabile.
In questo paragrafo implementeremo un’app Google Chrome
per realizzare una gradevole interfaccia utente per il telecomando
clonato. Prima di proseguire, è consigliabile leggere l’Appendice
D, se non l’avete ancora fatto.
RemoteControl/TvRemoteUI/manifest.json
{
"manifest_version": 2,
"name": "TV Remote Emulator",
"version": "1",
"permissions": [ "serial" ],
"app": {
"background": {
"scripts": ["background.js"]
}
},
"minimum_chrome_version": "33"
}
Lo stesso vale per il file background.js dell’applicazione, che
esegue il rendering del seguente file HTML all’avvio
dell’applicazione.
RemoteControl/TvRemoteUI/main.html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8"/>
<link rel="stylesheet" type="text/css" href="css/
style.css"/>
<title>TV Remote Emulator</title>
</head>
<body>
<div id="main">
<div>
<button id="power"
type="button">Power</button>
<button id="hdmi" type="button">HDMI</button>
<button id="source"
type="button">Source</button>
396/517
</div>
<div>
<button id="one" type="button">1</button>
<button id="two" type="button">2</button>
<button id="three" type="button">3</button>
</div>
<div>
<button id="four" type="button">4</button>
<button id="five" type="button">5</button>
<button id="six" type="button">6</button>
</div>
<div>
<button id="seven" type="button">7</button>
<button id="eight" type="button">8</button>
<button id="nine" type="button">9</button>
</div>
<div>
<button id="txt" type="button">TXT</button>
<button id="zero" type="button">0</button>
<button id="pre_ch"
type="button">PRE-CH</button>
</div>
<div>
<button id="vol_up" type="button">V
Up</button>
<button id="mute" type="button">Mute</button>
<button id="prog_up" type="button">Ch
Up</button>
</div>
<div>
<button id="vol_down" type="button">V
Down</button>
<button id="ch_list" type="button">CH
LIST</button>
<button id="prog_down" type="button">Ch
Down</button>
397/517
</div>
</div>
<script src="js/jquery-1.11.1.min.js"></script>
<script src="js/serial_device.js"></script>
<script src="js/remote.js"></script>
</body>
</html>
Questo documento HTML definisce sette righe contenenti
ognuna tre pulsanti. Carica inoltre diversi file JavaScript, la lib-
reria jQuery e la classe SerialDevice definiti nell’Appendice D, e
un file remote.js che indica cosa accade quando un utente preme
un tasto del nostro telecomando virtuale.
RemoteControl/TvRemoteUI/js/remote.js
Riga 1 $(function() {
- var BAUD_RATE = 9600;
- var remote = new SerialDevice("/dev/
tty.usbmodem24311", BAUD_RATE);
-
5 remote.onConnect.addListener(function() {
- console.log("Connected to: " +
remote.path);
- });
-
- remote.connect();
10
- $("[type=button]").on("click",
function(event){
- var buttonType =
$(event.currentTarget).attr("id");
- console.log("Button pressed: " +
buttonType);
- remote.send(buttonType + "\n");
398/517
15 });
- });
In remote.js, nella prima riga utilizziamo la funzione $ di
jQuery per garantire che tutto il codice JavaScript venga eseguito
solo una volta completato il caricamento dell’intera pagina web.
Viene quindi definita una nuova istanza di SerialDevice
chiamata remote a cui ci si connette; qui è importante assicurarsi
di utilizzare il nome giusto per la porta seriale.
Il resto del codice associa funzioni di callback a tutti gli altri
pulsanti definiti. La funzione $ di jQuery consente di selezionare
tutti gli elementi con il tipo button. Viene quindi chiamata la fun-
zione on per ogni elemento button, passandole il parametro
click per aggiungere una funzione di callback chiamata alla pres-
sione del pulsante.
Nella funzione di callback per gli eventi click, alla riga 12 viene
utilizzata la proprietà currentTarget dell’evento per determinare
il pulsante selezionato. L’attributo ID del pulsante viene letto per
utilizzarlo come comando da inviare alla scheda Arduino. Se
l’utente seleziona il pulsante con ID one, il programma invia il
comando one alla scheda Arduino; a sua volta la scheda invia il
codice corrispondente tramite infrarossi. L’uso di uno schema di
denominazione costante per gli elementi button nella pagina
HTML è davvero vantaggioso.
Per aggiungere un altro pulsante è sufficiente modificare la
pagina HTML, creando un nuovo elemento button e impostando
il suo attributo ID sul nome del comando Arduino che genera il
codice del telecomando corrispondente.
399/517
RemoteControl/TvRemoteUI/css/style.css
#main {
width: 18em;
margin-left: auto;
margin-right: auto;
}
button {
width: 6em;
}
Il compito principale del foglio di stile è garantire che tutti i
pulsanti abbiano la stessa larghezza. Eseguendo l’app Chrome
otterrete un risultato simile a quello della Figura 12.6.
Caricate il programma del paragrafo precedente nella scheda
Arduino e avviate l’app Chrome. Fate clic su uno dei pulsanti per
eseguire l’azione corrispondente. Questa volta l’interfaccia è
decisamente più semplice, vero?
A questo punto il progetto prevede di collegare Arduino alla
porta seriale del computer per gestire il controllo da browser web.
Nel prossimo paragrafo vedrete come superare questo problema e
controllare Arduino senza collegamento seriale.
400/517
RemoteControl/InfraredProxy/infrared_proxy.h
#include <SPI.h>
#include <Ethernet.h>
#include <IRremote.h>
class InfraredProxy {
403/517
private:
IRsend _infrared_sender;
public:
void receive_from_server(EthernetServer server);
};
Dopo aver incluso le librerie richieste dal software, il pro-
gramma dichiara la classe InfraredProxy e definisce una vari-
abile membro chiamata _infrared_sender per memorizzare
l’oggetto IRsend che va impiegato per emettere i codici di con-
trollo tramite segnali a infrarossi. Si dichiarano quindi tre metodi
helper privati e il metodo receive_from_server, l’unico metodo
pubblico della classe InfraredProxy.
Vediamo ora l’implementazione di tutti i metodi, partendo da
read_line.
RemoteControl/InfraredProxy/infrared_proxy.cpp
void InfraredProxy::read_line(
EthernetClient& client, char* buffer, const int
buffer_length)
{
int buffer_pos = 0;
while (client.available() && (buffer_pos <
buffer_length - 1)) {
const char c = client.read();
if (c == '\n')
404/517
break;
if (c != '\r')
buffer[buffer_pos++] = c;
}
buffer[buffer_pos] = '\0';
}
read_line legge una riga di dati inviata dal client. La riga si
conclude con un carattere newline (\n) oppure con un carattere
carriage return seguito da newline (\r\n). La funzione read_line
si aspetta di leggere i dati da un oggetto EthernetClient, un buf-
fer di caratteri in cui memorizzare i dati (buffer) e la lunghezza
massima del buffer di caratteri (buffer_length). Il metodo
ignora qualsiasi carattere di newline o carriage return e imposta
l’ultimo carattere della riga di dati a \0, pertanto il buffer viene
riempito con una stringa che si conclude con il carattere null.
Il metodo send_ir_data è responsabile dell’invio di comandi a
infrarossi.
RemoteControl/InfraredProxy/infrared_proxy.cpp
bool InfraredProxy::send_ir_data(
const char* protocol, const int bits, const long
value)
{
bool result = true;
if (!strcasecmp(protocol, "NEC"))
_infrared_sender.sendNEC(value, bits);
else if (!strcasecmp(protocol, "SONY"))
_infrared_sender.sendSony(value, bits);
else if (!strcasecmp(protocol, "RC5"))
_infrared_sender.sendRC5(value, bits);
else if (!strcasecmp(protocol, "RC6"))
405/517
_infrared_sender.sendRC6(value, bits);
else if (!strcasecmp(protocol, "DISH"))
_infrared_sender.sendDISH(value, bits);
else if (!strcasecmp(protocol, "SHARP"))
_infrared_sender.sendSharp(value, bits);
else if (!strcasecmp(protocol, "JVC"))
_infrared_sender.sendJVC(value, bits, 0);
else if (!strcasecmp(protocol, "SAMSUNG"))
_infrared_sender.sendSAMSUNG(value, bits);
else
result = false;
return result;
}
Il metodo emette un comando a infrarossi in base alle spe-
cifiche dettate da tipo di protocollo (protocol), lunghezza del
codice in bit (bits) e valore del codice da inviare (value). Il nome
del protocollo stabilisce il metodo delegato a svolgere le
operazioni vere e proprie sull’istanza IRsend.
Il metodo handle_command implementa uno degli aspetti più
complessi della classe InfraredProxy in quanto deve analizzare
l’URL riportato dalla richiesta HTTP.
RemoteControl/InfraredProxy/infrared_proxy.cpp
Riga 1 bool InfraredProxy::handle_command(char* line)
{
- strsep(&line, " ");
- char* path = strsep(&line, " ");
-
5 char* args[3];
- for (char** ap = args; (*ap = strsep(&path,
"/")) != NULL;)
- if (**ap != '\0')
- if (++ap >= &args[3])
406/517
- break;
10 const int bits = atoi(args[1]);
- const long value = strtoul(args[2], NULL,
16);
- return send_ir_data(args[0], bits, value);
- }
Per comprendere le operazioni svolte da questo metodo è
necessario conoscere il funzionamento delle richieste HTTP. Si
supponga di digitare nella barra degli indirizzi del browser web
l’URL http://192.168.2.42/SAMSUNG/32/E0E040BF; il browser
invia in questo caso una richiesta HTTP come la seguente:
GET /SAMSUNG/32/E0E040BF HTTP/1.1
host: 192.168.2.42
La prima riga imposta una richiesta GET e il metodo
handle_command si aspetta proprio una stringa che contiene una
richiesta di questo genere. Le informazioni codificate nel percorso
della richiesta (/SAMSUNG/32/E0E040BF) vengono estratte e
impiegate per il segnale a infrarossi. L’analisi di queste inform-
azioni è abbastanza complessa, e si semplifica solo utilizzando la
funzione C strsep. Questa funzione separa le stringhe delimitate
da caratteri speciali, si aspetta come parametri una stringa che
contiene alcuni di questi caratteri di separazione e una stringa che
definisce quali sono i caratteri da interpretare come tali. Per
ottenere le stringhe separate è necessario chiamare più volte
strsep finché non restituisce NULL: in pratica, ogni volta che si
chiama strsep, la funzione restituisce la stringa successiva
oppure NULL.
407/517
RemoteControl/InfraredProxy/infrared_proxy.cpp
Riga 1 void
InfraredProxy::receive_from_server(EthernetServer
server) {
- const int MAX_LINE = 256;
- char line[MAX_LINE];
- EthernetClient client = server.available();
5 if (client) {
- while (client.connected()) {
- if (client.available()) {
- read_line(client, line, MAX_LINE);
- Serial.println(line);
10 if (line[0] == 'G' && line[1] == 'E'
&& line[2] == 'T')
- handle_command(line);
- if (!strcmp(line, "")) {
- client.println("HTTP/1.1 200 OK\n");
- break;
15 }
- }
- }
- delay(1);
- client.stop();
20 }
- }
Il metodo receive_from_server si occupa dell’imple-
mentazione della logica fondamentale della classe InfraredProxy
e si aspetta un’istanza della classe EthernetServer definita nella
libreria Ethernet. La funzione si aspetta alla riga 4 che un client si
colleghi utilizzando il metodo available della classe Ether-
netServer. Ogni volta che il server si collega a un client il
409/517
RemoteControl/InfraredProxy/InfraredProxy.ino
#include <SPI.h>
#include <Ethernet.h>
#include <IRremote.h>
#include "infrared_proxy.h"
EthernetServer server(PROXY_PORT);
InfraredProxy ir_proxy;
void setup() {
410/517
Serial.begin(BAUD_RATE);
Ethernet.begin(mac, ip);
server.begin();
}
void loop() {
ir_proxy.receive_from_server(server);
}
Analogamente ai progetti precedenti, anche in questo caso si
devono impostare gli indirizzi MAC e IP della scheda di controllo,
poi si definisce un oggetto EthernetServer cui si deve passare la
porta che trasmette i dati, ovvero la 80, che è la porta HTTP
standard. Il programma deve anche inizializzare un nuovo oggetto
InfraredProxy.
Il metodo setup inizializza la porta seriale per consentire il
debugging del programma. Si deve inizializzare anche la scheda
Ethernet shield, poi si chiama il metodo begin della classe Ether-
netServer per avviare la trasmissione di dati da parte del server.
Il metodo loop deve semplicemente chiamare la funzione
receive_from_server della classe InfraredProxy, cui deve pas-
sare l’istanza EthernetServer.
Finalmente potete provare il funzionamento del programma!
Collegate la scheda Ethernet shield ad Arduino, poi collegate il
circuito con il led a infrarossi alla scheda Arduino. Configurate gli
indirizzi MAC e IP, compilate e caricate il programma
InfraredProxy in Arduino. Puntate il browser web all’indirizzo
http://192.168.2.42/SAMSUNG/32/E0E040BF ricordando di
modificare l’URL in base alle impostazioni locali. Osservate ciò
che avviene nel televisore oppure nel dispositivo che volete
411/517
CONTROLLATE TUTTO
I progetti di questo capitolo si basano su dispositivi che potete già
controllare tramite un normale telecomando a infrarossi. Tenete
però presente che potete aggiungere un ricevitore a infrarossi a un
qualsiasi dispositivo che riceve segnali di controllo oppure realizzare
nuovi gadget a partire proprio da un ricevitore a infrarossi.
In teoria, per esempio, potreste controllare con un telecomando il
funzionamento di un frigorifero o del forno a microonde. Avete mai
pensato di realizzare un tosaerba controllato da remoto
(http://www.instructables.com/id/Arduino-RC-Lawnmower/)? È ven-
uto il momento di farlo!
Esercizi
• Realizzate l’emulatore di uno dei telecomandi che
trovate in casa. Fate in modo che suoi comandi siano
trasmessi via porta seriale e via Ethernet.
• Invece di controllare Arduino dal monitor seriale o dal
browser web, realizzate un controllo che utilizzi Nin-
tendo Nunchuk. Potete muovere il joystick analogico
verso l’alto e verso il basso per controllare il volume
del televisore, oppure muovere Nunchuk a sinistra e a
destra per cambiare canale.
• Progettate un telecomando universale con Arduino.
Procuratevi un touch screen, una tastiera, una scheda
SD e un modulo Bluetooth. Probabilmente non avrete
mai bisogno di costruire un dispositivo così articolato,
ma adesso sapete almeno che sareste in grado di
realizzarlo con Arduino.
Capitolo 13
Cosa serve
1. Un servomotore, per esempio un servo Hitec
HS-322HD.
2. Cavi di collegamento su breadboard.
3. Un sensore di temperatura TMP36 (facoltativo; è
richiesto solo per gli esercizi).
4. Una scheda Arduino, per esempio un modello Arduino
Uno, Duemilanove o Diecimila.
5. Un cavo USB per collegare la scheda Arduino al
computer.
Utilizzo di base di un
servomotore
L’IDE Arduino offre una libreria per il controllo dei servomo-
tori da impiegare nei primi progetti. Nella Figura 13.3 è visibile lo
schema di cablaggio che permette di collegare Arduino con un
servomotore. Collegate il cavo di massa a uno dei pin GND di
Arduino, il cavo di alimentazione al pin 5V e il cavo di controllo al
pin 9.
ATTENZIONE
Ricordate che questo schema può essere utilizzato solo con servo
a 5 V! Molti servomotori economici utilizzano 9 V di tensione e
420/517
Figura 13.4 Dovete inserire tre cavi nel connettore del servo
per collegare il motore ad Arduino.
Motors/SerialServo/SerialServo.ino
Riga 1 #include <Servo.h>
-
- const unsigned int MOTOR_PIN = 9;
- const unsigned int MOTOR_DELAY = 15;
5 const unsigned int SERIAL_DELAY = 5;
- const unsigned int BAUD_RATE = 9600;
-
- Servo servo;
422/517
-
10 void setup() {
- Serial.begin(BAUD_RATE);
- servo.attach(MOTOR_PIN);
- delay(MOTOR_DELAY);
- servo.write(1);
15 delay(MOTOR_DELAY);
- }
-
- void loop() {
- const unsigned int MAX_ANGLE = 3;
20 char degrees[MAX_ANGLE + 1];
-
- if (Serial.available()) {
- int i = 0;
- while (Serial.available() && i < MAX_ANGLE
+ 1) {
25 const char c = Serial.read();
- if (c != -1 && c != '\n')
- degrees[i++] = c;
- delay(SERIAL_DELAY);
- }
30 degrees[i] = 0;
- int value = atoi(degrees);
- if (value == 0)
- value = 1;
- Serial.print(value);
35 Serial.println(" degrees.");
- servo.write(value);
- delay(MOTOR_DELAY);
- }
- }
Il programma include la libreria Servo e alla riga 8 definisce un
nuovo oggetto Servo. La funzione setup inizializza la porta
seriale, mentre la funzione attach collega l’oggetto Servo al pin
423/517
inviare alcuni valori, per esempio 45, 180 oppure 10. Osservate
che il motore ruota fino a raggiungere la posizione indicata dal
comando. Per valutare meglio l’effetto di rotazione ritagliate in
forma di freccia un pezzo di cavo o di carta e fissatelo all’albero
del motore.
È abbastanza facile controllare la posizione di un servomotore
tramite la porta seriale, e il circuito appena realizzato costituisce
la base di partenza per lo sviluppo di progetti utili e divertenti.
Nel prossimo paragrafo vedrete come realizzare un dispositivo
automatico particolarmente curioso.
L’ARTE DI ARDUINO
Potete utilizzare Arduino non solo per realizzare gadget più o meno
divertenti ma anche per progettare soluzioni stimolanti da un punto
di vista artistico. Potete trovare proposte intriganti di progetto in
particolare nelle creazioni che si rifanno ai nuovi media. Uno dei
progetti più interessanti è Anthros (http://makezine.com/2010/04/
19/arduino-powered-kinetic-sculpture/), un ambiente responsive
che osserva un’area tramite una webcam. L’area tenuta sotto con-
trollo da Anthros contiene una serie di “tentacoli” che si muovono
verso chiunque l’attraversa. I servomotori muovono i tentacoli e
una scheda Arduino controlla il funzionamento dei servomotori.
Chi è interessato ad approfondire il tema dei nuovi media nelle
creazioni artistiche deve assolutamente leggere la tesi di Alicia Gibb
intitolata New Media Art, Design, and the Arduino Microcontroller: A
Malleable Tool (http://aliciagibb.com/thesis/).
Realizzare un Blaminatr
Puntare il dito per dare la colpa a qualcuno non è mai piacevole,
anche se a volte dà grandi soddisfazioni. In questo paragrafo
425/517
Motors/Blaminatr/Blaminatr.ino
Riga 1 const unsigned int MAX_MEMBERS = 10;
-
- class Team {
- const char** _members;
427/517
Motors/Blaminatr/Blaminatr.ino
#include <Servo.h>
const unsigned int MOTOR_PIN = 9;
429/517
class Blaminatr {
Team _team;
Servo _servo;
public:
Motors/Blaminatr/Blaminatr.ino
Riga 1 const unsigned int MAX_NAME = 30;
- const unsigned int BAUD_RATE = 9600;
- const unsigned int SERIAL_DELAY = 5;
-
430/517
Esercizi
• Aggiungete al Blaminatr una scheda Ethernet shield,
in modo da poter dare la colpa alle persone via Inter-
net e non solo attraverso la porta seriale. Fate in
modo, per esempio, di puntare il browser web all’indi-
rizzo http://192.168.1.42/blame/Maik per dare la
colpa all’autore di questo libro.
• Realizzate un termometro basato su un sensore di
temperatura TMP36 e su un servomotore. Il display
del dispositivo potrebbe essere simile a quello
mostrato nella Figura 13.6; in sintesi, il progetto deve
muovere una freccia in modo che indichi la temper-
atura misurata dal sensore.
434/517
Appendici
Elettronica e saldatura di
base
Circuiti elettrici
Un circuito elettrico è analogo per molti versi a un circuito
idraulico. Nella Figura A.1 (l’immagine della lampadina appar-
tiene a Benji Park, https://openclipart.org/detail/26218/
Lightbulb_Bright-bybpcomp) si può osservare a sinistra un cir-
cuito idraulico e a destra l’equivalente circuito elettrico. È vera-
mente interessante l’analogia tra i due circuiti: esiste una stretta
relazione tra le due situazioni fisiche, se si considera per esempio
una dinamo messa in funzione dalla corrente idraulica e impie-
gata come generatore della tensione elettrica. Vale la pena studi-
are più da vicino i singoli componenti presenti nei due circuiti.
L’acqua scorre nei tubi idraulici, mentre gli elettroni scorrono
nei cavi elettrici. La tensione elettrica è misurata in Volt (V) ed è
analoga alla pressione esercitata dalla pompa idraulica. La ten-
sione elettrica provoca il passaggio di corrente; maggiore è la ten-
sione, maggiore è la velocità con la quale gli elettroni scorrono nel
circuito.
438/517
R=V/I
I = 2,5 V / 150Ω = 17 mA
I = 2,5 V / 220Ω = 11 mA
Resistori
È molto difficile trovare un progetto elettronico che non
richieda l’uso di resistori. Dovete pertanto conoscere questo
441/517
Nella Figura A.7 sono mostrati due degli strumenti più comuni
per la rimozione delle saldature: a sinistra è visibile una treccia di
dissaldatura, a destra una pompa dissaldante.
verificate inoltre di usare una parte della treccia che non sia già
piena di stagno.
Queste istruzioni sono un punto di partenza che vi permette di
affrontare le prime saldature più semplici. Avete visto però che la
saldatura non è un’operazione particolarmente complessa: come
prossimo passo potete provare a realizzare dei kit per principianti,
di quelli che trovate in qualsiasi negozio di componenti elettronici
e corredati di istruzioni dettagliate per la saldatura. Inoltre potete
consultare altre guide e perfino dei tutorial disponibili su Internet
che spiegano come saldare i componenti elettronici
(http://bit.ly/1Ecj2JK).
Appendice B
Programmazione
avanzata di Arduino
Il linguaggio di
programmazione di Arduino
I primi programmi che realizzerete per Arduino possono sem-
brare scritti in un linguaggio Arduino “speciale”, anche se in
effetti si utilizzano di solito semplici istruzioni in C/C++. In realtà
Arduino non comprende il codice C o C++, che deve quindi essere
convertito in codice macchina sul vostro computer per essere ese-
guito dal microcontrollore della scheda di controllo Arduino.
Questo processo è definito cross-compiling, ed è il metodo
comunemente utilizzato per creare software eseguibile per i
459/517
Programmazione seriale
avanzata
SerialProgramming/AnalogReader/AnalogReader.ino
const unsigned int BAUD_RATE = 9600;
const unsigned int NUM_PINS = 6;
void setup() {
Serial.begin(BAUD_RATE);
}
void loop() {
if (input_available) {
if (pin_name.length() > 1 &&
(pin_name[0] == 'a' || pin_name[0] == 'A'))
{
469/517
void serialEvent() {
while (Serial.available()) {
const char c = Serial.read();
if (c == '\n')
input_available = true;
else
pin_name += c;
}
}
Questo programma attende il nome di un pin analogico (a0,
a1... a5) e restituisce il valore corrente del pin indicato. In questo
modo i client devono inviare un dato alla scheda Arduino (il nome
del pin) e ricevere il risultato corrispondente, come si può vedere
nell’esempio illustrato nella Figura C.2, che fa riferimento
all’utilizzo del monitor seriale dell’IDE Arduino.
470/517
C/C++
Nonostante la scheda Arduino sia programmata con istruzioni
scritte in C++, non dovete configurare direttamente i client che
comunicano con Arduino in C++ o C. Potete però effettuare
questa operazione in modo abbastanza semplice utilizzando come
riferimento la classe arduino_serial messa a disposizione da
Tod E. Kurt (https://github.com/todbot/arduino-serial).
Il progetto implementa uno strumento da riga di comando
completo che offre opzioni veramente utili. Molte di queste vanno
oltre gli scopi dei progetti Arduino; a noi serve solamente scari-
care i file arduino-serial-lib.h e arduino-serial-lib.c. La
libreria arduino-serial esporta le seguenti funzioni.
SerialProgramming/C/analog_reader.c
Riga 1 #include <stdio.h>
- #include <unistd.h>
- #include <termios.h>
- #include "arduino-serial-lib.h"
5 #define MAX_LINE 256
-
- int main(int argc, char* argv[]) {
- int timeout = 1000;
-
10 if (argc == 1) {
- printf("You have to pass the name of a
serial port.\n");
- return -1;
- }
- int baudrate = B9600;
15 int arduino = serialport_init(argv[1],
baudrate);
- if (arduino == -1) {
- printf("Could not open serial port %s.\n",
argv[1]);
- return -1;
- }
20 sleep(2);
- char line[MAX_LINE];
- while (1) {
- int rc = serialport_write(arduino, "a0\n");
475/517
- if (rc == -1) {
25 printf("Could not write to serial
port.\n");
- } else {
- serialport_read_until(arduino, line,
'\n', MAX_LINE, timeout);
- printf("%s", line);
- }
30 }
- serialport_close(arduino);
- return 0;
- }
In primo luogo si importano le librerie necessarie e si definisce
una costante relativa alla lunghezza massima delle righe che
devono essere lette sulla scheda Arduino, dopodiché si imposta la
funzione main.
Dopo aver verificato che da riga di comando sia stato trasmesso
il nome della porta seriale, il programma inizializza la porta
seriale (riga 15), poi attende 2 secondi per dare tempo alla scheda
Arduino di entrare in funzione. Al termine di questa pausa, alla
riga 23 ha inizio un ciclo di istruzioni che invia costantemente la
stringa "a0" alla scheda Arduino. Il programma verifica il
risultato della funzione serialport_write e se l’operazione ha
avuto successo le riga 27 permette di leggere il risultato inviato
dalla scheda Arduino. Ora potete compilare il programma
utilizzando il comando che segue:
maik> gcc arduino-serial-lib.c analog_reader.c -o
analog_reader
Identificate la porta seriale del computer ove è collegata la
scheda Arduino (nell’esempio è connessa a /dev/
476/517
Java
La piattaforma Java propone svariate soluzioni standard che
includono anche la Java Communications API che definisce
l’accesso a una porta seriale (http://www.oracle.com/technet-
work/java/index-jsp-141752.html). Ricordate però che l’API è
semplicemente una raccolta di specifiche che devono essere
implementate nel programma di controllo. Purtroppo, attual-
mente non è disponibile alcuna implementazione completa o
quasi perfetta.
Per molti anni il progetto RXTX (http://rxtx.qbang.org/)
ha offerto una buona implementazione, ma è da qualche tempo
che non viene aggiornato. L’implementazione di Oracle è
477/517
SerialProgramming/Java/AnalogReader.java
import jssc.SerialPort;
import jssc.SerialPortList;
import jssc.SerialPortException;
try {
SerialPort serialPort = new SerialPort(args[0]);
478/517
serialPort.openPort();
Thread.sleep(2000);
serialPort.setParams(
SerialPort.BAUDRATE_9600,
SerialPort.DATABITS_8,
SerialPort.STOPBITS_1,
SerialPort.PARITY_NONE
);
while (true) {
serialPort.writeString("a0\n");
System.out.println(readLine(serialPort));
}
}
catch (SerialPortException ex) {
System.out.println(ex);
}
}
a0: 433
a0: 328
a0: 328
^C
AnalogReader esegue esattamente le operazioni desiderate:
visualizza in modo continuo i valori rilevati sul pin analogico 0.
L’accesso alla porta seriale in Java diventa un problema semplice
da risolvere se si utilizzano correttamente le librerie software.
jSSC consente anche di scrivere codice orientato agli oggetti:
dispone infatti di un’interfaccia SerialPortEventListener che
facilita la separazione della gestione della comunicazione seriale
dalla logica dell’applicazione. Studiate qualche esempio di pro-
getto per saperne di più su queste caratteristiche.
Ruby
Anche i linguaggi di programmazione dinamici, per esempio
Ruby (http://it.wikipedia.org/wiki/Ruby), consentono di
accedere direttamente alla porta seriale dl computer e a una
scheda Arduino collegata alla medesima porta. Prima di eseguire
qualsiasi operazione è necessario installare la gem serialport
utilizzando questo comando:
maik> gem install serialport
Questa libreria Ruby permette di collegarsi alla scheda Arduino
tramite le righe di codice qui riportate.
SerialProgramming/Ruby/analog_reader.rb
Riga 1 require 'rubygems'
- require 'serialport'
481/517
-
- if ARGV.size != 1
5 puts "You have to pass the name of a serial
port."
- exit 1
- end
-
- port_name = ARGV[0]
10 baud_rate = 9600
- data_bits = 8
- stop_bits = 1
- parity = SerialPort::NONE
-
15 arduino = SerialPort.new(
- port_name,
- baud_rate,
- data_bits,
- stop_bits,
20 parity
- )
-
- sleep 2
- while true
25 arduino.write "a0\n"
- sleep 0.01
- line = arduino.gets.chomp
- puts line
- end
Il programma crea alla riga 15 un nuovo oggetto SerialPort,
cui vengono passati i parametri necessari per configurare la porta
seriale. Dopo un’attesa di 2 secondi il programma esegue un ciclo
di istruzioni e chiama la funzione write sull’oggetto SerialPort.
Il metodo gets permette di ricavare i risultati elaborati dalla
482/517
Python
Python è un altro linguaggio di programmazione dinamico che
permette di realizzare rapidamente client software della scheda di
controllo Arduino. La programmazione di una porta seriale
richiede di scaricare e installare in primo luogo la libreria pyserial
(http://sourceforge.net/projects/pyserial/files/). In
ambiente Windows è disponibile un installer specifico ma in gen-
erale è sufficiente usare un comando simile a quello indicato di
seguito per eseguire l’installazione della libreria:
maik> python setup.py install
483/517
SerialProgramming/Python/analog_reader.py
Riga 1 import sys
- import time
- import serial
-
5 if len(sys.argv) != 2:
- print "You have to pass the name of a serial
port."
- sys.exit(1)
-
- serial_port = sys.argv[1]
10 arduino = serial.Serial(
- serial_port,
- 9600,
- serial.EIGHTBITS,
- serial.PARITY_NONE,
15 serial.STOPBITS_ONE)
- time.sleep(2)
- while 1:
- arduino.write("a0\n")
- line = arduino.readline().rstrip()
20 print line
Il programma verifica innanzitutto che sulla riga di comando
sia stato impostato il nome di una porta seriale, poi crea un nuovo
oggetto Serial alla riga 10, cui passa i parametri necessari per
configurare la comunicazione seriale.
Dopo un’attesa di 2 secondi il programma avvia un ciclo di
istruzioni che trasmette la stringa "a0" alla porta seriale
chiamando la funzione write. I risultati restituiti da Arduino
484/517
Perl
Perl è ancora oggi uno dei linguaggi di programmazione dinam-
ici più diffusi e supporta egregiamente le comunicazioni seriali.
Alcune distribuzioni includono le librerie di programmazione
della porta seriale, ma in genere è necessario installare un modulo
apposito.
Gli utenti Windows possono fare riferimento alla libreria
Win32::SerialPort (http://search.cpan.org/dist/
Win32-SerialPort/); per gli altri sistemi operativi si può utiliz-
zare la libreria Device::SerialPort. L’installazione richiede di
impostare un comando simile al seguente:
maik> perl -MCPAN -e 'install Device::SerialPort'
A questo punto si può scrivere il programma Perl.
485/517
SerialProgramming/Perl/analog_reader.pl
Riga 1 use strict;
- use warnings;
- use Device::SerialPort;
-
5 my $num_args = $#ARGV + 1;
- if ($num_args != 1) {
- die "You have to pass the name of a serial
port.";
- }
-
10 my $serial_port = $ARGV[0];
- my $arduino =
Device::SerialPort->new($serial_port);
- $arduino->baudrate(9600);
- $arduino->databits(8);
- $arduino->parity("none");
15 $arduino->stopbits(1);
- $arduino->read_const_time(1);
- $arduino->read_char_time(1);
-
- sleep(2);
20 while (1) {
- $arduino->write("a0\n");
- my ($count, $line) = $arduino->read(255);
- print $line;
- }
Questo programma verifica la presenza sulla riga di comando
del nome della porta seriale, poi crea una nuova istanza
Device::SerialPort alla riga 11. A questo punto si configurano i
parametri della porta seriale e alla riga 16 si imposta il valore di
timeout relativo alle chiamate della funzione read. Se non si
imposta un timeout, il metodo read restituisce immediatamente i
486/517
ChromeApps/SimpleApp/manifest.json
{
"manifest_version": 2,
"name": "My First Chrome App",
"version": "1",
"permissions": ["serial"],
"app": {
"background": {
"scripts": ["background.js"]
}
491/517
}
}
Viene utilizzata la JavaScript Object Notation (JSON,
http://json.org/) ed è disponibile il supporto di numerose
opzioni (https://developer.chrome.com/apps/manifest).
Solo name e version, che specificano il nome e la versione
dell’app, sono obbligatori. Questi attributi saranno visualizzati nel
Chrome Web Store, se deciderete di mettere l’applicazione a dis-
posizione del pubblico: sceglieteli quindi con attenzione se inten-
dete divulgare la vostra app.
manifest_version contiene la versione della specifica del file
manifest: attualmente è necessario impostarla a 2 per le app
Chrome.
Con l’opzione permissions l’app Chrome può richiedere per-
messi di cui l’applicazione non dispone di default: in questo caso
la impostiamo a serial affinché l’app Chrome possa accedere alla
porta seriale del computer. Gli utenti che installano un’app
Chrome devono concedere tutti i permessi che l’app richiede.
Alla fine, manifest.json imposta la pagina di sfondo dell’app
Chrome su un file chiamato background.js. Arriviamo così al
secondo file obbligatorio per tutte le app Chrome, back-
ground.js.
Ogni app Chrome necessita non solo di un punto di partenza,
ma anche di una sorta di gestione del ciclo di vita. Così come i sis-
temi operativi controllano il ciclo di vita di un’applicazione nativa,
l’ambiente Chrome controlla il ciclo di vita di un’applicazione
492/517
ChromeApps/SimpleApp/background.js
chrome.app.runtime.onLaunched.addListener(function() {
chrome.app.window.create('main.html', {
id: 'main',
bounds: { width: 200, height: 100 }
});
});
Il file precedente aggiunge una nuova funzione listener
all’ambiente di runtime Chrome: nello specifico, registra una fun-
zione listener per l’evento onLaunched, che si verifica all’avvio
dell’app Chrome.
Il listener apre una nuova finestra per l’app Chrome chiamando
la funzione chrome.app.window.create, che accetta il nome di
un documento HTML da aprire e alcuni argomenti facoltativi,
come l’ID della finestra e i suoi confini. Per la nostra prima app
Chrome, il documento HTML si presenta come segue.
ChromeApps/SimpleApp/main.html
<!DOCTYPE html>
<html>
<head>
<title>My First Chrome App</title>
</head>
<body>
493/517
<p>Hello, world!</p>
</body>
</html>
Questi tre file (manifest.json, background.js e main.html)
sono tutto ciò che serve per un’app Chrome di base. Nel prossimo
paragrafo vedremo come eseguire l’applicazione per la prima
volta.
ChromeApps/SerialDevice/js/serial_device.js
Riga 1 var SerialDevice = function(path, baudRate) {
- this.path = path;
- this.baudRate = baudRate || 38400;
- this.connectionId = -1;
5 this.readBuffer = "";
- this.boundOnReceive =
this.onReceive.bind(this);
- this.boundOnReceiveError =
this.onReceiveError.bind(this);
- this.onConnect = new chrome.Event();
- this.onReadLine = new chrome.Event();
10 this.onError = new chrome.Event();
- };
Con questa funzione potete creare nuovi oggetti SerialDevice
come questo:
var arduino = new SerialDevice("/dev/
tty.usbmodem24321");
Osservate l’uso frequente della parola chiave this: in
JavaScript, this fa riferimento al contesto di esecuzione della
funzione corrente ed è quindi utilizzabile per numerosi scopi.
Durante la creazione di oggetti, spesso lo utilizzerete per creare
502/517
ChromeApps/SerialDevice/js/serial_device.js
Riga 1 SerialDevice.prototype.connect = function() {
- chrome.serial.connect(
- this.path,
- { bitrate: this.baudRate },
5 this.onConnectComplete.bind(this))
- };
-
- SerialDevice.prototype.onConnectComplete =
function(connectionInfo) {
- if (!connectionInfo) {
10 console.log("Could not connect to serial
device.");
- return;
- }
- this.connectionId =
connectionInfo.connectionId;
-
chrome.serial.onReceive.addListener(this.boundOnReceive);
15
chrome.serial.onReceiveError.addListener(this.boundOnRece
- this.onConnect.dispatch();
- };
-
- SerialDevice.prototype.disconnect = function()
{
20 if (this.connectionId < 0) {
- throw "No serial device connected.";
- }
- chrome.serial.disconnect(this.connectionId,
function() {});
- };
Per prima cosa dovreste notare che tutti i metodi vengono
definiti nella proprietà prototype dell’oggetto SerialDevice.
504/517
ChromeApps/SerialDevice/js/serial_device.js
SerialDevice.prototype.onReceive =
function(receiveInfo) {
if (receiveInfo.connectionId !== this.connectionId)
{
return;
}
this.readBuffer +=
this.arrayBufferToString(receiveInfo.data);
var n;
while ((n = this.readBuffer.indexOf('\n')) >= 0) {
var line = this.readBuffer.substr(0, n + 1);
this.onReadLine.dispatch(line);
this.readBuffer = this.readBuffer.substr(n + 1);
}
};
SerialDevice.prototype.onReceiveError =
function(errorInfo) {
if (errorInfo.connectionId === this.connectionId) {
this.onError.dispatch(errorInfo.error);
}
};
SerialDevice.prototype.send = function(data) {
if (this.connectionId < 0) {
throw "No serial device connected.";
}
chrome.serial.send(
this.connectionId,
this.stringToArrayBuffer(data),
506/517
function() {});
};
onReceive funziona analogamente al listener di esempio
implementato in un paragrafo precedente; l’unica differenza è che
la nuova implementazione cerca i caratteri newline. Quando ne
viene trovato uno, la funzione passa il buffer di lettura corrente
alla funzione che ascolta gli eventi onReadLine. Osservate che è
possibile trasmettere più righe in un singolo blocco di dati e che
onReceive verifica di aver ricevuto i dati sulla porta seriale
corretta.
Il metodo onReceiveError si assicura invece di ricevere le
informazioni di errore per la connessione corretta e in questo caso
invia l’evento alla funzione che ascolta gli eventi onError.
Per i nostri scopi non è necessario un metodo send, ma non fa
certo male aggiungerlo: avrete così una classe SerialDevice
utilizzabile in molti altri progetti.
Infine, ci servono due metodi helper per convertire gli oggetti
ArrayBuffer in stringhe e viceversa.
ChromeApps/SerialDevice/js/serial_device.js
SerialDevice.prototype.arrayBufferToString =
function(buf) {
var bufView = new Uint8Array(buf);
var encodedString = String.fromCharCode.apply(null,
bufView);
return decodeURIComponent(escape(encodedString));
};
SerialDevice.prototype.stringToArrayBuffer =
function(str) {
507/517
var encodedString =
unescape(encodeURIComponent(str));
var bytes = new Uint8Array(encodedString.length);
for (var i = 0; i < encodedString.length; ++i) {
bytes[i] = encodedString.charCodeAt(i);
}
return bytes.buffer;
};
È tutto: ora abbiamo una classe generica per la comunicazione
con i dispositivi seriali da un’app Chrome. Proviamo subito a
usarla scrivendo una piccola applicazione dimostrativa, vale a dire
un semplicissimo monitor seriale che legge permanentemente i
dati da una porta seriale e li visualizza in una pagina HTML. La
pagina HTML si presenta come segue.
ChromeApps/SerialDevice/main.html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8"/>
<title>Serial Device Demo</title>
</head>
<body>
<div id="main">
<p>The Arduino sends:</p>
<p id="output"></p>
</div>
<script src="js/serial_device.js"></script>
<script src="js/arduino.js"></script>
</body>
</html>
Nella pagina HTML trovate un elemento di paragrafo con il suo
attributo id impostato a output: si tratta dell’elemento che
508/517
“riempiremo” con i dati letti dalla porta seriale. Alla fine del docu-
mento includiamo la nostra nuova libreria JavaScript per
l’accesso al dispositivo seriale, nonché un file chiamato ardu-
ino.js.
ChromeApps/SerialDevice/js/arduino.js
var arduino = new SerialDevice('/dev/
tty.usbmodem24311');
arduino.onConnect.addListener(function() {
console.log('Connected to: ' + arduino.path);
});
arduino.onReadLine.addListener(function(line) {
console.log('Read line: ' + line);
document.getElementById('output').innerText = line;
});
arduino.connect();
Qui viene creato un nuovo oggetto SerialDevice chiamato
arduino, poi vengono aggiunte le funzioni listener per gli eventi
onConnect e onReadLine, che scrivono entrambi un messaggio
nella console. Il listener onReadLine inserisce la riga letta nel
Document Object Model (DOM) del browser.
Assicuratevi di utilizzare la porta seriale corretta nella prima
riga di arduino.js. Collegate quindi la scheda Arduino al com-
puter e caricate il progetto che genera continuamente righe di
testo sulla porta seriale. Potete utilizzare il programma relativo al
game controller realizzato nel Capitolo 6. Avviate l’app Chrome
per vedere un risultato simile a quello nella Figura D.3.
509/517
Bibliografia
Ringraziamenti
Prefazione
A chi si rivolge questo libro
Contenuti del libro
Arduino Uno e la piattaforma Arduino
Esempi di codice e convenzioni adottate
Risorse online
Componenti necessari
Starter Pack
Elenco completo dei componenti
Parte I - Iniziare a lavorare con Arduino
Capitolo 1 - Benvenuti in Arduino
Cosa serve
Cos’è esattamente un progetto Arduino?
Esplorare la scheda Arduino
Installazione dell’IDE Arduino
Conoscere l’IDE Arduino
Hello, World!
Compilare e caricare i programmi
512/517
Realizzare un Blaminatr
Cosa fare se non funziona?
Esercizi
Parte III - Appendici
Appendice A - Elettronica e saldatura di base
Corrente, tensione e resistenza
Circuiti elettrici
Come usare un tagliafili
La saldatura dei componenti elettronici
Rimozione delle saldature
Appendice B - Programmazione avanzata di Arduino
Il linguaggio di programmazione di Arduino
Operazioni sui bit
Appendice C - Programmazione seriale avanzata
Saperne di più sulle comunicazioni seriali
La comunicazione seriale nei vari linguaggi
Appendice D - Controllo della scheda Arduino con un
browser
Che cosa sono le app Google Chrome?
Creazione di un’app Chrome di base
Avvio dell’app Chrome
Esplorazione dell’API Chrome Serial
Scrittura di una classe SerialDevice
Appendice E - Bibliografia
@Created by PDF to ePub