Microcomputer: interfacciamento parallelo e gestione delle interruzioni

Giuliano Donzellini, Domenico Ponta

Generatore digitale di forme d'onda, su FPGA

120080

 

v1.71

In questo laboratorio si chiede di completare il progetto di un generatore digitale di forme d'onda basato su microcomputer. Dopo la verifica del progetto mediante simulazione, ne realizzeremo un prototipo su FPGA per verificarne il reale funzionamento. L'architettura del sistema è riportata in figura:

Il blocco in alto a sinistra è il generatore vero e proprio, che genera una sinusoide utilizzando una tabella precalcolata, producendo un campione da 8 bit ogni 0,25 mS (ossia ad una frequenza di campionamento fc = 4 KHz). Un ingresso numerico (Waveform Frequency, 4 bit) permette di impostare la frequenza f della sinusoide, secondo la relazione: f = wf (fc / 256), dove wf è il numero impostato. Possiamo quindi generare frequenze tra 0 e 234,4 Hz, a passi di 15,625 Hz. Se l'ingresso di test Low Freq è attivato, fc è ridotta a 40 Hz, e l'intervallo è scalato verso il basso di un fattore 100 (tra 0 e 2,34 Hz). Il metodo di generazione utilizzato consiste nel calcolo dell'angolo di fase d raggiunto ad ogni istante di campionamento. L'angolo giro è suddiviso, per comodità di calcolo, in 256 parti e, al campionamento, l'angolo di fase d è incrementato della quantità wf impostata. L'angolo d calcolato (0..255) è quindi utilizzato come indice in una tabella di valori, che ci fornisce il corrispondente valore della funzione sin(). Per ragioni di simmetria, la tabella riporta solo i valori della semionda positiva, ed è indicizzata da 0 a 127. L'altra metà della sinusoide è ottenuta dalla prima, complementando i valori della tabella.

Il generatore di modulazione funziona in modo simile per quanto riguarda il calcolo dell'angolo di fase. Tuttavia, dato che generiamo una forma d'onda triangolare, non è necessario ricorrere ad una tabella, ed i suoi valori sono ricavati per proporzionalità dall'angolo di fase. Il generatore produce un campione positivo, codificato su 4 bit, ogni 25 mS (ossia con una frequenza di campionamento di 40 Hz), e calcola l'angolo di fase in 1024 parti di angolo giro. Anche qui, un ingresso numerico (Modulation Frequency, 4 bit) permette di impostare la frequenza f, secondo la relazione: f = mf (fc / 1024), dove mf è il numero impostato. L'intervallo di frequenze generate è quindi tra 0 e circa 0,59 Hz, a passi di 0,039 Hz.

Il modulatore di ampiezza permette di attenuare i valori della sinusoide tramite un coefficiente moltiplicativo positivo, su 4 bit, il cui valore è generato, a scelta, dal generatore di modulazione, oppure dall'ingresso Amplitude. La selezione è controllata dall'ingresso Modulation On. La relazione ingresso-uscita del modulatore è Mw = Wave (Mod / 256), dove Mw è il segnale in uscita dal modulatore, Wave è il segnale che esce dal generatore, mentre Mod è il segnale di modulazione.

Lo stadio limitatore che segue (Clipper) ha lo scopo di limitare l'ampiezza del segnale a valori compresi tra -8 e +8.

L'ultimo elemento nella figura rappresenta il sistema di visualizzazione del segnale, ottenuta mediante una fila di 17 LED. Sulla scheda FPGA appariranno in orizzontale, con un solo LED acceso alla volta: quello al centro corrisponderà allo zero, i LED sulla sinistra ai valori negativi fino a -8, quelli a destra ai valori positivi fino a +8.

La nostra realizzazione del sistema è basata interamente su di un microcomputer, programmato in modo da realizzare, con tecniche di interrupt, i singoli blocchi prima descritti. Stiamo utilizzando una architettura per usi generali, quale quella del microprocessore DMC8, come Processore di Segnali Digitali (DSP).

Nella figura seguente è visibile lo schema:

