Sei sulla pagina 1di 78

Dispense di Sistemi elettronici A.A.

2015/2016
1MGVSGSRXVSPPSVM
Tredicesima lezione ( 02-12-2015 )

Oggi impareremo ad usare il microcontrollore e a conoscerlo a fondo. Inoltre utilizzeremo le


periferiche sapendo come sono fatte dentro. Un ingegnere informatico, in questa
prospettiva, incontra molte dicoltà in quanto non ha le giuste conoscenze di base.
Nei microcontrollori che useremo (ovvero i microcontrollori dell'MSP430) si può usare un
sistema di sviluppo chiamato Energia che mi permette di usare un approccio simile ad a
quello di Arduino. Energia è gratuito e consente di trattare i microcontrollori della famiglia
Texas come se fossero dei microcontrollori Arduino tanto che un codice per Arduino può
essere usato senza alcuna modi-ca sui microcontrollori della famiglia Texas.
Questo approccio è eciente per le applicazioni di tipo hobbistico perché mi permette di
arrivare brevemente ad un risultato ma non è eciente a livello professionale in quanto, nel
momento in cui il codice scritto da altri non funziona, non so come ottimizzarlo o farlo
funzionare.
Porremo delle basi per costruire delle basi più profonde sui microcontrollori.
I microcontrollori vengono usati ovunque così come l'ampli-catore operazionale viene usato
ovunque in analogico: è presente sia a livello di sistema elettronico sia a livello di circuito
integrato.
Nell'immagine a destra mostriamo i livelli di astrazione di un
circuito elettronico ed in particolare di un sistema digitale.
Finora abbiamo concentrato la nostra attenzione a livello di
modulo ma anche a livello di sistema (quando abbiamo
parlato di circuito stampato).
Se consideriamo un sistema VLSI costituito da un
microcontrollori, non sappiamo ancora come fare funzionare il
microcontrollore all'interno del sistema elettronico. Per capire
come funziona il microcontrollore devo usare ulteriori livelli di
astrazione:
1. livello di architettura interna del microcontrollore
(Istruction Set Architecture) o del processore: con le
architetture X86, ARM ecc. si lavora più ad altro livello per
cui possiamo dire che questo è di dominio dell'ingegnere
informatico.
2. linguaggi ed algoritmi: questo livello è anche più alto di quello visto precedentemente in
quanto posso fare funzionare il microcontrollore a prescindere dalla sua architettura interna
ed usando solo un linguaggio di programmazione o, ancora più ad altro livello, usando uno
schema a blocchi passando al livello algoritmico. In genere quando lavoro con i
microcontrollori lavoro in C o in Basic. Assembler è a metà tra il linguaggio di
programmazione e l'Istruction Set Architecture ma non è più usato in quanto è un

Antonio Daidone, Francesca Mignemi, Antony Scivoletto, Giovanni Susinni, Nella Romano - © Vietata la vendita
Dispense di Sistemi elettronici A.A. 2015/2016

approccio legato al particolare tipo di microcontrollore usato. Devo infatti usare le


istruzioni che sono direttamente interpretate dalla ALU (somma, lettura della memoria..) e
che sono speci-che di quel particolare microcontrollore. Invece con linguaggio C
(linguaggio ad alto livello) mi svincolo dalla particolare architettura hardware quindi posso
usare lo stesso linguaggio per programmare più microcontrollori (anche di famiglie diverse).
Svincolarsi dall'architettura diventa ancora più semplice se ragiono a livello algoritmico.
Posso descrivere le operazioni che il microcontrollore deve eseguire tramite un diagramma a
blocchi: la traduzione del diagramma a blocchi in linguaggio C è del tutto trasparente. Ci
sono anche tool che funzionano in Matlab per cui posso programmare il microcontrollore
costruendo uno schema a blocchi in Simulink e senza scrivere neanche una linea di codice.
Non vedo né l'Istruction Set né il linguaggio di programmazione C: lavoro a livello gra-co.
Cosa si intende per microcontrollore?
Uno microcontrollore è costituito da un
microprocessore con una serie di periferiche
integrate nello stesso die quali la memoria RAM, la
memoria ROM, le periferiche di I/O (convertitore
A/D), periferiche seriali (RS232), SPI, I2C, periferiche di tipo USB.
Il microprocessore ha la stessa architettura di un
microprocessore per tablet o PC ma con un numero
di bit non elevato (il numero di bit varia da 8 a 32). I
microprocessori a 32 bit sono abbastanza rari e sono
usati in particolari applicazioni.
Se avessi a che fare con un microprocessore e non con
un microcontrollore, avrei delle periferiche che
starebbero su altri die. Nel microcontrollore, invece,
tutto è integrato sullo stesso die: trovo l'oscillatore
per generare la frequenza di clock, un convertitore
A/D, le memorie. In alcuni trovo anche degli
ampli-catori operazionali che possono essere usati, per esempio, per implementare il -ltro
antialiasing di un convertitore A/D.
Quanti tipo di microcontrollori ci sono sul mercato?
Ce ne sono tantissimi perché dipendono dalla combinazione di queste periferiche, dalla
capienza della memoria RAM e della memoria ROM ecc.
Qual è il vantaggio di usare un microcontrollore?
Vantaggi:
1. sullo stesso die trovo il microprocessore e tutte le periferiche che mi servono per
interfacciarlo con il mondo esterno.
2. semplicemente alimentando il microcontrollore, questo è in grado di funzionare. Al
contrario, il microprocessore non potrebbe funzionare da solo in quanto dovrei prima
collegarlo alla mother board, collegare la memoria RAM e ROM, le periferiche I/O (in

Antonio Daidone, Francesca Mignemi, Antony Scivoletto, Giovanni Susinni, Nella Romano - © Vietata la vendita
Dispense di Sistemi elettronici A.A. 2015/2016

genere sono componenti esterni).


Nel microcontrollore ho tutto quello che mi serve per fare funzionare il sistema -nale e
quindi, sullo stesso die, ho tutto quello che mi serve per fare funzionare il sistema -nale.
Inoltre il PCB avrò meno componenti e l'adabilità migliorerà: se infatti ho tanti
componenti diversi su una scheda elettronica, l'adabilità di tutto il sistema dipenderà
dall'adabilità di ogni singolo componente. Supponiamo di avere un alimentatore, un
convertitore A/D ed un microprocessore: se uno di questi non funzionasse, il sistema non
funzionerebbe più. Quanto maggiore è il numero di componenti in serie nella catena di
processamento del segnale quanto minore sarà l'adabilità. Se tutti questi componenti si
trovano sullo stesso die e se il produttore lo ha già testato e garantisce un certo tempo di
vita in certe condizioni di funzionamento, l'adabilità aumenta e il costo diminuisce in
quanto occupo meno area nel circuito stampato. Il circuito stampato, quindi, potrà avere
una dimensione ridotta.
3. la riusabilità è un altro vantaggio perché il microcontrollore è riprogrammabile. Esso ha
una memoria ROM interna nella quale posso caricare il codice sviluppato con un
opportuno software. Sarebbe possibile riprogrammare il microcontrollore nel caso in cui
volessi usarlo per altri scopi oppure se nel caso in cui volessi correggere un errore.
Svantaggi:
1. quando suo un hardware a livello di CI progettato da qualcun altro, le prestazioni
diminuiscono perché mi devo accontentare delle prestazioni che può fornirmi quel
particolare microcontrollore. Posso implementare un sistema con prestazioni lontane da
quelle che potrei ottenere usando un approccio full custom. I microcontrollori vengono
usati da aziende molto piccole in quanto l'approccio full custom ha senso solo quando ho
grandi volumi di produzione. Il microcontrollore può essere usato da piccole aziende in
quanto può arrivare a costare solo 5 euro ed inoltre il sistema di sviluppo è gratuito. Se
volessi comprare la licenza, dovrei investire solo qualche centinaio o migliaio di euro.
Cos'è un sistema embedded?
Un sistema elettronico contenente all'interno un microprocessore è un microcontrollore
nella maggior parte dei casi.
Da Wiki: in elettronica ed informatica, con il termine sistema embedded
(generalmente tradotto in italiano con sistema integrato, letteralmente
immerso o incorporato) si identi-cano genericamente tutti quei sistemi
elettronici di elaborazione a microprocessore progettati appositamente per
una determinata applicazione (special purpose) ovvero non
riprogrammabili dall'utente per altri scopi, spesso con una piattaforma
hardware ad hoc, integrati nel sistema che controllano ed in grado di
gestirne tutte o parte delle funzionalità richieste.
Quanti sistemi embadded ci sono?
Quasi tutti i sistemi elettronici di una certa complessità sono dei sistemi
embedded visto che i microprocessori sono ovunque.

Antonio Daidone, Francesca Mignemi, Antony Scivoletto, Giovanni Susinni, Nella Romano - © Vietata la vendita
Dispense di Sistemi elettronici A.A. 2015/2016

Qualsiasi oggetto con dei componenti elettronici (hard-disk, mouse ecc.) avrà all'interno
anche un microcontrollore per la gestione. All'interno dell'hard-disk trovo anche un DSP
(Digital Signal Processor) che è un particolare microcontrollore in grado di eseguire un
numero elevato di operazioni al secondo e che viene programmato in C.
All'interno del DPS trovo un ALU che integra uno o più moltiplicatori per cui è capace di
eGettuare non solo le operazioni di somma ma anche quelle di prodotto.
Quando ho bisogno di fare delle operazioni in real-time nei sistemi di controllo, devo usare
un DSP: posso trovarlo nelle auto per il controllo elettronico della stabilità e dell'ABS.
Il sistema elettronico che gestisce un sistema elettromeccanico (come un auto) è molto
complesso tanto che il costo di tutta l'elettronica di bordo in un auto può arrivare al 30%
del costo totale.
Attualmente in una macchina moderna trovo un centinaio di microcontrollori che fanno il
controllo della temporizzazione della luce interna,il controllo del sistema di
climatizzazione, il controllo dell'ABS, del sistema di combustione e della della stabilità.
Nel caso della gestione del sistema di climatizzazione, uso un semplice microcontrollore a 8
bit mentre, per operazioni più complesse quale il controllo del sistema di alimentazione, uso
dei DSP a 32 bit. Questi microcontrollori, a loro volta, sono coordinati da un
microcontrollore a 32 bit che si trova nella centralina elettronica della macchina e che
riesce a dialogare con essi tramite un protocollo seriale.
Qual è la diGerenza tra un microprocessore e un microcontrollore?

Il microprocessore ha solo l'ALU e i registi. In realtà i microprocessori più moderni hanno


della memoria RAM ovvero una cache di sistema a più livelli. Questi elementi sono presenti
anche nei microcontrollori ma in aggiunta trovo anche la memoria RAM, una memoria
ROM e tutte le periferiche I/O (un convertitore A/D, un convertitore D/A, le periferiche
per generare forme d'onda di tipo pulse width modulation che servono a controllare i
motori, comparatori, encoder, porte I/O che possono essere con-gurate come ingresso o
come uscita digitale). Avere questi elementi sullo stesso die non esclude la presenza di un
converititore A/D esterno o di una memoria esterna: qualora mi dovesse servire

Antonio Daidone, Francesca Mignemi, Antony Scivoletto, Giovanni Susinni, Nella Romano - © Vietata la vendita
Dispense di Sistemi elettronici A.A. 2015/2016

un'aggiunta di memoria, potrei inserirla esternamente. Un sistema che presenta anche dei
componenti esterni e a cavallo tra i microprocessori e i microprocessori perché ho sia
periferiche interne (circuito per la generazione del clock ecc.) sia periferiche esterne (la
memoria deve essere interfacciata esternamente).

Le architetture interne dei microcontrollori sono di due tipi: architettura Von Neumann e
architettura Harvard.
L'architettura Von Neumann ha una memoria dati e per il programma separate tra loro e
che sono indirizzabili dallo stesso bus. Condividendo lo stesso bus, le memorie devono avere
per forza lo stesso numero di bit ed inoltre non posso contemporaneamente leggere
l'istruzione dalla memoria e scrivere il risultato di un operazione intermedia. Per fare ciò
ho bisogno di due cicli di clock.
Nell'architettura Harvard ho un bus dedicato per le periferiche di I/O per la memoria dei
programmi e dei dati. Con questa architettura posso ottenere maggiori prestazioni in
quanto posso leggere una periferica d'I/O, leggere un dato dalla memoria e leggere
l'istruzione successiva da eGettuare nello stesso periodo di clock. In compenso è più costosa
perché devo ridondare i bus da implementare: nei microcontrollori PIC e in quelli della
Intel (quando ancora faceva microcontrollori)
si usa l'architettura Harvard.
I microprocessori della Texas Instrument che
useremo nell'esercitazione fanno uso
dell'architettura Von Neumman.
Sul mercato ci sono diverse casi produttrici
di microcontrollori ed inoltre ne ho di diversi
tipi.
Attualmente i produttori principati solo la
Microchip (produce i PIC), la Atmel
(produce i microcontrollori che stanno sulla
piattaforma Arduino) e la Texas Instrument.
La Intel commercializza ancora
microcontrollori ma vengono usati

Antonio Daidone, Francesca Mignemi, Antony Scivoletto, Giovanni Susinni, Nella Romano - © Vietata la vendita
Dispense di Sistemi elettronici A.A. 2015/2016

pochissimo. La Philips e la General Instrument (non esiste più perché è stata assorbita
dalla Microchip) sono altri produttori.

La Texas Instrument produce molti tipi di microcontrollori: vediamone alcuni.

Abbiamo i microcontrollori a 16 bit (la prima a sinistra) che sono quelli della famiglia
MSP430 e che useremo durante l'esercitazione. Questi processori hanno un costo compreso
tra 0.49$ e 9$ e sono oggetti pensati per sistemi elettronici in cui non è necessario
eGettuare molte elaborazioni tanto che la frequenza di clock massima è di 25 MHz. Inoltre
sono stati pensati per tutti i sistemi elettronici che funzionano a batteria in quanto
consuma poco.
Se devo fare operazioni in tempo reale per controllare dei motori, uso dei microcontrollori a
32 bit e in particolare quella della famiglia C2000 che è pensata per controllare sistemi in
real-time ed è in grado di eseguire operazioni in Moating point. Ha dentro un unità a
virgola mobile che non è presente nel microcontrollore a 16 bit.
Una variabile reale può essere usata in maniera emulata: il sommatore in hardware che
abbiamo studiato non è in grado di fare operazioni in virgola mobile. Il costo di questo
microcontrollore sale in quanto il costo massimo è di 20 dollari, la frequenza di lavoro
cresce così come la quantità di periferiche che posso usare.
Ci sono anche i microcontrollori della famiglia ARM usati per sistemi elettronici di uso
generale.
Se devo fare un rilevatore di fumo posso usare un microcontrollore a 16 bit ma anche
quello a 32 bit. Se usassi un microcontrollore a 32 bit, però, metterei sul campo delle
risorse hardware che mi costano molto e che magari non uso.

Antonio Daidone, Francesca Mignemi, Antony Scivoletto, Giovanni Susinni, Nella Romano - © Vietata la vendita
Dispense di Sistemi elettronici A.A. 2015/2016

La scelta del microcontrollori va fatta in base alle speci-che che il sistema deve soddisfare e
anche in base al costo massimo che posso impiegare per il microcontrollore.
La Texas Instrument mette in gioco anche dei processori di alta fascia pensati per
macchine fotogra-che, per gli smartphone, per i tablet, per i sistemi di comunicazione che
ci sono nelle stazioni radiobase ecc. Nei sistemi che mi permettono di controllare le celle
GSM trovo dei DSP che hanno un costo anche elevato (un centinaio di dollari) in quanto
sono in grado di fare un numero di operazioni al secondo elevato.
Ci sono i microprocessori che al loro interno hanno un DSP ed un altro microprocessore:
sono gli OMA. Gli OMA integrano al loro interno DSP e microcontrollore. Sono pensati
per tablet , smartphone e in generale per dispositivi in cui devo fare codi-che audio-video.
Il DSP viene usato per fare queste operazioni e viene usato come se fosse una scheda
gra-ca nello stesso die mentre il microcontrollore viene usato per gestire il sistema
operativo.
I microcontrollori di questo tipo sono a metà tra microcontrollori e microprocessori: per
gestire il funzionamento di questi oggetti devo per fare uso per forza di un sistema
operativo come Linux. Per fare funzionare un microcontrollore serve un sistema operativo
da caricare. Anche per i microcontrollori a 16 bit posso caricare un piccolo sistema
operativo e questo l'approccio usato molto spesso dai softwaristi in quanto possono
applicare le nozioni di ingegneria del software per codi-care un certo algoritmo.
Il sistema operativo richiede una buona fetta di risorse della memoria quindi si preferisce
usare un approccio più di basso livello programmando il microcontrollore con-gurando
direttamente le periferiche.

Quanti tipi diversi di microcontrollori ricadono all'interno di questa categoria?


Ce ne sono centinaia. La Texas Instrument produce anche 400 microcontrollori diversi in
una categoria che si diGerenziano per consumo, memoria RAM, periferiche ecc. All'interno
della famiglia MSP430 ricadono queste categorie di microcontrollori.

Antonio Daidone, Francesca Mignemi, Antony Scivoletto, Giovanni Susinni, Nella Romano - © Vietata la vendita
Dispense di Sistemi elettronici A.A. 2015/2016

Partiamo dai microcontrollori G2 che useremo nell'esercitazione che hanno una frequenza
massima di clock di 16 MHz, una Mash che al massimo arriva a 16 kB, un numero di GPIO
compreso tra 10 e 16. Sono microcontrollori piuttosto semplici e pensati per sistemi
elettronici dove non devo fare un numero di operazioni elevate e per applicazioni a batterie.
Se ho un sistema alimentato da una batteria stilo che ha una tensione di 1.5 V, posso usare
questi microcontrollori. In-ne ho a disposizione una serie di periferiche.
In altre famiglie ho per esempio il convertitore D/A o l'ampli-catore operazionale: in base
alle operazioni da eseguire seleziono la famiglia ed il tipo di microcontrollore che soddisfa
maggiormente le mie esigenze. Una discriminante potrebbe essere la memoria Mash: su di
essa carico il codice per cui deve avere la capienza giusta. Quando scelgo la memoria Mash
probabilmente non ho ancora scritto il codice quindi devo cercare di tenere conto di un
certo margine: potrei prendere una Mash un po' più grande ma questo potrebbe portare ad
uno spreco di risorse. In queste applicazioni torna utile una famiglia che integra all'interno
una RAM di tipo ferroelettrico. Questa ha le stesse prestazioni della RAM 6T ma è non
volatile in quanto ogni singolo bit è fatto da un granulo di materiale ferroelettrico che può
essere magnetizzato e smagnetizzato. Questo mi permette di con-gurare in fase di
programmazione quanta RAM dedicare alle istruzioni e di non sprecare risorse. In questo
modo uso solo la porzione di memoria non volatile strettamente necessaria per
memorizzare il codice del microcontrollore. All'interno del microcontrollore la memoria
ROM perché devo sapere quali istruzioni eseguire dopo aver tolto l'alimentazione. Questo
tipo di memoria ha un vantaggio elevato in quanto è non volatile, è più stabile e consuma
di meno rispetto alla memoria RAM classica. Questo tipo di microcontrollori, quindi,
ricade nella categoria di microcontrollori ultra-low-power.
Andando verso destra ho un numero sempre maggiore di
periferiche. In alcuni ho una periferica che mi permette di
controllare un display LCD nel caso in cui abbia bisogno di
un sistema con feedback visivo mentre altri integrano un
tranceiver RF a 2.4 GHz nello stesso die nel caso mi servisse
un link wireless.
Adesso entriamo all'interno del microcontrollore e vediamo
com'è costituito. Vediamo in particolare quello che useremo
nell'esercitazione.
La CPU è semplice (vedi -gura a destra) perché è un
sommatore a 16 bit che riceve al suo ingresso l'uscita di più
registri. L'ALU riceve due parole binarie a 16 bit come
ingressi e fornisce una parola binaria a 16 bit in uscita.
Inoltre ho un ingresso di clock MCLK, un carry di uscita C,
un negative N e un zero Z. Questi sono segnali ausiliari usati
per capire se il risultato di un operazione è negativa, se
genera un carry, se il risultato di comparazione è true o false

Antonio Daidone, Francesca Mignemi, Antony Scivoletto, Giovanni Susinni, Nella Romano - © Vietata la vendita
Dispense di Sistemi elettronici A.A. 2015/2016

o se è andato in overMow. L'ALU fa delle operazioni di somma, di diGerenza, di confronto,


di AND, di OR e di NOT logico. L'operazione di prodotto viene fatta ciclicamente
sommando N volte lo stesso numero a sé stesso. All'ingresso dell'ALU posso mettere tutti
questi registri: ho 16 registri diversi, alcuni General Purpose che memorizzano dati caricati
dall'utente o delle operazioni intermedie che l'ALU è in grado di eGettuare. Un altro
registro è il Program Counter che tiene conto dell'istruzione che viene eseguita in quel
momento dalla CPU. Il Program Counter tiene conto dell'indirizzo di memoria
dell'istruzione che viene eseguita in quel momento dalla CPU.
Ho anche altri registri controllati dalla macchina a stati che gestisce il funzionamento di
tutto il processore. Questi registri non possono essere usati dall'utente: riprenderemo
questo discorso quando parleremo degli interrupt.
Se programmo in C e non uso particolari accortezze, i registri vengono allocati in maniera
automatica dal compilatore. In Assembler non vado a scrivere -sicamente su questi registri
ma è lo stesso compilatore che sceglie il primo registro libero e vi scrive gli operandi. Ho
pochi registri a disposizione quindi, se devo fare un elaborazione che richiede un numero
elevato di variabili, potrei dover usare tutti i registri. Se devo fare il prodotto di numeri
reali che richiede operazioni a 32 bit ma ho un ALU a 16 bit, posso fare ricorso ad
algoritmi particolari che permette l'esecuzione di quest'operazione facendo uso di un
numero elevato di cicli di clock. Se sto usando tutti i registri, la CPU non è in grado di
fare algoritmi più complicati per cui, in fase di compilazione, otterrò un errore.
Come è strutturata la memoria interna del microcontrollore che andremo ad usare
nell'esercitazione la cui sigla è MSP430G2553?
Dal punto di vista del microcontrollore, la memoria è come se fosse unica.
Ho degli indirizzi di memoria che puntano sia alla Mash che alla RAM.
L'utente vede la memoria come se fosse un unico blocco. Se devo scrivere
un informazione all'interno della Mash, devo puntare ad una certa locazione
di memoria e ad un certo range di memoria che dipende dal
microcontrollore e quindi dalla disponibilità di memoria Mash.
Gli indirizzi di memoria sono codi-cate a 16 bit: non tutte le allocazioni di
memoria sono scrivibili da parte dell'utente ma ci sono parti della memoria
che sono indirizzabili solo internamente dal microcontrollore. Quando vado
a gestire un operazione di interrupt, il microcontrollore va a scrivere in
una certa allocazione di memoria che è inaccessibile all'utente.
All'interno della memoria ho anche i registri di con-gurazione delle
periferiche: ognuno di questi registri corrisponde ad una periferica e la
parola binaria memorizzata all'interno di questo registro de-nisce una particolare
con-gurazione della periferica. Se devo con-gurare un certo I/O come uscita, devo scrivere
all'interno del registro corrispondente a quell'I/O. I registri di con-gurazione si trovano
all'interno della memoria del sistema e in particolare nella memoria RAM del sistema.
I microcontrollori della Texas Instrument nascono per applicazioni a basso consumo quindi

Antonio Daidone, Francesca Mignemi, Antony Scivoletto, Giovanni Susinni, Nella Romano - © Vietata la vendita
Dispense di Sistemi elettronici A.A. 2015/2016

per applicazioni a batteria: i loro microcontrollori sono stati i primi ad implementatare


internamente delle modalità di risparmio energetico. Queste modalità mi permettono di
attivare in maniera selettiva solo le periferiche, di ridurre la frequenza di clock anche in
maniera dinamica o di spegnere la CPU quando non devo eGettuare nessuna operazione.

Queste modalità di consumo energetico sono riportati in questa tabella per un particolare
microcontrollore della famiglia MSP20.
Supponendo di lavorare con la frequenza di clock di 1 MHz, quando è tutto accesso
(comprese le periferiche) la frequenza di clock è massima e il consumo è dell'ordine di 300
μA a 3 V. In questo microcontrollore ho la possibilità di usare le seguenti modalità di
consumo: LPM0 (Low Power Mode 0), LPM2, LPM3 e LPM4. Nella modalità LPM0
spengo tutte le periferiche e vado a ridurre la frequenza di clock. Nella modalità di
risparmio energetico più alto (LPM4) spegno anche la CPU. In questa situazione
rimangono accese solo la RAM e il controllo dell'interrupt che mi permettere di riaccendere
la CPU. L'interrupt può essere generato da un evento esterno o interno ad un
microcontrollore (pressione del tasto, timer ecc.).
Adesso mostriamo com'è possibile rilevare la
pressione di un dito sul PCB senza elementi di
tipo elettromeccanico. Il PCB implementa un
condensatore le cui facce sono costituite dal
cerchio interno e dal cerchio esterno separate tra
di loro (vedi -gura a destra). Ho un
condensatore che funziona con le linee di
campo che vanno in aria passando dalla
faccia interna a quella esterna. Queste
linee vengono modi-cate quando avvicino
il dito e questo causa una variazione di
capacità. Questo comportamento emula
un tipico tasti elettromeccanico. Si può
implementare questa funzionalità nei
microcontrollori che hanno una periferica
dedicata alla misura di una capacità. I

Antonio Daidone, Francesca Mignemi, Antony Scivoletto, Giovanni Susinni, Nella Romano - © Vietata la vendita
Dispense di Sistemi elettronici A.A. 2015/2016

microcontrollori che useremo hanno una periferica interna con-gurabile per rilevare questa
capacità.
La scheda che useremo (vedi scheda rossa in alto) è una scheda LaunchPad che ha un
socket di tipo PTH. Quindi posso mettere più microcontrollori compatibili con questo
socket all'interno della scheda. Inoltre ho un sistema di programmazione del
microcontrollore: ho un convertitore seriale/USB che mi consente di programmare il
microcontrollore usando la porta USB del PC ma all'interno ho una seriale a tutti gli
eGetti. Quindi quest'oggetto è un convertitore seriale/USB. Poi ho un altro
microcontrollore che eGettua l'operazione di programmazione del microcontrollore stesso.
La scheda può essere usata per programmare tutti i microcontrollori della famiglia MSP430
(anche quelli che non hanno questo socket compatibile). Usando dei -li (jumper) su questa
strip, posso andare a prelevare i segnali che in fase di programmazione mi permettono di
scrivere sulla memoria ROM del microcontrollore. Con 10 euro compro un programmatore
e questo è un grosso vantaggio rispetto ai PIC che hanno programmatori molto costosi (il
meno costoso costa 60 euro). Qui, invece, compro un sistema di sviluppo che mi permette
di programmare tutti i microcontrollori della famiglia stessa.
Sulla scheda ho una parte in cui eGettuo la programmazione e una parte che mi permette
di interfacciare il microcontrollore col PC.
Sulla scheda ho delle strip line ovvero dei pin che mi mettono a disposizione un contatto
con tutti i pin del package del microcontrollori. Inoltre ho un tasto per scrivere un segnale
digitale, due LED per rilevare cosa sta facendo il microcontrollore, un tasto di reset e
un'alimentazione ausiliaria esterna. Il microcontrollore, in realtà, si alimenta tramite la
porta USB del PC. Questo sistema semplice permette di implementare sistemi anche molto
complicati perché, oltre al sistema di sviluppo, la Texas Instrument fornisce anche delle
schede che si innestano su questa strip e che implementano periferiche aggiuntive. Ci sono
schede che hanno codec audio: se devo codi-care un segnale audio, c'è una scheda che
posso innestare. Posso quindi sfruttare altre risorse hardware esterne al microcontrollore.
Ho anche schedine con tranceiver bluetooth, con GPS, con interfaccia capacitiva (hanno la
tastiera capacitiva) e queste sono anche impilabili tra di loro. In fase di sviluppo posso
testare il codice usando queste schede e, una volta sviluppato il codice, posso compattare
tutto su un'unica scheda e progettare il sistema -nale.
Con questo hardware posso implementare anche sistemi elettronici di una certa
complessità.

Antonio Daidone, Francesca Mignemi, Antony Scivoletto, Giovanni Susinni, Nella Romano - © Vietata la vendita

Powered by TCPDF (www.tcpdf.org)


Dispense di Sistemi elettronici A.A. 2015/2016

Vediamo in dettaglio le periferiche che possiamo trovare sul microcontrollore.

Le più importanti sono le GPIO che possono essere conigurate come input e output. Inoltre posso
conigurare le resistenze di pull-up e pull-down e degli interrupt su alcuni GPIO. Ho anche a
disposizione due timer che possono essere conigurati in vario modo e che mi servono per portare
impulsi di clock, conteggiare un tempo o per contare in maniera asincrona eventi provenienti
dall’esterno (eventi esterni).
Un timer particolare è il WatchDog perché genera un segnale di reset per il microcontrollore
appena arriva a fondo scala. È un sistema che garantisce che il microcontrollore non si sia
bloccato: resettandolo ciclicamente evito che rimanga bloccato da qualche parte dell’esecuzione
perché all’interno c’è un baco di progetto del microcontrollore (su dispositivi già commercializzati
vengono elencati nell’error data sheet). Ci possono essere malfunzionamenti nel microcontrollore
dati dalla programmazione o da bachi hardware: per questo motivo viene usato il WatchDog che
riesce ad aggirare questo problema.
La Brownout Reset è una periferica che mi permette di spegnere il microcontrollore nel momento
in cui la tensione scende al di sotto di una certa soglia: in questo modo controllo che
l’alimentazione stia dentro il range che mi garantisce che tutto funzioni correttamente.

Antonio Daidone, Francesca Mignemi, Antony Scivoletto, Giovanni Susinni, Nella Romano - © Vietata la vendita
Dispense di Sistemi elettronici A.A. 2015/2016

Ho anche delle periferiche per la comunicazione seriale che posso conigurare come SPI, I2C e
UART. Quest’ultima mi consente di colloquiare con il PC tramite la SR32. Ho anche altre
periferiche come il comparatore che mi permette di confrontare due segnali analogici e di fornire un
segnale digitale in output. Ho anche un convertitore A/D dalle speciiche elencate sopra.

Questo è uno schema a blocchi di un microcontrollore: ho una CPU a 16 bit, un clock di sistema, le
memorie, gli ADC, 3 porte ecc. A ciascuna porta corrispondono 8 pin (8 GPIO al massimo), una
periferica seriale WD, un comparatore ecc. Ho una serie di blocchetti che mi servono per
programmare il microcontrollore. Per scrivere all’interno della memoria lash bypassando il

Antonio Daidone, Francesca Mignemi, Antony Scivoletto, Giovanni Susinni, Nella Romano - © Vietata la vendita
Dispense di Sistemi elettronici A.A. 2015/2016

microcontrollore e per efettuare la programmazione del microcontrollore uso delle periferiche


ausiliari JTAG e Spy-by-Wire che sono delle interfacce che si collegano al programmatore e scrivono
dentro la lash. Queste periferiche sono presenti in tutti i microcontrollori.

Vediamo come si usano le periferiche e concentriamoci sulle GPIO.

Le GPIO sono organizzate in porte e ciascuna di essa corrispondono 9 GPIO. La conigurazione


delle GPIO è di tipo memory mapped cioè è efettuata tramite registri di conigurazione: a ciascuna
GPIO corrisponde 1 byte (un registro a 8 bit) che mi consente di dire al microcontrollore come
conigurare quella particolare GPIO.
Quanti tipi di conigurazione posso fare?
In realtà per conigurarla in maniera completa devo usare più registri. Il registro è sempre a 16 bit
ma si leggono solo gli 8 bit meno signiicativi per cui si parla di registro a 8 bit. Leggo questo
registro a 8 bit e coniguro ciascun pin del microcontrollore.
Il registro P1IN mi permette di leggere il segnale digitale collegato su un particolare pin della
porta 1. Questo ragionamento non vale solo per la porta 1 ma vale anche per le porte 2, 3 ecc..
Esiste un registro P1IN che ha un indirizzo di memoria codiicato con il nome simbolico P1IN. A
questo nome simbolico corrisponde sempre una locazione di memoria isica. Ho un registro
all’interno del quale sono memorizzati il risultato della lettura del segnale digitale collegato a
ciascun pin delle 8 porte.
P1OUT mi permette di scrivere un segnale digitale su ciascun pin, P1DIR mi permette di
conigurare la direzione della porta (di default tutti i registri sono posti a 0 ovvero conigurati come
ingresso). Se tutti i bit sono a 0, tutti i pin corrispondenti a quella porta sono intesi come ingressi
ma, nel caso in cui mi servisse conigurare uno di questi pin come uscita, andrei a scrivere un 1 in

Antonio Daidone, Francesca Mignemi, Antony Scivoletto, Giovanni Susinni, Nella Romano - © Vietata la vendita
Dispense di Sistemi elettronici A.A. 2015/2016

uno di questo registri.


Consideriamo ad esempio il registro P1DIR in cui ho 8 locazioni di memoria. A ciascuno di questi
bit corrisponde un pin indicato come P1.0 (porta 1.pin 0 e così via).

Nel momento in cui vado a scrivere un 1 in un bit e negli altri degli 0, avrò un pin settato come
un’uscita e gli altri come ingressi.

Il valore corrispondente si trova nel registro corrispondente P1OUT che è fatto allo stesso modo:
quando nella seconda locazione di memoria scrivo un 1, in uscita sul pin corrispondente alla porta
P1.1 leggerò un 1 logico (VDD).

P1OUT

Abbiamo spiegato la conigurazione memory mapped per i registri.


La P1REN mi permette di conigurare la resistenza di pull-up o pull-down: ho una resistenza
interna che posso conigurare connettendola al pin quando è necessario. Questo evita di lasciare
loating il pin nel caso in cui questo sia un ingresso. In alternativa potrei anche mettere una
resistenza di pull esterna.
Il vantaggio di questi pin è che possono essere conigurati sia pin di input che output. Esistono dei
protocolli di comunicazione (one Wire) che vengono utilizzati sia per scrivere il dato sia per leggere
il dato. La conigurazione può essere fatta run time quindi è possibile conigurare in maniera
dinamica la resistenza (con uno switch). Questo è il motivo per cui di solito si evitano le resistenze
esterne perché queste non possono essere conigurate in maniera dinamica ma la conigurazione
rimane issa.

Antonio Daidone, Francesca Mignemi, Antony Scivoletto, Giovanni Susinni, Nella Romano - © Vietata la vendita
Dispense di Sistemi elettronici A.A. 2015/2016

P1SEL serve ad instradare l’ingresso o l’uscita di un’altra periferica su un pin. Per evitare di
aumentare enormemente il numero di pin del microcontrollore, posso mettere un GPIO o un
ingresso di un ADC sullo stesso pin tramite un multiplexer in maniera esclusiva.
La scelta della periferica da collegare a ciascun pin del package del microcontrollore viene fatta
tramite P1SEL che è la parola binaria di un multiplexer a più ingressi. In sostanza ho la possibilità
di mettere più periferiche in alcuni pin.
Posso abilitare anche un segnale di interrupt su una porta e decidere su quale fronte (di salita o
discesa) abilitare l’interrupt. Ho anche un lag ovvero un registro che mi dice se è scattato un
interrupt su una data porta.

Antonio Daidone, Francesca Mignemi, Antony Scivoletto, Giovanni Susinni, Nella Romano - © Vietata la vendita
Dispense di Sistemi elettronici A.A. 2015/2016

Abbiamo detto che il P1SEL mi permette di multiplexare più periferiche su una porta.
Vediamolo in dettaglio.
Se prendo il PINOUT del microcontrollore dal datascheet (vedi igura), mi ritrovo la DVCC ovvero
l’alimentazione digitale sul PIN1 (DVSS è la massa digitale).
Ho dei pin associati a più periferiche: ad esempio sul PIN2 ho P1.0, TAOCLK (è l’ingresso di clock
del timer 0 che mi conteggia gli impulsi) ecc. Per usare questa funzione devo settare un pin
all’interno del P1SEL. In realtà posso avere più registri P1SEL nel caso abbia più periferiche.
P1SEL.0 e P1SEL.1, P1SEL2.0 e P1SEL2.1; posso avere all’uscita del pin il clock interno.
A0 è l’ingresso di un ADC, CAO è l’ingresso non invertente di un comparatore che è presente anche
nelle altre periferiche.

Antonio Daidone, Francesca Mignemi, Antony Scivoletto, Giovanni Susinni, Nella Romano - © Vietata la vendita
Dispense di Sistemi elettronici A.A. 2015/2016

Vediamo la descrizione dei segnali in tabella.


Vediamo la conigurazione di P1SEL1 e P1SEL2 per usare un determinata funzione che prevede la
una certa combinazione di P1SEL1 e P1SEL2. Per capire l’efetto dei registri di conigurazione sulla
periferica PORT possiamo fare riferimento al seguente schema a blocchi.
Nella igura vediamo la GPIO che ha una serie elevata di componenti al suo interno.

Antonio Daidone, Francesca Mignemi, Antony Scivoletto, Giovanni Susinni, Nella Romano - © Vietata la vendita
Dispense di Sistemi elettronici A.A. 2015/2016

1. Al pin del package mi ritrovo le resistenze di pull-up o pull-down e collego il pin a V CC o a


VSS tramite un multiplexer. La resistenza diventa di pull-down se collego il pin a VSS
viceversa diventa pull-up e la conigurazione avviene tramite un bit di selezione controllato
dal registro P1REN.
Nel caso in cui devo leggere l’ingresso, ho un trigger di Smith e una serie di multiplexer e di
lip-lop che mi permettono di conigurare la porta come ingresso o come uscita.
Ho un bufer tristate pilotato da P1DIR e dai bit di selezione. Se ad esempio devo tirare
fuori il clock devo abilitare il bufer tristate e questo vale per tutte le altre periferiche.
Di lato vediamo una versione sempliicata dello schema a blocchi. Capiamo come è facile
conigurare il GPIO come ingresso o come uscita: c’è sempre un bufer, ed esso se è
conigurato come uscita, il P1DIR ( vedi igura sopra, penultimo bit del registro di
conigurazione) se è conigurato come uscita il selettore è collegato e quindi leggo il
contenuto di P1OUT. Se è conigurato come ingresso e quindi il selettore è opposto vado a
leggere quello che c’è sul pin e scrivo il dato che c’è sul P1IN.

Antonio Daidone, Francesca Mignemi, Antony Scivoletto, Giovanni Susinni, Nella Romano - © Vietata la vendita
Dispense di Sistemi elettronici A.A. 2015/2016

Se ho un ingresso digitale, esso non può essere lasciato loating perché rischierei di mantenere MOS
in saturazione. In ingresso ho un inverter (un bufer) o una cascata di inverter: se entrambi gli
inverter sono in saturazione mi ritroverei una corrente di cortocircuito che scorre tra V DD e massa.
Per evitare ciò vincolo l’uscita a VDD o massa usando delle resistenze di pull-up o pull-down che
devono essere sempre presenti in un ingresso digitale.

Vediamo come è fatto il clock di sistema e come posso generare il clock all’interno del
microcontrollore. Il clock è direttamente proporzionale alla potenza dinamica del circuito e deve
essere scelto molto accuratamente in base alle operazioni da fare. Inoltre conoscere esattamente il
clock è fondamentale quando devo colloquiare con una periferica esterna: se devo far colloquiare la
seriale col PC, devo conoscere esattamente il boundrate in modo da sapere come conigurare l’altra
periferica. Devo sapere anche la durata del clock per capire dove devo leggere ogni bit. Per fare
questo, il clock deve funzionare ad una frequenza ben issata. Fisso la frequenza conigurando
determinati registri usando il DCO.
Il clock di sistema viene generato da un oscillatore digitale interno che è a tutti gli efetti un

Antonio Daidone, Francesca Mignemi, Antony Scivoletto, Giovanni Susinni, Nella Romano - © Vietata la vendita
Dispense di Sistemi elettronici A.A. 2015/2016

oscillatore a rilassamento. Esso viene calibrato tramite la resistenza in modo da ottenere una
frequenza variabile. Questo clock è detto master clock perché è quello usato dalla CPU per il suo
funzionamento. Esso è usato anche per altre periferiche: ad es. il master clock viene dato al timer
ADC, periferica seriale ecc.
In applicazioni particolari in cui devo dissipare pochissimo e non è necessaria una frequenza
elevata, posso usare il VLO: è un oscillatore che lavora ad una frequenza issa di 12 kHz (non è
conigurabile). Tramite il multiplexer istrado questo segnale a 12 kHz in modo che diventi un
master clock e possa essere fornito a tutte le periferiche.
ACLK (analog) viene generato tramite un quarzo esterno: ho la possibilità di collegare un quarzo
esterno a 32 kHz per applicazioni in cui necessito di un timer molto preciso. Posso moltiplicare la
frequenza del quarzo per un certo fattore attraverso dei circuiti che mi permettono di arrivare ino
alla frequenza massima.

Il comparatore non è altro che un ampliicatore operazionale ad anello aperto ed è porta logica che
mi permette di ripristinare lo swing logico (0 – VDD). L’uscita del comparatore può normale o
invertita. Esiste un multiplexer collegato a un registro di conigurazione ed opzionalmente posso
collegare anche un iltro al segnale d’uscita (nel caso di segnali rumorosi in ingresso metto un iltro
passa-basso). Per evitare commutazioni indesiderate uso una sorta di trigger di Smith. Qui invece

Antonio Daidone, Francesca Mignemi, Antony Scivoletto, Giovanni Susinni, Nella Romano - © Vietata la vendita
Dispense di Sistemi elettronici A.A. 2015/2016

sono analogici. Attraverso il comparatore


posso confrontare due segnali proveniente dall’esterno oppure posso comparare un segnale analogico
esterno con uno interno generato tramite una serie di multiplexer. Il segnale interno è generato
tramite un partitore di precisione che seleziona una tensione pari a 0.5 VDD o a 0.25 VDD
attraverso un multiplexer.
I timer possono funzionare come contatori di clock interno o come contatori di eventi esterni
(contatori asincroni).
Come può funziona il timer?
Tramite dei registri di conigurazione scelgo il valore iniziale del contatore.

16
Posso farlo conteggiare anche ino a un certo valore massimo di 2 o posso mettere anche un
clock esterno. Conigurando i registri CCR0 o CCR1 riesco a settare il clock, il conteggio inale del
timer e il tipo di conteggio (posso contare anche in modalità up-down).
Il WatchDog è un timer che non sempre lo uso e che è settato attraverso dei registri di
conigurazione attraverso i quali scelgo il clock in ingresso e il valore in corrispondenza del quale il
WatchDog deve generare il segnale di reset al microcontrollore.
Se non volessi usare il WatchDog dovrei dirlo esplicitamente in quanto di default esso genera un
16
segnale di reset dopo aver contato da 0 a 2 , Dopo aver prodotto il segnale di reset, il program
counter viene azzerato. Per essere sicuro che il microcontrollore sia azzerato di solito mi aido a un
WatchDog esterno (vedi igura) in quanto quello interno potrebbe non funzionare per qualche
motivo.

Antonio Daidone, Francesca Mignemi, Antony Scivoletto, Giovanni Susinni, Nella Romano - © Vietata la vendita
Dispense di Sistemi elettronici A.A. 2015/2016

Nella igura vediamo un microcontrollore con un pin di reset e un WatchDog esterno che in genere
il suo funzionamento è garantito un quarzo esterno. Anche WatchDog ha un segnale di reset
collegato ad un pin del microcontrollore che permette di resettarlo ciclicamente per vedere se il
microcontrollore sta funzionando correttamente. All’interno del codice permetto al WatchDog di
essere resettato periodicamente: se per qualche motivo il segnale di reset non viene più generato,
vuol dire che il codice si è bloccato da qualche parte. A questo punto il WatchDog scatta e resetta
il microcontrollore permettendogli di ripartire da zero. Il microcontrollore potrebbe bloccarsi per
problemi dovuti al rumore di alimentazione, per disturbi a RF proventienti dall’esterno o per
radiazioni. Se dovesse arrivare una radiazione, una particella ad elevata energia dallo spazio
potrebbe settare un certo bit di un registro di conigurazione del sistema e questo potrebbe causare
il malfunzionamento del microcontrollore. Questo è il motivo per cui è utile resettare il
microcontrollore ciclicamente.
Dobbiamo quindi ricordare che WatchDog è sempre settato. Nella fase di sviluppo è consigliabile
disabilitarlo perché non sappiamo l’efettivo tempo di esecuzione del programma.

Tramite questa operazione stoppo il WatchDog in quanto sto scrivendo una certa maschera dentro
una certa locazione di memoria. Abbiamo dei nomi simbolici che coincidono con una parola binaria
a 16 bit. Sto mettendo in OR le due parole binarie scrivendole dentro il microcontrollore. È più
comodo lavorare con nomi simbolici perché scrivere 0A00b =00011001b + 100100100b sarebbe più
complicato. Nella locazione di memoria viene scritto il risultato di una operazione booleana di
somma. I 16 bit derivati da questa corrispondono ad una certa conigurazione del sistema.
Dov’è fatta l’associazione dei nomi simbolici con le maschere di bit?
Viene fatta all’interno di un ile .h fornito dal costruttore che sempliica molto il lavoro.
Vediamo nella slide che segue com’è fatto un registro di conigurazione del WatchDog.

Antonio Daidone, Francesca Mignemi, Antony Scivoletto, Giovanni Susinni, Nella Romano - © Vietata la vendita
Dispense di Sistemi elettronici A.A. 2015/2016

Abbiamo 8 bit all’interno del registro a ciascuno dei quali è associato un nome simbolico che ha un
determinato signiicato (ad es. il primo bit stoppa il WatchDog).

L'ADC in igura viene presentato attraverso uno schema a blocchi sempliicato costituito da un
convertitore analogico-digitale ad approssimazioni successive, da un sample-and-hold e da un

Antonio Daidone, Francesca Mignemi, Antony Scivoletto, Giovanni Susinni, Nella Romano - © Vietata la vendita
Dispense di Sistemi elettronici A.A. 2015/2016

multiplexer. Il multiplexer ha 10 canali di cui 8 esterni e 2 interni. Al multiplexer posso instradare


l'alimentazione intera, l'alimentazione scalata oppure l’uscita di tensione di un diodo polarizzato
inversamente. La tensione del diodo è proporzionale alla temperatura: poiché il diodo è sullo stesso
die del microcontrollore, sto ottenendo una misura di temperatura del microcontrollore.
Nel codice si deve conigurare il multiplexer, conigurare il S&H e l'ADC. Anziché usare la V CC,
potrei usare una tensione di riferimento esterna utilizzabile come tensione di riferimento dell’ADC.
Il risultato della conversione ADC viene direttamente memorizzata sulla RAM attraverso una
periferica che permette la scrittura sulla RAM bypassando il microprocessore.
Il convertitore agisce in maniera indipendente dal processore quindi potrei, ad esempio, far partire
la conversione mettendo il processore in modalità low-power. Anche spegnendo la CPU, la
conversione e la successiva scrittura del risultato della conversione sulla RAM viene efettuata.
Quando il processore viene riattivato, esso andrà a leggere il risultato: in questo modo evito che il
processore rimanga acceso durante il processo di conversione. Questo processo viene spesso usato
nei processori ad alta fascia e la metodologia è di tipo DMA.

Antonio Daidone, Francesca Mignemi, Antony Scivoletto, Giovanni Susinni, Nella Romano - © Vietata la vendita

Powered by TCPDF (www.tcpdf.org)


Dispense di Sistemi elettronici A.A. 2015/2016

Quattordicesima lezione ( 09-12-2015 )


Una periferica molto importante per il microcontrollore è la periferica seriale ovvero la cosiddetta
USCI (Universal Serial Communication Interface) e può essere usata per conigurare diversi
protocolli di comunicazione. I principali protocolli di comunicazione seriale sono: SPI, I2C,
UART.

Ci sono protocolli di comunicazione seriale che si occupano della comunicazione tra il


microcontrollore e altri circuiti integrati della stessa board e questo è il caso del protocollo SPI e
dell’I2C.
Questi si occupano di trasmettere dati tra il microcontrollore e i microcontrollori vicini della stessa
board oppure tra il microcontrollore e il convertitore AD. In generale si occupano di trasmettere
dati verso qualunque periferica che abbia bisogno di dialogare con l’host.
Il protocollo SPI fu inventato dalla Motorola (nasce come protocollo proprietario ma non è stato
mai brevettato) e consente la comunicazione tra un master (in genere è un microcontrollore) e uno
slave (per es. un convertitore A/D).
Per la comunicazione vengono usati tre segnali: un clock generato da un master e due linee usate
per la comunicazione dei dati di cui una va dal master allo slave e l’altra che va dallo slave al
master. Gli acronimi MOSI e MISO stanno rispettivamente per Master Output Slave Input e
Master Input Slave Output. Opzionalmente c’è un altro segnale di selezione ovvero uno Sleg (?)
select (di solito è un segnale negato) che serve per andare a dire allo slave che la comunicazione sta
per avvenire. In realtà, quando si usa solo uno slave, questo segnale è inutile. Questo protocollo

Antonio Daidone, Francesca Mignemi, Antony Scivoletto, Giovanni Susinni, Nella Romano - © Vietata la vendita
Dispense di Sistemi elettronici A.A. 2015/2016

però permette in generale di far comunicare il microcontrollore con più slave ed è qui che questo
segnale assume un'utilità in quanto esso permette solo ad uno slave alla volta di scrivere sul bus.
La velocità di comunicazione è molto bassa: non c’è uno standard però non si superano i 400 Kb/s.
Si ricordi, infatti, che sono protocolli che nascono per fare comunicare due circuiti integrati che
stanno sulla stessa board e che distano quindi qualche cm.

Anche il protocollo I2C nasce come protocollo proprietario e adesso è free. E’ molto usato a
diferenza del protocollo SPI in quanto consente di far coesistere più slaves sullo stesso bus: esistono
delle modalità che permettono addirittura di far coesistere più slaves e più master sullo stesso bus
oppure che permettono ad un master di essere anche slave. Tale protocollo funziona usando due
linee: una linea per i dati e un’altra per il clock. Il clock viene generato dal master e la linea dati
può essere scritta dal master e letta da uno slave e viceversa. Sia la linea dei dati che quella clock è
condivisa. Per il funzionamento è necessario che ogni slave abbia un indirizzo. La prima cosa che fa
il master è scrivere un comando sulla linea dati che corrisponde all’indirizzo dello slave e che deve
essere univoco. Lo slave può essere rappresentato un DAC, un ADC o un altro microcontrollore. Il
protocollo è half duplex ed esso non permette al master e allo slave di scrivere
contemporaneamente. Il protocollo SPI è full duplex (ci sono 2 linee dedicate): tramite delle
resistenze di pull-up, il master e lo slave sono collegati a V DD quando nessuno dei due deve
scrivere. Nel protocollo I2C la velocità di commutazione è standardizzata e non supera il Mb/s. Il
protocollo UART (quello che useremo nelle esercitazioni) usa due linee: una che funge da linea di
trasmissione ed un'altra da linea di ricezione. Oltre a queste due linee trovo la massa che è
condivisa tra tutti gli slaves che devono avere un riferimento comune in quanto sono tutti single
ended. In questo caso non c’è un clock quindi la comunicazione è di tipo asincrona: questo signiica
che un componente può funzionare a 1 MHz ed un altro a 1 GHz. La comunicazione può avvenire
nel momento in cui io stabilisco il cosiddetto baud rate ovvero il numero di caratteri al secondo che
possono transitare sulle linee Tx o Rx. E' quindi necessaria una conigurazione iniziale di Rx e Tx
che permetta lo scambio reciproco di dati. Esistono dei modi che mi permettono di calcolare il
baud rate ma, nella sua implementazione più semplice, questo calcolo non viene fatto e ciò non
permette a Rx e Tx di comunicare tra loro in quanto devo infatti sapere ogni quanto campionare
per leggere i bit in maniera opportuna (se per 1 ms ho un 1, devo sapere la durata di un bit in
quanto potrei leggere 11 oppure 111 ecc). E’ inoltre un protocollo full duplex e punto-punto quindi
possono colloquiare soltanto due oggetti sullo stesso bus: esistono delle versioni con indirizzo che
permettono di condividere lo stesso bus. E’ un protocollo estremamente difuso tra i
microcontrollori in quanto è il modo più semplice per poter accedere al microcontrollore, per
consentire al microcontrollore di estrapolare dei dati oppure per controllare periferiche di una certa
complessità (ad es. modem GSM, UMTS o anche il GPS che interagiscono con il microcontrollore
tramite un protocollo di comunicazione seriale).

Antonio Daidone, Francesca Mignemi, Antony Scivoletto, Giovanni Susinni, Nella Romano - © Vietata la vendita
Dispense di Sistemi elettronici A.A. 2015/2016

Il microcontrollore è a 16 bit per cui tutti i registri saranno a 16 bit. Le variabili che in genere
usate sono di tipo int con segno o senza (signed o unsigned). Se non mi serve usare una variabile a
16 bit posso usare la variabile char (8 bit): posso usare la variabile char per codiicare numeri. Per
implementare calcoli o formule matematiche è possibile usare variabili di tipo long (a 32 bits).
Quest’ultimo tipo di variabile è emulata all’interno del microcontrollore in quanto, per
immagazzinare questo tipo di variabile, devo occupare due celle di memoria. Per fare un’operazione
di somma con questo tipo di variabili è necessario usare più cicli di clock quindi, a meno che
l’applicazione non lo richieda, non conviene usare questo tipo di variabile.
La conigurazione di un microcontrollore avviene scrivendo dei dati all’interno di un registro di
conigurazione. Supponiamo che il registro sia a 4 bit e che all’inizio abbia il valore 0101: questo
contenuto corrisponde ad una certa conigurazione di una periferica. Quello che voglio fare è
cambiare il contenuto del registro senza alterare i bit che non voglio andare a scrivere. Se volessi
cambiare solo l’ultimo bit per ottenere 0100 ma andassi a scrivere R = 0, “0” verrebbe visto come
cifra decimale quindi verrebbe tradotto dal microcontrollore in binario come 0000. In tal caso
andrei a porre tutti i bit a zero ma il mio scopo era solo di cambiare l'ultimo bit. Per fare ciò potrei
andare a leggere il contenuto del registro e capire quale bit cambiare ma questa operazione
necessiterebbe di più istruzioni. Conviene invece usare un’istruzione che mi permetta di fare
l’operazione di assegnazione del bit in un solo colpo di clock. Suppongo di voler modiicare solo il
primo bit ossia considero il caso in cui voglia passare dalla conigurazione 0101 a quella 1101.

Antonio Daidone, Francesca Mignemi, Antony Scivoletto, Giovanni Susinni, Nella Romano - © Vietata la vendita
Dispense di Sistemi elettronici A.A. 2015/2016

Se scrivo R |= 1000, (“ | ” sta per OR e tale espressione coincide a scrivere l'espressione R = R |


1000) riuscirò a commutare con un unico colpo di clock solo il bit più signiicativo in quanto ho
applicato una maschera. Quindi tale OR equivale ad un’operazione di set di un bit.
Altro operatore è “ &= “ ed è l’operatore di AND bit a bit (la ̴ che compare nella slide
corrisponde ad un’operazione di negazione bit a bit). In questo modo applico l'operatore di AND
bit a bit con la negazione della maschera che mi individua il bit da modiicare ossia
R = R& ̴(0001) = R&(1110) dove ̴(0001) = 1110. In altre parole faccio l’AND tra il contenuto che
avevo in precedenza nel registro e la negazione della maschera che mi individua il bit.
̴ implementa un’operazione di NOT mi fa un reset del bit individuato dalla maschera. Ogni
maschera può essere scritta in esadecimale, in decimale o binario e individua il bit. Poichè spesso le
variabili sono a 16 bits, uso principalmente la codiica in esadecimale.
^= implementa l'operazione di XOR bit a bit che funge da toggle in quanto permette la
commutazione di tutti i bit.
Inine troviamo gli operatori di shift a destra (>>) o shift a sinistra (<<) che vengono usati per le
operazioni di divisione e moltiplicazione per potenze di 2.

Antonio Daidone, Francesca Mignemi, Antony Scivoletto, Giovanni Susinni, Nella Romano - © Vietata la vendita
Dispense di Sistemi elettronici A.A. 2015/2016

In questa slide troviamo le maschere corrispondenti a ciascun bit. Nella GPIO ogni porta ha 8 pin
(numerati ad esempio da 1.0 a 1.7) e a ciascuno dei quali corrisponde un bit all’interno del registro
di conigurazione. Un numero in esadecimale si indica con 0x all'inizio del codice (se volessi
esprimere il codice in binario, dovrei scriverei 0b prima del codice.)
Con l’istruzione include includo una libreria rappresentate da un ile di tipo .h .

