Forum: Mikrocontroller und Digitale Elektronik ATXMega DMA mit Compare Match Trigger funktioniert nicht wie erwartet


von Dieter F. (Gast)


Lesenswert?

Hallo,

Aktuell habe ich das Problem, dass der DMA beim erstem Compare-Ereignis
wie gewünscht losläuft - aber nicht mehr aufhört zu Laufen, obwohl ich
das entsprechende Interrupt-Flag (per DMA :-) ) lösche.

Atmel schweigt sich (soweit ich Lesen konnte) darüber aus, was genau die 
Übertragung auslöst. Man kann Compare-Channel auswählen - weiß aber 
nicht (wie in meinem Fall), was genau die Übertragung auslöst.

Meine Vorgehensweise:
Zunächst habe ich die DMA-Kanäle 0 und 1 (0 für den Compare-Wert, 1 für
das Löschen der Interrupt-Flags) initialisiert und aktiviert. Dann habe
ich den Compare-Wert vorbelegt und den Timer gestartet.

Mit dem ersten Compare startete auch DMA-Kanal 0 - kurz darauf DMA-Kanal
1, da ich diese Reihenfolge über die Priorität festgelegt habe. Prima,
bis dahin.

Dann beginnt das Elend. Die DMA-Kanäle arbeiten weiter, bis keine Werte
mehr zu übertragen sind - ohne auf den nächsten Compare zu Warten -  ...
. Das Ergebnis ist nicht zufriedenstellend :-(

Weiß jemand Rat?

Ja, der Code - hier die DMA-Initialisierung ...
1
void dma_init()
2
{
3
  DMA.CTRL        = 0;                        // Reset DMA Controller
4
  DMA.CTRL        = DMA_RESET_bm;
5
  
6
  while ((DMA.CTRL & DMA_RESET_bm) != 0);
7
  
8
  
9
  DMA.CTRL        = DMA_CH_ENABLE_bm                  // DMA Aktivieren
10
              | DMA_DBUFMODE_DISABLED_gc              // kein double buffer
11
              | DMA_PRIMODE_CH0123_gc;              // Priorität Kanal 0->1->2->3
12
13
  DMA.CH0.REPCNT      = 0;                        // Keine Wiederholung
14
  
15
  DMA.CH0.CTRLA      = DMA_CH_BURSTLEN_2BYTE_gc              // Pro Transfer 2 Byte / 1 Wort für das 16-Bit-Register
16
              | DMA_CH_SINGLE_bm;                  // Immer nur 1 burst pro Trigger
17
              
18
  DMA.CH0.ADDRCTRL    = DMA_CH_SRCRELOAD_BLOCK_gc              // Die Quell-Adresse wird nach jedem Block-Transfer neu geladen
19
              | DMA_CH_SRCDIR_INC_gc                // Die Quell-Adresse wird nach jedem burst inkrementiert
20
              | DMA_CH_DESTRELOAD_NONE_gc              // Die Ziel-Adresse bleibt unverändert
21
              | DMA_CH_DESTDIR_FIXED_gc;              // Die Ziel-Adresse bleibt unverändert
22
              
23
  DMA.CH0.TRIGSRC      = DMA_CH_TRIGSRC_TCD0_CCA_gc;            // Trigger ist Compare-Channel A des Zählers TCD0
24
  
25
  DMA.CH0.DESTADDR0    = (( (uint16_t) &TCD0.CCA) >> 0) & 0xFF;      // Die Ziel-Adresse ist das Register mit dem Vergleichswert A des Zähler TCD0
26
  DMA.CH0.DESTADDR1    = (( (uint16_t) &TCD0.CCA) >> 8) & 0xFF;
27
  DMA.CH0.DESTADDR2    = 0;
28
  DMA.CH0.SRCADDR0    = (( (uint16_t) &data_table + 2) >> 0) & 0xFF;    // Die Quell-Adresse + 2 ist die Adresse der Daten-Tabelle
29
  DMA.CH0.SRCADDR1    = (( (uint16_t) &data_table + 2) >> 8) & 0xFF;    // 2 Byte Offset, da der erste Wert beim Zähler-Start mitgegeben wird
30
  DMA.CH0.SRCADDR2    = 0;
31
32
33
  DMA.CH1.REPCNT      = 0;                        // Keine Wiederholung
34
  
35
  DMA.CH1.CTRLA      = DMA_CH_BURSTLEN_1BYTE_gc              // Pro Transfer 1 Byte / 1 Wort für das 8-Bit-Flag-Register
36
              | DMA_CH_SINGLE_bm;                  // Immer nur 1 burst pro Trigger
37
  
38
  DMA.CH1.ADDRCTRL    = DMA_CH_SRCRELOAD_NONE_gc              // Die Quell-Adresse bleibt unverändert
39
              | DMA_CH_SRCDIR_FIXED_gc              // Die Quell-Adresse bleibt unverändert
40
              | DMA_CH_DESTRELOAD_NONE_gc              // Die Ziel-Adresse bleibt unverändert
41
              | DMA_CH_DESTDIR_FIXED_gc;              // Die Ziel-Adresse bleibt unverändert
42
  
43
  DMA.CH1.TRIGSRC      = DMA_CH_TRIGSRC_TCD0_CCA_gc;            // Trigger ist Compare-Channel A des Zählers TCD0
44
  
45
  DMA.CH1.TRFCNT      = 1;                        // Genau das Interrupt-Flag-Register wird übergeben
46
47
  DMA.CH1.DESTADDR0    = (( (uint16_t) &TCD0.INTFLAGS) >> 0) & 0xFF;    // Die Ziel-Adresse ist das Interrupt-Flag-Register des Zähler TCD0
48
  DMA.CH1.DESTADDR1    = (( (uint16_t) &TCD0.INTFLAGS) >> 8) & 0xFF;
49
  DMA.CH1.DESTADDR2    = 0;
50
  DMA.CH1.SRCADDR0    = (( (uint16_t) &Flag_del) >> 0) & 0xFF;      // Die Quell-Adresse ist die Adresse der Bit-Maske zum Löschen der Flags 
51
  DMA.CH1.SRCADDR1    = (( (uint16_t) &Flag_del) >> 8) & 0xFF;      //
52
  DMA.CH1.SRCADDR2    = 0;
53
}

Und hier der Aufruf (es werden über data_table Compare-Werte übergeben)

...
1
  TCD0.CTRLA         = (TCD0.CTRLA & (~TC1_CLKSEL_gm));          // Timer f. Belichtung ausschalten
2
  TCD0.CTRLC         = 0x00;                      // Clear WG output at OC0A (Pin0, PORTD)
3
  
4
//  TCD0.CCA          = *data++;                      // Den ersten Wert aus dem Zeilenspeicher und als Vergleichswert setzen
5
  TCD0.CCA          = *data;                      // Den ersten Wert aus dem Zeilenspeicher und als Vergleichswert setzen
6
//  TCD0.CCABUF        = *data++;                      // Den zweiten Wert aus dem Zeilenspeicher holen und in den Vergleichspuffer schreiben
7
  TCD0.CNT          = 0x0000;                      // Sicherheitshalber ...
8
9
10
  data_table[(data_size / 2) - 1] = ( last_line_start 
11
                    + end_delay 
12
                    - data_sum
13
                    - begin_delay     );            // Als letzten Wert den nächsten synch-Wert übergeben
14
                    
15
  DMA.CH0.TRFCNT      = data_size;                      // Die Anzahl per DMA zu übergebender Bytes
16
  DMA.CH0.CTRLA          |= DMA_CH_ENABLE_bm;                  // DMA starten
17
  DMA.CH1.CTRLA          |= DMA_CH_ENABLE_bm;                  // DMA starten
18
19
  TCD0.CTRLA        = (TCD0.CTRLA & (~TC1_CLKSEL_gm))          // Timer f. Belichtung starten
20
                | TC_CLKSEL_DIV2_gc;                //

Gruß
Dieter

: Verschoben durch Moderator
von Dieter F. (Gast)


Lesenswert?

... push ...

von Moby (Gast)


Lesenswert?

Dieter Frohnapfel schrieb:
> obwohl ich
> das entsprechende Interrupt-Flag (per DMA :-) ) lösche.

