Sommario:

Sintetizzatore di arpa laser su Zybo Board: 10 passaggi (con immagini)
Sintetizzatore di arpa laser su Zybo Board: 10 passaggi (con immagini)

Video: Sintetizzatore di arpa laser su Zybo Board: 10 passaggi (con immagini)

Video: Sintetizzatore di arpa laser su Zybo Board: 10 passaggi (con immagini)
Video: LaserHarp - Progetto di Arpa Laser 2024, Novembre
Anonim
Sintetizzatore di arpa laser su Zybo Board
Sintetizzatore di arpa laser su Zybo Board

In questo tutorial creeremo un'arpa laser completamente funzionante utilizzando sensori IR con un'interfaccia seriale che consentirà all'utente di modificare l'accordatura e il tono dello strumento. Questa arpa sarà il remake del 21° secolo dell'antico strumento. Il sistema è stato creato utilizzando una scheda di sviluppo Xilinx Zybo insieme a Vivado Design Suites. Cosa ti servirà per completare il progetto:

  • 12 sensori IR ed emettitori (più o meno utilizzabili a seconda del numero di stringhe)
  • Scheda di sviluppo Zybo Zynq-7000
  • RTOS gratuiti
  • Vivado Design Suite
  • Filo (per il collegamento dei sensori alla scheda)
  • 3 pezzi di tubo in PVC ((2) 18 pollici e (1) 8 pollici)
  • 2 gomiti in PVC

Passaggio 1: ottieni la demo audio DMA Zybo di Digilent

Il lato FPGA di questo progetto si basa in gran parte sul progetto demo trovato qui. Utilizza l'accesso diretto alla memoria per inviare dati direttamente dalla memoria che il processore può scrivere su AXI Stream a un blocco audio I2S. I seguenti passaggi ti aiuteranno a far funzionare il progetto demo audio DMA:

  1. Potrebbe essere necessaria una nuova versione del file della scheda per la scheda Zybo. Segui queste istruzioni per ottenere nuovi file di bordo per Vivado.
  2. Segui i passaggi 1 e 2 nelle istruzioni in questa pagina per aprire il progetto demo in Vivado. Usa il metodo Vivado, non l'handoff hardware dell'SDK.
  3. Potresti ricevere un messaggio che dice che alcuni dei tuoi blocchi IP dovrebbero essere aggiornati. In tal caso, seleziona "Mostra stato IP" e quindi nella scheda Stato IP seleziona tutti gli IP scaduti e fai clic su "Aggiorna selezionato". Al termine e viene visualizzata una finestra che chiede se si desidera generare un prodotto di output, andare avanti e fare clic su "Genera". Se ricevi un messaggio di avviso critico, ignoralo.
  4. Passa dal design alla scheda sorgenti in Vivado per vedere i file sorgente. Fare clic con il pulsante destro del mouse sul design del blocco "design_1" e selezionare "Crea wrapper HDL". Quando richiesto, seleziona "copia wrapper generato per consentire le modifiche dell'utente". Verrà generato un file wrapper per il progetto.
  5. Ora che sono stati completati quei passaggi critici che erano stati in qualche modo tralasciati nell'altro tutorial, puoi tornare al tutorial precedentemente collegato e continuare dal passaggio 4 fino alla fine e assicurarti che il progetto demo venga eseguito correttamente. Se non hai un modo per inserire l'audio per la registrazione, registra semplicemente con le cuffie e ascolta un suono sfocato di 5-10 secondi quando premi il pulsante di riproduzione. Finché qualcosa esce dal jack delle cuffie quando si preme il pulsante di riproduzione, probabilmente funziona correttamente.

Passaggio 2: apporta alcune modifiche in Vivado

Apporta alcune modifiche in Vivado
Apporta alcune modifiche in Vivado

