Perché non usare sempre DMA a favore degli interrupt con UART su STM32? [chiuso]


9

Ho trascorso il mese scorso molto tempo a far funzionare UART (per MIDI) con un STM (STM32F103C8T6) usando gli interrupt, senza molto successo.

Tuttavia, questa sera usando DMA ha funzionato abbastanza velocemente.

Dal momento che per quanto riguarda la lettura di DMA è più veloce e allevia la CPU, perché non utilizzare sempre DMA a favore degli interrupt? Soprattutto dal momento che sull'STM32 sembrano esserci alcuni problemi.

Sto usando STM32CubeMx / HAL.


2
Perchè no? Questa è o una questione di opinione, una che cerca di indovinare quale possibile ragione tecnica, o allo stesso modo troppo ampia, e quindi non una domanda che appartiene qui. Per citare un esempio casuale, DMA significherà più latenza nel rivendicare i dati, soprattutto perché non si ottiene alcun vantaggio reale a meno che non gli si permetta di raccogliere più caratteri. Spesso potrebbe andare bene, a volte no.
Chris Stratton,

6
Se ottenere interruzioni di lavoro ha richiesto settimane, è perché ti sei avvicinato al compito nel modo sbagliato; far funzionare DMA potrebbe richiedere più tempo - in realtà è un compito più complesso, quindi l'apparente facilità del compito più complesso rispetto a quello più semplice presumibilmente dipende dalle risorse che hai utilizzato per l'orientamento con ciascuno, non dal meccanismo stesso.
Chris Stratton,

5
Non dare mai per scontato che dma libera la cpu, a volte sì, la cpu continua a funzionare, a volte no il processore è bloccato per contenere il bus per il motore dma. È fondamentale fare questo con un'implementazione del braccio, quindi non posso semplicemente dire che tutti i bracci sono in questo modo e tutti gli x86 sono in quel modo o qualunque cosa, non è così semplice, devi sempre esaminare il design del sistema e forse fare un po 'di hacking. Il chip che hai potrebbe benissimo liberare il core del braccio, questo è solo un commento su dma. Per quanto riguarda la tua domanda, non ha senso che non riesci a tenere il passo e dma + int è probabilmente la soluzione completa se non puoi semplicemente sondare.
old_timer

5
Gli interrupt sono piuttosto banali sulla porta seriale STM32F. Perché non pubblichi una domanda con il tuo codice così alcuni di noi possono provare a individuare dove stai sbagliando? Non è mai una buona idea hackerare il codice fino a quando non funziona senza capire quale fosse il problema di fondo.
Jon,

7
Secondo la mia (non così) modesta opinione, questo è uno dei lati negativi dell'uso del cubo orribile e gonfio. Scrivi da zero il software, imparerai esattamente come funziona l'UART (perché devi farlo), capirai molto meglio la periferica e alla lunga ti farà risparmiare così tanto tempo.
DiBosco,

Risposte:


24

Mentre DMA allevia la CPU e quindi può ridurre la latenza di altre applicazioni guidate da interrupt in esecuzione sullo stesso core, ci sono dei costi associati:

  • C'è solo un numero limitato di canali DMA e ci sono limitazioni su come questi canali possono interagire con le diverse periferiche. Un'altra periferica sullo stesso canale potrebbe essere più adatta all'uso DMA.

    Ad esempio, se si dispone di un trasferimento I2C di massa ogni 5 ms, questo sembra un candidato migliore per DMA rispetto a un comando di debug occasionale che arriva su UART2.

  • L'impostazione e la gestione di DMA è un costo in sé. (Normalmente, la configurazione di DMA è considerata più complessa della configurazione di un normale trasferimento guidato dall'interruzione per carattere, a causa della gestione della memoria, di un numero maggiore di periferiche coinvolte, del DMA che utilizza gli interrupt stessi e della possibilità che sia necessario analizzare i primi caratteri all'esterno di DMA comunque, vedi sotto.)

  • DMA può utilizzare ulteriore potenza , poiché è ancora un altro dominio del core che deve essere sincronizzato. D'altra parte, è possibile sospendere la CPU mentre è in corso il trasferimento DMA, se il core lo supporta.

  • Il DMA richiede il funzionamento dei buffer di memoria (a meno che non si stia eseguendo un DMA da periferica a periferica), pertanto sono associati alcuni costi di memoria.

    (Il costo della memoria può anche essere presente quando si utilizzano gli interrupt per carattere, ma può anche essere molto più piccolo o svanire se i messaggi vengono interpretati immediatamente all'interno dell'interrupt.)

  • DMA produce una latenza perché la CPU viene avvisata solo quando il trasferimento è completo / metà completo (vedere le altre risposte).

  • Tranne quando si trasmettono dati in streaming da / verso un ring buffer, è necessario sapere in anticipo quanti dati verranno ricevuti / inviati .

    • Ciò può significare che è necessario elaborare i primi caratteri di un messaggio utilizzando gli interrupt per carattere: ad esempio, quando si interfaccia con un XBee, si legge prima il tipo e la dimensione del pacchetto e quindi si avvia un trasferimento DMA in un buffer allocato.

    • Per altri protocolli, ciò potrebbe non essere affatto possibile se utilizzano solo delimitatori di fine messaggio: ad esempio protocolli basati su testo che usano '\n'come delimitatore. (A meno che la periferica DMA non supporti la corrispondenza su un carattere.)