Sorry keine echte Vermutung jetzt. Die Idee mit der Rücksetzung in einem 
zweiten DMA-Channel hatte ich noch nicht...
Aber hast Du Flaglöschung (1->TRNIF) schon mal ganz regulär via 
zugehörigem DMA-Interrupt probiert?

von Dieter F. (Gast)


Lesenswert?

Moby schrieb:
> Aber hast Du Flaglöschung (1->TRNIF) schon mal ganz regulär via
> zugehörigem DMA-Interrupt probiert?

Ich beziehe mich nicht auf ein DMA-Flag sondern auf das Compare-Flag des 
Timers in TCD0.INTFLAGS. Das kann ich so am schnellsten Löschen, da ich 
den Interrupt nicht aktivieren wollte - bzw. die Interrupt-Behandlung 
vermeiden wollte.

von Markus M. (adrock)


Lesenswert?

Ich kann mir ehrlich gesagt nicht vorstellen, dass man das Compareflag 
explizit per DMA (oder etwa per ISR o.ä.) löschen soll, das wäre 
ziemliche Verschwendung von Ressourcen.

Ich würde erwarten dass der DMA durch eine positive Flanke des 
Compareflags ausgelöst wird. Aber da müsste man nochmal ganz genau in 
die Doku schauen...

von Moby (Gast)


Lesenswert?

Dieter Frohnapfel schrieb:
> Compare-Flag des
> Timers in TCD0.INTFLAGS. Das kann ich so am schnellsten Löschen,

Warum sollte das denn explizit gelöscht werden müssen?
"A DMA read or write access of the corresponding CCx or
CCxBUF will then clear the CCxIF and release the request."
Warum dann das Rücksetzen im zweiten Channel?

von Dieter F. (Gast)


Lesenswert?

Moby schrieb:
> A DMA read or write access of the corresponding CCx or
> CCxBUF will then clear the CCxIF and release the request.

Wo hast Du das denn gefunden? Kannst Du mir bitte die Quelle nennen?
-> Hat sich erledigt, habs gefunden - vielen Dank.

Moby schrieb:
> Warum dann das Rücksetzen im zweiten Channel?

Weil es bei mir nicht so funktioniert, wie es vorstehend beschrieben ist 
:-(

von Dieter F. (Gast)


Lesenswert?

Hallo an alle,

aktuell stelle ich meine Frage mal zurück - ich muss selbst erst noch 
mal nachdenken. Wenn ich nicht mehr oder weniger neben der Spur bin 
stimmen da Dokumentation und Realität nicht überein.

Vielen Dank für die Antworten.

Gruß
Dieter

von Jörg W. (dl8dtl) (Moderator) Benutzerseite


Lesenswert?

Dieter Frohnapfel schrieb:

>   DMA.CTRL        = DMA_CH_ENABLE_bm                  // DMA Aktivieren

Ist streng genommen der falsche Namen, DMA_ENABLE_bm wäre korrekt,
denn die Aktion bezieht sich nicht auf den Kanal, sondern den
kompletten DMAC.

Ist allerdings beides 0x80.

So ganz verstehe ich nicht, warum das bei dir immer weiter laufen
sollte.  Ich habe DMA letztens mal benutzt, um eine Ausgabe auf
einem Pin zu organisieren, der nicht direkt als Ausgangspin für einen
Timer konzpiert ist.  Das sieht bei mir so aus:
1
  DMA.CTRL = DMA_ENABLE_bm;
2
  static uint8_t dmaval = _BV(0); /* PF0 port bit */
3
4
  DMA.CH0.TRIGSRC = DMA_CH_TRIGSRC_TCC0_CCA_gc;
5
  DMA.CH0.SRCADDR0 = ((uint16_t)&dmaval) & 0xff;
6
  DMA.CH0.SRCADDR1 = ((uint16_t)&dmaval >> 8) & 0xff;
7
  DMA.CH0.DESTADDR0 = ((uint16_t)&PORTF.OUTCLR) & 0xff;
8
  DMA.CH0.DESTADDR1 = ((uint16_t)&PORTF.OUTCLR >> 8) & 0xff;
9
  DMA.CH0.ADDRCTRL = DMA_CH_SRCRELOAD_TRANSACTION_gc;
10
11
  DMA.CH1.TRIGSRC = DMA_CH_TRIGSRC_TCC0_OVF_gc;
12
  DMA.CH1.SRCADDR0 = ((uint16_t)&dmaval) & 0xff;
13
  DMA.CH1.SRCADDR1 = ((uint16_t)&dmaval >> 8) & 0xff;
14
  DMA.CH1.DESTADDR0 = ((uint16_t)&PORTF.OUTSET) & 0xff;
15
  DMA.CH1.DESTADDR1 = ((uint16_t)&PORTF.OUTSET >> 8) & 0xff;
16
  DMA.CH1.ADDRCTRL = DMA_CH_SRCRELOAD_TRANSACTION_gc;
17
18
  TCC0.CTRLA = TC_CLKSEL_DIV256_gc;
19
  TCC0.CTRLB = TC0_CCAEN_bm | TC_WGMODE_SS_gc;
20
  /* 1 s measurement */
21
  TCC0.CCA = F_CPU / 256;
22
  TCC0.PER = 65535;    /* 1.048576 s period */
23
  TCC0.INTCTRLA = TC_OVFINTLVL_HI_gc;
24
  TCC0.INTCTRLB = TC_CCAINTLVL_HI_gc;

Der default count ist 1, es wird also jeweils genau ein Byte
übertragen.  Das übertragene Byte ist das gleiche ;) (Variable
dmaval), aber Ziel ist einmal das Register zu Setzen und das
andere Mal das zum Löschen eines Portpins.

