Forum: Mikrocontroller und Digitale Elektronik XMEGA, DMA, synchrone parallele Eingabe


von Horst M. (horst)


Lesenswert?

Komme hier nicht richtig weiter.
Die Datenquelle liefert 16-Bit-Werte in zwei 8-Bit-Häppchen, jeweils mit 
einer steigenden Taktflanke.
Die Daten sollen mittels DMA von PORT A gelesen und im Speicher abgelegt 
werden.
Der Takt wird vom Timer D0 erzeugt, PWM-Kanal D liefert den externen 
Takt, PWM-Kanal C den versetzten Trigger für den DMA-Kanal 0.

Etwa so:
1
externer Takt  ____|¯¯¯¯¯¯|____|¯¯¯¯¯¯|_  invertierter Output
2
DMA-Trigger    ¯¯¯¯¯¯|____|¯¯¯¯¯¯|____|¯

Der DMA schreibt jedoch Blödsinn in den Speicher.
Insbesondere scheint es eine Abhängigkeit vom Burst-Length-Setting zu 
geben (bei ansonsten absolut identischer Konfiguration).

Burst-Länge 1 Byte:

7B7B ACAC 7B7B ACAC AC7B 7BAC AC7B 7BAC AC7B 7B7B ACAC 7B7B ACAC 7B7B

Burst-Länge 8 Byte:

7B7B 7B7B 7B7B 7B7B ACAC ACAC ACAC 7B7B 7B7B 7B7B ACAC ACAC ACAC 7B7B

Die Datenquelle liefert 7BAC (oder AC7B, das ist erstmal nicht 
entscheidend).
Offensichtlich erfolgt hier keine zuverlässige Synchronisation, der 
DMA-Kanal transferiert ein Byte mehrfach.
1
;invertiere PORTD.3 (Timer D0 PWM-Ausgang D)
2
  ldi r16,1<<PORT_INVEN_bp
3
  sts (PORTD_PIN3CTRL),r16
4
;TIMER D0 Setup
5
;Taktperiode
6
  ldi r16,10
7
  sts (TCD0_PER),r16
8
  ldi r16,0
9
  sts (TCD0_PER+1),r16
10
;PWM-Compare Kanal D, externer Takt
11
  ldi r16,4
12
  sts (TCD0_CCD),r16
13
  ldi r16,0
14
  sts (TCD0_CCD+1),r16
15
;PWM-Compare Kanal C, DMA-Trigger
16
  ldi r16,6
17
  sts (TCD0_CCC),r16
18
  ldi r16,0
19
  sts (TCD0_CCC+1),r16
20
  ldi r16,1<<TC0_CCDEN_bp|TC_WGMODE_SS_gc
21
  sts (TCD0_CTRLB),r16
22
;DMA Kanal 0 Setup
23
  ldi r16,1<<DMA_ENABLE_bp|DMA_DBUFMODE_DISABLED_gc|DMA_PRIMODE_CH0123_gc
24
  sts (DMA_CTRL),r16
25
  ldi r16,DMA_CH_SRCRELOAD_NONE_gc|DMA_CH_SRCDIR_FIXED_gc|DMA_CH_DESTRELOAD_NONE_gc|DMA_CH_DESTDIR_INC_gc
26
  sts (DMA_CH0_ADDRCTRL),r16
27
  ldi r16,DMA_CH_TRIGSRC_TCD0_CCC_gc ;Trigger Timer D0 PWM-Kanal C
28
  sts (DMA_CH0_TRIGSRC),r16
29
  ldi r16,LOW(4096)
30
  sts (DMA_CH0_TRFCNT),r16
31
  ldi r16,HIGH(4096)
32
  sts (DMA_CH0_TRFCNT+1),r16
33
  ldi r16,1
34
  sts (DMA_CH0_REPCNT),r16
35
  ldi r16,LOW(PORTA_IN)
36
  sts (DMA_CH0_SRCADDR0),r16
37
  ldi r16,HIGH(PORTA_IN)
38
  sts (DMA_CH0_SRCADDR1),r16
39
  ldi r16,BYTE3(PORTA_IN)
40
  sts (DMA_CH0_SRCADDR2),r16
41
  ldi r16,LOW(SRAM_START)
42
  sts (DMA_CH0_DESTADDR0),r16
43
  ldi r16,HIGH(SRAM_START)
44
  sts (DMA_CH0_DESTADDR1),r16
45
  ldi r16,BYTE3(SRAM_START)
46
  sts (DMA_CH0_DESTADDR2),r16