Come puoi vedere, ci sono molti compromessi da considerare qui. Alcuni sono legati a limitazioni hardware (numero di canali, conflitti con altre periferiche, corrispondenza sui caratteri), altri sono basati sul protocollo utilizzato (delimitatori, lunghezza nota, buffer di memoria).

Per aggiungere alcune prove aneddotiche, ho affrontato tutti questi compromessi in un progetto di hobby che utilizzava diverse periferiche con protocolli molto diversi. C'erano alcuni compromessi da fare, principalmente sulla base della domanda "quanti dati sto trasferendo e con che frequenza lo farò?". Questo in sostanza fornisce una stima approssimativa dell'impatto del semplice trasferimento guidato da interrupt sulla CPU. Ho quindi dato la priorità al suddetto trasferimento I2C ogni 5 ms sul trasferimento UART ogni pochi secondi che utilizzava lo stesso canale DMA. Un altro trasferimento UART avviene più spesso e con più dati d'altra parte ha la priorità su un altro trasferimento I2C che avviene più raramente. Sono tutti compromessi.

Naturalmente, anche l'uso del DMA ha dei vantaggi, ma non è quello che hai chiesto.


Grazie per la tua risposta dettagliata. Il MIDI sarà la parte più critica, quindi immagino che DMA sia adatto (anche se la velocità è bassa: 31250 baud). Ho abbastanza canali DMA, in seguito userò un altro STM32 quando uso 4 USART. Non ho bisogno di sospendere la CPU, poiché avrà un'alimentazione USB a 5 V e devo eseguire l'elaborazione tra i messaggi (per elaborare i messaggi nel loop principale). Ho un buffer di lettura di 256 byte e 256 byte di trasmissione. Posso aumentarlo in seguito, se necessario. STM32f103c8t6 ha 20 KB di RAM, l'eventuale STM che userò ha 192 KB.
Michel Keijzers,

E mi dai un'ottima idea su come migliorare. Finora ho sempre letto 1 byte e controllo continuamente quando viene ricevuto un messaggio (MIDI) completo. Ma posso leggere il primo byte e, a seconda di ciò, soprattutto la dimensione è nota e posso chiedere il resto. Questo mi è costato un altro piccolo buffer ma va bene.
Michel Keijzers,

La lettura di singoli byte con DMA è molto inefficiente. Per una latenza più bassa e una maggiore efficienza, l'uso di interruzioni per carattere fino a quando non si conosce la dimensione e quindi il passaggio a DMA sarebbe favorevole.
Jonas Schäfer,

Beh, ho avuto molti problemi con gli interrupt (senza DMA), penso che userò una ricezione DMA da 1 byte, e dopo che so quanti byte mi aspetto e farò una richiesta DMA per ottenere di più.
Michel Keijzers,

6
Questo è probabilmente un errore: dovresti correggere il tuo semplice codice di interruzione, senza DMA.
Chris Stratton,

10

L'uso di DMA di solito significa che non si sta più interrompendo ogni carattere, ma piuttosto solo dopo aver ricevuto (o trasmesso) un "buffer pieno" di caratteri. Ciò aumenta la latenza dell'elaborazione di tali caratteri: il primo carattere non viene elaborato fino a quando non viene ricevuto l'ultimo carattere nel buffer.

Questa latenza può essere una cosa negativa, specialmente in un'applicazione sensibile alla latenza come il MIDI, dove alcuni ms qua e là possono aggiungere gravi problemi di suonabilità per le esibizioni dal vivo.


Quello che faccio è ricevere 1 byte alla volta (quindi un buffer 'DMA' di 1 byte) e dopo ogni callback DMA di quel byte, per memorizzarlo in un buffer ad anello che gestisco manualmente. Nel mio ciclo principale intendo verificare la presenza di messaggi MIDI completi ed elaborarli.
Michel Keijzers,