In figura sono visibili, dall'alto e da sinistra:

  • L'ingresso Low_Freq (Low Frequency) di riduzione della frequenza di campionamento (da 4 KHz a 40 Hz);
  • L'ingresso Modul_On (Modulation On) di selezione tra l'ampiezza impostata dall'utente (Modul_On = '0') e l'ampiezza modulata dal generatore di modulazione (Modul_On = '1');
  • L'ingresso multiplo Modul_Freq (Modulation Frequency) che controlla la frequenza del segnale di modulazione (valori da 0 a 15);
  • L'ingresso multiplo Wave_Freq (Waveform Frequency) che controlla la frequenza della forma d'onda generata (valori da 0 a 15);
  • L'ingresso multiplo Amplitude che controlla l'ampiezza della forma d'onda generata, quando non è controllata dal generatore di modulazione (valori da 0 a 15);
  • L'ingresso di !RESET;
  • L'uscita diretta di controllo Direct Waveform Output, prelevata prima della modulazione (otto linee, visualizzate da altrettanti LED);
  • Le uscite di visualizzazione del segnale modulato Modulated Waveform Display (17 LEDs), come descritte in precedenza.

Nella figura è visibile inoltre, sulla destra del microcomputer, un componente Interrupt Timer, predisposto per interrompere ciclicamente il microprocessore.

Specifiche del programma principale

Per seguire meglio la descrizione, è utile fare riferimento alla struttura generale del programma, scaricabile qui (è la traccia meno completa, tra quelle disponibili più sotto). Dopo le necessarie inizializzazioni dello stack, delle variabili e delle interruzioni, il programma principale esegue un ciclo infinito, nel quale si occupa di leggere i porti di ingresso per ricavare le varie informazioni provenienti dagli interruttori, e codificarle in opportune variabili, ad uso del programma di interruzione, come le seguenti:

  SLOWFC   boolean "Vera" se l'utente richiede la frequenza di campionamento "bassa" di 40 Hz
  FREQ   integer

Controllo della frequenza della sinusoide generata (valori da 0 a 15)

  AMPL   integer Controllo della ampiezza della sinusoide generata (valori da 0 a 15)
  MODUL   boolean

"Vera" se l'utente richiede che sia il generatore di modulazione a controllare il modulatore,
"Falsa" se vuole controllare l'ampiezza del segnale dall'ingresso Amplitude

  MODFREQ   integer

Controllo della frequenza di modulazione (valori da 0 a 15)

Specifiche del programma di interruzione

Il programma di interruzione è lanciato ogni 0,25 mS dal timer di interruzione, ossia 4000 volte al secondo. Dopo il necessario salvataggio dei registri interni sullo stack, valuta se sono trascorsi 25 mS (pari a 100 chiamate).

a) Se i 25 mS non sono ancora trascorsi, controlla l'eventuale richiesta dell'utente di ridurre la frequenza di campionamento del segnale (nella variabile SLOWFC): se è attiva, esce subito, perchè tutte le valutazioni saranno effettuate solo ogni 25 mS (ossia 40 volte al secondo). Altrimenti procede, saltando in avanti a WAVE, con la generazione ed elaborazione del prossimo campione, eseguendo queste operazioni ad ogni chiamata (e cioè alla frequenza di campionamento di 4000 Hz).

b) Invece, se i 25 mS sono trascorsi, prima di passare in ogni caso alla generazione del prossimo campione (a WAVE), viene eseguita una chiamata al sottoprogramma MODGEN, che realizza il generatore di modulazione, descritto più avanti.

A questo punto, all'etichetta WAVE, il programma lancia il sottoprogramma WAVEGEN, che realizza il generatore principale (descritto dopo); il valore generato viene scritto sulle otto linee di uscita Direct Waveform Output.

Prima di chiamare il sottoprogramma del modulatore di ampiezza MULTIPLY, selezioniamo il valore di modulazione che, a seconda della richiesta dell'utente (ora nella variabile MODUL), potrà essere pari a quanto generato dal sottoprogramma MODGEN, oppure al contenuto della variabile AMPL. MULTIPLY esegue una moltiplicazione 8x8 bit tra il valore del segnale (compreso nell'intervallo -127..+127) e quello di controllo dell'ampiezza, dividendo poi il risultato per 256. Il risultato complessivo è una attenuazione del segnale generato, di un coefficiente pari ad (A / 256).

Prima di essere visualizzato sul display ottenuto con i 17 LED in fila (sottoprogramma DISPLAY), i valori del segnale vengono limitati dal sottoprogramma CLIPPER all'intervallo visualizzabile +8..-8.