Quindi ora hai la demo audio DMA di Digilent funzionante, ma non è affatto l'obiettivo finale qui. Quindi dobbiamo tornare a Vivado e apportare alcune modifiche in modo che i nostri sensori possano essere collegati alle intestazioni PMOD e possiamo utilizzare il loro valore sul lato software.

  1. Apri lo schema a blocchi in Vivado
  2. Crea un blocco GPIO facendo clic con il pulsante destro del mouse in uno spazio vuoto nello schema a blocchi e selezionando "Aggiungi IP" dal menu. Trova e seleziona "AXI GPIO".
  3. Fare doppio clic sul nuovo blocco IP e nella finestra Ripersonalizza IP vai alla scheda Configurazione IP. Seleziona tutti gli ingressi e imposta la larghezza a dodici, poiché avremo 12 "corde" sulla nostra arpa e quindi necessitiamo di 12 sensori. Se si desidera utilizzare meno o più sensori, regolare questo numero in modo appropriato. Impostare anche l'abilitazione dell'interruzione.
  4. Fare clic con il pulsante destro del mouse sul nuovo blocco IP GPIO e selezionare "Esegui automazione connessione". Seleziona la casella AXI e premi OK. Questo dovrebbe connettere automaticamente l'interfaccia AXI, ma lasciare le uscite del blocco non connesse.
  5. Per fare spazio all'interrupt extra, fare doppio clic sul blocco IP xlconcat_0 e modificare il numero di porte da 4 a 5. Quindi è possibile collegare il pin ip2intc_irpt dal nuovo blocco GPIO alla nuova porta inutilizzata sul blocco xlconcat.
  6. Fare clic con il pulsante destro del mouse sull'output "GPIO" del nuovo blocco IP GPIO e selezionare "rendi esterno". Trova dove va la linea e fai clic sul piccolo pentagono laterale e sulla sinistra dovrebbe aprirsi una finestra in cui puoi cambiare il nome. Cambia il nome in "SENSORI". È importante utilizzare lo stesso nome se si desidera che il file dei vincoli che forniamo funzioni, altrimenti sarà necessario modificare il nome nel file dei vincoli.
  7. Torna nella scheda delle fonti, trova il file dei vincoli e sostituiscilo con quello che forniamo. Puoi scegliere di sostituire il file o semplicemente copiare il contenuto del nostro file dei vincoli e incollarlo sul contenuto di quello vecchio. Una delle cose importanti che fa il nostro file dei vincoli è abilitare i resistori di pullup sulle intestazioni PMOD. Ciò è necessario per i sensori particolari che abbiamo utilizzato, tuttavia non tutti i sensori sono uguali. Se i tuoi sensori richiedono resistori pulldown, puoi cambiare ogni istanza di "set_property PULLUP true" con "set_property PULLDOWN true". Se richiedono un valore di resistenza diverso da quello sulla scheda, è possibile rimuovere queste linee e utilizzare resistori esterni. I nomi dei pin sono nei commenti nel file dei vincoli e corrispondono alle etichette nel primo diagramma negli schemi Zybo pagina che puoi trovare qui. Se si desidera utilizzare pin pmod diversi, è sufficiente abbinare i nomi nel file dei vincoli alle etichette nello schema. Usiamo l'intestazione PMOD JE e JD e utilizziamo sei pin di dati su ciascuno, omettendo i pin 1 e 7. Queste informazioni sono importanti quando si collegano i sensori. Come mostrato nello schema, i pin 6 e 12 sul PMODS sono VCC e i pin 5 e 11 sono a terra.
  8. Rigenera il wrapper HDL come prima e copia e sovrascrivi quello vecchio. Al termine, generare bitstream ed esportare l'hardware come prima e riavviare l'SDK. Se ti viene chiesto se desideri sostituire il vecchio file hardware, la risposta è sì. Probabilmente è meglio chiudere l'SDK quando esporti l'hardware in modo che venga sostituito correttamente.
  9. Avvia l'SDK.

Passaggio 3: avvia FreeRTOS

