PDA

Archiv verlassen und diese Seite im Standarddesign anzeigen : XMEGA: Zeitproblem mit zwei parallel sehr schnell laufenden Timern



erik_wolfram
09.07.2014, 13:23
Hallo,

ich versuche grade eine Messung aufzubauen, bekomme jedoch Probleme bezüglich der Timer.
Für die Messung verwende ich ein Atmel XPLAINED Board mit einem XMEGA128A1. Der Prozessor läuft für diese Aufgabe mit dem internen 32 MHz-Takt.
Der XMEGA soll zeitlich getaktet ein Signal mit Hilfe des DACs generieren und parallel ein AD-Wandler mit einer 16-Bit Parallelschnittstelle auslesen. Das Signal soll mit ca. 40 Samples bei 10 kHz ausgegeben werden - damit ergibt sich ein Takt von 2,5 µs für den ersten Timer. Der AD-Wandler soll parallel 800 kSamples aufnehmen, welche alle 1,25 µs mit Hilfe des DMAs ausgelesen werden sollen.
Beides bekomme ich einzeln zum laufen - die Zeiten liegen dann in dem gewünschten Bereich. Für Versuchszwecke gebe ich das Signal als Sinus mit dem DAC aus und anstatt den AD-Wandler auszulesen gebe ich Flanken an einem Port aus (Der AD-Wandler ist Hardwareseitig noch nicht vorhanden) und lasse mir diese auf einem Oszilloskop anzeigen.
Aktiviere ich aber beide Timer, so kommt es mindestens bei einem Timer zu erheblichen Verzögerungen oder der Timer arbeitet deutlich schneller als eingestellt oder gar unregelmäßig.

Ich verwende die 16 Bit Timer TCC0 und TCC1. Für Versuche habe ich das Interrupt leer gelassen und erhalte schon im Simulator (Atmel Studio 6.1) abweichende Zeiten. Auch bei der Ausgabe und dem Einlesen der Spannungen mit dem Oszilloskop sieht es nicht anders aus.
Auch das Spielen mit den Interrupt-Levels der Timer hat mir nicht weiter geholfen. Weiterhin ist mir aufgefallen, dass die Optimierungs-Einstellung im Atmel Studio ebenfalls negativen Einfluss auf die Zeiten hat.
Leider finde ich keinen Ansatzpunkt wo ich nun mit der Fehlersuche weiter vorgehen soll.
Ich hoffe es findet sich wer, der mir einen guten Hinweis geben kann. Ich bin mir bewusst, dass ich bezüglich der Anforderungen an die Schnelligkeit mit dem XMEGA an die Grenzen stoße, bin aber optimistisch, dass ich die Aufgabe mit den funktionierenden Timern lösen kann.

Mein Problem besteht grundsätzlich daraus, zwei 16-Bit Timer so einzustellen, dass diese im µs-Bereich exakt arbeiten ohne sich gegenseitig zu stören.

Gruß Erik

Che Guevara
09.07.2014, 14:37
Hi,

du willst also 2 Timer verwenden, um mit dem einen alle 2.5us und mit dem anderen alle 1.25us einen Interrupt zu generieren. Richtig?
Wenn wir mal von 1.25us bei 32MHz ausgehen, wäre da ein Interrupt alle 40 Takte. Mit Register sichern und tralala, kommt man da ggf schon über diese 40 Takte. Dann kommt irgendwann auch noch der 2. Timer, dessen Interrupt schon lange pendet und sofort in die ISR springt.
Was hast du den vor? Evtl. gäbe es ja einfachere Wege, um deine Aufgabe zu verwirklichen, so wird das denke ich mal nichts.
Evtl. könntest du die 2 Timer ja auch durch 1 ersetzen, der alle 1.25us triggert und bei jedem 2. mal hast du dann auch deine 2.5us.

Gruß
Chris

Peter(TOO)
09.07.2014, 16:40
Hallo Erik,