47
  ldi r16,1<<DMA_CH_ENABLE_bp|1<<DMA_CH_REPEAT_bp|DMA_CH_BURSTLEN_1BYTE_gc ;bzw. DMA_CH_BURSTLEN_8BYTE_gc
48
  sts (DMA_CH0_CTRLA),r16
49
50
;Start Timer D0
51
  ldi r16,TC_CLKSEL_DIV1_gc
52
  sts (TCD0_CTRLA),r16

Das Ganze läuft auf einem Xplained mit XMEGA128A1.

"Zu Fuß" funktioniert es prinzipiell, die Datenquelle sendet keinen 
Mist.
1
;Mappen der Ports A...D auf die virtuellen Ports (ATMega-like)
2
  ldi r16,PORTCFG_VP1MAP_PORTB_gc|PORTCFG_VP0MAP_PORTA_gc
3
  sts (PORTCFG_VPCTRLA),r16
4
  ldi r16,PORTCFG_VP3MAP_PORTD_gc|PORTCFG_VP2MAP_PORTC_gc
5
  sts (PORTCFG_VPCTRLB),r16
6
7
.equ PINA=VPORT0_IN
8
.equ PORTD=VPORT3_OUT
9
  sbi PORTD,3
10
  in r16,PINA
11
  cbi PORTD,3
12
  st X+,r16
13
  sbi PORTD,3
14
  in r16,PINA
15
  cbi PORTD,3
16
  st X+,r16

Habe ich beim Setup was übersehen oder bin ich hier über irgendeinen Bug 
des Controller-DMA gestolpert und kann das Thema abhaken?

von Heinz Elmann (Gast)


Lesenswert?

Möglicherweise liegen noch mehr Dinge in deinem Code im argen, aber 
zumindest hierzu kann ich was sagen:

Die Burst-Länge gibt die Größe der Daten an, die bei einem Trigger 
kopiert werden sollen. Burst-Transfers von > 1 B verwendet man 
beispielsweise zum Kopieren eines ADC Wertes: Der DMA Controller 
speichert die 2 Byte der ADC-Messung zwischen und schreibt sie dann mit 
1 Byte Zugriffen ins RAM. Das vermeidet, dass Werte gemischt werden, 
wenn ein neues ADC-Ergebnis vorliegt (low byte vom alten Wert, high byte 
vom neuen).

Für deinen I/O Port sind also nur 1-Byte Bursts sinnvoll (ansonsten 
kopierst du das PORTA.IN Register und die nachfolgenden Register), und 
immer getriggert durch den Timer.

von Heinz Elmann (Gast)


Lesenswert?

Horst M. schrieb:
> Etwa so:
>
>
1
> externer Takt  ____|¯¯¯¯¯¯|____|¯¯¯¯¯¯|_  invertierter Output
2
> DMA-Trigger    ¯¯¯¯¯¯|____|¯¯¯¯¯¯|____|¯
3
>
>
> Der DMA schreibt jedoch Blödsinn in den Speicher.
> Insbesondere scheint es eine Abhängigkeit vom Burst-Length-Setting zu
> geben (bei ansonsten absolut identischer Konfiguration).
>
> Burst-Länge 1 Byte:
>
> 7B7B ACAC 7B7B ACAC AC7B 7BAC AC7B 7BAC AC7B 7B7B ACAC 7B7B ACAC 7B7B

Ich vermute, dass du 7BAC sehen möchtest?
Da das mit 1 Byte Bursts nicht zuverlässig funktioniert: hast du die 
Setup-/Reaktions-Zeiten deiner datenliefernden Komponente 
berücksichtigt?
Wenn das Signal rechteckig ist, wieso triggerst du nicht bei in der 
Mitte eines Bits, bei 50% der Periode, wo wie es bei SPI auch üblich 
ist?

Des Weiteren kann es sein, dass dein Takt zu groß ist und der DMA mit 
dem Kopieren nicht hinterher kommt. Auf das RAM darf nur eine Komponente 
zugreifen. Wenn CPU und DMA Peripheral zugreifen wollen, gewinnt immer 
die CPU und die DMA Transaktion muss warten (nennt sich 
"Arbitrierung/arbitration", steht im Datenblatt).

von Heinz Elmann (Gast)


Lesenswert?

Heinz Elmann schrieb:
> Des Weiteren kann es sein, dass dein Takt zu groß ist und der DMA mit
> dem Kopieren nicht hinterher kommt. Auf das RAM darf nur eine Komponente
> zugreifen. Wenn CPU und DMA Peripheral zugreifen wollen, gewinnt immer
> die CPU und die DMA Transaktion muss warten (nennt sich
> "Arbitrierung/arbitration", steht im Datenblatt).