Il prossimo passo è far funzionare FreeRTOS sulla scheda Zybo.

  1. Se non ne hai già una copia, scarica FreeRTOS qui ed estrai i file.
  2. Importa la demo di FreeRTOS Zynq che si trova in FreeRTOSv9.0.0\FreeRTOS\Demo\CORTEX_A9_Zynq_ZC702\RTOSDemo. Il processo di importazione è praticamente lo stesso dell'altro progetto demo, tuttavia poiché la demo FreeRTOS Zynq si basa su altri file nella cartella FreeRTOS, non dovresti copiare i file nel tuo spazio di lavoro. Invece, dovresti posizionare l'intera cartella FreeRTOS all'interno della cartella del tuo progetto.
  3. Crea un nuovo pacchetto di supporto della scheda andando su "file" -> "nuovo" -> "pacchetto di supporto della scheda". Assicurati che sia selezionato autonomo e fai clic su Fine. Dopo un momento apparirà una finestra, seleziona la casella accanto a lwip141 (questo impedisce a una delle demo di FreeRTOS di non essere compilata) e premi OK. Al termine, fai clic con il pulsante destro del mouse sul progetto RTOSdemo e vai su "proprietà", vai alla scheda "riferimenti del progetto" e seleziona la casella accanto al nuovo bsp che hai creato. Si spera che venga riconosciuto, ma a volte l'SDK Xilinx può essere strano per questo genere di cose. Se ricevi ancora un errore dopo questo passaggio che manca xparameters.h o qualcosa del genere, prova a ripetere questo passaggio e magari a uscire e riavviare l'SDK.

Passaggio 4: aggiungi il codice dell'arpa laser

Ora che FreeRTOS è stato importato, puoi portare i file dal progetto dell'arpa laser nella demo di FreeRTOS

  1. Crea una nuova cartella nella cartella src nella demo di FreeRTOS e copia e incolla tutti i file c forniti tranne main.c in questa cartella.
  2. Sostituire il main.c RTOSDemo con il main.c fornito.
  3. Se tutto è stato fatto correttamente, dovresti essere in grado di eseguire il codice dell'arpa laser a questo punto. A scopo di test, l'input del pulsante utilizzato nel progetto demo DMA viene ora utilizzato per riprodurre suoni senza sensori collegati (uno qualsiasi dei quattro pulsanti principali funzionerà). Suonerà una stringa ogni volta che lo premi e scorrerà tutte le stringhe nel sistema su più pressioni. Collega alcune cuffie o altoparlanti al jack per cuffie sulla scheda Zybo e assicurati di poter sentire i suoni delle corde che escono quando premi un pulsante.

Passaggio 5: Informazioni sul codice

Molti di voi che leggono questo tutorial sono probabilmente qui per imparare come impostare l'audio o utilizzare DMA per fare qualcosa di diverso o per creare uno strumento musicale diverso. Per questo motivo le prossime sezioni sono dedicate alla descrizione di come funziona il codice fornito insieme all'hardware precedentemente descritto per ottenere un'uscita audio funzionante utilizzando DMA. Se capisci perché i pezzi di codice sono lì, dovresti essere in grado di regolarli per qualunque cosa tu voglia creare.

Interruzioni

Per prima cosa menzionerò come vengono creati gli interrupt in questo progetto. Il modo in cui l'abbiamo fatto è stato creare prima una struttura di tabella vettoriale di interrupt che tiene traccia dell'ID, del gestore di interrupt e di un riferimento al dispositivo per ogni interrupt. Gli ID di interrupt provengono da xparameters.h. Il gestore di interrupt è una funzione che abbiamo scritto per DMA e GPIO e l'interrupt I2C proviene dal driver Xlic I2C. Il riferimento del dispositivo punta a istanze di ciascun dispositivo che inizializziamo altrove. Verso la fine della funzione _init_audio un ciclo passa attraverso ogni elemento nella tabella del vettore di interrupt e chiama due funzioni, XScuGic_Connect() e XScuGic_Enable() per connettersi e abilitare gli interrupt. Fanno riferimento a xInterruptController, che è un controller di interrupt creato in FreeRTOS main.c per impostazione predefinita. Quindi in pratica alleghiamo ciascuno dei nostri interrupt a questo controller di interrupt che è già stato creato per noi da FreeRTOS.

DMA