Mit Interrupts wird das so nichts werden!
Es dauert jedesmal etwas Zeit, biss der laufende Prozess unterbrochen, gesichert und die Interrupt-Routine aufgerufen ist.
Die Verzögerungszeit hängt dabei schon mal vom gerade laufenden Befehl ab, von gesperrten IRQs reden wir mal gar nicht!
Das Problem liegt nicht bei den Timern, sondern in der Software, welche nur endlich schnell ist.

Die einzige Möglichkeit sehe ich darin, dass du das über die DMA machst.

Du baust dir z.B. die Sinustabelle auf.
Dann überträgst du diese Tabelle per DMA zum DAC
Die DMA triggerst du mit dem einen Timer.

Umgekehrt geht's beim ADC.
Hier konfigurierst du alles so, dass die DMA die Messwerte in einer Tabelle, evtl. Ringbuffer. ablegt.
Hier musst du aber sicherstellen, dass die CPU die Daten auch schnell genug verarbeiten kann.

Die DMA "stielt" dir dann nur die paar Takte, welche für das Lesen und Schreiben auf dem Bus nötig sind.

MfG Peter(TOO)

Besserwessi
09.07.2014, 18:16
2 so schnelle Aufgaben per Interrupt geht nicht, dafür die die Verzögerungen zu lange. Mit DMA (wenigstens für einen der Aufgaben) gibt es beim Xmega eine elegante Lösung. Möglich wäre sonst noch ein starrer ASM Code der Zyklengenau ausgeklügelt ist - ist aber etwas aufwendig.

PICture
09.07.2014, 18:26
Hallo!

@ erik_wolfram

Damit zwei Timer völlig unabhängig voneinander arbeiten könnten, würde ich dafür zwei µC nehmen, was am einfachsten ist. Sonst müsste man nur mit Flagsetzen für Hauptprogramm, also mit gesperrten Unterbrechungen arbeiten (als ASMan habe ich k.A. über "Cäh"). ;)

Crazy Harry
09.07.2014, 19:38
Ich bin kein C-Programmierer und kann dir auch nichts zu deiner Frage sagen, aber wenn du mehr Power brauchst, warum taktest du den XMega nicht mit 64MHz ?

Wsk8
09.07.2014, 21:43
32MHz = 31,25 ns / Takt
1,25 µs = ~40 Takte

Selbst wenn du nur den 1,25 µs Timer laufen lässt, ist das fast nicht möglich. (Bei 40 Takten für eine ISR darfst du im Prinzip gar nichts berechnen um noch mitzukommen)

mfg

erik_wolfram
15.07.2014, 17:03
Hallo,

vielen Dank für die vielen Antworten. Ich merke langsam, dass meine Anfoderungen die Rechenleistung des XMEGA's überschreiten. Wenn ich bald keine weitere Möglichkeit sehe bin ich wohl gezwungen einen stärkeren Controller zu verwenden.

Abgesehen davon, konnte ich nun eine Anforderung verringern: es werden nicht mehr 800 ksps sondern 400 ksps benötigt. Die zu messenden Spannungen sollen nun mittels OPV's verrechnet werden.
Damit habe ich wieder einen kleinen Lichtblick, stoße aber wieder auf die Grenzen des XMEGAs.
Als Option habe ich jetzt zwei Einzel-AD-Wandler mit SPI-Schnittstelle erwägt. Diese lassen sich in einer Daisy-Chain verschalten und verringern zu mindest die Komplexität der Ansteuerung und des Aufbaus der Schaltung.
Leider ergibt sich für diese AD-Wandler eine asynchrone zeitliche Ansteuerung, welche das Timing erschwert.

Diese AD-Wandler haben folgendes Verhalten:

Sie benötigen ein Signal für die AD-Wandlung "CONVST" welches über die AD-Wandlung und die Ausgabe erfolgt (high-Zustand) und dann kurz pausieren muss (low-Zustand).
Das lässt sich recht einfach mit einem PWM-Signal realisieren.

