Forum: Mikrocontroller und Digitale Elektronik XMega DMA transfers zu UART


von stefan s. (stef89)


Lesenswert?

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
1
while (DMA_CH_IsOngoing(DMA_TX_CHANNEL) || DMA_CH_IsPending(DMA_TX_CHANNEL));
2
loop_until_bit_is_set(USART.STATUS, USART_DREIF_bp);

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)); \
4
            loop_until_bit_is_set(USART.STATUS, USART_DREIF_bp); \
5
            uart_dma_send(snprintf_P((char *)uart_tx_buf, TX_BUFSIZE, __VA_ARGS__)); \
6
    } while(0)
7
8
#define DMA_Enable()        ( DMA.CTRL |= DMA_ENABLE_bm )
9
10
#define TX_BUFSIZE          100
11
#define DMA_TX_CHANNEL      &DMA.CH0
12
13
volatile uint8_t uart_tx_buf[TX_BUFSIZE];
14
15
// taken from Atmel dma_driver
16
void DMA_SetupBlock(volatile DMA_CH_t * channel,
17
                     const void * srcAddr,
18
                     DMA_CH_SRCRELOAD_t srcReload,
19
                     DMA_CH_SRCDIR_t srcDirection,
20
                     void * destAddr,
21
                     DMA_CH_DESTRELOAD_t destReload,
22
                     DMA_CH_DESTDIR_t destDirection,
23
                     uint16_t blockSize,
24
                     DMA_CH_BURSTLEN_t burstMode,
25
                     uint8_t repeatCount,
26
                     uint8_t useRepeat )
