Forum: Mikrocontroller und Digitale Elektronik ARM Cortex M3/M4: Wieviele Takte verbraucht diese Funktion?


von Ductus cochlearis (Gast)


Lesenswert?

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_inline void loop4cycles(uint64_t nLoop)
5
{
6
    assert( nLoop != 0 );
7
    asm volatile (\
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

von Stefan F. (Gast)


Lesenswert?

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!

von Nop (Gast)


Lesenswert?

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!).

von Karl der erste von oben (Gast)


Lesenswert?

Nop schrieb:
> Falls Du mit einer Auflösung von > 1 µs zufirden bist, mach das mit
> DWT_CYCCNT

Warum ist die Auflösung > 1 us?

von Volle2 (Gast)


Lesenswert?

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

von Ductus cochlearis (Gast)


Lesenswert?

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?

von Nop (Gast)


Lesenswert?

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.

von (prx) A. K. (prx)


Lesenswert?

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. ;-)

von Nop (Gast)


Lesenswert?

Ductus cochlearis schrieb:
> Ziel ist tatsächlicht irgendetwas Richtung taktgenaue Auflösung.

Das ist Murks, wie bereits vor wenigen Tagen in diesem Thread erklärt:

Beitrag "Delay-Funktion STM32: delay_ns()"

Wenn Du sowas willst, nimm komplett Assembler.

von Ductus cochlearis (Gast)


Lesenswert?

Nop schrieb:
> Wenn Du sowas willst, nimm komplett Assembler.

Ich kann kein Assembler. Ich muss mit dem klarkommen, was ich bekommen 
kann.

von Nop (Gast)


Lesenswert?

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.

von (prx) A. K. (prx)


Lesenswert?

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.

von Nop (Gast)


Lesenswert?

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?

von Nix (Gast)


Lesenswert?

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.

Beitrag #5393732 wurde vom Autor gelöscht.
von (prx) A. K. (prx)


Lesenswert?

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.

von Karl der erste von oben (Gast)


Lesenswert?

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.

von Johnny B. (johnnyb)


Lesenswert?

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

von Ductus cochlearis (Gast)


Lesenswert?

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?

von Volle2 (Gast)


Lesenswert?

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.

von Ductus cochlearis (Gast)


Lesenswert?

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.

von Karl der erste von oben (Gast)


Lesenswert?

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.

von (prx) A. K. (prx)


Lesenswert?

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.

: Bearbeitet durch User
von Nop (Gast)


Lesenswert?

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.

von Nop (Gast)


Lesenswert?

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.

von Johnny B. (johnnyb)


Lesenswert?

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.

von Ductus cochlearis (Gast)


Lesenswert?

Als Service habe ich den extra-Thread schon eröffnet:

Beitrag "Wie schlimm sind Wartezyklen auf ARM-Prozessoren?"

Bitte dort das andere Thema weiterdiskutieren! Danke!

von Ductus cochlearis (Gast)


Lesenswert?

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.

von Bulbus duodenitis (Gast)


Lesenswert?

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.

von Ductus cochlearis (Gast)


Lesenswert?

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.

von Nop (Gast)


Lesenswert?

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.

von Nop (Gast)


Lesenswert?

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()"

von Stefan F. (Gast)


Lesenswert?