3
Il DMA è in genere utilizzato per ottenere più byte e si interrompe solo quando sono stati tutti ricevuti. Interrompere dopo un solo byte è normale quando non si utilizza DMA, quindi mi chiedo: qual è il punto nella complicazione aggiuntiva dell'utilizzo di DMA per quello?
Steve Melnikoff,

5
@MichelKeijzers Quindi quello che fai è praticamente lo stesso che faresti in implementazioni pure guidate da interrupt. Quindi, non c'è alcun vantaggio nell'utilizzare DMA in questo caso e il tuo problema originale non è probabilmente risolto dal DMA ma dalla tua riscrittura del tuo codice (ISR, setup).
JimmyB,

@JimmyB ... grazie ... comunque grazie alla risposta di Jonas qui sotto, farò un miglioramento per leggere tanti byte dato che il messaggio è lungo. Lo so dopo aver ricevuto il primo byte (nella maggior parte dei casi). Di così sarà più utile usare DMA sugli interrupt.
Michel Keijzers,

8

DMA non sostituisce gli interrupt, in genere vengono utilizzati insieme! Se stai usando DMA per inviare dati su una UART, ad esempio, hai ancora bisogno di un interrupt per dirti quando l'invio è completo.


È vero, forse solo sull'STM32 il meccanismo di interruzione (puro non DMA) è un po 'goffo rispetto al DMA diretto.
Michel Keijzers,

2
@duskwuff Non proprio; puoi eseguire il polling per vedere quando il DMA è terminato e potresti volerlo fare perché uno dei motivi principali per l'utilizzo del DMA è non doversi preoccupare della porta seriale fino a quando il tuo programma non è in uno stato in cui può agire sul ricevuto dati. O per il DMA in uscita, puoi semplicemente eseguire il polling per vedere se è possibile aggiungere altro al buffer di invio.
Chris Stratton,

1
@MichelKeijzers: IDK il chip specifico, ma di solito l'alternativa a DMA non è letteralmente interruzioni, è programmata-IO (dove si usano le istruzioni della CPU per leggere / scrivere dati da / in un registro I / O). In un gestore di interrupt, in genere dovresti fare una lettura, e poi forse un'altra nel caso in cui un personaggio entrasse mentre stavi leggendo la prima, specialmente se ciò non innescherebbe un'altra interruzione. Oppure leggi fino a quando un buffer interno era vuoto se esiste un buffer di questo tipo. Ovviamente sono necessari più interrupt per PIO e configurarli in modo diverso.
Peter Cordes,

@ChrisStratton Un buon punto ... finora non ho verificato se è possibile trasmettere, trasmetto semplicemente qualcosa, senza verificare se è ok. Probabilmente in caso contrario, riproverò più tardi.
Michel Keijzers,

@PeterCordes Sembra che STM32 abbia abbastanza interrupt per DMA e leggo ogni volta solo 1 byte. Anche il più semplice STM32 (F103c8t6) ha abbastanza porte / interruzioni DMA disponibili.
Michel Keijzers,

5

L'uso di DMA introduce alcune domande e sfide interessanti oltre a tutte le altre considerazioni sull'uso delle periferiche UART. Vi darò alcuni esempi: supponiamo che il vostro uC sia seduto su un bus RS485 (o qualsiasi altra cosa) con altri dispositivi. Ci sono molti messaggi sul bus, alcuni sono destinati al tuo uC, altri no. Supponiamo inoltre che questi vicini di bus parlino tutti di un protocollo dati diverso, il che implica che le lunghezze dei messaggi sono diverse.

Alcune domande che sorgono solo quando si utilizza DMA sono:

  • quando interrompo?
    • Ai DMA piace davvero interrompere solo quando hanno trasferito una quantità preimpostata di dati.
    • Cosa fai se non ricevi mai abbastanza dati per attivare un interrupt DMA?
  • Cosa succede se si riceve un messaggio parziale solo quando il DMA si interrompe?
  • Come sono i tuoi buffer RX? Sono lineari o circolari?
    • DMA può essere un partecipante al buffer circolare indisciplinato, nel senso che obbedisce solo al limite dell'indirizzo, ma non ha problemi a passare oltre gli altri puntatori nel sistema del buffer circolare.

Comunque, solo spunti di riflessione.