ESEMPIO GPIO1

#include <msp430g2553.h>

int main(void)

WDTCTL = WDTPW + WDTHOLD; // Stop watchdog timer

P1DIR |= 0x01; // Set P1.0 to output direction

P1REN |= 0x08; // P1.3 pullup

while (1) // Test P1.3

if ((0x08 & P1IN)==0x08) P1OUT |= 0x01; // if P1.3 set, set P1.0

else P1OUT &= ~0x01; // else reset

Antonio Daidone, Francesca Mignemi, Antony Scivoletto, Giovanni Susinni, Nella Romano - © Vietata la vendita
Dispense di Sistemi elettronici A.A. 2015/2016

Tra le prime deine della libreria MSP430 troviamo quelle che mi permettono di associare un nome
simbolico alla maschera corrispondente a ciascun pin della porta (BIT0, BIT1…). Tale maschera
lpuò essere scritta esplicitamente (in esadecimale) oppure con una deine (scrivo ad esempio BIT0).
Ritorniamo al main. Prima del main potrei avere le variabili ausiliarie.
Nel primo programma, vorremmo che il LED1 rimanga acceso inchè il tasto non viene premuto:
quando il tasto viene premuto e per tutto il tempo durante il quale esso continua a restare
premuto, il LED deve rimanere spento.
Il led e il tasto sono collegati a dei pin di una porta.