> 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
void delay(uint16_t msec)
2
{
3
    for (uint32_t j=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.

von Ductus cochlearis (Gast)


Lesenswert?

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:
1
static_inline uint32_t nanosecondToCycle(uint32_t ns, uint32_t clockrate)
2
{
3
    /* Aufgerundete Division */
4
    uint64_t dividend = (uint64_t) clockrate * ns;
5
    uint64_t divisor = 1000000000ULL;
6
    uint64_t q = (dividend + divisor - 1)/divisor;
7
8
    /* Da ein Zyklus bei < 1GHz immer laenger als eine Nanosekunde dauert, ist das
9
    Ergebnis imer kleiner als der Eingabewert fuer Nanosekunden */
10
    return q;
11
}
12
13
14
/* nanoseconds: Konstant zur Compilezeit */
15
static_inline void delay_nsnew(uint32_t nanoseconds)
16
{
17
    const uint32_t loopOverhead = 0; // FIXME: Tatsaechlicher Overhead unbekannt
18
    uint32_t nCycle = nanosecondToCycle(nanoseconds, SYSTEMCORECLOCK);
19
    if( nCycle == 0 )
20
    {
21
        nCycle = 1;
22
    }
23
24
    uint32_t nLoop = (nCycle - loopOverhead)/4;
25
    uint32_t rem = nCycle - 4 * nLoop - loopOverhead;
26
27
    switch( rem )
28
    {
29
        case 3:
30
            asm volatile("nop;");
31
        case 2:
32
            asm volatile("nop;");
33
        case 1:
34
            asm volatile("nop;");
35
        case 0:
36
            break;
37
        default:
38
            //error("invalid case");
39
            TEST_FAIL();
40
    }
41
    loop4cycles(nLoop);
42
}

von Stefan F. (Gast)


Lesenswert?

Sorry Tippfehler:
> (1 für den NOP und 1 für den Rücksprung)

Soll heissen:
(1 für den NOP und 3 für den Rücksprung)

von (prx) A. K. (prx)


Lesenswert?

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.

: Bearbeitet durch User
von Ductus cochlearis (Gast)


Lesenswert?

Hoppla, veraltete Version. Richtig ist natürlich:
1
/* nanoseconds: Konstant zur Compilezeit */
2
void delay_nsnew(uint32_t nanoseconds)
3
{
4
    const uint32_t loopOverhead = 0; // FIXME: Tatsaechlicher Overhead unbekannt
5
    uint32_t nCycle = nanosecondToCycle(nanoseconds, SYSTEMCORECLOCK);
6
    if( nCycle == 0 )
7
    {
8
        nCycle = 1;
9
    }
10
11
    uint32_t nLoop = (nCycle - loopOverhead)/4;
12
    uint32_t rem = nCycle - 4 * nLoop - loopOverhead;
13
    if( nLoop != 0 )
14
    {
15
        rem -= loopOverhead;
16
    }
17
18
    switch( rem )
19
    {
20
        // Anzahl cases min. 3 + loopOverhead
21
        case 6:
22
            asm volatile("nop;");
23
        case 5:
24
            asm volatile("nop;");
25
        case 4:
26
            asm volatile("nop;");
27
        case 3:
28
            asm volatile("nop;");
29
        case 2:
30
            asm volatile("nop;");
31
        case 1:
32
            asm volatile("nop;");
33
        case 0:
34
            break;
35
        default:
36
            //error("invalid case");
37
            TEST_FAIL();
38
    }
39
    loop4cycles(nLoop);
40
}

von Nop (Gast)


Lesenswert?

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?

von Ductus cochlearis (Gast)


Lesenswert?

Kacke, wenn man nicht editieren kann. Finde nur meine Login-Daten gerade 
nicht.
1
/* nanoseconds: Konstant zur Compilezeit */
2
void delay_nsnew(uint32_t nanoseconds)
3
{
4
    const uint32_t loopOverhead = 0; // FIXME: Tatsaechlicher Overhead unbekannt
5
    uint32_t nCycle = nanosecondToCycle(nanoseconds, SYSTEMCORECLOCK);
6
    if( nCycle == 0 )
7
    {
8
        nCycle = 1;
9
    }
10
11
    uint32_t nLoop = (nCycle - loopOverhead)/4;
12
    uint32_t rem = nCycle - 4 * nLoop;
13
    if( nLoop != 0 )
14
    {
15
        rem -= loopOverhead;
16
    }
17
18
    switch( rem )
19
    {
20
        // Anzahl cases min. 3 + loopOverhead
21
        case 6:
22
            asm volatile("nop;");
23
        case 5:
24
            asm volatile("nop;");
25
        case 4:
26
            asm volatile("nop;");
27
        case 3:
28
            asm volatile("nop;");
29
        case 2:
30
            asm volatile("nop;");
31
        case 1:
32
            asm volatile("nop;");
33
        case 0:
34
            break;
35
        default:
36
            //error("invalid case");
37
            TEST_FAIL();
38
    }
39
    loop4cycles(nLoop);
40
}

von Ductus cochlearis (Gast)


Lesenswert?

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.

von (prx) A. K. (prx)


Lesenswert?

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.

: Bearbeitet durch User
von Nop (Gast)


Lesenswert?

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.

von Ductus cochlearis (Gast)


Lesenswert?

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.

von Ductus cochlearis (Gast)


Lesenswert?

Und, btw.: Im Kompilat steht dann sogar komplett das Gleiche. Nur daß 
kein extra-Makro-Generator aufgerufen werden muß.

von (prx) A. K. (prx)


Lesenswert?

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.

von Ductus cochlearis (Gast)


Lesenswert?

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?

von (prx) A. K. (prx)


Lesenswert?

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.

: Bearbeitet durch User
von Nop (Gast)


Lesenswert?

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.

von Bulbus duodenitis (Gast)


Lesenswert?

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.

von (prx) A. K. (prx)


Lesenswert?

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.

: Bearbeitet durch User
von Dr. Sommer (Gast)


Lesenswert?

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:
1
#include <utility>
2
3
template <typename... T>
4
__attribute__((always_inline)) inline void eat (T&&...) {}
5
6
__attribute__((always_inline)) inline void multinop (std::index_sequence<0>) {
7
  __asm__ volatile ("nop");
8
}
9
10
template <std::size_t... I>
11
__attribute__((always_inline)) inline void multinop (std::index_sequence<I...>) {
12
  eat ((multinop (std::index_sequence<0> {}), I) ...);
13
}
14
15
template <std::size_t I>
16
__attribute__((always_inline)) inline void multinop () {
17
  multinop (std::make_index_sequence<I> {});
18
}
19
20
int main () {
21
  multinop<7> ();
22
}
Funktioniert aber nur bei eingeschalteter Optimierung korrekt.

von Ductus cochlearis (Gast) (Gast)


Lesenswert?

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.

von Dr. Sommer (Gast)


Lesenswert?

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...

von Ductus cochlearis (Gast) (Gast)


Lesenswert?

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();.

von Dr. Sommer (Gast)


Lesenswert?

Okay, wusste nicht dass das so gut optimiert wird. Mit einem 
Präprozessor-Wust kriegt man das vielleicht ähnlich zu C++ hin, aber 
schön ist anders.

von (prx) A. K. (prx)


Lesenswert?

Nur sollte man nicht drauf wetten, dass es mit jedem Compiler 
funktioniert.

von Nop (Gast)


Lesenswert?

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.

von Ductus cochlearis (Gast) (Gast)


Lesenswert?

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.

von Nop (Gast)


Lesenswert?

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.

von Ductus cochlearis (Gast) (Gast)


Lesenswert?

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.

von Nop (Gast)


Lesenswert?

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.

von Ductus cochlearis (Gast) (Gast)


Lesenswert?

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.

von Nop (Gast)


Lesenswert?

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.

von Ductus cochlearis (Gast)


Lesenswert?

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)!

von (prx) A. K. (prx)


Lesenswert?

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.

: Bearbeitet durch User
von Nop (Gast)


Lesenswert?

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.

von (prx) A. K. (prx)


Lesenswert?

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.

von Ductus cochlearis (Gast)


Lesenswert?

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.

von Ductus cochlearis (Gast)


Lesenswert?

Taktzeit nicht kenne := Taktzeit nicht ändere

von (prx) A. K. (prx)


Lesenswert?

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.

von Ductus cochlearis (Gast)


Lesenswert?

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.

von Nop (Gast)


Lesenswert?

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.

von (prx) A. K. (prx)


Lesenswert?

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.

: Bearbeitet durch User
von Dr. Sommer (Gast)


Lesenswert?

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).