Deinem DMA-freien Beispiel entnehme ich, dass der Takt prinzipiell für 
deine Datenquelle ok ist. Allerdings sehe ich, dass du eine 
Periodendauer für von 10 Takten verwendest.

Das erhärtet meine Vermutung, dass es ein Arbitrierungsproblem ist. Hast 
du zufällig, während die DMA Transaktion läuft, viele 
(SRAM-)Speicherzugriffe z.B. kopierst Daten umher? Dann würde die CPU 
immer die Arbitrierung gewinnen und der DMA verpasst Ereignisse.

Wenn möglich, erhöhe zum Testen die Timer-Periode auf z.B. 100 Takte und 
kopiere weniger/nicht über die CPU währenddessen.

von Alexxx (Gast)


Lesenswert?

> Die Datenquelle liefert 16-Bit-Werte in zwei 8-Bit-Häppchen, jeweils mit
> einer steigenden Taktflanke.

Also dann liest man die Bytes mit fallender Flanke ein (=> 
DMA-Trigger)!
Dann könntest du auf den extra DMA-Trigger verzichten.
Wenn der externe Takt ein kleines Puls-Pausen-Verhältnis hat, dann 
bekommt die DMA mehr Zeit bis sie tatsächlich kopiert.
Den Trigger für die fallende Flanke erhältst du, wenn die 
DMA-Triggersource
auf CCx des PWM-Timers für ext. Takt gelegt wird.
Z.B.: DMA_CH_TRIGSRC_TCE0_CCA_gc

Tipp:
Bei Benutzung der XMega-DMAs kann man vorbeugend einige NOPs in die 
Hauptschleife einstreuen. Das sorgt dafür dass der Datenbus 
währenddessen für die DMA frei ist.

von Horst M. (horst)


Angehängte Dateien:

Lesenswert?

Herzlichen Dank für die Tips und Hinweise.
Um zu vermeiden, daß auf dem Datenbus irgendwas anderes läuft außer DMA, 
habe ich mal von Polling auf Interrupt umgestellt.
1
  rjmp start
2
3
  .org DMA_CH0_vect
4
  reti
5
6
start:
7
  ldi r16,20
8
  sts (TCD0_PER),r16
9
  ldi r16,0
10
  sts (TCD0_PER+1),r16
11
  ldi r16,8
12
  sts (TCD0_CCD),r16
13
  ldi r16,0
14
  sts (TCD0_CCD+1),r16
15
  ldi r16,12
16
  sts (TCD0_CCC),r16
17
  ldi r16,0
18
  sts (TCD0_CCC+1),r16
19
  ldi r16,1<<TC0_CCDEN_bp|TC_WGMODE_SS_gc
20
  sts (TCD0_CTRLB),r16
21
22
  ldi r16,1<<DMA_ENABLE_bp|DMA_DBUFMODE_DISABLED_gc|DMA_PRIMODE_CH0123_gc
23
  sts (DMA_CTRL),r16
24
  ldi r16,DMA_CH_SRCRELOAD_NONE_gc|DMA_CH_SRCDIR_FIXED_gc|DMA_CH_DESTRELOAD_NONE_gc|DMA_CH_DESTDIR_INC_gc
25
  sts (DMA_CH0_ADDRCTRL),r16
26
  ldi r16,DMA_CH_TRIGSRC_TCD0_CCC_gc
27
  sts (DMA_CH0_TRIGSRC),r16
28
  ldi r16,LOW(4096)
29
  sts (DMA_CH0_TRFCNT),r16
30
  ldi r16,HIGH(4096)
31
  sts (DMA_CH0_TRFCNT+1),r16
32
  ldi r16,1 ;150
33
  sts (DMA_CH0_REPCNT),r16
34
  ldi r16,LOW(PORTA_IN)
35
  sts (DMA_CH0_SRCADDR0),r16
36
  ldi r16,HIGH(PORTA_IN)
37
  sts (DMA_CH0_SRCADDR1),r16
38
  ldi r16,BYTE3(PORTA_IN)
39
  sts (DMA_CH0_SRCADDR2),r16
40
  ldi r16,LOW(SRAM_START)
41
  sts (DMA_CH0_DESTADDR0),r16
42
  ldi r16,HIGH(SRAM_START)
43
  sts (DMA_CH0_DESTADDR1),r16
44
  ldi r16,BYTE3(SRAM_START)
45
  sts (DMA_CH0_DESTADDR2),r16
