Corso di microprogrammazione: i Sistemi di Calcolo Embedded

Codice microprogrammazione

Sempre più pratico, sempre più "nella" materia, il nostro corso di microprogrammazione si sta addentrando sempre di più nella trattazione delle architetture, delle loro peculiarità e dei motivi per i quali esse risultano interessanti. Oggi iniziamo una lunga parte del corso in cui parliamo dei sistemi di calcolo Embedded affrontando l'argomento sia dal punto di vista concettuale sia pratico. Parleremo meglio, tra le altre cose, anche del Controller IRQ, un elemento assolutamente fondamentale. Siete pronti?

Diciamo che per poter iniziare bene ed in maniera completa a trattare questo argomento non possiamo che cominciare analizzando l'architettura di base. Nella scorsa puntata abbiamo visto il primo micro prendendo in considerazione alcuni dei dispositivi che lo compongono. L'esempio, naturalmente, non poteva che essere in qualche modo "limitato" ma di certo era rappresentativo. Quell'esempio, adesso, ci sarà di grande aiuto per l'analisi che andremo a fare nei prossimi paragrafi in cui verranno affrontate ed ampliate alcune tematiche già trattate ed altre che, viceversa, non incluse nel microcontrollore, ma comunque fondamentali.

La CPU

Lo abbiamo già detto, ed ora lo riprendiamo con maggiore attenzione, che questo elemento in realtà è un elemento complesso, composto da diverse "sezioni". Tre, in particolare: Control Logic, Program Counter, Alu.
Andiamo con ordine ed iniziamo dal primo, ovvero Control Logic.
Nel nostro esempio abbiamo fatto delle assunzioni che, se vogliamo, "forzando" un po' la mano:

  • il microcontrollore ha una lunghezza di parola fissata. Ciò implica l'utilizzo di un'architettura che molti conoscono con il suo acronimo, ovvero RISC (che sta per Reduced Instruction Set Cpu). Questa deve essere sempre confrontata con la sua "duale", la CISC (Complex Instruction Set Cpu). I primi, come il nome suggerisce, utilizzano un set di istruzioni ridotto con il vantaggio di garantire una maggiore velocità di esecuzione delle istruzioni. Ma non è tutto, dal momento che l'hardware risulta anche meno complesso.
  • l’istruzione ha una sezione di opcode di 5 bit, il che implica un instruction set di massimo 32 istruzioni.
  • l’istruzione ha una sezione dato/indirizzo di 8 bit, che rappresenta una limitazione sia per l'accesso ai dati che sono più grandi di 8 bit (che deve essere fatto per passi successivi) sia per l'indirizzamento dell'area di memoria che non può superare il valore FF.

Sia la Control Logic sia il sequencer sono stati dimensionati, come visto, al solo scopo didattico illustrativo ma è importante tenere sempre a mente che anche il più elementare e meno performante di microcontrollori attualmente disponibili in commercio non si allontana poi tanto da questa descrizione. Potete tranquillamente utilizzare questa analisi per approcciarvi a qualunque macchina.

Per quanto riguarda il Program Counter (PC), invece, esso è stato dimensionato in maniera tale da poter gestire la quantità di memoria disponibile a bordo del micro. In tutti i dispositivi commerciali non è difficile trovare almeno 16 bit per la gestione di un PC. Questo permette un indirizzamento di area di programma di 64 kB e, nei casi in cui questo sia necessario, si può arrivare anche a 20 oppure 24 bit.

L'ALU, invece, ci interessa perché nei dispositivi commercialmente disponibili è fondamentale fornire almeno la moltiplicazione di dati profondi 8 bit ciascuno, e quindi gestire un risultato da 16. Inoltre sono indispensabili operazioni di "rotazione" sia a sinistra sia a destra. La rotazione è un'operazione molto simile allo shift con l'unica differenza che l'ultimo bit viene riposizionato in testa. In pratica è una specie di ring counter e funziona da Buffer. Risulta, altresì, fondamentale offrire la possibilità di gestire operazioni di MAC (Multipliy and Accumulate), tipiche dei dispositivi DSP. Infine, sottrazione e comparazione possono essere utili per generare informazioni sul segno (cioè se il dato sia maggiore oppure minore).