Nachdem das CONVST-Signal angelegt wurde benötigt die Wandler 1,4 µs bis zur Ausgabe. Diese erfolgt mittels SPI und beginnt mit dem ansteigenden SCK-Signal des XMEGAS und umfasst 2 x 16-bit also 4 byte.
Das Auslesen dieser Werte ist mit DMA und dem Interrupt Flag des SPI-Moduls kein Problem und ist recht einfach gehalten.
Mehr Probleme macht hier die Erzeugung des SCK-SIgnals indem Dummy-Bytes in das SPI-Data-Register geschoben werden. Um die benötigte Datenrate zu erreichen muss das SPI mit 16 Mhz laufen. Damit wird ein Byte innerhalb von 0,5 µs übertragen. Damit müsste theoretisch alle 16 Clockcylces ein neuer Wert in das Data-Register geschoben werden. Wohl oder Über ist das zu wenig für jeden Timer und muss wohl Quick&Dirty erfolgen.

Das Problem mit den Timern konnte ich eingrenzen. Man muss auf jeden Fall vermeiden, dass zwei Timer gleichzeitig auslösen. Da ich recht grade und bekannte Zeiten habe konnte ich die Timer über das CNT-Register entsprechend vorladen und so verschieben, dass sich Timer mit einem vielfachen zu einem anderen Timer nicht gegenseitig stören.
Die Ursache hierfür liegt wohl in einem Temporären Register welches sich alle Timer teilen. Wird ein Interrupt ausgelöst während ein anderes noch abgearbeitet wird wird der Wert in diesem Register überschrieben.
In Anbetracht dessen finde ich die Priorisierung der Timer mittels der LVL etwas unnütze. Soweit ich mich erinnere konnte man dieses Register in Assembler hierfür kopieren und nach der Interrupt wieder herstellen...


Der DMA für die Erzeugung des Sinus ist kein Problem - hierfür gibt es ausführliche Tutorials von Atmel. So erfolgt die Ausgabe des SInus ohne Rechenleistung. Es dürfen ledigleich keine Timer miteinander kollidieren.

Anbei: der XMEGA ist für maximal 32 Mhz ausgelegt, extern dürfen sogar nur 16 Mhz Quarze angeschlossen werden.

Gruß Erik

Crazy Harry
15.07.2014, 19:32
Dann schau dir mal die AppNote 1019 und 1020 von Atmel an. Da steht:

The 32MHz Internal Oscillator can be tuned to run at any frequency between 30MHz and 55MHz
Es soll auch eine weiter AppNote geben in der von 64MHz geschrieben wird. Ich selber betreibe 2 GPS-Tachos mit 64MHz auf XMega256A3U seit einigen Monaten ohne Probleme.

erik_wolfram
16.07.2014, 20:17
@CrazyHarry:

Danke für den Hinweis. Das war schonmal eine große Hilfe - konnte den XMEGA128A1 bis ca. 55 MHz hochtakten - danach hat er aber gesponnen - für den vewendeten µC liegt hier wohl die Grenze (wie im Datenblatt angegeben).

Habe heute noch fleißig weiter probiert und dabei kam mir eine vermeintlich gute Idee - die jedoch in der Ausführung Zicken macht -warum weiß ich noch nicht.
Ich hatte die Idee, um die ganzen Interupts zu umgehen DMA zu verwenden. Für die Generierung des Analogen Ausgangssigals und den SPI-Lesevorgang kein Problem. Die Initiierung der AD-Wandlung lies sich auch rein Hardwaretechnisch über ein PWM-Timer lösen.
Also bis jetzt habe ich es geschafft alles ohne Software umzusetzten - lediglich beim Absetzten der 4 Bytes mittels SPI gibt es Probleme.

Zum Timing:

Ein Zyklus dauert 5 µs.
Die AD-Wandlung benötigt 1,4 µs vom Zyklusbeginn.
Dann sollen alle 0,5 µs willkürliche Bytes in das SPI-Data-Register geschrieben werden.
Das soll sich so jeden Zyklus wiederholen.

Nach langen Überlegen hatte ich folgende Idee:
Ein separater Timer löst mit jedem Overflow ein DMA-Schreibzugriff auf das SPI-Data-Register aus.
Ein weiterer DMA-Kanal greift zyklisch auf einen Array mit den Timer-Werten zu und schreibt diese in das PER-Register des benannten Timers.