In particolare, il LED rosso è collegato al pin P1.0 e viene alimentato dalla corrente erogata su tale
pin. Quando P1.0 è 0 il LED è spento viceversa il LED si accende. Devo quindi scrivere su questo
pin per far accendere o spegnere il led. Il tasto è collegato sul pin P1.3 ed è collegato direttamente
a massa: quando esso viene premuto, si crea un collegamento a massa creando uno zero per tale
ingresso. Devo prima efettuare un’operazione di lettura e poi un’operazione di scrittura.
Per fare tutto ciò, devo innanzitutto conigurare il microcontrollore scrivendo nei registri di
conigurazione (N.B: tutte le uscite sono conigurate come ingressi di default).
Per settare il pin P1.0 come uscita, devo andare a scrivere la sua maschera corrispondente nel
registro di conigurazione P1DIR (scrivo P1DIR |= 0x01): in questo modo setto P1.0 come uscita
mentre P1.3 è già conigurato come ingresso per cui non necessito di ulteriori istruzioni (in caso
avrei dovuto scrivere P1DIR &= ̴ BIT3).
Quando il tasto non viene premuto, il pin risulta loating per cui devo fare in modo di collegarlo a
VDD: in questo modo faccio in modo che si abbia una commutazione da 1 a 0 in seguito alla
pressione del tasto. Per fare ciò devo collegare una resistenza di pull-up usando l’istruzione
P1REN |= 0x08 dove 0x08 corrisponde alla maschera relativa al pin P1.3. A rigore manca una
linea di codice che mi permetta di far diventare questa resistenza una resistenza di pull-up o di
pull-down. La linea di codice mancante sarebbe P1OUT |= 0x08.
Dopo ho un ciclo while che è sempre presente: analizziamo le istruzioni all’interno del ciclo.
Innanzitutto devo veriicare che il tasto sia stato premuto o meno: intanto che il tasto non è