Non abbiamo finito, dal momento che restano da analizzare Status Register e Stack. Il primo è un registro che porta e gestisce lo stato di diversi flag. Esso dipende moltissimo dall'architettura del sistema e nel caso che abbiamo visto questi erano soltanto relativi a Zero e Carry. Parlando dello Stack, invece, la sua area è separata dalle aree di dati il che può essere vero in molti microcontrollori di tipo RISC. Una grande quantità di dispositivi prevedono un'area di memoria dedicata per la gestione dello stack, il che garantisce un costo minore e la possibilità di avere aree anche più estese.

Naturalmente ci stiamo occupando di flussi di dati, di bit che si spostano tra registri, aree di memoria, unità di elaborazione e così via dicendo. Perché tutto questo funzioni al meglio è necessario il bus controller. Nel caso del nostro uC didattico, esso si occupa di gestire, in maniera semplice, l’accesso in lettura o scrittura dei pochi dispositivi presenti. In generale possiamo dire che il microcontrollore:

  • può accedere a dispositivi interni ed esterni;
  • può accedere a dispositivi di diversa dimensione (8, 16, 32 bits);
  • può accedere ai dispositivi di sola lettura oppure di sola scrittura oppure entrambe;
  • può effettuare la mappatura della memoria in aree con diverse funzionalità;
  • deve abilitare ed arbitrare l'accesso alle risorse (CPU, DMA e così via dicendo);
  • può regolare la velocità di accesso alle periferiche più lente;
  • può gestire DRAM.

L'ultima risulta particolarmente interessante perché, come sappiamo, le memorie dinamiche hanno necessità di cicli di refresh per il mantenimento dei dati e quindi anche la temporizzazione di questo genere di operazioni si dimostra essere un fattore cruciale.

Con le immagini che seguiranno dimostriamo alcuni dei diagrammi relativi ai cicli di lettura e scrittura tratti dalla documentazione di un microcontrollore 16 bit. Vengono riportati nell'ordine un accesso ad 8 bit, uno a 16 access even, uno a 16 access odd, uno a 16 iword ed infine uno con wait state.

 

 

 

nell'esempio che segue, invece, viene riportata la mappatura di alcuni dispositivi di memoria che sono stati interfacciati al microcontrollore mediante l'utilizzo di un bus esterno. Quello che è importante sottolineare qui è la presenza dei segnali di chip select che vengono generati dallo stesso microcontrollore.

IRQ Controller

Molto spesso, nella pratica, si pone il problema di dover gestire una periferica che può generare, in qualunque momento, un determinato evento a proprio piacimento. Per gestire questo genere di situazioni si può, per esempio, controllare periodicamente lo stato della periferica e, nel caso in cui sia necessario, si può intervenire direttamente su di essa. Questo metodo è detto Polling.
Un altro modo è quello di eseguire le normali operazioni fintanto che la periferica stessa non genera una richiesta di intervento. Questo metodo è detto interrupt.
Per facilitare le cose, facciamo un esempio: supponiamo di essere in casa e di voler ordinare una stanza. Nello stesso tempo ci aspettiamo di ricevere una rivista in abbonamento e una chiamata telefonica da una persona cara.

Approccio con Polling: Mentre metto a posto la stanza, ogni due minuti vado prima alla porta per vedere se c’è fuori il postino con la rivista, quindi chiudo la porta, vado al telefono, alzo la cornetta e controllo che la persona cara sia in linea. Finiti questi controlli, riprendo ad ordinare la stanza.

Approccio con Interrupt: Mentre metto a posto la stanza, squilla il telefono, interrompo il lavoro nella stanza, vado al telefono alzo la cornetta e rispondo. Finita la telefonata, torno a lavorare. Quando il postino suona alla porta, interrompo il lavoro, vado alla porta, la apro e ritiro la rivista consegnata dal postino. Quindi torno a lavorare.