Also so, dass das PER-Register des Timers rein Hardwaremäßig aktualisiert wird, ohne das der µC eine Programmzeile benötigt.

Theoretisch sollte das doch funktionieren? (2 DMA's können vom gleichen Timer ausgelöst werden - das habe ich getestet)
Leider kann ich das ganze nicht im Simulator prüfen - auf dem Board wird mir die Änderung des PER-Registers via DMA unterschlagen.

Hier nochmal der DMA-Zugriff auf das Timer-Register (vereinfacht):


volatile uint8_t TestNum = 200;

void SetupWriteChannel( DMA_CH_t * dmaChannel )
{
DMA_SetupBlock(
dmaChannel,
TestNum,
DMA_CH_SRCRELOAD_NONE_gc,
DMA_CH_SRCDIR_FIXED_gc,
(void *) &(TCD0.PER),
DMA_CH_DESTRELOAD_NONE_gc,
DMA_CH_DESTDIR_FIXED_gc,
1,
DMA_CH_BURSTLEN_1BYTE_gc,
0,
true
);

DMA_EnableSingleShot( dmaChannel );
DMA_SetTriggerSource( dmaChannel, DMA_CH_TRIGSRC_TCD0_OVF_gc );
}

...
DMA_Enable();

DMA_CH_t * WriteChannel = &DMA.CH1;
SetupWriteChannel( WriteChannel );
DMA_EnableChannel( WriteChannel );

Die Änderung des PER-Werts wird leider nicht übernommen - wo kann hier der Fehler liegen?

Vielen Dank für die Hilfe!

Gruß Erik

Che Guevara
16.07.2014, 21:16
Hi,

ohne zu wissen, ob deine SetupWriteChannel Funktion (und auch die anderen) richtig arbeitet, fällt mir auf, dass du den DMA auf 1Byte-Burst sowie auf SCRDIR_FIXED gestellt hast. Das PER Register der Timer ist aber 16Bit breit, das passt schonmal nicht.
Außerdem weiß ich nicht, ob du das PER Register überhaupt irgendwo veränderst? Dir ist klar, was dieses Register macht?

Gruß
Chris

erik_wolfram
17.07.2014, 08:57
Vielen Dank für den Hinweis - ich habe schlicht und einfach vergessen, dass es ein 16 Bit Register ist.
Jetzt funktioniert (theoretisch) alles bestens - eine komplette Messung von 400 ksps mit 16 Bit ohne jegliche CPU-Last (bzw. laufenden Programm-Code).

Der variable mit DMA veränderbare Timer sieht jetzt so aus:


void SetupWriteTimer( void )
{
// Start Timer/Counter C0 with actual clock (32MHz) divided by 4
TCD0.CTRLA = (TCD0.CTRLA & ~TC0_CLKSEL_gm) | TC_CLKSEL_DIV1_gc;
// AD_Wandlung 1,5 µs - Per = 47
TCD0.PER = 30;
// Timer vorladen um Kollision mit Timer TCC0 zu vermeiden
TCD0.CNT = 15;
// Priorität setzten
TCD0.INTCTRLA = TC_OVFINTLVL_MED_gc;
}

#define Interval 4
volatile uint16_t WriteDelay[Interval] = { 20, 20, 20, 96 };

void SetupWriteDelayChannel( DMA_CH_t * dmaChannel )
{
DMA_SetupBlock(
dmaChannel,
WriteDelay,
DMA_CH_SRCRELOAD_BLOCK_gc,
DMA_CH_SRCDIR_INC_gc,
(void *) &(TCD0.PER),
DMA_CH_DESTRELOAD_BURST_gc,
DMA_CH_DESTDIR_INC_gc,
Interval * 2,
DMA_CH_BURSTLEN_2BYTE_gc,
0,
true
);

DMA_EnableSingleShot( dmaChannel );
DMA_SetTriggerSource( dmaChannel, DMA_CH_TRIGSRC_TCD0_OVF_gc );
}



Ich werde jetzt noch einen Test mit der SPI-Geschwindigkeit machen und prüfen, ob fehlerfrei übertragen wird - dann geht es zum Hardware-Aufbau.