Antonio Daidone, Francesca Mignemi, Antony Scivoletto, Giovanni Susinni, Nella Romano - © Vietata la vendita
Dispense di Sistemi elettronici A.A. 2015/2016

premuto è il led rimane acceso.

if ((0x08 & P1IN)==0x08) P1OUT |= 0x01;

L’operazione tra parentesi non fa altro operare l’AND bit a bit tra il registro P1IN (che legge gli
ingressi) e la maschera corrispondente al bit che voglio andare a leggere. Se tale AND è uguale alla
stessa maschera del pin (se nella posizione P1.3 il bit è uguale a 1), sto leggendo un 1 ed il LED è
acceso (altrimenti è spento). Ovviamente le maschere numeriche possono essere sostituite con quelle
simboliche (posso usare BIT3 invece di 0x08).
Sulla destra del codice vedo anche le corrispettive linee di codice in Assembly.
Abbiamo la possibilità di andare a visualizzare il contenuto della memoria: in particolare nella
modalità debugger possiamo visualizzare il contenuto di ciascun registro. Per abilitare tale inestra
di visualizzazione bisogna andare su View Watch. Nel campo expression possiamo scrivere il nome
simbolico del registro oppure il nome della variabile da visualizzare: per scrivere dobbiamo fermare
l’esecuzione del codice cliccando su Break. La freccia verde indicherà dove si è fermato il codice.
Posso inoltre fermare l’esecuzione del codice in corrispondenza del veriicarsi di un certo evento
cliccando due volte a sinistra della linea di codice corrispondente.
Per fare funzionare il microcontrollore di solito si usa un altro programmatore che mi prende il
codice compilato e lo programma nella lash. Potrei anche usare l’ambiente di debug per
programmare il microcontrollore e per permettere il funzionamento.
Quando esco dalla modalità debugging, il microcontrollore continuerà ad eseguire il codice
nonostante io sia scollegato da esso: non posso piùscrivere sulla sua memoria.

Nell’esempio GPIO1_1 uso le deine al posto della maschera.

#include <msp430g2553.h>

#deine LED1 0x01 // P1.0 to red LED

#deine B1 0x08 // P1.3 to button

int main(void)

WDTCTL = WDTPW + WDTHOLD; // Stop watchdog timer

P1DIR |= LED1; // Set P1.0 to output direction

P1REN |= B1; // P1.3 pullup

while (1) // Test P1.3

Antonio Daidone, Francesca Mignemi, Antony Scivoletto, Giovanni Susinni, Nella Romano - © Vietata la vendita
Dispense di Sistemi elettronici A.A. 2015/2016

if ((B1 & P1IN)) P1OUT |= LED1; // if P1.3 set, set P1.0

else P1OUT &= ~LED1; // else reset

ESEMPIO GPIO1_2

#include <msp430g2553.h>

#deine LED1 0x01 // P1.0 to red LED

#deine B1 0x08 // P1.3 to button

int main(void)

WDTCTL = WDTPW + WDTHOLD; // Stop watchdog timer

P1DIR |= LED1; // Set P1.0 to output direction

P1REN |= B1; // P1.3 pullup

char state=1;

while (1) // Test P1.3

if (!(B1 & P1IN)) state=0;