Grazie per queste considerazioni. Attualmente ricevo sempre 1 byte e lo memorizzo in un buffer ad anello, poiché in effetti i miei messaggi (MIDI) possono avere lunghezze diverse e non so quale sarà il prossimo. Nel mio ciclo principale controllo la presenza di messaggi completi per elaborarli (e, se completo, li rimuovo dal buffer dell'anello). Quindi riceverò sempre abbastanza dati (a meno che non mi manchino i byte, devo controllarli). Il mio buffer RX è solo 1 byte, ma lo copio su un buffer circolare / circolare. Non ho fatto controlli se è pieno (è necessario aggiungerlo).
Michel Keijzers,

Ehi, non preoccuparti. Sono sicuro che la tua applicazione sarà ben programmata. Come altri hanno già detto, DMA è fantastico, ma non è gratuito è tutto. Introduce considerazioni extra nel sistema che non esistono se riesci a scappare senza usarlo.
pgvoorhees,

bene spero, sono ancora un principiante.
Michel Keijzers,

3

Dal lato della ricezione (come ricordo) DMA termina in corrispondenza di una corrispondenza di caratteri o al conteggio dei terminali. Alcuni protocolli e molte applicazioni interattive non si adattano facilmente a questo modello e devi davvero gestire le cose carattere per carattere. Le tecniche DMA possono anche essere fragili se il collegamento di comunicazione è inaffidabile, perdere un singolo carattere nel flusso può facilmente rovinare la tua macchina a stati DMA.


In effetti ricevo byte per byte e lo copio manualmente in un buffer ad anello per elaborarlo in un secondo momento.
Michel Keijzers,

1

Ho usato STM32CubeMx / HAL su un paio di progetti e ho scoperto che il software di gestione UART che genera ha delle carenze evidenti sul lato di ricezione.

Al momento della trasmissione, normalmente si desidera inviare un blocco di dati o una riga di testo. In questo caso sai quanto tempo dura il trasferimento dei dati e quindi usare il DMA è una soluzione ovvia. Si ottiene un interruzione una volta completato il trasferimento e si può utilizzare la funzione di richiamata completa di UART TX per indicare al proprio codice principale che la trasmissione è completa e che è possibile inviare un altro blocco di dati.

Quando si tratta di ricezione dei dati, tutte le funzioni fornite da ST presuppongono che si sappia quanti caratteri verranno inviati dal dispositivo di invio prima che inizi a inviare. Normalmente questo non è noto. La funzionalità di interruzione inserisce i dati ricevuti in un buffer e indica che ci sono dati disponibili solo quando è stato ricevuto il numero predefinito di caratteri. Se si tenta di utilizzare la funzionalità DMA o di interruzione per ricevere dati impostando trasferimenti sequenziali a singolo carattere, il tempo di impostazione per ciascuno di questi significherà che si perderanno i caratteri a qualsiasi velocità diversa da quella dei dati più lenta (la velocità di trasmissione che si dovrà iniziare a perdere i dati dipenderà dalla velocità di clock del processore) e caricherà eccessivamente il processore, senza lasciare cicli di istruzioni per qualsiasi altra elaborazione

Per ovviare a questo ho scritto la mia funzione di gestore di interrupt che memorizza i dati in un piccolo buffer circolare locale e imposta un conteggio che viene letto dal codice principale (un semaforo di conteggio RTOS) per indicare che ci sono dati ricevuti pronti. Il codice principale può quindi raccogliere i dati da questo buffer a suo piacimento, non importa se c'è qualche ritardo nella raccolta dei dati a condizione che il buffer locale non trabocchi prima che i dati vengano raccolti.


Faccio esattamente lo stesso (penso). Ho letto 1 byte alla volta e l'ho memorizzato in un buffer ciclico, e ho intenzione di controllare nel ciclo principale per i messaggi completi. Può essere migliorato un po 'però.
Michel Keijzers,

Pensi che potrei incontrare il problema che la configurazione del DMA ogni volta sovraccaricherà il mio processore / caratteri mancanti a 31.250 baud?
Michel Keijzers,

1
Finché si imposta il DMA per trasferire un numero di caratteri alla volta, questo non sarà un problema. Ho 4 UART con 115200 e versioni successive e I2C usando DMA senza problemi. Le trasmissioni UART sono tutte ~ 20 byte o più lunghe. Il problema stava utilizzando DMA per la ricezione sull'UART (processore L4 a 80 MHz, 9600 baud).
uɐɪ

Attualmente l'ho impostato su 1 byte alla volta, ma posso migliorarlo (facendo il primo byte e quindi controllare quanti ulteriori byte sono necessari).
Michel Keijzers,
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.