46
  ldi r16,1<<DMA_CH_ENABLE_bp|1<<DMA_CH_REPEAT_bp|DMA_CH_BURSTLEN_1BYTE_gc
47
  sts (DMA_CH0_CTRLA),r16
48
  ldi r16,DMA_CH_TRNINTLVL_HI_gc
49
  sts (DMA_CH0_CTRLB),r16
50
51
  ldi r16,1<<PORT_INVEN_bp
52
  sts (PORTD_PIN3CTRL),r16
53
54
  ldi r16,1<<PMIC_HILVLEN_bp
55
  sts (PMIC_CTRL),r16
56
  ldi r16,1<<SLEEP_SMODE_IDLE_gc|1<<SLEEP_SEN_bp
57
  sts (SLEEP_CTRL),r16
58
59
  ldi r16,TC_CLKSEL_DIV1_gc
60
  sts (TCD0_CTRLA),r16
61
62
  sei
63
  sleep
64
65
  ldi r16,TC_CLKSEL_OFF_gc
66
  sts (TCD0_CTRLA),r16
67
68
stop:  rjmp stop

Mit obigem Code liest der DMA bspw. folgendes Muster ein:

1313 1313 6767 6767 1313 1313 6767 6767

Eigentlich nicht anders als vorher.

Werden die Timersettings von 20-8-12 auf 100-40-60 geändert, sieht das 
dann so aus:

7B7B 7B7B 7B7B 7B7B 7B7B 7B7B 7B7B 7B7B 7B7B 7BAC ACAC ACAC ACAC ACAC 
ACAC ACAC ACAC ACAC ACAC

Ich habe mal zwei LA-Screenshots angehangen, auf dem Interface läuft 
alles wie vorgesehen, die Bytes werden wie erwähnt mit der steigenden 
Taktflanke rausgeschoben.

Irgendwie immer noch so, als würde der DMA sich absolut nicht vom 
Timer/Trigger beeindrucken lassen...

von Noop (Gast)


Lesenswert?

Wie kann man sich im 21. Jhd und einem Xmega immernoch Assembler antun? 
Ich verstehe es einfach nicht..

von Horst M. (horst)


Lesenswert?

Noop schrieb:
> Wie kann man sich im 21. Jhd und einem Xmega immernoch Assembler antun?
> Ich verstehe es einfach nicht..

Hast Du hier auch was Sinnvolles beizutragen?
Kasper...

von Hagen R. (hagen)


Lesenswert?

Route den Timer auf einen Kanal das Event System und nutze dieses Event 
als Trigger für den DMA. Das was du versuchst habe ich schonmal 
umgesetzt, es geht.

: Bearbeitet durch User
von Horst M. (horst)


Lesenswert?

Hagen R. schrieb:
> Route den Timer auf einen Kanal das Event System und nutze dieses
> Event
> als Trigger für den DMA. Das was du versuchst habe ich schonmal
> umgesetzt, es geht.

Du meinst so?
1
  rjmp start
2
3
  .org DMA_CH0_vect
4
  reti
5
6
start:
7
  ldi r16,20*5
8
  sts (TCD0_PER),r16
9
  ldi r16,0
10
  sts (TCD0_PER+1),r16
11
  ldi r16,20*2
12
  sts (TCD0_CCD),r16
13
  ldi r16,0
14
  sts (TCD0_CCD+1),r16
15
  ldi r16,20*4
16
  sts (TCD0_CCC),r16
17
  ldi r16,0
18
  sts (TCD0_CCC+1),r16
19
  ldi r16,1<<TC0_CCDEN_bp|TC_WGMODE_SS_gc
20
  sts (TCD0_CTRLB),r16
21
22
;route Timer D0 Compare C auf Event Channel 0
23
  ldi r16,EVSYS_CHMUX_TCD0_CCC_gc
24
  sts (EVSYS_CH0MUX),r16
25
26
  ldi r16,1<<DMA_ENABLE_bp|DMA_DBUFMODE_DISABLED_gc|DMA_PRIMODE_CH0123_gc
27
  sts (DMA_CTRL),r16
28
  ldi r16,DMA_CH_SRCRELOAD_NONE_gc|DMA_CH_SRCDIR_FIXED_gc|DMA_CH_DESTRELOAD_NONE_gc|DMA_CH_DESTDIR_INC_gc
29
  sts (DMA_CH0_ADDRCTRL),r16
30
  ldi r16,DMA_CH_TRIGSRC_EVSYS_CH0_gc ;Triggerquelle Event Channel 0
31
  sts (DMA_CH0_TRIGSRC),r16
32
  ldi r16,LOW(4096)