Il codice di inizializzazione DMA inizia in lh_main.c. Innanzitutto viene dichiarata un'istanza statica di una struttura XAxiDma. Quindi nella funzione _init_audio() viene configurato. Per prima cosa viene chiamata la funzione configure dal progetto demo, che è in dma.c. È abbastanza ben documentato e arriva direttamente dalla demo. Quindi l'interrupt viene connesso e abilitato. Per questo progetto è richiesto solo l'interrupt master-to-slave, poiché tutti i dati vengono inviati dal DMA al controller I2S. Se desideri registrare l'audio, avrai bisogno anche dell'interruzione slave-to-master. L'interrupt master-to-slave viene chiamato quando il DMA finisce di inviare i dati che gli hai detto di inviare. Questo interrupt è incredibilmente importante per il nostro progetto perché ogni volta che il DMA finisce di inviare un buffer di campioni audio deve iniziare immediatamente a inviare il buffer successivo, altrimenti si verificherebbe un ritardo udibile tra gli invii. All'interno della funzione dma_mm2s_ISR() puoi vedere come gestiamo l'interrupt. La parte importante è verso la fine, dove usiamo xSemaphoreGiveFromISR() e portYIELD_FROM_ISR() per notificare a _audio_task() che può avviare il prossimo trasferimento DMA. Il modo in cui inviamo dati audio costanti è alternando due buffer. Quando un buffer viene trasmesso al blocco I2C, l'altro buffer sta calcolando e memorizzando i suoi valori. Quindi, quando l'interrupt proviene dal DMA, il buffer attivo cambia e il buffer scritto più di recente inizia a essere trasferito mentre il buffer precedentemente trasferito inizia a essere sovrascritto con nuovi dati. La parte fondamentale della funzione _audio_task è dove viene chiamato fnAudioPlay(). fnAudioPlay() accetta l'istanza DMA, la lunghezza del buffer e un puntatore al buffer da cui verranno trasferiti i dati. Alcuni valori vengono inviati ai registri I2S per far sapere che stanno arrivando altri campioni. Quindi XAxiDma_SimpleTransfer() viene chiamato per avviare il trasferimento.

Audio I2S

audio.c e audio.h sono dove avviene l'inizializzazione di I2S. Il codice di inizializzazione I2S è un pezzo di codice piuttosto comune che fluttua in diversi punti, potresti trovare lievi variazioni da altre fonti, ma questo dovrebbe funzionare. È abbastanza ben documentato e non c'è molto bisogno di essere cambiato per il progetto dell'arpa. La demo audio DMA da cui proviene ha funzioni per passare agli ingressi microfono o di linea in modo da poterli utilizzare se hai bisogno di quella funzionalità.

Sintesi del suono

Per descrivere come funziona la sintesi del suono, elencherò ciascuno dei modelli sonori utilizzati nello sviluppo che hanno portato al metodo finale, poiché ti darà un'idea del perché è fatto nel modo in cui è fatto.

Metodo 1: un periodo di valori del seno viene calcolato per ciascuna stringa alla frequenza corrispondente per la nota musicale di quella stringa e memorizzato in una matrice. Ad esempio, la lunghezza dell'array sarà il periodo dell'onda sinusoidale in campioni, che equivale a # di campioni/ciclo. Se la frequenza di campionamento è 48kHz e la frequenza della nota è 100Hz, allora ci sono 48.000 campioni/secondo e 100 cicli/secondo che portano a 4800 campioni per ciclo, e la lunghezza dell'array sarà di 4800 campioni e conterrà i valori di uno completo periodo dell'onda sinusoidale. Quando la stringa viene riprodotta, il buffer del campione audio viene riempito prendendo un valore dall'array dell'onda sinusoidale e inserendolo nel buffer audio come campione, quindi incrementando l'indice nell'array dell'onda sinusoidale in modo che utilizzando il nostro esempio precedente nel corso di 4800 campioni un ciclo di onda sinusoidale viene inserito nel buffer audio. Un'operazione modulo viene utilizzata sull'indice dell'array in modo che rientri sempre tra 0 e la lunghezza e quando l'indice dell'array supera una certa soglia (come forse 2 secondi di campioni) la stringa viene disattivata. Per suonare più stringhe contemporaneamente, tieni traccia dell'indice dell'array di ciascuna stringa separatamente e aggiungi insieme il valore dell'onda sinusoidale di ciascuna stringa per ottenere ciascun campione.

