Megistone
L'istruzione di iterazione (o ciclo)
Nella descrizione di un algoritmo può capitare che alcune operazioni debbano essere eseguite più di una volta, cioè ripetute in modo identico.
Se dobbiamo effettuare il conto alla rovescia da 10 a 1, dobbiamo ripetere per 10 volte l'operazione "sottrai 1", mentre se dobbiamo riempire d'acqua una vasca da bagno utilizzando un bicchiere è necessario versare ripetutamente il contenuto del bicchiere finché la vasca non risulti piena.
La ripetizione di un insieme di istruzioni prende il nome di iterazione o ciclo (in inglese, loop); con questa istruzione indichiamo al calcolatore di ripetere un gruppo di istruzioni:
- un numero di volte ben determinato, come nel primo esempio;
- finché non si verifica una situazione, come nel secondo esempio.
Il gruppo di istruzioni ripetute prende il nome di corpo del ciclo. L'istruzione di iterazione viene classificata proprio per la caratteristica di conoscere o meno a priori il numero di ripetizioni (del corpo del ciclo) che deve effettuare:
- quando il numero di ripetizioni è noto a priori, cioè sai quante volte le istruzioni devono essere ripetute, il ciclo prende il nome di iterazione definita;
- quando invece il ciclo viene ripetuto un numero di volte sconosciuto a priori e termina quando si verifica una particolare condizione, l'iterazione si dice indefinita.
Vediamo alcuni esempi dei due diversi tipi di istruzione di iterazione.
ITERAZIONI DEFINITE | ITERAZIONI INDEFINITE |
Fai 10 giorni di ferie | Mescola la pasta finché è cotta |
Leggi 20 pagine | Innaffia le piante finché basta |
Per 10 volte scrivi "devo studiare di più" | Mangia finché sei sazio |
Attacca 30 figurine sull'album | Mentre piove fatti una dormita |
Invia 100 sms alla zia | Mentre non sei stanco, studia |
Imbianca con 3 mani la parete | Finché c'è vita c'è speranza |
Tra 210 giorni è finita la scuola | Mentre il serbatoio non è pieno metti benzina |
Per 210 giorni devi andare a scuola | Dormi finché hai sonno |
Osservando attentamente i due gruppi di istruzioni possiamo affermare che nelle iterazioni definite è sempre presente un numero, cioè viene sempre indicato chiaramente quante volte deve essere eseguita un'operazione, mentre in quelle indefinite non si sa a priori per quante volte questa debba essere ripetuta.
Codifichiamo l'iterazione
Lo schema generale di un'istruzione iterativa è riportato nelle figure seguenti, dove possiamo osservare come il percorso di un ramo formi un "anello" con l'istruzione di test.
Il test controlla se deve essere ripetuto il blocco di istruzioni, oppure se la ripetizione del ciclo termina e quindi si deve "uscire" dalla parte di destra dove prosegue il programma.
Possiamo osservare che gli schemi sono identici, cambia solamente il tipo di test:
- nel primo caso il controllo viene fatto sul valore del contatore che contiene il numero di volte che il ciclo è stato eseguito confrontandolo con il valore totale predefinito;
- nel secondo caso come test che ci "fa entrare in loop" abbiamo una qualunque condizione logica.
Iterazione indefinita
Esistono due diverse istruzioni di iterazione indefinita iniziamo con quella che prende il nome di iterazione precondizionata detta anche "ciclo a condizione iniziale" o "a controllo di testa".
In tutti e tre i nomi sopra riportati possiamo sottolineare come venga indicato il momento in cui viene fatto il test, cioè quando viene eseguito il controllo che permette di stabilire se bisogna entrare nel ramo di sinistra ed eseguire (o ripetere) le istruzioni del ciclo oppure si esce dal ramo di destra e si prosegue con il resto del programma.
I programmatori la chiamano "ciclo while" facendo riferimento alla parola riservata con la quale viene codificata.
Con un pulsante e un LED, si progetti un sistema in cui:
- con il pulsante rilasciato il LED è OFF;
- con il pulsante premuto il LED lampeggia alla frequenza di 2 Hz (ON per 0,25 s e OFF per 0,25 s).
Soluzione
il codice dello sketch
bool puls;
const int pinPuls=7;
const int pinLed=13;
void setup()
{
pinMode(pinPuls, INPUT_PULLUP);
pinMode(pinLed, OUTPUT);
}
void loop()
{
digitalWrite(pinLed, LOW);
puls = digitalRead(pinPuls);
while(puls==0)
{
digitalWrite(pinLed, HIGH);
delay(250);
digitalWrite(pinLed, LOW);
delay(250);
puls = digitalRead(pinPuls);s
}
}
Nello sketch puoi notare che:
è utilizzata l'istruzione WHILE che ha la seguente sintassi:
while(espressione) {
// blocco di istruzioni
}
dove il blocco di istruzioni viene ripetuto finché espressione (booleana) è VERA; appena diventa FALSA, il programma procede con le istruzioni dopo la graffa di chiusura del while.
L'espressione viene testata prima dell'esecuzione del blocco di istruzioni.
È fondamentale che nel blocco di istruzioni del while sia prevista la possibilità di uscita dal ciclo: in questo caso l'ultima istruzione del blocco:
puls - digitalRead(pinPuls)
effettua la lettura del pin del pulsante (pinPuls) e aggiorna la variabile puls, in modo che, se il pulsante viene rilasciato, al prossimo test l'espressione del while risulta FALSA e si esce dal ciclo.
Esiste anche l'istruzione DO...WHILE, la cui sintassi
do
{
// blocco di istruzioni } while (espressione);
è analoga a quella del WHILE, ma l'espressione è testata dopo aver eseguito il blocco di istruzioni, quindi il blocco viene eseguito almeno una volta.
L'istruzione di selezione doppia
Nella descrizione di un algoritmo si può verificare che le operazioni da eseguire siano diverse a seconda dei dati inseriti.
Ad esempio, se dobbiamo andare in vacanza, dobbiamo verificare di non avere "debiti scolastici" a settembre, e quindi si prospettano due soluzioni alternative:
- se abbiamo tutte sufficienze, allora andiamo in vacanza;
- altrimenti dobbiamo studiare tutta l'estate.
Nel diagramma a blocchi questa situazione è illustrata ricorrendo a un elemento grafico specifico, detto blocco di test o di confronto.
Blocco di confronto: al suo interno vengono effettuate operazioni di confronto e il risultato può essere soltanto SÌ O NO (oppure VERO O FALSO).
Di seguito riportiamo alcuni esempi di istruzioni di confronto che possiamo scrivere nei blocchi condizionali.
- Ho soldi sufficienti per comperare la moto?
- Il serbatoio è pieno?
- Il numero 50 è maggiore di 0?
- Giochi a calcio?
- Mario è un alunno di questa classe?
- 27 è un numero pari?
A queste domande si può solo rispondere SÌ oppure NO, esistono cioè soltanto due alternative: si tratta dunque di "istruzioni di confronto" che prendono il nome di condizioni logiche dalle quali, a seconda del valore della risposta, è sempre necessario intraprendere percorsi alternativi, cioè in base alla risposta è necessario eseguire operazioni diverse.
Li rappresentiamo graficamente nei diagrammi a blocchi con uno schema come il seguente:
Possiamo osservare che, a seconda della risposta data alla domanda (cioè al test), le strade "si dividono", vengono eseguite operazioni diverse nei due "rami" ma, successivamente, si ricongiungono.
La presenza di istruzioni in entrambi i rami del diagramma fa sì che in questo caso si parli di selezione doppia. Scriviamo il diagramma a blocchi per l'algoritmo "semplificato" che descrive la procedura per l'acquisto di una moto.
Vediamo come si traduce l'istruzione di selezione doppia con Arduino.
Con un pulsante e due LED, progetta un sistema in cui:
- con il pulsante rilasciato si accende solo il LED rosso;
- premendo il pulsante si accende solo il LED verde.
Soluzione
Si colleghi il pulsante al pin7 (con pull-up interno) e i LED rosso al pin 12 e verde al pin 13.
Il codice dello sketck
bool puls;
const int pinPuls=7;
const int pinRosso=12;
const int pinVerde=13;
void setup()
{
pinMode(pinPuls, INPUT_PULLUP);
pinMode(pinRosso, OUTPUT);
pinMode(pinVerde, OUTPUT);
}
void loop()
{
puls=digitalRead(pinPuls);
if (puls==1)
{
digitalWrite(pinRosso, HIGH);
digitalWrite(pinVerde, LOW);
}
else
{
digitalWrite(pinRosso, LOW);
digitalWrite(pinVerde, HIGH);
}
}
Nello sketch puoi notare che:
- sono state definite le costanti intere pinPuls-7, pinRosso-12 e pinVerde =13, che memorizzano i pin a cui sono collegati il pulsante e i due LED; queste sono poi richiamate nello sketch. In questo modo il programma è più leggibile e, nel caso in futuro si volesse modificare i pin a cui sono collegati i componenti, sarà sufficiente variare il contenuto delle costanti all'inizio dello sketch.
- È stata usata l'istruzione IF...ELSE per compiere due azioni differenti secondo il valore della variabile puls: se è soddisfatta la condizione tra parentesi (puls==1), sono eseguite le istruzioni del blocco tra le graffe successive, altrimenti quelle del blocco dopo l'else.
- Il simbolo = è usato per assegnare un valore a una variabile/costante, ad esempio puls=0.
- Il simbolo == è usato nelle espressioni per effettuare un confronto (il risultato è booleano, vero o falso); ad esempio nella istruzione if(puls==1) l'espressione tra parentesi è VERA se puls vale 1. È equivalente scrivere if(puls) ma, per maggiore chiarezza, evidenziamo la condizione completa.
La sintassi dell'istruzione IF...ELSE è:
if (espressione) {
// blocco di istruzioni A
else {
// blocco di istruzioni B
}
Se espressione è VERA si esegue il blocco di istruzioni A, altrimenti viene eseguito il blocco B.
L'else è facoltativo e va eliminato se non esiste alternativa ad A:
if (espressione) {
// blocco di istruzioni A
}
Microcontrollori
Il microcontrollore è il dispositivo digitale che ha maggiormente caratterizzato lo sviluppo dell'elettronica a partire dalla seconda metà degli anni '90; esso è caratterizzato da una elevata versatilità, unita ad una potente capacità di elaborazione e da una estesa capacità di immagazzinamento della memoria.
Lo sviluppo del microcontrollore (µC) ha permesso all'elettronica di espandersi capillarmente in tutti i settori tecnologici.
Alcune aree di applicazione del microcontrollore sono:
Elettronica di consumo | Industria |
Cellulari e GPS | PLC |
Giochi elettronici | controllo di processi |
Orologi digitali | Robotica |
Impianti antifurto | Navigazione avionica |
Condizionatori termici | Autronica |
Domotica | Trasmissione dati |
Foto e videocamere | Telefonia |
Lettori audio e video | Pannelli luminosi |
Strumentazione | Commercio |
Apparecchi di misura | Bancomat |
Monitoraggio | Terminali punti vendita |
Analisi di laboratorio | Distributore di alimenti |
Autodiagnostica | Fotocopiatrici Fax |
Strumenti elettromedicali | Editoria |
Sistemi di sicurezza | |
Sistemi di puntamento |
La centralità di questo componente nella moderna progettazione, rende obbligatoria, da parte di ogni tecnico elettronico, la conoscenza non solo della sua struttura hardware ma anche del suo linguaggio di programmazione. La distinzione tra abilità hardware ed abilità software, che fino ai tempi recenti distingueva i progettisti, è diventata con il microcontrollore è stata superata.
In generale, i microcontrollori vengono usati in applicazioni che richiedono un livello di complessità minore rispetto a quelle che coinvolgono l'uso dei microprocessori; il loro impiego è orientato alla gestione di apparati relativamente semplici che hanno un livello di complessità medio-basso come lavatrici, forni, apparecchi per la distribuzione di alimenti etc..
STRUTTURA
I microcontrollori (µC) sono circuiti integrati digitali che nella forma più ridotta, contengono in un unico chip un microprocessore (µP o CPU) una memoria di programma di tipo non volatile (ROM), una memoria dati volatile (RAM) un'unità di ingresso-uscita (I/O) ed almeno un timer.
La memoria di programma può essere di tipo PROM in tal caso il µC può essere programmato solo per una volta oppure di tipo EPROM o E2PROM nel qual caso può essere riscritta più volte.
I µC più complessi sono dotati al loro interno di svariate periferiche come comparatori, convertitori analogico-digitali, generatori di segnali PWM (Pulse Widht Modulation) porte di comunicazione seriale, sistemi di comunicazioni ad onde, e così via.
La maggioranza presenta un parallelismo a 8bit ma ormai sono frequenti anche dispositivi a 16 o 32bit.
La potenza di elaborazione, l'elevato numero di linee di I/O, la disponibilità di periferiche di tutti i tipi, le ridotte dimensioni, la comodità dell'ambiente di sviluppo , la ripetibilità illimitata della programmazione rendono il microcontrollore ideale per le cosiddette applicazioni embedded nelle quali il dispositivo è incorporato nell'apparecchiatura o nella macchina da controllare.
Grazie a questa innovativa filosofia di progettazione non c'è ormai più un'apparecchiatura anche di dimensioni ridotte che non abbia il suo µC incorporato. Si parla infatti dei µC come di un dispositivo embedded cioè incorporato in un apparato più grande in grado di facilitare le funzioni di quest'ultimo. Infatti, non bisogna solo pensare ad oggetti sofisticati come telefoni cellulari, lettori MP3 o DVD , i moduli di localizzazione GPS, i ricevitori TV ma anche ai più famigliari elettrodomestici (domotica).
Si potrebbe continuare parlando delle applicazioni nell'industria degli autoveicoli, terreno preferito per le applicazioni embedded (autronica) e di quelle macchine intelligenti che erogano denaro (bancomat) controllano il pagamento con carta di credito, distribuiscono bibite e alimenti.
Nelle apparecchiature elettromedicali i controlli embedded sono la norma come nel settore aereo e satellitare (avionica).
L'importanza di questo innovativo componente elettronico, ha messo in competizione le più grandi multinazionali del settore elettronico con il risultato di avere sul mercato famiglie di microcontrollori per
tutte le esigenze.
Alcune case come Zilog propongono µC derivati dallo Z80, altre presentano famiglie di microcontrollori originali come NEC, Intel, Toshiba Philips Semiconductor, Texas Istruments, ST Microelectronics fino alla Microchip Technology diventata famosa per la produzione dei popolarissimi PIC. Motivo della grande diffusione di questi µC oltre alle indubbie qualità è stata la vasta campagna pubblicitaria della Microchip, che ha messo a disposizione una ricca documentazione di progetti e programmi sorgente di tutti i tipi scaricabili da Internet.
AMBIENTE DI SVILUPPO
Per rendere operativo un µC bisogna prima di tutto programmarlo, cioè bisogna scrivere un algoritmo (una sequenza di istruzioni) che descriva tutte le azioni che il dispositivo deve eseguire. Il programma viene normalmente sviluppato a bordo di un normale computer, attraverso l'utilizzo di un IDE(Integrated Development Environment) cioè un software che costituisce un opportuno ambiente di sviluppo normalmente fornito dal produttore del µC.
La maggior parte dei dispositivi richiedono una programmazione in linguaggio Assembly, ma negli ultimi anni molti dispositivi consentono anche la scrittura del codice in linguaggi di medio livello come il C. L'IDE è un software costituito generalmente da un editor di testo e da un debugger cioè un correttore che permette di individuare eventuali errori di sintassi presenti nel codice scritto.
- scrittura del programma in codice sorgente sorgente (C/ASM) attraverso un editor di testo;
- debug: correzione del programma da eventuali errori di sintassi;
- compilazione, cioè creazione del programma eseguibile in codice macchina;
- caricamento (upload) del programma eseguibile a bordo della scheda µC.
Praticamente i passaggi sono gli stessi che si hanno per la realizzazione di un normale programma per computer, solo che in questo caso ad essere programmato è un dispositivo elettronico.
Questo nuovo tipo di approccio alla progettazione elettronica e all'automazione ha portato a coniare nella letteratura del settore il termine "physical computing".
Il physical computing utilizza l'elettronica per progettare oggetti interattivi che possano comunicare con gli esseri umani utilizzando sensori ed attuatori controllati da un comportamento che viene implementato sotto forma di software eseguito all'interno di un microcontrollore .
Si potrebbe obiettare che la stessa programmazione di un computer (PC) sia assimilabile al concetto di physical computing, perché si accettano segnali fisici da tastiere e mouse (periferiche di input) e si producono segnali si video e stampanti (periferiche di output) ma sarebbe difficile impiegare un PC convenzionale per controllare direttamente il funzionamento di sensori ed attuatori.
ARDUINO
Attualmente possiamo considerare il progetto Arduino come la piattaforma open source di physical computing più diffusa e popolare.
Arduino è composto da due parti: la scheda (hardware) e l' IDE (software) che deve essere scaricato ed eseguito sul PC.
L'IDE, come si è accennato, serve per creare piccoli sketch (programmi) salvati come file con estensione .ino (dalle ultime tre lettere della parola Arduino) che poi devono essere caricati sulla scheda. Lo sketch istruisce la scheda.
Non molto tempo fa, lavorare con l'hardware significava costruire circuiti da zero utilizzando centinaia di componenti diversi, resistenze, condensatori, transistor etc.. Ogni circuito era cablato per eseguire una applicazione specifica e apportare delle modifiche significava tagliare cavi e saldare collegamenti.
Con l'avvento delle tecnologie digitali e dei microprocessori, queste funzioni, una volta realizzate coi cavi, sono state sostituite da programmi software. Il software è più facile da modificare dell'hardware .
La scheda Arduino è una piccola scheda microcontrollore, cioè un circuito (la scheda) che contiene un chip (il microcontrollore) ; il risultato è un piccolo computer almeno 1000 volte meno potente di qualsiasi PC, ma molto più economico ed in grado di costruire dispositivi interessanti. Sulla scheda è infatti riconoscibile un rettangolo nero in plastica dotato di 28 piedini (quadrato se si dispone della versione SDM) ; si tratta del µC AT-mega328 della Microchip: il cuore della scheda.
Bisogna specificare che ci sono diversi tipi di scheda Arduino, ma la più comune è la scheda Arduino Uno descritta di seguito.
Sulla scheda sono posizionati tutti i componenti necessari affinché il µC funzioni correttamente e che possa comunicare con un computer.
In alto ed in basso si notano i connettori per i collegamenti ai sensori e agli attuatori; ricordiamo che i sensori percepiscono qualche tipo di grandezza fisica e la traducono in un segnale elettrico interpretabile da un computer, mentre un attuatore è l'opposto di un sensore, converte un segnale elettrico proveniente da un computer e lo traduce in una azione fisica. Quando non altrimenti specificato, noi faremo sempre riferimento alla scheda base, quella denominata Arduino Uno; a bordo di questa scheda si riconoscono:
14 pin I/O digitali (pin 0-13)
Questi possono essere usati per eseguire operazioni di input ed output. Come connettori di input sono usati per leggere le informazioni provenienti dai sensori mentre come output sono usati per il controllo degli attuatori. Gli input digitali possono solo leggere uno dei due valori HIGH/LOW, gli output digitali possono solo inviare uno dei due valori HIGH/LOW.
6 pin di ingresso analogico (pin 0-5)
I pin di ingresso analogico sono usati per leggere misure di tensione dai sensori analogici.
A differenza dei pin digitali, che possono distinguere solo tra due livelli differenti (HIGH e LOW) i pin di ingresso analogico possono misurare 1024 livelli differenti di voltaggio.
6 pin di uscita analogica (pin 3, 5, 6, 9 10 e 11)
Questi sono in pratica sei pin digitali che possono eseguire una terza funzione: fornire un' uscita analogica.
Come per i pin I/O digitali, si deve sempre specificare il loro compito nel codice sorgente del programma dell'applicazione che si intende eseguire.
La scheda può essere alimentata attraverso la porta USB del computer, attraverso la maggior parte dei caricatori USB oppure tramite un alimentatore esterno, possibilmente da 9V con spinotto cilindrico da 2,1 mm (con positivo centrale). Se nella presa non è collegato alcun alimentatore, la scheda si alimenta via USB, ma, non appena si collega un alimentatore di corrente, la scheda lo utilizza automaticamente. Come al solito è più sicuro disporre dell'alimentazione sulla presa rispetto all'alimentazione USB.
PROGRAMMAZIONE ARDUINO
Una volta aperto l'IDE di Arduino ci si accorge che la struttura del codice è suddivisa in due parti fondamentali: la funzione setup() e la funzione loop().
setup() viene eseguita una sola volta e serve per inizializzare variabili, per impostare lo stato dei pin, per far partire le librerie da usare e per l'impostazione delle comunicazioni seriali; essa è sempre la prima a essere eseguita dopo ogni accensione o reset della scheda.
loop() esegue ciclicamente il codice scritto al suo interno, permette quindi l'esecuzione del programma, interagendo con la scheda Arduino. Questa impostazione è data dal fatto che il dispositivo non è in grado di eseguire diversi programmi contemporaneamente (multitask) come un normale computer e dal programma principale non si può uscire. Quando si accende la scheda, il codice viene eseguito, per fermare il codice basta spegnere la scheda.
Nella parte superiore dell'editor si trovano alcuni pulsanti molto utili:
![]() |
Pulsante di Verifica, la sua funzione è di controllare se si sono commessi degli errori. |
![]() |
Pulsante di Caricamento (upload) che compila il codice scritto e lo carica sulla scheda configurata. |
![]() |
Pulsante Nuovo. Crea un nuovo sketch ogni volta che si vuole. |
![]() |
Pulsante Apri. Fa apparire un menu con tutti gli sketch già realizzati. |
![]() |
Pulsante Salva. Salva il codice appena scritto. |
![]() |
Apre il Serial Monitor che permette di controllare i dati scambiati col computer e controllare cosa avviene nel codice eseguito da Arduino. |
Il software è scritto in Java e basato su Processing e altri software open source; esso può essere utilizzato con qualsiasi scheda Arduino e funziona su Windows, Mac OS X e Linux.
Il linguaggio di programmazione è il Wiring, derivato dal C/C++; cioè uno standard dove il punto e virgola è usato come terminatore di una istruzione.
Le strutture di controllo:
if
if-else
for
switch
while
do-while
break
continue
return
goto (quest'ultima, ovviamente deprecata)
rimangono invariate. Un primo esempio di sketch è il seguente:
void setup(){
pinMode(13, OUTPUT);
}
void loop(){
digitalWrite(13, HIGH);
delay(1000);
digitalWrite(13, LOW);
delay(1000);
}
Viene fissato il pin 13 come uscite del collegamento al LED nella funzione setup().
La funzione loop() contiene la logica principale del programma, e viene usata per creare un ciclo infinito.
La logica di questa funzione, deve prima di tutto accendere il led collegato al pin 13.
Per effettuare questa operazione si utilizza il metodo digitalWrite() passando come parametri il numero di pin e la costante HIGH. In questo modo il pin fornisce in uscita 5V fino alla modifica successiva ed il led collegato al pin si accende. A questo punto il programma chiama il metodo delay() e attende 1000 millisecondi (1 secondo) senza eseguire alcuna operazione.
Durante questo intervallo di tempo il pin 13 rimane nello stato HIGH ed il led è sempre acceso.
Il led viene spento con l'istruzione digitalWrite(13, LOW); poi il programma attende altri 1000 ms prima di concludere la funzione loop().
Poi, il programma, viene avviato di nuovo ed il led torna ad accendersi.
Oltre le periferiche di uscita come il LED ci sono le periferiche di ingresso come il pulsante.
Nel seguente sketch il pulsante viene usato come interruttore per accendere e spegnere il LED stesso.
const int Switch = 2, LED = 13;
int state = 0, LEDstate=0;
void setup() {
pinMode(Switch, INPUT);
pinMode(LED, OUTPUT);
}
void loop() {
if (state == 0 && digitalRead(Switch) == HIGH) {
state = 1;
LEDstate=!LEDstate;
}
if (state == 1 && digitalRead(Switch) == LOW) {
state = 0;
}
digitalWrite(LED, LEDstate);
}
Si vede che come in altri linguaggi di programmazione la parte della dichiarazione delle variabili e delle costanti viene scritta per prima.
Viene usata la variabile state in concomitanza con il metodo digitalRead(Switch);per far commutare il led da acceso a spento (e viceversa) attraverso la variabile LEDstate che agisce sul led di uscita attraverso l'istruzione digitalWrite(LED, LEDstate);
La possibilità di scrivere il codice usando un linguaggio di medio livello come il C è molto vantaggiosa se si considera che i primi PIC potevano essere programmati solo attraverso il linguaggio assembly che spesso differiva da dispositivo a dispositivo.
Sensori e microcontrollori
In precedenza abbiamo introdotto i microcontrollori descrivendoli come oggetti tecnologici molto interessanti, evidenziando anche la loro attitudine ad interagire col mondo reale che ci circonda. Ricordiamoci che la costituzione strutturale di un μC può essere ricondotta a tre parti principali:
- Il core (nucleo) del processore : è l'elaboratore, il cervello del μC.
- La memoria di programma : formata da istruzioni, in qualche modo paragonabile alla memoria del nostro cervello.
- Gli input ed output : utilizzati per acquisire i dati esterni o per trasferire segnali verso l'esterno.
Gli input e output (I/O) sono di fatto i punti attraverso i quali i μC comunicano con il mondo esterno. I pin di input possono essere collegati a periferiche di input come:
- pulsanti (interruttori)
- potenziometri
- fotoresistenze
mentre le periferiche di output sono ad esempio:
- cicalini
- led
- motori
In genere, si utilizza il termine trasduttori (o sensori) per indicare le periferiche di input, mentre le periferiche di output vengono talvolta chiamate attuatori.
Per effettuare alcuni esempi pratici abbiamo fatto riferimento alla scheda a microcontrollore Arduino Uno descrivendone gli aspetti fondamentali, senza però, mettere in evidenza quella che può essere considerata una differenza fondamentale tra un computer ed una scheda μC; cioè l'obbligatoria presenza su quest'ultima di un convertitore analogico-digitale.
Infatti, il nucleo del μC, può comprendere solo codice binario composto da sequenze di 0 e 1. Quello che deve essere fatto capire al nucleo del μC deve essere tradotto per forza in binario, che sia la temperatura dell'acqua oppure la luminosità di una stanza.
Per questo motivo all'interno di un μC deve esserci un convertitore analogico-digitale direttamente collegato ai pin analogici A0÷A5 così come si vede sulla scheda di Arduino. Questi pin leggono dei valori di tensione analogica e la convertono in un valore digitale (numerico).
Un μC riconosce come 1 una tensione alta (HIGH) di solito 5V e come 0 una tensione bassa (LOW); in questo caso coincidente con gli 0V.
Oltre ad una memoria e ad una ALU (Arithmetic Logic Unit) nel nucleo di un microcontrollore sono installati dei registri interni (come un qualsiasi microprocessore). Essi hanno lo scopo di ospitare gli operandi che devono essere sottoposti alle operazioni aritmetiche eseguite dalla ALU. Alcuni registri possono essere collegati direttamente ai pin di I/O oppure lalla porta seriale. Ad esempio, i registri collegati ai pin di Arduino, sono di tre tipi:
- un primo registro serve ad fornire la direzione dei dati che significa input o output, nel codice può essere visto perché utilizzato nella dichiarazione pinMode();
- un secondo registro è detto PORT. Quando un pin viene configurato di output, si usa il registro PORT attraverso la funzione digitalWrite();
- un terzo registro viene chiamato registro PIN. Quando un pin è stato configurato per leggere valori, per farlo utilizza il registro PIN. Questo registro è quello che viene coinvolto nell'esecuzione dell'istruzione pinRead().
Dopo queste osservazioni, si deduce che l'abbinamento tra sensori di input e convertitore analogico-digitale rende i μC meglio predisposti all'acquisizione e all'elaborazione di dati fisici, provenienti dall'esterno, rispetto ai normali computer.
Per quanto è stato già visto negli esempi fatti in precedenza, queste istruzioni vengono usate sempre durante l'esecuzione del codice programma che, ricordiamo, è sempre strutturato in due parti fondamentali; la funzione:
void setup(){
//codice da eseguire solo una volta
}
e la successiva funzione:
void loop(){
//codice da eseguire ciclicamente
}
Simboli
Nella scrittura di uno sketch il simbolo principale è:
; (punto e virgola)
esso viene usato come terminatore di un'istruzione. Ciascuna istruzione termina con un punto e virgola. È possibile mettere anche più di una istruzione per linea a patto di separarle con il punto e virgola.
Un'altra simbologia molto importante è costituita dalle:
{ } (parentesi graffe)
Le parentesi graffe si usano per contrassegnare blocchi di istruzioni; sono particolarmente consigliate nell'esecuzione dei cicli oppure nella scrittura delle funzioni ad esempio:
void loop(){
Serial.println('hello');
}
Commenti
Si tratta di porzioni di testo che vengono ignorate dal processore ma che sono molto utili per ricordare a se stessi (e agli altri) a cosa serve una determinata parte di codice. I commenti possono essere di due tipi:
// (commenti in singola linea)
/*
...
*/ (commenti multilinea)
Come si vede i simboli usati per commentare il codice sono gli stessi usati in C, Java, JavaScript.
Costanti
Arduino comprende una serie di parole chiave predefinite con valori speciali. HIGH e LOW si usano, ad esempio, quando si vuole accendere o spegnere un pin. INPUT ed OUTPUT si usano invece per impostare un determinato pin come input oppure output. Come suggerisce il nome , le costanti, una volta fissate, non possono essere mai essere cambiate durante l'esecuzione di un programma.
Variabili
Le variabili sono aree di memoria dotate di un nome dove possono essere conservati i dati. Nel codice sorgente costituito dallo sketch, è possibile manipolare questi dati, riferendosi ad essi tramite il nome della variabile. Come suggerisce il nome , le variabili possono essere cambiate tutte le volte che si vuole durante l'esecuzione di un programma. Dato che Arduino è un tipo di controllore molto semplice, quando si dichiara una variabile si deve sempre specificare anche il tipo , cioè, la dimensione del valore che si vuole conservare. Ecco i tipo di dati disponibili:
boolean - può avere uno dei due valori : true o false;
char - contiene un solo carattere, come per esempio 'A'. Un tipo di variabile char occupa, come nel caso dei computer, 1 byte di memoria;
byte - contiene un numero compreso tra 0 e 255;
int - questo tipo di dato utilizza 2 byte per rappresentare un numero compreso tra -32.768 e +32.767; è il tipo di dato che viene usato più spesso;
unsigned int - come nel caso del tipo int, utilizza 2 byte ma il prefisso unsigned significa che non può contenere numeri negativi, quindi il suo intervallo va da 0 a 65.535;
long - la sua dimensione è doppia rispetto al tipo int, dunque occupa 4 byte e contiene numeri compresi tra -2.147.483.648 e 2.147.483.647;
unsigned long - è la versione senza numeri negativi di long, dunque può contenere numeri interi che vanno da 0 a 2.294.967.295;
float - è un tipo di dato piuttosto grande e può contenere valori in virgola mobile, un modo come un altro per indicare cifre seguite da decimali. Ciascuna variabile float occupa 4 byte;
double - numeri in virgola mobile a doppia precisione con un valore massimo pari a 1,7976931348623157×10308. Un tipo di dato molto oneroso da mantenere in memoria.
string - una serie di caratteri ASCII che si usano per conservare informazioni testuali. Le stringhe possono essere usate per inviare messaggi attraverso la porta seriale oppure per essere mostrati su un monitor LCD. La memoria usata consiste in 1 byte per carattere. La sua forma di dichiarazione è simile a quella usata in linguaggio C.
char st[]="Arduino";
in questo caso la memoria predisporrà uno spazio per 7 caratteri + uno spazio terminatore (null).
array - è un elenco di variabili cui si può accedere attraverso un indice. Si usano per creare tabelle di valori a cui si possa accedere facilmente. Ad esempio se si vuole conservare i diversi livelli di luminosità a cui è sensibile una fotoresistenza si può usare un array semplice come il seguente
int lux[6]={0,20,40,60,80,100}
come in linguaggio C, per accedere al primo elemento si usa lux[0], per accedere all'ultimo si usa lux[5].
Gli array sono l'ideale quando si desidera fare la stessa cosa su più elementi dello stesso tipo, perché si può scrivere quello che si deve fare una volta e quindi eseguire su ogni variabile dell'array semplicemente cambiando indice, ad esempio usando un ciclo.
Visibilità delle variabili
Le variabili in uno sketch Arduino hanno lo stesso tipo di visibilità usato in altri linguaggi.
Le variabili possono essere locali o globali a secondo di dove sono dichiarate. Una variabile globale è una variabile che può essere vista ed usata da ogni funzione presente nel programma.
La variabili locali sono visibili solo dalla funzione in cui sono dichiarate. Nell'ambiente Arduino, qualsiasi variabile dichiarata al di fuori di una funzione, come ad es. setup() o loop() è una variabile globale. Qualsiasi variabile dichiarata all'interno di una funzione è locale (e accessibile) solo all'interno di tale funzione.
Strutture di controllo
Arduino comprende parole chiave che controllano il flusso logico del programma. Questi costrutti di programmazione sono gli stessi già visti in altri linguaggi.
if-else: questa struttura è molto importante perché permette al dispositivo di prendere delle decisioni autonome. if deve essere seguito da una domanda specificata in forma di espressione e contenuta tra parentesi. Se l'espressione è vera, viene eseguito il blocco di codice che segue. Se è falsa viene eseguito il blocco di codice che segue l'else. Il costrutto if può essere usato anche senza la clausola else.
if(x==1){
digitalWrite(LED,HIGH);
}
for: permette di ripetere un blocco di codice per un numero specifico di volte.
for(int i=0;i<10;i++){
Serial.println("allarme!");
}
switch: è un costrutto utile perché permette di sostituire lunghi elenchi di controlli if. È importante ricordare l'istruzione break al termine di ciascun ramo case altrimenti Arduino eseguirà le istruzioni dei rami case successivi fino a raggiungere un break o la fine dello switch.
switch(val){
case 22 : digitalWrite(13,HIGH);break;
case 45 : digitalWrite(12,HIGH);break;
case 80 : digitalWrite(9,HIGH);break;
default: //eseguito se non si sono verificare le occorrenze precedenti
digitalWrite(12,LOW);
}
while: è un costrutto simile all'if, esegue un blocco di codice mentre è vera una determinata condizione. Tuttavia, if esegue il blocco una sola volta mentre while continua ad eseguire il blocco finché la condizione è vera.
//fa lampeggiare il led finchè val<128
val=analogRead(1);
while(val<128){
digitalWrite(13,HIGH); delay(100);
digitalWrite(13,HIGH); delay(100);
val=analogRead(1);
}
do-while: è come il ciclo while, ma in questo caso il codice viene eseguito subito prima che la condizione venga valutata. Questa struttura si utilizza quando si vuole che il codice all'interno del blocco venga eseguito almeno una volta prima di verificare la condizione logica.
do{
digitalWrite(13,HIGH); delay(100);
digitalWrite(13,HIGH); delay(100);
val=analogRead(1);
}while(val<128);
break: questo termine permette di uscire da un ciclo e di proseguire con l'esecuzione del codice che appare dopo il ciclo.
//fa lampeggiare il led finché val<128
do{
if(digitalRead(7)==HIGH)break;
digitalWrite(13,HIGH); delay(100);
digitalWrite(13,HIGH); delay(100);
val=analogRead(1);
}while(val<128);
continue: quando viene usato all'interno di un ciclo, continue permette di saltare il resto del codice al suo interno e costringe a ricontrollare la condizione.
for(lux=0;lux<128;lux++){ //salta i valori tra 40 e 100;
if(x>40 && x<100) continue;
analogWrite(pinPWM,lux);
delay(10);
}
continue è simile a break, ma break abbandona il ciclo , mentre continue prosegue con la successiva ripetizione del ciclo.
return - interrompe l'esecuzione di una funzione e ne restituisce il risultato
int computa(){
int temp=0;
temp=(analogRead(0)+45)/100;
return temp;
}
Gli operatori aritmetici, relazionali e logici rimangono quelli già studiati nell'informatica generale, mentre la libreria delle funzioni matematiche coincide con le stesse istruzioni viste in Java/JavaScript/C/C++ che può essere usata rispettando la stessa sintassi. Ad esempio:
y=pow(x,2);//imposta y al valore di x elevato al quadrato
Trattandosi di un dispositivo particolare bisognerà far sempre attenzione a quelle che possono essere considerate delle funzioni dedicate all'I/O specifico di Arduino.
Tra le funzioni matematiche speciali utili al funzionamento del dispositivo si possono annoverare:
constrain(x,a,b);
che restituisce il valore di x compreso tra a e b; se x<a viene restituito a.
Se x>b viene restituito b. Ad esempio:
val=constrain(analogRead(0),0,255);
che rifiuta valori maggiori si 255.
map(valore,basso1,alto1,basso2,alto2);
Mappa un valore dell'intervallo compreso fra basso1 e alto1 e lo converte in un valore compreso tra basso2 ed alto2. Ad esempio:
val=map(analogRead(0),0,1023,100,200);
converte un valore compreso tra 0 e 1023 in un valore compreso tra 100 e 200.
Funzioni di input e output
pinMode(pin, mode): riconfigura un pin digitale in modo che si comporti come un input oppure come un output.
pin(7, INPUT) //converte il pin 7 in un input
dimenticare di impostare i pin come output con pinMode() è causa comune di output difettoso.
Anche se in genere viene utilizzato nella funzione setup() , pinMode() può essere usato in un ciclo se è necessario modificare il comportamento del pin.
digitalWrite(pin, value): attiva o disattiva un pin digitale . Perché digitalWrite abbia effetto, i pin devono essere resi esplicitamente degli output usando pinMode()
digitalWrite(9,HIGH) //attiva il pin 9 a 5V
Si noti che HIGH e LOW di solito corrispondono a on ed off, rispettivamente, a seconda di come viene usato il pin. Per esempio un LED collegato tra 5V e un pin si accende quando questo pin è LOW e si spegne quando questo pin è HIGH.
int digitalRead(pin): legge lo stato di un pin di input, restituisce HIGH se al pin è applicato un voltaggio oppure LOW se non ne è applicato alcuno.
val=digitalRead(8)//inserisce il valore di pin 8 nella variabile val
int analogRead(pin): legge il voltaggio applicato ad un pin di input analogico e restituisce un numero compreso tra 0 e 1023, che rappresenta voltaggio compresi tra 0V e 5V.
val=analogRead(0);//inserisce in val il valore dell'input analogico sul pin 0
analogWrite(pin, value): cambia la frequenza PWM su uno dei pin PWM. pin può essere solo un pin che supporta PWM, cioè, uno tra i pin 3, 5, 6, 9, 10 o 11. value deve essere un numero compreso tra 0 e 255.
analogWrite(6, 128);//attenua al %50 il LED sul pin 6
shifOut(dataPin, clockPin, bitOrder, value): invia dati ad uno shift register, un dispositivo che si usa per espandere il numero di output digitali. Questo protocollo utilizza un pin per i dati ed uno per il clock. bitOrder indica l'ordinamento dei byte (meno significativo e più significativo) e value è il byte vero e proprio che deve essere inviato. Ad esempio:
shiftOut(dataPin, clockPin, LSBFIRST,255);
unsigned long pulseIn(pin, value): misura la durata di un impulso proveniente da uno degli input digitali. Questo è utile, per esempio, per leggere alcuni sensori ad infrarossi o accelerometri che inviano i loro valori sotto forma di impulsi di durata variabile.
t=pulseIn(7,HIGH); // misura il tempo in cui rimane attivo l'impulso successivo
Funzioni temporali
Arduino comprende alcune funzioni che servono a misurare il tempo trascorso e anche per mettere in pausa l'esecuzione del programma:
unsigned long millis(): restituisce il numero di millisecondi che sono passati da quando è partito lo sketch.
tempo=millis()-itempo; //conta il tempo trascorso a partire da itempo
delay(ms): mette in pausa il programma per la quantità di secondi specificata
delay(1000);// mette in pausa lo sketch per 1 secondo
delayMicroseconds(μs): mette in pausa il programma per la quantità di microsecondi specificata:
delayMicroseconds(2000);// aspetta 2 millisecondi
Numeri casuali
Se bisogna generare dei numeri random, si può usare:
randomSeed(seed);
reinizializza il generatore di numeri casuali di Arduino. Anche se la distribuzione dei numeri restituiti da random() è essenzialmente casuale, la sequenza è prevedibile. Quindi bisognerebbe reimpostare il generatore su un valore a caso. Un buon seed (seme) è un valore letto da un ingresso analogico non connesso, come un pin non connesso che riceve un rumore casuale dall'ambiente circostante :onde radio, raggi cosmici interferenze elettromagnetiche e così via.
randomSeed(analogRead(5));//genera un numero casuale basato sul rumore del pin 5
long random(max);
long random(min,max);
sono istruzioni che restituiscono un valore intero long pseudocasuale tra min e max-1 se min non viene specificato il minimo è 0.
Serial monitor
È possibile far comunicare Arduino con dei dispositivi esterni attraverso la porta USB.
Serial.begin(speed): prepara Arduino a cominciare a spedire e ricevere dati seriali. Generalmente con il monitor seriale dell'IDE si usano 9600 bit al secondo anche se sono disponibili altre velocità, in genere non superiori a 115.200 bps. Ad esempio:
Serial.begin(9600);
Serial.print(data): invia dei dati alla porta seriale. La codifica è facoltativa; se non viene fornita, i dati sono trattati come testo semplice:
Serial.print(65);// stampa 65
Serial.print(65,DEC);// stampa 40
Serial.print(65,HEX);// stampa 41
Serial.print(65,OCT);// stampa 101
Serial.print(65,BIN);// stampa 1000001
Serial.write(65);// stampa 'A' corrispondente al 65 nel codice ASCII
Serial.println(data): è uguale a Serial.print() ma aggiunge un ritorno a capo (ritorno carrello + nuova linea) come se si fosse digitato e poi premuto un invio.
int Serial.avaiable(): restituisce quanti byte non letti sono disponibili sulla porta seriale da leggere attraverso read().
Dopo che si è letto tutto il possibile con read(), Serial.avaible() restituisce 0 finchè sulla porta seriale non arrivano nuovi dati:
int Serial.read(): ricava un byte di dati seriali in arrivo, ad esempio:
int dati=Serial.read();
Serial.flush(): poiché i dati possono arrivare dalla porta seriale più rapidamente di quanto il programma sia in grado di elaborarli, Arduino conserva in un buffer tutti i dati che arrivano. Se bisogna svuotare il buffer per permettere che venga riempito di nuovi dati bisogna usare la funzione flush().
Se stiamo usando Tinkercad™ l'accesso al serial monitor può essere trovato all'interno della voce di menu "code" cliccando sopra l'icona Serial monitor:
Pagina 6 di 6