von Ductus cochlearis (Gast)


Lesenswert?

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?

von Dr. Sommer (Gast)


Lesenswert?

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?

von Bulbus duodenitis (Gast)


Lesenswert?

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.

von Dr. Sommer (Gast)


Lesenswert?

Dann miss halt die Datenrate. Oder verwende eine Peripherie Einheit, 
welche die fragliche Schnittstelle beherrscht.

von (prx) A. K. (prx)


Lesenswert?

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.

von Nop (Gast)


Lesenswert?

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.

von Christopher J. (christopher_j23)


Lesenswert?

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.

von Nop (Gast)


Lesenswert?

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.

von Stefan F. (Gast)


Lesenswert?

> 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.

von Nop (Gast)


Lesenswert?

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/

von Stefan F. (Gast)


Lesenswert?

> 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.

von Ductus cochlearis (Gast)


Lesenswert?

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!"

von Dr. Sommer (Gast)


Lesenswert?

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.

von Ductus cochlearis (Gast)


Lesenswert?

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.

von Karl der erste von oben (Gast)


Lesenswert?

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?

von Ductus cochlearis (Gast)


Lesenswert?

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.

von Stefan F. (Gast)


Lesenswert?

Ist Dir bewusst, dass deine ganze Idee mit dem automatischen Berechnen 
der NOP's platzt, sobald du die Taktfrequenz zur Laufzeit änderst?

von Dr. Sommer (Gast)


Lesenswert?

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.

von Ductus cochlearis (Gast)


Lesenswert?

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.

von Ductus cochlearis (Gast)


Lesenswert?

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.

von Dr. Sommer (Gast)


Lesenswert?

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.

von Stefan F. (Gast)


Lesenswert?

> 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.

von Ductus cochlearis (Gast)


Lesenswert?

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.

von Dr. Sommer (Gast)


Lesenswert?

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.

von Ductus cochlearis (Gast)


Lesenswert?

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.

von Ductus cochlearis (Gast)


Lesenswert?

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.

von Ductus cochlearis (Gast)


Lesenswert?

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.

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.