Metodo 2: per creare un tono più musicale, iniziamo con il modello precedente e aggiungiamo armoniche a ciascuna frequenza fondamentale. Le frequenze armoniche sono frequenze che sono multipli interi della frequenza fondamentale. A differenza di quando due frequenze non correlate vengono sommate insieme, il che si traduce in due suoni distinti che vengono riprodotti contemporaneamente, quando le armoniche vengono aggiunte insieme continua a suonare come un solo suono, ma con un tono diverso. Per ottenere ciò, ogni volta che aggiungiamo al campione audio il valore dell'onda sinusoidale nella posizione (indice di array % lunghezza dell'array) aggiungiamo anche (2 * indice dell'array % lunghezza dell'array) e (3 * indice dell'array % lunghezza dell'array), e così via per quante armoniche si desiderano. Questi indici moltiplicati attraverseranno l'onda sinusoidale a frequenze che sono multipli interi della frequenza originale. Per consentire un maggiore controllo del tono, i valori di ogni armonica vengono moltiplicati per una variabile che rappresenta la quantità di quell'armonica nel suono complessivo. Ad esempio, l'onda sinusoidale fondamentale potrebbe avere i suoi valori tutti moltiplicati per 6 per renderla più un fattore nel suono complessivo, mentre la quinta armonica potrebbe avere un moltiplicatore di 1, il che significa che i suoi valori contribuiscono molto meno al suono complessivo.

Metodo 3: Ok, ora abbiamo un tono molto carino sulle note, ma c'è ancora un problema abbastanza cruciale: suonano a un volume fisso per una durata fissa. Per suonare come uno strumento reale, il volume di una corda suonata dovrebbe diminuire gradualmente nel tempo. Per fare ciò, un array viene riempito con i valori di una funzione che decresce esponenzialmente. Ora, quando vengono creati i campioni audio, il suono proveniente da ciascuna stringa viene calcolato come nel metodo precedente, ma prima di essere aggiunto al campione audio viene moltiplicato per il valore all'indice dell'array di quelle stringhe nell'array della funzione di decadimento esponenziale. Questo fa sì che il suono si dissipi dolcemente nel tempo. Quando l'indice dell'array raggiunge la fine dell'array di decadimento, la stringa viene interrotta.

Metodo 4: Quest'ultimo passaggio è ciò che dà davvero ai suoni delle corde il loro suono realistico delle corde. Prima suonavano piacevoli ma chiaramente sintetizzati. Per cercare di emulare meglio una corda di arpa del mondo reale, a ciascuna armonica viene assegnato un tasso di decadimento diverso. Nelle corde reali, quando la corda viene suonata per la prima volta, c'è un alto contenuto di armoniche ad alta frequenza che crea il tipo di suono pizzicato che ci aspettiamo da una corda. Queste armoniche ad alta frequenza sono molto brevemente la parte principale del suono, il suono della corda che viene percossa, ma decadono molto rapidamente quando le armoniche più lente prendono il sopravvento. Viene creato un array di decadimento per ogni numero armonico utilizzato nella sintesi del suono, ciascuno con il proprio tasso di decadimento. Ora ogni armonica può essere moltiplicata indipendentemente per il valore del corrispondente array di decadimento all'indice dell'array della corda e aggiunta al suono.

Nel complesso la sintesi del suono è intuitiva ma pesante nei calcoli. Memorizzare l'intero suono della corda in una volta richiederebbe troppa memoria, ma calcolare l'onda sinusoidale e la funzione esponenziale tra ogni fotogramma richiederebbe troppo tempo per tenere il passo con la velocità di riproduzione dell'audio. Nel codice vengono utilizzati numerosi trucchi per velocizzare il calcolo. Tutta la matematica, tranne la creazione iniziale delle tabelle di decadimento seno ed esponenziale, viene eseguita in formato intero, il che richiede la distribuzione dello spazio numerico disponibile nell'output audio a 24 bit. Ad esempio, la tabella del seno è di ampiezza 150 in modo che sia liscia ma non così grande che molte corde suonate insieme possono aggiungere oltre 24 bit. Allo stesso modo, i valori della tabella esponenziale vengono moltiplicati per 80 prima di essere arrotondati a numeri interi e memorizzati. I pesi armonici possono assumere valori discreti compresi tra 0 e 10. Inoltre tutti i campioni sono effettivamente raddoppiati e le onde sinusoidali sono indicizzate per 2, dimezzando effettivamente la frequenza di campionamento. Questo limita la frequenza massima che può essere suonata, ma era necessario per calcolare abbastanza velocemente il numero attuale di corde e armoniche.