if ((state==0)&&(B1 & P1IN)){

P1OUT ^= LED1; //detect 0 -> 1 transition on P1.3 and toggle P1.0

state=1;

Antonio Daidone, Francesca Mignemi, Antony Scivoletto, Giovanni Susinni, Nella Romano - © Vietata la vendita
Dispense di Sistemi elettronici A.A. 2015/2016

In questo vogliamo sempre accendere e spegnere il LED ma stavolta voglio che si accenda quando il
tasto viene rilasciato. Si ricordi che la variabile booleana non può essere implementata per devo
usare una variabile ausiliaria di tipo char che è la quella con meno bits.

If (!(B1 & P1IN)) state=0;


La precedente istruzione mi dice che, se l’operazione all’interno delle parentesi è 0 (ossia quando il
tasto è stato premuto), devo porre state = 0.
Per veriicare che il tasto venga rilasciato devo fare un’altra operazione. Devo prima capire se il
tasto è stato premuto e quindi capire se state == 0 e mettere di nuovo il risultato di questo
confronto in AND con il risultato di B1 & P1IN. Se il tasto è stato rilasciato e prima era stato
premuto, lo stato commuta.
Se premo il tasto velocemente possono veriicarsi delle commutazioni indesiderate. Normalmente la
linea connessa all’ingresso P1.3 è alta perché ho la resistenza di pull-up (va a 0 se premo il tasto)
Non appena il tasto viene rilasciato il led deve commutare. A volte però succede che sul fronte di
salita, anziché avere solo una commutazione, io abbia più commutazioni. Questo avviene perché la
forma d’onda in realtà è questa (vedi la seguente immagine)

Ho dei bounds del tasto: esso è un sistema elettromeccanico per cui non è detto che il contatto si
chiuda istantaneamente quando esso viene premuto. In realtà c’è un transitorio durante il quale il
segnale può avere variazioni molto rapide dovute alle vibrazioni del tasto. A causa di queste
vibrazioni, il microcontrollore può rivelare più fronti di salita e il led può commutare più volte. Se
tali variazioni sono eccessivamente ripide, il microcontrollore non sarà neanche in grado di rilevarli
in quanto le istruzioni vengono eseguite ogni N colpi di clock. Se ho variazioni più veloci, il
microcontrollore non è neanche in grado di osservarle ( se il clock è di circa 1 MHz, è come se il
segnale venisse campionato ad 1 MHz). Ci sono due modi per evitare questi bounds:

1. posso agire da un punto di vista hardware aggiungendo un circuito che mi permette di evitare
tali rimbalzi. Il circuito più semplice che posso aggiungere è un iltro RC (ci sono tecniche più
rainate che fanno uso di latch).

Antonio Daidone, Francesca Mignemi, Antony Scivoletto, Giovanni Susinni, Nella Romano - © Vietata la vendita
Dispense di Sistemi elettronici A.A. 2015/2016

2. posso inoltre adottare delle tecniche di tipo software. Prima di porre state == 0 dovrei usare dei
cicli for per capire quando lo stato si assesta. Dovrei quindi veriicare che il tasto sia stato premuto
ad esempio per 10 cicli di clock.

Finora abbiamo eseguito delle linee di codice in maniera ciclica: questa strategia spesso è
ineiciente perché, in questo modo, stiamo campionando il segnale P1.3 in maniera ciclica e stiamo
impegnando sempre il nostro processore a fare delle operazioni di “pointing” ovvero cioè di veriica
dello stato di un determinato ingresso. In questo modo vincoliamo il processore a fare queste
operazioni in maniera continua per cui questa è una strategia poco eiciente. Agire in questo modo
equivarrebbe a prendere la cornetta del citofono e a chiedere sempre se c'è qualcuno. Mantenendo
la precedente metafora vado ad installare un campanello in modo da rispondere solo quando il
campanello suona: questa operazione ha un equivalente software chiamato interrupt. Lo scatto
dell’interrupt, ferma l’esecuzione ciclica del microcontrollore in un determinato punto e permette
l'esecuzione di un codice alternativo che si chiama ISR (Interrupt Service Routine).
Successivamente riprendo l’esecuzione del codice deinito nel ciclo while. Ovviamente nel
microcontrollore dovrò implementare tutta una serie di hardware aggiuntivo che mi permetta di
andare a gestire quest’operazione non è banale: fortunatamente per il programmatore queste
operazioni sono quasi trasparenti. L’interrupt può essere usato per rilevare il veriicarsi di un evento
in corrispondenza di una determinata periferica del microcontrollore (ad es. la pressione di un
tasto, l'overlow di un timer ecc.). Innanzitutto devo speciicare al microcontrollore la periferica
nella quale voglio leggere l’interrupt. Successivamente devo speciicare le istruzioni da eseguire a
seguito dell’interrupt e riportare nuovamente il microcontrollore nelle condizioni in cui è sensibile a
rilevare un interrupt.

Antonio Daidone, Francesca Mignemi, Antony Scivoletto, Giovanni Susinni, Nella Romano - © Vietata la vendita
Dispense di Sistemi elettronici A.A. 2015/2016

Vi sono vari tipi di interrupt ma ne studieremo solo tre:


1. interrupt altamente prioritario. Il reset appartiene a questa categoria: a seguito della pressione
del tasto di reset, il microcontrollore resetta tutte le periferiche e il program counter torna
all'esecuzione della prima linea di codice.
2. interrupt mascherabili: possono essere disabilitati dal programmatore.
3. interrupt non mascherabili: sono interrupt simili al reset ma vengono generati internamente al
microcontrollore. Un esempio è l’interrupt generato dal watchdog timer.

Gli interrupt vengono immagazzinati all’interno di registri di conigurazione ben precisi


corrispondenti a ciascuna delle periferiche per le quali posso abilitare un interrupt.

Può succedere che dei segnali di interrupt arrivino contemporaneamente e per questo motivo è
presente una scala di priorità che è stabilità per ogni microcontrollore (le priorità del
microcontrollore che stiamo usando sono mostrate nela tabella in alto).

Antonio Daidone, Francesca Mignemi, Antony Scivoletto, Giovanni Susinni, Nella Romano - © Vietata la vendita
Dispense di Sistemi elettronici A.A. 2015/2016

La stringa di codice #pragma vector=TIMERA0_VECTOR associa un certo numero di istruzioni


da eseguire al veriicarsi di un certo interrupt (TIMERA0_VECTOR sarebbe il nome del vettore
corrispondente alla periferica sulla quale voglio leggere l’interrupt). Tale linea di codice quindi
associa l’ISR all’interrupt corrispondente.

ESEMPIO GPIO2

#include <msp430g2553.h>

int main(void)

WDTCTL = WDTPW + WDTHOLD; // Stop watchdog timer

P1DIR = 0x01; // P1.0 output

P1OUT = 0x08; // P1.3 set

P1REN |= 0x08; // P1.3 pullup

P1IE |= 0x08; // P1.3 interrupt enabled

P1IES |= 0x08; // P1.3 Hi/lo edge

P1IFG &= ~0x08; // P1.3 IFG cleared

_BIS_SR(GIE); // general interrupt enable

while(1){}

// Port 1 interrupt service routine

#pragma vector=PORT1_VECTOR

__interrupt void Port_1(void)

P1OUT ^= 0x01; // P1.0 = toggle

P1IFG &= ~0x08; // P1.3 IFG cleared

Antonio Daidone, Francesca Mignemi, Antony Scivoletto, Giovanni Susinni, Nella Romano - © Vietata la vendita
Dispense di Sistemi elettronici A.A. 2015/2016

Tale codice esegue la stessa operazione del codice precedente ma usa un interrupt in modo da
sfruttare al meglio le risorse hardware del microcontrollore. Le prime linee di codice sono le stesse.
Con l’istruzione P1IE |= 0x08 abilito l’interrupt sulla porta P1.3.
Con la porta P1IES isso il fronte: con l'istruzione P1IES |= 0x08 scelgo il fronte di salita (di
default rilevarei il fronte di discesa).
Inine vado a resettare il lag: il frago ritorna al valore pari a uno non appena viene rilevato un
interrupt (ossia quando rilevo il fronte di salita).
L’istruzione _BIS_SR(GIE) è una macro ovvero una serie di istruzioni Assembler deinite in uno
degli header ile che vengono caricati dal sistema di sviluppo di default. All’interno di questi header
ile (ad es. 430.h e intrinsic.h) si trovano delle macro Assembler che mi permettono di efettuare
delle conigurazioni particolari sul microcontrollore. Una di queste macro è proprio quella appena
scritta e che mi permette di attivare le GIE (General Interrupt Enable): vado a scrivere una
certa conigurazione in più registri di conigurazione del microcontrollore e questo mi permette di
abilitare gli interrupt mascherabili del microcontrollore. Abilito quindi le varie periferiche che gli
permettono di servire l’ISR.
Dopo faccio partire il ciclo while vuoto (non ha istruzioni tra {} ): potrei spegnere la CPU e
riattivarla solo quando premo il tasto. Quindi, rispetto al codice precedente, risparmio molta
energia.
Tramite il #pragma deinisco l’ISR. Nell’istruzione successiva c’è il nome dell’ISR che in tal caso è
Port_1 (ma potrei chiamarlo in qualsiasi altro modo): il nome dell’ISR viene deinito tramite
l’istruzione __interrupt void nome. Tutto ciò che inizia con il doppio underscore sono macro
interne all’ambiente di sviluppo (non sono scritte in C).
Quando scatta l’interrupt devo commutare il led rosso e resettare il lag dell’interrupt altrimenti
l’interrupt non scatterà più. Anche in questo caso ho il problema dei bounds: in questo caso però in
microcontrollore è più reattivo perché con il meccanismo dell’interrupt sono sensibile agli eventi
asincroni (che sono indipendenti dal clock) quindi, se usassi un clock a minor frequenza, la
reattività sarebbe la stessa. Da questo punto di vista il sistema è complessivamente più reattivo.

ESEMPIO GPIO3

#include <msp430g2553.h>

#deine red_LED BIT0

#deine grn_LED BIT6

#deine BTN BIT3

void delay(void);

Antonio Daidone, Francesca Mignemi, Antony Scivoletto, Giovanni Susinni, Nella Romano - © Vietata la vendita
Dispense di Sistemi elettronici A.A. 2015/2016

void main(void) {

unsigned int lash;

WDTCTL = WDTPW + WDTHOLD;

P1OUT = 0;

P1DIR |= red_LED + grn_LED; // LED pins to outputs, BTN is

// still an input by default.

for (;;) {

for (lash=0; lash<7; lash++) {

P1OUT |= red_LED; // red LED on

delay(); // call delay function

P1OUT &= ~red_LED; // red LED of

delay(); // delay again

while ((P1IN & BTN) == BTN); // wait for button press

for (lash=0; lash<7; lash++) {

P1OUT |= grn_LED; // green LED on

delay();

P1OUT &= ~grn_LED; // green LED of

delay();

while ((P1IN & BTN) == BTN); // wait for button press

} // main

void delay(void) {

Antonio Daidone, Francesca Mignemi, Antony Scivoletto, Giovanni Susinni, Nella Romano - © Vietata la vendita
Dispense di Sistemi elettronici A.A. 2015/2016

unsigned int count;

for (count=0; count<60000; count++);

} // delay

Questo codice mi permette di far lampeggiare sette volte il LED rosso. Quando il tasto viene
premuto, il LED verde lampeggerà per sette volte.
La novità di questo codice sta nella deinizione della funzione di ritardo. Tale ritardo può essere
implementato in vari modi ma il modo più semplice è quello di far eseguire al microcontrollore una
serie di cicli macchina a vuoto. Ho una funzione void che conta da 0 a 60000 e che mi permette di
creare un ritardo pari a 60000 volte il tempo di clock. Tale ritardo è utile per far lampeggiare il
LED. La frequenza del lampeggio dipende da questo ritardo: minore è il ritardo e maggiore sarà la
frequenza del lampeggio. Si ricordi che non si può andare contare un valore superiore a 65535
(questo limite è dovuto al tipo unsigned int). Questa frequenza è diicile da calcolare infatti non
conosco con precisione la frequenza di clock ed inoltre non so quanti cicli macchina vengono usati
per eseguire l’istruzione for (non è detto che sia un ciclo macchina). Se volessi far commutare il
LED ad una frequenza ben deinita devo cambiare strategia usando, ad esempio, un timer.

ESEMPIO TIMER

#include "msp430g2553.h"

void main(void)

WDTCTL = WDTPW + WDTHOLD; // Stop WDT

/*

BCSCTL1 = CALBC1_8MHZ; // Set DCO

DCOCTL = CALDCO_8MHZ;

*/

CCTL0 = CCIE; // CCR0 interrupt enabled

TACTL = TASSEL_2 + MC_1 + ID_3; // SMCLK/8, upmode

CCR0 = 10000; // SMCLK/8/10000=12.5 Hz; SMCLK default value is about


1MHz

Antonio Daidone, Francesca Mignemi, Antony Scivoletto, Giovanni Susinni, Nella Romano - © Vietata la vendita
Dispense di Sistemi elettronici A.A. 2015/2016

P1OUT &= 0x00; // Shut down everything

P1DIR |= BIT0; // P1.0 output

_BIS_SR(CPUOFF + GIE); // Enter LPM0 w/ interrupt

while(1) //Loop forever

{}

// Timer A0 interrupt service routine

#pragma vector=TIMER0_A0_VECTOR

__interrupt void Timer_A (void)

P1OUT ^= BIT0; // Toggle P1.0

Prima andiamo a vedere com’è fatto un timer (lo schema sottostante è stato preso dalla user
guide).

Antonio Daidone, Francesca Mignemi, Antony Scivoletto, Giovanni Susinni, Nella Romano - © Vietata la vendita
Dispense di Sistemi elettronici A.A. 2015/2016

I registri di conigurazione del timer sono tre:

1. TACTL (Timer_A Control Register): mi permette di scegliere opportunamente il clock da


utilizzare mediante un multiplexer. Vi sono quattro modi che mi permettono di scegliere il clock:

• ACLK: è il clock generato dal quarzo.

• SMCLK è il clock generato dallo stesso VCO della CPU.

• posso usare un clock esterno.

• TACLK: posso usare il clock generato da un altro oscillatore interno.

Dopo aver selezionato un clock posso usare un divisore di frequenza (block divider: 1/2/4/8) e posso
anche scegliere la modalità di conteggio. Posso contare in vari modi:
16
1. da 0 a 2 − 1 oppure da 2 ad un numero ben preciso che viene scritto nel registro TACCR0.

2. posso andare da 0 all’overlow.

3. posso contare ino al valore contenuto in TACCR0 e poi contare all’indietro.

Il tutto è riassunto nella tabella successiva

Ritorniamo al codice.
La prima linea (CCTL0 = CCIE) mi abilita l’interrupt perché io voglio efettuare un’operazione nel

Antonio Daidone, Francesca Mignemi, Antony Scivoletto, Giovanni Susinni, Nella Romano - © Vietata la vendita
Dispense di Sistemi elettronici A.A. 2015/2016

momento in cui il timer ha inito il suo conteggio. Il modo migliore per gestire il timer è proprio
l’interrupt. Abilito quindi l’interrupt (interno) del timer e faccio delle operazioni nel momento in
cui il timer arriva a far scattare l’interrupt.
Vado a selezionare il tipo di clock: qua è stato selezionato l’SMCLK tramite TASSEL_2. Dalla
precedente user guide si osserva che, ad ogni tipo di clock, è associato un codice: il TASSELx
sceglie il valore che devo avere in uscita dal multiplexer (TASSEL_2 sarebbe 10 in codice binario a
cui corrisponde un 2 in decimale).
Nell’espressione TACTL = TASSEL_2 + MC_1 + ID_3, oltre al TASSEL_2, compare MC; esso
rappresenta la modalità di conteggio (MC_1 sarebbe l’UP mode). Inine vediamo ID che
rappresenta il divisore: abbiamo quattro opzioni in questo caso ovvero la divisione per 1, per 2, per
4 o per 8. ID_3 sarebbe un divisore per 8 perché, partendo da zero, 8 è la terza opzione.
Con CCR0 = 10000 isso il conteggio a 10000.
La frequenza con la quale scatta l’interrupt del timer sarà SMCLK(1MHz)/8/10000 = 12.5 Hz: il
LED commuterà con questa frequenza che è abbastanza precisa.
P1OUT &= 0x00 mette a zero il LED.
P1DIR |= BIT0 conigura il pin P1.0 come uscita.
La macro _BIS_SR(CPUOFF + GIE) mi permette di abilitare l’interrupt globale (cosa che devo
fare sempre quando voglio ascoltare l’interrupt di una periferica).
Spengo la CPU con CPUOFF.
Entro in un ciclo while ininito.
Per deinire l’ISR ho sempre il codice #pragma vector = (nome vettore periferica).
L’istruzione __interrupt void Timer_A (void) mi permette di associare un nome arbitrario ad un
ISR. Tra parentesi grafe ho le istruzioni da eseguire dopo lo scatto dell’interrupt: in questo caso
devo commutare il LED quando scatta l’interrupt.
Come risultato avrò che il LED rosso lampeggerà ad una frequenza che stavolta conosco.
Per variare la frequenza con cui lampeggia il LED rosso posso variare il divisore, cambiare modalità
di conteggio oppure cambiare la frequenza di clock.
Come appena detto, possiamo agire sulla frequenza del clock. Questa frequenza però non è
conosciuta con accuratezza perchè il VCO è un oscillatore a rilassamento che ha una resistenza e un
condensatore che genera una tolleranza del 20%. Quando il produttore del microcontrollore testa
ciascun microcontrollore,immagazzina all’interno della lash delle costanti di calibrazione (speciiche
per ciascun microcontrollore) che mi permettono di settare la frequenza di clock ad un valore ben
deinito. Il VCO quindi ha due registri di conigurazione (BCSCTL1 e DCOCTL) che mi
permettono di scalare la frequenza di clock anche in maniera non intera:
Se all’interno di questi due registri scrivo le costanti di calibrazione (che sono nella lash del
microcontrollore) e se decommento le due istruzioni del codice, la frequenza di commutazione
aumenterà di 8 volte perché la frequenza del clock è aumentata di 8 volte e per questo il LED

Antonio Daidone, Francesca Mignemi, Antony Scivoletto, Giovanni Susinni, Nella Romano - © Vietata la vendita
Dispense di Sistemi elettronici A.A. 2015/2016

commuterà ad una velocità tale da sembrare sempre acceso. Se aumento ancora la frequenza, il
LED apparirà più luminoso.

Antonio Daidone, Francesca Mignemi, Antony Scivoletto, Giovanni Susinni, Nella Romano - © Vietata la vendita

Powered by TCPDF (www.tcpdf.org)


-RXVSHY^MSRIEPPMRKYEKKMSHIWGVMXXMZS:,(0
LEZIONE DEL 25-11-2015 ---- INTRODUZIONE AL VHDL

Oggi iniziamo la parte del corso relativa al VHDL.


Naturalmente l’obiettivo non è quello di diventare esperti di VHDL, ma soltanto quello di fornire delle
basi che ci consentano di essere autonomi nel momento in cui dobbiamo usare il VHDL all’interno
della progettazione di un sistema più complesso oppure, per esempio, per programmare un FPGA.
Cominciamo ad introdurre cos’è questo linguaggio, a cosa serve e dove lo si può utilizzare.
Innanzitutto il VHDL non è un linguaggio di programmazione, ma è un linguaggio per la descrizione
dell’hardware: infatti l’acronimo HDL sta per hardware description language.
Allora i linguaggi HDL sono diversi; tra i principali linguaggi troviamo:
Il VHDL;
Il Verilog.

Il VHDL è standardizzato ed è utilizzabile senza licenza, mentre il Verilog è stato acquistato dalla
Cadence, dunque è licenziato e perciò lo si può usare soltanto acquistando una licenza di Cadence.
Ne esistono anche di altri, ma quelli più diffusi sono questi due.
Come dice lo stesso nome, un linguaggio per la descrizione dell’hardware serve sostanzialmente a
rappresentare il funzionamento di un sistema elettronico digitale in maniera non ambigua. Storicamente
è stato sviluppato alla fine degli anni 70’ dal Ministero della Difesa statunitense, proprio con l’obiettivo
di mostrare ai fornitori esterni le specifiche di un certo sistema da realizzare in maniera non ambigua.
A tal proposito, se ad esempio il ministero della Difesa doveva andare a sviluppare un processore
oppure un particolare circuito, allora per definire le specifiche in maniera non ambigua, anziché
utilizzare il linguaggio del mondo comune (oppure uno schema a blocchi, oppure uno schematico),
utilizzando un linguaggio di descrizione dell’hardware poteva descrivere le specifiche del sistema in
maniera non ambigua.
Successivamente, col passare degli anni, il VHDL è stato utilizzato anche:
per la sintesi dei sistemi digitali: in particolare, a partire dagli anni 80’ sono stati messi a punto
dei software che erano in grado di tradurre questo linguaggio VHDL in uno schematico in
maniera automatica. E’ questo l’utilizzo che si fa del VHDL nell’approccio a Standard Cell;
ricordiamo, infatti, che per tale approccio si può utilizzare un linguaggio di descrizione
dell’hardware per definire il sistema.
Per la progettazione degli FPGA: io posso utilizzare il linguaggio VHDL per definire, ad
esempio, una macchina a stati , e poi il sintetizzatore sarà in grado di tradurre automaticamente
questa descrizione in una configurazione dell’FPGA.
Per il testing di un sistema anche non puramente digitale. Per fissare le idee, se volessi
progettare un convertitore D/A in Cadence, dovrei progettare ciascuno dei sottoblocchi a livello
di schematico. Chiaramente, questo non è un sistema puramente digitale, ma avrà anche una
parte analogica.
Per quanto riguarda il test di questo convertitore D/A, mi serve mettergli in ingresso delle
parole binarie; le parole binarie le potrei generare tramite un convertitore A/D , che dovrei
progettare io oppure dovrei direttamente andare a prelevare da una libreria. Questa cosa,
ovviamente, non è conveniente da fare, dunque si preferisce adottare un approccio misto: il
convertitore A/D, che è quello che mi serve soltanto per fare le simulazioni (ovvero per
caratterizzare in simulazione il convertitore D/A) lo descrivo tramite un linguaggio di
descrizione dell’hardware.
Per andare a descrivere delle equazioni integro-differenziali: sono i cosiddetti linguaggi di tipo
AMS (Analog Mixed Signal). Pertanto esiste una versione del VHDL, detta VHDL-A (dove A
sta per Analog), che mi permette di andare a definire un sistema utilizzando delle equazioni
integro-differenziali. Tuttavia il VHDL nasce soltanto per i circuiti digitali; pertanto ci
limiteremo a parlare di VHDL soltanto per sistemi appartenenti a quest’ambito.

Questo linguaggio di descrizione dell’hardware:


- È stato standardizzato nell’81’ dall’IEEE;
- Si sono poi susseguite diverse versioni; l’ultimo standard risale al 2002.

In generale, quando si usa il VHDL, non è detto che si faccia uso di tutti i possibili costrutti, ma in
genere si utilizzano quei costrutti che possono essere sintetizzati. Infatti, come vedremo, esistono dei
tipi di variabili, o anche delle funzioni, che non possono essere sintetizzate in hardware, perché sono
molto ad alto livello. Perciò, non è detto che si riescano ad utilizzare tutti i costrutti del VHDL.

Dove viene usato il VHDL?


Se andiamo a guardare il grafico (già visto parecchie volte nell’ambito di questo corso) che evidenzia i
vari livelli di astrazione di un sistema elettronico, il linguaggio VHDL si presta a descrivere un sistema
digitale sui seguenti livelli di astrazione:
Livello di gate;
Livello di modulo.

Esistono linguaggi che permettono di descrivere un sistema elettronico ancora più ad alto livello
(dunque a livello di sistema), come ad esempio il SYSTEM-C. Tuttavia, questi linguaggi sono molto
distanti dalla sintesi; in genere vengono utilizzati solo per fare test, oppure per standardizzare la
documentazione.
Il SYSTEM-C viene tipicamente usato da ingegneri informatici; invece, il VHDL può essere usato sia
da un ing. informatico che da un ing. elettronico; l’ambito di utilizzo è proprio il livello di gate e/o il
livello di modulo.
Spesso il livello di modulo lo si può trovare anche sotto l’acronimo di RTL (Register Transfer Logic):
è il livello all’interno del quale la propagazione di un segnale digitale nel sistema la posso descrivere
facendo riferimento ai registri, che sono interposti fra i vari strati di processore.

Com’è fatto un codice VHDL?


Tipicamente un codice VHDL è costituito da 3 elementi fondamentali:
1. ENTITY: mi dice come il sistema che voglio descrivere, si interfaccia col mondo esterno,
ovvero mi indica quali e di che tipo sono gli ingressi e le uscite del sistema;
2. ARCHITECTURE: mi descrive come questo sistema è fatto al suo interno.
3. PACKAGE (opzionali): questi sono le librerie. Ciò significa che, per la descrizione di un
sistema, io posso utilizzare una o più librerie già precompilate da qualcun altro.
Ad esempio, nel caso dell’FPGA, posso avere una serie di elementi che sono già stati
sintetizzati e testati dal costruttore dell’FPGA. Questi elementi mi vengono forniti sottoforma
di librerie e dunque li posso prendere così come sono.

Se, ad esempio, volessi descrivere un sommatore, allora:


La sua entity sarebbe costituita da due ingressi (le due parole binarie d’ingresso a n bit); avrei
poi un’uscita, più eventualmente il carry d’ingresso e il carry d’uscita;
La sua architecture la posso fare:
i. Attraverso uno schema a blocchi, interconnettendo opportunamente fra loro i diversi
full-adder (ottenendo per esempio un Ripple Carry Adder), oppure se ho architetture più
complesse, vado a disegnare proprio lo schema a blocchi;
ii. Tramite le equazioni booleane: se io descrivo il sistema tramite le equazioni booleane
della somma, di fatto sto facendo una descrizione equivalente.

Parleremo, allora, di questi tre elementi fondamentali di un codice VHDL ed, in particolare, ci
concentreremo sui primi due.
Se lo guardiamo come schema a blocchi, un sistema descritto in codice VHDL si presenta così:

- Posso avere il cosiddetto package, che è una libreria;


- Oltre all’entity e all’architecture (che sono fondamentali), ci potrebbe essere un altro elemento
costitutivo: la configuration. Quest’ultima non è fondamentale, dunque non è detto che ci sia.

Per definire l’ Entity di un sistema in VHDL, devo andare a specificare:


quali sono gli ingressi e quali sono le uscite;
di che tipo sono questi ingressi e queste uscite: potrebbero essere dei bit, ma potrei avere anche
casi in cui gli ingressi sono valori reali, oppure delle stringhe, ecc.
posso poi andare a definire dei parametri. Ad esempio, se volessi descrivere un sommatore,
posso dire che gli ingressi sono a K bit, dove K è appunto un parametro. Perciò non è
necessario che io specifichi il numero dei bit del sommatore, ma tale numero posso intenderlo
come un parametro che poi, eventualmente, vado a specificare successivamente.
Un altro parametro potrebbe essere il ritardo di propagazione di una porta logica.

Fintantoché non vado a specificare com’è fatto dentro


questo sistema in VHDL, per me questa è una scatola
nera di cui non conosco ancora il funzionamento.
Per capire come le uscite si relazionano con gli ingressi, dobbiamo andare a definire l’architettura di
questa entità: lo vedremo più avanti.
Di seguito è mostrato un primo esempio di codice VHDL:

Le parole evidenziate in blu sono le parole chiave del codice VHDL.


Per definire l’entità utilizzo il costrutto mostrato sopra, ovvero:
- utilizzo la parola chiave entity;
- metto il nome dell’entità;
- utilizzo la parola chiave is;
- poi uso la parola chiave port e, tra parentesi, vado a specificare quali sono gli ingressi e le
uscite, e qual è il tipo di ogni singolo ingresso/uscita;
- Alla fine utilizzo End test.

Nell’esempio mostrato in figura sopra, questa entità avrà n ingressi di tipo Standard Logic. Come
vedremo fra poco, Standard Logic non è un tipo predefinito all’interno del VHDL: infatti SL equivale
sostanzialmente ad una variabile booleana, anche se può assumere altri valori oltre quelli tradizionali di
0 e 1, per motivi che chiariremo fra poco.
Gli ingressi sono di tipo in, mentre le uscite le definisco di tipo out.
Qui sotto viene mostrata la definizione dell’entità completa, così come è stata definita nello standard.

In particolare notiamo il costrutto generic, che mi permette di introdurre dei parametri. Ad esempio,
con questo costrutto posso andare a definire il ritardo di propagazione all’interno del sistema che sto
andando a specificare; in generale, posso andare ad introdurre qualsiasi altro parametro.
Vediamo adesso di che tipo possono essere i segnali che mi vanno a specificare le entità.
A tal proposito, evidenziamo il fatto che gli ingressi e le uscite vengono chiamate porte.
Di che tipo possono essere le porte in VHDL?
I tipi principali sono:
- in: questo segnale può essere soltanto letto dall’entity;
- out: questo segnale può essere soltanto letto dal mondo esterno;
- buffer: è una uscita “intermedia” dell’entity, ovvero un segnale che proviene da una
elaborazione intermedia;
- inout: è un segnale che contemporaneamente sia un ingresso che un’uscita, dunque un segnale
che io posso forzare dall’esterno, ma lo posso anche leggere.

Nella figura sopra notiamo che, l’uscita del buffer tristate la vado ad utilizzare per forzare l’ingresso
del buffer successivo: pertanto quello sarà un segnale di tipo inout. Invece il segnale intermedio
all’uscita della prima porta NOR a sinistra sarà un segnale di tipo buffer.
Di che tipo può essere un segnale che vado a mettere o vado a leggere all’uscita di un entity?
Lo vediamo qui sotto.

Questi sono i tipi predefiniti che in VHDL. Quelli che vengono utilizzati maggiormente sono:
il tipo Bit: può assumere valori ‘1’ o ‘0’;
il tipo Bit_vector: è un array di bit;
il tipo Boolean: può assumere valori “true” o “false”, dunque è del tutto equivalente al tipo bit,
con la differenza che, se lo definisco di tipo boolean, allora non lo posso andare a sintetizzare.
Ho poi dei tipi più complicati, distanti dalla sintesi, che utilizzo in casi particolari:
- Integer;
- Real;
- Physical: è come l’integer, però ha in più l’unità di misura;
- Record (definito dall’utente);
- Character;
- String: array di caratteri.

Quest’ultimi tipi, per ovvi motivi, non sono sintetizzabili. Tuttavia potrebbe tornare utile usarli quando
non sono interessato alla sintesi, dunque quando ad esempio devo fare solo le simulazioni e voglio
vedere che, dal punto di vista funzionale appunto, tutto evolva correttamente.
Concentriamo la nostra attenzione sul tipo bit, e supponiamo che io voglia descrivere un sistema di
questo tipo:
Ho un sistema digitale fatto in questo modo: collegati alla stessa linea di segnale ci sono due o più
inverter. Questa situazione me la ritrovo, ad esempio, quando ho un bus di dati.
Se io utilizzo dei buffer tristate, e dunque faccio in modo che soltanto un driver piloti la linea, allora il
segnale X che vado a leggere sulla linea è ben definito. Tuttavia potrebbe capitare che questi due
inverter vadano a scrivere contemporaneamente in uscita sul nodo X.
Allora, se ad esempio accade che il primo inverter mi forza uno 0 logico, mentre il secondo mi forza un
1, allora quale sarà il valore che andrò a leggere in X? Non lo so, perché col tipo bit non riesco a
descrivere questa situazione. Qui entra in gioco il tipo Standard Logic, che mi permette di affrontare in
maniera non ambigua condizioni di questo tipo.
Nella realtà, se questi due driver avessero esattamente la stessa driving capability, allora l’uscita
sarebbe indeterminata, perché in X avrei praticamente un valore di tensione pari a ⁄ , dunque il
valore logico non sarebbe né 0 né 1.
Altro caso di indeterminazione è quello in cui ho un buffer tristate disabilitato (figura sotto a destra).
In questo caso l’uscita è un’alta impedenza.
Allora il tipo SL (abbreviazione di Standard Logic) mi permette di
andare a descrivere situazioni di questo tipo, in cui ad esempio su
un nodo ho alta impedenza.
Allora, il tipo SL, pur non essendo predefinito all’interno del
VHDL, viene sempre utilizzato, perché mi consente di andare a
trattare in maniera non ambigua casi che mi ritrovo molto spesso
nella realtà.
Il tipo SL può assumere valori:

Discutiamo gli stati H ed L, che corrispondono rispettivamente a “weak 1” e “weak 0”. Questi due stati
mi permettono di andare a descrivere una situazione del genere: immaginiamo di avere un driver, con
una sua resistenza di pull-up (figura sotto a lato). In
queste condizioni, indipendentemente da qual è lo stato
del buffer tristate, l’uscita sarà di tipo H (alta): in pratica è
un “weak 1”, poiché l’uscita è sempre vincolata a VDD
dalla resistenza di pull-up.
Allora il tipo SL è più completo rispetto al tipo bit e conviene utilizzarlo più spesso, proprio per evitare
di trovarmi in situazioni che si incontrano nella realtà e che non potrei andare a trattare usando il
VHDL.
Andiamo adesso a vedere una serie di definizioni di entity partendo dal considerare circuiti che già
conosciamo. In particolare andiamo a guardare il full-adder:

N.B: I commenti in un codice VHDL si inseriscono anteponendo due trattini ad una riga.

- Nel full-adder ho 3 ingressi che sono di tipo bit (anche se abbiamo già capito che, in generale,
conviene farli di tipo SL);
- Ho poi le uscite, anch’esse di tipo bit.

Posso anche andare a definire strutture logiche più semplici. Un esempio è il Flip-Flop FF di tipo D.
Inizio già a capire una cosa: per scrivere un codice VHDL, devo avere davanti uno schema a blocchi.
In pratica devo partire da una visione schematica del mio sistema, per poi tradurlo in codice VHDL.
I tool più evoluti permettono di generare automaticamente un codice VHDL a partire dallo schematico.
Allora, se io ho la possibilità di disegnare il sistema come schema a blocchi e ognuno dei sottoblocchi
che compongono il sistema è contenuto all’interno di una libreria, praticamente non devo scrivere
neanche una riga di codice, proprio perché la traduzione in VHDL di quello schema è praticamente
immediata e semplice da fare.
Supponiamo adesso che, anziché definire un solo FF di tipo D, io voglia andare a definire un array di
FF di tipo D; posso andare a fare questa cosa introducendo un parametro tramite il costrutto Generic.
In particolare inserisco un parametro, che rappresenta il numero di bit di questo array di FF.

Introduciamo anche il parametro width, ovvero la lunghezza di questo array.


Poi avremo:
Due ingressi, che sono il clock e il reset negato, entrambi di tipo bit;
Gli ingressi D e le uscite Q saranno, invece, dei vettori di bit, grandi quanto il valore del
parametro width. Andrò a specificare il valore di questo parametro quando poi andrò ad
istanziare questa entità: più avanti vedremo come si fa.

Un array di bit lo posso fare:


a) Dicendo al VHDL che il bit meno significativo (LSB) sta a sinistra e quello più significativo
(MSB) sta a destra; questo lo faccio scrivendo bit_vector (1 to “dimensione dell’array”);
b) Se volessi fare al contrario, ovvero scrivere il bit più significativo a sinistra, e quello meno
significativo a destra, allora devo scrivere bit_vector (“dimensione dell’array downto 0).
L’esempio mostrato qui sotto si riferisce al caso di una ALU a 32 bit:

Se considero una ALU a 32 bit generica, abbiamo:


- Due ingressi, A e B, che sono delle parole binarie a 32 bit;
- L’uscita C;
- Op, che mi dice quale istruzione deve andare ad eseguire l’ALU;
- Infine ho delle uscite ausiliarie, N e Z, che possono essere o il carry, oppure un bit di stato che
mi dice se eventualmente la somma è andata in overflow.

Finora abbiamo visto come si definiscono le entità: è la cosa più semplice da fare quando descrivo un
sistema in codice VHDL.
Adesso passiamo alla parte più importante, che è la descrizione dell’architettura. In pratica,
l’architettura mi permette di andare a definire in che modo le uscite vengono generate a partire dagli
ingressi.
Partiamo da questo semplice sistema, costituito da due porte AND e una porta OR.
Nella figura a lato abbiamo una logica
combinatoria molto semplice, che implementa
una certa funzione booleana.
La prima cosa che posso fare è quella di tradurre
questo schema a blocchi in codice VHDL. Per
fare questo devo descrivere in VHDL ciascuno
dei sottoblocchi; alla fine metto tutto insieme e,
cos’, riesco a generare l’uscita a partire dagli
ingressi.
Ora, per assegnare un valore ad un segnale, devo
usare l’operatore <=
Ora, in un linguaggio di programmazione tradizionale, queste due istruzioni vengono eseguite in
maniera sequenziale: in pratica, prima vado ad assegnare ad X il risultato dell’operazione di A AND B,
e poi al successivo colpo di clock, vado ad assegnare a Y il risultato di C AND D. Invece, in VHDL,
queste operazioni di assegnazione vengono eseguite tutte in maniera concorrente, ovvero in parallelo,
proprio perché il VHDL cerca di emulare ciò che avviene nella realtà: nel momento in cui metto questi
segnali, nella realtà questi segnali evolvono in parallelo. Allora queste linee di codice che mi
descrivono l’evoluzione del segnale devono essere eseguite in parallelo.
Pertanto l’operatore “<=” dice al simulatore di fare evolvere i segnali in maniera concorrente.
Capiamo bene che, quando faccio tale operazione su un calcolatore che fa uso di un processore, allora
tale operazione di tipo parallelo la posso solo emulare, poiché il calcolatore esegue le istruzioni in
maniera sequenziale. Allora c’è un meccanismo, chiamato del Δ (delta) – delay, che mi consente di
emulare questa evoluzione parallela di un sistema hardware, anche per un calcolatore che evolve in
maniera sequenziale.

Qui sopra troviamo la definizione della porta AND a due ingressi.


Com’è fatta la definizione dell’architettura? Per definire l’architettura devo usare:
La parola chiave architecture, seguita dal nome dell’architettura;
La parola chiave of, seguita dal nome dell’entity (in questo esempio tale nome è and2; N.B: il
nome dell’entità nell’architettura deve essere lo stesso di quello utilizzato quando si è fatta la
dichiarazione dell’entity), seguita poi dalla parola chiave is;
Poi ho una parte di architettura in cui posso andare a definire delle variabili ausiliarie (qualora
mi dovessero servire);
Infine ho il corpo dell’architettura, ovvero l’equazione booleana.

Sotto è rappresentato l’estratto della sintassi (preso dallo Standard) di una architettura, in cui vediamo
la stessa cosa vista poco fa.
All’interno dell’architettura quale approcci posso seguire per descrivere il sistema?
1. Approccio strutturale: ho una schema a blocchi e lo traduco in codice VHDL. Questo
approccio è molto utile quando parto da una descrizione di alto livello del sistema;
2. Approccio Behavioral: consiste nell’andare a descrivere il sistema in maniera
comportamentale, ovvero vado semplicemente a dire cosa fa il sistema. Come si vedrà, in tale
approccio posso utilizzare anche dei costrutti di tipo sequenziale (così come faccio in un
linguaggio di programmazione tradizionale) per andare a dire come viene generata l’uscita: è
dunque l’approccio più vicino ad un linguaggio comune, ovvero è quello più ad alto livello.
3. Approccio Dataflow: vado a specificare come i segnali evolvono all’interno dello schematico.
Per utilizzare tale approccio, mi serve partire da una descrizione a schema a blocchi, e poi vado
semplicemente a dire come i segnali vengono prodotti in corrispondenza dei valori intermedi, e
come evolve il segnale in uscita, a partire dall’evoluzione dei segnali intermedi.

Vediamo un esempio di descrizione, partendo dal Latch SR.

Per il Latch SR posso:


- Usare un approccio di tipo strutturale, ovvero descrivendo come sono connessi i due inverter
fra di loro;
- Usare la tavola della verità;
- Scrivere le equazioni booleane in codice VHDL, prescindendo dalla struttura interna del
sistema.
Iniziamo dalla descrizione di tipo Strutturale.

Per scrivere questo codice, devo innanzitutto andare a definire l’entity. Nell’ architecture uso la parola
chiave component, che mi permette di istanziare un componente che devo aver definito da qualche altra
parte, per esempio in una libreria. Pertanto si presuppone che l’entity e l’architecture della porta NOR
siano già state fatte da qualche altra parte.
Uso poi la parola chiave port, dove ricopio la definizione degli ingressi e delle uscite del NOR gate.
Una volta definito un componente, posso utilizzare più istanze di quel componente; in questo caso me
ne servono due, che chiamiamo n1 e n2.
Poi uso il costrutto port map, che mi permette di associare a ciascun segnale presente nella definizione
dell’entità un segnale presente nello schematico.

Questo esaurisce la descrizione strutturale.


A questo punto il simulatore è in grado di fare evolvere correttamente le uscite a partire dagli ingressi,
perché è come se stessi facendo una simulazione a livello schematico del sistema.
Nei tool più evoluti, questo codice viene generato automaticamente.
Questo stesso sistema lo posso descrivere in maniera behavioral.

Posso sostanzialmente definire l’evoluzione delle uscite a partire dagli ingressi, utilizzando il
linguaggio comune, ovvero utilizzando una serie di istruzioni sequenziali. Per fare questo bisogna
utilizzare un particolare costrutto, che è il cosiddetto process: questo consiste in una serie di istruzioni
che vengono eseguite in maniera sequenziale dal simulatore. In pratica funziona così: all’interno del
campo dell’architettura vado ad utilizzare il processo; opzionalmente posso assegnare un nome al
processo. Il processo ha fra parentesi la cosiddetta Sensitivity List : questa è la lista di quei segnali che,
nel caso in cui subiscano una variazione, provocano l’attivazione del processo. In altre parole, il
processo non viene eseguito sempre in maniera ciclica, ma viene eseguito soltanto quando c’è una
variazione di uno di quei segnali specificati fra parentesi, ovvero quando varia uno dei segnali facente
parte della Sensitivity List: in tal caso il simulatore sa che deve eseguire tutte le istruzioni contenute fra
le parole chiave process e end process. In tal caso, all’interno del process, mi comporto così come mi
comporterei se programmassi in C, ovvero (figura sopra):
Definisco una variabile ausiliaria, che è la variabile di test;
Assegno a test il risultato dell’OR fra s ed r ;

Mettendomi poi davanti la tavola della verità, allora devo far sì che:
Se test = ‘1’, allora assegno a q<=s , altrimenti assegno a nq<=r ;
faccio end if;
infine finisco il processo e finisco anche la descrizione dell’architettura.
In pratica, allora, sto guardando la tavola della verità e sto dicendo come devono evolvere le uscite
sulla base degli ingressi.
Allora ci sono molti casi in cui mi conviene prescindere dall’hardware del sistema e utilizzare una
descrizione molto più vicina al linguaggio comune.

Passiamo, infine, alla descrizione di tipo Dataflow.

La descrizione di tipo Dataflow è quel tipo di descrizione che mi dice come evolvono i segnali
utilizzando le equazioni booleane. Anche questa è una descrizione molto utilizzata, ed è anche molto
efficace. Infatti, come si vede dal codice mostrato sopra, nel campo dell’architettura ho soltanto quelle
due equazioni booleane:

Allora, come abbiamo visto, lo stesso sistema lo posso descrivere mediante uno di questi tre approcci.
Di fatto, fra i tre non c’è n’è uno migliore dell’altro, ma dipende da cosa voglio fare e, soprattutto, se
sono orientato alla sintesi o meno: certamente se sono orientato alla sintesi, gli approcci migliori sono
quello Dataflow e strutturale.
Finora, allora, abbiamo capito che alla stessa entità possono corrispondere diverse architetture. In altre
parole significa che ad un sistema che ha i medesimi ingressi e le medesime uscite posso associare più
architetture.
Nel momento in cui decido di
associare più architetture, al
simulatore devo dire di
utilizzarne soltanto una. Questa
cosa la faccio utilizzando la
cosiddetta Configuration.
Questa cosa è poco usata,
perché in genere io di un
sistema dò soltanto una
descrizione. Tuttavia, se ci
dovessero essere più descrizioni,
utilizzando la configuration io
posso associare una particolare
architettura all’entità (la quale,
ovviamente, è unica, perché il
sistema ha degli ingressi e delle uscite ben determinate).
Qui di seguito è mostrato (per completezza, ma non lo useremo praticamente mai!) l’estratto della
dichiarazione di una configuration:

Diamo adesso qualche cenno sui Package.


Un package è sostanzialmente una libreria, in cui io posso:
andare a definire dei componenti;
andare ad utilizzare componenti definiti da qualcun altro;
andare a definire delle funzioni;
definire dei tipi di dato particolari.

I package sono del tutto equivalenti alle librerie che vado ad utilizzare in un linguaggio di
programmazione comune.
L’utilizzo dei package è uno dei punti di forza del VHDL, in quanto mi consente di andare a riusare il
codice che è stato sviluppato da qualcun altro. Addirittura potrei comprare il codice scritto da qualcun
altro, per poi andare a descrivere sistemi più complessi, quali ad esempio una memoria, una parte del
processore, ecc. Perciò la riusabilità del codice VHDL è analoga alla riusabilità di un codice in un
linguaggio classico ad alto livello.
I package possono essere:
- STANDARD: sono inclusi nel VHDL di base. Per esempio, il package dove sono definiti i tipi
di dato è già compreso all’interno di qualsiasi versione di base del VHDL;
- IEEE: non sono presenti nel VHDL di default, ma bisogna istanziarli esplicitamente. Tra questi
i più importanti sono:
i. TEXTIO: utilizzato quando devo effettuare il processamento di caratteri o stringhe di
caratteri;
ii. STD_LOGIC_1164: all’interno di questo package è presente il tipo di dato SL. Poi ci
sono delle funzioni matematiche, che mi consentono di effettuare delle operazioni che,
di default, non sono presenti nel VHDL.

Allora, se sono orientato alla sintesi, difficilmente parto dalla descrizione della singola porta logica.
Infatti queste ce le ho già presenti nella libreria; le vado poi ad istanziare per poterle opportunamente
utilizzare. Tutto ciò accelera e migliora il processo, poiché tutti i componenti presenti all’interno della
libreria sono già stati testati da qualcun altro e sono sicuro che sono stati descritti in maniera corretta.
Vediamo finalmente com’è fatto un codice VHDL:
Ho l’entità e l’architettura, che sono le parti fondamentali;
Poi ho i package, che mi servono ad istanziare delle entità già predefinite e descritte altrove;

Nell’entity abbiamo:
Le porte;
I parametri.

L’architettura può essere di tipo:


Dataflow: posso andare a definire le equazioni booleane, dunque ho a che fare con istruzioni
concorrenti;
Behavioral: posso utilizzare istruzioni concorrenti, ma anche sequenziali tramite il costrutto
process;
Strutturale: quest’ultima è quella più ovvia, ma spesso anche quella più complicata.

Vediamo adesso come viene generato un codice VHDL a partire dallo schema rappresentato sotto:

Questo è uno shift-register a 8 bit che, ad ogni colpo di clock, mi propone in uscita un certo bit.
Quello che faccio in questo sistema è confrontare tutta la parola binaria (che cambia ad ogni colpo di
clock) dello shift-register, con una parola binaria in ingresso di riferimento.
Lo shift-register ha:
- Un ingresso, chiamato Init[8], che serve ad inizializzare lo shift-register;
- Un segnale di Load, che mi dice quando farlo funzionare;
- Il clock Clk;
- Un segnale di reset Rst.

Iniziamo con la descrizione dei sottocomponenti (in quanto, come vedremo a breve, la descrizione di
tutto il sistema sarà di tipo strutturale). Allora quello che dobbiamo andare a fare è descrivere ciascuno
dei due sottocomponenti.
Iniziamo dal comparatore e decidiamo di utilizzare una descrizione di tipo Dataflow per l’architettura.

Sopra è riportato il codice.


Vediamo per la prima volta quali sono le istruzioni che servono per istanziare un package all’interno
del codice. Si utilizzano le due righe di codice:

Scrivendo queste linee di codice, stiamo dicendo al simulatore di utilizzare tutto quello che è contenuto
all’interno del package.
Una volta istanziati i package, la prima cosa che devo andare a fare è definire l’entità di questo sistema.
Ho due ingressi, che sono due vettori a 8 bit di tipo SL, e l’uscita che è un bit di tipo SL.
La descrizione dell’architettura tramite un approccio di tipo Dataflow è estremamente semplice, tant’è
che la posso fare con una sola linea di codice. In particolare dico di assegnare all’uscita EQ il valore ‘1’
quando A=B, altrimenti assegno ‘0’. Notare che, quando assegno un certo valore all’uscita, questo
valore lo devo mettere fra apici.
Come sarà fatto dentro questo oggetto? Al suo interno questo oggetto potrebbe essere molto
complicato, e noi abbiamo visto che l’operazione di comparazione si può fare anche con un sommatore,
oppure con una batteria di comparatori a un bit. Tuttavia a noi questa cosa non ci interessa, perché se
poi saremo interessati alla sintesi, allora sarà il simulatore a tradurre questa equazione booleana in uno
schematico (se faccio la sintesi in termini di circuito integrato) o in una configurazione interna
dell’FPGA (se la faccio, appunto, programmando l’FPGA).

Adesso passiamo allo shift-register.


Questo sistema è particolare, in quanto quello che viene fatto è ruotare la parola che vado ad inserire
all’inizio; in pratica, ad ogni colpo di clock, io vado a spostare il bit meno significativo dalla parte
opposta. Questa operazione viene effettuata fintantoché la parola binaria viene poi comparata a quella
di riferimento, ottenendo alla fine un ‘1’.

Come faccio a definire uno shift-register che funziona in


questo modo? In questo caso uso una descrizione
dell’architettura di tipo Behavioral.

Nell’entity abbiamo:
- Il clock, il reset e il Load, che sono
segnali a un bit di tipo SL;
- Il dato in ingresso, che è un vettore a 8
bit;
- L’uscita Q, anch’essa un vettore a 8
bit.

Volendo fare una descrizione dell’architettura tutta


con approccio Dataflow, in realtà non riuscirei a
pensare ad una equazione booleana che mi dia
l’uscita a partire dall’ingresso, anzi probabilmente
non esiste proprio. Pertanto non posso fare a meno di
utilizzare una descrizione di tipo Behavioral.
- Definisco un processo, la cui Sensitivity List
ha due segnali, il reset e il clock;
- Definisco una variabile ausiliaria, Qreg, utile
per effettuare la rotazione.
- Nel process le istruzioni vengono eseguite in
maniera sequenziale.
Innanzitutto, se Reset=1, allora assegno a Qreg la parola costituita da tutti 0;
- poi, se ho il fronte di salita del clock ( Clk=’1’ e Clk’event), allora
i. se sono in fase di Load assegno a Qreg il dato iniziale che devo scrivere sul registro
ii. altrimenti devo ruotare la parola binaria: prendo tutti i bit della parola binaria, tranne
quello di posto (0), e questi bit li concateno man mano con quello meno significativo.
Allora l’operatore & fa l’operazione di concatenazione fra stringhe o bit.
- Alla fine assegno questa variabile ausiliaria Qreg al segnale d’uscita Q, e finisco il processo.

Una volta descritti i due elementi costituenti, dobbiamo adesso andare a descrivere l’intero sistema: lo
faccio tramite un approccio di tipo strutturale.
Partiamo dall’entity.

Com’è definita l’architettura?


La prima cosa che devo fare è istanziare i due componenti che costituiscono il sistema, ovvero il
comparatore e lo shift-register.
Una volta descritti questi componenti tramite il costrutto component, all’interno del corpo
dell’architettura devo utilizzare il costrutto port map.

Prima di fare questo, nella parte dichiarativa dell’architettura definisco un segnale ausiliario, che
chiamo Q, che è un segnale interno all’architettura, che non vedo dall’esterno, ma che mi serve andare
a definire per andare a descrivere correttamente questo sistema, ovvero per connettere l’uscita del
primo blocco all’ingresso del secondo. In seguito, nella port map, faccio l’assegnazione in maniera
esplicita, tramite l’operatore “=>”. Questo operatore mi permette di dimenticarmi dell’ordine in cui ho
definito i segnali nell’entity del comparatore e dello shift-register. In altre parole, con => sono sicuro di
fare le corrispondenze in maniera corretta, indipendentemente dall’ordine in cui avrei dovuto fare le
corrispondenze dei segnali nel caso in cui avessi scritto i segnali fra parantesi: ricordiamo, infatti, che
la sintassi per la port map vista in precedenza era

Faccio la stessa cosa nel rotate. Questa cosa è molto utile quando il comparatore ha molti ingressi e
molte uscite, in quanto evito di fare errori.
La descrizione del sistema in VHDL è conclusa. Dove devono stare tutti questi codici che ho scritto?
Possono stare tutti sullo stesso file, oppure posso usare un file per ciascun elemento.

Vediamo adesso una cosa importante di quando si scrive in VHDL: come faccio a verificare che il
codice VHDL è corretto?
Ora, durante la stesura di un codice VHDL, potrei commettere due tipi di errori:
1. Errore di sintassi: potrei sbagliare a scrivere una parola chiave, oppure dimenticarmi di
mettere un punto e virgola, e così via. Problemi di questo tipo li rilevo quasi subito in fase di
compilazione, in quanto il software ha un compilatore che permette di capire se ci sono errori
nella scrittura del codice;
2. Errore di descrizione del sistema: mi potrei dimenticare, ad esempio, di scrivere un
equazione booleana, oppure anziché scrivere AND sbadatamente scrivo OR, e così via.
In questo caso andrei a commettere degli errori di cui mi posso accorgere soltanto se metto
degli ingressi e vado a vedere se le uscite vengono generate correttamente. Allora devo fare
una verifica funzionale del sistema.

Se, ad esempio, ho descritto un sommatore, allora per verificare che la descrizione è stata fatta in
maniera corretta, devo mettere due parole in ingresso e verificare che le uscite siano corrette per tutte le
possibili combinazioni degli ingressi.
Questa procedura, in generale, potrebbe essere estremamente lunga e delicata. Non a caso ci sono degli
ingegneri che si occupano solo di questo, ovvero sviluppare il cosiddetto Test bench del sistema
digitale. Questi ingegneri hanno il compito di capire se il sistema funziona, utilizzando la combinazione
degli ingressi minima, così da effettuare la verifica nel più breve tempo possibile.
Per capire se il sistema funziona correttamente, devo mettere in ingresso degli stimoli, e devo verificare
che in uscita ottengo un segnale coerente a quello che mi aspettavo allorché ho definito le specifiche
del sistema stesso. Pertanto, per fare la verifica funzionale del sistema, si fa il Test Bench.
In pratica chiamo UUT (Unity
Under Test) il sistema da
verificare, e gli metto dei segnali
fittizi in ingresso, così da
verificare che le uscite siano
corrette.
Nel caso più semplice, tale
verifica è visiva, perché ho delle
forme d’onda; nei casi più
complessi, la verifica sarà di tipo
automatico o semiautomatico.
Nel caso del sistema
precedentemente descritto,
vediamo che il Test bench è a tutti gli effetti un altro sistema in VHDL, che ha un solo componente:
l’UUT. Pertanto devo scrivere un altro pezzo di codice, in cui uso un approccio strutturale, nel quale
ho:
- Un entity, che non ha né ingressi né uscite:
- All’interno dell’architettura definisco dei segnali ausiliari/fittizi che metto all’ingresso e leggo
all’uscita.
Nell’architettura ho:
i. La descrizione di tutta l’entità, costituita dal rotate e dal comparatore;
ii. Nella parte dichiarativa, vado a definire una serie di variabili ausiliarie d’ingresso e
d’uscita, che posso chiamare allo stesso modo di come li ho chiamati nell’architettura,
oppure posso dare nomi diversi.

iii. Nel campo dell’architettura, definisco più processi. Questi processi non hanno
Sensitivity list, dunque vengono eseguiti sempre.

iv. Definisco una variabile ausiliaria, clktemp, che mi permette di fare la commutazione del
clock;
v. All’interno del processo, vado ad invertire clktemp ( che avevamo inizializzato a 0
tramite la parola chiave variable);
vi. Al clock assegno il valore di clktemp;
vii. Aspetto 50 ns e poi ripeto questa operazione. Pertanto ogni 50 ns ho la commutazione
del clock: allora il periodo di clock è di 100 ns.

Da questo stralcio di codice vediamo che il modo che ho per definire un ritardo in VHDL è la parola
chiave wait for.
Quello appena visto è il primo processo che mi consente di fare variare il clock; praticamente tale
processo l’avrò sempre laddove mi ritrovo ad avere un segnale di clock.
Una volta fatto questo, definisco un altro processo per tutti gli altri segnali (figura che segue).

In questo processo faccio evolvere i segnali in maniera opportuna. Ad esempio:


prima resetto il sistema;
Aspetto 100 ns;
Poi faccio evolvere la macchina a stati e aspetto 6 cicli di clock.

Di fatto, se ad esempio considerassi un semplice sommatore a 4 bit, allora io dovrei considerare un


numero di combinazioni degli ingressi pari a 24 * 24 =16 * 16 =256 combinazioni degli ingressi,
proprio per verificare che per tutte le possibili combinazioni l’uscita evolve correttamente. Allora,
anche per un semplice sommatore a 4 bit, il numero di combinazioni è piuttosto elevato. In un sistema
complesso, come un processore, questa operazione di definizione del test bench è estremamente
delicata, in quanto, non solo devo capire qual è la combinazione minima di segnali che mi permette di
beccare in maniera esaustiva eventuali difetti che ho nel codice, ma questa operazione la devo
effettuare eseguendo il numero minore possibile di passi di simulazione. Infatti, anche se il test bench
lo riesco a scrivere con poche righe di codice , utilizzando dei costrutti più elaborati e compatti (per
esempio un ciclo for), di fatto la simulazione potrebbe poi durare anche migliaia di cicli di clock, e
questa chiaramente è una cosa che non ci si può permettere nella fase di test funzionale. Pertanto
esistono delle persone che si occupano di sviluppare i test bench per sistemi digitali scritti in VHDL da
terzi. Inoltre ci sono alla base delle teorie matematiche che permettono di capire qual è il sottoinsieme
minimo degli ingressi per cui faccio evolvere il sistema e rilevo eventuali malfunzionamenti.
Vediamo adesso qualche altro sistema descritto in VHDL. Prendiamo come riferimento un Flip Flop
di tipo D Edge Triggered.

L’architettura la definisco
utilizzando un approccio di
tipo behavioral.
Definisco un processo, che
ha come Sensitivity List il
clock: dunque ogni qualvolta
ho una variazione sul clock,
attivo questa serie di
istruzioni.
Quando sono all’interno del
processo, devo
semplicemente andare a fare
in modo che, quando sono
sul fronte di salita, devo
assegnare D all’uscita.

Visto che ho fatto una descrizione di tipo behavioral, come verrà tradotto quel simbolo a livello
implementativo? Non lo so, mi devo fidare del sintetizzatore. In realtà il sintetizzatore mi fornisce uno
schematico, in cui poi io posso andare a controllare qual è il risultato di questa sintesi: non è detto che
quest’ultimo sia univoco, perché lo posso fare in tanti modi, dunque non è detto che sia ottimizzato dal
punto di vista delle prestazioni; se voglio essere sicuro delle prestazioni, mi conviene andare ad
utilizzare dei FF già precompilati in dei package del VHDL. Nell’ FPGA non ho transistori fisici, ma
ho soltanto CLB (blocchi logici configurabili), shift-register, ecc. , dunque in questo contesto il discorso
dell’ottimizzazione delle prestazioni non c’è e conseguentemente la fase di sintesi è molto più semplice
da fare: è proprio per questo che si utilizza il VHDL per programmare l’FPGA.
Vediamo adesso di capire com’è fatto il test bench del FF (codice alla pagina seguente).
Devo andare a definire una entity fittizia, senza ingressi né uscite, e una architettura di tipo strutturale,
in cui vado a definire tutti i segnali ausiliari.
Tra begin e and devo andare ad istanziare il componente, che è l’UUT, e poi i vari processi:
- Un processo per il clock: assegno inizialmente al clock il valore 0, aspetto 10 ns, assegno 1, e
poi aspetto altri 10 ns; dunque è un clock di 20 ns di periodo.
- Ho poi un altro processo per il segnale d’ingresso. In particolare faccio in modo che d vari in
maniera scorrelata rispetto al clock, sempre però rispettando i tempi di setup e di hold, ovvero
garantendo che il dato sia stabile un tempo di setup prima rispetto al fronte di salita del clock.

Devo dunque verificare che l’uscita commuta soltanto in corrispondenza del fronte di salita del
clock. Per capire che ciò accade, la prima verifica che posso fare è andare a vedere che le forme
d’onda stanno evolvendo in maniera corretta.
Quando poi ho delle specifiche sul ritardo di
propagazione, le cose si complicano un po’, perché
devo capire se sto rispettando anche le specifiche sul
ritardo. Si può dimostrare, in particolar modo, che il
ritardo di propagazione del Ripple Carry Adder, nel
caso peggiore, è pari a quello che mi aspetto dai conti
carta e penna che abbiamo svolto in una delle prime
lezioni del corso.

Powered by TCPDF (www.tcpdf.org)


LEZIONE DEL 30/11/2015----2° PARTE DEL VHDL

In questa lezione vengono visti alcuni esempi di codici in VHDL attraverso l’ausilio del software
Sonata. Successivamente sono stati integrati alcuni concetti teorici di rilevante importanza, riportati nel
seguito.

Innanzitutto vediamo come vengono definiti i ritardi in VHDL.


Esistono tre principali modelli di ritardo in VHDL:
Modello di ritardo inerziale;
Modello di ritardo di tipo transport;
Modello di ritardo del Delta Delay.

Abbiamo già visto che, se io voglio andare a simulare questa condizione, vado ad utilizzare il costrutto
after. Tale costrutto, in pratica, mi permette di andare ad introdurre in un sistema digitale l’evoluzione
dinamica di una porta logica reale.
Infatti, se all’ingresso di un gate (modellizzabile, in
prima approssimazione, come un sistema RC a
singolo polo) ci metto una forma d’onda digitale, in
uscita osserverò qualcosa. Ma se il segnale digitale in
ingresso è troppo rapido, ovvero ha un energia troppo
piccola per sollecitare adeguatamente la mia porta
logica, succede che in uscita non osserverò niente o,
per meglio dire, osservo un segnale (filtrato dal filtro
passa-basso) che non riesce a superare la soglia logica
e quindi non può essere correttamente ricostruito dal
circuito che sta a valle.
Allora, il costrutto after mi permette di andare a
codificare questa condizione: se ho segnali che hanno
una durata più piccola del ritardo di propagazione,
allora li perderò.
Questo significa che, se in una riga di codice VHDL scrivo “[…] after 8 ns” , vuol dire che tutti i
segnali che hanno durata minore o uguale di 8 ns li perdo, mentre tutti quelli che hanno una durata
maggiore me li ritroverò in uscita dopo 8 ns (vedi grafico sopra in riferimento all’Out1).
Questo tipo di ritardo è detto inerziale, ed è quello predefinito: pertanto la parola chiave inertial si può
omettere (e di fatto si omette sempre), proprio perché tale tipo di ritardo viene considerato per default
dal VHDL.
In realtà, in VHDL posso usare un modello un po’ più accurato di questo ritardo di propagazione, che
mi permette di filtrare impulsi la cui durata è definita a prescindere dal ritardo di propagazione. Per
esempio, se io ho un ritardo di propagazione di 5 ns, potrei decidere di filtrare quei segnali che hanno
una durata non minore o uguale di 5 ns (i quali verrebbero filtrati per default a causa del ritardo
inerziale), ma minore o uguale di soli 2 ns. Pertanto, tramite questa modalità, filtro dei segnali di durata
qualsiasi rispetto al ritardo di propagazione. La sintassi in VHDL per fare quanto appena detto è:

Un altro tipo di ritardo che posso definire in VHDL è il cosiddetto ritardo di tipo Transport. Questo mi
permette di andare a modellizzare una guida d’onda (o una linea di trasmissione) lossless. Infatti una
linea lossless, opportunamente terminata, mi dà in uscita lo stesso segnale che ho all’ingresso, dopo un
tempo di volo. Allora il modello di ritardo di tipo Transport mi permette di andare a modellizzare cosa
succede ad un sistema descritto in VHDL nel momento in cui si va fuori dal chip, ovvero dal circuito
integrato; al contrario, il modello di ritardo di tipo inerziale mi consentiva di descrivere il ritardo che,
per default, ho all’interno dell’IC, dove i modelli di ritardo sono quelli di un circuito RC.
In questo caso, il segnale viene riportato in uscita indipendentemente da quanto dura l’impulso. Quindi,
con il modello di tipo transport io riporto in uscita segnali di X nanosecondi, indipendentemente da
quanto durano; in altre parole, mi riporto in uscita lo stesso segnale che ho all’ingresso, esattamente
come succede in una linea di trasmissione lossless.
Vediamo di capire questa cosa con un esempio pratico.

Supponiamo di avere un sistema con due ingressi, a e b; poi abbiamo due uscite, s1 e s2, generate
rispettivamente mediante una porta XOR e una porta AND. Alla fine questo sistema è quello che si
chiama half-adder, in quale non ha il carry d’ingresso: ho due ingressi e due uscite; quest’ultime sono
la somma e il carry d’uscita. Dunque internamente l’half-adder è fatto in questo modo, anche se
nell’ambito di questo corso non l’abbiamo visto.
Ora, per l’analisi dell’evoluzione delle uscite, io potrei usare un modello ibrido: infatti, potrei da una
parte utilizzare un modello di ritardo di tipo inerziale per la generazione dei segnali intermedi s1 e s2 e,
contestualmente, un modello di tipo Transport per generare i segnali finali di somma e carry.
Le linee di codice utilizzate per fare questo sono mostrate qui sotto.

In pratica, prendo i segnali s1 e s2 e li vado a riportare in uscita dopo 4 ns, indipendentemente da


quanto durano. Se vado a guardare il grafico della forma d’onda, qui mostrata:

Vedo che inizialmente ho a=1 e b=0 ; dopo un ritardo di propagazione pari a 2 ns genero s1. Adesso la
sum non è altro che s1 traslato di 4 ns.
Posso pensare, quindi, di usare un modello ibrido. In particolare:
Con il modello di tipo inerziale, genero s1 e s2 all’interno del IC;
Con il modello di tipo Transport esco dal circuito integrato, dunque dovendo leggere il segnale
fuori dal chip lo modellizzo con tale tipologia di ritardo.
Esiste un terzo modello di ritardo, che in realtà non è essenziale conoscere: il ritardo Delta Delay.
Questo modello di ritardo mi serve per andare a simulare le assegnazione di tipo concorrente.
Ricordiamo che il VHDL funziona nella maniera seguente: se ho diverse istruzioni del tipo <=, io devo
leggere l’uscita, considerando che queste istruzioni non vengono eseguite in maniera sequenziale, ma
vengono eseguite in parallelo.
Allora il problema che si pone è quello di poter simulare questa condizione, dal momento in cui io so
che di fatto il processore evolve in maniera sequenziale. Per fare questo devo introdurre un ritardo Δ,
che è un ritardo arbitrario molto piccolo deciso dal simulatore stesso, che mi permette di andare a
simulare correttamente cosa succede nella realtà quando questi segnali evolvono in parallelo.
Ad esempio, se consideriamo queste tre istruzioni:

Nella realtà queste tre istruzioni vengono eseguite in maniera sequenziale. Infatti, se inizialmente a=1,
b=0 e c=0, succede che, solo dopo un colpo di clock del simulatore, viene eseguita la seconda
istruzione. Perciò, se a era uguale
a 1, dopo un certo ritardo di
ampiezza Δ, deve diventare 0 (
che poi è il valore che deve
assumere b): questo ritardo Δ
coincide, di fatto, con la durata di
un colpo di clock del simulatore.
Stessa cosa per c: questo deve
diventare 1, ma di fatto questa
istruzione viene eseguita dopo due
colpi di clock del simulatore.
Perciò, alla fine, il risultato che
otteniamo è quello mostrato nel
grafico della figura a fianco.
Questo risultato sarebbe corretto
se il sistema evolvesse in maniera
sequenziale; invece, il sistema deve evolvere in parallelo. Allora , se consideriamo:
Un primo intervallo, di ampiezza Δ, impiegato affinché a si porti al suo valore corretto;
Un secondo intervallo a partire da quando a vale 1, sempre di ampiezza Δ, affinché b si porti a
0;
Un terzo intervallo Δ per avere il valore corretto di c
allora capiamo che di fatto in uscita visualizzeremo il risultato corretto solamente dopo che saranno
state eseguite tutte e tre le istruzioni di assegnazione, ovvero dopo un ritardo di 3Δ.
Questo è il meccanismo che viene usato internamente al simulatore del VHDL, per emulare il
comportamento parallelo all’interno di un sistema sequenziale.

Questo è un fatto trasparente per l’utente: infatti l’utente non si accorge che c’è questo Δ delay.
Chiaramente il tempo di simulazione è del tutto scorrelato rispetto al tempo effettivo di evoluzione del
sistema. Infatti la situazione che vado a visualizzare alla fine sulla forma d’onda è di tipo statico; di
fatto, però, questa è scorrelata dal tempo reale che poi nel sistema mi è servito per andare a generare
tutte quelle forme d’onda.

Parliamo adesso di un altro elemento importante presente nel linguaggio VHDL: gli attributi.
Gli attributi sono delle proprietà che hanno i segnali. Infatti, i segnali sono, a tutti gli effetti, dei vettori
di valori binari (0,1) sui quali posso effettuare e includere delle condizioni.
Tra i principali attributi troviamo:

1. Attributo EVENT: si specifica come nome_del_segnale’ event. Questa istruzione mi ritorna un


valore booleano pari a 1, nel momento in cui c’è stata una variazione del segnale, dovuta o ad
una assegnazione oppure ad una evoluzione interna del sistema;
2. Attributo ACTIVE: mi restituisce 1 nel momento in cui ho assegnato un valore ad un segnale.
Significa che, se io assegno un valore ad un segnale (indipendentemente da qual’ era il valore
che quel segnale aveva in precedenza), allora il risultato di active sarà uguale a 1. In altre
parole, con questo attributo riesco a rilevare quando viene forzato un segnale;
3. Attributo LAST EVENT: mi restituisce il tempo in corrispondenza del quale c’è stata l’ultima
variazione del segnale;
4. Attributo LAST ACTIVE: mi restituisce il tempo in corrispondenza del quale c’è stata l’ultima
assegnazione su quel segnale;

5. Attributo LAST VALUE: mi restituisce il valore precedente del segnale. Questo attributo
funziona anche sui segnali vettoriali, ovvero costituiti da più bit;
6. Attributo STABLE: mi restituisce un valore ‘vero’ se il segnale non è variato nei precedenti X
nanosecondi, dove tale numero di nanosecondi viene indicato fra parentesi e lo posso stabilire
io.

Negli esempi che seguono

Troviamo:
- Come rilevare il fronte di salita e di discesa;
- Come valutare il periodo di un segnale (nell’ipotesi che sia periodico);
- La stability.

Powered by TCPDF (www.tcpdf.org)

Potrebbero piacerti anche