Infine, la routine si conclude con il ripristino di tutti i registri salvati in precedenza e la riabilitazione delle interruzioni.

Specifiche dei singoli sottoprogrammi

MODGEN (Modulation Waveform Generator)

Calcola la forma d'onda triangolare che modula in ampiezza il segnale sinusoidale. L'angolo di fase corrente è calcolato su 10 bit, nella variabile a 16 bit MODPHASE: è ottenuto incrementando la variabile, ogni 25 mS, della quantità contenuta nella variabile MODFREQ. Il valore della funzione è quindi calcolato dividendo per 64 il valore dell'angolo di fase; il valore prodotto è direttamente questo numero se è compreso tra 0 e 7, mentre se è uguale o maggiore di 8, "ribaltiamo" verso il basso la curva (invertendo i bit e aggiungendo 1), in modo da generare il segnale "triangolare" visibile in figura:

Il valore generato è salvato nella variabile MODAMPL.

WAVEGEN (Main Waveform Generator)

Genera i campioni della sinusoide, come descritto in precedenza. L'angolo di fase corrente è memorizzato nella variabile PHASE; il successivo angolo è calcolato aggiungendo a PHASE il contenuto della variabile FREQ. L'angolo di fase corrente (0..255), ridotto a 7 bit, è quindi utilizzato come indice nella tabella SINTABLE, che contiene i valori della semionda positiva della sinusoide. Come già scritto, l'altra metà della sinusoide è ottenuta dalla prima, complementando a due i valori della tabella, se l'angolo di fase è maggiore di 127. Il valore corrente della sinusoide è generato nell'accumulatore A. Si noti che la tabella è allocata in memoria (con la 'ORG 1000h') in modo che la parte alta dell'indirizzo sia sempre lo stesso per tutte le sue locazioni (per semplificare il calcolo dell'indirizzo della locazione).

CLIPPER (Signal Clipping)

Il limitatore (Clipper) impedisce che i valori assunti dal segnale, in uscita dal modulatore, possano superare i limiti pre-definiti (qui -8..+8). Lavora sul numero binario presente nell'accumulatore A, considerandolo senza segno, e controllando dapprima che sia compreso tra '11111000' e '11111111' (ossia tra -8 e -1), oppure tra '00000000' e '00001000' (ossia tra 0 e +8). Se rientra in quei limiti, torna al chiamante senza modificare il numero. Altrimenti il numero deborda e deve essere limitato: a tal fine ne controlla il segno (il bit 7) e quindi lo sostituisce con '00001000' (+8) se il numero è positivo, oppure con '11111000' (-8) se è negativo. Nella figura seguente è rappresentato un esempio di segnale la cui escursione è ridotta dal limitatore:

DISPLAY (Signal Display on a LED bar)

Il valore da visualizzare è ricevuto nell'accumulatore A; il sottoprogramma usa la tabella LEDTABLE per visualizzare un solo LED acceso alla volta, la cui posizione deve corrispondere al valore del segnale. Si osservi che ogni riga della tabella definisce appunto un solo '1', corrispondente al LED che deve essere acceso. La tabella ha 4 byte per riga, per cui per usare il valore come indice nella tabella, viene moltiplicato per 4 per ottenere la dislocazione dall'indirizzo di base della tabella (anche in questo caso la tabella è allocata in modo che la parte alta dell'indirizzo sia sempre lo stesso per tutte le righe). Si noti che l'etichetta LEDTABLE definisce il centro della tabella, e che la dislocazione può essere positiva o negativa, come il numero in ingresso. I primo byte definito nella riga della tabella è ricopiato sul porto che pilota gli otto LED posizionati a sinistra dello '0'; il secondo è riversato sul porto che pilota il LED dello '0', il terzo viene scritto sul porto collegato agli otto LED a destra.

MULTIPLY (Integer multiplication)

Il sottoprogramma MULTIPLY realizza il modulatore di ampiezza, moltiplicando il valore del segnale (ricevuto nel registro B), per il valore della modulazione (ricevuto nel registro A). Il risultato viene calcolato per passi successivi nel registro HL, utilizzato come accumulatore a 16 bit del risultato. Il sottoprogramma, prima di tutto, ricopia il valore contenuto in B nel registro DE, il moltiplicando. Per fare questo occorre prestare attenzione al segno di B, che deve essere esteso a 16 bit (la parte alta del registro DE deve essere azzerata se il numero è positivo, altrimenti deve essere posta a '11111111').

Poi esegue un ciclo che viene ripetuto 8 volte, ossia per ogni bit presente nel registro A (il moltiplicatore). Ad ogni ripetizione viene scalato a sinistra il moltiplicando A e controllato il bit che fuoriesce: se questo è pari a '1', il moltiplicando DE è sommato ad HL (risultato parziale), altrimenti no. In ogni caso, prima di passare alla ripetizione successiva, HL è scalato a sinistra di un passo. Il procedimento è simile a quello che eseguiremmo noi "a mano" sulla carta ma con tre differenze: 1) cominciamo dal bit più a sinistra del moltiplicatore, invece che da destra, 2) eseguiamo una somma parziale ad ogni passo, invece che eseguire la somma totale alla fine e, 3) scaliamo il risultato a sinistra ad ogni passo, invece che "incolonnare" in diagonale, sulla carta, i singoli risultati.