27
{
28
    channel->SRCADDR0 = (( (uint16_t) srcAddr) >> 0*8 ) & 0xFF;
29
    channel->SRCADDR1 = (( (uint16_t) srcAddr) >> 1*8 ) & 0xFF;
30
    channel->SRCADDR2 = 0;
31
32
    channel->DESTADDR0 = (( (uint16_t) destAddr) >> 0*8 ) & 0xFF;
33
    channel->DESTADDR1 = (( (uint16_t) destAddr) >> 1*8 ) & 0xFF;
34
    channel->DESTADDR2 = 0;
35
36
    channel->ADDRCTRL = (uint8_t) srcReload | srcDirection |
37
                                  destReload | destDirection;
38
    channel->TRFCNT = blockSize;
39
    channel->CTRLA = ( channel->CTRLA & ~( DMA_CH_BURSTLEN_gm | DMA_CH_REPEAT_bm ) ) |
40
                      burstMode | ( useRepeat ? DMA_CH_REPEAT_bm : 0);
41
42
    if ( useRepeat ) {
43
        channel->REPCNT = repeatCount;
44
    }
45
}
46
47
inline void DMA_EnableSingleShot(volatile DMA_CH_t * channel)
48
{
49
    channel->CTRLA |= DMA_CH_SINGLE_bm;
50
}
51
52
inline void DMA_SetTriggerSource(volatile DMA_CH_t * channel, uint8_t trigger)
53
{
54
    channel->TRIGSRC = trigger;
55
}
56
57
inline void DMA_EnableChannel(volatile DMA_CH_t * channel)
58
{
59
    channel->CTRLA |= DMA_CH_ENABLE_bm;
60
}
61
62
void DMA_StartTransfer(volatile DMA_CH_t * channel)
63
{
64
    channel->CTRLA |= DMA_CH_TRFREQ_bm;
65
}
66
67
uint8_t DMA_IsOngoing(void)
68
{
69
    uint8_t flagMask;
70
    flagMask = DMA.STATUS & 0xF0;
71
    return flagMask;
72
}
73
74
uint8_t DMA_CH_IsOngoing(volatile DMA_CH_t * channel)
75
{
76
    uint8_t flagMask;
77
    flagMask = channel->CTRLB & DMA_CH_CHBUSY_bm;
78
    return flagMask;
79
}
80
81
uint8_t DMA_CH_IsPending(volatile DMA_CH_t * channel)
82
{
83
    uint8_t flagMask;
84
    flagMask = channel->CTRLB & DMA_CH_CHPEND_bm;
85
    return flagMask;
86
}
87
88
uint8_t DMA_ReturnStatus_non_blocking(volatile DMA_CH_t * channel)
89
{
90
    uint8_t relevantFlags;
91
    relevantFlags = channel->CTRLB & (DMA_CH_ERRIF_bm | DMA_CH_TRNIF_bm);
92
    //relevantFlags = channel->CTRLB & DMA_CH_CHBUSY_bm;
93
    return relevantFlags;
94
}
95
96
void DMA_ResetChannel(volatile DMA_CH_t * channel)
97
{
98
    channel->CTRLA &= ~DMA_CH_ENABLE_bm;
99
    channel->CTRLA |= DMA_CH_RESET_bm;
100
    channel->CTRLA &= ~DMA_CH_RESET_bm;
101
}
102
103
inline void DMA_SetBlockSize(volatile DMA_CH_t * channel, uint16_t blocksize)
104
{
105
    channel->TRFCNT = blocksize;
106
}
107
108
void uart_init(void)
109
{
110
    USART_PORT.DIRSET = PIN3_bm;    // pin PC3 (TXD0) as output
111
    USART.CTRLC = USART_CHSIZE_8BIT_gc | USART_PMODE_DISABLED_gc;   // 8N1
112
    USART.BAUDCTRLA = (USART_BSEL & 0xff);                          // baudrate
113
    USART.BAUDCTRLB = (USART_BSCALE << USART_BSCALE_gp) | ((USART_BSEL>>8) & 0x0f); // baudrate
114
    USART.CTRLB |= USART_TXEN_bm;   // enable TX
115
}
116
117
void uart_dma_init(void)
118
{
119
    uart_init();
120
 DMA_SetTriggerSource(DMA_TX_CHANNEL, DMA_CH_TRIGSRC_USARTC0_DRE_gc);    // copy as soon as the uart data register can be written
121
    DMA_EnableSingleShot(DMA_TX_CHANNEL);   // one uart trigger should only transfer one burst (i.e. 1 byte)
122
}
123
124
// send what's in uart_tx_buffer
125
int uart_dma_send(uint16_t len)
126
{
127
    // we only support sending up to TX_BUFSIZE bytes
128
    if (len>TX_BUFSIZE)
129
        return -1;
130
    DMA_SetupBlock(DMA_TX_CHANNEL,  // DMA channel
131
        (void *)uart_tx_buf,
132
        DMA_CH_SRCRELOAD_NONE_gc,
133
        DMA_CH_SRCDIR_INC_gc,
134
        (void *) &USART.DATA,       // Destination
135
        DMA_CH_DESTRELOAD_BLOCK_gc, // Reload address at end of each block transfer
136
        //DMA_CH_DESTRELOAD_BURST_gc,
137
        DMA_CH_DESTDIR_FIXED_gc,    // Neither increment or decrement index
138
        len,                        // Block size
139
        DMA_CH_BURSTLEN_1BYTE_gc,
140
        0,                          // Perform once
141
        0                           // Disable repeat
142
    );
143
    DMA_EnableChannel(DMA_TX_CHANNEL);
144
    DMA_StartTransfer(DMA_TX_CHANNEL);
145
146
    return len;
147
}
148
149
int main(void)
150
{
151
    DMA_Enable();
152
    uart_dma_init();
153
154
    DBG(PSTR("test\n"));
155
    DBG(PSTR("test1\n"));
156
    DBG(PSTR("test-test-test\n"));
157
158
   while(1);
159
}

lg,
stef

von Heiko V. (xmegaman)


Lesenswert?

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

von stefan s. (stef89)


Lesenswert?

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

