Guten Morgen,
ich habe eine Funktion geschrieben, die Takte verbrauchen soll:
1
/* Schleife ausfuehren, um Rechenzeit zu verbrauchen
2
* nLoop: Anzahl der Schleifendurchlaeufe (Compilezeit-Konstante)
3
* Verbraucht 4 * nLoop + X Takte (FIXME: Wie gross ist X ?) */
4
static_inlinevoidloop4cycles(uint64_tnLoop)
5
{
6
assert(nLoop!=0);
7
asmvolatile(\
8
"1: subs %0, %0, #1 \n\t" \
9
" bne 1b\n\t" \
10
" nop\n\t" \
11
:"+r"(nLoop));
12
}
Diese Funktion braucht (nLoop * 4 + X) Takte und wird mit einer zur
Compilezeit bekannten Konstante aufgerufen.
assert() wird bei einer Compilezeit-Konstante wegoptimiert.
Jetzt ist die Frage: Wie groß ist "X"? "X" müßte ja irgendwie den
Overhead abbilden, um die Konstante aus dem Flash in ein Register zu
kopieren, die letzte Schleifeprüfung durchführen und wieder
herauszuspringen.
Viele Grüße
Ductus Cochlearis
Da musst du in der Dokumentation deines konkreten Mikrocontrollers
nachschauen, wie viele Takte die einzelnen befehle benötigen. Außerdem
solltest du berücksichtigen, dass viele ARM Mikrocontroller Wait States
einfügen, weil der Flash Speicher langsamer ist. Um die Performance
wiederum zu verbessern, setzen viele ARM Controller einen
Prefetch-Buffer ein, auf dessen Eigenschaften und Konfiguration es hier
ankommt. Es wird auch eine Rolle spiele, was unmittelbar VOR dem Aufruf
der Funktion loop4cycles() passierte. Dazu kommt dann noch der ganze
Code, den der Compiler am Anfang und Ende der Funktion sowie zum Aufruf
der Funktion erzeugt (Register sichern, Springen, etc). Und wie deiser
Code aussieht, hängt wiederum von deiner Programmstruktur und den
Compiler-Optionen ab.
Das sind zu viele Variablen!
Bei einer C-Funktion vergiß das mit Taktzählung, da hängt zuviel vom
Compiler ab. Laß Dir beim Compilieren das Assemblerlisting ausgeben und
zähle dann ab. Zudem haben Deine Einstellungen von
Dcache/Icache/Prefetch auch noch Auswirkungen.
Falls Du mit einer Auflösung von > 1 µs zufirden bist, mach das mit
DWT_CYCCNT, siehe ARM Cortex-M4 Technical Reference Manual (nicht ST!).
abgesehen davon das Warteschleifen nur von Anfängern verwendet werden
ist es wie bei fast jedem Prozessor das es eine unterschied macht ob der
Branch ausgeführt wird oder nicht.
Also stimmt deine Formel schon mal nicht.
bei arm steht für Branch Conditional:
1 or 1 + P[d]
[d] Conditional branch completes in a single cycle if the branch is not
taken.
und für P
The number of cycles required for a pipeline refill. This ranges from 1
to 3 depending on the alignment and width of the target instruction, and
whether the processor manages to speculate the address early.
http://infocenter.arm.com/help/index.jsp?topic=/com.arm.doc.ddi0337h/CHDDIGAC.html
Nop schrieb:> Falls Du mit einer Auflösung von > 1 µs zufirden bist
Bin ich nicht.
Ich rechne beim Overhead "X" mit irgendetwas zwischen -4 und +10. Ziel
ist tatsächlicht irgendetwas Richtung taktgenaue Auflösung.
Cache- und Prefetch-Buffer hat mein Target nicht.
Inwiefern können bei einer Funktion, bei der die Anzahl der
Schleifendurchläufe schon von Anfang an feststeht, die Flash-Waitstates
hineinspielen?
Karl der erste von oben schrieb:> Warum ist die Auflösung > 1 us?
Die Auflösung ist natürlich taktgenau, aber wenn man das mit einer
simplen Abzählschleife abfragt, dann ist der Schleifenoverhead erst ab
ungefähr 1 µs vernachlässigbar.
Will man wesentlich weiter runter, dann muß man den Schleifenaufwand in
die Taktzyklen reinrechnen und abziehen, was seinerseits wieder Takte
kostet. Und dann muß man aufpassen, daß man nicht zuviel abzieht und
einen wrap-around hinlegt, also muß da auch noch wieder ein Check für
rein, der Takte kostet.
Volle2 schrieb:> abgesehen davon das Warteschleifen nur von Anfängern verwendet werden
So wirds wohl sein. Profis verwenden auch für 1µs einen Timer, oder
gleich ein FPGA. ;-)
Ductus cochlearis schrieb:> Ich kann kein Assembler.
Offensichtlich ja doch, wie man am Eingangspost sieht.
> Ich muss mit dem klarkommen, was ich bekommen kann.
Dann mußt das entweder lernen oder ohne taktgenaue Verzögerung
auskommen. Dein Bustreiber wird's überleben.
Man kann Delayloops selbstkalibrierend bauen, wenn man eine Zeitreferenz
hat. Beispielsweise den Systick. Dann sind sowohl Durchlaufzeit als auch
Overhead daraus ableitbar.
Und man kann aktiv wartende Delays natürlich auch gleich über den bei
allen Cortexen vorhanden Systick abwickeln.
A. K. schrieb:> Und man kann aktiv wartende Delays natürlich auch gleich über den bei> allen Cortexen vorhanden Systick abwickeln.
Im Bereich unter 1µs? Idealerweise wenige Takte? Wirklich?
Sobald du einen Branch verwendest ist die Berechnung wie schon erwähnt
nicht eindeutig (Pipline neu füllen; Sprungziel Vorhersage). Da du
allerdings meintest das die Anzahl deiner Durchläufe zur Compile-Zeit
bekannt ist, kannst du Loop unrolling verwenden
(https://de.wikipedia.org/wiki/Loop_unrolling). Damit bekommst du eine
Function ohne Branch, was zur Berechenbarkeit beiträgt.
Für 6 Takte warten einfach 6 mal "NOP;".
Allerdings wird sich die Funktion an sich nicht so verhalten wie du es
dir vorstellst, da zwar der Assembler-Teil deine berechnete Zeit
verbraucht, der Overhead der umschließenden C-Funktion allerdings
unbekannt ist.
Nop schrieb:> Im Bereich unter 1µs? Idealerweise wenige Takte? Wirklich?
Unter 1µs geht schon, abhängig vom Takt, aber ich sehe grad, dass er als
Ziel "taktgenau" nachgelegt hat. Das ist allerdings härterer Tobak.
Volle2 schrieb:> abgesehen davon das Warteschleifen nur von Anfängern verwendet werden
Ach so... Wie soll ich denn sonst warten im niederpriorsten Task? Oder
wenn ich nur ganz kurz warten möchte und jeglicher Sprung in ein rtos
o.ä. zu lange dauern würde?
Nop schrieb:> Die Auflösung ist natürlich taktgenau, aber wenn man das mit einer> simplen Abzählschleife abfragt, dann ist der Schleifenoverhead erst ab> ungefähr 1 µs vernachlässigbar.>> Will man wesentlich weiter runter, dann muß man den Schleifenaufwand in> die Taktzyklen reinrechnen und abziehen, was seinerseits wieder Takte> kostet. Und dann muß man aufpassen, daß man nicht zuviel abzieht und> einen wrap-around hinlegt, also muß da auch noch wieder ein Check für> rein, der Takte kostet.
Ach so meinst du das. Auflösung sehe ich trotzdem bei einem Taktzyklus,
die Genauigkeit ist natürlich schlechter. Wie schlecht müsste man Mal
konkret nachmessen. Bei > 100 MHz ist das aber sicher besser als 1 us,
oder? Ich sehe da 1x Overhead zum berechnen des zielwerts und dann eine
zufällige Latenz nach Erreichen dieses Werts. Je nachdem wann man ihn
trifft. In Summe schätze eigentlich deutlich weniger als 20 Takte.
Ductus cochlearis schrieb:> ich habe eine Funktion geschrieben, die Takte verbrauchen soll
Das ist auf einem ARM basierten Mikrocontroller einfach nur Unfug!
Es gibt genügend Mechanismen um genaue Timings zur Signalgenerierung und
zeitkritische Datenübertragungen zu realisieren, ohne dass die gesamte
"Maschine" mittels Warteschleifen lahmgelegt werden muss.
Es seien z.B. genannt: Timer, DMA, Interrupts
Johnny B. schrieb:> Das ist auf einem ARM basierten Mikrocontroller einfach nur Unfug!
Das war jetzt aber tatsächlich gar nicht die Frage. Die Frage war:
Wieviele Takte braucht diese Funktion?
A. K. schrieb:> Volle2 schrieb:>> abgesehen davon das Warteschleifen nur von Anfängern verwendet werden>> So wirds wohl sein. Profis verwenden auch für 1µs einen Timer, oder> gleich ein FPGA. ;-)
Richtig!
Ein µC unterscheidet sich von einem Prozessor dadurch das er
spezialisiert Peripheriemodule z.B für Kommunikation, Signalerzeugung
oder Erfassung hat. Wenn der ausgewählte µC nichts für die Aufgabe hat,
dann ist es der Falsche.
Deswegen gibt es auch mehr als einen.
mit Delays was zusammenbasteln bleibt immer basteln.
Da ist dieser Thread hier wiedermal ein schönes Beispiel.
Wir können es ja so machen: Wenn ich eine brauchbare Antwort auf meine
Frage bekomme, mache ich danach auch einen Thread auf mit der Frage "Wie
schlimm sind Wartezyklen auf einem ARM Cortex M?". Diesen Thread hier zu
kapern und zu mißbrauchen, ist ja alles andere als hilfreich.
Johnny B. schrieb:> Das ist auf einem ARM basierten Mikrocontroller einfach nur Unfug!
Sehr allgemein formuliert. IMO gibt es schon Anwendungen dafür.
> Es gibt genügend Mechanismen um genaue Timings zur Signalgenerierung und> zeitkritische Datenübertragungen zu realisieren, ohne dass die gesamte> "Maschine" mittels Warteschleifen lahmgelegt werden muss.> Es seien z.B. genannt: Timer, DMA, Interrupts
Das sind zwei Paar Schuhe. Warten muss ja nicht immer gleich hoch genau
sein.
Du hast aber schon Recht, dass sich taktgenaues Timing zb zum
pinwackeln so nicht realisieren lässt, weil es neben dem Prozessor core
selbst noch viele andere jitterquellen gibt.
Die Frage ist, ob das eine akademische Übung werden soll bei der man
viel lernt oder ob es eine konkrete Anwendung gibt. Dann müsste der OP
noch Licht ins Dunkel bringen.
Volle2 schrieb:> Ein µC unterscheidet sich von einem Prozessor dadurch das er> spezialisiert Peripheriemodule z.B für Kommunikation, Signalerzeugung> oder Erfassung hat. Wenn der ausgewählte µC nichts für die Aufgabe hat,> dann ist es der Falsche.
Also kein 1-Wire Master ohne 1-Wire-Hardware. Naja.
Karl der erste von oben schrieb:> Ach so... Wie soll ich denn sonst warten im niederpriorsten Task?
Wenn man ein RTOS hat: mit der Wartefunktion des RTOS. Oder auf ein
Ereignis.
> wenn ich nur ganz kurz warten möchte und jeglicher Sprung in ein rtos> o.ä. zu lange dauern würde?
Das ist der Punkt, der hier wesentlich ist, ja.
> In Summe schätze eigentlich deutlich weniger als 20 Takte.
Dann noch die C-Funktion mit Overhead. Siehe hier, etwas weiter unten
mit dem CYCCNT:
https://www.carminenoviello.com/2015/09/04/precisely-measure-microseconds-stm32/
Da kommt man mit der Lösung auf 1.2 µs, also 20% Abweichung. Wesentlich
drunter ist das mit so einer einfachen Implementierung also nicht
brauchbar.
Ductus cochlearis schrieb:> Wenn ich eine brauchbare Antwort auf meine Frage bekomme
Hast Du. So wie im vorigen Thread auch schon. Wenn taktgenau, dann
Assembler. Das hat sich in den letzten Tagen auch nicht geändert. Das
IST die brauchbare Antwort, und daß sie Dir nicht gefällt, ändert auch
nichts daran.
Ductus cochlearis schrieb:> "Wie schlimm sind Wartezyklen auf einem ARM Cortex M?"
Das kann ich Dir jetzt schon beantworten: Sehr schlimm!
Wenn Du Dich mit der Geschichte der ARM-CPU befasst wirst Du schnell
erkennen, wofür sie entwickelt wurde: Hohe Rechenleistung bei kleinem
Stromverbrauch.
Die CPU ist also nicht dafür optimiert um in Warteschlaufen zu verweilen
sondern um in möglichst kurzer Zeit viel Programmcode auszuführen.
Wieviele Takte ein Befehl braucht kannst Du ja selber in den
Datenblättern heraussuchen, aber das ist insofern wenig hilfreich, weil
Mechanismen wie Pipelining, ART Accelerator, Waitstates, etc. auch noch
dreinfunken.
Irgendwie wäre es jetzt sinnvoll, die Antworten, die zu der anderen
Frage besser als zu dieser passen, in den entsprechenden Thread zu
verschieben. Aber wahrscheinlich muß ich ohne diesen Luxus leben. Denn:
Ductus cochlearis schrieb:> Ich muss mit dem klarkommen, was ich bekommen> kann.
Nop schrieb:> dann ist der Schleifenoverhead erst ab> ungefähr 1 µs vernachlässigbar.
Der Schleifenoverhead muss nicht vernachlässigbar sein, solange man ihn
kennt. Die Korrektur ist eine einfache lineare Funktion.
Hoppla, ich habe mich verwechselt. Bin ja jemand anders.
Nop schrieb:
> dann ist der Schleifenoverhead erst ab> ungefähr 1 µs vernachlässigbar.
Der Schleifenoverhead muss nicht vernachlässigbar sein, solange man ihn
kennt. Die Korrektur ist eine einfache lineare Funktion.
Bulbus duodenitis schrieb:> Die Korrektur ist eine einfache lineare Funktion.
Auch wieder mit Overhead, und der Check gegen Wrap-Around auch. Wobei
man das in C++ noch mit constexpr abfackeln könnte, aber in C nicht.
Zudem wirst Du keine Schleife ganz ohne Overhead hinbekommen, weswegen
taktgenaue Verzögerung mit so einer Schleife nichtmal bei null
Durchläufen möglich ist.
Nop schrieb:> Auch wieder mit Overhead, und der Check gegen Wrap-Around auch. Wobei> man das in C++ noch mit constexpr abfackeln könnte, aber in C nicht.
Wobei man das auch in C etwas umständlicher machen könnte und dann ganz
ohne Schleife:
Beitrag "Re: Delay-Funktion STM32: delay_ns()"
> Inwiefern können bei einer Funktion, bei der die Anzahl der> Schleifendurchläufe schon von Anfang an feststeht, die Flash-Waitstates> hineinspielen?
Ich erkläre das mal am Beispiel eines STM32F103 und diesem (nicht
optimalen) Code:
1
voiddelay(uint16_tmsec)
2
{
3
for(uint32_tj=0;j<2000*msec;j++)
4
{
5
__NOP();
6
}
7
}
Diese Funktion soll eine gewisse Anzahl von ms warten und ist für 8MHz
Systemtakt ausgelegt. Jede Wiederholung der Schleife benötigt 4
Takzyklen (1 für den NOP und 1 für den Rücksprung). Dazu kommt aber noch
Overhead am Anfang und Ende der Funktion, die ich hier nicht
berücksichtigt habe. Die tatsächliche Verzögerung wird also immer etwas
länger sein, als gewollt.
Wenn du jetzt die Taktfrequenz um das achtfache auf 64MHz erhöhst, musst
du zwei Waitstates für die Flash-Zugriffe konfigurieren, weil der Flash
nicht so schnell ist.
Zwar hat der µC einen Prefetch Buffer, der Befehle im Voraus laden kann,
aber der wird bei jeden Rücksprung nach "oben" verworfen. Unterm Strich
läuft die Funktion nach der Erhöhung der Taktfrequenz daher nicht 8x so
schnell, sondern nur 6x so schnell.
Wenn wir das Ganze in kleinerem Maßstab (Nanosekunden) durchführen,
müssen wir außerdem noch berücksichtigen, was unmittelbar VOR dem Aufruf
der Funktion passiert ist, denn davon hängt ab, wie weit der
Prefetch-Buffer gefüllt ist.
Andere ARM Modelle haben teils ein komplexeres Buffering System, da wird
es dann noch schwieriger zu erklären.
Nop schrieb:> weswegen> taktgenaue Verzögerung mit so einer Schleife nichtmal bei null> Durchläufen möglich ist.
Natürlich ist es das, solange der Overhead bekannt ist. Und das nicht
einmal schwer:
Nop schrieb:> Auch wieder mit Overhead,
Bei bekannter Taktfrequenz und konstanter Zeit kann die Umrechnung von
Zeit in Durchläufe konstant werden und damit im C-Compiler verschwinden.
Auch mit Overhead.
A. K. schrieb:> Bei bekannter Taktfrequenz und konstanter Zeit kann die Umrechnung von> Zeit in Durchläufe konstant werden und damit im C-Compiler verschwinden.> Auch mit Overhead.
Wenn man das so straight-forward machen will, braucht man aber
constexpr, und das wurde in C++ deswegen eingeführt, weil das eben nicht
automatisch so geht. C hat kein constexpr.
Ductus cochlearis schrieb:> Natürlich ist es das, solange der Overhead bekannt ist.
Nicht ohne constexpr. Und Walter, wieso Du eigentlich jetzt im
Wochentakt genau dasselbe fragst, erschließt sich mir auch nicht. An dem
Punkt warst Du letzte Woche auch schon. Nein, es wird Dir niemand Deinen
Assembler-Treiber schreiben, nicht letzte Woche und auch nicht diese.
Nimm einfach die Lösung, die ich am Ende des letzten Threads vorgestellt
habe und bau Dir per CLI-Tool die Macros als Teil des Builds. Was ist
daran so schwer, daß Du das in einer Woche nicht geschafft hast?
Nop schrieb:> CLI-Tool die Macros als Teil des Builds.
Per Extra-Tool etwas zu machen, was ein Compiler völlig allein selbst
kann, ist meiner Meinung nach nicht sinnvoll.
Nop schrieb:> Wenn man das so straight-forward machen will, braucht man aber> constexpr, und das wurde in C++ deswegen eingeführt, weil das eben nicht> automatisch so geht. C hat kein constexpr.
C hat Makros: #define delay(us) delayloop((us) * FACTOR - OVERHEAD)
Die Delay-Implementierung in der avrlibc funktioniert deshalb nur mit
Konstanten als Parameter - eine Anfängerfalle - ist aber ausgesprochen
präzise, wenn kein Interrupt dazwischen kommt.
A. K. schrieb:> C hat Makros:
Da war ich letzte Woche auch schon. Für den genannten Ensatzzweck nicht
brauchbar, weil die Schleife immer noch drin ist. Zudem rennt der
Vorschlag in einen wrap-around, auf den man auch noch prüfen müßte. Noch
mehr Overhead. Macros können zudem nicht rechnen, weswegen sie kein
Ersatz für constexpr sind.
Hat seinen Grund, wieso ich das vorgeschlagen habe mit dem
Makro-Generator, der obendrein auch die Schleife gleich komplett
eliminieren würde. Und auch den Einsprung in die C-Funktion, weil dann
am Ende nur die richtige Menge an inline-nops rausfällt.
Nop schrieb:> Hat seinen Grund, wieso ich das vorgeschlagen habe mit dem> Makro-Generator
Der Makro-Generator ist unbrauchbar. Selbst die völlig an den Haaren
herbeigezogene Variante
Beitrag "Re: Delay-Funktion STM32: delay_ns()" ist brauchbarer.
Nop schrieb:> Da war ich letzte Woche auch schon. Für den genannten Ensatzzweck nicht> brauchbar, weil die Schleife immer noch drin ist.
Das oben war die Trivialversion. Beliebig ausbaubar.
#define delay(us) \
F(us) < N1 ? delay1(U1(us)) : F(us) < N2 ? delay2(U2(us)) : ...
> Macros können zudem nicht rechnen, weswegen sie kein> Ersatz für constexpr sind.
Aber der Compiler. Reicht hier.
Man wird dann für kleine Delays (delay1 oben) nicht mit Timer arbeiten,
sondern klassisch mit Schleife oder Codesequenz, und erst darüber hinaus
mit Timer und dem möglichen Wraparound.
A. K. schrieb:> erst darüber hinaus> mit Timer und dem möglichen Wraparound.
Man benötigt keinen Wraparound. Der Wertebereich für die Zyklen ist für
einen Prozessor < 1 GHz immer kleiner als die Anzahl der Nanosekunden.
Deswegen ist ja die Auflösung begrenzt. Käme man auf die absurde Idee,
2^32 Nanosekunden (ca. 4 Sekunden) mit der Nanosekunden-Verzögerung
warten zu wollen: Wo läge das Problem?
Ductus cochlearis schrieb:> Man benötigt keinen Wraparound. Der Wertebereich für die Zyklen ist für> einen Prozessor < 1 GHz immer kleiner als die Anzahl der Nanosekunden.> Deswegen ist ja die Auflösung begrenzt. Käme man auf die absurde Idee,> 2^32 Nanosekunden (ca. 4 Sekunden) mit der Nanosekunden-Verzögerung> warten zu wollen: Wo läge das Problem?
Wenn man einen Hardware-Zykluszähler wie den Systick verwenden, und den
nicht ausschliesslich für Delays reserviert, sondern beispielsweise auch
als 1ms Interrupt verwendet, dann kann man den nicht am Anfang des
Delays setzen. Und dann kann es auch bei kurzen Zeiten einen
Wraparound geben.
Ductus cochlearis schrieb:> Der Makro-Generator ist unbrauchbar.
Wieso? Weil Du lieber wöchentlich hier dieselbe Frage stellst und
offenbar eh keine Lösung brauchst?
> Man benötigt keinen Wraparound.
Doch. Wenn man den Overhead von der Wartezeit abzieht, die Wartezeit
aber nur wenige Takte ist, dann gibt's einen Wraparound.
Nop schrieb:> Wenn man den Overhead von der Wartezeit abzieht, die Wartezeit> aber nur wenige Takte ist, dann gibt's einen Wraparound.
Deswegen berücksichtigt der geübter Overheadsubtrahierer diesen Fall von
im Vornherein. Siehe oben.
Noch eine Kleinigkeit zur Umrechnung von Zeit in Schleife: Wenn man
keine Konstanten hat, aber Multiplikation oder Division in der
Umrechnung nicht mag, dann subtrahiert man in der Schleife nicht um 1,
sondern übergibt direkt die Zeit in Nanosekunden und subtrahiert darin
die Nanosekunden pro Iteration. Leicht ungenau wenns nicht ohne Rest
aufgeht, aber für kleine Delays sollte es reichen.
Die Nanosekunden pro Iteration mögen sich zwar von einem µC zum anderen
ändern können, aber meistens nicht zur Laufzeit auf dem gleichen. Die
kann man einmalig beim Start anhand eines Referenztimers berechnen.
Wenn man nur kurze Delays hat, könnte man sich auch die korrekte Anzahl
an NOP's erzeugen lassen, ganz ohne Schleife. Das könnte die Berechnung
einfacher machen. Allerdings wird hier möglicherweise der Einfluss der
Instruction Caches (und ART bei STM32F4) stärker, weil mehr Code da ist.
In C++ kann man das mit templates machen:
Dr. Sommer schrieb:> Wenn man nur kurze Delays hat, könnte man sich auch die korrekte Anzahl> an NOP's erzeugen lassen, ganz ohne Schleife.
Kann nacktes C auch. Brute Force.
https://www.mikrocontroller.net/attachment/highlight/362972
Schön ist anders, aber funktioniert. Im Moment ist das sogar meine
"Produktivlösung". Warteschleifenlösung wäre mir etwas lieber, wegen des
Flash-Verbrauchs.
Ductus cochlearis (Gast) schrieb:> Kann nacktes C auch. Brute Force.
Funktioniert schon etwas anders, man hat noch den Funktionsaufruf mit
drin und man hat immer fix 1000 NOP's im Code. Warum ist eigentlich ein
NOP bei "case 0" und keines bei "case 1"?
Und da es ja eh kaum einen Grund gibt auf C++ zu verzichten...
Dr. Sommer schrieb:> man hat noch den Funktionsaufruf mit> drin und man hat immer fix 1000 NOP's im Code.
Nö. Mit "static inline attribute always inline" und einem
Funktionsaufruf mit einem zur Compilzeit bekannten Parameter (das war ja
die Voraussetzung ganz oben) gibt es im Assembler-Listing keinen Jump
mehr und auch nur noch die benötigte Anzahl an NOP();.
A. K. schrieb:> Nur sollte man nicht drauf wetten, dass es mit jedem Compiler> funktioniert.
Der Konsens war im letzten Thread, den der OT letzte Woche zum selben
Thema eröffnet hat, daß taktgenaue Programmierung in C ohnehin Blödsinn
ist, weil zuviel davon abhängt, wann der Compiler optimieren kann, was
auch vom umliegenden Code abhängt.
Das bekommt man nur in Assembler hin. Diese Erkenntnis hat der TE im
letzten Thread auch schon bekommen. Da ihm das nicht paßt, denkt er, es
werde sich von letzter Woche auf diese Woche bestimmt grundlegend etwas
geändert haben, weswegen er einen neuen Thread aufmacht.
Aber auch diese Woche heißt es: wer sich nicht mit Assembler befassen
will, nichtmal mit dem sehr eingängigen ARM-Assembler, der braucht auch
keinen taktgenauen Bustreiber, sondern muß mit etwas weniger Durchsatz
vorliebnehmen.
Das wird auch nächste Woche höchstwahrscheinlich immer noch so sein.
Nop schrieb:> Der Konsens war im letzten Thread,
Nö. Im letzten Thread habe ich gefragt, ob mir jemand bei der
Assembler-Programmierung helfen kann. Da dem nicht so ist, geht es jetzt
eben darum, wenigstens soviel hinzubekommen, wie ich selbst machen kann.
Das läuft im wesentlichen dann auf die Frage nach dem Overhead der
Verzögerungsschleife hinaus.
Ductus cochlearis (Gast) schrieb:> Das läuft im wesentlichen dann auf die Frage nach dem Overhead der> Verzögerungsschleife hinaus.
Dazu war im letzten Thread die Antwort auch schon gegeben. Hängt nicht
nur vom Code dieser Funktion ab, sondern auch vom umliegenden Code. U.a.
deswegen kann man in C nicht taktgenau verzögern. Steht alles schon im
letzten Thread.
Pragmatische Lösung: ruf die Schleife mit 0 auf und miß per GPIO, wie
lange es dauert.
Im Moment steht zwischen mir und meinem Ziel auch nur noch der Wert
einer einzigen Zahl X. Wer weiß. Vielleicht finde ich einen Weg, das
empirisch zu messen. Dann brauche ich gar keine nervigen Sprüche à la
"alles was Du da machst ist scheiße" mehr zu lesen.
Ductus cochlearis (Gast) schrieb:> Vielleicht finde ich einen Weg, das empirisch zu messen.
Ja, vielleicht bekommst Du es tatsächlich mal hin, ein GPIO-Register zu
setzen, es zurückzusetzen (ohne HAL!) und die Zeit mit nem Oszi zu
messen. Dann dasselbe mit der Verzögerungsfunktion.
Hatte ich übrigens auch schon im letzten Thread vorgeschlagen.
Nop schrieb:> Hatte ich übrigens auch schon im letzten Thread vorgeschlagen.
Stimmt. Und im letzten Thread gab es auch eine Antwort, was daran nicht
praktikabel ist.
Ductus cochlearis (Gast) schrieb:> Stimmt. Und im letzten Thread gab es auch eine Antwort, was daran nicht> praktikabel ist.
1) wenn die rise/fall-Zeiten so eine Messung schon schwierig machen, ist
der ganze Aufbau schon so unsauber, daß es keinen Sinn macht, da vom
Delay her an die Kante zu gehen.
2) wenn man einen Funktionsaufruf nicht gut messen kann, dann ist die
triviale Lösung, die Funktion zwischen dem GPIO-Gewackel zehnmal
aufzurufen. Oder hundertmal. Ohne Schleife natürlich, sondern
copy/paste.
Nop schrieb:> Hast Du. So wie im vorigen Thread auch schon. Wenn taktgenau, dann> Assembler.
Du hast von Assembler genauso wenig Ahnung wie ich. Warum fühlst Du Dich
genötigt, mit hoher Frequenz hineinzugrätschen?
Du kennst keine bessere Antwort als ich. Das ist in Ordnung. Du bist in
keiner Prüfung. Wenn Du es nicht weißt: alles gut. Niemand wird es
merken, wenn Du keine Antwort gibst.
Nop schrieb:> wenn man einen Funktionsaufruf nicht gut messen kann, dann ist die> triviale Lösung,
Und sie ist nicht gut. Wenn ich vor und nach der Verzögerungsfunktion
Zugriff auf MMIO mache, wird sie sich mit Sicherheit anders verhalten,
als wenn ich sie zehnmal hintereinander ausführe.
Stefanus F. schrieb:> Ich erkläre das mal am Beispiel eines STM32F103 und diesem (nicht> optimalen) Code:
Jetzt habe ich durch das längliche Zerreden tatsächlich den sinnvollen
Beitrag übersehen! Danke (in echt an Stefanus, in ironisch an Nop)!
Ductus cochlearis schrieb:> Wenn ich vor und nach der Verzögerungsfunktion> Zugriff auf MMIO mache, wird sie sich mit Sicherheit anders verhalten,> als wenn ich sie zehnmal hintereinander ausführe.
Wenn damit der Einfluss auf den Speicher für den Code gemeint ist:
Funktionen mit zwingend reproduzierbarem Zeitverhalten wird man
sinnvollerweise in einen entsprechenden Speicher legen.
Gegen DMA und eingeschaltete Interrupts ist natürlich kein Kraut
gewachsen, was das Laufzeitverhalten von Code angeht. Das sollte sowieso
klar sein.
Ductus cochlearis schrieb:> Du hast von Assembler genauso wenig Ahnung wie ich.
Nö, ich hab nur ganze Projekte schon in Assembler abgewickelt. Ist ja
auch nicht sonderlich schwierig, nur die Entwicklungszeit ist halt
länger als in C. Für sehr hardwarenahe Teile, die man in C grundsätzlich
nicht hinbekommen kann, nehme ich immer noch gerne Assembler.
> Warum fühlst Du Dich> genötigt, mit hoher Frequenz hineinzugrätschen?
Warum fühlst Du Dich genötigt, dauernd dieselben Fragen zu stellen?
Glaubst Du, diese Woche sei alles anders?
> Und sie ist nicht gut. Wenn ich vor und nach der Verzögerungsfunktion> Zugriff auf MMIO mache, wird sie sich mit Sicherheit anders verhalten,> als wenn ich sie zehnmal hintereinander ausführe.
Auch das hatte man Dir bereits letzte Woche gesagt, daß es auch darauf
ankommt, wie der Code drumherum aussieht. Deswegen Assembler.
Es ist auch potentiell problematisch, Delay-Code zu replizieren.
Besonders bei Schleifen sollte es exakt eine einzige sein. Andernfalls
könnte man bedingt durch unterschiedliches Alignment Abweichungen
bekommen.
Stefanus F. schrieb:> Andere ARM Modelle haben teils ein komplexeres Buffering System, da wird> es dann noch schwieriger zu erklären.
Stimmt. Aber zum Glück stört mich das ja nicht. Das Risiko, daß jemand
in meiner MCU klammheimlich zusätzlich Cache verbaut, vernachlässige ich
jetzt.
Stefanus F. schrieb:> Wenn du jetzt die Taktfrequenz um das achtfache auf 64MHz erhöhst, musst> du zwei Waitstates für die Flash-Zugriffe konfigurieren, weil der Flash> nicht so schnell ist.
Genau. Solange ich die Taktfrequenz zur Laufzeit nicht kenne, sind damit
die Waitstates schon zur Compilezeit bekannt.
Stefanus F. schrieb:> Zwar hat der µC einen Prefetch Buffer, der Befehle im Voraus laden kann,> aber der wird bei jeden Rücksprung nach "oben" verworfen.
Genau. Das gibt mir die untere Grenze der Laufzeit, die ich ja auch
brauche.
Stefanus F. schrieb:> (1 für den NOP und 3 für den Rücksprung)
+ X1 für Caching Effekte.
Danke, darauf kann ich aufsetzen.
Ductus cochlearis schrieb:> + X1 für Caching Effekte.> Danke, darauf kann ich aufsetzen.
Wär besser, nicht auf Caching-Effekte aufzusetzen, sondern darauf zu
verzichten.
A. K. schrieb:> Wär besser, nicht auf Caching-Effekte aufzusetzen, sondern darauf zu> verzichten.
Ich muß halt nehmen, was ich kriegen kann.
Wenn n Personen kenne, die eine Kreissäge, ein Schweißgerät oder einen
Lötkolben haben und damit umgehen können, ist es kein Problem, jemanden
zu finden, der hilft.
Nur bei Assembler ist alles anders. Ich hoffe, daß es an meinem
Bekanntenkreis liegt.
Ductus cochlearis schrieb:> Nur bei Assembler ist alles anders.
Meine Güte, also man kann sich auch anstellen. Dann LERNE das eben mal.
Bei x86-Assembler würde ich Dir ja zustimmen, plus noch die ewig langen
Pipelines, das ist wirklich kompliziert. Aber Thumb2 für den ARM ist
doch echt gutartig. Für jemanden, der C kann, gibt's da absolut keinen
Grund zur Angst.
Notfalls compilierst Du Dir ein paar ganz einfache Stücke C und guckst,
was der Compiler daraus so macht, um ein Gefühl dafür zu kriegen.
Oder, wenn Du das partout nicht selber willst, dann beauftrage jemanden
damit. Kostenpflichtig zu Freiberufler-Tarifen natürlich.
Ductus cochlearis schrieb:> Ich muß halt nehmen, was ich kriegen kann.
Microcontroller mit komplexen Speichersystemen besitzen üblicherweise
RAM-Adressräume, die entweder von vorneherein nicht dem Caching
unterliegen, oder entsprechend konfiguriert werden können.
Bei einfacheren Typen wie vielen CM3, die nur RAM und Flash haben, mag
zwar das Flash irgendwelche Cache-artigen Beschleunigungen haben, aber
nicht das RAM.
A. K. schrieb:> mag zwar das Flash irgendwelche Cache-artigen Beschleunigungen haben,> üblicherweise aber nicht das RAM.
Dafür wird der Bus für den RAM dann gleichzeitig für Daten und
Instruktionen genutzt, sodass es ggf. wieder komplizierter wird, während
für den Flash ein eigener Bus zur Verfügung steht. Manche Controller
haben eigene RAM Blöcke mit extra Bus, um daraus wirklich mit 1:1
Geschwindigkeit Code ausführen zu können (zB STM32F7).
Nop schrieb:> dann beauftrage jemanden> damit. Kostenpflichtig zu Freiberufler-Tarifen natürlich.
Habe ich auch schon überlegt. Ein Lastenheft (inklusive
Lizenzierungsoptionen) schreiben. Werkvertrag ausarbeiten. Ausschreibung
z.B. hier im Forum machen.
Und wie ich dann wissen, ob eine Pißnelke, die für 10 Zeilen Assembler
300 Euro will, mir dann auch eine Funktion liefert, die genau die
richtige Anzahl an Takten verbraucht?
Ductus cochlearis schrieb:> Und wie ich dann wissen, ob eine Pißnelke, die für 10 Zeilen Assembler> 300 Euro will, mir dann auch eine Funktion liefert, die genau die> richtige Anzahl an Takten verbraucht?
Wenn es das nicht tut, funktioniert dein Programm nicht, welches ja
anscheinend derart präzises Timing braucht (wozu auch immer). Daran
siehst du es dann.
Sicher dass es keine clevere Konstruktion mit Timern (PWM, Input
Capture, One Pulse Mode...), DMA, Timer -Synchronisierung, ggf. den
BSRR-Registern gibt die das Problem mit exaktem Timing lösen Kann?
Dr. Sommer schrieb:> Wenn es das nicht tut, funktioniert dein Programm nicht, welches ja> anscheinend derart präzises Timing braucht (wozu auch immer).
Ich brauche das präzise Timining nicht. Aber es nützt. Weil die
Datenrate im best case steigt.
Dr. Sommer schrieb:> Dafür wird der Bus für den RAM dann gleichzeitig für Daten und> Instruktionen genutzt, sodass es ggf. wieder komplizierter wird,
Komplizierter ja, und vielleicht auch langsamer, wenn viele Zugriffe auf
Daten im RAM beteiligt sind (bei Delay-Code eher nicht). Aber ohne DMA
und ohne Interrupts ist das Zeitverhalten des Delay-Codes
deterministisch.
Bulbus duodenitis schrieb:> Ich brauche das präzise Timining nicht. Aber es nützt. Weil die> Datenrate im best case steigt.
Und die Zuverlässigkeit sinkt. Immerhin ist das von der Physik her ja
schon so an der Kante, daß wegen Jitter und Slewrate die Flanken so
verschliffen sind, daß nichtmal mehr 5 ns sauber meßbar sind - und dann
willst Du mit so einem Setup an die Kante von 20 ns ran.
Ductus cochlearis schrieb:> Und wie ich dann wissen, ob eine Pißnelke, die für 10 Zeilen Assembler> 300 Euro will, mir dann auch eine Funktion liefert, die genau die> richtige Anzahl an Takten verbraucht?
Wenn du mal auf die ganzen "Pissnelken" hier gehört hättest, die dir
geraten haben Assembler zu lernen, dann könntest du dir diese Frage
selber beantworten und außerdem wäre dein Problem entweder schon zehn
mal gegessen oder du hättest eingesehen, dass busy-wait nicht die Lösung
für dein Problem ist.
Ich frage mich auch, was diese Eröffnung von einem Thread nach dem
anderen soll und dann noch die unterschiedlichen Nicknames denen du
selber offensichtlich nicht mehr Herr bist. Bei dem anderen Thread
dachte ich im ersten Moment du wärest ein Troll und im zweiten Moment
habe ich mich dann gefragt ob du besoffen bist.
Lern verdammt nochmal Assembler. Das ist wirklich nicht schwer und es
erweitert den Horizont. Wenn du jetzt loslegst hast du heute Abend schon
so viel gelernt, dass dir die obige Erklärung von Stefan wie eine
absolute Selbstverständlichkeit vorkommt.
Christopher J. schrieb:> Lern verdammt nochmal Assembler. Das ist wirklich nicht schwer und es> erweitert den Horizont.
Zudem macht es einen auch zu einem besseren C-Programmierer, was die
Performance des Codes angeht.
> Zudem macht es einen auch zu einem besseren C-Programmierer
Das glaube ich auch. Allerdings bin ich unsicher ob sich der Aufwand
lohnt. Ebenso hierbei:
Ich sage immer den Java Entwicklern, dass sie mal C auf µC lernen
sollen, dann werden sie zu besseren Java Programmierern.
Stefanus F. schrieb:> Das glaube ich auch. Allerdings bin ich unsicher ob sich der Aufwand> lohnt.
Welcher Aufwand? ARM-Assembler ist doch einfach, da RISC. Wenn man nicht
gleich ein ganzes Projekt machen will, ist das locker.
> Ich sage immer den Java Entwicklern, dass sie mal C auf µC lernen> sollen, dann werden sie zu besseren Java Programmierern.
Wer C programmiert, denkt in Assembler, weil C keine richtige
Hochsprache ist, sondern ein portabler Macro-Assembler. Das ist mit Java
zu C schon anders.
Dennoch wäre das auch für Java-Programmierer nicht schlecht, etwas
low-level zu verstehen, dann schreiben sie nicht dauernd so lahme
Programme:
https://www.joelonsoftware.com/2001/12/11/back-to-basics/
> dann schreiben sie nicht dauernd so lahme Programme
Eben deswegen. Ich habe hinter meinem Rücken ein Buch über Java
Performance stehen, da geht es zu 80% um Speicherverwaltung und Garbage
Collector - zu recht. Wenn man weiss, wie die Speicherverwaltung
Funktioniert und welche Schwächen sie hat, programmiert man kleine
Details anders und hat dann viel seltener Probleme.
C zwingt dich dazu, dich damit auseinander zu setzen. In Java geht das
alles erstmal wie mit Magie ganz automatisch, bis das erste ernsthafte
Problem kommt. Und dann hat man womöglich Stress und keine Zeit. Also
bei Gelegenheit besser vorher lernen.
Das gilt eigentlich für jede Sprache oder?
Sorry, wir sind vom Thema abgedriftet.
Christopher J. schrieb:> Wenn du mal auf die ganzen "Pissnelken" hier gehört hättest, die dir> geraten haben Assembler zu lernen,
Hm. Bislang habe ich noch niemanden hier so genannt. Aber wenn sich hier
jetzt jemand meldet mit "würde ich sonst nie tun, aber für 300 Euro
mache ich's", darf er sich natürlich gerne angesprochen fühlen.
Christopher J. schrieb:> Lern verdammt nochmal Assembler.
Nicht jetzt. Vielleicht nie. Da stehen für mich Aufwand und Nutzen in
keinem sinnvollen Verhältnis. Meine Freizeit ist begrenzt, und in ihr
lassen sich viele andere Sachen lernen, die mich durchaus mehr
interessieren.
Die Welt besteht aus mehr als Assembler, C, Java und überhaupt
Elektronik.
Irgendwie erinnert mich das an einen alten Witz.
Ein Schwabe geht an einem Tümpel vorbei und hört, wie jemand "Help!
Help!" ruft. Ruft der Schwabe zurück: "Hättest Du wohl besser schwimmen
gelernt als Englisch!"
Ductus cochlearis schrieb:> Aber wenn sich hier jetzt jemand meldet mit "würde ich sonst nie tun,> aber für 300 Euro mache ich's", darf er sich natürlich gerne> angesprochen fühlen.
Dann beschreibt mal genau, was du machen willst, was das für ein
Interface ist, wie genau das Timing wirklich sein soll, was das für ein
Controller ist. Vielleicht lässt sich was machen.
Stefanus F. schrieb:> Das gilt eigentlich für jede Sprache oder?
Ich habe eigentlich ganz gut Französisch gelernt, ohne jemals Latein
gelernt zu haben.
Bulbus duodenitis schrieb:> Ich brauche das präzise Timining nicht. Aber es nützt. Weil die> Datenrate im best case steigt.
Ist es jetzt erlaubt im thread den Namen zu wechseln?
Bist du der Walter aus dem andern thread?
Hat du einfach per Debugger und cyccnt gemessen wie viele Takte
verbraucht werden bzw. Was hält dich davon ab?
Karl der erste von oben schrieb:> Ist es jetzt erlaubt im thread den Namen zu wechseln?
Nein, aber das Versehen lässt sich nicht mehr korrigieren.
Karl der erste von oben schrieb:> Hat du einfach per Debugger und cyccnt gemessen wie viele Takte> verbraucht werden bzw. Was hält dich davon ab?
Der DWT->CYCCNT wird durch den Debugger beeinflusst. Selbst wenn ich die
Breakpoints eine Memory Barrier weit weg positioniere, ist das Ergebnis
nicht reproduzierbar, sondern weicht um -2...+4 ab. Damit kann ich keine
Funktion mit einer Anzahl an NOP()s taktgenau vergleichen.
Dr. Sommer schrieb:> Dann beschreibt mal genau, was du machen willst
Es ist der übliche Bitbanging-Treiber. Dieser wird momentan für zwei
Targets verwendet. Eines ein STM32F103, das andere ein STM32F446. Ab
nächste Woche kommt ein drittes Target hinzu (STM32F407). Die Targets
haben unterschiedliche Zwecke, verwenden aber natürlich den gleichen
Bitbanging-Treiber.
Die Pins werden mit Makros gesetzt und gelesen, die letzten Endes auf
atomare Operationen im Bitbanding-Bereich der GPIOs münden. Keine
Standard Peripheral Library, kein HAL.
Zwischen den Zugriffen auf die GPIO liegen NOP()s, um die geforderten
Zeiten einzuhalten.
Werden die Zeitabstände aus dem Datenblatt stumpf mit dem Taschenrechner
in eine Anzahl an NOP()s umgerechnet, ist alles paletti. Für jedes
Target bei jedem (festen) Takt, bei dem ich diese stumpfsinnige Arbeit
gemacht habe, funktioniert der Treiber.
Für jede Funktion, jede Verzögerung existieren jetzt gerade
unterschiedliche Anzahlen handgezählter NOP()s, die durch den
Präprozessor für jedes Target einzeln eingesetzt werden. Entsprechend
furchtbar sehen die Funktionen aus.
Und da ich immer noch nicht einsehe, daß ich von Hand (mit dem
Taschenrechner) NOP()s einsetze, die mein Präprozessor/Compiler völlig
problemlos vorausberechnen kann, will ich im Bitbanging-Treiber eine
Funktion delay_nanoseconds() haben, die eine Anzahl von Takten
verzögert, mit den folgenden Anforderungen:
- niemals weniger als die angegebene Zeit (um den Treiber stabil zu
halten)
- mögichst wenig und selten mehr als die angegebene Zeit (um den
Durchsatz möglichst hoch zu halten).
Diese Anforderungen sind vermutlich wenig ungewöhnlich.
Der erforderlichen Verzögerungen liegen im Bereich 10 ... 70 ns.
Insbesondere bei den sehr kurzen Verzögerungen geht jeder Takt zuviel
direkt merkbar in den Durchsatz ein.
Meine aktuell zweitbeste Lösung besteht aus dem Brute-Force-Delay von
oben. Diese hat nur einen Nachteil: Sie optimierungsabhängig. Im
Debug-Build ist sie ein markanter Platzfresser. In -O2 fällt sie auf die
gleiche Anzahl an NOP()s zusammen, die ich vorher per Taschenrechner
ausgerechnet habe.
Meine aktuell beste Variante ist eine Mischung, bei der für kleine
Delays ähnlich der brute-force-Methode arbeite (switch-case-nop-Reihe),
und für größere Delays auf eine Inline-Assembler-Loop mit drei Takten
und entsprechend geringerer Auflösung gesprungen wird. Auch nicht schön,
weil es eine Unstetigkeitsstelle gibt. Um diese zu beheben, müßte ich
den Overhead der Schleifenfunktion kennen. Das X. Also das gleiche
Problem wie oben. Oder die NOP-Rutsche so lang machen, daß die
Unstetigkeitsstelle weit weg von meinem interessanten Bereich ist. Also
wieder optimierungsabhängige Größe.
Eine Assembler-Variante mit vorberechneten Konstanten wäre in gleicher
Weise ausreichend deterministisch, aber ansonsten in allen Belangen
überlegen.
Ductus cochlearis schrieb:> Es ist der übliche Bitbanging-Treiber.
Und was bitbangt der? USART, SPI, SD-Bus, USB, ... ?!
Ductus cochlearis schrieb:> Diese hat nur einen Nachteil: Sie optimierungsabhängig.
Der ganze Rest vom Code ist auch optimierungsabhängig, bei -O0 wird
sowieso alles langsamer. Daher nicht schlimm.
Stefanus F. schrieb:> Ist Dir bewusst, dass deine ganze Idee mit dem automatischen Berechnen> der NOP's platzt, sobald du die Taktfrequenz zur Laufzeit änderst?
Ja.
Dr. Sommer schrieb:> Und was bitbangt der? USART, SPI, SD-Bus, USB, ... ?!
In diesem Fall einen popeligen 8-Bit-Parallel-Bus mit fünf
Steuerleitungen. Aber warum sollte das wichtig sein?
Dr. Sommer schrieb:> Daher nicht schlimm.
Sagst Du. Du mußt mein Target ja auch nicht vor jedem Debug flashen.
Ductus cochlearis schrieb:> In diesem Fall einen popeligen 8-Bit-Parallel-Bus mit fünf> Steuerleitungen. Aber warum sollte das wichtig sein?
Weil sich das eventuell mit DMA und/oder dem FMC ganz einfach und
präzise in Hardware abfrühstücken lässt...
Ductus cochlearis schrieb:> Sagst Du. Du mußt mein Target ja auch nicht vor jedem Debug flashen.
Verstehe den Zusammenhang nicht.
Naja, wenn du Interesse hast das gegen Geld machen zu lassen, gib mal
eine e-Mail Adresse an, dann melde ich mich und wir klären die
Modalitäten. Ich bräuchte auf jeden Fall mehr Informationen darüber, was
da genau gebitbangt wird - vermutlich gibt es eine bessere Lösung als
Busy Waiting.
> warum sollte das wichtig sein?
Weil man bei UART und USB die Bitrate einigermaßen genau einhalten muss,
während zum Beispiel I²C und SPI meisten beliebig verlangsamt werden
dürfen.
Dr. Sommer schrieb:> vermutlich gibt es eine bessere Lösung als> Busy Waiting.
Etwas besseres als Busy Waiting im Bereich 10 ... 60 ns? Das ist
Geheimwissen, daß Du Dir ordentlich vergolden lassen solltest. Das kann
ich mir eh für ein Hobbyprojekt nicht leisten.
Stefanus F. schrieb:> Weil man bei UART und USB die Bitrate einigermaßen genau einhalten muss
Vielleicht habe ich wirklich zu selten erwähnt, daß es mir nur darum
geht, eine Anzahl an Takten zu warten und das seltene Ausreißer nach
oben akzeptabel sind, solange es meistens am korrekten Minimum bleibt.
Ductus cochlearis schrieb:> Etwas besseres als Busy Waiting im Bereich 10 ... 60 ns? Das ist> Geheimwissen, daß Du Dir ordentlich vergolden lassen solltest. Das kann> ich mir eh für ein Hobbyprojekt nicht leisten.
Wie gesagt, durch schlaue Verkettung der Peripherie-Module kann so
etwas gehen. z.B. lässt man einen Timer laufen der DMA-Transfers auslöst
vom RAM in die GPIO->BSRR Register. Damit erreicht man präzises Timing.
Andere Peripherie-Module könnte man zweckentfremden falls es zum
Interface passt, wie z.B. DCMI, FMC, SDIO, QSPI, welche komplett in
Hardware die Transfers erledigen.
Ductus cochlearis schrieb:> - mögichst wenig und selten mehr als die angegebene Zeit (um den> Durchsatz möglichst hoch zu halten).Bulbus duodenitis schrieb:> Ich brauche das präzise Timining nicht. Aber es nützt. Weil die> Datenrate im best case steigt.Ductus cochlearis schrieb:> Genau. Das gibt mir die untere Grenze der Laufzeit, die ich ja auch> brauche.
Stimmt. Höchstens dreimal. Und ich erwähnte, daß die
"Referenz"implementierung mit einer NOP-Rutsche funktioniert, bei der
Abweichungen nach oben bekanntermaßen auch auftreten können, wenn ISRs
ins Spiel kommen.
Dr. Sommer schrieb:> Wie gesagt, durch schlaue Verkettung der Peripherie-Module kann so> etwas gehen. z.B. [...]
Nicht übel! Ich denke, es gibt Anwendungsfälle, in denen soetwas extrem
hilfreich ist.
Hm. Wenn ich drüber nachdenke: Meine Brute-Force-NOP-Rutsche ist gar
nicht so übel. Ich darf sie nur nicht inlinen, sondern lasse sie einfach
als normale Funktion stehen. Dann gibt es keine Verzweigung. Nur einen
Ein- und einen Rücksprung. Welche Register gesichert werden, sehe ich im
Listing. Und den Overhead kann sogar ich mit meinen nicht vorhandenen
Assembler-Kenntnissen zusammenzählen, sobald ich weiß, welche Register
auf dem Stack gesichert werden.