Forum: Mikrocontroller und Digitale Elektronik ATXmega DMA und ADC einzelne Bytes mit Timer als Trigger


von Toubi (Gast)


Lesenswert?

Hallo zusammen

Folgendes Problem:

Ich möchte gerne den ADC welcher sich im Freerunning-mode befindet 
periodisch auslesen. Dies gerne mit dem DMA.
Der DMA soll Byte um Byte in einem Buffer schreiben.
Als "Takt" zum Schreiben soll der Overflow eines Timers (TCC1) dienen.

Mein Code sieht aktuell so aus:
1
  // set TCC1 to 11024Hz overflow, actually 11019.2838Hz (-0.052% error)
2
  TCC1.CTRLA = 0; // stop if running
3
  TCC1.CNT = 0;
4
  TCC1.PER = 1000-1;
5
6
  EVSYS.CH0MUX = EVSYS_CHMUX_TCC1_OVF_gc; // trigger on timer overflow
7
8
9
  // reset DMA controller
10
  DMA.CTRL = 0;
11
  DMA.CTRL = DMA_RESET_bm;
12
  while ((DMA.CTRL & DMA_RESET_bm) != 0);
13
  
14
  DMA.CTRL      = DMA_CH_ENABLE_bm | DMA_DBUFMODE_CH01_gc; // double buffered with channels 0 and 1
15
  
16
  //Bei Double Buffering wird automatisch aus Channel 0 und 1 ein "Pair" gebildet. 
17
  //Siehe dazu AVR1304.P8
18
  
19
  // channel 0
20
  // **** TODO: reset dma channels
21
  DMA.CH0.REPCNT    = 1;
22
  DMA.CH0.CTRLA    =  DMA_CH_BURSTLEN_1BYTE_gc | DMA_CH_SINGLE_bm | DMA_CH_REPEAT_bm; // ADC result is 1 byte (8 bit word)
23
  DMA.CH0.CTRLB    = 0x1;
24
  DMA.CH0.ADDRCTRL  = DMA_CH_SRCRELOAD_BURST_gc | DMA_CH_SRCDIR_INC_gc | // reload source after every burst
25
  DMA_CH_DESTRELOAD_TRANSACTION_gc | DMA_CH_DESTDIR_INC_gc; // reload destination after every transaction
26
  DMA.CH0.TRIGSRC    = DMA_CH_TRIGSRC_TCC1_OVF_gc;  //DMA0 gets synched by TCC1
27
  DMA.CH0.TRFCNT    = 32; // always the number of bytes, even if burst length > 1
28
  DMA.CH0.DESTADDR0  = (( (uint16_t) buffer_a) >> 0) & 0xFF;
29
  DMA.CH0.DESTADDR1  = (( (uint16_t) buffer_a) >> 8) & 0xFF;
30
  DMA.CH0.DESTADDR2  = 0;
31
  DMA.CH0.SRCADDR0  = (( (uint16_t) &ADCB.CH1.RES) >> 0) & 0xFF;
32
  DMA.CH0.SRCADDR1  = (( (uint16_t) &ADCB.CH1.RES) >> 8) & 0xFF;
33
  DMA.CH0.SRCADDR2  = 0;
34
35
  DMA.CH0.CTRLA    |= DMA_CH_ENABLE_bm;
36
  //TCC1.INTCTRLA = 0x03;
37
  TCC1.CTRLA      = TC_CLKSEL_DIV1_gc; // start timer, and in turn ADC
38
}
39
...
40
ISR(DMA_CH0_vect)
41
{
42
  
43
  //Interrupt quittieren
44
  DMA.CH0.CTRLB |= 0x10;
45
  TCC1.INTFLAGS |= 0x01;
46
  PORTF.OUTTGL = 0x01
47
}

ADC läuft. Dies wurde getestet.
Das Problem ist aktuell, dass der DMA Interrupt nicht nach TCC-Takt / 32 
(wegen 32 Byte Datenlänge im DMA) kommt sondern eigentlich nach jedem 
Byte. Wenn ich den TCC1 Prescaler bzw. dessen Periode reduziere = 
schnellerer DMA Trigger, dann läuft gar nichts mehr.

Wäre froh, wenn jemand kurz über den Code schauen könnte.

Danke.
Gruss
Toubi

von Timo H. (Gast)


Lesenswert?

Sourceadresse scheint ja zwei Bytes zu umfassen.
Gleichzeitig ist die Burstlänge ein Byte.

Vielleicht da mal schauen?

von Holger K. (Gast)


Lesenswert?

Wäre auch an einer Lösung interessiert.
Hatte mal ein ähnliches Problem.

Bist du bereits weitergekommen Toubi?

von Anno (Gast)


Lesenswert?

Wie schon angemerkt, hat der ADC-Wert 2 Bytes! => BURSTLEN.
DMA_CH_BURSTLEN_1BYTE_gc ist also falsch.
Dann ist es falsch den ADC im Freerunning Modus zu betreiben,
die DMA aber über Timer zu triggern - passt nicht zusammen.
(Datenkollision bei 8Bit-Prozessor und nicht synchronem Zugriff - 
Schreiben/Lesen).

Richtig: ADC-Convert über Timer triggern. Dazu über einen Eventchannel
gehen - EVSYS_CHMUX0 = EVSYS_CHMUX_TCxy_OVF_gc
und mit ADC_EVCTRL = <SWEEP> | <EVSEL> | <EVACT>) Kanal zum triggern 
einstellen...
<xy>: Die gewünschten Konstanten aus Datenblatt / ioxxx.h einsetzen

...und die DMA über DMA.TRIGSRC= DMA_CH_TRIGSRC_ADCx_CHx_gc
zu triggern.
Evtl. über DMA-Transferinterrupt im Int. das Array auslesen.
Die ADC-Wandlung kannst du abschalten in dem du den CHMUX0 auf 0 setzt..

Weitere Fehler:
- Wenn die DMA kontinuierlich arbeiten soll, muss
DMA.CH0.REPCNT = 0 sein!
- Doublebuffer ist eher nicht gewollt.
- Die richtige Initialisierung des ADCs fehlt hier...
-...und sicher noch einige andere.

Dringender Rat: Das Family-Datasheet zu ADC, DMA und Eventsystem
komplett und eingehend studieren.

von Toubi (Gast)


Lesenswert?

Danke für die Tipps. Ich werde das testen.

Was meinst du mit:
Doublebuffer ist eher nicht gewollt ??

Der Grund für Double Buffering, ist um eine sauberes und schnelles 
Auslesen der Daten zu ermöglichen.
Da ich ein 1kHz signal abtasten und analysieren möchte, ist ein 
schnelles Auslesen erforderlich.

von Anno (Gast)


Lesenswert?

>> Was meinst du mit Doublebuffer ist eher nicht gewollt ??
Doublebuffering verwendet zwei DMA-Kanäle.
Für 1kHz & zwei Bytes ist aber *ein DMA-Kanal völlig ausreichend!*
Bei sehr niedrigem Systemtakt kann man evtl. einige _NOP()s in die 
Hauptschleife / Schleifen einstreuen, damit der Datenbus im Prozessor 
öfter mal unbenutzt ist. Aber zuerst mal ohne probieren!
PS:
Ich habe, bei 32MHz Systemtakt 2 DMA-Kanäle mit 100KHz erfolgreich 
laufen lassen...

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.