Hallo,
ich moechte auf einem XMega ueber den UART debug messages mit beliebiger
Laenge ausgeben.
Um Verzoegerungen durch das Senden am UART zu vermeiden, moeche ich fuer
die transfers DMA nutzen.
Basierend auf der AVR Appnote AVR1522 und dem Beispielcode habe ich
bereits eine Loesung entwickelt die zwar bereits Daten ueber den UART
verschickt, leider jedoch auch sehr häufig einzelne Bytes verliert.
Mein Grundgedanke ist folgender:
Ich habe einen Buffer mit 100 bytes in den ich die Debug-Meldung
hineinschreibe. Die Debug Meldung kann dabei zwar maximal 99 bytes lang
sein (wegen dem \0 am Ende des Strings), ueblicherweise ist sie aber
viel kuerzer.
Sobald die Daten im Buffer sind, setze ich einen neuen DMA transfer auf,
der den Inhalt des Puffers mit der gegebenen Laenge des jeweiligen
Debug-Strings an den UART schicken soll.
Schicke ich mehere debug Meldungen hintereinander raus, so muss ich
jedoch warten bis der DMA transfer beendet ist. Schliesslich wuerde ich
sonst den Buffer ueberschreiben, der in dem Augenblick noch mittels dem
letzten DMA Transfer zum UART geschickt wird.
Wie oben erwaehnt verliert meine debug-Ausgabe jedoch trotz warten immer
wieder einzelne Bytes. Ich schicke also etwa ein "test-test-test" und
beim UART kommt dann lediglich ein "tst-test-test" raus.
Das Warten realisiere ich derzeit mit
Weiss jemand, waran das liegt und wie ich das Problem in den Griff
bekomme ?
Mein code basiert zum groessten Teil auf dem dma_driver Code von Atmel
aua der Appnote AVR1522:
1
#define DBG(...) \
2
do { \
3
while (DMA_CH_IsOngoing(DMA_TX_CHANNEL) || DMA_CH_IsPending(DMA_TX_CHANNEL)); \
Hallo Stefan,
hier meine Theorie:
Du kannst auf DMA_StartTransfer() verzichten, da der Trigger
(DMA_CH_TRIGSRC_USARTC0_DRE_gc) vor der Aktion normalerweise immer TRUE
ist.
D.h. der DMA-Vorgang wird schon bei DMA_Enable() gestartet!
Der danach folgende StartTransfer haut dann mehr oder weniger asynchron
dazwischen und hat zur Folge, dass du ein Byte verlierst.
Ich nutze übrigens auch den DMA-Controller zur Ausgabe auf dem UART,
starte den Vorgang aber immer nur mit CHEN und nie mit TRFREQ. Und es
klappt...
Gruß vom
Xmegaman
Hallo Heiko !
Heiko vG schrieb:> Hallo Stefan,>> hier meine Theorie:>> Du kannst auf DMA_StartTransfer() verzichten, da der Trigger> (DMA_CH_TRIGSRC_USARTC0_DRE_gc) vor der Aktion normalerweise immer TRUE> ist.> D.h. der DMA-Vorgang wird schon bei DMA_Enable() gestartet!> Der danach folgende StartTransfer haut dann mehr oder weniger asynchron> dazwischen und hat zur Folge, dass du ein Byte verlierst.
Ich habe jetzt ganz auf den Atmel Code verzichtet und alles selbst
implementiert.
Das Resultat: Weniger Code wird ausgefuehrt und es funktioniert ;)
Ich habe nicht mehr weiter nachgeschaut weshalb der obige Code nicht
funktioniert hat.
Jedenfalls habe ich jetzt den DMA channel so aufgesetzt, dass er fuer
jede transaction die src und dst Addressen reloaded.
Die bursts (1 byte zum UART.DATA register) triggert er selbst ueber
DMA_CH_TRIGSRC_USARTC0_DRE_gc.
Um einen Transfer zu starten setze ich auf dem DMA channel einfach das
enable bit.
Ob ein transfer fertig ist, erkenne ich am CHBUSY bit im CTRLB Register
des DMA channels.
Waehrend ich Atmel dafuer dankbar bin, dass sie zu ihren Appnotes auch
example Code hinzufuegen, kann ich nur jedem empfehlen den Code
lediglich als Referenz fuer die eigene Implementation zu verwenden.
> Ich nutze übrigens auch den DMA-Controller zur Ausgabe auf dem UART,> starte den Vorgang aber immer nur mit CHEN und nie mit TRFREQ. Und es> klappt...
Ja, das mache ich jetzt auch so. Ist aus meiner Sicht auch der einzig
richtige Weg, da TRFREQ ja lediglich fuer das triggern eines Transfers
(und nicht einer vollstaendigen transaction) gedacht ist.
Vielmehr triggern sich in unsrem Fall die einzelnen bursts ja von selbst
durch DMA_CH_TRIGSRC_USARTC0_DRE_gc.
Was mich jedoch noch stoert ist folgendes:
Sendet man zwei Messages hintereinander, so muss man beim Senden der
zweiten Nachricht erst recht wieder warten bis die DMA transaction der
letzten Nachricht abgeschlossen ist.
Ansonsten wuerde man vorzeitig in den gemappten DMA buffer schreiben und
kauderwelsch ausgeben.
Zumindest begrenzte Abhilfe wuerde aus meiner Sicht etwa ein Ringbuffer
oder double buffering bieten.
Fuer ersteres wuerdest du einfach immer deine Nachrichten den den
gemappten DMA buffer anhaengen.
Wuerde man ueber das Pufferende schreiben, so faehrt man einfach am
Anfang des Buffers fort (Ringbuffer).
Natuerlich darf man noch nicht gesendete Nachrichten nicht
ueberschreiben.
Es darf also nicht zu einem Ringbuffer-Overrun kommen.
Jedenfalls waere mein Idee dann einen Interrupt Handler fuer den DMA
Channel aufzusetzen (transaction complete), in welcher automatisch die
naechste DMA transaction getriggert wird.
Damit koennte man dann ohne jegliches Warten messages senden solange der
Ringbuffer nicht voll ist.
Hast du etwas in dieser Richtung schon implementiert ?
Ich muss erst mal sehen obs nicht auch ohne Ringbuffer in meiner
Applikation geht.
lg,
Stef
stefan m. schrieb:> Ich habe jetzt ganz auf den Atmel Code verzichtet und alles selbst> implementiert.> Das Resultat: Weniger Code wird ausgefuehrt und es funktioniert ;)
Hat den weiteren Vorteil, dass man wirklich versteht, was passiert ;)
> Ich habe nicht mehr weiter nachgeschaut weshalb der obige Code nicht> funktioniert hat.
Je länger ich darüber nachdenke, desto sicherer bin ich, dass das
nachträgliche Anschubsen mittels DMA_StartTransfer() absolut
kontraproduktiv ist. Der Witz besteht bei Quellen, die aus der
xmega-Peripherie kommen, ja gerade darin, dass diese selbst den Transfer
anstoßen!
Das manuelle Triggern wird nur für DMA-Tranfers gebraucht, die selbst
keinen Trigger auslösen können, z. B. blockweise Daten vom internen ins
externe RAM kopieren.
> Jedenfalls habe ich jetzt den DMA channel so aufgesetzt, dass er fuer> jede transaction die src und dst Addressen reloaded.
Ich denke mal dst reload ist sogar überflüssig, da kein
Adress-Inkr./Dekr. benötigt wird.
> Die bursts (1 byte zum UART.DATA register) triggert er selbst ueber> DMA_CH_TRIGSRC_USARTC0_DRE_gc.
Kann auch nur so funktionieren, da der UART trotz FIFO nur 1 Byte pro
Trigger verarbeiten kann (Stichwort Handshake).
> Um einen Transfer zu starten setze ich auf dem DMA channel einfach das> enable bit.> Ob ein transfer fertig ist, erkenne ich am CHBUSY bit im CTRLB Register> des DMA channels.
Achtung: Hier kann eine weitere Falle lauern. Obwohl der DMA-Vorgang
fertig ist, ist der UART u. U. noch am Senden, was ja deutlich langsamer
ist (je nach Baudrate) als der DMA-Zufgriff. Will man nämlich den UART
nach dem Transfer umprogrammieren oder ein DTR-Signal o. ä. erzeugen,
muss man auch noch warten bis der UART kopmplett fertig ist. (Ich weiß,
das ist bei dir nicht der Fall).
> Waehrend ich Atmel dafuer dankbar bin, dass sie zu ihren Appnotes auch> example Code hinzufuegen, kann ich nur jedem empfehlen den Code> lediglich als Referenz fuer die eigene Implementation zu verwenden.
Und man sollte IMMER ins aktuellste Datenblatt schauen...
> ... Ringbuffer ...>> Hast du etwas in dieser Richtung schon implementiert ?> Ich muss erst mal sehen obs nicht auch ohne Ringbuffer in meiner> Applikation geht.
Ich sehe ein Problem dabei: Du müsstest mit unlimited repeat arbeiten.
Das geht solange gut, wie immer messages im Buffer stehen. Was aber wenn
der Buffer 'leer' ist (d. h. alle messages abgearbeitet) ?
Aber eigentlich ist genau für dieses Probelm der Double Buffer Modus
gedacht. Natürlich kann es hier zum Busy Waiting kommen, aber immerhin
kann man nahtlos messages rausschicken.
Gruß
Xmegaman
Heiko vG schrieb:>> ... Ringbuffer ...>>>> Hast du etwas in dieser Richtung schon implementiert ?>> Ich muss erst mal sehen obs nicht auch ohne Ringbuffer in meiner>> Applikation geht.>> Ich sehe ein Problem dabei: Du müsstest mit unlimited repeat arbeiten.> Das geht solange gut, wie immer messages im Buffer stehen. Was aber wenn> der Buffer 'leer' ist (d. h. alle messages abgearbeitet) ?
Nein, ich wuerde bei jedem Schreibvorgang in den Ringbuffer immer nur
genau eine DMA transaction starten (und das auch nur wenn nicht bereits
eine laeuft).
Falls die Nachricht ueber die obere Grenze des Ringpuffers hinaus geht,
wird nur der aktuelle Chunk vor dem Bufferende transferiert.
Damit das ganze funktioniert, gibt es zusaetzlich einen (high-level)
transaction-complete interrupt handler.
In dem werden die ringbuffer Datenstrukturen upgedatet (etwa wie viele
bytes noch frei oder noch zu senden sind) und falls noch Daten im
Ringpuffer sind, wird wieder (gleich wie oben) eine neue DMA transaction
fuer einen chunk angestossen.
Nehmen wir an, dass mein Ringbuffer 20 bytes gross ist.
Im ersten Fall moechte ich lediglich ein "Hallo!" (6 bytes) ausgeben,
danach folgen keine weiteren Daten.
In diesem Fall wird das "Hallo!" sofort in den Ringpuffer geschrieben
(es sind ja noch 20 bytes frei) und eine DMA transaction wird gestartet.
Irgendwann ist die transaction complete und im interrupt werden die
Datenstrukturen upgedatet (free=20, tosend=0).
Nachdem es keine Daten mehr zu senden gibt, wird keine neue DMA
transaction durch den interrupt handler angestossen.
Im zweiten Fall moechte ich 3 mal hintereinander "Hallo!" ausgeben.
Das erste "Hallo!" wird in den Ringpuffer kopiert (free=14, tosend=6)
und eine DMA transaction fuer 6 bytes wird angestossen.
Waehrend die transaction laeuft, schreibe ich noch zwei weitere "Hallo!"
in den Ringpuffer: zuerst (free=8, tosend=12), dann (free=2, tosend=18).
Das geht ohne Warten, weil im Ringpuffer ja genug Platz ist.
Beim Schreiben in den Ringbuffer werden keine neue DMA transactions
gestartet, weil ja noch die erste DMA transaction (fuer 6 byte) im Gange
ist.
Irgendwann ist die DMA transaction fertig und wir landen im transaction
complete interrupt.
Dort werden die Datenstrukturen upgedatet: (free=8, tosend=12).
Nachdem nun noch 12 bytes "Hallo!Hallo!" zu senden sind, wird eine neue
DMA transaction fuer 12 bytes angeworfen).
Ist diese fertig (und es sind keine Daten mehr im Ringbuffer), so wird
schliesslich keine neue DMA transaction mehr gestartet (es gaebe ja
nichts mehr zu senden).
Im dritten Fall moechte ich 4 mal hintereinander "Hallo!" senden.
Das funktioniert gleich wie im vorherigen Fall, jedoch ist nach dem 3.
Schreibvorgang der Ringbuffer so voll, dass das letzte "Hallo!" nicht
mehr in den Puffer passt (free=2, tosend=18).
Man koennte zwar die 2 bytes noch schreiben und den Rest spaeter, aber
einfacherweise lassen wir das und warten (busy-waiting) stattdessen bis
wieder zumindest 6 bytes im Ringpuffer frei sind.
Nach dem ersten transaction complete interrupt ist im Ringpuffer wieder
genug Platz und das 4. "Hallo!" kann in den Ringbuffer geschrieben
werden (das "Ha" steht am Ende des Buffers, das "llo!" am Anfang.
Wie oben werden durch den transaction complete interrupt handler weitere
transactions angestossen. Fuer das "[......]Ha" und das "llo!" gibt es
aber eine eigene transaction, da der Lesevorgang wieder am Beginn des
Puffers stattfinden soll (ist ja ein Ringbuffer).
Damit lassen sich nahtlos messages schicken. Je groesser man den
Ringbuffer macht, dest unwahrscheinlicher wird es, dass man irgendwann
busy-waiten muss weil der Ringbuffer voll ist.
> Aber eigentlich ist genau für dieses Probelm der Double Buffer Modus> gedacht. Natürlich kann es hier zum Busy Waiting kommen, aber immerhin> kann man nahtlos messages rausschicken.
Aus meiner Sicht geht das nur solange ich nicht mehr als 2 messages
schicken will.
Erste message: channel1, zweite message: channel2.
Ist das Senden der ersten Message fertig, wird automatisch der transfer
der zweiten message gestartet.
Will ich aber 3 oder noch mehr Nachrichten hintereinander schicken, muss
ich erst recht wieder warten. Nach der ersten message ist die DMA
transaction auf channel1 im gange. Bei der zweiten message ist noch
immer die DMA transaction auf channel1 im Gange, ich kann aber schon
channel2 aufsetzen. Bei der dritten message ist noch immer die erste
transaction im Gange, die zweite transaction schon vorbereitet und ich
muesste warten, bis die erste transaction fertig ist.
Mit der ringbuffer-Loesung hast du dieses Problem nicht so schnell:
* man kann ihn mit mehreren messages vollschreiben (verfuegbarer Platz
wird weniger)
* der Puffer wird gleichzeitig wieder durch die DMA transactions
geleert (verfuegbarer Platz wird mehr)
* bei vielen messages im Ringbuffer wird nicht jede message einzeln
uebertragen, sondern stattdessen lediglich eine grosse DMA transaction
durchgefuehrt (bei einem Uebergang im Ringbuffer sind es zwei
transactions)
* die Groesse des Ringbuffers ist frei waehlbar
Aktuell ist mir jedoch (unabhaengig von der Ringbuffer Idee) was
eigenartiges aufgefallen:
Ich werfe eine DMA transaction an und habe den transaction complete
interrupt aktiviert.
Wurden meine Daten mittels der DMA transaction transferiert, so sollte
sich der DMA channel deaktivieren und ich einen interrupt bekommen.
Stattdessen wird mein interrupt handler aber endlos aufgerufen, obwohl
der channel bereits deaktiviert (DMA_CH_ENABLE_bp clear) und nicht mehr
busy (DMA_CH_CHBUSY_bp clear) ist.
Im Datenblatt kann ich nichts zu dem Verhalten finden. Muss ich da die
interrupt flag selbst loeschen ? Ist das ein bug ?
lg,
Stef
stefan m. schrieb:> Stattdessen wird mein interrupt handler aber endlos aufgerufen, obwohl> der channel bereits deaktiviert (DMA_CH_ENABLE_bp clear) und nicht mehr> busy (DMA_CH_CHBUSY_bp clear) ist.> Im Datenblatt kann ich nichts zu dem Verhalten finden.
Ich hab nun doch was bei den flags gefunden:
"Since the DMA Channel Transaction Complete Channel Error Interrupt
share interrupt address with DMA Channel Error Interrupt, the TRNIF will
not be cleared when the interrupt vector is executed. This flag is
cleared by writing a one to the bit location."
Demnach muss man zwei Dinge beachten:
1) bei einem interrupt muss man die flag selbst loeschen
2) die flag geloescht, indem man das jeweilige bit setzt (und nicht
loescht)
lg,
stef
Super, wenn's mit Ringbuffer klappt!
Weiterer Vorteil: Du brauchst nur einen DMA-Channel anstatt zweien.
> Ich hab nun doch was bei den flags gefunden:>> "Since the DMA Channel Transaction Complete Channel Error Interrupt> share interrupt address with DMA Channel Error Interrupt, the TRNIF will> not be cleared when the interrupt vector is executed. This flag is> cleared by writing a one to the bit location.">> Demnach muss man zwei Dinge beachten:> 1) bei einem interrupt muss man die flag selbst loeschen> 2) die flag geloescht, indem man das jeweilige bit setzt (und nicht> loescht)
Wie gesagt, Datenblatt studieren hilft ungemein.
Manchmal ist es aber auch irritierend. Es fehlt z. B. ein eindeutiger
Hinweis zum TRFREQ-Flag (s. o.).
Hat Spaß gemacht mit dir zu fachsimpeln, Stef!
Gruß
Xmegaman
Heiko vG schrieb:> Weiterer Vorteil: Du brauchst nur einen DMA-Channel anstatt zweien.
Stimmt, das natuerlich auch ;)
> Manchmal ist es aber auch irritierend. Es fehlt z. B. ein eindeutiger> Hinweis zum TRFREQ-Flag (s. o.).
Ja, manchmal muss man dann eben doch einfach probieren.
Ich hab die Atmel Datenblaetter trotzdem lieb gewonnen. Das meiste wird
nach wie vor kurz und buendig beschrieben.
In einigen Faellen sind mir da schon ganz andere Datenblaetter ueber den
Weg gekommen ;)
> Hat Spaß gemacht mit dir zu fachsimpeln, Stef!
Ebenfalls !
War sicher nicht mein letzter xmega post ;)
lg und schoenes wochenende noch,
Stef
Hallo,
ich habe deine Beiträge gelesen und stecke bei dem selben Problem bzw.
einer sinnvollen Implementierung eines Ringbuffers. Möchtest du deinen
Code hochladen, sodass auch andere Forenmitglieder den Code aus der
Appnote und von dir als Vorlage verwenden können? Mir persönlich würde
er viel helfen, da ich leider keinen JTAG debugger habe und somit nur
per trial und error zum Ziel kommen würde.
Vielen herzlichen Dank!
Elektrofreak
Hallo,
Mats Marcus schrieb:> Möchtest du deinen> Code hochladen, sodass auch andere Forenmitglieder den Code aus der> Appnote und von dir als Vorlage verwenden können?
ich stelle den Code hier unter der GPLv2 zur Verfuegung und wuerde mich
freuen an evt. Verbesserungen teilhaben zu koennen.
Aktuell hat der Code noch den Nachteil, dass er nicht verlaesslich aus
einer ISR heraus aufgerufen werden kann.
Ich habe zwar eine entsprechende Funktion erstellt
(isr_write_to_txringbuf()), da sie nicht warten kann, arbeitet sie
jedoch nur nach dem "best effort" Prinzip.
Hier ist der (hoffentlich vollstaendige) Code:
Hallo, bin auch gerade beim Einarbeiten. Meine Idee zum Transfer einer
Anzahl Pufferbytes zum USART wäre einfach die Initialisierung eines
Blocktransfers mit inkrementierender Source- und fixer USART-Data
Adresse... Synchronisiert sich das hinsichtlich Speed dann selber oder
geht das so prinzipiell nicht?
if(usart_tx_is_complete(&USARTF0)==true)//wait for the byte to leave the shift reg
34
{
35
break;
36
}
37
}
38
}
ein dma_channel_enable(DMA_F_TX_CHANNEL); startet den transfer. wenn
USARTF0 DRE interrupt feuert wird das nächste bit nach USARTF0.DATA
gepackt. wenn USART_TX_BUFF_SIZE errweicht wird, dann ruft er den
callback aus der dma ISR auf.