Creare questo modello sonoro e farlo funzionare ha richiesto uno sforzo considerevole dal lato del processore, e sarebbe stato incredibilmente difficile farlo funzionare da zero sul lato FPGA nell'arco di tempo di questo progetto (immagina di dover ricreare il bitstream ogni volta che un pezzo di Verilog è stato cambiato per testare il suono). Tuttavia, farlo sull'FPGA potrebbe essere probabilmente un modo migliore per farlo, eliminando eventualmente il problema di non essere in grado di calcolare i campioni abbastanza rapidamente e consentire l'esecuzione di più stringhe, armoniche e persino effetti audio o altre attività sul lato processore.

Passaggio 6: cablaggio dei sensori

Cablaggio dei sensori
Cablaggio dei sensori

Per creare le corde abbiamo utilizzato sensori a raggio di rottura IR che rileveranno quando la corda viene suonata. Abbiamo ordinato i nostri sensori dal seguente link. I sensori hanno un cavo di alimentazione, terra e dati mentre gli emettitori hanno solo un cavo di alimentazione e terra. Abbiamo usato i pin da 3,3 V e di massa dalle intestazioni PMOD per alimentare sia gli emettitori che i sensori. Per alimentare tutti i sensori ed emettitori è necessario collegare tutti i sensori e l'emettitore in parallelo. I cavi dati dai sensori dovranno andare ciascuno al proprio pin pmod.

Passaggio 7: costruzione dello scheletro

Costruire lo scheletro
Costruire lo scheletro

Per creare la forma dell'arpa i tre pezzi sono usati come uno scheletro su cui posizionare i sensori e gli emettitori. Su uno dei due pezzi di tubo in PVC da 18 pollici allineare i sensori e gli emettitori in ordine alternato a 1,5 pollici l'uno dall'altro e quindi fissarli con del nastro adesivo al tubo. Sull'altro tubo in PVC da 18 pollici allineare i sensori e gli emettitori in ordine alternato, ma assicurarsi di sfalsare l'ordine (cioè se il primo tubo ha avuto un sensore prima, il secondo dovrebbe avere un emettitore prima e viceversa). Sarà necessario saldare fili più lunghi sui fili dati, alimentazione e terra per assicurarsi che possano raggiungere la scheda.

Passaggio 8: costruire l'esterno in legno

Costruire l'esterno in legno
Costruire l'esterno in legno

Questo passaggio è facoltativo ma altamente consigliato. L'esterno in legno non solo rende l'arpa bella, ma protegge anche i sensori e i cavi da eventuali danni. La cornice in legno può essere creata da un anello rettangolare sacro in legno. L'interno del rettangolo deve avere un'apertura di almeno 1-1/2 pollici per adattarsi al tubo e allo scheletro del sensore. Una volta costruito il telaio, praticare due fori che consentiranno l'uscita dei cavi dal sensore e dagli emettitori per collegarli alla scheda.

*Nota: si consiglia di aggiungere punti di accesso per poter rimuovere e inserire lo scheletro del tubo nel caso in cui sia necessario eseguire riparazioni o apportare lievi modifiche.

Passaggio 9: mettere insieme tutti i pezzi

Mettere insieme tutti i pezzi
Mettere insieme tutti i pezzi

Una volta completati tutti i passaggi precedenti, è il momento di costruire l'arpa. Per prima cosa posiziona lo scheletro del tubo all'interno dell'esterno in legno. Quindi collegare i fili per i sensori e gli emettitori nella posizione corretta sulla scheda. Quindi apri l'SDK e fai clic sul pulsante di debug per programmare la scheda. Una volta programmata la scheda, collegare un paio di cuffie o un altoparlante. A seconda di quale sensore finisce in quale porta pmod, le corde della tua arpa saranno probabilmente fuori servizio all'inizio. Poiché può essere difficile dire quale cavo va a quale sensore quando sono coinvolti così tanti cavi, abbiamo incluso un modo per mappare i numeri delle stringhe per interrompere le posizioni dei bit nel software. Trova "static int sensor_map[NUM_STRINGS]" e regola i valori nell'array finché le stringhe non suonano dall'ordine più basso a quello più alto.

Il menu può essere utilizzato aprendo un terminale seriale (es. RealTerm) e impostando il baud rate su 115200 e il display su ANSI. È possibile navigare nel menu utilizzando i tasti w e s per spostarsi su e giù e i tasti a e d per modificare i valori.

Passaggio 10: ROCK OUT

Una volta che l'arpa è completamente funzionante. Padroneggia l'arpa e ascolta il dolce suono della tua musica!

Consigliato: