Sei sulla pagina 1di 104

2 Corso C su Raspberry PI partendo da zero: introduzione

12
Corso C su Raspberry PI partendo da zero: il nostro primo programma

21 Corso C su Raspberry PI partendo da zero: cicli e condizioni

32
Corso C su Raspberry PI partendo da zero: vettori e matrici

40 Corso C su Raspberry PI partendo da zero: il controllo del video


50
Corso C su Raspberry PI partendo da zero: i Puntatori

58 Corso C su Raspberry PI partendo da zero: Introduzione alle porte di


Output

67
Corso C su Raspberry PI partendo da zero: Introduzione alle porte di
Input
74
Corso C su Raspberry PI partendo da zero: Ottimizzazione

81 Corso C su Raspberry PI partendo da zero: scrivere le proprie funzioni

89 Corso C su Raspberry PI partendo da zero: La gestione delle stringhe

97
Corso C su Raspberry PI partendo da zero: Gestire i files su disco
{2

Corso C su Raspberry Pi
partendo da zero

Corso C su Raspberry PI partendo


da zero: introduzione
di Giovanni Di Maria

INTRODUZIONE Le due figure erano diametralmente opposte e

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

decenni di ricerca e sviluppo. Secondo periodo: computer con porte adattate


Dal 1980 in poi, i computer erano dotati di alcu-
LE ESIGENZE DELLE PORTE DI I/O ne porte logiche che avevano scopi esclusivi.
Dalla comparsa dell’elettronica digitale, le porte Ad esempio, esse potevano azionare e scam-
logiche di comunicazione hanno avuto un’im- biare dati con stampanti, scanner, modem, ecc.
portanza enorme. Anche questo aspetto ha do- Ma non esistevano vere e proprie porte dedica-
vuto transitare negli anni, adattandosi a ciò che te alle applicazioni personalizzate. Ad esempio,
la tecnologia offriva. Possiamo dire che ci sono un PC non poteva essere collegato direttamen-
stati quattro momenti differenti, in cui le porte di te ad una lampada esterna per venir comandata
comunicazione (I/O) hanno subito un diverso a piacimento tramite un software. Si potevano
trattamento, secondo diversi metodi di utilizzo. utilizzare le suddette porte, adattandole ecce-
zionalmente ai propri scopi, per poter raggiun-
Primo periodo: porte logiche non intelligenti gere il risultato (chi ricorda come si pilotava con
Nel primo periodo (dal 1950 circa in poi) le porte il GW-Basic la porta parallela 0D888 (ox378)
logiche erano pilotate esclusivamente da altre alla quale c’era un diodo Led collegato?). Tutto
porte logiche o da componenti discreti. Le scel- ciò causava alcuni svantaggi:
te decisionali dei comportamenti dei vari sistemi 1. Il numero di I/O era estremamente limitato;
erano effettuate elettronicamente e, in pratica, 2. Un problema al carico (o un suo corto cir-
l’intero circuito elettronico sovrintendeva a tutto. cuito) avrebbe causato irrimediabilmente
Non esistevano firmware e nemmeno siste- la distruzione della scheda madre del PC.
mi operativi che comunicavano con la circu- In ogni caso, anche con i PC obsoleti, si riu-
iteria. Se occorreva cambiare il comportamento sciva a realizzare dei piccoli sistemi di “do-
del prototipo, anche di poco, si doveva modifi- motica” capaci di pilotare le periferiche più di-
care l’intero schema elettrico o alcuni valori dei sparate.
componenti utilizzati. Inoltre erano richiesti nu-
merosissimi componenti elettronici, anche per Terzo periodo: porte logiche intelligenti
semplici applicazioni (Figura 1). Con l’alta integrazione dei circuiti, dal 1990 in poi
le aziende sono riuscite a pro-
durre dispositivi programma-
bili, con tanto di RAM e CPU.
Parliamo dei microcontrollo-
ri (MCU), piccoli circuiti inte-
grati dotati di tutta la logica di
controllo. Con gli MCU l’utente
non solo aveva a disposizione
tante porte di I/O, ma poteva
deciderne il funzionamento,
semplicemente modificando il
Figura 1: Circuito elettronico con diversi IC come porte logiche firmware in esso contenuto (Figura 2).
Cors o C s u Raspber r y P i par t endo da zer o {4

Figura 2: Microcontrollore PIC16F677A

Quarto periodo: i sistemi Embedded


E finalmente, ai nostri giorni, si affacciano i si- Figura 3: Esempio di scheda Raspberry collegata ad un LED
stemi embedded: veri e propri computer, di di-
mensioni estremamente ridotte, dotati di un Si- semplicemente per non costringere il lettore a
stema Operativo, di una memoria molto grande cercare altrove alcune informazioni tecniche. Il
(sia volatile che non), di periferiche di Input e Raspberry Pi è un computer a tutti gli effetti.
di Output ma, soprattutto, di tante porte logiche Quando uscì sul mercato il primo modello era
elettriche di I/O, dedicate all’utilizzo personaliz- praticamente impossibile entrarne in possesso
zato. Ognuna di queste porte può venir pilotata e le numerosissime prenotazioni prevedevano
da un programma scritto ad-hoc e può coman- consegne dopo parecchi mesi dai relativi ordini.
dare un Led, un display o un carico maggiore, Per le sue dimensioni minime può essere anche
attraverso opportuni transistor o relè (Figura 3). chiamato picocomputer (a differenza dei suoi
Chissà cosa ci riserva il futuro: sicuramente fratelli maggiori, il minicomputer e il microcom-
dispositivi fantascientifici, comandati diret- puter).
tamente dal cervello o altre diavolerie simili. Al
momento gustiamoci questo interessantissimo cor-
so, sicuri di far piacere a tutti gli hobbisti, principianti
e, perché no, ai tecnici più esperti ed esigenti.

PANORAMICA GENERALE SUL RA-


SPBERRY PI
Non sprecheremo pagine e pagine di trattazione
sulla teoria del Raspberry Pi e sulle sue caratteri-
stiche tecniche, la Rete ne è colma. Ovviamente
qualche piccola delucidazione è d’obbligo, Figura 4: Raspberry PI Model B
5} Cors o C s u Raspber r y P i par t endo da zer o

Il dispositivo è aperto e collegabile al mondo mente semplice. E’ sufficiente collegare il mou-


esterno attraverso tante porte di comunicazio- se e la tastiera alle porte USB, un televisore alla
ne, la maggior parte rientrante nello standard RCA (o HDMI) e l’alimentatore al relativo connet-
mondiale. Esse sono le seguenti: tore microUsb. Quello del vostro telefonino va
• Un connettore di rete Ethernet; senz’altro bene.
• Un connettore video HDMI; La cosa più importante è rappresentata dalla
• Un altro connettore videocomposito RCA; scheda SD, che si comporta in questo caso
• Un connettore microUSB per l’alimenta- come un Hard Disk, e deve contenere “l’im-
zione; magine” del sistema operativo. Senza di essa
• Alcuni connettori USB; il Raspberry, ovviamente, non si avvia. Successi-
• Un’uscita audio; vamente spiegheremo come preparare al meglio
• Uno slot per ospitare la memoria SD; tale supporto di massa.
• E soprattutto, il connettore GPIO com-
posto da tante porte digitali di input e di CARATTERISTICHE TECNICHE
output, a cui poter collegare le nostre ap- Come tutte le periferiche che si rispettano, nel
parecchiature elettroniche autocostruite e corso degli anni il Raspberry Pi è stato spesso
non. aggiornato e migliorato. Di seguito proponiamo
Con pochissimi euro è, dunque, possibile acqui- una tabella (tabella 1) comparativa, mostrante le
stare un computer a tutti gli effetti, abbastanza caratteristiche salienti di alcuni modelli. Una ta-
potente per la maggior parte delle applicazioni, bella più completa è presente a fine articolo (ta-
collegabile al monitor o al TV di casa e con la bella 2).
possibilità, ovviamente, di utilizzare il mouse e
la tastiera (Figura 4 e 5). CABLAGGIO DEL SISTEMA
Il collegamento dei cavi e del-
le periferiche è estremamente
semplice e alla portata di tutti. Il
Raspberry Pi va collegato ad un
adeguato alimentatore universale
con attacco micro Usb, ad un cavo
RCA o HDMI per il TV, al mouse e
alla tastiera, entrambi di tipo USB
e, soprattutto, ad una scheda USB
formattata e preparata con il siste-
ma operativo. Questo aspetto sarà
approfondito tra poco. Se i colle-
gamenti sono stati effettuati corret-
Figura 5: Raspberry PI Model B in dettaglio tamente, accendendo l’embedded
e il TV si vedrà subito l’immagine
Il collegamento delle periferiche risulta estrema- di caricamento del sistema operativo (Figura 6).
Cors o C s u Raspber r y P i par t endo da zer o {6

RPi 1 Model A+ RPi 1 Model B RPi 2 Model B


900MHz quad-core ARM
Cpu 700 MHz ARM1176JZF-S 700 MHz ARM1176JZF-S
Cortex-A7
Ram 256Mb 512Mb 1GB
Usb 1 2 4
2x13 header pins for GPIO, SPI,
I/O 40 GPIO pins 40 GPIO pins
I²C, UART, +3,3 Volt, +5 Volt
Net no Ethernet 10/100 Ethernet 10/100
Tabella 1: Caratteristiche dei alcuni modelli del Raspberry PI

te Web che Torrent (quest’ultimo molto più


veloce):
• Raspbian Jessie, basato su Debian Jes-
sie, versione settembre 2015;
• Raspbian Wheezy, basato su Debian
Wheezy , versione maggio 2015.
Queste due sono le versioni ufficiali delle distri-
buzioni per Raspberry Pi. Sotto sono, invece,
elencate, le versioni prodotte da terze parti:
• Ubuntu MATE immagine per Raspberry Pi
2;
• Snappy Ubuntu Core;
Figura 6: Cavi e periferiche per il collegamento alla
Raspberry PI • Windows 10 Core;
• OSMC;
Per le nostre prove utilizzeremo il Raspberry Pi • Openelec;
modello B ma ai fini del corso, qualsiasi altro • Pinet;
modello andrà senz’altro bene. • Risc OS.
Ai fini del nostro corso utilizzeremo la versione
PREPARAZIONE DELLA SD ufficiale Raspbian Jessie. E’ consigliabile con-
Senza perderci in ulteriori chiacchiere, passia- trollare spesso il sito, in quanto le distribuzioni
mo alla preparazione della scheda SD, che con- sono rilasciate abbastanza di frequente (Figura
terrà il Sistema Operativo atto a far funzionare il 7).
nostro Raspberry. Occorre, dunque, effettuare il download del file
Accedendo alla pagina https://www.raspberrypi. 2015-09-24-raspbian-jessie.zip (il nome po-
org/downloads/, sono disponibili diverse “imma- trebbe cambiare con la versione). Si tratta di
gini” da copiare sulla memoria. Oltre quelle uffi- un file compresso, dal peso di circa 1,3 Gb. Al
ciali sono presenti anche alcune “ISO” di terze suo interno è presente l’immagine da copiare
parti. su SD, dalle dimensioni, ovviamente, maggiori.
Alla data del presente articolo, si possono Esse ammontano a circa 4 Gb. Il suo nome è
prelevare le seguenti distribuzioni sia trami- 2015-09-24-raspbian-jessie.img.
7} Cors o C s u Raspber r y P i par t endo da zer o

nali. Questa utility non si limita a copiare il file


scaricato su SD, ma costruisce su essa l’intero
File System, con tanto di FAT, S.O. e quant’altro
(figura 8).

Figura 8: Finestra di Win32 Disk Imager in fase di lavoro

Se l’intera operazione è andata a buon fine,


apparirà il messaggio di avvenuto successo di
scrittura e la SD può essere subito utilizzata
Figura 7: Pagina di download del Raspbian sul Raspberry Pi. Se avete un mini SD la pote-
te usare senza problemi ma occorre, in questo
Per trasferire il contenuto dell’immagine su SD, caso, adoperare anche un opportuno adattatore
non basta copiarla fisicamente su tale supporto delle dimensioni.
ma occorre utilizzare un utility (per utenti Win-
dows) particolare, il Win32DiskImager, prele-
vabile da questo sito. La sua versione attuale è PRIMO AVVIO
la 0.8.5. A questo punto è possibile accendere la televi-
Si esegua il programma con il doppio click del sione, selezionare il canale AV (o scegliere la
mouse e si scelga il file immagine da trasferi- fonte HDMI) e alimentare il Raspberry Pi. Ini-
re e l’unita di destinazione. A questo punto è zia subito l’operazione di bootstrap ed il sistema
possibile premere il pulsante “Scrivi”. Dopo la operativo, dopo alcuni secondi, è caricato. E’
conferma inizia il processo di copia e di prepa- impressionante il fatto di avere un computer
razione della SD. L’operazione è un pò lunga così piccolo ed economico tra le mani (Figu-
(circa 10-15 minuti). Il contenuto della memoria, ra 9).
ovviamente, andrà irrimediabilmente perso. Oc- Il desktop del sistema operativo mostra le sue
corre notare che la SD deve avere una capacità principali risorse ed il menù contiene molti sof-
uguale o maggiore a quella del file immagine. Il tware ed utility, persino utilizzabili proficuamen-
Win32DiskImager adegua la dimensione della te in ufficio (figura 10).
scheda a quella della ISO, per qui non spaven- Nel proseguo dell’articolo si noterà che alcune
tatevi se la vostra SD di 32 Gb sarà “ridotta” a procedure saranno emulate direttamente al PC
soli 4 Gb. Con una successiva formattazione a con il programma “Qemu”, altre saranno effetti-
basso livello le dimensioni ritorneranno origi- vamente testate sul Raspberry Pi originale.
Cors o C s u Raspber r y P i par t endo da zer o {8

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.

Figura 10: Desktop del Raspberry PI

Figura 12: LibreOffice

Proseguiamo con la perlustra-


zione dei software a bordo del-
la distribuzione utilizzata. Non
immaginate la mia grande feli-
cità nel vedere il grandioso pro-
gramma matematico “Wolfram
Mathematica” nella versione
“Pilot release for Raspberry Pi”
Figura 11: Calcolatrice e terminale presenti nella distri-
buzione ufficiale Raspbian (Figura 13 e 14). Per chi conosce questo sof-
tware, specialmente in ambito universitario, tro-
9} Cors o C s u Raspber r y P i par t endo da zer o

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.

ELENCO DEI SOFTWARE


Oltre al software di base, ossia l’insieme dei co-
mandi di cui dispone il sistema operativo, sono
presenti tantissimi altri programmi utili. Di segui-
to sono elencati alcuni di essi, in ordine di appa-
Figura 13: Wolfram Mathematica nella versione “Pilot rizione nel relativo menù:
release for Raspberry Pi”
• Programming
• BlueJ Java IDE
• Greenfoot Java IDE
• Mathematica
• Python 2 (IDLE)
• Python 3 (IDLE)
• Scratch
• Sonic Pi
• Wolfram
Figura 14: Wolfram Mathematica al lavoro
• Office
• LibreOffice Base
Per gli amanti del diver-
timento, la distribuzione
ospita anche alcuni gio-
chi, tra i quali spicca “Mi-
necraft - Pi edition”. Vi
sono anche altri program-
mi ludici già precaricati e
altri ancora, ovviamente,
potranno essere scaricati
da Internet.
Ovviamente, e la cosa ci
interessa da vicino, non
manca il compilatore gcc
(Figura 15), che useremo
molto intensamente nel
presente corso. Esso ha
lo scopo di compilare i no- Figura 15: Il compilatore gcc
Cors o C s u Raspber r y P i par t endo da zer o {10

Tabella 2: I diversi modelli del Raspberry PI

• LibreOffice Calc • Claws Mail


• LibreOffice Draw • Epiphany Web Browser
• LibreOffice Impress • Raspberry Pi Resources
• LibreOffice Math • Games
• LibreOffice Writer • Minecraft Pi
• Internet • Python Ganes
11} Cors o C s u Raspber r y P i par t endo da zer o

• Accessories • Main Menu Editor


• Archiver • Mouse and Keyboard Settings
• Calculator • Raspberry Pi Configuration
• File Manager • Run
• Image Viewer • Shutdown
• PDF Viewer
• Task Manager CONCLUSIONI
• Terminal Bene, con la prima presentazione del Raspber-
• Help ry Pi termina questa prima puntata. Aspettateci
• Debian Reference per i prossimi articoli con tanti esperimenti inso-
• Raspberry Pi Help liti e interessanti, sulla programmazione in lin-
• The Magpi guaggio C e, soprattutto, sull’utilizzo delle porte
• Preferences di comunicazione. Vedremo come il Raspberry
• Appearance Settings Pi diventerà parte integrante, e funzionante,
• Audio Device Settings della nostra abitazione. Vi aspettiamo.

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

INTRODUZIONE stanza facile ma, come si vedrà in seguito, la

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.

IL LINGUAGGIO C Figura 1: I padri del linguaggio C


E’ un linguaggio di programmazione estrema-
mente potente e veloce. Lo supera, in veloci- IL GCC
tà, solo il linguaggio macchina. Il suo utilizzo è Si tratta di una fantastica collezione di compila-
molto semplice e un programma, a volte, sem- tori per tanti linguaggi di programmazione, come
bra una sorta di discorso umano (alto livello). il C, il C++, l’Objective-C, il Fortran, il Java, l’A-
La sua struttura permette, però, di accedere da, e il Go. Sono tutti compilatori gratuiti. Gli ag-
direttamente alle componenti interne del PC, giornamenti sono rilasciati abbastanza spesso
consentendo al programmatore di scrutarne e ed essi sono utilizzati ogni giorno letteralmente
modificarne i comportamenti più intimi e nasco- da milioni di programmatori.
sti (basso livello). Il presente corso sarà basato sul linguaggio C,
E’ talmente potente che con esso vengono rea- pertanto vedremo e cercheremo di raggiunge-
lizzati anche i Sistemi Operativi. La sua carat- re la più vasta esperienza possibile su questo
teristica più importante è la portabilità. Un co- potentissimo linguaggio. La maggior parte delle
dice sorgente scritto, ad esempio in Windows, operazioni sarà effettuata dalla console dei co-
può “girare” senza problemi anche su Linux. A mandi del Sistema Operativo del Raspberry Pi.
questo risultato si è arrivati dopo anni di dispute
e di battaglie, alla fine delle quali si è decretata VERSIONE DEL GCC
una sorta di standard universale, l’Ansi C. Dal momento che la cartella del compilatore è
Senza perdere ulteriore tempo prezioso, dicia- riconosciuta da qualsiasi directory in cui ci si tro-
mo che il linguaggio C è composto da molte va (grazie all’utilizzo delle variabili d’ambiente
funzioni che calcolano o eseguono qualcosa. del PATH), la compilazione può essere invoca-
La maestria nell’utilizzare tali funzioni e la mo- ta da qualsiasi locazione. La prima operazione
Cors o C s u Raspber r y Pi par t endo da zer o {14

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");
}

Figura 3: Creazione della cartella di lavoro

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

La compilazione non ha evidenziato un piccolo


./a.out “errore” tecnico: è stato volutamente omesso il
ritorno di un risultato finale, dal momento che
Il programma è immediatamente eseguito e il abbiamo scritto il programma all’interno di una
nostro primo obiettivo è pienamente raggiunto funzione. Come si vedrà in seguito, infatti, le
(figura 5). Ovviamente, in futuro, studieremo al- funzioni servono per calcolare qualcosa e de-
tre opzioni più complesse da passare al compi- vono restituire un risultato. Più avanti si vedrà
latore; al momento ci basta iniziare a prendere come abilitare la notifica degli errori di compila-
una minima confidenza con la programmazione zione.
C.
CALCOLI SUL CERCHIO
Il modo migliore per imparare a programmare è,
certamente, quello di sperimentare e provare.
Non preoccupatevi se, inizialmente, qualcosa
non funziona; perseverando e ritentando si arri-
va sicuramente al successo. Mai arrendersi!
Il successivo programma è già leggermente più
complesso del precedente e serve per entrare
in confidenza con alcuni nuovi concetti. Essi
possono così riassunersi:
• Come inserire con la tastiera i dati richie-
Figura 5: Prima esecuzione di “Salve mondo” sti del programma;
• Come utilizzare le variabili;
Tutto è andato come previsto. Possiamo rite- • Come effettuare calcoli aritmetici;
nerci molto soddisfatti. Gustiamoci un meritato • Cove visualizzare i risultati sul monitor.
caffè e meditiamo su quanto abbiamo fatto, con Sono queste le basi della programmazione. Il
particolare attenzione alle varie linee di codice programmatore deve investire un po’ del suo
sorgente. tempo per acquisire quella familiarità con il lin-
Il nostro listato inizia con l’inclusione del file guaggio che gli permetterà, in futuro, di affronta-
stdio.h. In esso sono memorizzati i prototipi del- re progetti sicuramente più pesanti e complessi.
le funzioni che permettono le operazioni di input
e output del programma. Quest’ultimo, compo- ANALISI DEL PROGRAMMA
sto solo da una procedura che sovrintende alla Il programmatore deve sempre progettare e
visualizzazione del messaggio “Salve mondo”, prevedere, a priori, il comportamento del suo
è contenuto nella funzione main(). Il linguaggio software. Non può improvvisare nulla, nell’inu-
C, infatti, esegue ciecamente tutto ciò che è tile ottica utopistica che il computer faccia mi-
contenuto nel corpo di tale funzione. lI carattere racoli. Tutto dipende dalle sue mani. Lui è la
“\n” ha lo scopo di portare il cursore a capo, in mente, il PC è solo un mezzo di calcolo, co-
modo da scrivere un rigo più in basso. modo e veloce, nulla di più. Se il programma
17} Cors o C s u Raspber r y P i par t endo da zer o

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.

Sono tre passi concettualmente semplici (per


una mente umana) ma che devono essere spie- Figura 7: Il sorgente C del programma del cerchio
gati e trasmessi ad una macchina “non intelli-
gente” con un diversa tipologia di approccio. Salviamo il programma, uscendo dall’editor pico
Diamoci la mano e vediamo come fare. e compiliamo il sorgente scrivendo, stavolta, il
Le prime cose che il programmatore deve cono- comando:
scere sono le formule del cerchio, il computer,
infatti, non le conosce (Figura 6). sudo gcc -Wall esempio02.c

Il parametro -Wall abilita i messaggi e le notifi-


che per qualsiasi tipologia di errore o di imperfe-
zione del codice. E’ molto comodo. Un program-
ma “perfetto” non deve visualizzare alcun tipo di
avvertimento.

#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

diverso nome e ogni programma in linguaggio C


printf("\n\n\n\n\n"); la deve obbligatoriamente contenere.
printf("Inserire la misura del raggio ");
/*------Dichiarazione variabili-----*/
scanf("%d",&raggio);
/*---Calcoli ed elaborazioni-----*/ Ed ecco il primo commento, quello che spie-
area=raggio*raggio*PI; ga, a chi legge, che la successiva sezione del
circ=2*PI*raggio;
programma serve per la dichiarazione delle va-
/*----Stampa dei risultati-----*/
printf("\n\n\n"); riabili. Si potrebbe fare a meno dei commenti,
printf("L'area misura %f \n",area); ma non è consigliabile. A lungo termine si vedrà
printf("La circonferenza misura %f \n \n",circ);
come essi sono utili.
return 0;
} int raggio;
Finalmente la nostra prima dichiarazione di una
Dettagliatamente spieghiamo ogni riga. Innan- variabile. Il compilatore vuol conoscere a prio-
zitutto, aspetto importantissimo, un program- ri tutte le variabili utilizzate, per riservare loro
matore deve sempre commentare il proprio il giusto spazio in memoria RAM. Con questo
programma. Un commento è una piccola e bre- statement comandiamo la creazione della va-
ve spiegazione, rivolta ad altri programmatori (e riabile “raggio” (qualsiasi altro nome va bene)
a se stessi), scritta direttamente nel listato sor- di tipo intero. Successivamente approfondiremo
gente. Per non interferire con il resto del codice, l’aspetto delle tipologie.
i commenti devono essere scritti tra i caratteri /* float area,circ;
e */ (asterisco e backslash). Il listato proposto Anche in questo caso si dichiarano due variabi-
è particolarmente commentato e fornisce una li, stavolta con la virgola (float). Perché questa
buona spiegazione delle varie parti che lo com- scelta? Qui interviene l’istinto e l’esperienza del
pongono. programmatore: sia l’area che la circonferenza
#define PI 3.141592654 saranno sempre dei numeri decimali, in quan-
Il linguaggio C non conosce il pi-greco e non to nei relativi calcoli c’è coinvolto il pi-greco. Se
sa nemmeno cosa sia un cerchio. Con questa le dichiarassimo di tipo intero, si perderebbero
dichiarazione insegnamo al PC che la parola i numeri dopo la virgola, con conseguenze disa-
magica PI vale circa 3,14. Non è una costante strose.
ma una sorta di “jolly”. Il compilatore sostituisce /*---Richiesta dei dati----*/
ogni occorrenza di PI con 3.14. Un po’ come la Da questo punto inizia la parte dedicata alla ri-
funzione trova e sostituisci che, in questo caso, chiesta dei dati, da parte dell’elaboratore. Sarà,
è svolta dal preprocessore. infatti, il PC a fare delle domande all’operatore.
#include <stdio.h> Questa sezione è definita “input” del program-
Come detto per il precedente esempio, il file ma.
stdio.h contiene i prototipi delle funzioni di input printf(“\n\n\n\n\n”);
e output. Per evitare che i messaggi a seguire siano scrit-
int main() { ti “attaccati” al resto della precedente visualiz-
Dichiara una funzione, nel corpo della quale zazione, decidiamo di fare un po’ di pulizia del
l’intero programma è eseguito. Non può avere video, scendendo il cursore di cinque linee più
19} Cors o C s u Raspber r y P i par t endo da zer o

in basso e creare, in tal modo, un po’ di spazio circ=2*PI*raggio;


sottostante. Anche questo è un calcolo matematico. Si trat-
printf(“Inserire la misura del raggio “); ta di un’altra assegnazione: il prodotto di 2 per
Questa funzione ha il solo scopo di visualizzare 3,14 per raggio è memorizzato nella variabile
il messaggio di invito. Non si aspetta alcun inse- “circ”, fino a eventuale nuovo ordine.
rimento di valori ma si limita a chiedere il raggio. /*----Stampa dei risultati-----*/
scanf(“%d”,&raggio); Finalmente siamo nella fase comunicativa, nella
Ecco l’importante funzione di input. Essa “bloc- quale i risultati, fino ad ora tenuti nascosti nei
ca” e congela il programma e aspetta l’immis- meandri del PC, sono estrapolati e resi noti al
sione della misura del raggio, tramite la tastiera. mondo esterno, attraverso il monitor.
L’utente deve inserire, in questa fase, un valore printf(“\n\n\n”);
numerico e premere, quindi, il tasto <Invio> per Con questa funzione si va tre volte a capo, per
confermare il dato. Fino a quando l’operatore creare un po’ di spazio sul video.
non effettua tale input, il programma resterà fer- printf(“L’area misura %f \n”,area);
mo a vita. Per il momento prendiamo la sintassi Si tratta di un primo messaggio che visualizza il
così com’è. Sappiate solo che il numero inserito valore dell’area sullo schermo. Il “segnaposto”
andrà a memorizzarsi all’interno della variabile %f contrassegna il punto in cui sarà visualizzata
“raggio”, definita in precedenza di tipo intero, la variabile “area”, di tipo float.
proprio come se quest’ultima fosse una scatola. printf(“La circonferenza misura %f \n
/*---Calcoli ed elaborazioni-----*/ \n”,circ);
Ecco la fase operativa più interessante. Il pro- Anche questa visualizzazione mostra a video
gramma, che fino ad ora ha svolto solo funzioni il valore della variabile “circ”, anch’essa di tipo
di input e di output, di colpo diventa “intelligente” float.
e capace di eseguire cal-
coli matematici. Calcoli
che, in realtà, gli sono
stati insegnati dal pro-
grammatore.
area=raggio*raggio*PI;
Questa di sopra è un’o-
perazione matematica di
assegnazione. L’elabo-
ratore calcola material-
mente una moltiplicazio-
ne tra raggio per raggio
per 3,14 e memorizza
l’intero prodotto nella va-
riabile “area”, di tipo flo-
at. Figura 8: Esecuzione e risultati del programma del cerchio
Cors o C s u Raspber r y P i par t endo da zer o {20

return 0; vedrà che la passione accrescerà a dismisura,


Il programma finisce qui è il controllo passa al una volta capita la filosofia di base e la fame di
sistema operativo (figura 8). nuove informazioni comincerà a farsi sentire. Si
Le parentesi graffe racchiudono un intero bloc- consiglia di leggere più volte l’articolo per co-
co logico, trattato quindi come un’unica identità gliere, di volta in volta, le diverse sfumature. Il
esecutiva. fatto di esaminare il listato riga per riga, come fa
un debugger, è sicuramente un fatto proficuo,
ai fini dell’acquisizione delle migliori tecniche di
CONCLUSIONI programmazione.
Abbiamo rotto il ghiaccio con la programma-
zione, il lettore provi fino alla nausea quanto ri- Alla prossima puntata, dunque, con ulteriori
portato, eventualmente aggiungendo modifiche interessanti approfondimenti sul linguaggio
personalizzate e variazioni sul tema. Si C.

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

Corso C su Raspberry PI partendo


da zero: cicli e condizioni
di Giovanni Di Maria

INTRODUZIONE sempre pronto con la giusta sequenza di codice

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,

Tabella 1: Tensione, corrente e corrispondente potenza


Cors o C s u Raspber r y Pi par t endo da zer o {22

ovviamente, generata dal computer. Senza un opportuna gestione del problema, un


I valori dei Volt e degli Ampere sono già noti a programmatore sprovveduto scriverebbe un co-
priori e, come si vede, incrementano di 5 in 5 e dice molto lungo, magari funzionante ma non
di 3 in 3 (per una nostra libera scelta). Il valore funzionale ed ottimizzato. Si limiterebbe, infatti,
della potenza, invece, è calcolato sulla base a scrivere e determinare i calcoli, riga per riga, e
degli altri due, secondo la nota formula della visualizzarli. Ecco di seguito un esempio sba-
legge di Ohm: gliato di programma, assolutamente da non
W=V*I seguire.

#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

listato una volta sola, permette di ripetere


#include <stdio.h> il codice contenuto innumerevoli volte (an-
int main() {
che infinite). Volendo, ad esempio, scrivere a
int volt,ampere,watt,k;
for(k=1;k<=10;k++) { video, dieci volte, il messaggio “tanti auguri”, è
volt=k*5; sufficiente implementare il seguente codice:
ampere=k*3;
watt=volt*ampere;
printf("%2dV %2dA = %4dW\ for(k=1;k<=10;k++)
n",volt,ampere,watt); printf("Tanti auguri\n");
}
return 0;
} invece di scrivere, per ben dieci volte, altrettan-
te funzioni printf.

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.

chiuso tra le parentesi graffe). La sua sintassi è


la seguente: Paragoniamo il ciclo iterativo “for” con una gio-
stra che gira. Al primo giro, il valore della va-

for(k=1;k<=10;k++) riabile “k” sarà 1 (il limite inferiore). Al secondo


giro, il valore di k sarà due, e così via. Tutti i

• 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

• Il secondo parametro, k<=10, specifica la ciclo, per evitare risultati imprevisti.

condizione di vita del ciclo stesso. Esso Eseguendo il programma finale, il risultato vi-

“funzionerà” sino a quando il valore dell’i- sualizzato sarà quello di figura 1.

teratore k risulta minore o uguale a 10.


Anche questo limite può essere variato
a piacimento, utilizzando altri operatori di
confronto.
• Il terzo parametro, k++, indica al linguag-
gio che il valore di k, durante i “giri” di gio-
stra, aumenterà, di volta in volta, di una
unità. Un incremento, ad esempio di quat-
tro, potrebbe essere implementato con Figura 1: Output del programma

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

4. Infine si calcola la tensione su queste un po’ complicato, ma mettendo assieme i vari


quattro resistenze (V=R*I). tasselli, un po’ per volta, il tutto risulterà abba-
Si tratta di calcoli molto semplici, applicati stanza comprensibile. Il listato completo è elen-
con la legge di Ohm, che saranno ripetuti dal cato sotto. Leggiamolo con molta attenzione e
Raspberry Pi per migliaia di volte al secon- commentiamo i punti salienti. Vi sono tanti nuovi
do. concetti da imparare.
Il progetto che ci accingiamo a fare, in effetti, è Facendo “girare” il programma per alcuni minuti

#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.

Il codice è lungo ma ben commentato e suddi- Ciclo infinito


viso in diverse parti logiche. Esaminiamole as- Questa è la parte di codice che viene continua-
sieme. mente, e indefinitamente, eseguita dall’elabo-
ratore. Senza il ciclo, il PC eseguirebbe tutto
Files include il programma una sola volta, generando in un
solo tentativo i valori e terminando subito la
Come detto nel precedente articolo, i files di procedura. Un algoritmo, per funzionare bene,
inclusione (che hanno estensione “.h”) conten- deve essere sempre iterato e ripetuto.
gono i prototipi delle funzioni utilizzate nel pro-
gramma. Nel nostro esempio: Genera casualmente il valore di 6 resistenze
• stdio.h include la funzione printf(); Da questo punto inizia il programma vero e pro-
• stdlib.h include la funzione rand(). prio. L’elaboratore genera “a caso” sei numeri
compresi tra 100 e 30099. La particolare struttu-
Dichiarazioni ra dello statement effettua, infatti, delle estrazio-
Le variabili vanno dichiarate all’inizio con una ni a sorte. Come fa il computer a estrarre in ma-
certa attenzione. Il linguaggio C è altamente niera aleatoria un numero? Grazie alla funzione
“tipizzato”. Vin e Vout sono stati definiti di tipo rand() che genera un valore compreso tra 0 e
float. Se per Vout il motivo è ovvio (dopo tante 32767. L’adozione dell’operatore “%” effettua
divisioni è normale che qualche virgola salti fuo- una forzatura ed una limitazione al limite supe-
ri), per Vin si potrebbe, in futuro prevedere un riore dei valori consentiti. Ad esempio, se si vuol
valore decimale, al posto di 14V. Il valore delle generare un numero tra 1 e 6 (per simulare il
sei resistenze è di tipo unsigned int. Si tratta, lancio di un dado) si deve invocare la seguente
dunque, di “contenitori” che ammettono valori assegnazione:
Cors o C s u Raspber r y P i par t endo da zer o {28

dado=rand() % 6 + 1; potenza, è necessario utilizzare la variabile “mil-


In questa maniera, il risultato prodotto dalla fun- liwatt” come naturale moltiplicazione della ten-
zione rand, per quanto grande sia, sarà “ade- sione di alimentazione per quello della corrente
guato” al nuovo range 1:6. Per maggiori dettagli circolante (W=V*I). Dal momento che il calcolo
sull’estrazione di valori casuali, si consulti il se- è effettuato in milliwatt, si deve moltiplicare il ri-
guente articolo. sultato per 1000.

Calcola la resistenza totale Se la tensione è 8,5V e i Watt sono meno an-


Come si sa in elettronica, sei resistenze colle- cora
gate in serie tra loro corrispondono ad un unico Ed eccoci alla condizione di stampa. I risulta-
resistore di valore pari alla somma del valore ti sono visualizzati sul monitor solo se la ten-
dei sei componenti. La variabile Rtot contiene, sione d’uscita dal partitore è esattamente 8,5V
pertanto, tale valore. ed anche (&&=”and” logico) se i milliwatt dissi-
pati sono di meno della massima dissipazione
Calcola la corrente nel circuito fino a quel momento trovata (infatti la variabi-
Alla variabile “corrente” è assegnato il valore le maxdissipazione è impostata inizialmente a
della corrente circolante nel circuito, grazie alla 10000).
legge di Ohm. Il suo valore è calcolato dividen- Grazie a questa tecnica è possibile stabilire,
do la tensione di alimentazione per quello della eventualmente, un nuovo “primato” quando le
resistenza totale del carico. condizioni lo prevedono. Una sorta di “record
del mondo” stabilito ogni tanto e di cui viene te-
Calcola la resistenza equivalente delle ulti- nuta traccia.
me 4 resistenze La programmazione è un’arte logica e il let-
La variabile “Rtot4” contiene, invece, il valore tore cerchi, con tutte le sue forze, di com-
della resistenza equivalente delle ultime quattro prendere alla perfezione tutti questi concetti.
resistenze. Esso è utile per la successiva deter-
minazione della tensione ai suoi capi. PARI E DISPARI
Per diventare bravi programmatori occorre scri-
Calcola tensione di uscita dal partitore al vere tanto codice, provarlo, modificarlo e rite-
nodo starlo, magari sbagliando. Anzi, sono gli errori
Finalmente, lo scopo del programma, è quello che “fortificano” la preparazione perché con-
di determinare la tensione uscente dal partitore sentono di perlustrare, con uno spirito critico
resistivo. La variabile Vout (vedi schema elettri- più incisivo, il codice, alla ricerca del più piccolo
co di figura 2) è calcolata con la legge di Ohm, “bug”.
moltiplicando il valore della resistenza di carico Come ultima prova proponiamo un semplicissi-
per quello della corrente che vi passa (V=R*I). mo algoritmo:
1. Il PC chiede un numero intero (n);
Calcola la dissipazione totale nel circuito 2. Se n è pari sarà visualizzato il messaggio
Al fine di monitorare la minore dissipazione in “n è PARI”;
29} Cors o C s u Raspber r y P i par t endo da zer o

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

Tabella 2: Tipo e dimensione

variabile, nella memoria RAM. Esso è, tuttavia,


abbastanza relativo poiché l’effettiva memoria printf("Float %d\n",sizeof(v5));
richiesta da ogni tipologia di variabile può varia- printf("Double %d\n",sizeof(v6));
printf("Long Double %d\n",sizeof(v7));
re sensibilmente con l’architettura della propria
printf("Long long %d\n",sizeof(v8));
CPU. return 0;
Il seguente listato sorgente, eseguito sulla pro- }
pria macchina, mostra l’effettiva e reale occupa-
zione. Il suo output potrebbe cambiare, quin-
di, da macchina a macchina. Il programma
si basa sull’utilizzo della funzione “sizeof”, che
restituisce il numero di byte occupati dalla va-
riabile specificata tra i suoi parametri (Figura 7).

#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

Corso C su Raspberry PI partendo


da zero: vettori e matrici
di Giovanni Di Maria

INTRODUZIONE possono assumere due distinti stati (On e Off),

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...”.

UN ESEMPIO... QUASI ELETTRONICO


Il seguente esempio mostra un po’ l’embrione
di ciò che creeremo in futuro. Si debba creare
un gioco di luci nel quale delle ipotetiche lampa- Figura 1: Tabella gioco di luci

de (che al momento non esistono ma sono solo


virtuali) si accendono, seguendo una sequenza Come si vede in figura 1, il gioco di luci pre-
ben precisa ma complessa. vede ben 16 combinazioni diverse di illumina-
Il circuito virtuale è composto da otto lampadine zione, opportunamente codificate secondo una
(guarda caso...) e ogni combinazione luminosa semplice numerazione binaria. Sarà il micro-
corrisponde ad un codice ben preciso. Il numero processore ad inviare al circuito la sequenza di
delle lampade non è casuale: otto elementi, che codici e, di conseguenza, illuminare le corrette
33} Cors o C s u Raspber r y P i par t endo da zer o

lampade. Nell’esempio non ci occuperemo, ov-


codice=129;
viamente, della circuiteria elettronica ma è suf-
printf(“Codice lampade: %d\n”,codice);
ficiente stampare, a video, la sequenza, in un codice=66;
ciclo senza fine, che è: 1, 2, 4, 8, 16, 32, 64, printf(“Codice lampade: %d\n”,codice);
128, 255, 0, 24, 36, 66, 129, 66, 0. codice=0;
printf(“Codice lampade: %d\n”,codice);
}
LISTATO NON OTTIMIZZATO return 0;
Un primo approccio “stupido” porterebbe il }

programmatore a creare tante funzioni “printf”


Si tratta di uno stupidissimo programma che
quanti sono i codici da stampare. Ma un gioco
“tira fuori” dei numeri, ripetendo all’infinito l’al-
di luci composto non da sedici combinazioni
goritmo. Anche un principiante si accorge subito
(come il nostro) ma da centinaia o migliaia di
che questo approccio, benché funzionante, non
soluzioni, produrrebbe listati sorgenti chilometri-
è gestibile. Vediamo, dunque, come ottimizzare
ci, composti da tantissime righe di programma.
ed accorciare il codice, rendendolo più efficien-
Proponiamo, in ogni caso, questo esempio, as-
te, dopo aver visionato (Figura 2) il risultato pro-
solutamente da non seguire.
dotto dal precedente software.
#include <stdio.h>
int main() {
unsigned char codice;
while(1) {
codice=1;
printf(“Codice lampade: %d\n”,codice);
codice=2;
printf(“Codice lampade: %d\n”,codice);
codice=4;
printf(“Codice lampade: %d\n”,codice);
codice=8;
printf(“Codice lampade: %d\n”,codice);
codice=16;
printf(“Codice lampade: %d\n”,codice);
codice=32;
printf(“Codice lampade: %d\n”,codice);
codice=64;
printf(“Codice lampade: %d\n”,codice);
codice=128; Figura 2: Il risultato del gioco di luci non ottimizzato
printf(“Codice lampade: %d\n”,codice);
codice=255;
IL VETTORE
printf(“Codice lampade: %d\n”,codice);
codice=0; Può essere chiamato in tanti modi: vettore, ar-
printf(“Codice lampade: %d\n”,codice); ray, tabella unidimensionale o monodimensio-
codice=24;
nale. E’ una specie di variabile che, anziché
printf(“Codice lampade: %d\n”,codice);
codice=36; avere un unico “cassetto” per contenere i dati, è
printf(“Codice lampade: %d\n”,codice); formato da tanti contenitori. E’ quindi un insieme
codice=66; di valori, contrassegnati dallo stesso nome di
printf(“Codice lampade: %d\n”,codice);
Cors o C s u Raspber r y P i par t endo da zer o {34

3. Stampa automatica sequenziale dei codi-


ci, con indice incrementale.
Si esamini bene il seguente listato, equivalen-
te al precedente, ma molto ridotto ed efficien-
te, sia in termini di velocità che spazio occupa-
to su RAM e disco.

Figura 3: Struttura di un vettore


LISTATO OTTIMIZZATO
L’utilizzo del vettore ha, così, permesso di scri-
variabile, ma differenziati attraverso un nume-
vere un codice molto compatto, composto sola-
ro d’ordine, o indice. La figura 3 mostra chiara-
mente da nove righe, contro le trentanove del
mente la struttura di un array. Ogni elemento è
precedente. Non si tratta solo di un risparmio
rintracciabile da un indice, che inizia sempre da
del 25% di programma, ma di una completa ef-
zero. La sua dichiarazione, in un programma, è
ficienza a livello procedurale.
identica a quella della variabile. E’ sufficiente,
in aggiunta, specificare il numero degli ele-
#include <stdio.h>
menti tra parentesi quadre.
int main() {
unsigned char codi-
SCORRIMENTO DI UN VETTORE ce[16]={1,2,4,8,16,32,64,128,255,0,24,36,66,129,66,0};
int k;
Una delle caratteristiche più importanti del
while(1)
vettore, oltre che a contenere tanti valori in for(k=0;k<16;k++)
un unico “armadio”, è quella di poter esse- printf("Codice lampade: %d\n",codice[k]);
re “analizzato” automaticamente, elemen- return 0;
}
to per elemento. Per far ciò, è sufficiente
scandire l’array all’interno di un ciclo “for”.
L’output del programma è il medesimo. Esami-
Scandire significa esaminare un elemento per
niamo in dettaglio le righe di codice, approfon-
volta, al fine di osservarlo, modificarlo, ecc. Altre
dendone anche i comportamenti algoritmici e
applicazioni utili degli array sono la memorizza-
logici.
zione, in RAM, di tanti valori per la loro gestione
e l’ordinamento numerico o alfabetico di una
unsigned char codice[16]={1,2,4,8,16,...};
collezione di dati.
Il listato che segue risolve ed ottimizza egre-
Si tratta di una dichiarazione di variabile a tutti
giamente il problema del gioco di luci, e la
gli effetti. Comunica al compilatore che si è cre-
metodologia adottata riduce drasticamente
ato un vettore composto da sedici elementi (di-
la lunghezza del codice sorgente, eseguen-
slocati dalla posizione 0 alla posizione 15). Gli
do le seguenti operazioni:
elementi in esso contenuto sono di tipo “unsig-
1. Dichiarazione del vettore;
ned char” che possono, quindi, gestire numeri
2. Memorizzazione dei codici all’interno del
compresi tra 0 e 255. Gli elementi in parentesi
vettore;
graffe effettuano anche l’assegnazione dei ri-
35} Cors o C s u Raspber r y P i par t endo da zer o

spettivi valori in altrettante posizione dell’array. come “rallentare” il programma. Se è vero


Il valore 1 è memorizzato nella posizione 0, il che in alcune situazioni (vedi nella ricerca ma-
valore 2 nella posizione 1, il valore 4 nella po- tematica) la velocità è un fattore predominante,
sizione 2, e così via... In una sola riga di codice in altri casi è necessario abbassare la velocità di
abbiamo effettuato, dunque, una dichiarazione esecuzione dei programmi.
e una contestuale assegnazione complessa di Il programma precedente, se eseguito, mo-
valori. stra a video, in rapidissima sequenza, i codici
operativi memorizzati in array. Il suo utilizzo è
printf(“Codice lampade: %d\n”,codice[k]); prettamente inutile. Sarebbe altrettanto inutile
se esistesse il vero circuito elettrico con le otto
Esaminiamo prima questa funzione per com- lampadine: esse si accenderebbero e spegne-
prenderla meglio. Essa è contenuta all’interno rebbero milioni di volte al secondo. Il gioco di
del ciclo “for”. Dal momento che si tratta di una luci non si potrebbe gustare.
sola funzione, non sono necessarie le parentesi Pensiamo alle luci dell’albero di Natale: la loro
graffe, che evidenziano un blocco di istruzioni frequenza di lampeggio è molto ridotta ed è
(come detto nel precedente articolo). Guardia- possibile osservarne le evoluzioni. Diciamo che
mo l’oggetto della visualizzazione del printf: il per essere notate dall’uomo le lampade dovreb-
programma visualizza il contenuto di codice[k], bero lampeggiare con una frequenza compresa
e dal momento che la variabile k passa in rasse- tra 0,5 Hz e 5 Hz.
gna i valori da 0 a 15, saranno mostrati a video
i valori di codice[0], codice[1], codice[2], ecc. Il COME SI ABBASSA LA VELOCITÀ DI
ciclo for, per essere ripetuto infinite volte, è con- ESECUZIONE
tenuto, a sua volta, in un “while” infinito. Anche Esistono alcune tecniche per raggiungere lo
in questo caso non sono necessarie le parente- scopo. La prima, più precisa ma complicata,
si graffe. In pratica, a livello gerarchico: prevede l’utilizzo del timer interno del compu-
• Il ciclo “while” contiene il ciclo “for”; ter. Consente di ottenere temporizzazioni estre-
• Il ciclo “for” contiene la funzione “printf”. mamente precise, nell’ordine dei microsecondi,
In realtà, l’intera sequenza potrebbe essere ge- ma implica l’utilizzo di particolari librerie e sofi-
nerata in modo efficiente utilizzando complica- sticate funzioni. Al momento non la tratteremo.
tissimi algoritmi di regressioni lineari e di cur- La seconda è più semplice ma meno precisa.
ve fitting, ma questa è un’altra storia. Per raggiungere un buon grado di affidabilità
deve essere testata svariate volte ed è quello
COME SI GESTISCE UN RITARDO che faremo adesso. La tecnica è definita loop
Ai giorni d’oggi, negli appuntamenti, si cerca di di ritardo. E’ un ciclo “for” all’interno del quale
essere il più puntuali possibile. Il linguaggio C non c’è nulla. Il programma, in pratica, conta da
(come anche altri linguaggi di programmazio- 1 a n perdendo, appunto, del tempo. Per un ap-
ne) è estremamente veloce e non consente di prezzabile ritardo il conteggio deve proseguire
osservare le evoluzioni esecutive con il proprio oltre il milione, viste le velocità in gioco.
occhio. In questo capitolo vi insegneremo Il seguente esempio chiarisce ogni dubbio.
Cors o C s u Raspber r y P i par t endo da zer o {36

Dopo aver dichiarato la variabile “t” con una suf- LE MATRICI


ficiente ampiezza, l’enunciato: Mentre il vettore possiede solo una dimensione,
la matrice è caratterizzata da due dimensioni,
for(t=1;t<=100000000;t++); altezza e larghezza. E’ molto usato per memo-
rizzare tabelle bidimensionali e dati di forma
non fa altro che contare da 1 a cento milioni, quadrata e rettangolare. Esistono anche matrici
perdendo del tempo e bloccando l’esecuzione a tre, quattro, cinque dimensioni ma il procedi-
del programma per tutto il periodo del conteg- mento di gestione è il medesimo.
gio. Lo svantaggio di questa tecnica è che il Una matrice bidimensionale, per poter essere
ritardo varia da computer a computer. Non gestita e processata, deve adottare due indici:
tutte le CPU sono, infatti, uguali e un ritardo di uno che esplora i dati in orizzontale e l’altro in
quattro secondi su una macchina potrebbe cor- verticale. Con alcuni esempi chiariremo meglio
rispondere a una pausa di attesa di dieci secon- il concetto di utilizzo.
di su un’altra macchina. Si noti il punto e virgola
a fine funzione. Esso attesta che all’interno di LA SCACCHIERA
essa non c’è nulla e si tratta solo di un loop a Le matrici sono molto usate per rappresentare
vuoto, ossia di una funzione che solo lo scopo ambienti bidimensionali, come scacchiere, ta-
di perdere del tempo e basta. belle, ed altro. Il prossimo esempio, molto inte-
Il listato precedente, con l’implementazione del- ressante, ha la seguente finalità:
la pausa, è il seguente. Notate la ricomparsa si ha a disposizione una scacchiera per gioca-
delle parentesi graffe per il ciclo for più esterno. re a scacchi. La sua dimensione è, ovviamente,
Con il limite impostato nel conteggio, il ritardo di 8x8. Su alcune caselle sono poggiati alcu-
ammonta a circa 500 ms (Figura 4). per ciclo ni pezzi del gioco, come cavalli, alfieri, regine,
(almeno nel mio PC). ecc. Il programma deve contare quanti pezzi ci
sono sulla scacchiera e
#include <stdio.h> di quale tipo. Vedrete che
int main() {
unsigned char codice[16]={1,2,4,8,16,32,64,128,255,0,24,36,66,129,66,0}; l’analisi e la codifica sono
int k; estremamente semplici
unsigned long t; e, come al solito, l’impor-
while(1)
tante è partire con il piede
for(k=0;k<16;k++) {
printf("Codice lampade: %d\n",codice[k]); giusto.
for(t=1;t<=100000000;t++); /* Conta da 1 a cento milioni */ Al fine della semplicità e
}
della generalizzazione
return 0;
} dell’algoritmo, preferiamo
gestire solamente i se-
guenti pezzi, indipendentemente dal
loro colore (bianco o nero):
• Re, che codifichiamo con il codice
Figura 4: gioco di luci animato 100;
37} Cors o C s u Raspber r y Pi par t endo da zer o

• Donna, con codice 50; Ecco, in figura 6, l’implementazione della matri-


• Torre, con codice 25; ce con i relativi valori numerici.
• Alfiere, con codice 15;
• Cavallo, con codice 10;
• Pedone, con codice 1;
• Casella vuota, con codice 0.
Questa assegnazione di valori è personale e
dipende esclusivamente dal programmatore.
E’ importante far capire al computer la tipologia
di pezzo che occupa una casella, grazie all’a-
dozione di un numero (es: 50 per la Donna e
non il suo nome). Anzi, tale strategia è molto
più efficiente e semplice allo stesso tempo. La
scacchiera da rappresentare come matrice è vi- Figura 6: Valori dei pezzi nella matrice.
sualizzata in figura 5.
Il listato sorgente è estremamente semplice. Lo
commentiamo dopo averlo digitato, compilato
ed eseguito (Figura 7).
#include <stdio.h>
int main() {
unsigned char scacchiera[8][8] = { {0,0,0,0,0,0,0,
0},{1,0,10,50,100,1,0,1},{0,0,0,0,10,0,1,0},{0,50,0,0,0
,0,0,0},{0,0,0,0,1,0,25,0},{0,0,0,100,0,0,1,0},{15,0,0,0
,0,0,0,1},{0,0,0,0,0,0,0,0} };
int x,y;
int tot_re,tot_donne,tot_torri,tot_alfieri,tot_
cavalli,tot_pedoni;

/*---------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

unsigned char scacchiera[8][8] = {


for(y=0;y<8;y++) { {0,0,0,0,0,0,0,0},......
if(scacchiera[x][y]==100)
tot_re++;
if(scacchiera[x][y]==50) Come visto per il precedente software, que-
tot_donne++; sto statement effettua una doppia funzione di
if(scacchiera[x][y]==25) dichiarazione matrice e di assegnazione. In
tot_torri++;
if(scacchiera[x][y]==15) questo caso i valori da applicare sono scritti co-
tot_alfieri++; modamente in una sola riga di programma. E’
if(scacchiera[x][y]==10) possibile, in alternativa, assegnare ogni singolo
tot_cavalli++;
elemento, in questa maniera:
if(scacchiera[x][y]==1)
tot_pedoni++;
} scacchiera[0][0]=0;
scacchiera[0][1]=50;
/*---------Visualizza totali-------*/
printf("\n"); scacchiera[0][2]=100;
printf("Sulla scacchiera ci sono:\n\n");
printf("%2d Re\n",tot_re);
In questo modo occorrerebbero ben 64 righe di
printf("%2d Donne\n",tot_donne);
printf("%2d Torri\n",tot_torri); codice. Tale metodo è, tuttavia, comodo, per ri-
printf("%2d Alfieri\n",tot_alfieri); ferirsi al singolo elemento di una matrice o di un
printf("%2d Cavalli\n",tot_cavalli);
vettore.
printf("%2d Pedoni\n",tot_pedoni);

return 0; Azzeramento valori


} Questa parte di programma è dedicata all’az-
zeramento iniziale degli accumulatori, cosa ob-
bligatoria in quanto gli stessi incrementeranno
di una unità, a seconda del pezzo trovato sulla
scacchiera.

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

gramma esamina il valore in ogni cella, incre- Visualizza totali


mentando il corretto accumulatore a seconda Questo semplicissimo “pezzo” di codice, for-
del suo contenuto. La clausola “if” non necessi- mato solo da funzioni “printf”, mostra a video
ta di parentesi graffa, dal momento che lo state- il contenuto dei conteggi effettuati. Le variabili
ment che segue è uno solo. Nel secondo ciclo utilizzate in questo caso, dal momento che in-
di “for”, invece, ossia quello che incrementa la crementano di volta in volta il loro valore di una
variabile y, le parentesi graffe sono necessarie, unità, sono detti accumulatori.
poiché le condizioni al suo interno sono ben sei
(Figura 8). CONCLUSIONI
Lo studio dei vettori e delle matrici è un
passo obbligatorio per chi studia il linguag-
gio C. La loro utilità è massima quando i
dati da gestire sono molti e si devono ana-
lizzare e processare anche dopo qualche
tempo. Anche in questo caso, lo studio e le
sperimentazione di listati sono necessari
alla corretta interpretazione dell’argomen-
to. Alla prossima puntata.

Figura 8: Il doppio indice perlustra una matrice

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

Corso C su Raspberry PI partendo


da zero: il controllo del video
di Giovanni Di Maria

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);

Al carattere ESC deve seguire la parentesi qua-


dra aperta. Solitamente si utilizza la prima solu-
zione. Andiamo adesso a perlustrare le grandio-
Figura 1: Conteggio da 1 a 1000 se possibilità offerte dalle sequenze di Escape
e a verificare come esse possano radicalmente
LE SEQUENZE DI ESCAPE modificare la normale visualizzazione dei dati
Per ottenere un maggiore controllo del video, sullo schermo.
specialmente nei sistemi Unix e Linux, si pos-
sono usare utilmente le sequenze di Escape. CANCELLAZIONE DELLO SCHERMO
Si tratta di particolari sequenze di caratteri che, Qualsiasi tipologia di programma, dalla più sem-
inviate allo schermo tramite opportuna funzio- plice alla più complessa, deve sempre “pulire”
ne (printf() o altra) ,producono svariati risultati e il video all’inizio della sua esecuzione. Non si
modificano il normale flusso dei dati. Si chiama- possono, infatti, impastare i vecchi dati presenti
no anche caratteri di controllo. Sono, in pratica, con i risultati appena elaborati, si verificherebbe
delle successioni di caratteri non visualizzabili, una confusione pazzesca. Un semplice metodo
che causano effetti, a volte, molto utili ed inte- alternativo, che funziona sempre, è quello di ini-
ressanti. Come testimonia il loro nome, esse ziare il proprio listato con una emissione molto
devono iniziare con il carattere di Escape (Ascii lunga di “ritorno a capo”, in modo da far scende-
Cors o C s u Raspber r y P i par t endo da zer o {42

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

• Input dati: Ad ogni posi-


zionamento del cursore viene
effettuato l’input delle variabili.
Adesso il programma si com-
porta con una procedura com-
merciale, nella quale il cursore
“salta” da una parte all’altra
dei campi di inserimento, per
permettere l’input dei dati. Si
noti che per le stringhe non è
necessario utilizzare il punta-
tore nella funzione scanf.
• Riepilogo: In questa sezio-
Figura 3: Posizionamenti di cursore ne i dati sono visualizzati nelle
rispettive coordinate. Questa
del video, nella quale l’utente deve inserire, da
ha lo scopo, soprattutto, di far
tastiera, il proprio cognome, il nome, l’età e l’in-
comprendere come la visualizzazione di
dirizzo email. Gli stessi dati saranno riscritti, dal
un dato può essere effettuata contestual-
sistema, nella finestra situata più in basso, dopo
mente al posizionamento del cursore, tut-
essere stati confermati dall’utente (figura 3).
to in un unico statement. Per stampare la
Il listato è suddiviso in sezioni. Commentiamole
stringa viene usato il segnaposto “%s”.
assieme, una per una.
• Dichiarazione variabili: in questa sezio-
CREIAMO LE FUNZIONI PERSONA-
ne vengono ovviamente dichiarate le va-
LIZZATE
riabili utilizzate. Occorre fare una partico-
Benché le sequenze di Escape siano molto effi-
lare nota per le variabili cognome, nome,
caci, esse rendono un po’ ostico ed illeggibile il
email, considerate come array di caratteri
codice sorgente. Una valida soluzione è quella
e aventi funzione di stringa a tutti gli effetti.
di creare alcune funzione personalizzate (UDF),
• Intestazione: Lo schermo è pulito e can-
di semplice utilizzo, che sovrintendano al posi-
cellato. Poi il cursore è posizionato alla
zionamento e alla visualizzazione dei dati. Dal
coordinata 2,20 ed ivi viene visualizzata
momento che l’informazione da stampare, al-
una doppia linea, composta dal simbolo
meno per il momento, può essere di tipo stringa
“=”. Quindi il cursore è portato alla co-
“%s” o di tipo numerico intero (“%d”), possiamo
ordinata 3,22 e qui la stringa “Gestione
creare due diversi prototipi, ognuno dei quali si
CLIENTI” viene visualizzata.
interessa della propria tipologia di dato:
• Visualizza etichette: In questa sezione
• PrintStrAt(r,c,str): visualizza la stringa
vengono visualizzate le etichette Cogno-
“str” alla riga “r” e alla colonna “c”;
me, Nome, Età ed Email alle rispettive co-
• PrintIntAt(r,c,n): visualizza il numero “n”
ordinate di schermo.
alla riga “r” e alla colonna “c”;
45} Cors o C s u Raspber r y P i par t endo da zer o

• InputStrAt(r,c,str): effettua l’input della FUNZIONE PER DISEGNARE DEI BOX


stringa “str” alle coordinate r,c; Una funzione standardizzata e generica per il
• InputIntAt(r,c,n): effettua l’input dell’inte- disegno immediato di un rettangolo o di un box
ro “n” alle coordinate r,c; è necessaria e molto comoda (vedi figura 4).
• CursorAt(r,c): posiziona il cursore sullo Con un solo statement essa rende possibile il
schermo alle coordinate r,c; disegno di un quadrato che, altrimenti, richiede-
• cls(): si occupa della cancellazione del vi- rebbe molte righe di codice e alcuni calcoli di
deo. posizionamento dei caratteri. La funzione che
andiamo a descrivere è parametrizzata e la sua
Una siffatta programmazione rende il codice sintassi è la seguente:
estremamente modulare ed elegante. La leggi-
bilità è estrema ed il programmatore può com- box(r1,c1,r2,c2);
prenderlo facilmente anche dopo molto tempo.
In più, il listato sorgente si è ridotto drasticamen- dove:
te. Con tale metodo abbiamo un po’ “potenziato” • r1 e c1 sono, rispettivamente, la riga su-
il linguaggio C, creando nuove funzioni che pri- periore e la colonna a sinistra del box;
ma non esistevano. • r2 e c2 sono, rispettivamente, la riga infe-

#include "stdio.h"

void PrintStrAt(int r,int c,char *s) {


printf("\033[%d;%dH%s",r,c,s);
}

void PrintIntAt(int r,int c,int n) {


printf("\033[%d;%dH%d",r,c,n);
}

void InputStrAt(int r,int c,char *s) {


printf("\033[%d;%dH",r,c);
scanf("%s",s);
}

void InputIntAt(int r,int c,int *n) {


printf("\033[%d;%dH",r,c);
scanf("%d",n);
}

void CursorAt(int r,int c) {


printf("\033[%d;%dH",r,c);
}

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;
}

riore e la colonna a destra del box.

Figura 4: Il box
Figura 5: Esempio di utilizzo dei box

La funzione “box” esegue il disegno di un qua-


sulla colonna c1 e la seconda sulla colonna c2.
drato utilizzando il carattere “-” per tracciare la
Il secondo loop, invece, disegna due successio-
riga orizzontale e il carattere “!” per la colonna
ni di trattini per le due righe orizzontali, la prima
verticale. Il primo ciclo di for presente ha il com-
in riga r1 e la seconda in riga r2. Il listato che
pito di visualizzare due righe verticali, la prima
segue mette in pratica tutto ciò e crea una simu-
47} Cors o C s u Raspber r y P i par t endo da zer o

#include "stdio.h"

void PrintStrAt(int r,int c,char *s) {


printf("\033[%d;%dH%s",r,c,s);
}

void cls() {
printf("\033[2J");
}

void box(int r1,int c1,int r2,int c2) {


int k;
for(k=r1;k<=r2;k++) {
printf("\033[%d;%dH!",k,c1);
printf("\033[%d;%dH!",k,c2);
}
for(k=c1+1;k<=c2-1;k++) {
printf("\033[%d;%dH-",r1,k);
printf("\033[%d;%dH-",r2,k);
}
}

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;
}

lazione di un controllo di sistema motorizzato, il FINALMENTE I COLORI


