Documenti di Didattica
Documenti di Professioni
Documenti di Cultura
12
Corso C su Raspberry PI partendo da zero: il nostro primo programma
32
Corso C su Raspberry PI partendo da zero: vettori e matrici
67
Corso C su Raspberry PI partendo da zero: Introduzione alle porte di
Input
74
Corso C su Raspberry PI partendo da zero: Ottimizzazione
97
Corso C su Raspberry PI partendo da zero: Gestire i files su disco
{2
Corso C su Raspberry Pi
partendo da zero
L
a tecnologia e l’elettronica hanno seguito senza alcun legame tra loro. Le esigenze del
continui progressi. Inizialmente esistevano mercato e, soprattutto, quelle dei sistemi, hanno
due sole figure professionali legate ad esse, portato gradatamente alla fusione e all’integra-
dedicate allo sviluppo dei sistemi e delle auto- zione delle due figure. Ai giorni d’oggi non si
mazioni: può progettare un sistema elettronico senza
• Una figura di progettista elettronico, con conoscere la programmazione algoritmica.
mansioni di creatore dei prototipi; L’elettronica moderna è basata interamente su
• Una figura di programmatore informatico, dispositivi intelligenti e programmabili. A questo
con mansioni di ideatore di software. grandioso risultato si è arrivati nel corso di vari
3} Cors o C s u Raspber r y P i par t endo da zer o
IL SOFTWARE DI JESSIE
La distribuzione Jessie è abba-
stanza fornita di software. Un
computer così piccolo può esse-
re già utilizzato così com’è per
lavorare in ufficio, giocare e na-
vigare (figura 11).
Senza spendere un solo cente-
simo in più possiamo trovare la
fantastica suite LibreOffice (fi-
gura 12), composta dal Base
(database), Calc (foglio elettro-
nico), Draw (grafica vettoriale),
Impress (presentazioni), Math
(formule matematiche) e Writer
Figura 9: Caricamento del sistema operativo
(videoscrittura). Un ufficio può
tranquillamente meccanizzare le
proprie procedure informatiche con una spesa
davvero irrisoria.
verà tanto giovamento nel suo utilizzo gratuito stri programmi scritti in linguaggio C e di renderli
(la versione per PC costa parecchio...). eseguibili e funzionanti; con esso prenderemo
la massima confidenza con il Raspberry Pi, cre-
ando prototipi e programmi vari.
L’autore è a disposizione nei commenti per eventuali approfondimenti sul tema dell’Articolo.
Di seguito il link per accedere direttamente all’articolo sul Blog e partecipare alla discussione:
http://it.emcelettronica.com/corso-c-su-raspberry-pi-partendo-da-zero-introduzione
Cors o C s u Raspber r y P i par t endo da zer o {12
Corso C su Raspberry PI
partendo da zero: il nostro
primo programma
di Giovanni Di Maria
Q
ualsiasi firmware o software, a prescindere creazione di un software costituisce una vera e
dalla sua tipologia, richiede per la sua crea- propria arte.
zione una buona conoscenza di un linguag-
gio di programmazione. In questo articolo inizie- IL LISTATO SORGENTE
remo a prendere confidenza con il compilatore Il sorgente è generalmente un file di testo, visua-
presente nella distribuzione Raspian Jessie, il lizzabile e comprensibile, composto da parole
GCC, e a creare i nostri primi utili programmi. chiave. Raccoglie tutte le istruzioni logiche per
Non utilizzeremo ancora la porta GPIO e il sof- raggiungere lo scopo finale. E’ una raccolta di
tware creato svolgerà solo mansioni di calcolo piani e di strategie per trovare risultati o perse-
ed esecuzione, senza connessione alcuna alle guire obiettivi. Di seguito proponiamo una sorta
porte di I/O. di software, rappresentato tramite uno pseudo-
codice, il cui tema è il semaforo e l’automobile.
COME SI CREA UN PROGRAMMA I passi che deve rispettare l’automobilista sono
Il PC, incluso il nostro Raspberry Pi, “parla” i seguenti:
esclusivamente una lingua composta da tanti 1. All’incrocio osserva il semaforo.
“0” e “1”, ossia il linguaggio binario. A meno che 2. Se è spento, prosegui con molta pruden-
si intenda produrre il software direttamente in za.
linguaggio macchina (esempio di programma: 3. Se è rosso:
110011101110100010101...), che risulterebbe 1. Fermati e aspetta.
tremendamente astruso, complicato e incom- 4. Se è verde:
prensibile, occorre utilizzare altri mezzi. Quello 1. Prosegui con prudenza.
sicuramente più utilizzato e più diffuso è il se- 2. Se ci sono pedoni sulla strada:
guente: 1. Fermati e falli passare.
1. Si scrive il programma, come sequenza di 5. Se è giallo:
istruzioni, diciamo il lingua inglese, sotto for- 1. Rallenta e ti fermi all’incrocio.
ma di listato sorgente; 6. Ritorna al punto 1 iniziale.
2. Si dà tale listato in pasto ad un compilatore
che, dopo il controllo di validità, produce il Tale metodo di risoluzione del problema è de-
programma eseguibile. finito algoritmo. Il software deve essere scrit-
Vista così, in effetti, la procedura sembra abba- to, ovviamente, con un opportuno linguaggio
13} Cors o C s u Raspber r y P i par t endo da zer o
di programmazione. Il mondo informatico offre, dalità di interazione tra esse genera il program-
letteralmente, migliaia e migliaia di soluzioni e ma finale. I padri del C (Figura 1) furono Brian
alternative, semplici o complicati. Alcuni di essi Kernighan e Dennis Ritchie (quest’ultimo dece-
sono dedicati alla matematica, altri all’intelligen- duto da recente).
za artificiale, e così via. In assoluto i linguaggi
più utilizzati e conosciuti sono:
• Il linguaggio C;
• Il linguaggio Basic;
• Il linguaggio Pascal.
Man mano si vanno ad aggiungere
alcuni linguaggi potenti, alternativi e
semplici al tempo stesso. Per chi vo-
lesse conoscere l’elenco dei linguag-
gi, in questo link è presente una lista,
nemmeno completa, dei più famosi.
utile da effettuare con il GCC è quella di con- cissima videoscrittura, che consente di scrivere
trollarne la versione. Il compilatore è aggiornato qualsiasi frase o parola su un foglio vuoto. La
molto frequentemente ed è consigliabile dispor- distribuzione del Raspberry Pi utilizzata mette a
re sempre della versione più recente, anche se disposizione i seguenti editor di testo:
le precedenti funzionano estremamente bene. • vi (tradizionale potentissimo editor);
Allo scopo, si apra una finestra di terminale e si • nano;
digiti il comando: • pico;
• Leafpad.
gcc -v
Vista la sua grande semplicità utilizzeremo il
a cui il sistema risponderà con la videata di figu- pico.
ra 2. L’ultimo dato è, appunto, quello della rele- E’ opportuno che il lettore crei una propria car-
ase del compilatore. tella di lavoro, entro la quale egli possa memo-
rizzare i propri esperimenti e i
propri lavori, in modo da non
“sporcare” il resto del sistema
operativo. La creazione di una
directory può essere effettua-
ta in due modi distinti:
• in modo terminale, tramite
il comando mkdir;
• in modo GUI, attraverso il
File Manager.
E’ consigliabile eseguire le
operazioni sul “File System”
attraverso il terminale, che ri-
sulta sicuramente più potente
Figura 2: Versione del GCC e flessibile. Si consiglia di creare, pertanto, la
cartella “corso_C” come sottocartella di “home”,
SALVE MONDO con il comando (Figura 3):
Universalmente è noto che il primo programma
che si prova con qualsiasi linguaggio di program- sudo mkdir /home/corso_C
mazione si intitola “Salve mondo” (Hello world)
ed ha il solo e semplice scopo di visualizzare, Il comando “sudo” permette di eseguire i pro-
a video, una semplice frase. Vediamo come, in cessi e i comandi con permessi di root con i
pochi passi, sia possibile costruire da zero il li- massimi privilegi e la massima potenza. E’ una
stato e come compilarlo per renderlo eseguibile. situazione un po’ pericolosa, in quanto il posse-
L’unico “attrezzo” da utilizzare è un editor di te- dere i diritti di root potrebbe provocare, in caso
sti, ossia un programma simile ad una sempli- di errori, anche la cancellazione dell’intero file
15} Cors o C s u Raspber r y P i par t endo da zer o
system. Per alcune operazioni, comunque, è importante e utile dell’editor pico è che esso ri-
necessario avere tali permessi. Con il File Ma- esce ad effettuare la colorazione del codice in
nager è possibile controllare l’effettiva riuscita maniera contestuale al programma e alle varie
dell’operazione. E’ bene che l’utente cominci un funzioni.
po’ a familiarizzare con i comandi principali del
Linux, in quanto Raspberry Pi è basato intera- #include <stdio.h>
mente su esso. int main() {
printf("\n \n \n Salve MONDO \n \n \n");
}
Bene, siamo pronti per iniziare questa fan- Figura 4: Il nostro primo listato sorgente in C
tastica avventura. Scegliamo di lavorare attra-
verso la console dei comandi. Accediamo alla I tasti funzione più importanti dell’editor Pico
nostra cartella di lavoro, digitando il comando: sono:
• Ctrl-O, che memorizza il programma sor-
cd /home/corso_C gente;
• Ctrl-X, che chiude il programma stesso.
seguito dal tasto Invio. Si invochi, dunque, l’edi-
tor Pico, inoltrando quest’altro comando: Dopo un breve controllo del listato, possiamo
chiudere l’editor e ritornare al sistema opera-
sudo pico esempio01.c tivo. Siamo adesso pronti a compilare il listato
da noi creato. Allo scopo si digiti, al terminale,
L’estensione “.c” indica al sistema operativo che quanto segue:
stiamo andando a preparare proprio un docu-
mento che riguarda il linguaggio C. Alla compar- sudo gcc esempio01.c
sa dell’editor, molto semplice ma potente, si può
scrivere il seguente listato sorgente in C (Figu- Il compilatore crea un file eseguibile dal nome
ra 4), senza preoccuparsi, al momento, di cosa a.out. Per mandarlo in funzione è sufficiente
voglia significarne il contenuto. Un punto molto scrivere il comando:
Cors o C s u Raspber r y P i par t endo da zer o {16
fornisce risultati errati, la colpa sarà da ad- scrivendolo e compilandolo, poi proviamo il suo
debitarsi esclusivamente al programmatore, funzionamento, quindi lo commentiamo in det-
non al computer. Bene, analizziamo il “flusso” taglio. Ritorniamo, dunque, al terminale del Ra-
che la procedura dovrà seguire: spberry Pi e iniziamo la digitazione di un nuovo
1. Il PC deve chiedere la misura del raggio sorgente C, scrivendo il comando:
di un cerchio, tramite un messaggio ami-
chevole, ad esempio: “Per favore inserire sudo pico esempio02.c
la misura del raggio”;
2. Il programma, una volta che ha ricevuto, All’interno dell’editor possiamo scrivere il listato
tramite la tastiera, il dato numerico del di figura 7:
raggio, deve calcolare l’area e la circonfe-
renza della figura piana;
3. Alla fine dell’elaborazione, ovviamente, si
devono visualizzare sul monitor i relativi
risultati, altrimenti essi resterebbero se-
greti e nascosti nella memoria RAM del
computer e tutto il lavoro svolto sarebbe
inutile.
#define PI 3.141592654
#include <stdio.h>
int main() {
/*------Dichiarazione variabili-----*/
Figura 6: Alcune formule del cerchio
int raggio;
float area,circ;
Adesso proponiamo l’intero listato sorgente, /*---Richiesta dei dati----*/
Cors o C s u Raspber r y Pi par t endo da zer o {18
L’autore è a disposizione nei commenti per eventuali approfondimenti sul tema dell’Articolo.
Di seguito il link per accedere direttamente all’articolo sul Blog e partecipare alla discussione:
http://it.emcelettronica.com/corso-c-su-raspberry-pi-partendo-da-zero-il-nostro-primo-programma
21} Cors o C s u Raspber r y P i par t endo da zer o
N
ella scorsa puntata si è visto come realiz- operativo. Gli esempi, di difficoltà sempre cre-
zare un semplice programma che potesse scente, consentiranno di muovere i primi passi
“comunicare” con l’operatore. Abbiamo ap- con disinvoltura e cognizione di causa. Inoltre
preso le tecniche per visualizzare messaggi e essi avranno un carattere “tendente” all’elettro-
risultati e studiato anche i metodi per consentire nica, proprio in vista di futuri sviluppi del settore.
l’immissione di dati numerici da tastiera, dietro
richiesta da parte del PC. RIPETERE COMODAMENTE UN
Questa volta faremo molto di più. Impareremo a ALGORITMO
creare un software più “usabile” e più dinamico, Abbiamo già visto il significato e la funzione di
che sappia districarsi tra varie evenienze e con- una variabile. Si supponga, adesso, di voler vi-
dizioni. La funzione del programmatore, infat- sualizzare a video, una tabella (tabella 1), ripor-
ti, è quella di far prevedere alla propria creatura tante i valori di tensione, corrente e potenza cir-
qualsiasi tipologia di fatto, in modo da essere colanti in un circuito. Tale tabella deve essere,
#include <stdio.h>
int main() {
/*------Dichiarazione variabili-----*/
int volt,ampere,watt;
/*-----Primo calcolo------*/
volt=5;
ampere=3;
watt=volt*ampere;
printf("%2dV %2dA = %2dW\n",volt,ampere,watt);
/*-----Secondo calcolo------*/
volt=10;
ampere=6;
watt=volt*ampere;
printf("%2dV %2dA = %2dW\n",volt,ampere,watt);
/*-----Terzo calcolo------*/
volt=15;
ampere=9;
watt=volt*ampere;
printf("%2dV %2dA = %2dW\n",volt,ampere,watt);
/*-----Quarto calcolo------*/
volt=20;
ampere=12;
watt=volt*ampere;
printf("%2dV %2dA = %2dW\n",volt,ampere,watt);
/*-----Quinto calcolo------*/
volt=25;
ampere=15;
watt=volt*ampere;
printf("%2dV %2dA = %2dW\n",volt,ampere,watt);
/*-----Sesto calcolo------*/
volt=30;
ampere=18;
watt=volt*ampere;
printf("%2dV %2dA = %2dW\n",volt,ampere,watt);
/*-----Settimo calcolo------*/
volt=35;
ampere=21;
watt=volt*ampere;
printf("%2dV %2dA = %2dW\n",volt,ampere,watt);
/*-----Ottavo calcolo------*/
23} Cors o C s u Raspber r y P i par t endo da zer o
volt=40;
ampere=24;
watt=volt*ampere;
printf("%2dV %2dA = %2dW\n",volt,ampere,watt);
/*-----Nono calcolo------*/
volt=45;
ampere=27;
watt=volt*ampere;
printf("%2dV %2dA = %2dW\n",volt,ampere,watt);
/*-----Decimo calcolo------*/
volt=50;
ampere=30;
watt=volt*ampere;
printf("%2dV %2dA = %2dW\n",volt,ampere,watt);
return 0;
}
Come si vede, si tratta di uno stupidissimo pro- ciclo iterativo finito, nel quale una variabile, in
gramma che, di volta in volta, assegna alle ri- maniera automatica, ha la capacità di incremen-
spettive variabili i corretti valori di tensione e di tare il proprio valore fino a un limite prefissato.
corrente, calcola la relativa potenza e stampa Il nostro algoritmo, tradotto inizialmente in
i risultati. Funziona perfettamente, ma è scritto uno pseudocodice per aumentarne la chiarez-
in maniera non ponderata, generando un listato za, potrebbe essere il seguente:
molto lungo. E se i valori da calcolare, anziché
dieci, fossero stati cento o mille? Sicuramente
Esplora tutti i numeri "k" da 1 a 10 (1, 2, 3, 4..... 10)
non è questo l’approccio da seguire. Invitiamo, calcola volt = k*5 (quindi 5, 10, 15, 20... 50)
tuttavia, il lettore a digitare e provare il program- calcola ampere = k*3 (quindi 3, 6, 9, 12... 30)
calcola watt = volt * ampere
ma. Ragioniamo, dunque in maniera differente.
stampa volt, ampere e watt
IL CICLO ITERATIVO
Esaminando e analizzando la tabella 1 conte- Come si vede, il chilometrico listato sorgente di
nente i valori elettrici, notiamo e constatiamo tre prima si è magicamente e drasticamente ridotto
fatti: all’osso e la sua lunghezza è sempre la mede-
1. I valori della tensione sono compresi tra 5 sima anche se si allungano i limiti dei valori. I
e 50 V, con incremento di 5 V; calcoli sono eseguiti per dieci volte, automatiz-
2. I valori della corrente oscillano tra 3 e 30 zando e meccanizzando tutto il processo nume-
A, con incremento di 3 A; rico. Traducendo lo pseudocodice in linguaggio
3. I valori della potenza sono calcolati come C, otteniamo un listato compatto e breve ma
moltiplicazione tra tensione e corrente, estremamente potente ed automatico. Dopo il
secondo la legge di Ohm. codice spiegheremo, in dettaglio, ogni riga di
Bene, siamo davanti ad un classico esempio di programma.
Cors o C s u Raspber r y P i par t endo da zer o {24
Quale lavoro svolge il magico ciclo for? Esso Come avranno notato i più attenti, se lo state-
ha il compito di ripetere tante volte il codice in ment da ripetere diverse volte è uno solo, non
esso contenuto (nel nostro esempio quello rac- sono necessarie le parentesi graffe.
• Il primo parametro, k=1, indica che il valo- calcoli numerici, dunque, saranno influenzati da
re da iterare inizia da 1. Può, ovviamente, questo fatto. E’ bene, non modificare mai ma-
partire da qualsiasi altro punto; nualmente il valore del contatore all’interno del
condizione di vita del ciclo stesso. Esso Eseguendo il programma finale, il risultato vi-
k+=4.
Esaminiamo, adesso in dettaglio, la funzione
Il lettore deve capire proprio questo aspetto. “printf”, codificata in modo da visualizzare i risul-
Un ciclo iterativo, pur essendo riportato sul tati con un minimo di formattazione “elegante”.
Sembra un po’ ostica e complicata ma, come al
25} Cors o C s u Raspber r y P i par t endo da zer o
solito, basta capire la filosofia di funzionamento sipazione sempre minore, visualizza il va-
che tutto risulterà più chiaro. L’intero statement lore delle sei resistenze;
risulta il seguente: 4. Ritorna al punto 1.
Il problema ammette milioni di soluzioni. Com-
printf(“%2dV %2dA = %4dW\ binando, infatti, opportunamente resistenze di
n”,volt,ampere,watt); diverso valore ohmico, si riesce sempre a tro-
vare un numero elevato di possibilità che diano
La sottostringa %2dV serve per visualizzare un un’uscita di 8,5V. Il nostro programma ha una
numero intero (%d) riservando due spazi per lo marcia in più: non solo trova la combinazione
stesso. In coda alla stessa sarà aggiunta la let- giusta, ma nella sua frenetica ricerca, visualizza
tera “V” per esprimere il valore in Volt. Le altre le soluzioni che, via via, provocano una dissi-
variabili seguono lo stesso ragionamento. pazione sempre minore di potenza. E’ possibile
Come si vede, il ciclo “for” è estremamente po- simulare il circuito di figura 2 con il programma
tente e consente di risparmiare la scrittura di LTSpice.
tanto codice.
E ADESSO... LE CONDIZIONI
Qualsiasi linguaggio di programmazione, e a
maggior ragione il linguaggio C, è capace di
prendere delle decisioni. Si tratta di decisioni
“binarie”, per le quali sono previste una o due
alternative. Con l’implementazione delle con-
dizioni, il flusso del programma può essere ri-
direzionato secondo particolari percorsi. Ecco
un esempio molto esplicativo ed estremamente Figura 2: Partitore
affascinante, riguardante sempre l’elettronica. Il
problema è il seguente: Come prima cosa, non considerando ancora il
si deve realizzare un partitore resistivo (senza software da scrivere, occorre conoscere il me-
carico), composto da sei resistenze, capace di todo di calcolo di una tensione da un partitore
fornire, in uscita (dopo la seconda resistenza), resistivo. Il computer non lo conosce affatto, e
la tensione di 8,5V. Quella di alimentazione è tanto meno il linguaggio C. Il metodo di calcolo
di 14V. Esistono diverse tipologie di soluzioni. è il seguente:
Quella che adottiamo per l’esempio segue que- 1. Si calcola la resistenza totale equivalente,
sta strategia: sommando il valore dei singoli resistori;
1. Il computer sceglie “a caso” il valore di sei 2. Si calcola la corrente che passa nel circu-
resistenze (non commerciali ma teoriche); ito (I=V/R);
2. Calcola, con la legge di Ohm, la tensione 3. Si calcola la resistenza equivalente delle
in uscita; ultime quattro resistenze (R3, R4, R5 e
3. Se la tensione è pari a 8,5V, con una dis- R6);
Cors o C s u Raspber r y P i par t endo da zer o {26
#include <stdio.h>
#include <stdlib.h>
int main() {
/*-----Dichiarazioni variabili-----*/
float Vin,Vout;
unsigned int r1,r2,r3,r4,r5,r6;
unsigned int Rtot,Rtot4;
float corrente;
float milliwatt,maxdissipazione;
/*-----Assegnazioni iniziali-----*/
Vin=14;
maxdissipazione=10000;
/*-----Ciclo infinito-----*/
while(1) {
/*---Genera casualmente il valore di 6 resistenze-----*/
r1=rand() % 30000 + 100;
r2=rand() % 30000 + 100;
r3=rand() % 30000 + 100;
r4=rand() % 30000 + 100;
r5=rand() % 30000 + 100;
r6=rand() % 30000 + 100;
/*-----Calcola la resistenza totale-----*/
Rtot=r1+r2+r3+r4+r5+r6;
/*-----Calcola la corrente nel circuito-----*/
corrente=Vin/Rtot;
/*-----Calcola la resistenza equivalente delle ultime 4 resistenze-----*/
Rtot4=r3+r4+r5+r6;
/*-----Calcola tensione di uscita dal partitore al nodo-----*/
Vout=Rtot4*corrente;
/*-----Calcola la dissipazione totale nel circuito-----*/
milliwatt=Vin*corrente*1000;
/*--Se la tensione è 8,5V e i Watt sono meno ancora--*/
if(Vout==8.5 && milliwatt<maxdissipazione) {
maxdissipazione=milliwatt;
printf("R1=%5d R2=%5d R3=%5d R4=%5d R5=%5d R6=%5d\nVout=%1.1f mW=%f \n",r1,r2,r3,r
4,r5,r6,Vout,maxdissipazione);
printf("------------------------------\n");
}
}
return 0;
}
27} Cors o C s u Raspber r y P i par t endo da zer o
(o meglio, delle ore), si ottengono i migliori ri- positivi. A seconda del PC ospitante, il loro li-
sultati. Per bloccarne l’esecuzione è necessario mite potrebbe variare. Anche Rtot e Rtot4 sono
premere insieme i tasti <CTRL> e <C> (Figura 3). della stessa tipologia delle precedenti. Le varia-
bili che riguardano la corrente e le dissipazioni
sono tutte di tipo float. Essendo coinvolte, infat-
ti, in operazioni di divisione, il loro valore finale
potrebbe essere decimale.
Assegnazioni iniziali
In questa porzione di codice vengono assegnati
semplicemente due valori numerici ad altrettanti
variabili. Vin vale 14V e maxdissipazione vale
10000. Più avanti vedremo come è strutturato
l’algoritmo per determinare la minore dissipa-
Figura 3: Esecuzione del software zione possibile.
3. Se n è dispari sarà visualizzato il messag- sente di calcolare il resto di una divisione (senza
gio “n è DISPARI”; interessarsi al suo risultato) è “%” (segno di per-
4. Se n è zero, il programma termina, altri- centuale). E’ stato anche utilizzato nel sorgente
menti sarà richiesto un nuovo valore, fino precedente, per limitare i numeri casuali estratti.
all’eternità. L’intero listato sorgente, rispettante in toto le di-
rettive dell’analisi software, è riportato qui sotto
COME STABILIRE SE UN NUMERO È (Figura 6).
PARI
Esistono diversi metodi per raggiungere lo sco- #include <stdio.h>
po. Si tratta, ovviamente, di procedure matema- int main() {
/*-----Dichiarazioni variabili-----*/
tiche. Arriviamoci poco a poco e non serviamo
int n;
direttamente la soluzione sul piatto d’argento. /*-----Ciclo infinito-----*/
Perché 36 è pari? Perché 97 è dispari? while(1) {
printf("Inserire un numero intero: ");
Effettuiamo 36 diviso 2 (Figura 4):
scanf("%d",&n);
/*---Controlla se si digita zero---*/
if(n==0)
break;
if(n % 2==0)
printf("Il numero %d e' PARI\n\n",n);
else
printf("Il numero %d e' DISPARI\n\n",n);
}
Figura 4: Divisione return 0;
}
Come sanno tutti i bambini delle scuole elemen-
tari, tutti i numeri pari, se si dividono per due,
producono, come resto, zero.
Effettuiamo, adesso, 97 diviso 2 (Figura 5):
Figura 5: Divisione
In questo caso, tutti i numeri dispari, se divisi Figura 6: Il programma Pari e Dispari
per due, producono resto di uno. Provare per
credere (su questo concetto si basano molte DIMENSIONI DELLE VARIABILI IN
operazioni binarie sull’omonima numerazione). RAM
In linguaggio C, l’operatore aritmetico che con- La tabella 2 mostra lo spazio occupato da ogni
Cors o C s u Raspber r y P i par t endo da zer o {30
#include <stdio.h>
int main() {
char v1;
short v2; Figura 7: “Sizeof” eseguito su AMD Sempron
int v3;
long v4;
float v5;
double v6; CONCLUSIONI
long double v7;
Il lettore non si limiti a leggere, come un roman-
long long v8;
printf("Char %d\n",sizeof(v1)); zo, questi articoli. Per ottenere buoni risultati
printf("Short %d\n",sizeof(v2)); egli deve, invece, studiarli, applicarli e digerirli e
printf("Int %d\n",sizeof(v3));
questo scopo si raggiunge facendo solamente
printf("Long %d\n",sizeof(v4));
31} Cors o C s u Raspber r y P i par t endo da zer o
molta pratica. In definitiva si può affermare che l’esatta soluzione (se la conosce...) . Le moltipli-
se un programmatore riesce a risolvere gli errori cazioni richieste devono essere cinque e i fatto-
elencati dal compilatore (a causa di un errore di ri, composti da una sola cifra, possono essere
sintassi, semantico, di tipologia, ecc) allora egli generati casualmente dal programma. Alla fine,
può considerarsi molto soddisfatto e pronto ad l’elaboratore visualizzerà il numero di risposte
affrontare compiti più pesanti. esatte. Un ipotetico screenshot potrebbe essere
Gli esempi riportati in queste pagine sono quello di figura 8.
propedeutici ai prossimi, ed imminenti,
esperimenti pratici ed elettronici con il Ra-
spberry Pi.
Lascio il lettore con un piccolo esercizio, da fare
a casa. Volendo può inviare la propria soluzione
sui commenti del forum.
L’esercizio è il seguente: l’elaboratore mostrerà
all’utente una moltiplicazione, senza risultato. Figura 8: Esercizio per i lettori
Sarà l’utente a dover digitare, con la tastiera,
L’autore è a disposizione nei commenti per eventuali approfondimenti sul tema dell’Articolo.
Di seguito il link per accedere direttamente all’articolo sul Blog e partecipare alla discussione:
http://it.emcelettronica.com/corso-c-su-raspberry-pi-partendo-da-zero-cicli-e-condizioni
Cors o C s u Raspber r y P i par t endo da zer o {32
N
ella scorsa puntata si è visto come realizza- formano un byte e può gestire ben 256 stati di-
re alcuni programmi dotati di “vita propria”, versi di commutazione.
capaci di prendere decisioni ed effettua- Si assume anche che il prototipo preveda i se-
re compiti ripetitivi. Questa volta tratteremo un guenti giochi di luce, mostrati nella tabella di fi-
argomento estremamente importante, che non gura 1.
può essere tralasciato. Studieremo, infatti, i vet-
tori e le matrici. Si tratta di potentissimi mez-
zi che consentono di ottimizzare al massimo i
programmi e gestire, con una logica chiara ed
efficiente, gli algoritmi. Anche in questo caso, gli
esempi saranno di difficoltà sempre crescente, e
verteranno spesso sull’elettronica. Come al so-
lito, saranno proposti inizialmente degli esempi
semplici ma concettualmente errati, senza uti-
lizzare le argomentazioni trattate, proprio per far
capire il vantaggio che esse forniscono nel loro
utilizzo. Un po’ come dire: “Prima togli il chiodo
dal legno con le mani, poi ti insegno ad usare la
pinza, vedrai come cambia la musica...”.
/*---------Azzeramento valori-------*/
tot_re = 0;
tot_donne = 0;
tot_torri = 0;
Figura 5: La scacchiera da rappresentare in C tot_alfieri = 0;
tot_cavalli = 0;
tot_pedoni = 0;
La prima operazione da compiere è, dunque,
quella di rappresentare la scacchiera in linguag- /*---------Visualizza scacchiera-------*/
for(x=0;x<8;x++) {
gio C. Per fa questo, oltre alla dichiarazione op-
for(y=0;y<8;y++)
portuna di una matrice 8x8, occorre assegna- printf("%4d",scacchiera[x][y]);
re alle caselle interessate, i valori codificati dei printf("\n");
}
pezzi. Il metodo di numerazione delle caselle
può essere personalizzato a piacere, purché /*---------Conta pezzi-------*/
il programmatore si riferisca sempre ad esso. for(x=0;x<8;x++)
Cors o C s u Raspber r y P i par t endo da zer o {38
Visualizza scacchiera
Si tratta di un doppio ciclo nidificato. La pre-
senza di due indici assicura lo scorrimento
dell’intera matrice, in larghezza e altezza. Nota-
re la presenza opportuna di punti e virgola e di
parentesi graffe. Il ciclo, dopo aver visualizzato
otto valori di riga, esegue un ritorno a capo, gra-
zie alla funzione printf(“\n”).
Figura 7: Esecuzione del programma della scacchiera
Conta pezzi
Esaminiamo in dettaglio le funzionalità del pro- Questa è la parte decisionale più importante del
gramma. listato. In un altro doppio ciclo nidificato, il pro-
39} Cors o C s u Raspber r y P i par t endo da zer o
L’autore è a disposizione nei commenti per eventuali approfondimenti sul tema dell’Articolo.
Di seguito il link per accedere direttamente all’articolo sul Blog e partecipare alla discussione:
http://it.emcelettronica.com/corso-c-su-raspberry-pi-partendo-da-zero-vettori-e-matrici
Cors o C s u Raspber r y P i par t endo da zer o {40
INTRODUZIONE comando.
L
’occhio, si sa, vuole sempre la sua parte.
Non basta che il programma da noi prodotto IL CONTROLLO DEL VIDEO
sia estremamente efficiente, potente e velo- Sino ad ora, per visualizzare dei risultati o dei
ce. Se esso non dispone di una adeguata inter- messaggi, abbiamo utilizzato in modo abbon-
faccia grafica, magari un po’ più amichevole, dif- dante la funzione printf(). Essa, ricordiamolo, ha
ficilmente l’utente finale amerà il nostro lavoro. lo scopo di inoltrare a video qualsiasi sequen-
Questo discorso, a maggior ragione, vale per i za di dati e di informazioni. Per il suo utilizzo
software scritti con il linguaggio C e il compilato- si vedano le puntate precedenti del presente
re GCC. L’efficienza è, in questo caso, massima corso. Tale funzione si limita ad accodare sul
ma non si può dire altrettanto dell’aspetto grafi- video, per la precisione verso lo standard ou-
co ed estetico. Sono molte, infatti, le caratteristi- tput (STDOUT), le informazioni, che il video
che che, normalmente, non sono presenti in un gestisce in maniera “discendente”. In altre pa-
programma scritto in linguaggio C: role, ogni chiamata alla funzione inoltra l’infor-
• La possibilità della cancellazione e della mazione a video, scrivendola una sotto l’altra.
pulizia dello schermo. Se si raggiunge il limite inferiore del monitor, la
• La gestione del cursore e posizionamento videata scorre verso l’alto, effettuando il cosid-
libero dei dati sul video. detto “scrolling-up” ed eliminando le informazio-
• La facoltà di colorare a piacimento i mes- ni dalla sommità dello schermo. Un eventuale
saggi e le scritte sul video. buffer video non sarebbe capace di contenere
E’ sufficiente implementare questi tre concetti eventuali numerose informazioni. Il seguente
nei propri lavori, per ottenere un prodotto finale esempio chiarisce meglio il concetto. Si abbia il
molto più amichevole ed usabile. In ogni caso seguente listato:
stiamo parlando sempre di un ambiente “a con-
sole” ed in modalità testo, che è ben lungi da #include "stdio.h"
qualsiasi paragone con le interfacce GUI. Ma int main() {
int k;
per piccoli sistemi di domotica, automatizzazio-
for(k=1;k<=1000;k++)
ne e controllo, già queste peculiarità sono più printf("%4d %7d %10d\n",k,k*k,k*k*k);
che sufficienti. Gli stessi concetti sono prope- return 0;
}
deutici per nostri futuri esperimenti con le porte
di I/O, con le quali interagiremo utilizzando gra-
devoli interfacce a video ed amichevoli menù di Si tratta di un conteggio nel quale sono visua-
41} Cors o C s u Raspber r y Pi par t endo da zer o
lizzati, nell’ordine, un numero progressivo (da 1 dec 27 o ottale 033) a cui seguono altri caratteri
a 1000), il suo quadrato e il suo cubo. L’esecu- di controllo. Tale sequenza può essere tranquil-
zione causa una lunghissima sequenza di risul- lamente data in pasto alla funzione printf().
tati che, oltre a risultare graficamente non gra-
devole, rende impossibile la consultazione dei SINTASSI
precedenti valori, ormai scomparsi verso l’alto. Una sequenza di controllo può essere scritta in
La figura 1 mostra il risultato dell’esecuzione del diversi modi. Vi sono, infatti, tante soluzioni al-
programma. ternative per ottenere il medesimo risultato. Ma
occorre obbligatoriamente seguire una regola
fondamentale: la sequenza deve iniziare con il
carattere di Escape (ESC) che, come detto pri-
ma, corrisponde al codice 27 (decimale) o 33
(ottale) della famosa tabella Ascii. Pertanto, le
seguenti soluzioni sono tutte valide:
printf("\033 comando");
oppure
printf("%c comando",27);
re il cursore e far scomparire, verso l’alto, i dati e i risultati. Tutto questo si traduce in un enorme
precedenti. La seguente funzione persegue tale miglioramento dell’aspetto grafico del proprio
obiettivo: programma. Lo schermo di console è logica-
mente suddiviso in righe e colonne. Ogni carat-
printf("\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n"); tere può essere posizionato in ogni cella, dando
luogo alle tradizionali visualizzazioni. La figura 2
Utilizzando, invece, le sequenze di Escape, una mostra una mappa video in modalità carattere.
soluzione più elegante e comoda potrebbe es- Normalmente essa è composta da 25 righe e
sere la seguente: da 80 colonne, costituendo una griglia di 2000
celle, ma con i moderni sistemi operativi tale ca-
printf("\033[2J"); pienza può essere aumentata.
Per posizionare il cursore in una qualsiasi loca-
che, inserita all’inizio del listato, provvede a zione a video è sufficiente invocare la seguente
cancellare tutto il contenuto del video. In alcuni funzione:
ambienti il cursore è automaticamente posi-
zionato in posizione “home” (coordinate 0,0), printf("\033[<R>;<C>H");
in altri, purtroppo, questa operazione dovrebbe
essere eseguita manualmente. dove:
• \033[ inizia la sequenza di Escape;
POSIZIONAMENTO DEL CURSORE • R rappresenta il numero di riga dello
Questa possibilità è realmente grandiosa, poi- schermo;
ché il programmatore può decidere la posizione • C è il numero della colonna;
fisica del video su cui scrivere i propri messaggi • H è il comando vero e proprio.
Qualsiasi altra suc-
cessiva chiamata
della funziona printf()
causa la visualizza-
zione del testo nella
posizione determina-
ta prima. Il seguente
esempio completo
mostra ed evidenzia
l’enorme possibilità
di miglioramento vi-
deo con il posiziona-
mento del cursore. Si
tratta di una masche-
ra di input, situata
Figura 2: La mappa video sulla zona superiore
43} Cors o C s u Raspber r y P i par t endo da zer o
#include "stdio.h"
int main() {
/*------Dichiarazione variabili------*/
char cognome[20],nome[20],email[40];
int eta;
/*------Intestazione------*/
printf("\033[2J"); /* Cancella video */
printf("\033[2;20H"); /* Cursore a 2,20 */
printf("====================");
printf("\033[3;22H"); /* Cursore a 3,22 */
printf("Gestione CLIENTI");
/*------Visualizza etichette------*/
printf("\033[5;5H");
printf("Cognome");
printf("\033[7;5H");
printf("Nome");
printf("\033[9;5H");
printf("Eta'");
printf("\033[11;5H");
printf("E-mail");
/*------Input dati------*/
printf("\033[5;15H");
scanf("%s",cognome);
printf("\033[7;15H");
scanf("%s",nome);
printf("\033[9;15H");
scanf("%d",&eta);
printf("\033[11;15H");
scanf("%s",email);
/*------Riepilogo------*/
printf("\033[13;25H Riepilogo");
printf("\033[15;10H Cognome: %s",cognome);
printf("\033[16;10H Nome: %s",nome);
printf("\033[17;10H Eta': %d",eta);
printf("\033[18;10H Email: %s",email);
printf("\033[20;1H");
return 0;
}
Cors o C s u Raspber r y P i par t endo da zer o {44
#include "stdio.h"
void cls() {
printf("\033[2J");
}
int main() {
Cors o C s u Raspber r y P i par t endo da zer o {46
char cognome[20],nome[20],email[40];
int eta;
cls();
PrintStrAt(2,20,"====================");
PrintStrAt(3,22,"Gestione CLIENTI");
PrintStrAt(5,5,"Cognome");
PrintStrAt(7,5,"Nome");
PrintStrAt(9,5,"Eta'");
PrintStrAt(11,5,"E-mail");
InputStrAt(5,15,cognome);
InputStrAt(7,15,nome);
InputIntAt(9,15,&eta);
InputStrAt(11,15,email);
PrintStrAt(13,25,"Riepilogo");
PrintStrAt(15,10,cognome);
PrintStrAt(16,10,nome);
PrintIntAt(17,10,eta);
PrintStrAt(18,10,email);
CursorAt(20,1);
return 0;
}
Figura 4: Il box
Figura 5: Esempio di utilizzo dei box
#include "stdio.h"
void cls() {
printf("\033[2J");
}
int main() {
cls();
box(2,4,5,28);
PrintStrAt(3,5,"Tensione di Ingresso");
PrintStrAt(4,13,"52 Volt");
box(2,35,5,59);
PrintStrAt(3,36,"Tensione di Uscita");
PrintStrAt(4,44,"41 Volt");
box(8,4,11,59);
PrintStrAt(9,6,"Orari di accensione motori");
PrintStrAt(10,6,"Ore 11:00 - Ore 14:00 - Ore 18:00");
PrintStrAt(18,0,"");
return 0;
}
L’autore è a disposizione nei commenti per eventuali approfondimenti sul tema dell’Articolo.
Di seguito il link per accedere direttamente all’articolo sul Blog e partecipare alla discussione:
http://it.emcelettronica.com/corso-c-su-raspberry-pi-partendo-da-zero-il-controllo-del-video
Cors o C s u Raspber r y P i par t endo da zer o {50
B
enché il linguaggio C permetta, in teoria, di
scrivere qualsiasi programma senza punta- Un puntatore è, dunque, una variabile che con-
tori, il loro utilizzo permette di sfruttare a pie- tiene un indirizzo di memoria. Al momento, il
no la potenza del linguaggio e del calcolatore. suo utilizzo pratico non balza subito agli occhi
C’è una falsa credenza sulla difficoltà dell’argo- ma vedremo, nel proseguo della lezione, come
mento. Se studiati attentamente e in maniera del suo apporto di aiuto non se ne potrà più fare
approfondita, la loro implementazione risul- a meno. Dobbiamo immaginare la memoria del
terà indolore. Purtroppo, se da un lato è facile computer come un gigantesco array, con un
capire la filosofia di funzionamento dei puntato- indirizzo specifico per ogni cella (e in effetti è
ri, dall’altro è altrettanto facile commettere errori così). A questo link potete trovare un’altra vali-
nel loro utilizzo. Spesso l’errore è così impercet- da documentazione. Per conoscere l’indirizzo di
tibile e nascosto che anche i più bravi program- memoria nel quale è memorizzata una variabile
matori fanno fatica a scovarlo. si usa l’operatore “&”. L’esempio successivo ci
dà l’occasione per il primo approccio ai punta-
IL PUNTATORE IN BREVE tori.
Per capire il concetto di puntatore dobbiamo sa-
pere cosa è l’indirizzo di memoria. Esso è sem- PRIMO ESEMPIO
plicemente un numero (per il momento generi- In questo primo esempio, un programma dichia-
co) che contraddistingue una particolare cella ra quattro variabili di diversa tipologia (e quindi
di memoria. Qualche lezione addietro abbiamo di differente lunghezza) assegnando dei valori,
studiato come le variabili occupino un certo spa- come segue:
zio in RAM, e nella maggior parte dei computer, • char figli = 5;
esse sono stanziati nel seguente modo: • char eta = 27;
• Char: 1 byte; • int velocita = 123;
• Short: 2 bytes: • float peso = 93.3;
• Int: 4 bytes;
• Long: 4 bytes; Si vuole sapere quanti bytes occupano le va-
• Float: 4 bytes; riabili, in memoria, e in quali locazioni di RAM
• Double: 8 bytes; esso sono memorizzate (anche se quest’ultima
• Long Double: 12 bytes; richiesta non ha delle implicazioni pratiche e uti-
• Long long: 8 bytes; li). Il seguente listato risolve il problema.
51} Cors o C s u Raspber r y P i par t endo da zer o
#include <stdio.h>
int main() {
char figli = 5;
char eta = 27;
int velocita = 123;
float peso = 93.3;
double coefficente = 3.456E23;
• La variabile “eta” occupa 1 byte ed è allo- contenuto è l’indirizzo di un’altra variabile, ossia
• La variabile “velocita” occupa 4 bytes ed è tatore si dichiara come le altre variabili, con
return 0;
VETTORI E PUNTATORI
}
Gli array e i puntatori hanno uno stretto legame.
Abbiamo visto, nelle scorse puntate, che la di-
chiarazione:
int vettore[5];
vettore[2]=77;
L’assegnazione multipla si risolve con l’adozio- Il programma alloca in memoria due vettori, di
ne di un ciclo iterativo, come ad esempio: tipo short int. Ogni elemento occupa, pertanto,
due bytes. Quindi, essi sono “riempiti” di valori
for(k=1;k<=10;k++) numerici, elemento per elemento. Infine sono
vettore[k]=123; visualizzati a video le seguenti informazioni:
• Gli indirizzi di partenza dei due vettori.
Che rapporto esiste tra i vettori e i puntatori? • L’occupazione di ogni elemento dei due
Un primo legame è dato dal fatto che il singolo vettori.
nome dell’array costituisce, di fatto, l’indirizzo • Gli indirizzi e il relativo contenuto di ogni
iniziale del vettore stesso. Ossia, la variabile elemento.
vettore, senza la specifica di un indice tra pa- La mappa di memoria e l’esecuzione del pro-
rentesi quadre, contiene la locazione di memo- gramma mostra un fatto curioso: benché il vet-
ria iniziale da cui inizia la serie di informazioni tore2 sia stato dichiarato e inizializzato dopo
in esso memorizzate. Il seguente esempio è il vettore1, in memoria si trova prima. Questa
estremamente istruttivo. Studiamolo bene, dan- scelta è fatta dal compilatore. Per la precisione,
do un’occhiata anche alla figura 6, mostrante la gli indirizzi di partenza di entrambi i vettori sono:
mappa di memoria dei due vettori utilizati, e la • Per il vettore1: 0022FEE6;
figura 7 come risultato dell’output dell’esecuzio- • Per il vettore2: 0022FEE0.
ne del programma. Analizzatelo riga per riga,
cercando di comprenderne al massimo il fun- Inoltre, il nome del vettore corrisponde al pun-
zionamento. tatore al primo elemento dell’array. E’ possibile
#include <stdio.h>
int main () {
short int vettore1[5],vettore2[3];
/*-----Assegnazioni-----*/
vettore1[0]=33;
vettore1[1]=55;
vettore1[2]=77;
vettore1[3]=88;
vettore1[4]=99;
vettore2[0]=222;
vettore2[1]=444;
vettore2[2]=777;
/*-----Visualizzazioni-----*/
printf("L'indirizzo di partenza del vettore1 e' %p\n",vettore1);
printf("L'indirizzo di partenza del vettore2 e' %p\n",vettore2);
printf("\n");
printf("Lunghezza in byte di ogni elemendo: %d\n",sizeof(vettore1[2]));
printf("\n");
printf("L'indirizzo di vettore1[0] e' %p. Contenuto: %d \n",&vettore1[0],vettore1[0]);
printf("L'indirizzo di vettore1[1] e' %p. Contenuto: %d \n",&vettore1[1],vettore1[1]);
printf("L'indirizzo di vettore1[2] e' %p. Contenuto: %d \n",&vettore1[2],vettore1[2]);
printf("L'indirizzo di vettore1[3] e' %p. Contenuto: %d \n",&vettore1[3],vettore1[3]);
printf("L'indirizzo di vettore1[4] e' %p. Contenuto: %d \n",&vettore1[4],vettore1[4]);
Cors o C s u Raspber r y P i par t endo da zer o {56
printf("\n");
printf("L'indirizzo di vettore2[0] e' %p. Contenuto: %d \n",&vettore2[0],vettore2[0]);
printf("L'indirizzo di vettore2[1] e' %p. Contenuto: %d \n",&vettore2[1],vettore2[1]);
printf("L'indirizzo di vettore2[2] e' %p. Contenuto: %d \n",&vettore2[2],vettore2[2]);
}
colato).
CONCLUSIONI
Un puntatore ad una variabile è, quindi, un in-
dirizzo in memoria. La grandezza del puntato-
re è sempre la stessa, per qualsiasi tipologia di
variabile su cui punti. L’uso dei puntatori, in
effetti, fa un po’ paura e, almeno inizialmen-
te, il programmatore può imbattersi in errori
che provocano il blocco della CPU. Benché la
programmazione con i puntatori non sia obbliga-
toria, spesso essa è utile per migliorare di molto
pertanto accedere ai singoli elementi sia con
Figura 6: Allocazione dei vettori in RAM le prestazioni e l’efficienza dei programmi. Inol-
tre, il loro utilizzo intensivo con strutture, array,
liste e funzioni può
rendere l’esecuzione
del proprio software,
veloce quanto quelli
scritti in Assembler
(o Assembly). Come
detto prima, un gran-
de vantaggio del loro
uso è la possibilità di
scrivere una funzio-
ne che modifichi, in
cedura del programma evolve logicamente. nella programmazione. Sapendo, come detto in
Ci sarebbero ancora centinaia di argomenti su precedenza, che l’operatore * fornisce il valore
cui discutere, come l’aritmetica sui puntatori, i contenuto all’indirizzo specificato e il simbolo &
puntatori di puntatori, i puntatori a strutture e ritorna l’indirizzo della variabile che segue, si
gli array di puntatori, ma è meglio affrontare la vuol sapere il risultato del seguente program-
materia a fuoco lento, specialmente questa, che ma, eseguito solo mentalmente e non effetti-
è alquanto critica e difficoltosa. Terminiamo la vamente compilandolo (riferitevi soprattutto al
lezione con un esempio divertente, per far com- contenuto della funzione printf). Alla prossima
prendere la flessibilità del C e la sua elasticità puntata.
#include <stdio.h>
int main () {
int k,*p;
k=100;
p=&k;
printf("%p\n",&*&*&*p);
}
L’autore è a disposizione nei commenti per eventuali approfondimenti sul tema dell’Articolo.
Di seguito il link per accedere direttamente all’articolo sul Blog e partecipare alla discussione:
http://it.emcelettronica.com/corso-c-su-raspberry-pi-partendo-da-zero-i-puntatori
Cors o C s u Raspber r y P i par t endo da zer o {58
INTRODUZIONE
I
l Raspberry Pi è un potente computer con una
marcia in più: la sua piastra madre è dotata di
alcune porte elettroniche di I/O da poter colle-
gare a dispositivi esterni come LED, transistor,
sensori ed altro. Il suo interfacciamento con il
mondo esterno è, dunque, assicurato e le sue
potenzialità di utilizzo sono pressoché infinite.
Pensate un attimo ad un microcontrollore: si
può programmare, è dotato di alcune porte di
I/O ma manca la gestione di un sistema operati-
vo e non può essere collegato normalmente ad
un monitor o TV. Tutte queste lacune, a prima
vista superabili, sono egregiamente risolte dal
nostro gioiello, oggetto del presente corso.
LE PORTE GPIO
Si tratta di un insieme di porte presenti sul Ra-
spberry Pi che fanno capo ad un connettore ma-
schio. GPIO è l’acronimo di General Purpose
I/O. La sua identificazione è semplice, in quanto Figura 1: La porta GPIO (Model B)
Può anche servire per alimentare il Ra- anche il primo circuito elettrico comandato da
spberry Pi; una logica algoritmica vuole un diodo LED ac-
• Il pin 6 costituisce la massa comune del cendersi e spegnersi. Un esempio estremamen-
sistema. te semplice ed immediato ma che racchiude in
sé diversi concetti, come:
La porta GPIO dispone, in totale, di diciassette • schema di collegamento con LED e resi-
contatti anche se quelle direttamente utilizzabili, stenza;
quali input e output digitali, sono otto. Esse la- • legge di Ohm;
vorano con una tensione di 3.3 Volt, sia come • configurazione e funzionalità delle porte
ingresso che uscita. In base alla loro numera- di I/O;
zione sul bus, esse posseggono i seguenti nomi: • ciclo infinito;
• GPIO0 • temporizzazione.
• GPIO1
• GPIO4 Come si vede, si tratta di concetti chiave che
• GPIO14 saranno presenti, sempre, in tutti i propri pro-
• GPIO15 grammi e progetti.
• GPIO17
• GPIO18 GESTIONE DI UNA PORTA DI I/O
• GPIO21 La gestione di una porta del Raspberry Pi è un
• GPIO22 tantino più complicata rispetto a quella di un mi-
• GPIO23 crocontrollore. In questo contesto, infatti, ab-
• GPIO24 biamo a che fare anche con un filesystem,
• GPIO10 oltre che una memoria RAM. A grandi linee, le
• GPIO9 fasi che dobbiamo seguire per lavorare con le
• GPIO25 porte sono le seguenti:
• GPIO11 1. Creare fisicamente la porta di I/O su fi-
• GPIO8 lesystem.
• GPIO7 2. Definirne la direzione (ingresso o uscita).
3. Scrivere un valore su tale porta.
Vedremo adesso come si configurano le porte 4. Rilasciarla a fine lavoro.
per l’output di un segnale digitale, come effet-
tuare un collegamento elettrico di un diodo led Si tratta delle semplici operazioni che vanno
su essi e come scrivere un adeguato software. compiute su dei files in alcune cartelle opportu-
Inizieremo, dunque, da un classico e semplicis- ne. Vediamo come procedere.
simo esempio pratico.
CREAZIONE DELLA PORTA
UN DIODO LED LAMPEGGIANTE Per accedere alle porte elettriche del RPi oc-
Così come il primo software creato prevede la corre creare un “collegamento” di riferimento sul
visualizzazione del messaggio “Hello World!”, filesystem. Ciò si effettua scrivendo, solo una
Cors o C s u Raspber r y P i par t endo da zer o {60
IL PROGRAMMA
Bene, apprese tutte le tecniche di realizzazio- Si inizia con la definizione, da parte del prepro-
ne a livello elettronico ed informatico, possiamo cessore, della costante RITARDO, che ha la
procedere alla scrittura del codice sorgente, in funzione di implementare un limite massimo per
linguaggio C. Il programma deve prevedere il l’applicazione di una temporizzazione grezza.
lampeggio del diodo LED per dieci volte, quindi Quindi si passa alla inclusione dei prototipi del-
termina la sua esecuzione. Esso è ampiamen- le funzioni di input e output (stdio.h). Nel corpo
te commentato e facile da capire. Proponiamo della funzione main troviamo subito le dichia-
sotto il listato per poi analizzarne i punti salienti. razioni di tre variabili. Esse hanno le seguenti
funzionalità:
#define RITARDO 30000000
#include "stdio.h" • handle: è un puntatore a file;
int main() { • k: serve per contare i dieci lampeggi;
FILE *handle; • t: esegui il ciclo di ritardo.
int k;
long t;
/*------Crea porta GPIO4------*/ Il programma prosegue, dunque, con la vera e
handle=fopen("/sys/class/gpio/export","w"); propria gestione delle porte di I/O, come abbia-
fprintf(handle,"4");
fclose(handle); mo visto in precedenza. Si crea la porta GPIO4,
/*-----GPIO4 in uscita------*/ grazie alla scrittura del valore “4” nel file /sys/
handle=fopen("/sys/class/gpio/gpio4/ class/gpio/export. Poi la si configura come usci-
direction","w");
fprintf(handle,"out"); ta, scrivendo nel file /sys/class/gpio/gpio4/di-
fclose(handle); rection il valore “out”. Quindi un ciclo iterativo,
Cors o C s u Raspber r y Pi par t endo da zer o {62
./a.out
sudo ./a.out
è un optional. Teoricamente
si può scrivere un software
implementandolo semplice-
mente in un file batch.
La figura 9 illustra
il cablaggio dell’in-
tero dispositivo.
Figura 7: Caratteristiche del transistor BD243C.
Nel collegare il Raspberry Pi a dispositivi di po-
tenza, e comunque funzionanti ad una tensione
SCHEMA ELETTRICO
nominale maggiore, occorre sempre prestare la
In pratica, lo schema elettrico è equivalente al
massima prudenza, al fine di non danneggiarli.
precedente visto prima. La
differenza consiste nell’ag-
giunta del transistor, la cui
base è collegata alla porta di
I/O del Raspberry Pi, tramite
una resistenza di limitazione,
e dal carico applicato sul col-
lettore dello stesso compo-
nente (lampada da 12V, 2A,
24W). Può essere osservato
in figura 8. La porta GPIO4 è
abilitata come uscita logica e Figura 8: Schema elettrico.
65} Cors o C s u Raspber r y P i par t endo da zer o
IL PROGRAMMA
Il listato è estremamente sem-
plice ed è, in pratica, una va- Figura 10: Esecuzione del software per il comando di una lampada a 12V.
riazione sul tema del prece-
dente esempio. Tutta l’esecuzione del codice CONCLUSIONI
è racchiuso in un loop infinito, in modo da ri- Dopo aver scritto il codice, vi assicuro che la
proporre il menù iniziale dopo aver effettuato le soddisfazione nell’accendere una lampada
scelte. Dopo la visualizzazione delle opzioni, il con la tastiera è davvero tanta. Soprattutto se
programma si arresta, in attesa della decisione si pensa che un semplice comando impartito
da parte dell’utente. Se egli preme il tasto “1”, dall’esterno potrebbe azionare apparecchiature
seguito da <Invio>, la lampadina si accende. Se ben più complesse, come motori, monitor, auto-
preme il tasto “2” la lampadina si spegne. Se, in- mazioni, ecc. Nell’ottimizzare il codice, è possi-
fine, preme lo “0”, il programma termina, ponen- bile prototipare le funzioni di accensione e spe-
do a livello logico basso la porta e concludendo gnimento, in modo che il listato risulti più snello
l’esecuzione del software. Questo esempio, an- e leggibile. Alla prossima puntata, con lo studio
che se molto semplice, apre notevoli possibilità delle porte di ingresso del Raspberry Pi.
Cors o C s u Raspber r y P i par t endo da zer o {66
#include "stdio.h"
#include "stdlib.h"
int main() {
FILE *handle;
int scelta;
/*------Crea porta GPIO4------*/
handle=fopen("/sys/class/gpio/export","w");
fprintf(handle,"4");
fclose(handle);
/*-----GPIO4 in uscita------*/
handle=fopen("/sys/class/gpio/gpio4/direction","w");
fprintf(handle,"out");
fclose(handle);
/*----Inizio ciclo----*/
while (1) {
system("clear");
printf(" Menu' Scelta\n\n");
printf(" 1) Accendi lampada\n");
printf(" 2) Spegni lampada\n");
printf(" 0) Fine programma\n\n");
printf(" Scelta: ");
scanf("%d",&scelta);
/*------Accende diodo Led-----*/
if(scelta==1) {
handle=fopen("/sys/class/gpio/gpio4/value","w");
fprintf(handle,"1");
fclose(handle);
}
/*------------Spegne diodo Led------*/
if(scelta==2) {
handle=fopen("/sys/class/gpio/gpio4/value","w");
fprintf(handle,"0");
fclose(handle);
}
/*----Rilascia porta GPIO4------*/
if(scelta==0) {
handle=fopen("/sys/class/gpio/unexport","w");
fprintf(handle,"4");
fclose(handle);
system("clear");
printf(" Bye bye\n");
break;
}
}
return 0;
}
L’autore è a disposizione nei commenti per eventuali approfondimenti sul tema dell’Articolo.
Di seguito il link per accedere direttamente all’articolo sul Blog e partecipare alla discussione:
http://it.emcelettronica.com/corso-c-su-raspberry-pi-partendo-da-zero-introduzione-alle-porte-di-
output
67} Cors o C s u Raspber r y P i par t endo da zer o
INTRODUZIONE • GPIO9
I
l Raspberry Pi è dotato, come abbiamo potuto • GPIO11
vedere, di parecchie porte digitali. Esse pos- • GPIO14
sono fungere da uscita o da ingresso. Ad una • GPIO15
porta in input si possono applicare solamente • GPIO18
due diversi potenziali: quello “alto” (chiamato • GPIO23
anche “vero” o “1” o “true”), corrispondente a • GPIO24
3,3V e quello basso (chiamato anche “falso”, o • GPIO25
“0” o “false”), corrispondente a 0V. Occorre pre- • GPIO8
stare attenzione a rispettare questi valori, pena • GPIO7
la distruzione del dispositivo. Una tensione col-
locata a metà quadrante (ad esempio 1,5V) po- GESTIONE DELLA PORTA DI INPUT
trebbe essere riconosciuta con difficoltà dal Rpi Le operazioni per la configurazione di una porta
e il relativo stato non identificato correttamente. in ingresso (vedi figura 2) sono le stesse per la
Quindi, ricordate sempre di utilizzare due ten- porta di uscita: cambia solo il parametro di flus-
sioni ben precise e decise: 0V e 3.3V. In altri so (“in” oppure “out”) che attesta il suo funziona-
sistemi questi valori potrebbero variare, come mento in ingresso o in uscita. In pratica si può
nel caso dei microcontrollori Pic, che adottano i decidere se un piedino deve erogare tensio-
livelli 0V e 5V per rappresentare, rispettivamen- ne oppure riceverla dall’esterno. Le fasi da
te, il livello logico basso e alto. Per maggiori in- seguire sono le seguenti:
formazioni sull’elettronica digitale si può consul- • creazione della porta;
tare questo corso. • definizione del flusso dei dati (ingresso o
Come detto nella scorsa puntata, il nostro siste- uscita);
ma dispone di ben 17 porte utilizzabili (vedi figu- • lettura della porta;
ra 1) sia come ingresso che uscita: • distruzione della porta a fine lavoro.
• GPIO1
• GPIO4 CREAZIONE DELLA PORTA
• GPIO17 L’operazione si deve eseguire all’inizio, una
• GPIO21 volta sola. Come abbiamo fatto per l’output, si
• GPIO22 deve memorizzare il numero della porta GPIO
• GPIO10 desiderata, dentro il seguente file:
Cors o C s u Raspber r y P i par t endo da zer o {68
IL PROGRAMMA IN
C
Il risultato del software,
prodotto in linguaggio C,
è visibile in figura 6. L’in-
terfaccia utente è ridotta
all’osso ma, al momento,
è la didattica e la logica
del problema che ci inte-
Figura 6: Esecuzione del programma “Contapezzi”.
ressa, non l’estetica fina-
le. Il listato, presentato di
Lo schema elettrico funziona secondo la se-
seguito nel listato 1, con-
71} Cors o C s u Raspber r y Pi par t endo da zer o
essere scritte prima della funzione main. tware, infatti, resta esecutivo all’interno
Se si vogliono riportare dopo, per una co- della clausola “while” e non c’è modo di
modità di lettura del codice, occorre refe- interromperlo, se non quello di premere
renziarle all’inizio come prototipi. In caso contemporaneamente i tasti <CTRL><C>.
contrario il compilatore non saprebbe mai Più in là vedremo anche come risolvere
della loro esistenza; questo problema;
• La riga 12 richiama una funzione per la • Dalla riga 30 alla riga 36 viene definita
creazione della porta, mentre la riga 13 una funzione preposta alla creazione del-
la definisce come pin in uscita. Da notare la porta GPIO4;
che tali funzioni si riferiscono esclusiva- • Dalla riga 37 alla 43 è stata creata una
mente alla GPIO4. In una delle prossime funzione che definisce tale porta come in-
puntate vedremo come generalizzarle e gresso digitale. Abbiamo preferito non la-
parametrizzarle; sciare aperti i files relativi alle porte GPIO
• Dalla riga 18 alla riga 22 si implementa una ma di chiuderli di volta in volta;
sorta di antirimbalzo software, permetten- • Dalla riga 44 alla riga 52 la funzione “Leg-
do di contare una volta sola l’evento del giPorta” ha il compito di vedere lo stato
passaggio di un oggetto. Senza questa logico della porta GPIO4 (0 oppure 1) e
soluzione, il contatore incrementerebbe di restituirlo al programma chiamante at-
velocemente il suo valore tante volte, per traverso la variabile “valore”. Tale ritorno
tutto il tempo in cui il sensore resta oscu- prevede solo i valori “0” e “1”, di tipo nu-
rato, anche una sola volta, come avvie- merico intero;
ne per una tastiera in modalità “repeat”. • Dalla riga 53 alla 59 viene definita una
Come funziona questa routine? Essa è funzione che permette la distruzione del-
eseguita solo quando il livello logico della la porta, a fine lavoro. Come detto prima,
porta è basso (IF) e resta inchiodata per essa non sarà mai eseguita;
tutto il tempo in cui la stessa porta perma- • Dalla riga 60 alla riga 63, infine, creiamo
ne in un livello logico basso (WHILE); un’utile funzione che ha il compito di po-
• Le righe 23, 24 e 25 “congelano” il pro- sizionare il cursore in una determinata lo-
gramma per tutto il tempo in cui il sensore cazione dello schermo, utilizzando i codici
è illuminato, evitando inutili accessi alla Escape. Abbiamo trattato questo argo-
scrittura sul video. La filosofia è la mede- mento in una delle precedenti puntate del
sima del controllo precedente: la routine corso.
viene eseguita solo se la porta si commu-
ta ad un livello logico alto (IF) e vi resta Il programma proposto è già pienamente fun-
per tutto il tempo in cui il livello continua zionante e si può testarlo per monitorare gli ac-
ad essere alto (WHILE); cessi in un negozio, il transito di automobili in
• La riga 27 richiama la funzione per il rila- una strada o il passaggio di prodotti industriali
scio porta. A dire la verità, essa non sarà su nastro trasportatore. Se l’area da monitorare
mai eseguita in questo programma. Il sof- è vasta, si consiglia di utilizzare un puntatore
73} Cors o C s u Raspber r y P i par t endo da zer o
L’autore è a disposizione nei commenti per eventuali approfondimenti sul tema dell’Articolo.
Di seguito il link per accedere direttamente all’articolo sul Blog e partecipare alla discussione:
http://it.emcelettronica.com/corso-c-su-raspberry-pi-partendo-da-zero-introduzione-alle-porte-di-
input
Cors o C s u Raspber r y P i par t endo da zer o {74
U
n programma, come abbiamo anche visto dal raggio sempre più crescente (1, 2, 3, 4, 5,
nelle puntate precedenti, può essere scrit- ecc.). Funziona perfettamente ma ha un “picco-
to ed organizzato in tantissimi modi diversi. lo” difetto, che esamineremo dopo. Guardiamo
Non esiste una soluzione migliore in assoluto il listato, eseguiamolo e dopo lo commentiamo.
ma ci sono, sicuramente, dei casi in cui il sof- La figura 1 mostra la schermata di esecuzione.
tware risulta funzionante, ma non funzionale. Il tutto funziona egregiamente, ma c’è una pic-
Esistono, inoltre, casi in cui esso riesce a por- cola “ombra” che allontana il nostro algoritmo
tare avanti il compito per cui è stato scritto ma dall’essere perfetto. Riuscite a trovarla?
il modo con cui lo fa risulta
essere goffo e impreciso.
Parlare di ottimizzazione
richiederebbe migliaia di
pagine; noi trattiamo i casi
fondamentali è più abborda-
bili da parte dei principianti
e dei lettori che hanno intra-
preso la lettura del presente
corso. Iniziamo, per rompere
il ghiaccio, ad esaminare il
seguente codice. Esso ha lo
Figura 1: Esecuzione del programma del
#include <stdio.h> cerchio.
int main(){
int r; Ricordate questa preziosa regola:
float p,area;
dobbiamo evitare di fare eseguire
for(r=1;r<=20;r++) {
p=3.1415926; al PC compiti inutili e, soprattutto,
area=r*r*p; il meno possibile. Quale è il difet-
printf("La superficie di un cerchio con raggio %d equivale a to del listato? Guardate l’istruzio-
%f \n",r,area);
ne posta subito dopo il ciclo for:
}
return 0; p=3.1415926. Essa assegna alla
} variabile “p”, di tipo float, il valore
75} Cors o C s u Raspber r y P i par t endo da zer o
del pi greco, usato per calcolare l’area. Tutto lizzare dei costrutti efficienti.
giusto, ma perché tale comando deve trovarsi
all’interno del ciclo iterativo, ripetendosi (inutil- RISPARMIAMO SPAZIO IN RAM
mente) per 20 volte? Ne basterebbe una sola, Spesso il programmatore non sta molto atten-
inserita prima dell’inizio del ciclo stesso. to all’effettivo consumo di RAM da parte delle
Se vogliamo fare un paragone, è un po’ come variabili. Anticamente, quando la memoria era
se un bimbo si mettesse su una classica giostra davvero limitata (e parliamo di PC con 128Kb
con i cavalli e suo papà pagasse il biglietto (o di Ram), occorreva prevedere tanti stratagemmi
frazione di esso) ad ogni giro: tutto lavoro inu- per riuscire a farvi entrare il programma. Lo spa-
tile e dispendioso. Basta pagare solo all’inizio, zio libero era esiguo e si cercavano strategie,
prima che la giostra cominci a girare. Pertanto, anche difficoltose, per realizzare programmi
per correggere il sorgente, dovete “spostare” la compatti. Oggi tale esigenza non c’è più ma è
linea di codice incriminata subito prima dell’ini- sempre buona norma utilizzare variabili adatte,
zio del ciclo, come mostrato nel listato qui sotto. senza spreco inutile di spazio. E poi, diciamolo,
Il programma funziona nella medesima moda- un software compatto guadagna certamente in
lità ma il processore lavora un po’ meno e, so- eleganza e velocità. Il seguente esempio ne è la
prattutto, esegue meno compiti. State tranquilli prova. Immaginiamo di visualizzare la tabellina
che il PC ha tanta memoria e se gli insegniamo del 7. Il seguente sorgente esegue alla perfezio-
qualcosa “una volta sola”, lo ricorda per sempre. ne tale compito (Figura 2).
Il calcolo dell’area deve essere, invece,
eseguito, dentro il ciclo iterativo, perché #include <stdio.h>
int main(){
la misura del raggio varia sempre. Molte int k,tabellina;
volte queste tipologie di errori, o meglio tabellina=7;
di imperfezioni, non vengono trovate fa- for(k=1;k<=10;k++) {
printf("%2d x %2d = %2d \n",tabellina,k,k*tabellina);
cilmente poiché tutto funziona a dovere,
}
non ci sono blocchi e il programmatore return 0;
non immagina, nemmeno lontanamen- }
te, che il “bug” è in agguato. Lo scopo
dell’ottimizzazione è, dunque, quello di rea-
#include <stdio.h>
int main(){
int r;
float p,area;
p=3.1415926;
for(r=1;r<=20;r++) {
area=r*r*p;
printf("La superficie di un cerchio con raggio %d
equivale a %f \n",r,area);
}
return 0;
} Figura 2: Calcolo tabellina.
Cors o C s u Raspber r y P i par t endo da zer o {76
VALORI PARAMETRIZZATI
Un altro valido esempio viene fornito percentuale=12;
con il successivo listato, nella quale else if (zona==4)
percentuale=3;
occorre gestire diversi valori, a se- else if (zona==5)
conda di un parametro sequenziale. percentuale=9;
In pratica occorre calcolare la per- else if (zona==6)
percentuale=8;
centuale di provvigione di un ipoteti-
else if (zona==7)
co agente di commercio, a seconda percentuale=10;
di quale zona egli stia visitando (vedi /*-----Calcolo------*/
provvigione=fatturato*percentuale/100;
l’esecuzione in figura 3). La seguente
/*------Visualizza------*/
tabella mostra i vari casi: printf("\n\n");
• Zona 1: 8% printf("Zona di competenza: %d\n",zona);
printf("Fatturato: %ld euro\n",fatturato);
• Zona 2: 6%
printf("Percentuale provv: %d%%\n",percentuale);
• Zona 3: 12% printf("PROVVIGIONE: %ld euro\n",provvigione);
• Zona 4: 3% return 0;
}
• Zona 5: 9%
• Zona 6: 8%
• Zona 7: 10% Nel listato possiamo notare una sgradevolissi-
ma sequenza di condizioni (IF) che allontanano
Senza un’adeguata mentalità matematica, il l’algoritmo dalla buona programmazione. E se
programmatore scriverebbe una sorta di li- le zone visitate dall’agente di commercio, anzi-
stato a “forza bruta”, nel quale i vari parametri ché sette, fossero state cento, avremmo dovuto
sono visualizzati utilizzando una lunga serie di prevedere cento clausole di confronto? Assolu-
clausole “IF”, come visibile nel seguente sor- tamente no. Per queste tipologie di problema-
gente: tiche ci viene d’aiuto la matematica che, con la
regressione e il curve fitting (leggi il
#include <stdio.h>
int main(){ relativo articolo qui),
long fatturato=357000;
char percentuale;
long provvigione;
int zona;
/*-----Input------*/
printf("\n\n");
printf("-------------------------------------\n");
printf("Inserire il numero della zona (1-7) ");
scanf("%u",&zona);
/*------A seconda della zona calcola la percentuale------*/
if(zona==1)
percentuale=8;
else if (zona==2)
percentuale=6; Figura 3: Calcolo provvigione agente.
else if (zona==3)
Cors o C s u Raspber r y Pi par t endo da zer o {78
riesce a definire un’equazione unica per tutte le utilizzare l’equazione. Eseguendo il programma
tipologie di parametri iniziali. Lungi dal trattare si otterranno i medesimi risultati del precedente.
qui l’argomento aritmetico, la funzione matema-
tica che lega i vari valori è rappresentata in figu- TABELLA DI LOOKUP
ra 4 e, grazie ad essa il programma, in linguag- Non ottimizzato=303 secondi, ottimizza-
gio C, è molto più breve e snello e, soprattutto, to=118 secondi.
non contiene alcuna funzione decisionale. Questa tecnica è usata quando si devono ese-
guire milioni di operazioni ripetitive ed estrema-
#include <stdio.h> mente intensive per la CPU. Basta una piccola
#include <math.h>
int main(){ variazione del codice per rendere il programma
long fatturato=357000; centinaia di volte più veloce. Le tabelle di lo-
float percentuale; okup sono valori precalcolati e memorizzati
long provviggione;
int zona; in un vettore (o matrice), direttamente indiriz-
/*-----Input------*/ zabili e ricercabili tramite l’indice. L’accesso al
printf("\n\n"); contenuto è, quindi, immediato. E l’esempio che
printf("-------------------------------------\n");
segue è una prova. Si tratta di eseguire il cal-
printf("Inserire il numero della zona (1-7) ");
scanf("%u",&zona); colo del numero fattoriale di un valore casuale
/*------Calcola la percentuale col FIT- compreso tra 1 e 12, ripetuto per ben 2 miliardi
TING------*/
di volte. L’approccio tradizionale, che prevede
percentuale=floor(119.277+7.727*zona+zo-
na*floor(3.788*zona)+19.152*zona*floor(2.10618- l’utilizzo di una funzione idonea al calcolo, impie-
0.0475*zona)-0.135*pow(zona,3)-14.405*floor(3.7 ga ben 303 secondi (più di 5 minuti). Il listato è
88*zona)-58.271*floor(2.106-0.047*zona));
il seguente:
/*-----Calcolo------*/
provviggione=fatturato*percentuale/100; #include <stdio.h>
/*------Visualizza------*/ #include <stdlib.h>
printf("\n\n"); long fattoriale(int);
printf("Zona di competenza: %d\n",zona); int caso(int);
printf("Fatturato: %ld euro\n",fatturato); /*----------------------------------*/
printf("Percentuale provv: %f%%\ int main(){
n",percentuale); int c;
printf("PROVVIGGIONE: %ld euro\ long f;
n",provviggione); unsigned long k;
return 0; for(k=1;k<=2000000000;k++) {
} c=caso(12);
f=fattoriale(c);
}
Il listato, adesso, è molto più corto e leggibile. return 0;
Sono state introdotte, ovviamente, alcune fun- }
zioni matematiche (presenti in math.h) per poter /*----------------------------------*/
79} Cors o C s u Raspber r y Pi par t endo da zer o
PARAMETRO DI COMPILAZIONE -O
long fattoriale(int n) { Il compilatore GCC mette a disposizione il flag
int c; -O che consente di scegliere la tipologia di otti-
long risultato=1;
mizzazione che si vuol raggiungere per il pro-
for (c=1;c<=n;c++)
risultato=risultato*c; gramma. La figura 5 illustra le varie possibilità
return risultato; di utilizzo di tale opzione, secondo le proprie
}
esigenze.
/*----------------------------------*/
int caso(int fino_a) { Compilando l’ultimo precedente programma
return rand()%fino_a+1; con la seguente riga di comando:
}
il tempo di esecuzione passa da 118 secondi a
priori e la CPU non ha la necessità di ripetere il za di queste possibilità quando vi serve velocità
calcolo (inutilmente) per miliardi di volte. L’ese- operativa. Ovviamente, nel codice, dovete an-
cuzione del programma impiega solo meno di che inserire del vostro...
#include <stdio.h>
int main(){
unsigned long k,v[5];
for(k=1;k<=1000000000;k++) {
v[0]=k+0;
v[1]=k+1;
v[2]=k+2;
v[3]=k+3;
v[4]=k+4;
}
return 0;
Figura 5: Utilizzo del flag -O. }
L’autore è a disposizione nei commenti per eventuali approfondimenti sul tema dell’Articolo.
Di seguito il link per accedere direttamente all’articolo sul Blog e partecipare alla discussione:
http://it.emcelettronica.com/corso-c-su-raspberry-pi-partendo-da-zero-ottimizzazione
81} Cors o C s u Raspber r y P i par t endo da zer o
Corso C su Raspberry PI
partendo da zero: scrivere le
proprie funzioni di Giovanni Di Maria
I
l mio primo linguaggio di programmazione fu il UDF. In esso viene determinato se i numeri 23,
Basic per lo ZX81 Sinclair, uno dei primi Home 10007, 10011, 30000001 e 750000007 sono
Computer. La sua memoria era estremamente primi. Per ognuno di essi una variabile itera tra
limitata, solo 1Kb di Ram (avete letto bene, so- 2 e n-1 alla ricerca di un divisore (vedi n%k==0).
lamente 1 kilobyte) con i quali si riusciva a fare Il programma non è per nulla ottimizzato e si po-
ben poco, ma i rimedi estremi, a volte, esortava- trebbe velocizzarlo ulteriormente. Per gli ultimi
no a fare miracoli. Ebbene, il limitato potere del due numeri esso impiega qualche secondo per
sistema mi costringeva, spesso, a ripetere tanto decretare la soluzione. Si compili, dunque, con
codice. E molte volte tale ripetizione riguardava il solito comando:
uno stesso algoritmo, magari eseguito in prece-
denza. Per fortuna esistevano i Gosub e i Re- gcc -Wall prova.c
turn che risolvevano alcuni problemi.
e si esegua il programma. Il risultato è visibile
LE FUNZIONI UDF in figura 1.
Il C è un linguaggio interamente basato su fun- #include <stdio.h>
zioni. Tutto è una funzione, anche la main(), int main(){
unsigned long k,n;
che contiene il programma principale. Una UDF
char FlagPrimo;
(user-defined function) è una funzione creata /*----------------------------------*/
dall’utente che ha lo scopo di soddisfare ed ese- n=23;
FlagPrimo=1;
guire compiti non contemplati da altre funzioni
for(k=2;k<n;k++)
già esistenti. Con esse, in pratica, si arricchisce if(n%k==0)
il linguaggio di nuove possibilità. FlagPrimo=0;
if(FlagPrimo==1)
Il seguente esempio, anche se funziona, non
printf("%ld e' un numero Primo\n",n);
utilizza una UDF, pertanto il numero delle righe else
è elevato e il sorgente non risulta snello e leggi- printf("%ld e' un numero Composto\n",n);
/*----------------------------------*/
bile. Il programma deve determinare se quattro
n=10007;
numeri sono primi o meno. Ricordiamo che un FlagPrimo=1;
numero primo è un numero naturale che può for(k=2;k<n;k++)
essere diviso solo per 1 e per se stesso. if(n%k==0)
FlagPrimo=0;
Il listato è volutamente lungo e tedioso, proprio if(FlagPrimo==1)
Cors o C s u Raspber r y Pi par t endo da zer o {82
programma esiste tale funzione. All’interno del un quadretto contenente alcuni testi. La funzio-
corpo “main” si richiama e la si invoca esplici- ne è di tipo void, in quanto non ritorna alcun
tamente, passandole direttamente dei numeri e valore e non è presente nemmeno il comando
non delle variabili. Infatti lo statement: “return”. Il suo richiamo avviene due volte. La fi-
gura 5 mostra il risultato dell’esecuzione. In altri
re. Il seguente esempio ne mostra una che non essa non riceve alcun parametro dal program-
risultato. Si tratta di una funzione che visualizza, a funzione “Orario” non riceve alcun parametro
funzioni, adattate alle esigenze del caso. zando il carattere “X” ma può essere sostituito
con qualsiasi altra lettera riempitiva. In figura 7
possiamo osservare l’esecuzione del program-
Figura 6: Data e ora. ma sotto riportato.
#include <stdio.h>
#include <time.h> printf("\n");
char* Orario (void); Rettangolo(76,1);
int main() { return 0;
printf("Oggi e' il giorno: %s \n",Orario()); }
return 0; void Rettangolo(int larghezza,int altezza) {
} int x,y;
char* Orario (void) { for(y=0;y<altezza;y++) {
time_t rawtime; for(x=0;x<larghezza;x++)
time( &rawtime ); printf("X");
return asctime(localtime( &rawtime )); printf("\n");
} }
}
puntatore al primo elemento dell’array. Questo per non mettere troppa carne sul fuoco. Il consi-
significa che qualsiasi modifica degli elementi, glio è quello di provare e riprovare tanti esempi.
all’interno della funzione, influisce definitiva- Se un programma non funziona, tanto meglio,
mente su di essi, anche quando l’esecuzione occorre sforzarsi a trovare e a scovare l’erro-
del programma abbandona la funzione UDF. re che, sicuramente, è nascosto tra i meandri
Nel seguente esempio, viene creato un vettore del codice. Il vero programmatore è quello che
composto da 6 elementi che sono visualizzati riesce a localizzare e correggere un errore nei
prima e dopo la chiamata ad una funzione uten- listati. Ribadiamo un concetto fondamentale,
te. In essa gli elementi sono incrementati di un nell’utilizzo delle funzioni. I parametri passati
determinato valore. Il programma dimostra che i devono seguire le seguenti regole:
valori restano modificati anche dopo l’uscita dal- • il numero di parametri passati deve essere
la funzione. Il programmatore, pertanto, deve lo stesso di quello previsto dalla funzione;
sempre tenere a mente che, passando un vetto- • la tipologia dei parametri passati deve
re, potenzialmente esso può essere modificato essere la stessa di quella prevista dalla
dalla funzione UDF. Si possono tranquillamente funzione;
passare i singoli elementi per valore, come ab- • l’ordine dei parametri passati deve rispet-
biamo visto negli esempi precedenti. tare quello programmato nella funzione.
#include <stdio.h>
void Processa(int a[]);
int main() {
int vettore[]={11,22,33,44,55,66};
printf("Array PRIMA della chiamata: %d %d %d %d %d %d\n",vettore[0],vettore[1],vettore[2],vettore[3],
vettore[4],vettore[5]);
Processa(vettore);
printf("Array DOPO la chiamata: %d %d %d %d %d %d\n",vettore[0],vettore[1],vettore[2],vettore[3],ve
ttore[4],vettore[5]);
return 0;
}
void Processa(int a[]) {
int k;
for(k=0;k<6;k++)
a[k]+=5;
}
• di colore blu il parametro relativo alla base di cosa vi sia all’interno. Con una sola riga si
maggiore; possono risparmiare tante righe di lungo co-
• di colore rosso il parametro relativo alla dice. Rinnoviamo l’appuntamento con le suc-
base minore;
cessive lezioni, raccomandandovi sempre di
• di colore verde il parametro relativo all’al-
provare e sperimentare sempre. Lasciamo
tezza.
il lettore con un esercizio. Si crei la funzione
La funzione è, dunque, una scatola nera, da massimo(a,b,c,d,e) che restituisca il numero
richiamare all’occorrenza senza preoccuparsi più grande tra quelli passati. Alle prossime.
L’autore è a disposizione nei commenti per eventuali approfondimenti sul tema dell’Articolo.
Di seguito il link per accedere direttamente all’articolo sul Blog e partecipare alla discussione:
http://it.emcelettronica.com/corso-c-su-raspberry-pi-partendo-da-zero-scrivere-le-proprie-funzioni
89} Cors o C s u Raspber r y P i par t endo da zer o
Corso C su Raspberry PI
partendo da zero: La gestione
delle stringhe di Giovanni Di Maria
N
ormalmente, una stringa è un insieme, o Nel linguaggio C, una stringa è definita come
sequenza di caratteri, rappresentata come un array di caratteri. Non esiste, pertanto, una
costante o memorizzata in una variabile. parola chiave “string” per crearla, come avviene
Definita come costante, essa è racchiusa tra per altre tipologie di ambienti di programmazio-
doppi apici, i quali non fanno parte della stringa ne. Il suo stanziamento in memoria avviene in
stessa. Ad esempio: “Hello world” è una stringa più modalità, quindi, normalmente come abbia-
composta da 11 caratteri. Il carattere “spazio” fa mo visto nelle puntate precedenti. Il seguente
parte integrante di essa. I caratteri che la com- esempio:
pongono fanno parte di un alfabeto preesistente
come, ad esempio, il codice Ascii. Il numero di char Nome[10];
caratteri che compone una stringa rappresenta
la sua lunghezza e ovviamente non può essere definisce in memoria un insieme di 10 caratteri,
negativa. Può essere anche pari a zero quando un array (come approfondito in questo articolo)
non contiene alcun carattere (stringa nulla). In che “prenota” dieci celle da riempire in futuro.
molti linguaggi di programmazione la stringa è Ogni cella è numerata, in questo caso, da 0 a 9,
dichiarata come un tipo di dato presente nella come si evince dalla figura 1.
sintassi intrinseca. In linguaggio C il discorso è In qualche modo il linguaggio deve pur cono-
leggermente diverso e lo vedremo nei paragrafi scere dove termina una stringa, altrimenti le
a seguire.
Lo scopo delle stringhe è quello di memorizza-
re, al loro interno, sequenze di caratteri per poi
riutilizzarle all’occorrenza. In esse si possono
immagazzinare:
• Nominativi;
• Titoli delle applicazioni; Figura 1: Definizione di un array composto da 10 elementi.
• Le etichette dei pulsanti;
• Parole per poi elaborarle; elaborazioni su essa non avrebbero mai fine e
• Messaggi criptati e in chiaro; le relative visualizzazioni darebbero risultati ina-
• e tanto altro ancora... spettati. Il C considera terminata una stringa
quando incontra un carattere NULL, definita
Cors o C s u Raspber r y P i par t endo da zer o {90
I PUNTATORI
Per trattare le stringhe, un ottimo metodo è rap-
presentato dai puntatori, già esaminati in una
delle precedenti puntate del corso. L’implemen-
tazione è abbastanza semplice e il seguente
listato ne è la prova. “Nome” è un puntatore a
carattere. La sua assegnazione con una stringa Figura 5: Il risultato delle funzioni “maiuscolo” e “minuscolo”.
ne memorizza l’elenco dei caratteri e quello di
terminazione è accodato automaticamente. • maiuscolo(): converte stringa in maiusco-
#include "stdio.h" lo;
int main() { • minuscolo(): converte stringa in minusco-
char *Nome;
lo.
Nome="Giovanni Di Maria";
printf("%s\n",Nome);
Nome="Giovanni"; ANALISI
printf("%s\n",Nome);
Ben lungi dall’approcciare l’algoritmo a forza
Nome="Salve Mondo";
printf("%s\n",Nome); bruta, che prevede una lunghissima serie di
} condizioni “if”, una per ciascuna lettera dell’al-
93} Cors o C s u Raspber r y P i par t endo da zer o
stringhe.";
c = 0;
while (frase[c] != '\0') {
switch(frase[c]) {
case 'a':
tot_a++;
break;
case 'e':
tot_e++;
break;
case 'i':
Figura 6: I codici ASCII di alcune lettere dell’alfabeto. tot_i++;
break;
case 'o':
<string.h> sono presenti i prototipi delle due tot_o++;
funzioni strlwr e strupr. Quella che segue è un break;
case 'u':
possibile utilizzo: tot_u++;
break;
}
CONTA LE VOCALI c++;
}
printf("%s\n\n",frase);
printf("Stringa modificata %s\n\n", strupr(string)); printf("Quantita' di vocali 'A' nella frase:
%d\n",tot_a);
printf("Quantita' di vocali 'E' nella frase:
Esaminiamo, adesso, uno dei tanti metodi per %d\n",tot_e);
printf("Quantita' di vocali 'I' nella frase:
contare le vocali in una frase. La filosofia è sem- %d\n",tot_i);
printf("Quantita' di vocali 'O' nella frase:
plice: si esaminano, ad uno ad uno, tutti i carat- %d\n",tot_o);
teri della stringa e si confrontano con dei carat- printf("Quantita' di vocali 'U' nella frase:
%d\n",tot_u);
teri costanti, in modo da verificare la presenza return 0;
della vocale, nel qual caso viene incrementato }
il relativo contatore. Tale conteggio è delegato
a cinque variabili numeriche intere (tot_a, tot_e,
tot_i, tot_o, tot_u) che tengono il conto della
quantità delle rispettive vocali. Il listato ci propo-
ne una novità nelle condizioni: invece di utilizza-
Figura 7: Il conteggio delle vocali.
re cinque clausole “if” si è pensato di usare, più
propriamente, il costrutto “switch” “case”. L’ese-
cuzione del listato è mostrato in figura 7.
CONVERTIAMO UN NUMERO IN
STRINGHE CON SPRINTF
#include <stdio.h>
int main() { Utilizzando un’adatta funzione di libreria, è mol-
char *frase; to semplice e veloce effettuare la conversione
int c;
int tot_a = 0; di un valore numerico in stringa. Con la funzio-
int tot_e = 0; ne sprintf (che lavora in modo equivalente alla
int tot_i = 0;
int tot_o = 0; printf ma ridirige l’output su una stringa di carat-
int tot_u = 0; teri) è possibile dirottare un qualsiasi insieme di
frase = "Ciao a tutti. Questa e' una lezione sulle
95} Cors o C s u Raspber r y Pi par t endo da zer o
dati, anche misto, in un array di caratteri o strin- accodamenti di caratteri, come avviene per altri
ga. Un semplice esempio chiarirà ogni dubbio. linguaggi, è stato possibile, in maniera estre-
mamente facile, costruire la stringa identifican-
#include <stdio.h> done i componenti direttamente nella funzione
#include <malloc.h> sprintf. Il risultato ottenuto dalla esecuzione del
int main() {
programma è visibile in figura 8. Si usa questa
char *stringa;
long valore; tecnica quando si devono memorizzare, su sup-
stringa=malloc(20); porto di massa, i risultati di una elaborazione o
valore = 570800234; altro, in maniera semplice e lineare.
sprintf(stringa,"%ld\n",valore);
printf("Questa e' una stringa: %s\n",stringa);
return 0; ALCUNE UTILI FUNZIONI
} Per fortuna l’utente non deve costruirsi di sana
pianta le funzioni di stringa, per gestirle in toto.
Il listato dichiara un puntatore a caratteri, allo- Molte di esse sono state già implementate, per
cando 20 bytes di spazio libero tramite la fun- fortuna ed è sufficiente prevedere, nel proprio
zione malloc. Successivamente, la funzione programma, il file di inclusione:
sprintf “stampa” tale valore sulla stringa, anzi-
ché su STDOUT. Essa funziona, quindi, alla #include <string.h>
stessa stregua della sorella printf. Infine
viene visualizzato a video il contenuto del- Alcune di queste, a volte indispensabili, sono le
la stringa creata. Se non si coglie immediata- seguenti:
mente la grande utilità della funzione sprintf, si • strcpy;
osservi il seguente listato: • strcat;
• strlen;
#include <stdio.h> • strcmp.
#include <malloc.h>
Vediamole molto brevemente.
int main() {
char *stringa;
int valore; STRCPY
stringa=malloc(80); Serve a copiare una stringa su di un’altra. Il suo
valore = 124;
prototipo (e quindi la sua sintassi) è il seguente:
sprintf(stringa,"Il valore e' %d. Il suo doppio e':
%d. La sua meta' e': %d\n",valore,valore*2,valo
re/2); char *strcpy(char *destination, const char
*source);
printf("\nQuesta e' la stringa ricostruita:\n%s\
n",stringa);
return 0;
STRCAT
} Ha il compito di concatenare due stringhe. Il suo
prototipo è il seguente:
Con esso viene letteralmente costruita una
stringa dal contenuto promiscuo, composta da char *strcat(char *dest, const char *src);
testi e numeri. Invece di ricorrere a complessi
Cors o C s u Raspber r y P i par t endo da zer o {96
STRCMP
Serve per comparare (alfabeticamente) due equivalente all’assegnazione:
stringhe tra loro e fornisce tre valori diversi, a
seconda se una stringa è, rispettivamente, mag- char carattere = 65;
giore, minore o uguale all’altra. Il suo prototipo
è il seguente: La seguente assegnazione, invece, assegna la
stringa alla variabile “st”, ossia una sequenza
int strcmp(const char * s1, const char * s2); di caratteri (anche uno solo) seguita da NULL
(‘\0’):
L’autore è a disposizione nei commenti per eventuali approfondimenti sul tema dell’Articolo.
Di seguito il link per accedere direttamente all’articolo sul Blog e partecipare alla discussione:
http://it.emcelettronica.com/corso-c-su-raspberry-pi-partendo-da-zero-la-gestione-delle-stringhe
97} Cors o C s u Raspber r y P i par t endo da zer o
Corso C su Raspberry PI
partendo da zero: Gestire i
files su disco di Giovanni Di Maria
INTRODUZIONE
Q
ualsiasi PC, si sa, è dotato di una memo- IL FILE
ria RAM di tipo volatile che, quando non è I dati e le informazioni possono essere inoltrate,
alimentata da corrente elettrica, perde tutto attraverso appositi flussi, verso opportuni dispo-
il suo contenuto. Agli inizi della mia esperienza sitivi, logici o fisici. Questi dispositivi sono, per
informatica, diciamo quando avevo circa 10-12 esempio, una stampante, un disco, un video,
anni, i personal computer non possedevano le un terminale, ecc. La figura 1 illustra lo sche-
caratteristiche di quelli dei giorni d’oggi. I primi ma logico dell’utilizzo dei files. Come abbiamo
modelli di ZX-81 e Commodore 64 non pos- visto in qualche puntata precedente, in Linux,
sedevano le memorie di massa (esse uscirono tutto è considerato un file. Qualsiasi operazione
sul mercato qualche tempo dopo ed erano rela- di trasporto delle informazioni, dal/al dispositivo,
tivamente costose). In edicola si vendevano le preclude le seguenti fasi:
prime riviste informatiche, nelle quali erano pub- • Apertura del file;
blicati chilometrici listati sorgenti da ricopiare al • Lettura o scrittura delle informazioni;
PC. La digitazione poteva protrarsi per alcune • Chiusura del file.
ore e, dopo estenuante e lungo lavoro, passa-
to anche a correggere errori vari (proprio della Immaginiamo un file come una stanza piena
rivista), il programma funzionava regolarmente. di volantini di carta. Se io voglio accedere alla
Si avvicinava, ovviamente, il momento in cui il stanza e ai documenti in essa contenuti, devo
computer doveva essere spento. A malincuore aprire la porta (apertura del file). Quindi pos-
si staccava la spina e tutto il lavoro
veniva perso... Pazienza, il giorno
dopo si ritornava a ripetere tutte le
operazioni e a digitare, nuovamente,
l’intero listato. Pazzesco.
Per fortuna, l’adozione delle me-
morie di massa ha tolto per sempre
questo tedioso problema e, ai nostri
giorni, esse possono essere conside-
rate come dei contenitori di dati dalle
enormi capacità di memorizzazione. Figura 1: Schema logico.
Cors o C s u Raspber r y Pi par t endo da zer o {98
so portare dentro altri volantini (operazione di Il calcolo delle tabelline avviene all’interno di
scrittura) o leggerli (operazione di lettura). Alla due cicli annidati (i, k). Il primo (i) itera da 1 a 10
fine del lavoro è buona norma chiudere la porta per processare la relativa tabellina (dell’1, del
(chiusura del file) poiché un colpo di vento po- 2, ecc), il secondo (k) itera da 1 a 10 per ese-
trebbe far volare via tutte le carte. Il paragone guire le moltiplicazioni per tale valore (i*1, i*2,
calza a pennello con tale tipologia di argomen- i*3, ecc). E veniamo alle funzioni e istruzioni che
tazione (Figura 1). trattano i files:
Come detto in precedenza, è sempre buona nella memoria di massa, in forma definitiva,
norma chiudere un file, specialmente quando all’interno di un file di testo. Successivamen-
esso è aperto in modalità scrittura. In questa te gli operatori si preoccuperanno di esaminare
maniera si sigillano i dati e non c’è il pericolo tale archivio ed utilizzarlo secondo le proprie
della loro scomparsa. Un po’ come quando si esigenze per l’invio di newsletter o informazioni.
deve rimuovere la pendrive dallo slot USB. L’esecuzione del listato visualizza una masche-
L’esecuzione del programma non causa alcuna ra di inserimento come quella visibile in figura 3.
visualizzazione sul video, ma produce un file L’utente deve solamente riempire i due campi
di testo sul disco dal nome “tabelline.txt”, il cui che saranno memorizzati stabilmente su archi-
contenuto è mostrato in figura 2. Si ricorda di far vio di massa.
girare il programma con il permesso di superu-
ser (sudo) poiché viene concretizzata la scrittu-
ra su filesystem.
Un’altra cosa da notare è che ad ogni esecuzio-
ne del software, il file “tabelline.txt” sarà distrut-
to e ricreato.
#include "stdio.h"
/*---------Main--------*/
int main() {
char nominativo[30];
char email[50];
FILE *handle;
/*-----------------Chiede i dati all'utente-----------*/
printf("\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n");
printf(" MODULO DI ISCRIZIONE VISITATORI \n");
printf("\n\n\n");
printf("Inserisci il COGNOME e NOME: .......... ");
fgets(nominativo, 30, stdin);
printf("Inserisci la tua EMAIL: ............... ");
fgets(email, 50, stdin);
/*-------------Memorizza e ACCODA su file-----------*/
handle=fopen("iscritti.txt","a");
fprintf(handle,"Nominativo: %s",nominativo);
fprintf(handle,"Email: %s",email);
fprintf(handle,"====================================\n");
fclose(handle);
return 0;
}
mo utilizzato la funzione scanf ? Perché essa gli utenti, i nominativi non vengono cancellati (ci
“rifiuta” i caratteri che seguono gli spazi, in una mancherebbe altro...) ma accodati uno all’altro,
stringa, quindi il nominativo “Di Maria” sarebbe in una sorta di semplice database. Aprendo l’ar-
accettato dal sistema semplicemente come “Di”. chivio “iscritti.txt” con qualsiasi editor di testo è
Terminato l’inserimento dei dati, da parte del vi- possibile visionare, e gestire in modo facile, l’e-
sitatore, si passa alla loro memorizzazione su lenco delle persone che si sono registrate, sen-
file. Anche qui c’è qualche importante appunto za l’assillo di carta e penna e con la sicurezza
da fare. Come si vede, le macro-fasi del softwa- e la velocità del sistema informatico adottato,
re sono le seguenti: come visibile in figura 4.
• Inserimento dati da tastiera (data entry).
• Apertura file in append.
• Memorizzazione dati.
• Chiusura archivio su disco.
#include "stdio.h"
/*---------Main--------*/
int main() {
char nominativo[30];
FILE *handle;
int conta[26];
int k;
/*-------------Azzera i 26 contatori-----------*/
for(k=0;k<26;k++)
conta[k]=0;
/*-------------Apre il file e legge-----------*/
handle=fopen("nomi.txt","r");
while( !feof(handle) ) {
fscanf(handle,"%s",nominativo);
conta[nominativo[0]-65]++;
}
Figura 5: Esempio di file testuale contenente molti nomi. fclose(handle);
/*-------------Visualizza i 26 contatori-----------*/
for(k=0;k<26;k++)
E ADESSO... LA LETTURA DEI FILES printf("Lettera %c: %d\n",k+65,conta[k]);
Immaginiamo di ricevere in email un enorme return 0;
}
file contenente alcune informazioni e di cui dob-
Cors o C s u Raspber r y Pi par t endo da zer o {102
Esaminiamo un po’ il listato, per arrivare poi a Ascii della prima lettera.
comprendere il risultato finale dell’esecuzione, • Se da tale codice sottraiamo il valore fisso
visualizzato in figura 6. Il conteggio dei nomi, 65, otteniamo un altro valore con interval-
per lettera iniziale, è contenuto all’interno di 26 lo compreso tra 0 e 25. Si suppone che
elementi di un vettore, predisposto proprio per tutti i nomi inizino con lettera maiuscola.
questo scopo. Sarebbe assurdo e scomodo, in- • Questo valore diventa l’indice del vettore
fatti, dichiarare e usare 26 variabili distinte. In che si riferisce alla posizione ordinale del-
via preliminare, i contatori sono azzerati perché la lettera in questione;
prima del conteggio, ovviamente, non viene • Tale elemento del vettore viene incremen-
esaminata alcuna parola. Si parte, dunque, da tato di una unità.
zero. Vediamo se con un esempio chiariamo meglio il
Segue, quindi, la parte che si occupa della let- concetto. Supponiamo di aver trovato un nome
tura dei nomi, riga per riga. La funzione fscanf, che inizia per lettera “C” (codice Ascii 67) ed
stavolta, assolve bene al suo compito e leg- eseguiamo, passo passo, lo statement, parten-
ge, in formato stringa, ogni rigo del file ar- do dal suo formato iniziale:
chivio. Tale conteggio continua sino a quando
non si incontra la fine del file (EOF), evenienza conta[nominativo[0]-65]++;
controllata dalla funzione feof. Il controllo del-
la prima lettera e il relativo conteggio, invece, ossia:
avvengono in un’unica riga di programma. Per
esaminare il primo carattere del nome letto conta[67-65]++;
dall’archivio, è sufficiente “guardare” l’elemento
zero del vettore, nominativo[0]. ossia:
Un principiante potrebbe pensare di risolvere
l’algoritmo con una lunga serie di condizioni “if”, conta[2]++;
una per ogni lettera dell’alfabeto, con annesso
l’incremento del relativo contatore. Algoritmo Il terzo elemento del vettore è incrementato di
funzionante ma esageratamente lungo e poco uno, aumentando il numero delle “C” trovate (fi-
elegante. Con una sola riga risolviamo il proble- gura 6).
ma, in maniera un po’ oscura e misteriosa, ma
in piena filosofia C. Spezzettiamo la riga di codi- CONCLUSIONI
ce ed esaminiamola per bene: Ci sarebbero milioni di cose ancora da appro-
fondire e lo spazio è tiranno. L’importante è
conta[nominativo[0]-65]++; approcciare bene l’argomento e avere le idee
chiare. Non abbiate la presunzione di cercare di
Innanzitutto non viene usata nemmeno una imparare il linguaggio C in due mesi. Personal-
condizione if. mente, dopo trent’anni che lo conosco, imparo
• Nominativo[0] contiene il primo carattere sempre cose nuove. Quindi, pazienza e perse-
del nome e, nel caso specifico, il codice veranza, le soddisfazioni arriveranno sicu-
103} Cors o C s u Raspber r y P i par t endo da zer o
L’autore è a disposizione nei commenti per eventuali approfondimenti sul tema dell’Articolo.
Di seguito il link per accedere direttamente all’articolo sul Blog e partecipare alla discussione:
http://it.emcelettronica.com/corso-c-su-raspberry-pi-partendo-da-zero-gestire-i-files-su-disco