33
  sts (DMA_CH0_TRFCNT),r16
34
  ldi r16,HIGH(4096)
35
  sts (DMA_CH0_TRFCNT+1),r16
36
  ldi r16,1
37
  sts (DMA_CH0_REPCNT),r16
38
  ldi r16,LOW(PORTA_IN)
39
  sts (DMA_CH0_SRCADDR0),r16
40
  ldi r16,HIGH(PORTA_IN)
41
  sts (DMA_CH0_SRCADDR1),r16
42
  ldi r16,BYTE3(PORTA_IN)
43
  sts (DMA_CH0_SRCADDR2),r16
44
  ldi r16,LOW(SRAM_START)
45
  sts (DMA_CH0_DESTADDR0),r16
46
  ldi r16,HIGH(SRAM_START)
47
  sts (DMA_CH0_DESTADDR1),r16
48
  ldi r16,BYTE3(SRAM_START)
49
  sts (DMA_CH0_DESTADDR2),r16
50
  ldi r16,1<<DMA_CH_ENABLE_bp|1<<DMA_CH_REPEAT_bp|DMA_CH_BURSTLEN_1BYTE_gc
51
  sts (DMA_CH0_CTRLA),r16
52
  ldi r16,DMA_CH_TRNINTLVL_HI_gc
53
  sts (DMA_CH0_CTRLB),r16
54
55
  ldi r16,1<<PORT_INVEN_bp
56
  sts (PORTD_PIN3CTRL),r16
57
58
  ldi r16,1<<PMIC_HILVLEN_bp
59
  sts (PMIC_CTRL),r16
60
  ldi r16,1<<SLEEP_SMODE_IDLE_gc|1<<SLEEP_SEN_bp
61
  sts (SLEEP_CTRL),r16
62
63
  ldi r16,TC_CLKSEL_DIV1_gc
64
  sts (TCD0_CTRLA),r16
65
66
  sei
67
  sleep
68
69
  ldi r16,TC_CLKSEL_OFF_gc
70
  sts (TCD0_CTRLA),r16
71
72
stop:  rjmp stop

Ja nee, wird nicht besser.
DMA schreibt immer noch wiederholte Bytes in den Speicher.

7B7B 7B7B 7B7B 7B7B 7B7B 7B7B 7BAC ACAC ACAC ACAC ACAC ACAC ACAC ACAC 
ACAC ACAC AC7B 7B7B 7B7B 7B7B 7B7B 7B7B 7B7B

: Bearbeitet durch User
von Horst M. (horst)


Lesenswert?

Hab es zum Laufen gebracht.

Woanders hatte sich jemand gefragt, warum ich den DMA-Kanal mit einem 
Timer triggere und nicht direkt auf die fallende Flanke am Portpin.
Ist so als direkte Alternative natürlich nicht praktikabel, der Timer 
erzeugt ja den Takt.
Trotzdem habe ich den Code mal entsprechend geändert (der Timer macht 
weiterhin den Takt).
1
  ldi r16,1<<PORT_INVEN_bp|PORT_ISC_FALLING_gc
2
  sts (PORTD_PIN3CTRL),r16
3
4
  ldi r16,EVSYS_CHMUX_PORTD_PIN3_gc
5
  sts (EVSYS_CH0MUX),r16

Hat auf Anhieb allerdings erstmal nichts verbessert.

Also bin ich nochmal durch die Beschreibung des DMAC und habe dann den 
entscheidenden Hinweis gefunden.
1
  ldi r16,1<<DMA_CH_ENABLE_bp|1<<DMA_CH_REPEAT_bp|1<<DMA_CH_SINGLE_bp|DMA_CH_BURSTLEN_1BYTE_gc
2
  sts (DMA_CH0_CTRLA),r16 ;                              ^^^^^^

Das Bit nennt sich Single-Shot Data transfer und macht nach dem 
Trigger einen einzelnen Burst-Transfer mit der Burst-Length (hier 1 
Byte).
Ich hatte das vorher schon gelesen, aber weil es um maximale 
Geschwindigkeit ging, irgendwie ausgeblendet...
Allerdings - mit dem Trigger direkt auf den PWM-Kanal C vom Timer hat es 
auch mit diesem Setting nicht richtig funktioniert, es wurden weiterhin 
mehrere identische Bytes hintereinander in den Speicher geschrieben.

Egal, im Prinzip geht es nun, es hat sich aber leider gezeigt, daß der 
DMA-Transfer trotz aller Optimierung langsamer ist als die Methode "zu 
Fuß".
Also lege ich das DMA-Thema erstmal ad acta.

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.