von Heiko V. (xmegaman)


Lesenswert?

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

von stefan s. (stef89)


Lesenswert?

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

von stefan s. (stef89)


Lesenswert?

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

von Heiko V. (xmegaman)


Lesenswert?

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

von stefan s. (stef89)


Lesenswert?

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

von Mats M. (elektrofreak)


Lesenswert?

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

von stefan s. (stef89)


Lesenswert?

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:
1
#define DEBUG
2
#define F_CPU         32000000UL    // 32 MHz (after crystal clock initialization)
3
#define LED_PORT      PORTE
4
#define USART_PORT       PORTC      // USART connected to USB bridge
5
#define USART         USARTC0
6
#define USART_BSEL      2094      // 115200 baud at 32MHz sys clock
7
#define USART_BSCALE    (-7)
8
9
10
#ifdef DEBUG
11
#define DBG(...)  \
12
    do {    \
13
      printf_P(__VA_ARGS__); \
14
  } while(0)
15
#else
16
#define DBG(...)
17
#endif
18
19
#define ENTER_CRITICAL_REGION( ) saved_sreg = SREG; \
20
                                     cli();
21
#define LEAVE_CRITICAL_REGION( ) SREG = saved_sreg;
22
23
#define TX_RINGBUFSIZE    0xff
24
#define DMA_TX_CHANNEL     DMA.CH0
25
26
#include <avr/io.h>
27
#include <avr/interrupt.h>
28
#include <avr/pgmspace.h>
29
#include <avr/eeprom.h>
30
#include <util/delay.h>
31
#include <util/atomic.h>
32
#include <math.h>
33
#include <string.h>
34
#include <stdio.h>
35
#include <stdlib.h>
36
37
int uart_putchar(char c, FILE *unused);
38
int uart_dma_putchar(char c, FILE *unused);
39
int isr_uart_dma_putchar(char c, FILE *unused);
40
41
FILE mystdout = FDEV_SETUP_STREAM(uart_putchar, NULL, _FDEV_SETUP_WRITE);
42
FILE mystdout_dma = FDEV_SETUP_STREAM(uart_dma_putchar, NULL, _FDEV_SETUP_WRITE);
43
FILE mystdout_isr_dma = FDEV_SETUP_STREAM(isr_uart_dma_putchar, NULL, _FDEV_SETUP_WRITE);
44
45
volatile uint8_t saved_sreg;
46
47
volatile struct {
48
  char buf[TX_RINGBUFSIZE];
49
  uint16_t read_index;          // start of area currently used for DMA reading
50
  uint16_t write_index;          // start of area that can be written to
51
  uint16_t new_data_len;          // length of new data to transfer
52
  uint16_t free;              // number of free bytes that can be written
53
  uint8_t isr_done;
54
} tx_ringbuf;
55
56
void uart_init(void)
57
{
58
  USART_PORT.DIRSET = PIN3_bm;  // pin PC3 (TXD0) as output
59
  USART.CTRLC = USART_CHSIZE_8BIT_gc | USART_PMODE_DISABLED_gc;  // 8N1
60
  USART.BAUDCTRLA = (USART_BSEL & 0xff);              // baudrate
61
  USART.BAUDCTRLB = (USART_BSCALE << USART_BSCALE_gp) | ((USART_BSEL>>8) & 0x0f);  // baudrate
62
  USART.CTRLB |= USART_TXEN_bm;  // enable TX
63
}
64
65
void uart_send(uint8_t *buffer, uint16_t len)
66
{
67
  uint16_t i;
68
69
  for (i=0; i<len; i++)
70
  {
71
    loop_until_bit_is_set(USART.STATUS, USART_DREIF_bp);
72
    USART.DATA=buffer[i];
73
  }
74
}
75
76
int uart_putchar(char c, FILE *unused)
77
{
78
  loop_until_bit_is_set(USART.STATUS, USART_DREIF_bp);
79
  USART.DATA=c;
80
  
81
  return 0;
82
}
83
84
void init_tx_ringbuf(void)
85
{
86
  tx_ringbuf.read_index=0;
87
  tx_ringbuf.write_index=0;
88
  tx_ringbuf.new_data_len=0;
89
  tx_ringbuf.isr_done=1;
90
  tx_ringbuf.free=TX_RINGBUFSIZE;
91
}
92
93
// this can not be called from within an ISR !
94
int write_to_txringbuf(char *buf, int len)
95
{
96
  uint16_t i, maxlen, transfer_len, src_addr;
97
98
  if (len>TX_RINGBUFSIZE)
99
    return -1;
100
  
101
  // busy wait until there is enough space in the ringbuffer
102
  while (tx_ringbuf.free<len);
103
104
  // busy wait until ISR is done
105
  while (!tx_ringbuf.isr_done);
106
107
  for (i=0; i<len; i++)
108
    tx_ringbuf.buf[(tx_ringbuf.write_index+i) % TX_RINGBUFSIZE] = buf[i];
109
  
110
  ENTER_CRITICAL_REGION();
111
  tx_ringbuf.write_index = (tx_ringbuf.write_index + len) % TX_RINGBUFSIZE;
112
  tx_ringbuf.free-=len;
113
  tx_ringbuf.new_data_len+=len;
114
115
116
  // if there is no DMA transfer in progress already, initiate a new one
117
  if (bit_is_clear(DMA_TX_CHANNEL.CTRLB, DMA_CH_CHBUSY_bp) && bit_is_clear(DMA_TX_CHANNEL.CTRLA, DMA_CH_ENABLE_bp) && tx_ringbuf.isr_done)
118
  {
119
    maxlen=TX_RINGBUFSIZE-tx_ringbuf.read_index;  // maximum length for this transfer
120
121
    // only transfer data up to the buffer boundary
122
    if (tx_ringbuf.new_data_len>maxlen)
123
      transfer_len=maxlen;
124
    else
125
      transfer_len=tx_ringbuf.new_data_len;
126
127
    //printf_P(PSTR("write_to_txringbuf: starting transfer (%i of %i)\n"), transfer_len, tx_ringbuf.new_data_len);
128
129
    src_addr = ((uint16_t)tx_ringbuf.buf) + tx_ringbuf.read_index;
130
    
131
    DMA_TX_CHANNEL.SRCADDR0 = (src_addr >> 0) & 0xff;
132
    DMA_TX_CHANNEL.SRCADDR1 = (src_addr >> 8) & 0xff;
133
    DMA_TX_CHANNEL.SRCADDR2 = 0;
134
    DMA_TX_CHANNEL.TRFCNT = transfer_len;
135
    DMA_TX_CHANNEL.CTRLA |= DMA_CH_ENABLE_bm;
136
137
    tx_ringbuf.isr_done=0;
138
  }
139
140
141
  LEAVE_CRITICAL_REGION();
142
  return len;
143
}
144
145
int isr_write_to_txringbuf(char *buf, int len)
146
{
147
  uint16_t i, maxlen, transfer_len, src_addr;
148
149
  if (len>TX_RINGBUFSIZE)
150
    return -1;
151
  
152
  // busy wait until there is enough space in the ringbuffer
153
  if (tx_ringbuf.free<len)
154
    return -1;
155
156
  // busy wait until ISR is done
157
  //while (!tx_ringbuf.isr_done);
158
159
  for (i=0; i<len; i++)
160
    tx_ringbuf.buf[(tx_ringbuf.write_index+i) % TX_RINGBUFSIZE] = buf[i];
161
  
162
  //ENTER_CRITICAL_REGION();
163
  tx_ringbuf.write_index = (tx_ringbuf.write_index + len) % TX_RINGBUFSIZE;
164
  tx_ringbuf.free-=len;
165
  tx_ringbuf.new_data_len+=len;
166
167
168
  // if there is no DMA transfer in progress already, initiate a new one
169
  if (bit_is_clear(DMA_TX_CHANNEL.CTRLB, DMA_CH_CHBUSY_bp) && bit_is_clear(DMA_TX_CHANNEL.CTRLA, DMA_CH_ENABLE_bp) && tx_ringbuf.isr_done)
170
  {
171
    maxlen=TX_RINGBUFSIZE-tx_ringbuf.read_index;  // maximum length for this transfer
172
173
    // only transfer data up to the buffer boundary
174
    if (tx_ringbuf.new_data_len>maxlen)
175
      transfer_len=maxlen;
176
    else
177
      transfer_len=tx_ringbuf.new_data_len;
178
179
    //printf_P(PSTR("write_to_txringbuf: starting transfer (%i of %i)\n"), transfer_len, tx_ringbuf.new_data_len);
180
181
    src_addr = ((uint16_t)tx_ringbuf.buf) + tx_ringbuf.read_index;
182
    
183
    DMA_TX_CHANNEL.SRCADDR0 = (src_addr >> 0) & 0xff;
184
    DMA_TX_CHANNEL.SRCADDR1 = (src_addr >> 8) & 0xff;
185
    DMA_TX_CHANNEL.SRCADDR2 = 0;
186
    DMA_TX_CHANNEL.TRFCNT = transfer_len;
187
    DMA_TX_CHANNEL.CTRLA |= DMA_CH_ENABLE_bm;
188
189
    tx_ringbuf.isr_done=0;
190
  }
191
192
193
  //LEAVE_CRITICAL_REGION();
194
  return len;
195
}
196
197
int uart_dma_putchar(char c, FILE *unused)
198
{
199
  write_to_txringbuf(&c, 1);
200
  return 0;
201
}
202
203
int isr_uart_dma_putchar(char c, FILE *unused)
204
{
205
  isr_write_to_txringbuf(&c, 1);
206
  return 0;
207
}
208
209
void setup_dma_tx_channel(void)
210
{
211
  // reset source address
212
  DMA_TX_CHANNEL.SRCADDR0 = 0;
213
  DMA_TX_CHANNEL.SRCADDR1 = 0;
214
  DMA_TX_CHANNEL.SRCADDR2 = 0;
215
216
  // set up destination address
217
  DMA_TX_CHANNEL.DESTADDR0 = (((uint16_t) &USART.DATA) >> 0) & 0xff;
218
  DMA_TX_CHANNEL.DESTADDR1 = (((uint16_t) &USART.DATA) >> 8) & 0xff;
219
  DMA_TX_CHANNEL.DESTADDR2 = 0;
220
221
  DMA_TX_CHANNEL.ADDRCTRL = DMA_CH_SRCRELOAD_NONE_gc;        // never reload source address
222
  DMA_TX_CHANNEL.ADDRCTRL |= DMA_CH_SRCDIR_INC_gc;        // increment source address during transfer
223
  DMA_TX_CHANNEL.ADDRCTRL |= DMA_CH_DESTRELOAD_NONE_gc;      // destination address does not need to be reloaded
224
  DMA_TX_CHANNEL.ADDRCTRL |= DMA_CH_DESTDIR_FIXED_gc;        // the destination address should be fixed
225
226
  DMA_TX_CHANNEL.TRIGSRC = DMA_CH_TRIGSRC_USARTC0_DRE_gc;      // automatically trigger a new burst when UARTC0 is ready
227
  DMA_TX_CHANNEL.TRFCNT = 0;                    // reset block size
228
  DMA_TX_CHANNEL.REPCNT = 0;                    // do not repeat block transfers
229
  DMA_TX_CHANNEL.CTRLA = DMA_CH_SINGLE_bm;            // single shot mode (i.e. one burst transfer per trigger event)
230
  DMA_TX_CHANNEL.CTRLA |= DMA_CH_BURSTLEN_1BYTE_gc;        // 1 byte bursts
231
232
  DMA_TX_CHANNEL.CTRLB |= DMA_CH_TRNINTLVL_LO_gc;          // enable DMA transfer complete interrupt for this channel, low priority
233
}
234
235
236
int main(void)
237
{
238
  stdout = &mystdout;      // redirect STDOUT
239
240
  setup_dma_tx_channel();
241
242
  // enable DMA controller
243
  DMA.CTRL |= DMA_ENABLE_bm;
244
245
  // allow low- and high-level interrupts
246
  PMIC.CTRL |= PMIC_LOLVLEN_bm | PMIC_HILVLEN_bm;
247
248
  // enable interrupts
249
  sei();
250
251
  // switch stdout to DMA
252
  stdout = &mystdout_dma;
253
  printf_P(PSTR("Test ! This is a message sent over DMA\n"));
254
255
256
  while(1)
257
  {
258
  }
259
}
260
261
// This ISR is called whenever a transaction on the DMA_TX_CHANNEL has completed
262
ISR(DMA_CH0_vect, ISR_BLOCK)
263
{
264
  uint16_t read_index, new_data_len, len, maxlen, last_read_len;
265
  uint16_t src_addr;
266
267
  if (bit_is_set(DMA_TX_CHANNEL.CTRLB, DMA_CH_CHBUSY_bp))
268
  {
269
    printf_P(PSTR("DMA_CH0_vect: ieeeeee: DMA_TX_CHANNEL is still busy !\n"));
270
    while(1);
271
  }
272
273
  last_read_len=DMA_TX_CHANNEL.TRFCNT;  // number of bytes transferred in the last DMA transaction
274
  tx_ringbuf.read_index=(tx_ringbuf.read_index+last_read_len) % TX_RINGBUFSIZE;
275
276
  if (last_read_len>tx_ringbuf.new_data_len)
277
  {
278
    printf_P(PSTR("DMA_CH0_vect: ieeeeee: last_read_len(%i)>tx_ringbuf.new_data_len(%i) !!!\n"), 
279
      last_read_len, tx_ringbuf.new_data_len);
280
    while(1);
281
  }
282
283
  tx_ringbuf.new_data_len-=last_read_len;
284
  tx_ringbuf.free+=last_read_len;
285
  read_index=tx_ringbuf.read_index;
286
  new_data_len=tx_ringbuf.new_data_len;
287
    
288
  //printf_P(PSTR("DMA_CH0_vect: last_read_len: %i, new_data_len: %i\n"), last_read_len, new_data_len);
289
290
  // if there is data left to transfer, initiate a new DMA transaction
291
  if (new_data_len>0)
292
  {
293
    // maximum length for this transfer
294
    maxlen=TX_RINGBUFSIZE-read_index;
295
296
    // only transfer data up to the buffer boundary
297
    if (new_data_len>maxlen)
298
      len=maxlen;
299
    else
300
      len=new_data_len;
301
    
302
    //printf_P(PSTR("DMA_CH0_vect: starting transfer (%i of %i)\n"), len, tx_ringbuf.new_data_len);
303
304
    // set source address
305
    src_addr = ((uint16_t)tx_ringbuf.buf) + read_index;
306
307
    DMA_TX_CHANNEL.SRCADDR0 = (src_addr >> 0) & 0xff;
308
    DMA_TX_CHANNEL.SRCADDR1 = (src_addr >> 8) & 0xff;
309
    DMA_TX_CHANNEL.SRCADDR2 = 0;
310
    DMA_TX_CHANNEL.TRFCNT = len;                    // set up size of transaction
311
    DMA_TX_CHANNEL.CTRLA |= DMA_CH_ENABLE_bm;              // enable channel, transfer data to uart now
312
  }
313
  else
314
  {
315
    // otherwise reset the DMA channel
316
    DMA_TX_CHANNEL.TRFCNT = 0;
317
    DMA_TX_CHANNEL.CTRLA &= ~DMA_CH_ENABLE_bm;  // this shouldn't be necessary
318
    
319
  }
320
321
  // since ERRIF and TRNIF share the same interrupt, we need to clear
322
  // the interrupt flag manually by *setting* the corresponding flag bits
323
  DMA_TX_CHANNEL.CTRLB |= (DMA_CH_TRNIF_bm | DMA_CH_ERRIF_bm);
324
325
  tx_ringbuf.isr_done=1;
326
}