Questo esempio potrebbe sembrare banale ma apre la strada ad una serie di considerazioni, tipo: nel nostro esempio quale dei due approcci è più conveniente?
Si intuisce facilmente che il polling produce uno spreco di risorse, dal momento che ci distrae spesso dalla nostra attività primaria e la maggior parte delle volte senza che sia necessario.
Ho comunque la necessità di effettuare il polling con una frequenza tale che io riesca a garantire che il postino non stia davanti alla porta quando io non controllo.
Ed analoghe considerazioni vanno fatte per il telefono.
Ma non è finita perché nel caso di Interrupt, devo sempre riprendere il processo iniziale dal punto in cui lo avevo interrotto.

Questo esempio ci permette di spiegare un concetto: più "sorgenti" possono generare una IRQ (Interrupt Request), nel nostro caso la porta e il telefono.
Per ciascuna fonte di IRQ devo compiere operazioni differenti (ISR Interrupt Service Routine).
Ma non è ancora finita perché potrebbe anche succedere che sia il telefono sia la porta richiedano la mia attenzione contemporaneamente; che cosa devo fare in questo caso? Quale devo privilegiare?
Non è, inoltre, detto che sia il telefono sia la porta richiedano la mia attenzione in maniera significativa, nel senso che non è detto che sia il postino e nessuno mi garantisce che la telefonata non sia pubblicitaria.

Pertanto ne discendono le seguenti necessità:

  • devo differenziare gli IRQ sulla base del dispositivo che lo ha generato. Questo è semplice dal momento che ogni dispositivo genera un proprio IRV (Interrupt Request Vector);
  • ogni IRQ necessita di una ISR, ovvero una sezione di codice dedicata al servizio della richiesta di interrupt. La regola generale vuole che tale sezione di codice faccia lo stretto indispensabile per tornare al programma interrotto;
  • la necessità di ricordare il punto esatto in cui ho interrotto l'esecuzione del programma principale al quale devo fare ritorno dopo l’esecuzione della ISR. In questo caso lo stack è sicuramente la migliore soluzione possibile (!);
  • devo stabilire le priorità di interrupt, dal momento che devo sapere, nel caso di più IRQ contemporanei, quale sarà servita prima ma anche, nel caso sia in esecuzione una ISR, un IRQ a priorità più elevata che può interromperla.

Risulta evidente, poi, che il microcontrollore deve poter abilitare e disabilitare la generazione di ciascun IRQ.

Quello che vi abbiamo mostrato in figura rappresenta un esempio applicabile al microcontrollore didattico quando ci sia una porta di ingresso parallela che genera un IRQ nel caso di cambiamento di stato sui bit 0-3.
Notare i segnali:

  • RIV, Read Interrupt Vector, segnale usato per caricare nel PC l’indirizzo della ISR. Resetta automaticamente l’IRQ;
  • IRQ, il segnale di richiesta interrupt;
  • GIE, General Interrupt Enable, abilitazione generale dell’interrupt;
  • PIE, Port Interrupt Enable, abilitazione per la periferica alla generazione di interrupt.

Torniamo adesso al micro 30F48 e vediamone qualcosa di più. Lo schema a blocchi indicato riporta i segnali esterni ed interni al micro che possono generare richieste di interrupt. Fate attenzione al fatto che esiste una logica di controllo delle priorità, nonchè la generazione dell’indirizzo della ISR tramite il Vector Number.

Nel diagramma che riportiamo qui di seguito, invece, viene mostrata la logica di gestione di IRQ generati esternamente al micro. Il blocco “Edge/level sense circuit” e il bit IRQnSC determinano se l’IRQ viene generato da un fronte di discesa o dal livello basso sul pin di input. Il segnale IRQnE abilita/disabilita la generazione dell’IRQn. La differenza con il circuito del micro didattico è sicuramente il modo in cui avviene la cancellazione dell’IRQ. Nel 30F48 bisogna accedere un apposito registro.

