Hallo,
Ich bin gerade dabei, ein Phänomen zu untersuchen, welches ich nicht
nachvollziehen kann.
Aufbau:
- ATMEL Cortex-M7 Controller 300MHz
- Timer TC läuft mit f 150MHz und erzeugt periodisch einen Interrupt.
- In der Interruptroutine wird per DMA eine Ausgabe an der SPI
Schnittstelle gestartet, welche mit 50MHz läuft.
Es befinden sich anfangs nur 3 Befehle in der Interruptroutine, welches
im Listfile so aussehen:
Den Port habe ich zu Testzwecken gesetzt. Im Ernstfall sind nur die
Befehle
1
XDMAC->XDMAC_CHID[1].XDMAC_CSA = &SPIBuffer[0];
2
XDMAC->XDMAC_GE = 0x02;
relevant.
Damit alles so schnell wie möglich läuft, habe ich diese Befehle ganz zu
Anfang in die Interruptroutine gesetzt.
Es funktioniert alles, nur leider zu langsam.
Laut Beschreibung sollte der M7 eine maximale Interruptlatenz von 12
Zyklen haben.
Der Rest der Taktzyklen ergibt sich aus der Laufzeit der o.g. Befehle.
Danach sollte die SPI eigentlich mit der Ausgabe beginnen.
Tut sie auch, aber erst nach ca. 320 Nanosekunden ab Aufruf des
Interrupts.
Den Aufruf des Interrupts kann ich messen, weil der Timer zusätzlich
hardwareseitig einen Port triggert (TIOx).
Der Interrupt wird an der fallenden Flanke des Timersignals generiert
(gelb) und die SPI-Takte sind blau.
Das Phänomen sind nun diese ca. 310-350ns (Jitter durch die Latenz).
Für die paar Befehle scheint mir das sehr lange zu sein.
Caches sind alle aktiviert.
Ich habe sowohl die Interruptroutine als auch die Interruptvektortabelle
ins RAM verlagert, welches angeblich mit f(CPU)/2 = 150MHz läuft.
Das wäre eine Zykluszeit von 6,6 ns.
Es dauert also ca 50 Zyklen bis sich an der SPI etwas tut.
Ich könnte mir nun ca 30 Zyklen für Latenz und Ausführungszeit erklären,
kann aber die restliche Zeit nicht nachvollziehen.
Kann es sein, dass die CPU auf Peripherieadressen (egal ob PIO, SPI, DMA
oder TIMER) immer mit einer langsamen Zugriffszeit bzw Waitstates
arbeitet?
Die Flash-Zugriffszeit ist ja mit ca. 40ns (25MHz) angegeben. Das würde
bei 3 Zugriffen auf Peripherieadressen die verlorenen 120ns erklären.
40ns sind aber gegenüber 6,6ns Takt aus dem RAM eine gefühlte Ewigkeit.
Das würde aber auch bedeuten, dass man daran nichts ändern kann, egal
mit welchen Tricks man die CPU hochkitzelt.
Leider habe ich bisher keine konkreten Angaben gefunden, wie schnell die
Peripheriezugriffe sind. Oder an der falschen Stelle gesucht.
Kennt sich da jemand aus?
Das ist übrigens kein Cortex-M7 Problem. Auch bei den M3 sind mir
verdächtig lange Zeiten aufgefallen, die ich nicht erklären konnte.
Gruß
Joachim
Joachim schrieb:> Kann es sein, dass die CPU auf Peripherieadressen (egal ob PIO, SPI, DMA> oder TIMER) immer mit einer langsamen Zugriffszeit bzw Waitstates> arbeitet?
Das könnte so sein. Näheres verrät bestimmt die Dokumentation des
geheimen Controllers.
> Das ist übrigens kein Cortex-M7 Problem
Yep. Weil dafür nicht der Core zuständig ist, sondern das, was den Core
umgibt. Wie etwa diverse Systembusse.
Joachim schrieb:> - In der Interruptroutine wird per DMA eine Ausgabe an der SPI> Schnittstelle gestartet, welche mit 50MHz läuft.
Warum startet der Timer nicht direkt den DMA?
Und da liegt auch definitiv keinen anderer IRQ Handler davor?
Ich frage das, weil der Namen TC8_Handler IMHO kein Vektor Namen ist -
da kommt normalerweise ein IRQ drin vor, wie in Timer3_IRQHandler.
Ich sehe aber den Startup und die Vektortabelle hier natürlich nicht.
Ein übergeordneter Handler der erstmal mittels Registerzugriffen die
korrekte Funktion ermittelt und per Function-Pointer aufruft, könnte die
~300ns recht gut erklären.
A. K. schrieb:> Näheres verrät bestimmt die Dokumentation des> geheimen Controllers.
Wieso geheim? Cortex-M7 von ATMEL ist die ATSAME70 Serie.
Wobei sich SAME70 und SAMS70 oder auch SAMV70 in dieser Funktion nicht
unterscheiden sollten.
Ingo L. schrieb:> Warum startet der Timer nicht direkt den DMA?
Soweit bin ich nicht. Ich arbeite mich erst in die neue Reihe ein.
Einen Start eines Memory-to-SPI DMA per Timer direkt habe ich noch nicht
probiert. Weiss gar nicht ob das überhaupt geht.
Jim M. schrieb:> Und da liegt auch definitiv keinen anderer IRQ Handler davor?
In der Startup.c sind die Vektoren so benannt, z.B.
wobei mein TC8 aus der Liste fehlt, weil er benutzt wird.
Im MAP-File wird angegeben
Ich habe die Vektortabelle ab Adresse 0x00400000 (Flash) ins RAM kopiert
mit
zu Beginn der Mainroutine.
Das funktioniert auch. Würde es nicht, käme es zum Totalabsturz weil ich
ja VTOR umgeleitet habe.
Im MAP-file wird die Lage meiner VektorTable auch richtig im RAM
angezeigt.
1
0x20431600 vectorTable
Beim Interrupt sollte also der Handler direkt im RAM ohne Umwege
angesprungen werden.
Ich suche weiter. Jedenfalls schonmal vielen Dank für die Tips.
Joachim schrieb:> Ich suche weiter. Jedenfalls schonmal vielen Dank für die Tips.
Das ist löblich, da du aber in einer Hochsprache programmierst (was auch
löblich ist), bist du nun an dem Punkt angekommen an dem meiner Meinung
nur zwei Wege weiterführend sind:
1.
Du stepst das ganze im Debugger durch und guckst, wo deine Takte
verloren gehen und stellt u.U. fest, dass es so wie es ist nicht
änderbar ist
oder
2.
Du machst es gleich richtig:
- Schaue im RM unter DMA nach, welchen DMA Channel dein Timer besetzt.
- Initialisiere diesen DMA Channel für dein SPI
- Initialisiere deinen Timer, dass er ein DMA-Request absetzt
Für einen STM32F0 sieht das so aus:
Hier wird über TIM1 der DMA getriggert, welcher den SPI befeuert und
eine Sinustabelle ausspuckt, sobald der Timer läuft (TIM_Cmd( TIM1,
ENABLE );)
Sollte mich wundern, wenn ein M7 das nicht kann
Habe weiteres herausgefunden.
Ich habe nun einen Port über die Laufzeit der Interruptroutine gesetzt
und gemessen.
Der grobe Rahmen sieht so aus:
1
__attribute__ ((section(".ramfunc")))
2
void TC8_Handler(void)
3
{
4
register int value;
5
PIOA->PIO_SODR = 1<<24;
6
XDMAC->XDMAC_CHID[1].XDMAC_CSA = &SPIBuffer[0];
7
XDMAC->XDMAC_GE = 0x02;
8
value = TC2->TC_CHANNEL[2].TC_SR;
9
if ...
10
{
11
...
12
}
13
else
14
{
15
...
16
17
}
18
PIOA->PIO_CODR = 1<<24;
19
}
Mit 200ns Gesamtdauer inklusive etwas beiläufigem Code nach dem Start
des DMA sieht es gut aus.
Das komische ist aber dass die Ausgabe an der SPI erst NACH ENDE der
Interruptroutine beginnt, obwohl ich den DMA dafür bereits am Anfang der
Routine starte.
Es sieht also danach aus dass es nicht am Aufruf der Interruptroutine
liegt, sondern daran dass zwischen dem Startbefehl des DMA bis zum
Zeitpunkt an dem Daten an der SPI erscheinen über 200ns vergehen.
Das Problem (bzw. meines) scheint also am DMA zu liegen und nicht am
Aufruf des Interrupts.
Der DMA des M7 ist leider recht komplex, vielleicht habe ich da was
übersehen.
Ich versuche das ganze lowlevel zu verstehen und ASF von ATMEL möglichst
zu umgehen, weil das sehr verschachtelt und schlecht dokumentiert ist.
Ich war bisher der Meinung, dass die SPI den DMA triggern muss (mit dem
SPI_SR_TDRE Bit).
Ich gebe ja nicht nur einen Wert an die SPI, sondern eine Reihe von 6
Werten. Hatte ich nicht anfangs erwähnt, weil ich den Fehler leider
woanders gesucht habe.
Das klappt ja alles auch. DMA schiebt nacheinander die 6 Werte an die
SPI, ohne zeitlichen Verlust zwischen den Paketen.
Mit dem Timer soll lediglich bestimmt werden, wann das Senden der 6
Pakete beginnt.
Und eben bis das Senden des ersten Pakets beginnt, dauert es länger als
die gesamte Interruptroutine.
Deaktviere ich z.B. den DMA und schreibe im Interrupt manuell etwas an
die SPI, dann erscheint dies noch während der Laufzeit des Interrupt an
der SPI. Ca 150ns ab Interrupt, also 150ns früher als mit DMA.
Ingo L. schrieb:> Hier wird über TIM1 der DMA getriggert,
Es wird aber pro Timerzyklus nur ein Wert an SPI geschickt, oder sehe
ich das falsch? Dann kann ja der Timer den Takt übernehmen und der
Status der SPI ist unwichtig, solange sicher ist, dass der Timer nicht
schneller als die SPI-Ausgabezeit ist.
In meinem Fall werden aber pro Timerzyklus 6 Werte an SPI geschickt. Die
SPI muss dem DMA also mitteilen, wann die nächsten Daten geschrieben
werden können.
Zumindest weiss ich jetzt dass es kein Interruptproblem ist, sondern ein
DMA-Startproblem.
Vielen Dank!
Joachim schrieb:> Es wird aber pro Timerzyklus nur ein Wert an SPI geschickt
Ja, aber der M7 dürfte etwas mehr Einstellungen besitzen, sodass der DMA
n mal pro Timerinterrupt feuert. Das geht m.M.n.
Angenommen die Daten, welche per DMA rausgeschrieben werden sollen
liegen auch im SRAM, dann hast du einen gleichzeitigen Buszugriff von
DMA und deinem Code auf den RAM. Einer muss dann warten.
Ingo L. schrieb:> Ja, aber der M7 dürfte etwas mehr Einstellungen besitzen, sodass der DMA> n mal pro Timerinterrupt feuert. Das geht m.M.n.
Das hat zwar meiner Meinung nach mit dem Kern direkt nichts zu tun, weil
DMA herstellerspezifisch ist aber es sollte mit dem Boliden von
Controller den du hast ganz sicher gehen und ist die m.M.n. einzig
sinnvolle Lösung für niedrigen Latenzen. Trotzdem musst du etwaige
Buskollisionen im Auge behalten, weil die eben für Jitter sorgen können.
Da bin ich wohl zu spät.
Darüber hinaus:
Evtl. ist es sinnvoll den ISR Code statisch in den Cache zu schieben.
Das habe ich bis jetzt allerdings nur für ARM926ejs getan. Geht aber mit
dem M7 bestimmt auch.
Wie sieht das ganze Verhalten aus, wenn der ISR Code im Flash verbleibt?
Das sollte ja schnell zu testen sein.
Bezüglich der Frage ob es möglich ist, mittels Timer direkt den
DMA-Transfer zu triggern, hier mal ein Auszug aus dem Kapitel des
PWM-Controllers "49.2 Embedded Characteristics" aus dem Datenblatt des
SAM E70:
> 8 Comparison Units Intended to Generate Interrupts, Pulses on Event Lines and
DMA Controller Transfer Requests
Dazu dann noch die Tabelle in "35.4 DMA Controller Peripheral
Connections", wo explizit PWM0 und PWM1 gelistet sind und man kann ganz
sicher sagen, dass man DMA-Transfers auch direkt per Timer/PWM triggern
kann.
Ich muss aber sagen, dass die Atmel-Datenblätter an der Stelle schon
ganz schön verschwurbelt sind. Bei anderen Herstellern findet man
eindeutig schneller einen Hinweis ob das so überhaupt möglich ist und
wenn ja, dann wie.
Christopher J. schrieb:> dann hast du einen gleichzeitigen Buszugriff von> DMA und deinem Code auf den RAM. Einer muss dann warten.
Das ist mir klar.
Es wundert mich eben nur, dass trotz dieses ganzen Multiport-RAM und
mehrfacher Buscontroller es trotzdem ca. 60 Taktzyklen braucht, bis der
DMA nach der Freigabe mal in die Gänge kommt und die SPI befeuert. Eine
Latenz von einigen wenigen Buszyklen wäre ja kein Problem.
60 Taktzyklen für etwas erinnern mich eher an die alten 8086-Zeiten :-)
Ja es sind 60, nicht 30, denn bisher hatte ich mit 150MHz gerechnet.
Stimmt aber nicht, denn wie es scheint läuft der Code mit 300MHz aus dem
Cache.
200ns Verzögerung DMA<>SPI / 3,33ns = 60.
Test schrieb:> Wie sieht das ganze Verhalten aus, wenn der ISR Code im Flash verbleibt?> Das sollte ja schnell zu testen sein.
Es ändert sich messbar gar nichts.
Der Cache scheint dies bei der kurzen Routine gleich zu beschleunigen,
egal woher der Code stammt und selbst egal ob die Vektortable im Flash
oder im RAM liegt.
Ich habe folgende Kombinationen getestet:
- Code im Flash, Caches deaktivert, Dauer ca. 900ns Interruptstart bis
SPI
- Code im RAM, Caches deaktviert, Dauer ca. 640ns
- Code im Flash, D-Cache aktiviert, Dauer 600ns
- Code im Flash, D+I-Cache aktivert, Dauer 320ns
- Code im RAM, D+I-Cache aktiviert, Dauer 320ns
Das erscheint plausibel. Aus dem Cache läuft alles mit maximaler
CPU-Frequenz. Aus dem RAM ohne Cache mit halbem Takt (150MHz).
Das sind die 640 zu 320ns Laufzeitänderung.
RAM + Cache zusammen bringt nichts, da Cache 2x schneller als Code im
RAM.
Das Flash ist sogar noch überraschend schnell.
Ich glaube im Moment, die letzte Möglichkeit ist, alles in den TCM zu
verfrachten und schauen ob es beim vollen CPU-Takt und ohne den Cache
etwas bringt bzw. ob der DMA irgendwie vom Cache beeinflusst wird.
Tja, sonst muss dann eben so sein.
Gruß
Joachim
Joachim schrieb:> Ich glaube im Moment, die letzte Möglichkeit ist, alles in den TCM zu> verfrachten und schauen ob es beim vollen CPU-Takt und ohne den Cache> etwas bringt bzw. ob der DMA irgendwie vom Cache beeinflusst wird.
Mach das nicht, das sind keine Zugriffsprobleme auf die Speicher und da
läuft an anderer Stelle etwas nicht richtig.
Lass mal den Interrupt weg und trigger den DMA nachdem Du einen Portpin
gesetzt hast, um die Latenz zu sehen.