lg,
stef

von Tim (Gast)


Lesenswert?

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?

von peter (Gast)


Lesenswert?

es synct sich nur, wenn du vom usart aus triggerst. Also DRE als 
transaction start.
1
  #define DMA_F_TX_CHANNEL 0  //dma channel
2
  #define USART_TX_BUFF_SIZE 32 //size of the tx buffer
3
4
  char TX[USART_TX_BUFF_SIZE];//tx  buffer
5
  struct dma_channel_config dmach_tx_conf; //
6
7
  dma_channel_set_burst_length(&dmach_tx_conf, DMA_CH_BURSTLEN_1BYTE_gc);//1 byte bursts
8
  dma_channel_set_transfer_count(&dmach_tx_conf, USART_TX_BUFF_SIZE);//set tx buffer size as transfer size
9
  dma_channel_set_src_reload_mode(&dmach_tx_conf,DMA_CH_SRCRELOAD_TRANSACTION_gc);//transaction reload for src
10
  dma_channel_set_dest_reload_mode(&dmach_tx_conf,DMA_CH_SRCRELOAD_NONE_gc); //no reload
11
  dma_channel_set_src_dir_mode(&dmach_tx_conf, DMA_CH_SRCDIR_INC_gc);//inc src for tx buff
12
  dma_channel_set_source_address(&dmach_tx_conf,(uint16_t)(uintptr_t)&TX);//tx buffer as src