DMA Controller

Nell'ultima sezione di questo articolo vediamo come gestire uno dei problemi tipici dei sistemi digitali, ovvero il trasferimento di dati da una locazione di memoria per fare questo prendiamo in considerazione, per esempio, una versione turbo del nostro microcontrollore che:

  • è dotata di due ulteriori registri IX, IY;
  • ha un Opcode a 6 bit, espandendo cosi’ da 32 a 64 le possibili istruzioni;
  • permette il trasferimento dai registri in questione da e per l’accumulatore: LOAD A,Reg, LOAD Reg,A;
  • permette il trasferimento dai registri in questione da e per la memoria: LOAD @M,Reg, LOAD Reg,@M;
  • permette il caricamento dei registri in questione con un codice numerico: LOAD Reg,#N;
  • permette l’accesso indiretto alla memoria, ovvero nel suo instruction set ci sono le istruzioni LOAD A,@Reg (che carica in A il contenuto della cella di memoria il cui indirizzo è contenuto nel registro Reg) e LOAD @Reg,A (che carica in A il contenuto della cella di memoria il cui indirizzo è contenuto nel registro Reg).

Ora supponiamo di voler trasferire dei dati dalle locazioni contigue 50-59 alle locazioni contigue 60-69. Possiamo scrivere:

COUNT        .EQU    00
START_A    .EQU    50
START_B    .EQU    60

    .ORG     0
; Preparazione delle variabili:
; In COUNT preparo il numero di byte da trasferire
LOAD    A,#9
LOAD    @COUNT,A
; In IX l’indirizzo sorgente
LOAD    IX,#START_A
; In IY l’indirizzo destinazione
LOAD    IY,#START_B
; Inizio loop di trasferimento
Loop :
; Trasferimento byte
    LOAD    A,@IX
    LOAD    @IY,A
; Preparo indirizzi byte successivo
    LOAD    A,IX
    ADD    A,#1
    LOAD    IX,A
    LOAD    A,IY
    ADD    A,#1
    LOAD    IY,A
; Controllo fine loop
LOAD    A,@COUNT
    SUB    A,#1
    JMP    NZ,Loop
; Il resto del programma . . .

È buona norma, quando si scriva un qualsiasi codice, provare a fare una stima del costo computazionale. In questo caso il trasferimento di 10 byte ci "costa":

4 + (11*10) = 114 passi o us

È facile notare che maggiore è il numero di byte da trasferire, maggiore sarà il tempo necessario per l'esecuzione del codice. Se il byte da trasferire fossero 100 avremmo:
4 + (11*100) = 1104 passi o us

quindi 1,104 mS.

Esistono circuiti dedicati a questo genere di operazioni che alleggeriscono il compito della CPU. Tali dispositivi sono detti Direct Memory Access Controller o DMA Controller e possono essere dotati di più canali.
Tipicamente per ciascun canale abbiamo:

  • Registro / contatori di indirizzo di sorgente / destinazione che vengono dimensionati in base all’address bus del microcontrollore;
  • un contatore di trasferimento, normalmente a 16 bit (che permette un numero massimo di elementi trasferibili pari a 64 kb);
  • un registro di Stato / Programmazione per sorgente / destinazione che viene utilizzato per stabilire se l’indirizzo successivo al trasferimento debba essere incrementato, decrementato oppure lasciato inalterato. Si occuperà anche di stabilire se il dato è a 8 o 16 bit ma anche della generazione di un IRQ da parte del DMA quando il trasferimento sia finito. Ciò vale anche se l'IRQ viene generato da un'altra periferica, per esempio la seriale.

Torniamo adesso, brevemente, al nostro microcontrollore 30F48 e vediamo quali modalità possano essere programmate. Esse sono:

  • I/O;
  • Idle;
  • Repeat;
  • Normal;
  • Burst;
  • Block Trasfer.