cui output può essere osservato in figura 5. Si Le sequenze di Escape, oltre che a pulire lo
noti come, con poche righe di programma, l’in- schermo e a posizionare il cursore, rendono
terfaccia grafica sia migliorata di parecchio. Ov- fattibile anche la modifica degli attributi video.
viamente è possibile cambiare il set di caratteri Questo si traduce nella possibilità di cambiare
ed utilizzare quelli più confacenti ai propri gusti lo stile ed il colore dei testi visualizzati. Per mo-
e al progetto in generale. dificare il colore e l’attributo di un testo si
Cors o C s u Raspber r y P i par t endo da zer o {48

usa la seguente sequenza di Escape: • Gray \033[0;37m


• Dark Gray \033[1;30m
033[v(;v)m • Light Blue \033[1;34m
• Light Green \033[1;32m
dove “v” può assumere uno dei seguenti valori: • Light Cyan \033[1;36m
• attributes • Light Red \033[1;31m
• 0 normal display • Light Purple \033[1;35m
• 1 bold • Yellow \033[1;33m
• 4 underline (mono only) • White \033[1;37m
• 5 blink on Il seguente listato scrive, in sequenza, alcuni
• 7 reverse video on testi colorati, utilizzando i comandi appena visti
• 8 nondisplayed (invisible) sopra. Combinandoli opportunamente si posso-
• foreground colors no ottenere effetti interessanti (vedi figura 6).
• 30 black
• 31 red #include "stdio.h"
• 32 green
void cls() {
• 33 yellow
printf("\033[2J");
• 34 blue }
• 35 magenta
int main() {
• 36 cyan
cls();
• 37 white printf("\033[0;34m Blue\n");
• background colors printf("\033[0;32m Verde\n");
• 40 black printf("\033[0;36m Ciano\n");
printf("\033[0;31m Rosso\n");
• 41 red printf("\033[1;34m Blu chiaro\n");
• 42 green printf("\033[1;31m Rosso chiaro\n");
• 43 yellow printf("\033[1;33m Giallo\n");
return 0;
• 44 blue }
• 45 magenta
• 46 cyan
• 47 white
Le seguenti sequenze specificano esattamente
un attributo ed il colore:
• Black \033[0;30m
• Blue \033[0;34m
• Green \033[0;32m
• Cyan \033[0;36m
• Red \033[0;31m
• Purple \033[0;35m
• Brown \033[0;33m Figura 6: Testi colorati
49} Cors o C s u Raspber r y P i par t endo da zer o

CONCLUSIONI Sequenza di Escape Funzione


Anche se la possibilità di poter agire direttamen- moves cursor to line
ESC[#;#H
te sugli attributi video non è paragonabile agli #, column #
moves cursor up #
straordinari effetti di una finestra GUI, essa mi- ESC[#A
lines
gliora senz’altro le applicazioni in modo testo. moves cursor down #
Le sequenze di Escape non hanno bisogno di ESC[#B
line
alcuna libreria aggiuntiva e tutto il codice ope- moves cursor right #
ESC[#C
rativo risiede in un unico file eseguibile, leggero spaces
moves cursor left #
e compatto. La figura 7 mostra un esempio ESC[#D
spaces
completo di applicazione, che prevede e save cursor position
ESC[s
contempla tutti i concetti appresi nel presen- for recall later
te articolo. Return to saved cur-
ESC[u
sor position
Esistono, naturalmente, altre sequenze di Esca-
clear screen and
pe, che espletano altre funzioni. Alcune sono ri- ESC[2J
home cursor
portate nella tabella 1. Occorre sostituire il suf- ESC[K clear to end of line
fisso “ESC” con il “\033”, durante la codifica dei
comandi. Alla prossima puntata. Tabella 1: Sequenza di escape

Figura 7: Esempio completo

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

Corso C su Raspberry PI partendo


da zero: i Puntatori
di Giovanni Di Maria

INTRODUZIONE • Stringa: secondo la lunghezza.

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;

printf("Indirizzo di memoria di 'figli': %p, Figura 1: Esecuzione del primo programma


Bytes: %d\n",&figli,sizeof(figli));
printf("Indirizzo di memoria di 'eta': %p, • La variabile “peso” occupa 4 bytes ed è
Bytes: %d\n",&eta,sizeof(eta)); allocata all’indirizzo 0022FEE4;
printf("Indirizzo di memoria di 'velocita': %p,
• La variabile “coefficiente” occupa 8 bytes
Bytes: %d\n",&velocita,sizeof(velocita));
printf("Indirizzo di memoria di 'peso' %p, ed è allocata all’indirizzo 0022FED8.
Bytes: %d\n",&peso,sizeof(peso));
printf("Indirizzo di memoria di 'coefficente': %p,
La figura 2 mostra la ripartizione di tale RAM
Bytes: %d\n",&coefficente,sizeof(coefficente));
return(0); con la relativa mappa. E’ il compilatore a deci-
} dere in quale locazione si devono depositare le
variabili. Inoltre la disposizione delle stesse po-
trebbe alquanto cambiare da computer a com-
Pertanto, all’atto della dichiarazione, il compila-
puter e da sistema operativo a sistema opera-
tore “prenota” e assegna una o più stanze alla
tivo.
variabile (come succede per l’albergo). Le stan-
Nel listato abbiamo incontrato e visto due im-
ze, ossia gli indirizzi di memoria, sono progres-
portanti concetti:
sivamente numerate. Nell’esempio appena pro-
1. Per sapere in quale indirizzo di memoria è
posto, la variabile peso occupa 4 bytes, essendo
memorizzata una variabile si deve usare
di tipo float ed è allocata a partire dall’indirizzo
l’operatore “&”. Lo abbiamo incontrato du-
0022FEE4 e “invade” (ossia occupa, abbraccia)
rante l’utilizzo della funzione scanf.
quattro celle di memoria. Al programmatore
2. Per visualizzare un indirizzo di memo-
non serve molto conoscere in dettaglio tale
ria nel corretto formato, occorre utiliz-
memorizzazione. L’esecuzione del listato di cui
zare il segnaposto “%p”, ad esempio
sopra produce il risultato riportato in figura 1.
printf(“%p”,&n).
Come si nota, il responso sulle variabili mostra
la seguente dislocazione in memoria:
• La variabile “figli” occupa 1 byte ed è allo- COME SI DICHIARA UN PUNTATORE
cata all’indirizzo 0022FEEF; Si è detto che il puntatore è una variabile il cui

• La variabile “eta” occupa 1 byte ed è allo- contenuto è l’indirizzo di un’altra variabile, ossia

cata all’indirizzo 0022FEEE; l’indirizzo fisico in memoria di quest’ultima. Il pun-

• La variabile “velocita” occupa 4 bytes ed è tatore si dichiara come le altre variabili, con

allocata all’indirizzo 0022FEE8; la differenza che si antepone ad esso l’asterisco


Cors o C s u Raspber r y P i par t endo da zer o {52

4 int *ip; /* Dichiarazione di puntatore gene-


rico */
5 ip = &k; /* Assegna l'indirizzo di k a ip */
6 printf("Indirizzo di k: %p\n", &k);
7 printf("Indirizzo di ip: %p\n", ip );
8 printf("Valore della variabile *ip: %d\n", *ip );
9 return 0;
10 }

Abbiamo numerato progressivamente le righe


del programma per poterle commentare. Nel li-
stato sorgente i numeri devono essere rimossi
per evitare errori durante la compilazione. Spie-
ghiamo solo le righe critiche, in quanto le altre
sono state studiate nei precedenti articoli e do-
vrebbero risultare chiare.
• La riga 3 dichiara normalmente la varia-
bile k di tipo intero e le assegna, conte-
Figura 2: Indirizzi delle variabili
stualmente, il valore di 20. Tale istruzione
(lo stesso che si usa per le moltiplicazioni). Ad è stata ampiamente utilizzata in prece-
esempio, lo statement: denza.
• La riga 4 dichiara un puntatore ad un in-
float *importo; tero.
• La riga 5 è interessante. Viene preso l’in-
dichiara un puntatore alla variabile float impor- dirizzo della variabile k (&k) e assegnato
to. Si noti che non è il puntatore ad essere float al puntatore ip.
ma il contenuto. All’atto della dichiarazione di un • Nelle righe 6 e 7 si desume che, tramite
puntatore, esso non punta ad alcun oggetto, o la visualizzazione a video, ip contiene lo
meglio, il suo indirizzo iniziale potrebbe essere stesso indirizzo di k.
casuale. • La riga 8 stampa il contenuto “puntato” da
ip.
SECONDO ESEMPIO
I puntatori si trattano come le normali variabili, La figura 3 mostra il risultato dell’esecuzione.
possono essere assegnati, scambiati, calcolati
e stampati. Il seguente secondo esempio mo-
stra alcuni trattamenti dei puntatori.
Figura 3: Esecuzione del secondo esempio
1 #include <stdio.h>
2 int main () {
3 int k = 20; /* Dichiarazione e assegnazione Per comprendere bene queste iterazioni, occor-
variabile K */ re precisare che:
53} Cors o C s u Raspber r y P i par t endo da zer o

1. ip è l’indirizzo della variabile.


2. *ip è il valore puntato da ip, ossia il con- }
tenuto della variabile che si trova nell’in-
int main () {
dirizzo ip.
int x,y;
Capiti questi due concetti, i puntatori non avran- x=10;
no più alcun segreto. y=20;

printf("Prima dello scambio: : X=%d Y=%d\


OPERATORI PER I PUNTATORI n",x,y);
Per trattare i puntatori, dunque, esistono due di- scambia(&x,&y);
printf("Dopo lo scambio: X=%d Y=%d\
versi tipi di operatori unari:
n",x,y);
• & restituisce l’indirizzo di una variabile;
• * restituisce il contenuto del puntatore uti- return 0;
lizzato. }

LO SCAMBIO DELLE VARIABILI


Uno dei più interessanti e semplici esempi, in
cui i puntatori sono ben utilizzati, è rappresen-
tato dallo scambio di due variabili (swap). Sen- Figura 4: Swap di due variabili
za l’utilizzo degli indirizzi, infatti, non è possibile
barattare il contenuto di due variabili in una fun- La funzione è piuttosto semplice: in essa sono
zione UDF, a meno che non si utilizzino le varia- passati due indirizzi di memoria che si esplicita-
bili globali. La funzione che andiamo a prepara- no in altrettanti due puntatori (p1 e p2). Quindi,
re accetta, tra i propri parametri, due indirizzi di con tre semplici passaggi, vengono scambiati tra
variabili. Al termine dell’esecuzione il contenuto loro i contenuti dei puntatori (altri validi esempi
di esse verrà scambiato. Il programma inizia qui). La funzione non deve ritornare nulla (void).
con l’assegnazione dei valori 10 e 20, rispetti- L’operazione di scambio, infatti, avviene all’in-
vamente alle variabili x e y. Quindi, una prima terno di essa. All’uscita i puntatori contengono
visualizzazione mostra il loro reale contenuto. due nuovi valori, richiamabili ed utilizzabili an-
L’invocazione della funzione scambia(&x,&y) che all’esterno della funzione.
provoca, appunto, lo scambio di indirizzo, che
mantiene modificati i dati anche a conclusione UN ALTRO ESEMPIO DI SCAMBIO VA-
della funzione stessa. La seconda visualizza- RIABILI
zione conferma ciò. La figura 4 mostra il risul- Questo altro listato chiarisce, ancora meglio, il
tato dell’esecuzione del programma. concetto di scambio delle variabili tra loro. Non
#include <stdio.h> utilizza una funzione personalizzata e l’intero al-
goritmo è processato nella funzione principale
void scambia (int *p1, int *p2){ main. Nell’esempio, sono i puntatori ad essere
int temp = *p1;
*p1 = *p2; scambiati e non i valori (vedi figura 5). Guardia-
*p2 = temp; mo il listato e poi commentiamolo assieme.
Cors o C s u Raspber r y P i par t endo da zer o {54

nisce tre puntatori (a,b,c) e due normali variabili


#include <stdio.h>
intere (A,B). Si noti come il linguaggio C sia ca-
int main () { se-sensitive, per cui l’utilizzo delle lettere maiu-
int *a,*b,*c; /* Dichiara 3 puntatori a intero */ scole e minuscole è rilevante. La variabile ‘a’ è,
int A,B; /* Dichiara 2 normali variabili
intere */ dunque, diversa dalla variabile ‘A’. Il programma
inizia con l’assegnazione dei valori 12 e 9 alle
A = 12; /* Inizializza le variabili */ rispettive variabili A e B. Quindi si inizializzano
B = 9;
le variabili a e b con l’indirizzo dei contenitore
a = &A; /* Assegna ai puntatori i valori A e B. Questa è un’operazione obbligatoria di
delle variabili */ inizializzazione di puntatore, in quanto, senza di
b = &B;
essa, l’esecuzione potrebbe mandare in crash
printf ("Valori memorizzati nei puntatori 'a' e 'b': il sistema. Dopo la stampa dei primi valori, viene
%d %d\n",*a,*b); effettufato lo scambio degli indirizzi. Si usa il ter-

c = a; /* Scambia i puntatori */ zo indirizzo di comodo c, per non perdere i valo-


a = b; ri durante il trasferimento. Lo schema grafico di
b = c; figura 5 chiarisce bene tale concetto. Infine, la
stampa dei risultati conferma l’avvenuto scam-
printf ("Valori memorizzati nei puntatori 'a' e 'b':
%d %d\n",*a,*b); bio degli indirizzi.

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];

crea un vettore composto da 5 elementi, rispet-


tivamente presenti nelle “celle” vettore[0], vetto-
re[1], vettore[2], vettore[3] e vettore[4]. E’ pos-
sibile assegnare un valore ad un solo elemento
per volta, come ad esempio:

vettore[2]=77;

ma è vietato eseguire una assegnazione globa-


le e generale:
Figura 5: Scambio di indirizzi
vettore=123.
L’algoritmo è abbastanza semplice. Esso defi-
55} Cors o C s u Raspber r y Pi par t endo da zer o

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

Figura 7: Mappa in memoria dei due vettori


modo definitivo, una
variabile, passata ad
essa come parame-
tro, cosa normalmente impossibile. Inoltre è
l’indice esplicitato tra parentesi quadre, sia al
possibile allocare dinamicamente la memo-
puntatore del vettore aumentato di un fattore
ria, in modo che i dati stessi incrementino
che tiene conto della posizione ricercata non-
lo spazio utilizzato, man mano che la pro-
ché dalla lunghezza del dato stesso (indice cal-
57} Cors o C s u Raspber r y P i par t endo da zer o

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

Corso C su Raspberry PI partendo


da zero: Introduzione alle porte
di Output
di Giovanni Di Maria

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)

si tratta di un insieme numeroso di pin numerati.


La figura 1 mostra la dislocazione di tale connet- cavetti o jumper di tipo “femmina” da connettere
tore e la sua piedinatura. alle varie porte, senza la possibilità di acciden-
La piedinatura non è sequenziale ma alternata. tali corto circuiti tra le stesse. Guardiamo un po’
Per evitare malfunzionamenti dei propri proget- il connettore è descriviamo le funzionalità dei
ti o, addirittura, guasti al dispositivo, controllate contatti di alimentazione:
sempre attentamente il datasheet di riferimento. • Il pin 1 mette a disposizione una tensione
Lavorare con la GPIO richiede, infatti, sem- positiva di 3.3V. La corrente massima pre-
pre la massima attenzione. Il collegamento con levabile è di 50 mA;
i vari pin deve essere effettuato con opportuni • Il pin 2 eroga una tensione positiva di 5V.
59} Cors o C s u Raspber r y P i par t endo da zer o

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

volta, il valore della porta GPIO sul file:


/sys/class/gpio/export

DEFINIZIONE DELLA FUNZIONALITÀ


DELLA PORTA
Adesso occorre informare il sistema che una
determinata porta deve funzionare in modalità
di “uscita”, ovvero in output. Per raggiungere lo
scopo, si deve scrivere il valore “out” in un altro
file del filesystem, ossia:
/sys/class/gpio/gpioN/direction
dove N è il numero della porta GPIO da utiliz-
zare.

SCRITTURA DEL LIVELLO LOGICO


SULLA PORTA
La porta adesso è pronta per accendersi o spe-
gnersi, cioè a generare una tensione logica. Un
Figura 2: La gestione delle porte
altro file su cui scrivere il bit logico è:
/sys/class/gpio/gpioN/value
semplice. Occorrono, infatti, solo un diodo LED
Una memorizzazione del valore “0” su tale porta
e una resistenza di opportuno valore. Per la sua
la pone ad un livello logico basso, mentre il va-
determinazione occorre utilizzare la Legge di
lore “1” la fissa ad una tensione logica alta.
Ohm e serve considerare la tensione di uscita
delle porte del RPi che ammonta a 3,3V. Pertan-
RILASCIO DELLA PORTA DI I/O
to, ponendo a circa 2V la caduta di tensione del
Finalmente, quando tutte le operazioni sono
componente luminoso e una corrente tipica di
state compiute e le funzionalità delle porte non
attraversamento pari a 15mA, possiamo deter-
hanno più alcuna utilità, solitamente a fine pro-
minare il valore del resistore di limitazione con i
gramma, è buona norma “rilasciarle”. Ciò si ef-
seguenti calcoli:
fettua scrivendo il valore della porta GPIO utiliz-
R=V:I
zata su un altro file:
da dove:
/sys/class/gpio/unexport
R=(3,3-2):0,015=86,6 ohm
Sembrano tutte operazioni complicate ma, cre-
Possiamo utilizzare tranquillamente una re-
dete a me, sono di una semplicità estrema, e la
sistenza dal valore commerciale di 100 ohm
figura 2 lo testimonia.
con potenza di 1/4W. Lo schema elettrico delle
connessioni può essere osservato in figura 3,
SCHEMA ELETTRICO DEL LAMPEG-
tenendo conto anche del pinout del Raspberry
GIATORE
Pi. Il dispositivo luminoso è collegato alla porta
Lo schema elettrico del lampeggiatore è molto
61} Cors o C s u Raspber r y Pi par t endo da zer o

GPIO4 (pin 7) e alla massa comune (pin 6).


for(k=1;k<=10;k++) {
/*------Accende diodo Led-----*/
handle=fopen("/sys/class/gpio/gpio4/
value","w");
fprintf(handle,"1");
fclose(handle);
/*----------Ritardo--------*/
for(t=1;t<=RITARDO;t++);
/*------------Spegne diodo Led------*/
handle=fopen("/sys/class/gpio/gpio4/
value","w");
fprintf(handle,"0");
fclose(handle);
/*----------Ritardo--------*/
for(t=1;t<=RITARDO;t++);
}
/*----Rilascia porta GPIO4------*/
handle=fopen("/sys/class/gpio/unexport","w");
fprintf(handle,"4");
fclose(handle);
Figura 3: Schema elettrico del lampeggiatore return 0;
}

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

ripetuto dieci volte, scrive alternativamente il va-


lore “1” e “0” nel file /sys/class/gpio/gpio4/value.
Il ciclo di ritardo fissa una cadenza al lampeg-
gio del diodo Led. Alla fine, il collegamento alla
porta GPIO4 è rilasciato e la porta stessa vie-
ne “abbandonata”, grazie alla memorizzazione
del valore “4” all’interno del file /sys/class/gpio/
unexport. Il flowchart di cui alla figura 4 chiari-
sce meglio il flusso dell’algoritmo.
La compilazione del sorgente segue sempre le
stesse regole. Dal prompt di console occorre
impartire il comando:
e se la procedure va a buon fine viene creato il
file a.out.

ATTENZIONE: per usare le porte della GPIO


occorre eseguire il programma con i privilegi di
root. Pertanto non richiamate l’applicazione con
il solito:

./a.out

che produrrebbe il messaggio di errore “Seg-


mentation fault”, bensì con:

sudo ./a.out

Vi assicuro che vedere lampeggiare un diodo


LED, comandato da un vero computer, in lin-
guaggio C, è molto soddisfacente. Il fatto di ave-
re già appreso come gestire in toto una porta in
output, apre infinite porte alle applicazioni più
disparate (controlli, domotica, prototipi, ecc).

IN LINUX, QUALSIASI COSA È UN FILE


Per il sistema operativo del pinguino, qualsiasi
cosa è considerata come un file. Monitor, tastie-
ra, dispositivi, clock, ecc, possono essere ge-
stiti tramite accesso ai files. Le porte di I/O del Figura 4: Diagramma di flusso del lampeggiatore
63} Cors o C s u Raspber r y P i par t endo da zer o

è un optional. Teoricamente
si può scrivere un software
implementandolo semplice-
mente in un file batch.

PILOTARE UNA LAM-


PADA DA 12V
La logica di controllo funge
da “cervello”, preposto a go-
vernare i processi e prende-
Figura 5: Il comando ECHO può pilotare le porte
re le decisioni operative. Ma se i dispositivi da
comandare sono più esigenti, in termini di ten-
Raspberry Pi seguono la stessa regola come
sione e di corrente, il Raspberry Pi, da solo, non
abbiamo visto. Per comprendere meglio que-
ce la fa. Ogni porta, infatti, riesce ad erogare
sto concetto, proviamo a comandare lo stesso
una esigua quantità di corrente che non dovreb-
diodo LED tramite operazioni su file da sistema
be superare circa 16mA. Diciamo che le uscite
operativo, anziché disturbare il linguaggio C,
sono sufficienti a pilotare un diodo LED, nulla di
come mostrato in figura 5.
più. Per comandare carichi più robusti, si devo-
Il comando del sistema operativo “echo” per-
no corredare le porte di dispositivi più potenti,
mette di scrivere un dato su un dispositivo. Il
quali transistor, fet, mosfet, triac, scr, igbt, ecc.
simbolo di ridirezione, quindi, dirotta tale infor-
L’esempio che segue realizza un sistema for-
mazione sui files interessati. Queste di seguito
mato da tre parti fondamentali:
sono le procedure per pilotare la porta GPIO4 in
1. La logica di controllo (la porta GPIO4).
maniera del tutto indipendente dal linguaggio di
2. Il software di comando.
programmazione:
3. Il dispositivo di potenza.
• echo “4” > /sys/class/gpio/export (crea
porta);
Un menù a video deve proporre all’operatore 3
• echo “out” > /sys/class/gpio/gpio4/direc-
opzioni:
tion (GPIO4 in out);
1. Accendere la lampada.
• echo “1” > /sys/class/gpio/gpio4/value
2. Spegnere la lampada.
(GPIO4 ON);
3. Uscire dal programma.
• echo “0” > /sys/class/gpio/gpio4/value
(GPIO4 OFF);
Scegliendo la prima opzione, ovviamente, si
• echo “4” > /sys/class/gpio/unexport (rila-
provoca l’immediata accensione della lampada
scia GPIO4).
di potenza. La seconda opzione ne provoca lo
spegnimento. La terza permette di concludere il
E’ sufficiente, quindi, “parlare” direttamente con
programma, con la disattivazione forzata della
i dispositivi per raggiungere qualsiasi scopo. Il
lampada e la dismissione delle porte di I/O. Il
linguaggio di programmazione, in questo senso,
pilotaggio della lampada è eseguito tramite
Cors o C s u Raspber r y P i par t endo da zer o {64

un transistor di potenza, il BD243C, un caval- comanda il transistor (che funge da interruttore


lo di battaglia, il cui package e il relativo pinout elettronico) attraverso la resistenza di limitazio-
sono visibili in figura 6, mentre le caratteristiche ne R1, da 150 ohm. Sul collettore del BD243C è
in figura 7. collegata una lampada da 12V. La sua resisten-
za interna è molto bassa e l’utente può sceglie-
re altre potenze, purché non si superano i limiti
massimi del transistor. Il generatore, ovviamen-
te, deve poter alimentare il dispositivo, pertanto
per illuminare una lampada da 2A, la batteria ne
deve erogare almeno 3. Il Raspberry Pi deve es-
sere alimentato con una fonte alternativa o dalla
stessa batteria utilizzata. In questo caso occor-
Figura 6: Package e pinout del BD243C. re adeguare la sua tensione a quella dell’em-
bedded. Le due
masse, quella del
RPi e quella del
transistor, sono in
comune e devono
essere collegate
assieme.

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

nella realizzazione di svariate tipologie di


dispositivi. Proteggete sempre le porte di
comunicazione del vostro Raspberry Pi.
Il funzionamento del programma è osser-
vabile in figura 10. Non c’è nulla di nuovo
circa le tecniche utilizzate. Si riscontra
solamente una novità per effettuare la
cancellazione del video: è stata utilizza-
Figura 9: Cablaggio del sistema con lampada di potenza. ta la funzione system(“clear”) che ri-
chiama l’omonimo comando del siste-
ma operativo. Il prototipo è contenuto
L’utilizzo di opto-isolatori è sempre consigliato. nel file di inclusione #include “stdlib.h”.
Se il transistor, durante l’uti-
lizzo, dovesse scaldare molto
per via della potenza eccessi-
va della lampada, si può ap-
plicare un piccolo radiatore
metallico sul semiconduttore,
tramite un leggero strato di
pasta siliconica.

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

Corso C su Raspberry PI partendo


da zero: Introduzione alle porte
di Input
di Giovanni Di Maria

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

/sys/class/gpio/export Una lettura del valore “0” (zero) indica un livello


logico basso, mentre il valore “1” (uno) indica un
livello logico alto. Si ricordi di modificare il nome
del path con il numero della porta utilizzata.

DISTRUZIONE DELLA PORTA


A fine programma è buona norma rilasciare la
porta, utilizzando il seguente file:
/sys/class/gpio/unexport
E’ sufficiente scrivere su esso il numero della
porta da distruggere. Tutte queste operazioni
si devono eseguire per ciascuna porta utilizza-
ta nel proprio progetto. Si presti, dunque, molta
attenzione a identificare bene il loro numero di
riferimento.

ESEMPIO PRATICO: CONTATORE DI


ACCESSI
Un esempio completo, molto utile e istruttivo, è
quello del contatore di accessi, che proponiamo
come esercizio didattico per mettere in pratica
Figura 1: Le porte del Raspberry Pi. tutti i concetti visti fino ad ora. Il prototipo può
essere utilizzato in molti ambiti (vedi applicazio-
DEFINIZIONE DEL FLUSSO DEI DATI ni in figura 3), come ad esempio:
Adesso siamo pronti per definire la direzione • per il conteggio delle persone che entrano
della porta, ossia il verso dei dati e dei segnali. in un locale;
Se serve configurare la porta GPio4 come in- • per il conteggio delle automobili che tran-
gresso, occorre utilizzare il file: sitano in una strada;
/sys/class/gpio/gpio4/direction • come contapezzi industriale su nastro tra-
In esso si deve memorizzare la stringa “in” per sportatore;
configurare la porta come ingresso digitale.
PRINCIPIO DI FUNZIONAMENTO: IL
LETTURA DELLA PORTA SENSORE
Questa operazione è la più importante e la più Il sistema basa il suo funzionamento su un sen-
frequente, in quanto permette di stabilire la ten- sore luminoso, che normalmente è colpito dalla
sione logica presente sulla porta in questione. luce in mancanza di ostacolo. L’unità ricevente
Per conoscere lo stato del canale 4, ad esem- può essere rappresentata da qualsiasi compo-
pio, si deve leggere il file: nente elettronico sensibile alla luce, come ad
/sys/class/gpio/gpio4/value esempio:
69} Cors o C s u Raspber r y P i par t endo da zer o

fornisce una certa tipologia di in-


formazione. Quando il raggio lu-
minoso è interrotto dal passaggio
di un oggetto o di una persona,
lo stato del sensore cambia, così
come il segnale che esso forni-
sce. Sfrutteremo proprio questa
particolarità per far capire al Ra-
spberry come rilevare e contabi-
lizzare il numero di passaggi di
oggetti davanti un accesso.
Il sensore utilizzato per le nostre
prove è una comunissima fotore-
sistenza con le seguenti caratte-
ristiche:
• Resistenza alla luce massi-
ma: circa 200 ohm;
• Resistenza al buio: circa
200 Kohm.

Figura 2: La gestione delle porte di ingresso.


Allo stato di questi valori, dobbia-
mo realizzare una sorta di parti-
tore resistivo che fornisca una ten-
sione di circa 0V quando il sensore
non è illuminato dalla luce, ossia il
fascio luminoso è interrotto da un
ostacolo, e di circa 3.3V quando il
sensore è pienamente colpito dalla
luce. Lo schema elettrico dell’unità
Figura 3: Il contatore di accessi o contapezzi. ricevente è riportato in figura 4.
Per le migliori prestazioni del si-
• Fototransistor; stema, e una maggiore immunità
• Fotoresistenza; alla luce ambientale, si consiglia di inserire la
• Cellula fotoelettrica; fotoresistenza all’interno di un tubetto nero.
• Diodo IR ricevente; La tensione di 3.3V può essere ottenuta tramite
• E molti altri. due pile da 1.5V, collegate in serie, oppure di-
rettamente dal pin 1 del Raspberry Pi. Lo sche-
Un raggio luminoso deve colpire in maniera con- ma completo del sistema e del suo cablaggio è
tinuativa il sensore che, in assenza di ostacolo, visibile in figura 5.
Cors o C s u Raspber r y P i par t endo da zer o {70

guente logica: se nessun ostacolo occlude la


fotoresistenza, essa è colpita dalla luce e il suo
valore ohmico si abbassa drasticamente. La
corrente delle pile raggiunge, pertanto, la porta
di ingresso del Raspberry, non “disturbata” dalla
resistenza di pull-down R2. In caso di fotore-
sistenza oscurata, invece, la corrente delle pile
arriva lo stesso al Rpi, ma stavolta attraversan-
do una resistenza molto alta del sensore. La
tensione è, però, abbassata da R2 che, stavolta,
Figura 4: Schema del partitore di sensore luminoso. comanda la situazione, essendo caratterizzata
da una resistenza molto più bassa
di quella del sensore. Utilizzando
la legge di Ohm, i due casi limite
potrebbero essere così riassunti:
• Con luce: V=R*I;
V=10000*(3.3/10200)=3.23V;
• Al buio: V=R*I;
V=10000*(3.3/210000)=0.15V.
E’ molto importante, dunque, che
la fotoresistenza si trovi il più pos-
Figura 5: Cablaggio del contapezzi. sibile al buio in caso di suo occul-
tamento, oppure estrema-
mente illuminata, se non
ci sono oggetti in transito
che ostacolino la luce.

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

tiene alcune novità. Per poterlo agevolmen-


te commentare, sono stati inseriti i numeri 42 fclose(handle);
progressivi di riga. Ovviamente non devono 43 }
44 /*------Legge porta GPIO4------*/
essere trascritti durante la digitazione al PC 45 int LeggiPorta() {
e vanno eliminati nel caso di un copia e in- 46 FILE *handle;
colla. 47 int valore;
48 handle=fopen(“/sys/class/gpio/gpio4/
1 #include “stdio.h” value”,”r”);
2 #include “stdlib.h” 49 fscanf(handle,”%d”,&valore);
3 /*------Prototipi------*/ 50 fclose(handle);
4 void CreaPorta(); 51 return valore;
5 void DefinisciPortaInput(); 52 }
6 int LeggiPorta(); 53 /*--------Rilascio porta----*/
7 void RilasciaPorta(); 54 void RilasciaPorta() {
8 void CursorAt(int r,int c); 55 FILE *handle;
9 /*---------------------*/ 56 handle=fopen(“/sys/class/gpio/
10 int main() { unexport”,”w”);
11 unsigned long int passaggi=0; 57 fprintf(handle,”4”);
12 CreaPorta(); 58 fclose(handle);
13 DefinisciPortaInput(); 59 }
14 system(“clear”); 60 /*-------Posiziona cursore----*/
15 while (1) { 61 void CursorAt(int r,int c) {
16 CursorAt(6,15); 62 printf(“\033[%d;%dH”,r,c);
17 printf(“Passaggi: %ld\n”,passaggi); 63 }
18 /*------Lettura porta-----*/
19 if (LeggiPorta()==0) {
20 passaggi++;
Osserviamo in generale, le novità del program-
21 while(LeggiPorta()==0);
22 } ma, per poi passare all’analisi approfondita, se-
23 if (LeggiPorta()==1) { guendo la numerazione. Nel sorgente abbiamo
24 while(LeggiPorta()==1);
introdotto alcune nuove particolarità:
25 }
26 } 1. Sono stati introdotti all’inizio del listato i
27 RilasciaPorta(); prototipi di funzione;
28 return 0;
2. La gestione della GPIO è stata delegata a
29 }
30 /*------Crea porta GPIO4------*/ funzioni personalizzate (UDF);
31 void CreaPorta() { 3. E’ stata Implementata una sorta di anti-
32 FILE *handle; rimbalzo software.
33 handle=fopen(“/sys/class/gpio/
export”,”w”);
34 fprintf(handle,”4”); Esaminiamo adesso col microscopio le varie
35 fclose(handle); parti di codice, commentando la funzionalità
36 }
37 /*-----GPIO4 in ingresso------*/ espletata.
38 void DefinisciPortaInput() { • Dalla riga 3 alla riga 8 abbiamo “informa-
39 FILE *handle; to” il compilatore dell’esistenza di alcune
40 handle=fopen(“/sys/class/gpio/gpio4/
direction”,”w”); funzioni personalizzate, riportate in fondo
41 fprintf(handle,”in”); al listato. Le UDF, normalmente, devono
Cors o C s u Raspber r y P i par t endo da zer o {72

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

travano o uscivano. Chissà


perché... Con questi brevi
e semplici esempi il letto-
re può finalmente gestire
gli ingressi e le uscite del
Raspberry Pi, in modo da
poterlo collegare a qualsi-
asi apparecchiatura ester-
na, di potenza o meno. Gli
ingressi del RPi, visto che
ricevono tensione, devono
essere il più protetti possi-
Figura 7: Utilizzo di un puntatore laser per grandi distanze. bile. Si utilizzino, quindi, i
fotoaccoppiatori per isolare
laser rivolto verso la fotoresistenza, non dimen- l’embedded dal resto del
ticando di utilizzare il solito tubetto nero per pro- circuito. Inoltre si rispettino sempre i due livelli di
teggerla dalla luce ambientale. Tale soluzione è tensione logica, 0V e 3.3V. Si raccomanda an-
riportata in figura 7. che di utilizzare gli appositi cavetti femmina per
le connessioni fisiche al pettine di contatto delle
CONCLUSIONI porte e di non effettuare direttamente le salda-
Con un po’ di fantasia, il progetto presentato ture sui pin. Proponiamo, per chi lo volesse
può essere adattato alle situazioni più dispara- svolgere, un piccolo esercizio dedicato agli
te. Per esempio può essere adoperato per mi- ingressi digitali. Il sistema si compone di tre
surare la velocità di un ventilatore. Basta, infatti, pulsanti per quiz, utilizzati da altrettanti concor-
far transitare il fascio di luce (laser) tra le pale renti. Il primo concorrente che preme il pulsante
e contare, in un minuto, il conteggio raggiun- determina la frase sul video: “Ha risposto per
to. Il risultato finale va diviso, ovviamente, per primo il concorrente X”. Creare il programma e
il numero delle pale. Un mio cliente ha voluto rappresentare lo schema elettrico.
implementarlo sulla porta della propria abitazio-
ne, per contare il numero di persone che en- Alla prossima puntata.

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

Corso C su Raspberry PI partendo


da zero: Ottimizzazione
di Giovanni Di Maria

INTRODUZIONE scopo di mostrare a video l’area di un cerchio,

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

E’ del tutto inutile utilizzare le variabili di tipo “int” #include <stdio.h>


int main(){
per dei numeri che, sicuramente, non supereranno unsigned long n;
il valore di 255. Meglio utilizzare “unsigned char” unsigned long long somma;
così si risparmiano celle preziose in memoria. Per somma=0;
for(n=1;n<=1234567890;n++)
una sola variabile, magari, l’operazione non ne somma=somma+n; /* Puoi scrivere anche
varrebbe la pena, ma pensate a quei programmi somma+=n */
enormi, sistemi operativi, gestionali e matematici printf("Somma: %I64d\n",somma);
return 0;
nei quali tale tecnica risulta essere veramente uti-
}
le e determinante. Ecco il listato ottimizzato.

#include <stdio.h> La visualizzazione della somma viene


int main(){ fornita in 8 secondi. E numeri più gran-
unsigned char k,tabellina; di fanno aumentare esponenzialmente
tabellina=7;
for(k=1;k<=10;k++) { il tempo. Ricorriamo allora alla Matema-
printf("%2d x %2d = %2d \n",tabellina,k,k*tabellina); tica che ci regala una semplice formula
} per calcolare la somma dei primi numeri
return 0;
} naturali da 1 a N. Tale calcolo si studia
con le Progressioni Aritmetiche e la
formula è la seguente:
somma=(n*(n+1))/2
LA MATEMATICA CI DÀ UNA MANO
Il listato seguente, che implementa tale for-
Molte tecniche di ottimizzazione vengono forni-
mula, restituisce “istantaneamente” la somma,
te dalla Matematica. Piccole formule possono
qualunque numero venga immesso in input. Il
risolvere egregiamente algoritmi lunghi e ripeti-
risultato finale è 762078938126809995. In que-
tivi. Alcuni esempi chiariranno il concetto.
sto listato abbiamo introdotto il casting, ossia
l’adattamento delle variabili nelle formule, per
SOMMA DI N NUMERI
poterle trattare in maniera corretta. L’operatore
Non ottimizzato=8 secondi, ottimizzato=0
“long long” serve per “forzare” e far trattare la
secondi.
variabile “n” come intero a 64 bit. Senza di esso
Si supponga di voler sommare, tra loro, tutti i
il programma funzionerebbe male.
numeri naturali compresi tra 1 e un certo limite.
Se l’intervallo è ridotto, il classico algoritmo ite-
#include <stdio.h>
rativo funziona bene e restituisce il risultato in int main(){
pochi istanti. Ma se occorre calcolare la somma unsigned long n;
unsigned long long somma;
dei numeri compresi tra 1 e 1.234.567.890, il
n=1234567890;
PC deve eseguire tante iterazioni per poter somma=((long long)n*((long long)n+1))/2;
giungere al risultato finale. Il seguente algo- printf("Somma: %I64d\n",somma);
return 0;
ritmo funziona correttamente, dunque, ma è
}
estremamente lento:
77} Cors o C s u Raspber r y P i par t endo da zer o

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

Figura 4: Fitting dei valori di zona.

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

gcc -Wall -O2 prova.c


Una tecnica migliore, e più performante, pre-
vede invece la memorizzazione dei fattoriali in
un vettore. I risultati sono, pertanto, calcolati a soli 42 secondi. Ricordate, quindi, dell’esisten-

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...

2 minuti, con un enorme risparmio di tempo. Il


listato ottimizzato è il seguente: A VOLTE IL “FOR” NON CONVIENE
Non ottimizzato=33 secondi, ottimizzato=8
#include <stdio.h> secondi.
#include <stdlib.h>
L’ultimo esempio è molto interessante e mostra
static long factorial_table[] = {1, 1, 2, 6, 24, 120,
come, a volte, sia necessario raggirare un al-
720, 5040, 40320, 362880, 3628800, 39916800,
479001600 }; goritmo, rendendolo magari più lungo, pur di
long fattoriale(int); ottenere la massima velocità di esecuzione. Il
int caso(int); seguente programma ripete il suo nucleo per 1
/*----------------------------------*/ miliardo di volte. In esso, tramite un secondo ci-
int main(){
clo nidificato, viene inizializzato un vettore di 5
int c;
long f; elementi, al valore di k+1. La normale esecuzio-
unsigned long k; ne termina in 33 secondi:
for(k=1;k<=2000000000;k++) {
c=caso(12); #include <stdio.h>
f=factorial_table[c]; int main(){
} int i;
return 0; unsigned long k,v[5];
for(k=1;k<=1000000000;k++) {
}
for(i=0;i<5;i++)
/*----------------------------------*/ v[i]=k+i;
int caso(int fino_a) { }
return rand()%fino_a+1; return 0;
} }
Cors o C s u Raspber r y P i par t endo da zer o {80

#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. }

Qual è la tecnica di velocizzazione? Si deve


sapere che ogni ciclo di “for” ha, di per sé, una CONCLUSIONI
certa lentezza, poiché prevede le seguenti ope- Ovviamente le tecniche non finiscono qua. Ci
razioni: sarebbero letteralmente migliaia di trucchi, sug-
gerimenti e segreti per migliorare le prestazio-
• La chiamata alla funzione vera e propria;
ni dei propri programmi. Quelle esposte sin qui
• L’inizializzazione della variabile;
costituiscono solo le più semplici e alla portata
• Il controllo del limite raggiunto, a tutti gli
dei principianti. Probabilmente, in un futuro ar-
effetti è una “if” nascosta, che è lentissi-
ticolo avanzato, tratteremo anche delle ottimiz-
ma;
zazioni ad alto livello. Molto spesso, ottimizza-
• L’incremento della variabile.
re un programma e cercare di accelerare il
suo operato implica un aumento di numero
Dal momento che devono essere processati di righe del listato e, parallelamente, anche
solamente 5 elementi del vettore, essi possono della difficoltà di lettura e di interpretazione del-
essere “assegnati” esplicitamente con il costrut- lo stesso. Pertanto, in questi casi, commentate
to diretto: al massimo le varie parti di codice, in modo da
v[0]=k+0; ricordare, nel tempo, il perché si è scelta quella
v[1]=k+1; particolare soluzione. Suggeriamo, come ultima
v[2]=k+2; cosa, di esercitarsi ed acquisire una sensibilità a
v[3]=k+3; riguardo. Prendete, ad esempio, una problema-
v[4]=k+4; tica, codificatela e cercate di velocizzare, sem-
pre di più, l’esecuzione. Un piccolo esercizio?
senza utilizzare alcun ciclo. Il programma, per- Determinare se il numero 1111111111111111111
tanto, si allunga leggermente ma termina la sua è un numero primo o meno, nel tempo più bre-
esecuzione in soli 8 secondi, contro i 33 della ve possibile (si tratta di 19 cifre “1”). Pubblicate
prima versione. Un incremento notevole. pure il vostro listato. Alla prossima.

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

INTRODUZIONE per rimarcare l’importanza dell’esistenza delle

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

Immaginate se avessimo dovuto controlla-


printf("%ld e' un numero Primo\n",n); re 1000 numeri, quanto lungo sarebbe stato.
else Per fortuna esistono le UDF che ci consentono
printf("%ld e' un numero Composto\n",n);
/*----------------------------------*/ di ridurre drasticamente il codice e, soprattut-
n=10011; to, di riutilizzare il nostro lavoro anche in altri
FlagPrimo=1; programmi. Guardate di quanto si è ridotta la
for(k=2;k<n;k++)
if(n%k==0) lunghezza usando una funzione definita dall’u-
FlagPrimo=0; tente. E’ soprattutto, in caso di modifica, basta
if(FlagPrimo==1) semplicemente mettere le mani su di essa, in-
printf("%ld e' un numero Primo\n",n);
else vece di cambiare tutto il programma.
printf("%ld e' un numero Composto\n",n);
/*----------------------------------*/ 01 #include <stdio.h>
n=30000001; 02
FlagPrimo=1; 03 char *Primo(unsigned long n);
for(k=2;k<n;k++) 04
if(n%k==0) 05 int main(){
FlagPrimo=0; 06 unsigned long n;
if(FlagPrimo==1) 07 n=23;
printf("%ld e' un numero Primo\n",n); 08 printf("%ld e' un numero %s\n",n,Primo(n));
else 09 n=10007;
printf("%ld e' un numero Composto\n",n); 10 printf("%ld e' un numero %s\n",n,Primo(n));
/*----------------------------------*/ 11 n=10011;
n=750000007; 12 printf("%ld e' un numero %s\n",n,Primo(n));
FlagPrimo=1; 13 n=30000001;
for(k=2;k<n;k++) 14 printf("%ld e' un numero %s\n",n,Primo(n));
if(n%k==0) 15 n=750000007;
FlagPrimo=0; 16 printf("%ld e' un numero %s\n",n,Primo(n));
if(FlagPrimo==1) 17
printf("%ld e' un numero Primo\n",n); 18 return 0;
else 19 }
printf("%ld e' un numero Composto\n",n); 20
/*----------------------------------*/ 21 char *Primo(unsigned long n) {
return 0; 22 char FlagPrimo=1;
} 23 unsigned long k;
24 for(k=2;k<n;k++)
25 if(n%k==0)
26 FlagPrimo=0;
27 if(FlagPrimo==1)
28 return "Primo";
29 else
30 return "Composto";
31 }

Figura 1: Il programma dei numeri primi.


Commentiamo attentamente il listato, riferendo-
Nel listato si nota una caratteristica particolare: ci soprattutto ai numeri progressivi che, ovvia-
viene ripetuto tante volte lo stesso algoritmo. mente, non vanno trascritti nel vostro sorgente.
83} Cors o C s u Raspber r y Pi par t endo da zer o

te segue la stessa filosofia. E’


una sorta di “scatola” nella quale
si passano i parametri, ossia i dati
su cui effettuare le elaborazioni, i
calcoli e le scelte e, alla fine, vie-
ne restituito un risultato definitivo.
Sulla base di quanto detto, creiamo
adesso una funzione che accetti,
come parametri, la base maggio-
re, la base minore e l’altezza di un
trapezio e ne calcoli l’area. Ricor-
diamo che da sola, la UDF, non fa
miracoli. Deve essere il program-
matore a conoscere le formule
esatte e gli algoritmi da seguire.
Figura 2: La filosofia di una funzione utente.
AREA DEL TRAPEZIO
La riga 03 preannuncia al compilatore la crea- Ricordiamo che l’area del trape-
zione di una nuova UDF (prototipo con punto e zio è data dalla formula riportata in figura 3.
virgola finale). Si può eventualmente ometterla
e al suo posto riportare l’intera funzione, ma è
consigliabile seguire tale strada.
Dalla riga 21 alla 31 abbiamo la funzione vera
e propria. Il suo nome è “Primo()” e restituisce, Figura 3: Area del trapezio.
al programma chiamante, un puntatore ad una
stringa, per permettere il passaggio della frase E’ semplice creare una funzione, utilizzabile
“Primo” o “Composto”. A sua volta, la funzione anche per i nostri futuri programmi, che calco-
ha ricevuto dal programma chiamante il para- li l’area di un trapezio, passandole i parametri
metro “n”, scritto tra parentesi, che contiene il relativi alla base maggiore, base minore ed al-
numero da processare. La UDF appena creata tezza. Il listato seguente mostra l’intera imple-
può essere, dunque, “richiamata” in molti modi. mentazione mentre in figura 4 è visibile l’output
Gli esempi a seguire chiariranno ancor meglio del programma.
tale aspetto. Guardando la figura 2, possia- Il listato è molto semplice. Esso inizia con la
mo paragonare una funzione #include <stdio.h>
UDF ad una macchina per float AreaTrapezio(float b1,float b2,float h);
cucinare. In essa si inserisco- int main(){
printf("L'area del trapezio e': %f\n",AreaTrapezio(13,9,7));
no gli ingredienti, e dopo una printf("L'area del trapezio e': %f\n",AreaTrapezio(63.3,29.6,71.3));
adeguata preparazione, la pa- return 0;
sta è pronta. La funzione uten- }
float AreaTrapezio(float b1,float b2,float h) {
Cors o C s u Raspber r y P i par t endo da zer o {84

return (b1+b2)*h/2; Autore();


} return 0;
}
void Autore() {
printf("---------------------\n");
printf("! Autore del corso: !\n");
printf("! Giovanni Di Maria !\n");
Figura 4: Esecuzione del programma “Trapezio”. printf("---------------------\n");
}

dichiarazione del prototipo di funzione che in-


forma il compilatore che da qualche parte del La funzione Autore() ha il compito di visualizzare

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

printf(“L’area del trapezio e’: linguaggi di programmazione, una funzione che

%f\n”,AreaTrapezio(13,9,7)); non ritorna nulla è definita procedura.

la esegue direttamente e la “printf” visualizza a


video il risultato calcolato. Occorre sempre ri-
spettare le tipologie di dato. In questo caso si
tratta di float, in quanto occorre prevedere sem-
pre la virgola in questa casistica di operazioni.

FUNZIONI SENZA PARAMETRI E SEN-


Figura 5: Funzione void senza parametri.
ZA RISULTATO
Una funzione non deve obbligatoriamente rice-
vere dei dati dal programma chiamante e nem- FUNZIONE SENZA PARAMETRI CHE
meno ne deve restituire. Tutto dipende dal RITORNA UN VALORE
contesto e dalle esigenze del programmato- Ecco un esempio molto diffuso di funzione, dove

re. Il seguente esempio ne mostra una che non essa non riceve alcun parametro dal program-

riceve parametri e nemmeno le restituisce come ma chiamante eppure restituisce un valore. La

risultato. Si tratta di una funzione che visualizza, a funzione “Orario” non riceve alcun parametro

video, alcune informazioni ripetitive. ma restituisce, in formato stringa, la data e l’ora


odierna. Per questo motivo essa è definita come
#include <stdio.h> puntatore a carattere. La gestione dell’orario
void Autore();
sarà approfondita nel corso avanzato tra
int main(){
Autore(); qualche mese. La figura 6 mostra l’esecuzione
printf("\n\n"); dell’esempio. Imparate a creare da voi le vostre
85} Cors o C s u Raspber r y P i par t endo da zer o

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");
} }
}

FUNZIONE CON PARAMETRI SENZA


RITORNO DI VALORE
Anche questi tipi di funzioni
sono molto diffuse. L’esempio
che segue crea un modo per
disegnare dei rettangoli in mo-
dalità testo. Vengono passate
la larghezza e l’altezza della
figura, espresse in numero di
caratteri. Ogni volta che si in-
voca la funzione una figura
piana è disegnata sul video. Nessun valore Figura 7: Disegnare rettangoli con una funzione.
è, ovviamente, restituito e il compito della
funzione è esclusivamente esecutivo. Per ri- PASSAGGIO DEI PARAMETRI PER
chiamarla è sufficiente specificare il numero di VALORE E PER INDIRIZZO
caratteri desiderati per la larghezza e l’altezza Gli esempi visti fino ad ora hanno previsto il
del rettangolo. Quest’ultimo è disegnato utiliz- passaggio dei parametri per valore. Questo
significa che qualsiasi cosa fosse successo
#include <stdio.h>
void Rettangolo(int larghezza,int altezza); all’interno della funzione, il dato ad essa con-
int main(){ segnato non si sarebbe modificato. Guardia-
Rettangolo(12,5); mo, infatti questo esempio, seguendo la sua
printf("\n");
esecuzione, linea per linea, come in un debug-
Rettangolo(65,3);
printf("\n"); ger.
Rettangolo(44,8); Eseguendo il programma, il cui output è raf-
Cors o C s u Raspber r y P i par t endo da zer o {86

figurato in figura 8, i valori della


variabile “k” sono i seguenti:
• all’interno della funzione
main() vale 123;
Figura 8: Passaggio per valore.
• passata alla funzione UDF
vale sempre 123; #include <stdio.h>
void Incrementa(int* k);
• sempre nella UDF, modificata (+22) vale
int main(){
145; int k;
• ritornando nella main, perde le modifiche k=1;
printf("Siamo nella main. Prima della chiamata
e vale 123.
'K' vale %d\n",k);
Incrementa(&k);
Tutto questo vuol dire che ogni variabile di- Incrementa(&k);
Incrementa(&k);
chiarata all’interno di una funzione può esse-
Incrementa(&k);
re modificata solo in questo ambito. Per fa si Incrementa(&k);
che le modifiche possano essere effettuate printf("Siamo nella main. Dopo la quinta chia-
mata 'K' vale %d\n",k);
anche “da fuori”, si devono trasmettere le
return 0;
variabili per indirizzo (si può anche dire per }
referenza o per riferimento). In questo caso, void Incrementa(int* k) {
la funzione non riceve il valore del parametro, *k=*k+1;
}
ma l’indirizzo (puntatore) di quello dichiarato
nella funzione chiamante. Qualsiasi modifi-
ca, pertanto, interessa la variabile, cambian-
Figura 9: Passaggio per indirizzo.
dola stabilmente. Tale codifica è un po’ più
complicata ma estremamente potente.
PASSARE UN ARRAY AD UNA FUN-
La figura 9 mostra il valore della variabile “k”
ZIONE
prima e dopo le cinque chiamate della fun-
Per i vettori il discorso è un po’ diverso. Gli ar-
zione utente.
ray possono esse-
1 #include <stdio.h> re passati, come
2 void Funzione(int k);
3 int main(){ argomenti, ad una
4 int k; funzione e l’opera-
5 k=123; zione è estrema-
6 printf("Siamo nella main. Prima della chiamata 'K' vale %d\n",k);
mente semplice.
7 Funzione(k);
8 printf("Siamo nella main. Dopo la chiamata 'K' vale %d\n",k); Specificando, come
9 return 0; parametro, il nome
10 }
dell’array, in effetti
11 void Funzione(int k) {
12 printf("Siamo nella Funzione UDF. Prima della modifica 'K' vale %d\n",k); non si passano tut-
13 k=k+22; ti gli elementi ma
14 printf("Siamo nella Funzione UDF. Dopo la modifica 'K' vale %d\n",k);
semplicemente il
15 }
87} Cors o C s u Raspber r y Pi par t endo da zer o

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;
}

CONCLUSIONI La figura 10 spiega graficamente tali concetti.


Gli esempi potrebbero proseguire all’infinito. In essa viene riproposto il listato per il calcolo
Esistono altre tipologie di trattamento delle fun- dell’area del trapezio. I colori identificano i tre
zioni, ma preferiamo, al momento, fermarci qui diversi parametri, ossia:
Cors o C s u Raspber r y P i par t endo da zer o {88

Figura 10: Corrispondenza dei parametri nel numero, tipo e ordine.

• 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

INTRODUZIONE LE STRINGHE NEL LINGUAGGIO C

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

come carattere ‘\0’ (apice singola perché si


tratta di un solo carattere). Senza di esso la Nome[8]='\0';
Nome[1]='i';
lunghezza della stringa potrebbe essere molto Nome[3]='v';
printf("%s\n",Nome);
alta. Adesso, per inserire i caratteri nell’array, in }
modo che esso diventi una stringa, vi sono di-
verse possibilità.

MEMORIZZAZIONE DELLA STRINGA


PER SINGOLO CARATTERE
Figura 2: Come un array ospita la stringa.
Questo metodo, un po’ scomodo, prevede l’inse-
rimento di ogni carattere della stringa in ciascu-
La funzione printf la conosciamo già. La strin-
na cella dell’array. La cosa importante è quella
ga di formattazione “%s” fa capire alla funzione
di prevedere sempre il carattere di terminazione
che essa deve stampare il contenuto dell’array
della stringa, onde prevenire effetti spiacevoli,
“Nome” in formato stringa, ossia deve visua-
ai fini del programma, come visibile in figura 2.
lizzare, ad uno ad uno, tutti i caratteri in essa
Il seguente listato svolge proprio tale compito:
presenti, fino al segnale di terminazione stringa
(‘\0’).
#include "stdio.h"
int main() {
char Nome[10]; ASSEGNAZIONE DIRETTA
Nome[0]='G';
Nome[1]='i'; E’ possibile memorizzare direttamente la stringa
Nome[2]='o';
Nome[3]='v'; all’inizio del programma, nella fase di dichiara-
Nome[4]='a'; zione dell’array. Con il seguente statement:
Nome[5]='n';
Nome[6]='n';
Nome[7]='i'; #include "stdio.h"
Nome[8]='\0'; int main() {
printf("%s\n",Nome); char Nome[10]="Giovanni";
} printf("%s\n",Nome);
}

In questo modo, manualmente, abbiamo inse-


assegniamo la stringa, in un unico colpo, all’ar-
rito i caratteri facenti parte del nome “Giovanni”
ray, durante l’operazione di dichiarazione. Pur-
all’interno dell’array. Non è necessario rispet-
troppo, nel corso del listato, non è possibile
tare strettamente l’ordine degli statement, l’im-
eseguire la memorizzazione diretta, come nel
portante è allocare i caratteri nel posto giusto.
seguente caso. Il compilatore, infatti, produrreb-
Pertanto, il seguente codice è equivalente:
be un messaggio di errore.
#include "stdio.h"
int main() {
char Nome[10]; #include "stdio.h"
Nome[6]='n'; int main() {
Nome[0]='G'; char Nome[10];
Nome[2]='o'; Nome="Giovanni";
Nome[5]='n'; printf("%s\n",Nome);
Nome[4]='a'; }
Nome[7]='i';
91} Cors o C s u Raspber r y P i par t endo da zer o

Un altro metodo è quello di utilizzare l’assegna- in figura 3, ci sono alcune novità:


zione come un array ordinario, ossia elencare
i singoli caratteri tra parentesi graffa, racchiusi
#include "stdio.h"
tra apici singoli. In questo caso si deve anche #include "string.h"
riportare il carattere di fine stringa, come visibile int main() {
char Nome[70]="In questa puntata tratteremo
nel seguente listato: delle stringhe in linguaggio C";
char temp;
int lunghezza,k;
#include "stdio.h" lunghezza=strlen(Nome);
int main() { for(k=0;k<strlen(Nome)/2;k++) {
char Nome[]={'G','i','o','v','a','n','n','i','\0'}; temp=Nome[k];
printf("%s\n",Nome); Nome[k]=Nome[lunghezza-k-1];
} Nome[lunghezza-k-1]=temp;
}
printf("%s\n",Nome);
}

RIBALTAMENTO DI UNA STRINGA


Iniziamo adesso
a lavorare con le
stringhe, utilizzando gli array come contenitori Figura 3: Il ribaltamento di una stringa.
di esse. Vediamo come sia possibile ribaltare il
contenuto di una parola con un semplice ciclo Nello scambio delle lettere, il carattere NULL
iterativo. Si supponga di volere scrivere “al con- non viene toccato e resta al proprio posto, come
trario” la frase: si evince dalla figura 4.
“In questa puntata tratteremo delle stringhe in
linguaggio C”.
Il compito del programmatore è sempre quello
di trovare un buon algoritmo, ossia di una effi-
cace soluzione per raggiungere lo scopo. Come
potrebbe essere strutturato? Questa è una delle
tante soluzioni possibili, in pseudo codice. Figura 4: Il ribaltamento di una stringa.
• Prendi il primo carattere;
• Scambialo con l’ultimo; La prima cosa che balza subito agli occhi, nel
• Prendi il secondo carattere; nostro listato, è l’inclusione del file “string.h”. In
• Scambialo con il penultimo; esso sono memorizzati tutti i prototipi del-
• Prendi il terzo carattere; le funzioni dedicate alla manipolazione del-
• Scambialo con il terzultimo; le stringhe, che sono davvero tante. Una di
• Fermati appena raggiungi la metà della esse è quella utilizzata nell’esempio, strlen, che
parola; ha il compito di ritornare la lunghezza di una
• Stampa il risultato. stringa, fino a incontrare il famoso carattere di
terminazione. Si potrebbe fare a meno di essa e
Nel seguente listato, la cui esecuzione è visibile costruirsi “in proprio” una funzione che esegua
Cors o C s u Raspber r y P i par t endo da zer o {92

il medesimo compito. La sua implementazione, Usando i puntatori non si ha l’assillo di specifi-


visualizzata di seguito, è semplice e consiste care a priori la lunghezza della stringa, ma per
nello “scorrere” i caratteri dell’array, ad uno ad dimensioni importanti, si dovrebbe allocare lo
uno, sino a quando si incontra il carattere NULL. spazio in memoria, come vedremo in una dei
Nel frattempo si contano i simboli processati. Il prossimi esempi.
seguente listato contiene la funzione “Lunghez-
zaStringa” e il suo utilizzo. E’ restituito un intero CREIAMO UNA FUNZIONE PER
senza segno che contiene il numero dei carat- UPPER E LOWER
teri contati. La stringa da gestire è passata, ov- Una volta compresa la filosofia delle stringhe
viamente, come puntatore a carattere. è relativamente semplice implementare degli
algoritmi in grado di risolvere qualsiasi proble-
#include "stdio.h" matica legata ad esse. Il modo migliore per im-
int LunghezzaStringa(char *st) { parare è quello di provare e riprovare sul cam-
unsigned int tot=0;
while(1) { po la programmazione, magari sbagliando, ma
if(st[tot]=='\0') toccando con mano le esecuzioni e i comporta-
break;
tot++; menti da parte del compilatore e dell’eseguibile
}
return tot; prodotto.
} Quello che faremo adesso è creare alcune fun-
int main() {
printf("%d\n",LunghezzaStringa("Ciao a tutti")); zioni che eseguano i seguenti compiti, come
} evidenziato in figura 5:

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

fabeto, la soluzione potrebbe essere raggiunta


seguendo un particolare ragionamento, esami- int c = 0;
nando uno stralcio della tabella dei codici Ascii, while (s[c] != '\0') {
if (s[c] >= 'A' && s[c] <= 'Z')
di cui in figura 6. s[c] = s[c] + 32;
c++;
Come si vede in figura 6, la tabella propone le }
lettere maiuscole sulla sinistra e le lettere mi- }
nuscole sulla destra. I lettori più attenti avranno
notato che tra le lettere maiuscole e le minusco- Abbiamo realizzato due funzioni equivalenti,
le intercorre sempre la medesima differenza, dichiarate all’inizio del listato come prototipi.
relativamente al loro codice ASCII. Pertanto la A esecuzione del programma viene chiesto di
“A” maiuscola, con codice Ascii 65 e la “a” inserire una stringa composta da maiuscole e
minuscola, con codice Ascii 97, hanno una minuscole. Questa è memorizzata in un array di
differenza codificata di 32. caratteri. Quindi vengono eseguite, nell’ordine,
E’ facile, a questo punto, eseguire una conver- le due funzioni che modificano le lettere.
sione da maiuscole a minuscole e viceversa. Se Il tutto avviene per indirizzo, per cui le funzio-
si vuol tradurre un carattere maiuscolo in minu- ni non devono restituire alcun valore, visto che
scolo è sufficiente aggiungere il valore 32 al suo le modifiche restano “indelebili” in RAM, anche
codice Ascii. Per la conversione inversa, invece, all’uscita delle stesse. Il lettore studi bene il
basta sottrarre tale valore. L’implementazione comportamento del programma ben sapendo,
seguente sarà altrettanto semplice da capire. in ogni caso, che la stessa operazione potrebbe
essere eseguita in altre diverse modalità. Come
IMPLEMENTAZIONE funziona? Nella funzione UDF, un contatore (c)
#include <stdio.h> viene posto a 0 ed indica la posizione di un in-
void maiuscolo(char []); dice all’interno dell’array. Un ciclo iterativo “whi-
void minuscolo(char []);
int main() { le” effettua una scansione dell’array, carattere
char string[40]; per carattere, fino al simbolo NULL (‘\0’). Se il
printf("\n\nInserire una stringa composta da
caratteri maiuscoli e minuscoli\n\n"); carattere trovato è compreso nel set delle maiu-
gets(string);
printf("\n\n"); scole (o minuscole, a seconda dei casi), viene
maiuscolo(string); effettuata la conversione. Da notare, cosa molto
printf("Stringa tutta in MAIUSCOLO: %s\n\n",
string); interessante, che lo statement:
minuscolo(string);
printf("Stringa tutta in minuscolo: %s\n\n",
string); if (s[c] >= 'A' && s[c] <= 'Z')
return 0;
}
void maiuscolo(char s[]) { può essere anche sostituito con quest’altro:
int c = 0;
while (s[c] != '\0') {
if (s[c] >= 'a' && s[c] <= 'z') if (s[c] >= 65 && s[c] <= 90)
s[c] = s[c] - 32;
c++;
} In effetti una implementazione di queste funzioni
}
void minuscolo(char s[]) { esiste già nel linguaggio C. Nel file di inclusione
Cors o C s u Raspber r y P i par t endo da zer o {94

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

Figura 8: Costruzione di una stringa mediante la funzione sprintf.

STRLEN na, non c’è il bisogno di inserire i comandi


Restituisce la lunghezza di una stringa, ossia il del preprocessore per i files di inclusione. Si
numero dei caratteri che la compongono. Il suo faccia sempre attenzione a distinguere i carat-
prototipo è il seguente: teri dalle stringhe. La seguente assegnazione
memorizza la lettera ‘A’ (tra singoli apici) nella
variabile carattere
size_t strlen(const char *s);

char carattere = 'A';

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’):

CONCLUSIONI char st[] = "A";


Bene, l’articolo volge al termine e non ha, ov-
viamente, la pretesa della completezza. L’argo- Nelle funzioni che trattano le stringhe non si
mento è estremamente vasto e ciò che abbiamo devono specificare gli indirizzi delle variabili, in
visto in questa puntata costituisce solo un pic- quanto sono esse stesse degli indirizzi, dei pun-
colo granello di sabbia nel deserto della tratta- tatori.
zione delle stringhe. In un programma, se non
si utilizzano le funzioni della libreria ester- Alla prossima

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:

USIAMO SUBITO I FILES FILE *handle;


A differenza della stragrande maggioranza del-
le pubblicazioni che trattano l’accesso ai files, il Si tratta di una dichiarazione di variabile, posta
nostro approccio prevede una pratica diretta al ad inizio programma. E’ un puntatore a file, ed
loro utilizzo. In questa maniera, invece di sorbir- è dichiarato nel file di inclusione “stdio.h”, ma
si chilometriche teoriche trattazioni, si utilizzerà al programmatore non interessa più di tanto. Il
subito tali dispositivi e si comprenderà immedia- suo scopo è quello di creare un riferimento al
tamente l’utilità e il modo di accesso. dispositivo, cosicché ci si può riferire ad esso
per qualsiasi operazione.
TABELLINE A SCUOLA
Si supponga che vostro figlio vi chieda di stam- handle=fopen("tabelline.txt","w");
pare un foglio contenente tutte le tabelline,
dall’1 al 10, poiché la maestra ha lasciato tale Questa funzione “tenta” di aprire il file “tabelline.
compito per casa. Lungi dal trascrivere a mano txt” in modalità scrittura (“w”) e lo assegna ad
cento righe di moltiplicazioni, vediamo come uno stream di comunicazione. Se l’apertura non
produrre un file di testo contenente l’esercizio, ha successo, la funzione fopen ritorna NULL.
stampabile e visualizzabile a piacere, sia a casa
che a scuola. Guardiamo, innanzitutto, il sor- fprintf(handle,"Tabellina del %d\n\n",i);
gente.
Funziona esattamente come la sorella printf,
#include "stdio.h" ma il contenuto delle informazioni è inoltrato
int main() {
int i,k; verso il file specificato al primo parametro. Se
FILE *handle; si vogliono visualizzare i dati sullo schermo,
handle=fopen("tabelline.txt","w");
for(i=1;i<11;i++) { abbiamo due soluzioni alternative:
fprintf(handle,"Tabellina del %d\n\n",i); 1. Cambiare “fprintf” in “printf” e togliere il
for(k=1;k<11;k++)
fprintf(handle,"%2d x %2d = %3d\n",i,k,i*k); primo parametro “handle”.
fprintf(handle,"====================== 2. Cambiare il nome del file da “tabelline.txt”
=========\n");
} a “CON:” che rappresenta lo schermo.
fclose(handle);
return 0;
} fclose(handle);
99} Cors o C s u Raspber r y P i par t endo da zer o

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.

MODULO DI REGISTRAZIONE SU PC Figura 3: Maschera di inserimento degli iscritti.


L’esempio che segue realizza una sorta di inter-
faccia minimale, nella quale un utente può in- Il listato che segue contiene una grande novi-
serire al PC i propri dati, in particolare il proprio tà: la memorizzazione delle informazioni non
nominativo e l’indirizzo email. Quindi, il pro- va a eliminare i dati preesistenti, piuttosto essi
gramma, provvede a memorizzare tali dati sono accodati a quelli presenti concretizzando,
di fatto, la modalità di
memorizzazione di tipo
“append” con la funzione
fopen(“iscritti.txt”,”a”).
Il programma è estrema-
mente semplice. Le due
informazioni che gli uten-
ti devono digitare da ta-
stiera sono, ovviamente,
delle stringhe e vengono
dichiarate come array di
caratteri. Quindi si pas-
sa immediatamente alla
richiesta dei dati, attra-
verso la nuova funzione
Figura 2: Le tabelline generate e memorizzate in un file di testo. fgets. Perchè non abbia-
Cors o C s u Raspber r y P i par t endo da zer o {100

#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.

Questa strategia consente di tenere aperto il file


solamente durante la memorizzazione dei dati,
e non durante il data entry, che potrebbe anche
perdurare dei minuti. L’archivio, infatti, deve
restare aperto per il minor tempo possibile.
Dopo numerosi inserimenti dei dati da parte de- Figura 4: I nominativi degli iscritti memorizzati in un file
di testo.
101} Cors o C s u Raspber r y Pi par t endo da zer o

biamo calcolare delle statistiche. Come al soli-


to sarebbe impensabile effettuare i conteggi a
mano, siamo nell’era dei computer e qualsiasi
operazione ripetitiva, noiosa e critica deve es-
sere svolta automaticamente, senza la possi-
bilità che avvengano errori di qualsiasi genere.
L’elenco testuale contiene, semplicemente, mi-
gliaia di nominativi. Si devono contare tutti i nomi
che iniziano per “A”, per “B”, per “C”, e così via
fino alla “Z”. Questo esempio è particolarmente
didattico poiché mette a fuoco parecchie “acro-
bazie” informatiche. Un esempio di archivio po-
trebbe essere quello mostrato in figura 5.
Il programma che andremo a creare deve effet-
tuare alcune semplici operazioni:
• Aprire l’archivio dei nomi (“nomi.txt”).
• Leggere rigo per rigo, fino alla fine del file.
• Controllare la prima lettera di ciascun
nome.
• Incrementare il relativo contatore.
• Chiudere il file dei nomi.
• Visualizzare i totali.

#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

ramente. Questa puntata ha concluso il corso tata).


Base sul linguaggio C con il Raspberry PI, che
ci ha accompagnato assieme per un anno intero Ringrazio tutti coloro che hanno seguito il cor-
e che ha trattato i seguenti argomenti: so, chi ha commentato nel forum, chi ha posto
1. Introduzione; domande e chi ha esteso osservazioni. Spero di
2. Il nostro primo programma; essere stato utile a farvi entrare nel mondo della
3. I cicli; programmazione.
4. I vettori;
Dopo quest’ultima puntata del corso base
5. Gestione del video;
avrà inizio il corso avanzato, per il quale si
6. I Puntatori;
presuppone che il lettore abbia acquisito tutte
7. Gestire le porte di output con il Raspberry
le conoscenze fin qui erogate. Consiglio di evi-
PI
tare l’approccio con il nuovo corso senza aver
8. Gestire le porte di input con il Raspberry
visionato il precedente, poiché saranno affron-
PI;
9. Ottimizzazione del codice; tati argomenti più complessi senza trattare nuo-

10. Scrivere le proprie funzioni; vamente quelli base.


11. La gestione delle stringhe; Rinnovo l’appuntamento tra qualche settimana,
12. Gestire i files su disco (la presente pun- con il nuovo livello delle lezioni. Buon lavoro.

Figura 6: Il conteggio dei nomi per lettera iniziale.

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

Potrebbero piacerti anche