25 febbraio 2014 - 6 ottobre 2014
Schema di Arduino
![]() |
1 - Connettore USB 2 - Connettore alimentazione (da 7 a 12V), 3 - Processore (Atmega328 è il microcontrollore. All'interno di questo componente viene salvato il programma scritto dall'utente e tutta la configurazione di base che permette ad Arduino un funzionamento corretto). 4 - Chip per la comunicazione seriale 5 - Clock/cristallo a16 mhz 6 - Bottone di reset 7- Led di accensione (On) 8 - Led di ricezione (RX) e trasmissione (TX) in corso 9 - L-Led (può essere programmato dal vostro programma) 10 - Pin di alimentazione (uscita corrente a 5V, 3.3V, Massa (GND) e Vin 11 - Input analogici (Possono percepire molto precisamente una corrente DC tra 0 e 5V, resistuendo un valore da 0 a 1023) 12 - TX and RX pins 13 - Input/Output digitali (sono programmabili per essere input o output. Percepiscono se è presente o no corrente restituendo LOW se non c'è corrente e HIGH se c'è corrente. Infine possono essere programmati per generare corrente in output di massimo 40mA). La tilde "~" davanti al numero indica un output PWM (o pulse width modulation, permette di creare un'onda di corrente regolabile. Questa è utile per comandare comandare ad esempio i servomotori da modellismo). 14 - Pin per la terra e AREF (regola il voltaggio di massima risoluzione degli input analogici). 15 - ICSP for Atmega328 (per programmare il micro 16 - ICSP for USB interface |
Descrizione in inglese di ARDUINO UNO all'indirizzo: http://arduino.cc/en/Main/arduinoBoardUno#.UxX5kluYat-
ALIMENTAZIONE:
La scheda elettronica puà essere alimentata dalla porta USB, o dalla presa di
corrente, o dal connettore a pettine femmina dedicato alle alimentazioni che ha
la dicitura "Power" (Vin-Gnd-Gnd-5V-3.3V-Reset-IoRef).
La tensione di alimentazione esterna (la USB fornisce 5 Volt), può variare da un
minimo di 7 ad un massimo di 12 Volt. L'ingresso della presa di corrente è
protetto contro le inversioni di polarità da un diodo. Se alimentate la scheda
dal connettore a pettine "Power" dovete non sbagliare le polarità, perchè questo
ingresso non è protetto!
Sulla tensione che arriva direttamente dalla presa USB è presente un fusibile
autoripristinante da 500mA. In questo modo l'uscita USB del vostro computer
dovrebbe essere protetta da eventuali cortocircuiti del +5V.
COMUNICAZIONE PC:
la porta USB permette di comunicare e ricevere dati e informazioni da e per il
PC. La scheda elettronica Arduino si avvale di un circuito integrato (4) che
trasforma il protocollo seriale, proveniente dal microprocessore, nel protocollo
USB. Nel PC deve essere installato un driver che crea una "virtual USB". Questo
driver viene fornito insieme al pacchetto software di sviluppo dei programmi
della scheda Arduino.
INGRESSI ANALOGICI:
sono disponibili 6 ingressi analogici, sul pettine chiamato "Analog In", che
possono convertire un segnale 0-5 Volt in 1024 passi digitali (10 bit di
risoluzione). Questi pin (A0-A5) all'occorrenza possono funzionare in modalità
digitale.
INGRESSI e USCITE DIGITALI:
i connettori a pettine chiamati "DIGITAL" con la numerazione dei pin che va da 0
a 13 possono essere utilizzati sia come ingressi che come uscite digitali, con
il segnale elettrico che può variare tra 0 e 5 Volt. Questi pin del
microprocessore ATmega328 possono fornire fino a 40mA di corrente.
LED:
Sulla scheda Arduino sono installati 4 led. Il led PWR (power) che è di colore
verde e si accende quando colleghiamo Arduino al computer, quindi quando c'è
alimentazione. Il led L che è di colore arancione è connesso al pin 13 quindi
mandare corrente al pin 13 significa accendere il led L. Poi ci sono i led TX e
RX
PROGRAMMAZIONE:
la programmazione del microprocessore montato sulla scheda Arduino viene
effettuato o con la porta USB (sfruttando il bootloader) oppure attraverso il
connettore ICSP a 6 pin. Per programmare Arduino con la connessione ICSP
dobbiamo avere il programmatore dedicato ai microprocessori della Atmel (AVR-ISP
o STK500). Questo tipo di programmazione ci permette di utilizzare tutta la
memoria disponibile.
AMBIENTE IDE
L'ambiente di programmazione è un software che deve essere installato sul vostro Computer. Questo software è sia un editor di testo avanzato, che permette di programmare il microprocessore presente sulla scheda Arduino, sia un visualizzatore di errori e di messaggi trasmessi attraverso la porta USB. Malgrado sia un ambiente di sviluppo con i suoi limiti (in particolare ha un debug degli errori scritto in inglese e di non sempre immediata comprensione), è sicuramente semplice. A corredo di questo software, che permette di programmare il software che farà funzionare il microprocessore della scheda Arduino, ci sono moltissime librerie di programmi pronte per essere utilizzate nei più svariati progetti quali: webserver, display LCD, servo motori, motori passo passo, trasmissione Wi-Fi e sensori di posizione sia magnetici che ad ultrasuoni.
Il linguaggio utilizzato per programmare Arduino è il C.
All'interno di un programma vengono inseriti dei commenti che spiegano le
operazioni svolte da una o più righe di codice. I commenti possono essere
scritti in questo modo
// commento sulla singola linea
/*
Commento su +
linee
*/
Le azioni svolte all'interno di Arduino vengono inserite
all'interno di particolari costrutti detti funzioni. La sintassi di una
semplice funzione senza parametri è:
tipo restituito nomefunzione()
{
.... istruzioni (corpo funzione)
}
STRUTTURA PROGRAMMA:
Il codice di qualsiasi programma
per Arduino è composto
essenzialmente di due
parti:
void setup() -
Questo è il posto dove mettiamo
il codice di inizializzazione.
Inizializza tutte le
impostazioni e le istruzioni
della
scheda (gli INPUT e OUTPUT)
prima che il ciclo principale
del programma si avvii. La funzione predefinita setup() viene eseguita solo
all'accensione di Arduino oppure dopo aver premuto il pulsante reset.
void setup()
{
... istruzioni (corpo funzione)
}
void loop() -
E' il contenitore del codice
principale del programma.
Contiene una serie di istruzioni
che possono essere ripetute una
dopo l'altra fino a quando non
spegniamo la scheda Arduino.La funzione predefinita loop()
quindi viene eseguita da
Arduino per tutto il tempo in cui il micro resta alimentato
void loop()
{
... istruzioni (corpo funzione)
}
Vediamo un esempio di funzione che accetta 3 valori (argomenti)
e ne restituisce la loro somma
int somma(int a , int b, int c)
{
return a+b+c;
}
Per utilizzare questa funzione all'interno di un nostro programma
scriviamo:
x=somma(3,4,5);
Analizziamo gli elementi della funzione:
int a , int b, int c
=> elenco degli argomenti di una funzione
le ()
racchiudono gli argomenti di una funzione
le {}
racchiudono sequenze di istruzioni che costituiscono il corpo della
funzione
Una funzione che non presenta argomenti deve avere comunque le parentesi
() in
fondo. Ricordatevi: le funzioni si riconoscono perché hanno le
()!
la parentesi {
si ottiene premendo la combinazione di tasti ALTGR+SHIFT+[
la parentesi }
si ottiene premendo la combinazione di tasti ALTGR+SHIFT+]
Una funzione è come una scatola chiusa (blackbox) alla quale passo dei valori (parametri) e dalla quale ricevo un risultato che dipende dai valori in ingresso
Chiaramente quando costruisco una funzione devo assegnare
un nome che ne renda immediatamente evidente lo scopo. Chiamare la funzione
somma() con il nome pioppo() non è una buona scelta poiché crea solo
confusione! Non sarò in grado di intuirne il suo significato (quello che fa!) a
meno che non analizzi in dettaglio il codice all'interno del corpo della
funzione.
int pioppo(int a , int b, int c)
{
return a+b+c;
}
...
x=pioppo(3,4,5);
...
Il programma che scrivo nell'IDE si chiama sorgente. E' un testo umanamente comprensibile. Per il micro di arduino no!. Il processo che trasforma un prg sorgente in una sequenza di comandi comprensibile dal micro di Arduino (linguaggio assembler o hex) si dice COMPILAZIONE. Il programma che effettua tale codifica si dice compilatore.
ISTRUZIONI ARDUINO ANALIZZATE:
pinMode(argomento1,
modalità);
Definisce l'utilizzo di una determinata posta:
argomento1 => è la porta da utilizzare
modalità => INPUT / OUTPUT
delay(xmillisecondi)
=> ferma l'esecuzione di un programma per
xmillisecondi;
digitalWrite(argomento1,stato)
=> accende/spegne (a seconda del valore della
variabile stato)
il pin indicato come argomento1
Le variabili sono dei contenitori che contengono i valori utilizzati o
prodotti da un programma. Le variabili sono la memoria di un programma.
Le variabili vengono dichiarate seguendo questa regola:
tipo nomevaribile = valore iniziale;
esempio:
int eta =50;
char primaletteradellalfabeto='A';
Se devo effettuare questo calcolo 7+12 devo salvarlo in una variabile altrimenti
questo viene perduto irrimediabilmente.
int z;
z=7+12;
4 marzo 2014
Gli schemi e i programmi illustrati durante la lezione si trovano sul sito http://www.brescianet.com. Occorre cliccare sul pulsante "Archivio" nella pulsantiera orizzontale in alto. Successivamente nel menu che appare nel frame a sinistra bisogna cliccare sulla cartella "Elettronica", poi sottocartella "Arduino" e successivamente sottocartella "Corso Arduino".
Ricordatevi che una funzione che non restituisce alcun valore nel C di Arduino richiede l'inserimento all'inizio della dichiarazione della parola void. In altri linguaggi questa tipologia di funzione viene chiamata Procedura.
Ricordatevi che per stabilire se un dato è di INPUT o di OUTPUT occorre prendere una posizione "arduinocentrica". Ad esempio se premo un pulsante il suo stato viene letto da Arduino tramite un PIN che è quindi di ingresso (INPUT) per il micro. Se invece Arduino accende un LED manda il comando associato verso l'esterno per cui si tratta di un OUTPUT. Se vogliamo dire ad Arduino che il pin 2 è di INPUT dobbiamo scrivere:
pinMode(2, INPUT);
VARIABILI
Rappresentano la memoria di un
programma di Arduino. Come
dice il nome stesso, il
contenuto della variabile può cambiare tutte le volte che
vogliamo. Quando si dichiara una
variabile bisogna dichiararne
anche il tipo. Questo significa
dire al processore le dimensioni
reali
del dato che si vuole
memorizzare.
Mentre dichiaro la variabile posso anche immediatamente valorizzarla. Esempio:
int Eta=18;
in alternativa a:
int Eta;
Eta=18;
La prima forma è utilizzata soprattutto dai programmatori esperti che in questo modo abbreviano il numero di righe del loro programma. La maggior compattezza del codice riduce il suo grado di leggibilità: probabilmente un programmatore neofita potrebbe non cogliere alcuna differenza tra la scrittura int Eta=18; (creo la variabile e immediatamente la valorizzo) ed Eta=18;(assegno un valore ad una variabile che si presuppone sia stata dichiarata in precedenza).
Ne esistono di diversi tipi (colorerò di rosso quelle che man mano vediamo in aula):
boolean - Può
assumere solamente due valori:
true (vero) o false (falso).
char -
Contiene un singolo carattere (Ascii). Occupa solo 8
bit di memoria (1 byte).
Esempio:
char
RispostaOK = 'S';
byte - Può
contenere un numero tra 0 e 255.
Come un carattere occupa solamente
un byte di memoria.
int - Contiene
un numero compreso tra -32.768 e
32.767. E' il tipo di variabile
più usata e usa 2 byte di
memoria.
unsigned int -
Ha la stessa funzione di int,
solo che non può contenere
numeri negativi, ma numeri tra 0
e 65.535.
long - E' il
doppio delle dimensioni di un
int e contiene i numeri da
-2.147.483.648 a 2.147.483.647
(+/-231).
unsigned long -
Versione senza segno di long va
da 0 a 4.294.967.295 (232).
float - Può
memorizzare numeri con la
virgola. Occupa 4 bytes della
RAM.
double - A
doppia precisione in virgola
mobile con valore massimo di
1,7976931348623157x10308.
string -
Contiene una sequenza di caratteri ASCII
(detta anche stringa). Usa un
byte per ogni carattere
della stringa. Al termine della
sequenza viene messo un
carattere speciale '\0' detto carattere NULL che indica ad Arduino la
fine della stringa. La
dimensione della sequenza viene
indicata tra []. Se inizializzo
immediatamente la stringa posso
omettere la dimensione. Esempio:
char Saluto[] = "Hello";
// 5 caratteri+carattere NULL
char
Saluto[6]="Hello"
// La stessa cosa di
sopra<br>
array - un
elenco di variabili accessibili
tramite un indice. Vengono
utilizzate per creare tabelle di
valori facilmente accessibili.
Ad esempio se voglio memorizzare
i 3 led (rosso, giallo e verde)
di un semaforo posso creare un
elenco di 3 elementi booleani.
Quello a true risulta acceso.
Esempio:
bool Semaforo[3]={false, true,
false};//
Il
led giallo è acceso
Nel tipo della variabile la
parola "array" non si dichiara,
ma si usano i simboli [ ] per la
dimensione e le {} per
inizializzare i valori.
Vediamo ora di parlare della visibilità delle variabili. Una variabile definita all'esterno di una qualsiasi funzione del nostro programma si dice globale. Una variabile globale è visibile ovunque ovvero può essere scritta o letta da una qualsiasi funzione del nostro programma. Una variabile definita all'interno di una funzione si dice locale ed è visibile (scope) solo all'interno della funzione stessa. Le altre funzioni non possono vederla ne modificarla. Variabili con lo stesso nome, definite in funzioni differenti, possono quindi coesistere poiché a livello pratico risultano tra loro distinte. Vediamo come esempio questo codice di Arduino:
/* -----------------------------------------------------------------------------------
Esempio creato per mostrare i livelli di visibilità delle variabili.
----------------------------------------------------------------------------------- */
// Le variabili dichiarate all'esterno di una qualsiasi procedura si dicono GLOBALI
int ANNO;
tipodato funzioneA(parametriA)
{
// Le variabili dichiarate all'interno di una funzione sono LOCALI
int ETA; // è un'altra variabile rispetto a quella definita nella funzioneB
int PESO; // è locale alla funzioneA
...
PESO=12 // questa istruzione valorizza la variabile locale PESO
// La variabile globale ANNO è visibile in tutte le funzioni del programma
ANNO=2013; // questa istruzione valorizza la variabile globale ANNO
}
tipodato funzioneB(parametriB)
{
// Variabili LOCALI alla funzioneB
int ETA; // è un'altra variabile rispetto a quella definita nella funzioneA
PESO=12; // questa istruzione in fase di compilazione genera un errore poiché nella
// funzioneB non esiste una variabile con quel nome
// La variabile globale ANNO è visibile in tutte le funzioni del programma
ANNO=2013; // questa istruzione valorizza la variabile globale ANNO
}
Il monitor seriale è una particolare finestra dell'IDE che permette di visualizzare quello che Arduino invia al PC tramite le istruzioni:
Serial.print(1); // Stampo il
numero 1
Serial.print(')'); // Stampo un
singolo carattere ascii (
Serial.print("Stato LED:"); //
Stampo la stringa costante Stato
LED:
Serial.println(statoLed); //
Stampo il contenuto della
variabile statoLed
Per mettere in comunicazione
Arduino e il monitor seriale
è necessario che le velocità di
trasmissione dei due attori
siano identiche. In Arduino
quindi occorre inizializzare la
connessione seriale ad una
determinata velocità mediante
l'istruzione:
Serial.begin(9600); // il parametro è il numero di bit al secondo (BAUD)
La velocità del monitor seriale viene settata con l'apposito listbox (vedi figura sottostante)
STRUTTURE DI CONTROLLO
Nei linguaggi di programmazione
il construtto IF consente
di replicare, all'interno
di un programma, la capacità
umana di effettuare delle scelte
in base a delle condizioni. Il
comando IF manda in
esecuzione una porzione di codice
sulla
base della condizione scritta
immediatamente dopo la parola
chiave if. La condizione
è posta sotto forma di
espressione racchiusa tra
parentesi.
Sintassi construtto IF-ELSE:
if
(condizione)
{
// Sezione TRUE: metto il codice C che eseguo
//
se la condizione è vera
}
else
{
// Sezione FALSE: metto il codice C che eseguo
//
se la condizione è falsa
}
Se la condizione è vera tutto ciò che è sotteso all'if verrà eseguito. Se falsa verrà eseguito tutto il codice che segue else. Se la sezione TRUE o FALSE è composta da una sola riga di codice allora le {} possono essere omesse. Pertanto questa sequenza di istruzioni:
if
(condizione)
{
Serial.println("Premuto");
}
else
{
Serial.println("Premuto");
}
può essere riscritta in questo modo:
if
(condizione)
Serial.println("Premuto");
else
Serial.println("Premuto");
La rappresentazione mediante flowchart è la seguente:
Nell'If non è
necessario definire la sezione else.
Questa modalità è utilizzata quando alcune porzioni di codice devono essere eseguite solo a certe condizioni.
... // Istruzioni per portare la
macchina davanti al garage
if (GarageChiuso)
{
... // Istruzioni per aprire il garage
}
... // Istruzioni per parcheggiare la macchina nel garage
La struttura IF-ELSE può essere ampliata con ulteriori
else-if. In questo modo posso gestire N+1 sequenze di codice
alternative (azioni) a fronte di N condizioni diverse. Questa modalità è utilizzata quando
il codice da eseguire dipende da differenti condizioni che risultano tra loro mutuamente esclusive.
Solo uno dei blocchi di codice verrà eseguito tra gli N+1 presenti.
if (SonoAScuola)
{
... // Istruzioni per ascoltare la lezione
}
else if (SonoACasa)
{
... // Istruzioni per studiare la lezione
}
else if (SonoInVacanza)
{
... // Istruzioni per divertirmi
}
else
{
... // Istruzioni per far quel che voglio
}
I construtti IF possono essere
annidati tra loro (messi uno dentro l'altro):
che corrisponde al seguente PSEUDO CODICE.
if (Condizione A)
{
if (CondizioneB)
{
... // Blocco AvBv
}
else
{
if (CondizioneC)
{
if (CondizioneD)
{
...
// Blocco AvBfCvDv
}
else
{
...
// Blocco AvBfCvDf
}
}
else
{
... // Blocco
AvBfCf
}
}
}
else
{
... // Blocco Af
}
Si ricordi sempre l'indentazione del codice (rientri): aumenta la leggibilità
di un programma!
Vediamo ora come si costruiscono le condizioni
utilizzate dal construtto IF. Occorre utilizzare gli operatori di confronto (o
comparazione). Eccoli:
== Uguale a (attenzione a non usare = da solo!!!!)
> maggiore di
< minore di
!= diverso da
<= minore o uguale
>= maggiore o uguale
Mediante tali operatori mettiamo in relazione due variabili oppure una variabile con una costante
Variabile1 OperatoreDiConfronto Variabile2
Variabile OperatoreDiConfronto Costante
Il risultato della relazione restituisce un valore true
(vero) o false (falso) a seconda (TastoPremuto==true sarà vero se il tasto è
premuto altrimenti falso).
Non ha senso mettere due costanti a confronto poiché
il risultato della condizione sarebbe sempre costante (esempio
12==31 sarà
sempre falso!) in una qualsiasi esecuzione del programma!
Costante1 OperatoreDiConfronto Costante2
Esempi di condizioni:
Eta>17
=> vero se la persona è Maggiorenne e la variabile Eta è un intero.
Eta>=18
=> vero se la persona è Maggiorenne indipendentemente dal tipo della variabile
Eta.
Reddito<100
=> vero se la persona guadagna pochissimo
Eta==18
=> vero se la persona è un diciottenne
Eta!=90
=> vero se la persona non è un novantenne
11 marzo 2014
Per inizializzare una variabile appena
dichiarata possiamo scrivere:
int x=10;
al posto di:
int x;
x=10;
OPERATORI COMPUTAZIONALI
Per incrementare di 1 il contenuto di una variabile possiamo utilizzare la seguente istruzione:
x=x+1;
oppure in alternativa una delle seguenti istruzioni
equivalenti:
x++;
// oppure
x+=1;
Analogamente per decrementare di 1 il contenuto di una variabile possiamo
utilizzare una delle seguenti istruzioni:
x=x-1;
// oppure
x--;
// oppure
x-=1;
Ricordarsi che le variabili che appaiono sia a destra che sinistra di un'istruzione di assegnamento devono essere inizializzate. Quindi se ho questa istruzione:
somma=somma+23;
allora prima devo avere da qualche parte un'istruzione di inizializzazione del tipo:
somma=0;
Attenzione che un singolo = è l'operatore di assegnamento mentre == è l'operatore di confronto. Se per caso in un'istruzione if sbaglio e scrivo:
if (x=1)
{
BloccoIstruzioni
}
l'istruzione
BloccoIstruzioni
verrà sempre eseguita poiché con
x=1 la
variabile x
prende il valore 1 che corrisponde a
True.
Se scrivo invece correttamente
if (x==1)
{{
BloccoIstruzioni
}
il BloccoIstruzioni verrà eseguito solo se il valore di x è uguale a 1.
OPERATORI BOOLEANI
Gli operatori
booleani vengono utilizzati per
combinare più condizioni: ad
esempio per verificare
se il valore di un sensore è
compreso tra
1 e 5. In questo caso
basta scrivere:
if ( (sensore>=1) && (sensore<=5)
) { ... }
Esistono tre tipi di operazioni
booleane: && (And) -
|| (Or) - ! (Not).
&&
=> AND => che corrisponde alla frase: "ed anche"
Condizione1 && Condizione2
=> la combinazione delle 2 condizioni è vera se tutte e 2 le condizioni sono
vere
||
=> OR => che corrisponde alla frase: "oppure"
Condizione1 || Condizione2
=> la combinazione delle 2 condizioni è vera se almeno una delle 2
condizioni è vere
!
=> NOT => che corrisponde alla frase: "non"
!Condizione1
=> vera se la condizione1 è falsa
25 marzo 2014
Ricordarsi che la virgola, utilizzata nei numeri come
separatore della parte decimale, nel C di arduino viene sostituita con il punto.
Quindi 3,43 diventa:
3.43
LE FUNZIONI
La sintassi
di una funzione è:
TipoDatoRestituito NomeFunzione(tipo
parametro1, ... , tipo parametroN )
{
CodiceFunzioneF
return ValoreRestituito;
}
I parametri preceduti dalla parola
Const
sono quelli a sola lettura (ovvero non possono essere modificati dalla
funzione stessa). Un esempio di funzione potrebbe essere la seguente:
int Somma(const int A , const int
B , const int C )
{
int Z=A+B+C;
return(Z);
}
Con l'istruzione
return
viene restituito il valore prodotto dalla funzione che deve essere dello stesso
tipo tipo indicato nell'intestazione della funzione.
Vediamo ora come implementare (costruire) la funzione
arrotonda().
Immaginiamo che la funzione si comporti in questo modo:
Arrotonda(3.5693,1);
=> 3.5
Arrotonda(3.5693,2);
=> 3.56
Arrotonda(3.5693,3);
=> 3.569
Un modo per ottenere tale risultato potrebbe essere il seguente:
1) immaginiamo di avere il seguente numero X=3.39291
2) lo moltiplico per la potenza n-esima di 10 dove n è il numero
di cifre decimali che voglio mantenere. L'effetto di questa operazione è quello
di spostare la virgola verso destra di n
posizioni. Se ad esempio poniamo n=2 il numero diventa X=339.291
3) successivamente assegno il valore ottenuto ad una variabile intera. Questo
operazione determina la
cancellazione della parte decimale per cui 339.291 diventa 339
4) adesso dividiamo il valore intero appena ottenuto per la stessa potenza di 10
utilizzata precedentemente. Chiaramente tratto il tutto come un numero con la
virgola (float/double) e non come intero. Otteniamo come risultato
finale X=3.39 che è l'obiettivo che ci eravamo proposti.
Quindi la funzione Arrotonda()potrebbe essere scritta in questo modo:
double Arrotonda(const double v, const int cifre)
{
int
i=(int)(v*pow(10,cifre));
return((double)(i/pow(10,cifre)));
}
La funzione pow(b,n)
restituisce la potenza bn
Per assegnare un numero con la virgola ad una variabile intera è meglio inserire un'operazione di casting
int x;
x=(int)(10.89*21.34);
Se non utilizzassi l'istruzione di cast
x=(int)(10.89*21.34);
alcuni compilatori
potrebbero restituire un errore poiché si ritrovano ad assegnare un numero decimale
ad una variabile intera che non gestisce la parte decimale.
L'operazioni di casting (int)(10.89*21.34)
è il modo per il programmatore di segnalare al compilatore che la conversione da
decimale ad intero è voluta!
Le funzioni possono non avere parametri oppure non restituire alcun valore:
Solitamente le funzioni che non restituiscono alcun valore vengono chiamate "Procedure". Se un parametro y presenta un & davanti nella chiamata alla funzione questo indica (per ora non entro nei dettagli!) che potrà essere modificato nel contenuto da parte della nostra procedura P e pertanto può essere sfruttato come un possibile valore di ritorno. In altre parole le due istruzioni sono equivalenti.
1 aprile 2014
LE COSTANTI
Se sappiamo
che una variabile, utilizzata all'interno di un nostro programma, non viene mai
modificata ma solo letta (ed è quindi una costante!) allora è opportuno
anteporre alla sua dichiarazione la parola chiave const.
const int pigreco=3.14;
Se per errore, nel codice del nostro programma, scriviamo un'istruzione di assegnamento come questa:
pigreco
=3.14159;allora il compilatore, durante la fase di compilazione, ci avviserà mediante un messaggio di errore:
GLI ARRAY
Gli array sono particolari tipi
di variabili che possono contenere più valori contemporaneamente. Se dobbiamo gestire n
numeri possiamo creare n variabili
oppure in alternativa un array con n elementi.
Il vantaggio degli array è che le singole posizioni possono essere utilizzate usando un indice (variabile numerica) che potremo variare opportunamente. Per assegnare un valore alla cella possiamo utilizzare la seguente istruzione:
int Elenco[4]={0};
Elenco[2]=12; // assegno 12 al
terzo elemento di Elenco
Ad esempio per registrare i valori letti da un sensore
durante l'arco delle 24 ore posso utilizzare un array di questo tipo:
int ValoriLetti[24]
=> quindi un elenco di 24 celle che conterranno le letture rilevate dal sensore
nell'arco delle 24 ore.
int ValoriLetti[2]=analogRead(A1);
=> leggo sul pin del sensore il valore e se sono le 3.00 di notte lo registro
nel terzo elemento dell'array
ValoriLetti.
Ricordatevi che la prima cella dell'array è
ValoriLetti[0]
e l'ultima è
ValoriLetti[n-1]
dove n è la dimensione dell'array.
DIVERSE CODIFICHE CHE DETERMINANO LA STESSA
ESECUZIONE
In classe abbiamo visto che la
seguente codifica (esempio del JOYSTICK)
void setup()
{
Serial.begin(9600);
}
void loop()
{
unsigned int X = analogRead(A0);
unsigned int Y = analogRead(A4);
Serial.print("(X,Y) = (");
Serial.print(X);
Serial.print(", ");
Serial.print(Y);
Serial.println(")");
delay(10);
}
produce questo output
lo stesso risultato poteva essere ottenuto con questa sequenza
void setup()
{
Serial.begin(9600);
}
void loop()
{
unsigned int X = analogRead(A0);
unsigned int Y = analogRead(A4);
String Linea="(X,Y)=("+String(X)+","+String(Y)+")";
Serial.println(Linea);
delay(10);
}
oppure con quest'altra sequenza
void setup()
{
Serial.begin(9600);
}
void loop()
{
unsigned int X = analogRead(A0);
unsigned int Y = analogRead(A4);
char Linea[32];
snprintf(Linea, 32, "(X,Y)=(%d,%d)", X, Y);
Serial.println(Linea);
delay(10);
}
Questi esempi mostrano come la maggior conoscenza dei comandi disponibili in Arduino consente di ottenere diverse soluzioni e talvolta di minimizzare notevolmente il codice. Analizziamo ora alcune istruzioni incontrate nel corso della lezione:
- A0
Per accedere alla porta analogica A0 posso utilizzare una definizione
personalizzata:
- String
Con l''istruzione String Frase
dico ad Arduino che nella variabile Frase metterò una sequenza di simboli
ASCII. Quando invece scrivo
String(23)dico ad Arduino che il numero 23 deve essere trattato come
stringa quindi "23". Pertanto se scrivo:
23+12 => il risultato
sarà: 35
String(23)+String(12)=>
il risultato sarà "2312" e quindi concateno
- snprintf
La funzione snprintf(s,n,"formato",var1,...)
compone una stringa utilizzando le stesse regole della "maschera di formato"
della funzione C++ printf ma invece di stamparla a video la riversa
nell'area di memoria (con capacità massima n) puntata dalla variabile
s. Ad esempio la seguente istruzione
snprintf(Linea, 32, "(X,Y)=(%d,%d)",
X, Y);
sostituisce i simboli %d con i valori contenuti nelle
variabili X e Y. La stringa così ottenuta viene riversata fino ad
un massimo di 32 caratteri nella variabile char [] Linea. Il simbolo
%d è un esempio di "maschera di formato" ed indica che verrà
sostituito con un numero. Se il formato fosse stato %s allora questo
verrà sostituito con una variabile o costante di tipo stringa.
- map
La funzione
map(valore,A,B,a,b)
riscala
valore, che
appartiene all'intervallo [A,B]
nel nuovo intervallo [a,b].
Ad esempio
8 aprile 2014
Esempio JOYSTICK - sorgente B: Vediamo come possiamo trasformare un numero che varia da 0 a 1023 in una lettera che mostra la direzione orizzontale del joystick (L left=sinistra ; C center=centro - R right=destra)
LE
MATRICI
Abbiamo visto che un array è un elenco di valori. Ogni valore è raggiungibile mediante un indice che parte da 0. Se gli elementi presenti nell'array sono N allora l'ultimo valore è situato nella posizione N-1
Con il C di Arduino possiamo definire anche tabelle di valori (quindi possiamo accedere a dati mediante le coordinate di riga e colonna).
Ad esempio per inizializzare una tabella 2x2 contenente i seguenti valori:
scrivo:
int tabella2x2[2][2];
tabella[0][0]=1;
// metto 1 nella 1° riga e 1° colonna
tabella[0][1]=2;
// metto 2 nella 1° riga e 2° colonna
tabella[1][0]=3;
// metto 3 nella 2° riga e 1° colonna
tabella[1][1]=4;
// metto 4 nella 2° riga e 2° colonna
Potevo scrivere la stessa cosa con un'unica istruzione (metto il contenuto di ogni riga tra { }, separo poi le righe con delle virgole e racchiudo il tutto tra { } ).
int tabella[2][2]= { {1,2}, {3,4} };
Potevo scrivere anche in questo modo e come si può notare
la valorizzazione della tabella risulta più comprensibile
int tabella[2][2]= {
{1,2},
{3,4}
};
Per inizializzare l'intera tabella con degli zeri
mi basta scrivere in questo modo:
int tabella[2][2]={0}; // riempio di 0 il tabellone
15 aprile 2014
Per gestire il display LCD occorre utilizzare la libreria fornita dallo stesso sistema di sviluppo di Arduino <LiquidCristal.h>. Questa sequenza di istruzioni:
LCD.print("AVE
");
LCD.print("STUDENTS");
è equivalente a
LCD.print("AVE
STUDENTS");
poiché il metodo print, dopo aver scritto il testo, si
posiziona automaticamente sulla cella successiva all'ultimo carattere scritto.
Il comando setcursor(colonna,riga) permette di posizionare il cursore sul
prossimo punto da cui partire a scrivere. La numerazione delle righe e delle
colonne parte da 0 per cui LCD.setcursor(0,0) corrisponde alla prima
lettera in alto a sinistra del display LCD.
Per gestire il display occorre definire una variabile oggetto di tipo LiquidCrystal. .
GLI OGGETTI
Nella programmazione ad oggetti si immaginano le variabili come dotate di proprietà (Attributi) ed azioni (Metodi) capaci di rilevare il verificarsi di determinati accadimenti (Eventi).
Ad esempio pensando di applicare la programmazione ad oggetti relativamente ad una una persona abbiamo:
- Attributi: Cognome, Nome, Data di nascita, Altezza, Peso, ...
- Metodi: Alzarsi, Fermarsi, Studiare, Programmare, Dormire, ...
- Eventi: Suona la sveglia, semaforo rosso, Inizia il periodo degli
esami,mi regalano Arduino, scende la notte, ...
Quando all'interno di un programma troviamo 2 nomi separati dal punto
probabilmente abbiamo a che fare con un metodo o un attributo applicato ad
una variabile di tipo oggetto. Vediamo un esempio
Persona p;
p.Cognome="Sechi";
p.eta=50;
p.Spiega();
// chiama la routine che
gestisce questa azione
Non è nostro obiettivo costruire oggetti. L'importante è solo
riconoscerli quando usiamo le librerie.
STRUTTURE ITERATIVE
Nei linguaggi di programmazione
il construtto FOR consente
di implementare, all'interno
di un programma, la capacità
umana di ripetere un'operazione
un numero di volte noto a
priori.
Sintassi construtto FOR:
for (IstruzioniIniziali
; Condizione; IstruzioniIncremento )
{
IstruzioniIterate;
// vengono ripetute un certo numero di
volte
}
Il FOR viene eseguito in questo modo (Il nostro algoritmo, che risolve un determinato problema, dovrà quindi adattarsi a queste regole di funzionamento!)
1) Esegue l'IstruzioniIniziale (se più di una vanno separate
con la virgola)
2) Valuta la Condizione e se falsa passa al punto 6)
3) Esegue l' IstruzioniIterate (ripetute)
4) Esegue l'IstruzioniIncremento (solitamente è l'incremento o
decremento del contatore - se più di una vanno separate con la virgola)
5) Salta al punto 2)
6) Termina il FOR e continua con le istruzioni successive
Ecco il flowchar relativo al FOR:
Il for e l'array risultano spesso abbinati poiché l'accesso
indicizzato (per posizione!) agli elementi dell'array si adatta bene alle
modalità di funzionamento del FOR.
Supponiamo di dover riempire un array di 10000 elementi con valori progressivi
da 1 a 10000. Senza il for dovrei scrivere:
int
x[10000]={1,2,3,..., 9999}
oppure:
int x[10000];
x[0]=1;
x[1]=2;
...
x[9999]=9999;
x[10000]=10000;
quindi un numero esorbitante di righe. Invece con il FOR mi
bastano 3 righe:
int x[10000];
for (int i=0; i<10000 ; i++)
x[i]=i+1;
Si noti che la sequenza
void setup() {
}
void loop()
{
lcd.print("Ave");
delay(1000);
}
può essere riscritta utilizzando il for in questo modo:
void setup()
{
for ( ; ; ) // loop
infinito
{
lcd.print("Ave");
delay(1000);
}
}
void loop() { }
Nei linguaggi di programmazione il construtto WHILE consente di implementare, all'interno di un programma, la capacità umana di ripetere un'operazione più volte fino a ché una condizione risulta soddisfatta.
Sintassi construtto WHILE:
while
(Condizione)Il WHILE viene eseguito in questo modo (Il nostro algoritmo, che risolve un determinato problema, dovrà quindi adattarsi a queste regole di funzionamento!)
1) Valuto la Condizione e se falsa passa al punto 4)
2) Eseguo le IstruzioniIterate (ripetute)
5) Salta al punto 1)
6) Termina il WHILE e continua con le istruzioni successive
Ecco il flowchar relativo al WHILE:
Nel C++ le strutture iterative FOR e WHILE sono equipotenti ovvero qualsiasi programma scritto in FOR può essere riscritto con il WHILE e viceversa.
Istruzione FOR | Equivalente in WHILE |
for (IstruzioniIniziali
; Condizione; IstruzioniIncremento ) |
IstruzioniIniziali; { IstruzioniIterate; IstruzioniIncremento; } |
Istruzione WHILE | Equivalente in FOR |
while (Condizione){ IstruzioniIterate; } |
for (; Condizione;
) |
In altre parole quando dobbiamo implementare un'iterazione possiamo usare uno o l'altro construtto. Generalmente i programmatori preferiscono utilizzare l'istruzione FOR poichè più compatta (richiede un numero di righe limitato). E' comunque preferibile utilizzare il WHILE tutte le volte in cui non conosco a priori il numero di iterazioni che verrà svolto (esempio continuo a chiedere un numero finchè non mi si fornisce un valore uguale a 0).
#include <Servo.h> Servo myservo; int pos = 0; void setup() { myservo.attach(9); myservo.write(0); delay(15); void loop() { for(pos = 0; pos < 180; pos++) { myservo.write(pos); delay(15); } for (pos = 180; pos>=1; pos-=1) { myservo.write(pos); delay(15); } }
12 gennaio 2015
PASSAGGIO DEI PARAMETRI ALLE FUNZIONI
Analizziamo ora alcuni esempi che riguardano la manipolazione di variabili da parte di funzioni presenti in un nostro programma. Chiameremo funzione chiamante la funzione fm() che nel suo corpo presenta una chiamata ad un altra funzione fs() detta funzione chiamata. Ad esempio:
variabili globali del programma;
...
tipo fm(parametri
di fm)
{
variabili di fm;
// Corpo di fm()
...
fs(parametri di fm passati a fs);
...
}
tipodato fs(parametri di fs)
{
variabili di fs;
// Corpo di fs();
...
}
Negli esempi successivi considereremo come fm() la funzione di sistema setup() e come funzione fs() la funzione incrementa() da noi sviluppata e che si limita ad incrementare il contenuto del nostro parametro di un'unità. Per mia comodità, rifacendomi alle specifiche dei compilatori standard del C, ho posto a 4 byte la dimensione sia delle variabili int che quelle dei puntatore. In realtà in Arduino la dimensione in byte di una variabile int e di tipo puntatore è pari a 2 byte. Segue che la dimensione massima di RAM indirizzabile è 216 ovvero 64Kb mentre il range ammesso per il tipo int è +/- 215 = +/-32768.
Caso 1: la funzione chiamata non riceve alcun parametro ma manipola il contenuto di una variabile globale.
int x=7; void setup() { Serial.begin(9600); char c='A'; // istruzione fittizia per creare un'altra variabile Serial.println("Valore di X PRIMA della chiamata Incrementa(): x="+String(x)); Incrementa(); Serial.println("Valore di X DOPO della chiamata Incrementa() : x="+String(x)); } void Incrementa() { char S[3]={'\0'}; // istruzione fittizia per creare un'altra variabile x=x+1; } void loop() {}
Analizziamo l'output prodotto sul serial monitor. Notiamo che la nostra funzione incrementa() è in grado di modificare il contenuto della variabile globale X.
Vediamo di capirne il motivo in dettaglio.
1.a) Appena il programma di Arduino viene avviato subito vengono istanziate tutte le variabili definite all'esterno di una qualsiasi funzione. Tali variabili sono dette globali. Viene quindi creata in memoria la variabile x (dall'indirizzo 00 fino a 03)
1.b) Successivamente viene avviata la funzione setup() che immediatamente genera in memoria la variabile locale c (all'indirizzo 42) .
1.c) Quando viene richiamata la funzione incrementa() abbiamo la creazione nella memoria ram di uno spazio dedicato in modo esclusivo alla nostra funzione incrementa(). In tale spazio vengono messe le variabili locali dichiarate all'interno della funzione chiamata. Verrà quindi allocato lo spazio per la variabile s[3] (dall'indirizzo 65 all'indirizzo 67).
1.d) L'esecuzione prosegue con l'istruzione di incremento x=x+1 all'interno della funzione incrementa(). La variabile x non è definita all'interno di incrementa() per cui verrà utilizzata la variabile globale x.
1.e) Terminata l'esecuzione della funzione incrementa() tutto lo spazio di memoria ad essa riservato viene deallocato e la variabile locale s[3] viene distrutta (sostanzialmente se ne perde il contenuto!). L'esecuzione prosegue con l'istruzione successiva alla chiamata incrementa(); (all'interno della funzione setup()) quindi con la scrittura del contenuto della variabile globale x sul serial monitor. Abbiamo visto che la variabile globale x ha subito l'incremento del suo contenuto da parte della funzione incrementa() per cui stampa il valore 8.
RIASSUMENDO: In C le funzioni modificano il contenuto delle variabili globali
Caso 1 bis: la funzione chiamata non riceve alcun parametro ma manipola il contenuto di una variabile locale il cui nome è uguale a quello di una variabile globale.
int x=7;
void setup()
{
Serial.begin(9600);
char c='A'; // istruzione fittizia per creare un'altra variabile
Serial.println("Valore di X PRIMA della chiamata Incrementa(): x="+String(x));
Incrementa();
Serial.println("Valore di X DOPO della chiamata Incrementa() : x="+String(x));
}
void Incrementa()
{
char S[3]={'\0'}; // istruzione fittizia per creare un'altra variabile
int x=7;
x=x+1;
}
void loop() {}
Analizziamo l'output prodotto sul serial monitor. Notiamo che la nostra funzione incrementa() non modifica il contenuto della variabile globale X.
Vediamo di capirne il motivo in dettaglio.
1bis.a) Appena il programma viene avviato vengono subito istanziate tutte le variabili definite all'esterno di una qualsiasi funzione. Tali variabili sono dette globali. Viene quindi allocata in memoria la variabile x (dall'indirizzo 00 fino a 03)
1bis.b) Successivamente viene avviata la funzione setup(). che immediatamente alloca in memoria la variabile c (all'indirizzo 42) .
1bis.c) Successivamente viene chiamata la funzione incrementa(). Questo determina la creazione nella memoria ram di uno spazio dedicato in modo esclusivo alla nostra funzione incrementa(). In tale spazio verranno messe le variabili locali dichiarate all'interno della funzione chiamata. Verrà quindi allocato lo spazio per le variabili locali s[3] (dall'indirizzo 63 all'indirizzo 65) e x (dall'indirizzo 66 all'indirizzo 69). Quest'ultima ha il nome uguale a quello di una variabile globale.
1bis.d) L'esecuzione prosegue con l'istruzione di incremento x=x+1 all'interno della funzione incrementa(). La variabile locale x, definita all'interno di incrementa(), verrà utilizzata nell'operazione di incremento poichè "nasconde" (si dice shadowing) la variabile globale che resta quindi inalterata.
1bis.e) Terminata l'esecuzione della funzione incrementa() tutto lo spazio di memoria ad essa riservato viene deallocato e le variabili locali s[3] e x vengono distrutte (sostanzialmente se ne perde il contenuto!). L'esecuzione prosegue con l'istruzione successiva e quindi con la scrittura del contenuto della variabile globale x all'interno del serial monitor. Abbiamo visto che la variabile non ha subito alcuna modifica per cui stampa il valore originale 7.
RIASSUMENDO: In C le funzioni non riescono a modificare il contenuto delle variabili globali se hanno al loro interno delle variabili locali con lo stesso nome.
Caso 2: la funzione chiamata manipola il contenuto di una variabile passata come parametro (non è quindi presente il simbolo * nella definzione dei parametri).
void setup() { Serial.begin(9600); char c='A'; // istruzione fittizia per creare un'altra variabile int x=7; Serial.println("Valore di X PRIMA della chiamata Incrementa(x): x="+String(x)); Incrementa(x); Serial.println("Valore di X DOPO della chiamata Incrementa(x) : x="+String(x)); } void Incrementa(int x) { char S[3]={'\0'}; // istruzione fittizia per creare un'altra variabile x=x+1; } void loop() {}
Analizziamo l'output prodotto sul serial monitor. Notiamo che la nostra funzione incrementa() non è riuscita a modificare il contenuto della variabile originale X dentro setup().
Vediamo di capire come mai questo accada.
2.a) Appena il programma viene avviato viene lanciata la funzione setup() che immediatamente alloca in memoria le variabili richieste c (all'indirizzo 00) e x (all'indirizzo 01 fino a 04)
2.b) Successivamente viene richiamata la funzione incrementa() alla quale viene passato come parametro la variabile x definita nella funzione setup(). Questo determina la creazione nella memoria ram di uno spazio dedicato in modo esclusivo alla nostra funzione incrementa(). In tale spazio verranno messe le variabili locali dichiarate all'interno della funzione chiamata più il parametro che verrà inizializzato con il valore contenuto nella variabile x di setup() (quindi il valore 7). Verrà quindi allocato lo spazio per la variabile x (dall'indirizzo 73 all'indirizzo 76 poichè abbiamo supposto che il tipo int occupi 4 byte!) e s[3] (dall'indirizzo 77 all'indirizzo 79).
2.c) L'esecuzione prosegue con l'istruzione di incremento x=x+1 all'interno della funzione incrementa().
2.d) Terminata l'esecuzione della funzione incrementa() tutto lo spazio di memoria ad essa riservato viene deallocato e le variabili locali x e s[3] vengono distrutte (sostanzialmente se ne perde il contenuto!). L'esecuzione prosegue con l'istruzione successiva alla chiamata incrementa(x); all'interno della funzione setup() e quindi con la scrittura del contenuto della variabile x di setup()all'interno del serial monitor. Abbiamo visto che la variabile non ha subito alcuna modifica da parte della nostra funzione chiamata per cui stampa il valore originale 7.
RIASSUMENDO: In C le funzioni non sono in grado di modificare i parametri passati. Nel prossimo esempio vedremo come ovviare al problema.
Caso 3: la funzione chiamata manipola il contenuto di una variabile passata mediante l'utilizzo di un puntatore (è quindi presente il simbolo * nella definzione dei parametro).
Consideriamo il seguente programma che nella funzione di sistema setup() richiama la funzione Incrementa() alla quale passa come parametro l'indirizzo della variabile X. di setup() mediante la notazione &x.
void setup() { Serial.begin(9600); char c='A'; // istruzione fittizia per creare un'altra variabile int x=7; Serial.println("Valore di X PRIMA della chiamata Incrementa(&x): x="+String(x)); Incrementa(&x); Serial.println("Valore di X DOPO della chiamata Incrementa(&x) : x="+String(x)); } void Incrementa(int *x) { char S[3]={'\0'}; // istruzione fittizia per creare un'altra variabile *x=*x+1; } void loop() {}
Analizziamo l'output prodotto sul serial monitor. Notiamo che la nostra funzione incrementa() in questo caso è riuscita a modificare il contenuto della variabile originale X dentro setup().
Vediamo di capire il motivo.
3.a) Appena il programma viene avviato viene lanciata la funzione setup() che immediatamente alloca in memoria le variabili richieste c (all'indirizzo 00) e x (all'indirizzo 01 fino a 04)
3.b) Successivamente viene richiamata la funzione incrementa() alla quale viene passato come parametro l'indirizzo della variabile x definita nella funzione setup(). Questo determina la creazione nella memoria ram di uno spazio dedicato in modo esclusivo alla nostra funzione incrementa(). In tale spazio verranno messe le variabili locali dichiarate all'interno della funzione chiamata compresa quella relativa parametro (che non è di tipo intero ma puntatore ad intero!) che verrà inizializzata con l'indirizzo in memoria della variabile x di setup() passata come parametro (quindi con il valore 01). Verrà quindi allocato lo spazio per la variabile x (dall'indirizzo 73 all'indirizzo 76 poichè il tipo puntatore abbiamo supposto occupare 4 byte!) e s[3] (dall'indirizzo 77 all'indirizzo 79).
3.c) L'esecuzione prosegue con l'istruzione di incremento *x=*x+1 all'interno della funzione incrementa(). La scrittura *x non deve essere intesa come "puntatore ad x", come avviene nella dichiarazione int *x, ma come "contenuto della cella che in memoria si trova all'indirizzo contenuto nel puntatore x". Pertanto l'istruzione di incremento non opera nello spazio di memoria dedicato alla funzione incrementa() ma direttamente sulla variabile il cui indirizzo è stato passato come parametro (la x della funzione setup()).
3.d) Terminata l'esecuzione della funzione incrementa() tutto lo spazio di memoria ad essa riservato viene deallocato e le variabili locali *x e s[3] vengono distrutte (sostanzialmente se ne perde il contenuto!). L'esecuzione prosegue con l'istruzione successiva alla chiamata incrementa(&x); quindi con la scrittura del contenuto della variabile x di setup()all'interno del serial monitor. Abbiamo visto che la variabile ha subito un incremento per effetto dell'esecuzione della funzione chiamata per cui stampa il valore successivo 8.
RIASSUMENDO: In C le funzioni che ricevono come parametri un puntatore sono in grado di modificare il contenuto delle variabili originali il cui indirizzo è passato come argomento.
Caso 4: la funzione chiamata manipola il contenuto di una variabile passata come parametro (non è quindi presente il simbolo * nella definzione dei parametro) e lo restituisce alla funzione chiamante mediante un return.
Consideriamo il seguente programma che nella funzione di sistema setup() richiama l'istruzione di assegnamento x=Incrementa(x) alla quale viene passata come parametro la variabile X di setup().
void setup() { Serial.begin(9600); char c='A'; // istruzione fittizia per creare un'altra variabile int x=7; Serial.println("Valore di X PRIMA della chiamata x=Incrementa(x): x="+String(x)); x=Incrementa(x); Serial.println("Valore di X DOPO della chiamata x=Incrementa(x) : x="+String(x)); } int Incrementa(int x) { char S[3]={'\0'}; // istruzione fittizia per creare un'altra variabile x=x+1; return x; } void loop() {}
Analizziamo l'output prodotto sul serial monitor. Notiamo che la nostra istruzione x=incrementa(x) è riuscita a modificare il contenuto della variabile originale X dentro setup().
Vediamo di capire come mai questo accada.
4.a) Appena il programma viene avviato viene lanciata la funzione setup() che immediatamente alloca in memoria le variabili richieste c (all'indirizzo 00) e x (all'indirizzo 01 fino a 04)
4.b) Successivamente viene richiamata l'istruzione x=incrementa(x) alla quale viene passato come parametro la variabile x definita nella funzione setup(). Questo determina la creazione nella memoria ram di uno spazio dedicato in modo esclusivo alla nostra funzione incrementa(). In tale spazio verranno messe le variabili locali dichiarate all'interno della funzione chiamata più il parametro che verrà inizializzato con il valore contenuto nella variabile x di setup() (quindi il valore 7). Verrà quindi allocato lo spazio per la variabile x (dall'indirizzo 73 all'indirizzo 76 poichè abbiamo supposto che il tipo int occupi 4 byte!) e s[3] (dall'indirizzo 77 all'indirizzo 79).
4.c) L'esecuzione prosegue con l'istruzione di incremento x=x+1 all'interno della funzione incrementa().
4.d) L'esecuzione della funzione incrementa() termina con la restituzione del contenuto della variabile locale x mediante l'istruzione return;.
4.e) Terminata l'esecuzione della funzione incrementa() tutto lo spazio di memoria ad essa riservato viene deallocato e le variabili locali x e s[3] vengono distrutte (sostanzialmente se ne perde il contenuto!). Però "prima di morire" Incrementa() ha scaricato il valore modificato nella variabile x di setup()per cui la stampa all'interno del serial monitor stamperà il valore originale 8.
RIASSUMENDO: In C le funzioni che restituiscono un valore non possono modificare direttamente il parametro passato a meno che il valore restituito dalla funzione non venga assegnato alla variabile passata come parametro (che dovrà quindi comparire a sinistra del simbolo di assegnamento =)
TO DO (Prossimo corso):
A) Costruire con un KEYPAD un timer che mantiene l'ora dopo averla settata e fa
scattare degli allarmi ogni tot. Sul Display LCD appare l'ora e con un buzz
simulo l'allarme. Alla base utilizzare la funzione millis()
B) LCD con scrolling automatico in base a quello che scrivo sul Monitor
Seriale.
C) Mostrare esempi Servo Motore
D) Mostrare esempi IR
E) esempio_LED.htm - Sorgente B
F) esempio_TILT.htm
Note in sospeso .....
Link suggeriti:
http://www.raspibo.org/wiki/index.php?title=Ardubottino
CORSO: http://www.maffucci.it/area-studenti/arduino/
STRUTTURA
Il codice di qualsiasi programma
per Arduino è composto
essenzialmente di due
parti:
void setup() -
Questo è il posto dove mettiamo
il codice di inizializzazione.
Inizializza tutte le
impostazioni e le istruzioni
della
void loop() -
E' il contenitore del codice
principale del programma.
Contiene una serie di istruzioni
che possono essere ripetute
una
dopo l'altra fino a quando non
spegniamo la scheda Arduino.
COSTANTI
Nella scheda Arduino è inserita
una serie predefinita di parole
chiave con valori speciali. High
e Low sono usati per esempio
quando si vuole accendere o
spegnere un Pin di Arduino.
INPUT e OUTPUT sono usate per
definire se uno specifico Pin
deve essere un dato di entrata o
un dato di uscita. True e False
indicano il rispettivo
significato italiano: se abbiamo
un'istruzione, la condizione può
essere vera o falsa.
VARIABILI
Sono aree della memoria di
Arduino dove si possono
registrare dati e intervenire
all’interno del programma. Come
dice il nome stesso, le
variabili possono essere
cambiate tutte le volte che
vogliamo. Quando si dichiara una
variabile bisogna dichiararne
anche il tipo. Questo significa
dire al processore le dimensioni
del valore che si vuole
memorizzare. Ne esistono di
diversi tipi:
boolean - Può
assumere solamente due valori:
vero o falso.
char - Contiene
un singolo carattere. L'Arduino
lo registra come un numero (ma
noi vediamo il testo). Quando i
caratteri sonoo
usati per registrare un numero,
possono contenere un valore
compreso tra -128 e 127.
byte
int
- Contiene
un numero compreso tra -32'768 e
32'767. E' il tipo di variabile
più usata e usa 2 byte di
memoria.
unsigned int
-
Ha la stessa funzione di int,
solo che non può contenere
numeri negativi, ma numeri tra 0
e 65.535.
long - E' il
doppio delle dimensioni di un
int e contiene i numeri da
-2'147'483'648 a 2'147'483'647.
unsigned long -
Versione senza segno di long va
da 0 a 4'294'967''295.
float - Può
memorizzare numeri con la
virgola. Occupa 4 bytes della
RAM.
double - A
doppia precisione in virgola
mobile con valore massimo di
1'7976931348623157x10^308.
string - Un set
di caratteri ASCII utilizzati
per memorizzare informazioni di
testo. Per la memoria, usa un
byte per ogni caratteree
char string1[] = "Hello";
// 5 caratteri+carattere NULL
<br>char
string2[6]="Hello"
// La stessa cosa di
sopra<br>
N.B. ogni istruzione deve sempre
terminare con ";" in tale
linguaggio. Inoltre "//" è usato
per inserire commenti che
aiutano a comprenderlo.
array
- un
elenco di variabili accessibili
tramite un indice. Vengono
utilizzate per creare tabelle di
valori facilmente accessibili.
Comee
esempio se si vuole memorizzare diversi livelli di luminosità di un LED possiamo creare 4 variabili, una per ogni livello di luminosità.
int Luce[5]={0,25,50,100};
Nel tipo della variabile la
parola "array" non si dichiara,
ma si usano i simboli [] e {}.
STRUTTURE DI CONTROLLO
Il linguaggio di Arduino include
parole chiave per controllare il
progetto logico del nostro
codice.
If…else -
Permette di prendere delle
decisioni all’interno del
programma, ma deve essere
seguito da una domanda sotto
forma dii
espressione tra parentesi. Se la domanda è vera tutto ciò che segue verrà eseguito. Se falso verrà eseguito tutto il codice che
if (val=1){digitalWrite(LED,
HIGH);}
// (val=1) è la domanda se è
vera esegue ciò che è fra
parentesi<br>
For - Ripete il
codice per un numero predefinito
di volte.
for(int i=0;i<10;i++){Serial.print(“Ciao”);}
//stampa 10 volte “Ciao”
Switch - E’
come un interruttore nel corso
del programma. Fa prendere al
programma diverse direzioni
in base al valore della
variabile (il suo nome deve
essere messo tra parentesi dopo switch). E’
utile perché può sostituire
lunghe serie di if.
switch(valore sensore){<br>case
38:<br>digitalwrite(12,
High);break; <br>case 55:<br>digitalwrite(3,
High);break;<br>default:
// si usa per
indicare tutti i casi in
cui non è ne 38 ne 55
While - Esegue
un blocco di codice fino a
quando una certa condizione
posta tra le parentesi è vera.
while(valore sensore<500){<br>digitalWrite(13,
HIGH); <br>delay(100); <br>digitalWrite (13,
HIGH);
<br>delay(100); <br>Valoresensore=analogRead(1);
<br>
Do…While - E’
uguale a while solo che il
codice è avviato prima che la
condizione sia verificata. Si
usa quando si vuole eseguire il
codice almeno una volta prima che la condizione sia valutata. Esempio::
do {<br>digitalWrite(13,HIGH); <br>delay(100); <br>digitalWrite(13,HIGH); <br>DELAY (100);
<br>valore sensore=analogread(1);
<br>}
Break - Questo
termine consente di bloccare il
ciclo e continuare ad eseguire
il codice fuori dal ciclo.
Viene utilizzato anche per
separare le varie condizioni
nella funzione Switch.
Continuee -
Questo comando fa saltare il
resto del codice all’interno del
ciclo, e riavvia il ciclo.
Esempio:
<br>analogWrite(PWMPin,
luminosità); <br>delay(20);
<br>}
Return - Ferma
una funzione che si sta
eseguendo e restituisce un
risultato. E’ possibile infatti
usarlo per restituire un valore
da
una
funzione. Esempio chiama una
funzione “calcolaumidità” e
ritorna il valore dell’umidità.
Int calcolaumidità() {<br>Int
umidità=0;
<br>umidità0(analogread(0)+45/100)/100;<br>return umidità;<br>}
OPERAZIONI ARITMETICHE
Si può usare Arduino per
compiere operazioni matematiche
complesse con una semplice
sintassi: + e – indicano
addizione e
sottrazione, * indica la
moltiplicazione, e / la
divisione.
C’è un operatore in più in
questo linguaggio chiamato
“Modulo” che è un comando che
restituisce il resto di una
divisione. Esempio:
a=3+3; luminosità=((12*valore
sensore)/4);
Quando si specificano delle
condizioni ci sono vari
operatori che tu puoi usare::
== Uguale a
> maggiore di
< minore di
!= diverso da
< = minore o uguale
> = maggiore o uguale
OPERATORI BOOLEANI
Sono usati quando si vogliono
combinare più condizioni, ad
esempio se vogliamo verificare
se il valore di un sensore è tra
1 e 5
basta scrivere:
if(sensore=>1) && (sensore=<=5);
Esistono tre tipi di operazioni
booleane: &&(And),
||(Or), !(Not).
OPERATORI COMPUTAZIONALI
Servono a ridurre la mole di
un codice e a renderlo più
semplice e chiaro per operazioni
semplici come incrementare o
decrementare una variabile.
Esempio: val=val+1; è come dire
val++
incremento (++) e decremento
(--)
++ e --
incrementano/decrementano una
variabile di 1; lo stesso è
applicabile a +=, -=, *=, /= .
Esempio: le seguenti espressioni
sono equivalenti:
val=val+5;
Val+=5;
FUNZIONI INPUT E OUTPUTT
Arduino include funzioni per la
gestione degli Input e degli
Output.
pinMode(pin,mode)
- Riconfigura un pin digitale a
comportarsi come uscita o come
entrata.
pinMode(13,INPUT) -
imposta il pin 13 come Input.
digitalWrite(pin,value)
- imposta un pin digitale ad ON
o a OFF.
digitalWrite(7,HIGH)
- imposta come digitale il pin
7.
int digitalRead(pin)
- Legge lo stato di un input
Pin, ritorna HIGH se il Pin
riceve della tensione oppure
LOW se non c’è tensionee
applicata.
Val=digitalRead(7);
// legge il pin 7 dentro a val
Int analogRead(pin)
- Legge la tensione
applicata a un ingresso
analogico e ritorna un numero
tra 0 e 1023 che rappresenta lee
tensioni tra 0 e 5 V.
val=AnalogRead(0);
// legge l’ingresso analogico 0
dentro a val
analogWrite(pin,value)
- Cambia la frequenza PWM su uno
dei pin segnati PWM, nella voce
pin si può mettere 11 10 9 6 5
3,
value invece può essere un
valore da 0 a 255
che rappresenta la scala da 0 a
5 V.
analogWrite(9,128);
shiftOut(dataPin, clock,
Pin, bit, Order, value)
- Invia i dati ad un registro.
Questo protocollo usa un pin per
i dati e uno per ill
clock. bitOrder indica l'ordine
dei bytes (least significant
byte=LSB, most significant byte=LMB)
e value è il byte da inviare.
Esempio:
shiftOut(dataPin, Clock Pin,
LSBFIRST, 255);
insigned long pulseIn(pin,
value) - misura la
durata degli impulsi in arrivo
su uno degli ingressi digitali.
E’ utile ad esempio per
leggere alcuni sensori a
infrarossi o
alcuni accelerometri che
emettono impulsi di diversa
durata.
Tempo=pulsin(8,HIGH);
FUNZIONI DI TEMPO
Arduino include alcune funzioni
per misurare il tempo trascorso
e anche per mettere in pausa il
nostro programma.
Insigned long millis()
- Ritorna il numero in
millisecondi trascorsi
dall’inizio del programma,
esempio:
durata=millis()-tempo
// calcola il tempo trascorso
prima di “tempo”
delay(ms)
- Mette in pausa il
programma per un numero di
millisecondi specificato.
delay(1000);
//stoppa il programma per 1
secondo
delayMicroseconds(us)
- Come delay mette in pausa il
programma ma l’unità di misura è
molto più piccola, parliamo di
microsecondi.
delayMicroseconds(2000);
// aspetta per 2 millisecondi
(1000us=1ms)
FUNZIONI MATEMATICHEE
Arduino include molte funzioni
matematiche comuni. Servono, per
esempio, per trovare il numero
max o il numero min.
min (x,y)) -
Ritorna il più piccolo fra x e
y. Esempio:
Val= min(5,20);
// val adesso è 5
max(x,y) -
Ritorna il più grande fra x e
y.
abs(x) -
Ritorna il valore assoluto di x,
ossia trasforma i numeri
negativi in numeri positivi. Se
x fosse 5 ritorna 5, ma anche se
xx
fosse -5 ritorna sempre 5.
Esempio:
Val= abs(-5)
// val vale 5
constrain(x,a,b)
- Ritorna il valore "x"
costretta tra "a" e "b". Ciò
vuol dire che se "x" è minore di
"a" ritornerà semplicementee
map(value, fromLow,
fromHigh, toHigh) -
Associa un valore che sta nel
range fromLow e maxlow in
un nuovo range che va
da
toLow a toHigh. E’ molto utile
per processare valori
provenienti da sensori
analogici. Esempio:
val=map(analogRead(0),0,1023,100,200);
// associa il valore analogico 0
ad un valore tra 100 e 200
double pow(base,exponent)
- Restituisce come risultato la
potenza di un numero. Si deve
indicare la base e l’esponente.
Double sqrt(x)
- Restituisce la radice
quadrata di un numero x.
Double sin(rad)
- Restituisce il seno
dell’angolo specificato in
radianti. Esempio:
Double sine= sine(2);
// circa 0.909297370
Double cos(rad)
- Restituisce il coseno dell’
angolo specificato in radianti.
Double tan(rad)
- Restituisce il valore della
tangente di un angolo
specificato in radianti.
FUNZIONI NUMERI RANDOM
Se si ha bisogno di generare
numeri random (a caso), Arduino
ci viene incontro con alcuni
comandi standard per generarli.
randomSeed(seed)
- Anche se la distribuzione di
numeri restituita dal comando
random() è essenzialmente
casuale, la sequenza
è prevedibile. randomSeed(seed) inizializza il generatore di numeri pseudo-casuali, facendola partire da un punto arbitrario nella sua
sequenza casuale.
Long random(min,max)
- Restituisce un valore long
intero di valore compreso fra
min e max -1. Se min non è
specificato il suo
valore minimo è 0. Esempio:
long random= random(13);
// numero compreso fra 0 e 12
COMUNICAZIONE SERIALE
Queste sono le funzione seriali
cioè quelle funzioni che Arduino
usa per comunicare tramite la
porta Usb del nostro Pc.
Serial.begin(speed)
- Prepara Arduino a
mandare e a ricevere dati
tramite porta seriale. Possiamo
usare generalmente 9600 bits
per
secondo con la porta seriale
dell’Arduino, ma sono
disponibili anche altre
velocità, di solito non si
supera i 115.200 bps.
Serial.print(data)Serial.begin(9600);
Serial.print(data,codifica)
- Invia alcuni dati alla porta
seriale. La codifica è
opzionale.
Serial.print(32);
// stampa 32
Serial.Print(32, DEC);
// stampa 32 come sopra
Serial.Print(32, OCT);
// 40 (stampa10 in ottale)
Serial.Print(32 , BIN);
// 100000 (stampa 10 in binario)
Serial.Print(32 , BYTE);
// “Space” valore associato
nella tabella ASCII
Int Serial.available()
- Ritorna quanti bytes non
ancora letti sono disponibili
sulla porta Serial per leggerli
tramite la funzione
read(). Dopo aver read() tutti i bytes disponibili Serial.Available restituisce 0 fino a quando nuovi dati non giungono sulla Porta.
Int.Serial.read()
- Recupera un byte di dati in
entrata sulla porta Seriale.
int data= Serial.read();
Poichè i dati possono giungere
nella porta seriale prima che il
programma li possa leggere(per
la velocità), Arduino salva
tutti i dati
in
un buffer. Se è necessario
ripulire il buffer e aggiornarlo
con i dati aggiornati, usiamo la
funzione flush().
Serial.flush();