13
  dma_channel_set_dest_dir_mode(&dmach_tx_conf, DMA_CH_DESTDIR_FIXED_gc);//dest fixed
14
  dma_channel_set_destination_address(&dmach_tx_conf,(uint16_t)(uintptr_t)&USARTF0.DATA);// usart data register as destination
15
  dma_channel_set_trigger_source(&dmach_tx_conf, DMA_CH_TRIGSRC_USARTF0_DRE_gc);//set data register empty as trigger src
16
  dma_channel_set_single_shot(&dmach_tx_conf);//set channel to single shot
17
  dma_channel_set_repeats(&dmach_tx_conf,1);//disable repeat feature
18
19
20
  dma_enable();//enable dma controller
21
  dma_set_priority_mode(DMA_PRIMODE_CH0123_gc);//set fixed pri mode
22
  
23
    dma_set_callback(DMA_F_TX_CHANNEL, dma_F_TX_transfer_done);//set transfer done callback for channel
24
    dma_channel_set_interrupt_level(&dmach_tx_conf, DMA_INT_LVL_LO);//set iterrupt lvl low
25
  dma_channel_write_config(DMA_F_TX_CHANNEL, &dmach_tx_conf);//write config to channel
26
27
28
//callback:
29
static inline void dma_F_TX_transfer_done(enum dma_channel_status status)
30
{
31
      for(;;)
32
      {
33
        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.

von peter (Gast)


Lesenswert?

obriger code ist für ASF 3.7.3 asf.atmel.com/docs/latest/

Bitte melde dich an um einen Beitrag zu schreiben. Anmeldung ist kostenlos und dauert nur eine Minute.
Bestehender Account
Schon ein Account bei Google/GoogleMail? Keine Anmeldung erforderlich!
Mit Google-Account einloggen
Noch kein Account? Hier anmelden.