In den ISRs wird jeweils der eine oder andere DMA-Kanal dann
(einmalig) angestoßen:
1
ISR(TCC0_OVF_vect)
2
{
3
  DMA.CH0.CTRLA = DMA_CH_ENABLE_bm;
4
}
5
6
ISR(TCC0_CCA_vect)
7
{
8
  DMA.CH1.CTRLA = DMA_CH_ENABLE_bm;
9
  // hier passiert noch mehr, jetzt aber uninteressant
10
}

Vielleicht hilft dir das ja als Anregung weiter, scheint mir von
deinem Vorhaben nicht zu weit entfernt zu sein (außer dass ich
richtige ISRs habe, um die Interruptflags zu löschen).

: Bearbeitet durch Moderator
von Dieter F. (Gast)


Angehängte Dateien:

Lesenswert?

Jörg Wunsch schrieb:
> Ist streng genommen der falsche Namen, DMA_ENABLE_bm wäre korrekt,
> denn die Aktion bezieht sich nicht auf den Kanal, sondern den
> kompletten DMAC.

Hallo Jörg,

da hast Du allerdings Recht - werde ich korrigieren.

Dein Code-Schnipsel unterscheidet sich jetzt nicht so wesentlich von 
meinem - abgesehen davon, dass ich eine Timer-Daten-Tablle im single 
shot modus mit 2 Byte burstlänge übertragen will.

Es kommt übrigens nichts im CCA-Register an :-( obwohl der DMA arbeitet 
(kann ich per JTAG gut beobachten). Mit dem nahezu identischen Code 
schreibe ich aber ohne Probleme in das INTFLAGS-Register. Ein Aktivieren 
und Behandeln (leer) des Interrupts brachte übrigens auch nichts.

Ich werde wohl als nächstes mal versuchen, CCABUF zu beschreiben. Mal 
schauen, ob das funktioniert. Man könnte beiliegende Dokumentation so 
deuten, dass bei Output Compare nur der Zugriff auf CCxBUF das 
Interrupt-Flag löscht. Mal schauen ...

Gruß
Dieter

von Dieter F. (Gast)


Lesenswert?

Moby schrieb:
> "A DMA read or write access of the corresponding CCx or
> CCxBUF will then clear the CCxIF and release the request."

Das funktioniert bei mir definitiv nicht - zumindest nicht burst- oder 
block-weise.

Markus M. schrieb:
> Ich kann mir ehrlich gesagt nicht vorstellen, dass man das Compareflag
> explizit per DMA (oder etwa per ISR o.ä.) löschen soll, das wäre
> ziemliche Verschwendung von Ressourcen.

Ist es auch, geht aber nicht anders. Ich muss einen Interrupt auslösen 
und behandeln, damit das Ganze funktioniert. Habe ich jetzt mit einer 
"naked" ISR, die nur ein RETI() enthält, gelöst.

Burst-weise triggern geht in diesem Fall auch nicht, das habe ich 
ausschließlich block-weise (bzw. wenn burst- gleich block-länge ist) 
hinbekommen.

Das ganze "Verhalten" ist zumindest für mich logisch nicht 
nachvollziehbar - ist mir aber mittlerweile egal, da es jetzt (zumindest 
im Mini-Test-Programm) funktioniert. Werde es nun in meine Anwendung 
einbauen und schauen, ob alles O.K. ist.

Nochmals vielen Dank an alle.

Gruß
Dieter

von Jörg W. (dl8dtl) (Moderator) Benutzerseite


Lesenswert?

Dieter Frohnapfel schrieb:
> Das ganze "Verhalten" ist zumindest für mich logisch nicht
> nachvollziehbar

Nachvollziehbar ist das vermutlich nur im Verilog-Code von Atmel. ;-)

Du kannst natürlich mal probieren, ein Ticket aufzumachen bei denen,
schließlich hast du ja erstmal alles nach Handbuch gemacht.  Je
nachdem, wie engagiert der indische Firstlevel-Supporter gerade ist,
könnte es schon sein, dass da mal jemand ernsthaft reinguckt.

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.