Indice |
Introduzione
Questo piccolo articolo nasce per puro caso. Ultimamente sto armeggiando con le Low Level Library (LL) della ST e più scendo a basso livello più rimango affascinato dall'elettronica. Per me non è tanto veder funzionare le cose quando pensare a come sono fatte dentro, nel piccolo e poi ancora più nel piccolo. E così, visto che è estate e che le lezioni non sono ancora incominciate, ho colto il consiglio di un utente del forum che ogni volta che si parla di FPGA lui consiglia "fate una UART così che potete parlare col PC". Avevo già lavorato con le FPGA scrivendo in VHDL ma mi piaceva l'idea di lasciare qualcosa sul forum di un argomento non molto "user friendly" rispetto ai più comuni microprocessori che (a parte Arduino) sono comunque moooolto più usati dalla grande massa.
Dove sta la differenza?
Non partirò dagli albori a spiegare le ovvie differenze tra FPGA e microcontrollori nella teoria di funzionamento. Piuttosto è interessante vedere come si lavora con l'hardware su livelli di astrazione diversi. In un mictrocontrollore ci sono già dei pezzi di puzzle (UART, ADC, DAC, SPI.... ) e sta all'utente configurarli e montarli insieme. Si passa praticamente sempre per un primo processo di setup in cui si scelgono, ad esempio, numero di bit, parità, baudrate, DMA e altre impostazioni; fatto questo al programmatore non resta che scrivere il dato da mandare dentro un registro apposito e dare il via. Quello che succede da quel momento in poi non è più competenza del programmatore: ha impostato un blocchetto e adesso quel blocchetto sta funzionando, che si vuole di più dalla vita? A me piace scrivere anche quel blocchetto e farmelo felicemente a modo mio. E' ovvio che quello che io progetto come trasmettitore UART sarà mooolto meno elaborato del blocchetto che è presente in un micro, sarà meno ottimizzato, avrà meno funzioni, ma sarà mio. Le softwarehouse mettono a dispozione delle blackbox chiamate IPCore che sono anche questi dei pezzi prefatti da copincollare che funzionano alla grande. Ma in questo modo si ritorna al più semplice mettere insieme cose fatte da altri e non si scende nel dettaglio del protocollo e dell'implementazione. Tutto questo sproloquio per dire che mi sono messo a scrivere un trasmettitore (e nel prossimo capitolo anche il ricevitore) con protocollo UART su FPGA.
La FPGA
Sono un povero studente che vuole imparare, non ho molti soldi e mi è sempre piaciuto fare le cose con le mie mani. Invece di prendere una board (ad esempio della Xilinx) che ha già a bordo programmatore, display, pulsanti e tanto altro per divertirsi con una scheda mono-blocco, ho deciso di prendere una piccola ed economica (e cinese) board della Altera. Quello che c'è a bordo è: alimentazione, FPGA, basta. Il programmatore preso a parte costa anche lui una miseria. La board ha i pin tutti su connettori header maschi quindi connetterci altri dispositivi è facile e anche cablarla su millefori viene comodissimo. Cyclone II Mini Board: 15€
Il programmatore è sempre della Altera e non riserva particolari sorprese apparte il prezzo: 6€. E così con meno di 20€ si ha a disposizione una FPGA che apre lo svilupatore a un nuovo mondo diverso dai micro a cui è abituato.
Il trasmettitore UART
Specifiche:
- Word: 8bit
- BaudRate: dipendente dal clock (see later)
- No parity bit
- Segnale in uscita che indica quando il trasmettitore è pronto a ricevere un nuovo dato
- Segnale in ingresso di Start che indica che il dato è pronto e può iniziare la trasmissione
Assolutamente nulla di neanche lontanamente speciale. Si tratta di:
- aspettare il segnale di start
- quando arriva "fotografare" l'ingresso da trsmettere
- trasmettere lo start bit (basso)
- trasmettere il dato (8 bit)
- trasmettere lo stop bit
- comunicare all'esterno che si è pronti per una nuova trasmissione
Insomma: una "macchinetta" a stati senza pretese.
La Entity
La entity è il contenitore del nostro componente. Quando compriamo un operazionale ci viene dato un cosino nero con tutti i piedini e a noi interessa sapere (dal datasheet) a cosa corrispondono quei piedini! Anche senza avere la minima idea di che struttura abbia all'interno quell'operazionale serve sapere per lo meno dove stanno alimentazioni, entrate ed uscite: questa è la entity.
La architecture
Se premessimo sul "+" che sta in alto a sinistra nell'immagine della entity ci troveremmo di fronte ad un po' di Flip Flop, adder, mux e comparatori opportunamente connessi. Per fortuna il VHDL e i sintetizzatori ci permettono di non scrivere ad uno ad uno tutti i gate e tutte le funzioni logiche ma possiamo effettuare una descrizione più ad alto livello, sintatticamente vicina ad esempio al C ma teoricamente molto diversa. La seguente è l'idea di funzionamento:
Spiegato brevemente a parole:
- finchè l'output è 1 e ready è 1 non sto trasmettendo: controllo se l'enable è alto
- se Enable è basso vuol dire che non sto trasmettendo e non devo neanche iniziare a farlo: tutto fermo
- se Enable è alto vuol dire che inizia la trasmissione: salvo il registro di ingresso, start bit, trasmetto i dati, stop bit, ready alto, si ricomincia
che si trasforma nel seguente codice VHDL:
architecture Behavioral of UART_TX is signal ready: std_logic:= '1'; signal buff: std_logic_vector( 7 downto 0) := (others => '0'); signal counter: std_logic_vector (3 downto 0) := "0000"; begin process(CLK) begin if(rising_edge(CLK)) then if(ENABLE= '1' and ready='1') then -- carica start bit, prendi registo, porta basso ready TX_DATA_OUT <= '0'; buff<= TX_BUFFER; ready <= '0'; elsif(ready = '0') then if(counter = "1000") then TX_DATA_OUT <= '1'; counter<= counter +1; elsif(counter= "1001") then counter <= "0000"; ready <= '1'; else counter <= counter +1; --TX_DATA_OUT <= buff(7 - to_integer(unsigned(counter))); TX_DATA_OUT <= buff(to_integer(unsigned(counter))); end if; end if; end if; end process; RTT<= ready;end Behavioral;
Il risultato di una prima simulazione è il seguente:
Click to zoom. In verticale c'è una linea gialla che evidenzia l'inizio della trasmissione. Appena l'enable va alto e la trasmissione deve partire si vede che la linea dati viene portata bassa e non si è più Ready To Transfer (RTT). La simulazione ha confermato il corretto funzionamento del dispositivo.
Caricare sulla board
La simulazione conferma che il trasmettitore fa il suo dovere quando usato nella maniera corretta e in simulazione è facile impostare i segnali di ingresso con i timing giusti e far cambiare l'ingresso per vedere le trasmissioni. Quando si vuole caricare il bitstream su una board e vedere effettivamente la trasmissione allora bisogna inventarsi un modo per "stimolare" il componente. Si può fare in tanti modi, quello che preferisco (e non è il più facile) è quello di creare un' altra entity che generi i dati e li passi al trasmettitore. Questo vuol dire ricominciare da capo con un'altro progetto e poi collegarli insieme ma abbiamo la garanzia di avere due oggetti separati: Device Under Test( DUT: il nostro trasmettitore) e un coso a parte del tutto separato che genera dati. La struttura finale è di questo tipo
Clock e BaudRate
L'ultima domanda che rimane è: come impostiamo il Rate di trasmissione? Il vantaggio che il VHDL offre è di poter avere una struttura di astrazione molto verticale. Quindi per ora il trasmettitore che ho descritto manda un bit ogni colpo di clock, bisogna impostare una frequenza di clock corretta per avere una trasmissione corretta. Starà a chi usa questo componente (sempre io) usare un divisore coerente con le necessità. Le altre due idee sono:
- Modificare il clock prima: si usano delle specie di "define" che accettano la frequenza di clock da dividere e il baud rate e calcolano il divisore da usare. Nel momento in cui il sintetizzatore viene lanciato disegnerà già il circuto corretto: ottimizzato ma non modificabile runtime
- Progettare un blocco di clock: si fa una nuova entity che avrà come ingressi dei parametri e come uscita il clock così da essere modificata runtime. Si può creare anche un macroblocco con trasmettitore e gestore di clock già all'interno così da usarlo come un blocco unico senza dover ogni volta fare i collegamenti tra i due.
Conclusioni
Compilato il codice, generato il bitstream e connesso i pin della scheda con una FTDI il pc riceve i dati senza problemi. Il prossimo step sarà un ricevitore così da avere una comunicazione completa col PC.
Non metto bibliografia perchè è un articolo talmente banale che dovrei citare qualsiasi cosa. Non metto neanche foto del collegamento al PC perchè farvi vedere l'FPGA che dice "ciao" non è poi così entusiasmante per chi è dietro lo schermo (per me lo è stato eccome)!
Davide