La divisione finale per 256 corrisponderebbe ad uno scalamento a destra di HL di 8 posizioni. E' più semplice ed efficente prendere come risultato direttamente la parte alta H, ricopiandola nel registro A (valore di ritorno del sottoprogramma). E' opportuno, inoltre, arrotondare il risultato all'intero più vicino, invece che troncarlo: per fare questo, controlliamo il bit più alto della parte bassa di HL e, se a '1', incrementiamo il risultato di una unità.


Sono disponibili più tracce di soluzioni da completare, in codice assembly DMC8, in ordine decrescente di difficoltà:

  1. Nella prima traccia, la più impegnativa, è presente solo la struttura generale della soluzione: viene lasciato all'utente il compito di definire interamente tutti i singoli moduli, organizzati in sottoprogrammi;
  2. La seconda soluzione parziale aggiunge alla precedente la realizzazione dei generatori (quello principale e quello di modulazione);
  3. La terza traccia, alla realizzazione dei generatori è aggiunta quella del limitatore;
  4. Nella quarta traccia la soluzione è quasi completa, e manca solo la realizzazione del modulatore (il moltiplicatore).

Chi volesse eseguire solo un'analisi di codice già scritto, ed eseguirne solo la simulazione e la realizzazione su FPGA, trova disponibile qui anche una soluzione completa.

Al termine della scrittura e del test funzionale del programma nel d-McE, occorre programmare la ROM del microcomputer (potete aprirne lo schema nel d-DcS con un click qui). Si raccomanda di usare lo schema fornito senza cancellare o modificare le terminazioni di ingresso e di uscita perché sono state predisposte per la esportazione del progetto sulla scheda FPGA.

Nella finestra del diagramma temporale del d-DcS sono disponibili alcune appropriate sequenze di test. La sequenza "TestFrequency" propone l'analisi del sistema senza modulazione di ampiezza, impostando frequenze differenti nel generatore sinusoidale. Come si vede, è possibile osservare l'andamento nel tempo dell'accensione dei LED, che segue l'andamento sinusoidale:

La sequenza "TestModulated" propone l'analisi del sistema con la modulazione di ampiezza triangolare, senza variare la frequenza del generatore principale. Come si vede, è possibile osservare l'andamento nel tempo dell'ampiezza del segnale generato, che segue un inviluppo triangolare:

La figura seguente mostra lo stesso risultato ma, grazie all'utilizzo della "lente di ingrandimento", possiamo avere, oltre alla visione di insieme dell' inviluppo del segnale generato, anche una vista ravvicinata dello stesso, in un intervallo di tempo a scelta, riconoscendo l'andamento sinusoidale:


Passiamo alla realizzazione fisica del progetto sulla scheda FPGA. La procedura generale è descritta passo passo nei tutorial introduttivi:

   Realizzazione di un prototipo di circuito su scheda Altera DE2
   Test di una rete sequenziale su scheda Altera DE2
   Test di microcomputer su scheda Altera DE2

Il comando "Test on FPGA" (del d-DcS) apre la finestra di dialogo visibile qui sotto:

Tutte le associazioni degli ingressi e uscite con i corrispondenti dispositivi della scheda FPGA sono state già predefinite. Per ottimizzare la sperimentazione di quanto descritto sulla scheda FPGA, evidenziamo le associazioni, in modo riassuntivo, anche nella figura seguente (il "pannello di controllo" del nostro sistema):