Nel primo caso, un byte, oppure una word (2 byte), è trasferito su richiesta. Un numero prestabilito di trasferimenti  viene effettuato ed un interrupt può essere generato al completamento del numero prestabilito di trasferimenti. Vengono specificati un indirizzo a 24 bit e uno a 8 mentre la direzione del trasferimento è determinata dalla sorgente di attivazione.

Nella modalità Idle, abbiamo ancora trasferimento su richiesta. Esiste un numero prestabilito di trasferimenti che può essere effettuato e l'interrupt può essere generato al completamento di questi ultimi. Vengono specificati un indirizzo a 24 bit ed uno ad otto che sono tenuti fissi e la direzione del trasferimento viene determinata dalla sorgente di attivazione.

La modalità ripetizione prevede che, quando il numero prestabilito di trasferimenti sia stato completato, i valori iniziali di indirizzo e conteggio vengano ricaricati e pertanto l'operazione può continuare. Non viene generata alcuna richiesta di interrupt ed anche in questo caso gli indirizzi vengono tenuti fissi.

La modalità "normale" prevede due possibilità di richiesta: "auto-request" e "external". Nel primo caso, il DMAC viene attivato e continua l'esecuzione del trasferimento entro il numero prestabilito di trasferimenti da effettuare. Può essere richiesto un interrupt al completamento dei trasferimenti. Entrambi gli indirizzi sono a 24 bit. Nella modalità cycle-steal, il bus è rilasciato ad un altro bus master alla fine del trasferimento mentre nella modalità burst, a meno che non sia richiesto da un master a più alta priorità, non c'è rilascio del bus fino al completamento di tutti i trasferimenti.

Block Transfer mode indica il trasferimento, su richiesta, di un blocco di una misura specificata. Viene eseguito il numero di trasferimenti di blocchi prestabilito ed alla fine di ciascun trasferimento di blocco l'indirizzo viene ristabilito al suo valore iniziale. Alla fine del trasferimento dei blocchi, è possibile che venga generato un interrupt. Gli indirizzi sono, entrambi, a 24 bit.

La prossima figura serve per illustrare il trasferimento da un'area profonda 2 byte ad una profonda 1 byte

quello che segue, invece, è un esempio di trasferimento burst da un'area a 2 byte ad una della stessa dimensione.

Conclusioni

Bene, per oggi abbiamo concluso. Lo scopo di questa "puntata" era quello di rinforzare le basi che abbiamo visto di recente. Esistono un buon numero di architetture interessanti (ad esempio, Microchip Pic16F84, dsPic30F6015 e Renesas R8C 1A) che è importante conoscere e che costituiscono le basi per meglio comprendere ed utilizzare praticamente ogni micro esistente.

L'idea è, quindi, quella di chiudere questo corso oggi e avviarci alla stesura di un nuovo e più pratico corso in futuro in cui parleremo dei micro, mettendo a frutto tutti gli argomenti trattati. Teneteveli, dunque, da conto perchè naturalmente sarà difficile riprendere da capo proprio le basi.

Nel frattempo, come sempre, ricordate di utilizzare i commenti per ogni genere di segnalazione e/o considerazione che vogliate fare sul nostro corso.

Ed ora la parola a voi: vi è piaciuto? Vorreste veder trattati altri argomenti oppure altre architetture?

 

Quello che hai appena letto è un Articolo Premium reso disponibile affinché potessi valutare la qualità dei nostri contenuti!

 

Gli Articoli Tecnici Premium sono infatti riservati agli abbonati e vengono raccolti mensilmente nella nostra rivista digitale EOS-Book in PDF, ePub e mobi.
volantino eos-book1
Vorresti accedere a tutti gli altri Articoli Premium e fare il download degli EOS-Book? Allora valuta la possibilità di sottoscrivere un abbonamento a partire da € 2,95!
Scopri di più

4 Comments

  1. Piero Boccadoro Piero Boccadoro 29 agosto 2013
  2. Giorgio B. Giorgio B. 30 agosto 2013
  3. Piero Boccadoro Piero Boccadoro 31 agosto 2013
  4. gian.tassinario 9 settembre 2013

Leave a Reply