Forum: Projekte & Code [ARM / Cortex-M0(+)] delay-Funktionen "avr-libc style"


von Jörg W. (dl8dtl) (Moderator) Benutzerseite


Angehängte Dateien:

Lesenswert?

Bin gerade drüber gestolpert, dass man halt hin und wieder schon mal
ein paar simple "spin-loop" Delays benötigt, für die es aus diesem
oder jenem Grund gerade nicht lohnt, erst großartig die Timerhardware
in Gang zu setzen.

Solange man konstante Ausführungszeiten hat (also keinen Cache und
Ausführung der Befehle mit CPU-Frequenz), geht sowas durchaus auch
auf einem ARM zu implementieren.  Vorbild dafür waren die seit langem
in der avr-libc vorhandenen Implementierungen, allerdings wurde der
führende Unterstrich von den Namen gestrichen, denn das hier ist halt
nicht die Systembibliothek, sondern x-beliebiger Userland-Code.

: Bearbeitet durch Moderator
von Gerd E. (robberknight)


Lesenswert?

Ich möchte Dein Thread nicht mit langen Diskussionen über RTOS ja/nein, 
Sinn von längeren Spin-Loops etc. zumüllen. Nur wer über die Suche 
Deinen Code hier findet, sollte sich überlegen, ob er wirklich einen 
solchen "AVR-Style" Spin-Delay möchte.

Eine Alternative wäre ein kleines RTOS mit wunderschön bequemen 
Sleep-Funktionen und noch ein paar hübschen Sachen mehr. Eben 
"CortexM-Style".

Auch für die kleinsten Cortex M0 (STM32F030) verwende ich mittlerweile 
eigentlich immer ChibiOS, hier dann in der reduzierten NIL-Variante:
http://chibios.org

Sleep sieht dann so aus:
1
    chThdSleepMicroseconds(100);

Und ein Sleep, der gleichzeitig noch auf Events (z.B. aus anderen 
Threads oder IRQs) wartet:
1
eventmask_t evmask=0;
2
3
// US2ST: microseconds to system ticks
4
evmask = chEvtWaitAnyTimeout(ALL_EVENTS, US2ST(100));
5
6
if (evmask != 0)
7
{
8
    // event
9
}
10
else
11
{
12
    // timeout
13
}

von Ingo Less (Gast)


Lesenswert?

Nette Sache, aber wer auf nem Coretex den Systick-Timer nicht 
standardmäßig mitlaufen lässt macht was falsch...

von Jörg W. (dl8dtl) (Moderator) Benutzerseite


Lesenswert?

Gerd E. schrieb:
> Eine Alternative wäre ein kleines RTOS

Nee, du hast den Sinn von sowas nicht verstanden.

Um einen 500-ns-E-Impuls auf einem HD44780 zu generieren, hilft
mir ein RTOS rein gar nichts.  Solange braucht das schon für den
Taskwechsel.

Dass Spinloop-Delays keine richtigen Timer ersetzen können, ist
(völlig unabhängig vom Prozessor) sonnenklar.  Aber sie ergänzen
sich – je nach Situation.

Ingo Less schrieb:
> Nette Sache, aber wer auf nem Coretex den Systick-Timer nicht
> standardmäßig mitlaufen lässt

Der läuft, was genau hülfe der mir hier?

Solange ich während des Delays sowieso nichts anderes tun kann,
ist es auch so ziemlich egal, ob ich nun via SysTick eine Millisekunde
warte oder via Delay.  (Nur so: ich hatte das vorher auf einem Timer
aufgesetzt, aber der ist mir zu schade für dieses bisschen Gepopel,
der soll mal bessere Dinge zu tun bekommen können.)

von Gerd E. (robberknight)


Lesenswert?

Jörg W. schrieb:
> Um einen 500-ns-E-Impuls auf einem HD44780 zu generieren, hilft
> mir ein RTOS rein gar nichts.  Solange braucht das schon für den
> Taskwechsel.

Das stimmt, der Taskwechsel liegt irgendwo so zwischen 1 und 2 µs 
(@48MHz). Wenn es aber mehr als vielleicht 10µs werden, dann sieht die 
ganze Sache anders aus, dann kannst Du die Zeit sinnvoll für was anderes 
nutzen.

Auch musst Du in vielen Fällen damit rechnen, daß Dein Wartecode von was 
anderem, viel wichtigerem (IRQ, High-Prio-Thread,...) unterbrochen 
wurde. Deine hart gecodete Warteschleife wartet jetzt einfach noch 
länger.

Da finde ich Ingos Vorschlag, den normal sowieso mitlaufenden 
Systick-Timer abzufragen, gar keine so dumme Idee.

> (Nur so: ich hatte das vorher auf einem Timer
> aufgesetzt, aber der ist mir zu schade für dieses bisschen Gepopel,
> der soll mal bessere Dinge zu tun bekommen können.)

Ingo meint keinen der wertvollen Timer aus der Peripherie mit PWM etc., 
sondern den speziellen Systick-Timer des Cortex-M-Kerns. Der ist 
eigentlich genau für solche Aufgaben gedacht.

: Bearbeitet durch User
von Jörg W. (dl8dtl) (Moderator) Benutzerseite


Lesenswert?

Gerd E. schrieb:
> Ingo meint keinen der wertvollen Timer aus der Peripherie mit PWM etc.,
> sondern den speziellen Systick-Timer des Cortex-M-Kerns.

Ja, ist mir schon klar.  Ich schrieb ja auch, dass der
selbstverstänlich auch bei mir läuft (war das erste, was ich in
Betrieb genommen habe auf meinem kleinen Experimentierboard).

> zwischen 1 und 2 µs (@48MHz)

Klar, aber danach hast du noch kein Stück nützlichen Code in der
neuen Task abgearbeitet – dann müsstest du schon wieder zurückschalten.

Dass Delays nicht das Allseligmachende sind, ist auch auf dem AVR
nicht anders.  Es gibt aber Situationen, in denen man in der
Zwischenzeit ohnehin nichts anderes tun kann, genau dafür sind sie
gut.  Der einzige Sinn und Zweck des geposteten Codes ist es, dass
der Compiler sich die Anzahl der zu verplempernden Taktzyklen selbst
ausrechen kann, sodass man als Programmierer eben für den E-Impuls
einfach schreiben kann "delay_us(0.5);".

(Es sollte übrigens "avr-libc style" heißen, ich korrigiere das mal.
Es geht dabei nur um die API-Kompatibilität, nicht um grundlegende
Konzepte, wo man besser einen Timer benutzen würde.)

: Bearbeitet durch Moderator
von temp (Gast)


Lesenswert?

Es ist zwar richtig, dass eine hardwaregestütze Delay-Funktion besser 
ist, hin und wieder will man aber mal was austesten oder nur eine kurze 
Verzögerung haben. Dann ist ein simples Delay nicht die schlechteste 
Lösung. Um wenigstens einigermaßen reproduzierbare Zeiten zu bekommen 
sollte die innere Schleife aber in assembler programmiert werden, damit 
es nicht im Debug- oder Releasemodus zu extremen Unterschieden in der 
Laufzeit kommt. Dazu verwende ich folgenden Codeschnipsel. Den 
Grundgedanken hatte ich mal irgendwo her, wo weiss ich aber nicht mehr. 
Bei meinen Tests ursprünglich auf einem LPC1769 musste ich feststellen, 
dass es immer wieder extreme Unterschiede in der Laufzeit bei der 
kleinsten Codeänderung in anderen Programmteilen gab. Bei den LPC11xx 
oder LPC17xx liegt das im Flashcaching begründet. Das kann bei anderen 
Controllern anders aussehen. Mir hat jedenfalls geholfen die innerste 
Schleife mit align4 immer für den cache gleich auszurichten.
Sollte das ganze für einen M3/M4 verwendet werden, ist "subs" an Stelle 
für "sub" zu verwenden.
Ich habe das bei mehreren Controllern am Laufen. Wenn ich einen neuen 
Typ in die Finger kriege, messe ich das ganze mit einem LA mal aus und 
tune das fein. Auf große Genauigkeit kommt es da nicht an.
Das Register SystemCoreClock wird eigentlich immer vom CMSIS gesetzt. 
Wenn nicht, dann muss da der Systemtakt rein.

1
// Microsecond delay loop-
2
extern uint32_t SystemCoreClock;
3
void DelayuS(uint32_t uS)
4
{
5
  uint32_t CyclestoLoops;
6
7
  CyclestoLoops = SystemCoreClock<<1;
8
  if (CyclestoLoops >= 2000000) 
9
    {
10
    CyclestoLoops /= 1000000;
11
    CyclestoLoops *= uS;
12
    } 
13
  else
14
    {
15
    CyclestoLoops *= uS;
16
    CyclestoLoops /= 1000000;
17
    }
18
19
  if  (CyclestoLoops <= 320)
20
    return;
21
  
22
  CyclestoLoops -= 100; // cycle count for entry/exit 100? should be measured
23
  CyclestoLoops /= 8;   // cycle count per iteration
24
25
  if (!CyclestoLoops)
26
    return;
27
28
  // das align 4 ist extrem wichtig, sonst 
29
  // sind Unterschiede im faktor 2 möglich jenachdem
30
  // wie der Code erzeugt wird
31
  __asm volatile 
32
    (
33
    // Load loop count to register
34
    " mov r3, %[loops]\n"
35
    " .align 4\n"
36
    "loop: sub r3,#1   \n"        // für M3/M4 hier subs verwenden   
37
    "      bne loop  \n\n"
38
39
    : // No output registers
40
    : [loops] "r" (CyclestoLoops) // Input registers
41
    : "r3" // clobbered registers
42
    );
43
}

von Jörg W. (dl8dtl) (Moderator) Benutzerseite


Lesenswert?

temp schrieb:
> Das Register SystemCoreClock wird eigentlich immer vom CMSIS gesetzt.
> Wenn nicht, dann muss da der Systemtakt rein.

Davon abgesehen, dass Atmel es leider nicht für nötig hält, das zu
implementieren, was mich daran stört ist, dass man dann die
Berechnung der Zyklenzahl zur Laufzeit macht.  Gerade für sehr kurze
Delays entsteht daraus wieder zusätzlicher Overhead.

Obige Version erledigt (mit aktivierter Optimierung) alles zur
Compilezeit.

Dass so ein paar simple Delays ihre Grenzen haben (vor allem bei CPUs
mit Cache), ist logisch.

von temp (Gast)


Lesenswert?

Mit den Berechnungen hast du natürlich recht. Allerdings hat deine 
Variante den zusätzlichen Nachteil im Debugmodus die Berechnungen auch 
noch in double auszuführen. Da ist es manchmal besser man bleibt bei 
Macros. Hab deine Variante mal auf einem STM32F103 getestet:
1
static inline void
2
delay_cycles(unsigned int cycles) __attribute__((always_inline));
3
4
/*
5
 * Delay for "cycles" CPU cycles
6
 *
7
 * The subs and cmp instructions both take one CPU cycle, the jump
8
 * back takes two cycles, yielding four CPU cycles per loop
9
 * iteration.
10
 */
11
static inline void
12
delay_cycles(unsigned int cycles)
13
{
14
  __asm__ volatile (".syntax unified" "\n\t"
15
        " .align 4\n"
16
        "1: subs %[cycles], %[cycles], #1" "\n\t"
17
        "cmp %[cycles], #0" "\n\t"
18
        "bne 1b"
19
        : [cycles] "=l" (cycles)
20
          /* not really used as output, but the register
21
       is modified so the compiler must not rely on
22
       the old contents */
23
        : "0" (cycles) /* this is also an input value */
24
        : "cc");
25
}
26
27
28
#define delay_us(a) delay_cycles((double)F_CPU * (a) / 1E6 / 6.0-4)
29
#define delay_ms(a) delay_cycles((double)F_CPU * (a) / 1E3 / 6.0)

Da bei sowas die Zeiten immer direkt im Code stehen, kann das der 
Präprozessor rechnen. Das align habe ich wieder eingebaut und musste für 
den STM32 die Zeiten durch 6 teilen. Die -4 am Ende beseitigt für meine 
72MHz bei kleinen Zeiten etwas die fixen Zeiten.
Damit lagen die Unterschiede bei delay_us(10) bei <0.5us zwischen debug 
und release. Deine inline-Variante ergibt im Debugmodus 38us.
Den Teil um an einem Ausgang zu Wackeln lasse ich hier mal komplett 
aussen vor.

von Jörg W. (dl8dtl) (Moderator) Benutzerseite


Lesenswert?

temp schrieb:
> Allerdings hat deine
> Variante den zusätzlichen Nachteil im Debugmodus die Berechnungen auch
> noch in double auszuführen.

Ja.

-O0 kommt für mich einfach nicht in Frage, weil man dann sowieso
was komplett anderes debuggt als das, um was es eigentlich geht. ;-)

> Da bei sowas die Zeiten immer direkt im Code stehen, kann das der
> Präprozessor rechnen.

Der Präprozessor rechnet nicht, das macht immer der Compiler.  Der
Präprozessor ersetzt nur stur Texte.  Der wesentliche Unterschied
mit den Makros dürfte es sein, dass ein -O0 das Inlining obiger
Funktionen verhindert.  Wenn es auch -O0-fähig sein soll, müsste man
wohl auch delay_us und delay_ms als attribute "always_inline"
festzurren, das wird m. E. auch bei -O0 honoriert.  Da die Ersetzung
der konstanten double-Ausdrücke ja offenbar mit deinen Makros klappt,
müsste sie dann auch funktionieren.

Ich habe das oben mal entsprechend modifiziert.

von temp (Gast)


Lesenswert?

Jörg W. schrieb:
> Der Präprozessor rechnet nicht, das macht immer der Compiler.  Der
> Präprozessor ersetzt nur stur Texte.

Danke Herr Lehrer, das spielt in dem Zusammenhang aber keine Rolle.

Jörg W. schrieb:
> Der wesentliche Unterschied
> mit den Makros dürfte es sein, dass ein -O0 das Inlining obiger
> Funktionen verhindert.  Wenn es auch -O0-fähig sein soll, müsste man
> wohl auch delay_us und delay_ms als attribute "always_inline"
> festzurren

das mit dem "müsste" ist immer so eine Sache. Manchmal ist es besser man 
weiß was raus kommt auch wenn man beim Beschreiben Fehler macht, als 
wenn man ratet.

das ist jedenfalls das Ergebnis mit "inline":
1
while (1)
2
{
3
GPIOSET(GPIOA,11);
4
    4B26        ldr r3, 0x080006B4
5
    F44F6200    mov.w r2, #0x800
6
    611A        str r2, [r3, #16]
7
--- main.c -- 187 ------------------------------------------
8
DelayuS(10);
9
    200A        movs r0, #10
10
    F000FEBC    bl 0x080013A0 <DelayuS>
11
--- main.c -- 188 ------------------------------------------
12
GPIOCLR(GPIOA,11);
13
    4B22        ldr r3, 0x080006B4
14
    F44F6200    mov.w r2, #0x800
15
    615A        str r2, [r3, #20]
16
    F04F0200    mov.w r2, #0
17
    4B20        ldr r3, 0x080006B8
18
    E9C72302    strd r2, r3, [r7, #8]
19
--- main.c -- 189 ------------------------------------------
20
unsigned cycles = (double)F_CPU * us / 1E6 / 6.0;
21
    E9D70102    ldrd r0, r1, [r7, #8]
22
    A318        adr r3, 0x080006A0
23
    E9D32300    ldrd r2, r3, [r3, #0]
24
    F002FFB2    bl 0x080035AC <__muldf3>
25
    4602        mov r2, r0
26
    460B        mov r3, r1
27
    4610        mov r0, r2
28
    4619        mov r1, r3
29
    A315        adr r3, 0x080006A8
30
    E9D32300    ldrd r2, r3, [r3, #0]
31
    F003F85F    bl 0x08003718 <__aeabi_ddiv>
32
    4602        mov r2, r0
33
    460B        mov r3, r1
34
    4610        mov r0, r2
35
    4619        mov r1, r3
36
    F04F0200    mov.w r2, #0
37
    4B15        ldr r3, 0x080006BC
38
    F003F856    bl 0x08003718 <__aeabi_ddiv>
39
    4602        mov r2, r0
40
    460B        mov r3, r1
41
    4610        mov r0, r2
42
    4619        mov r1, r3
43
    F002FF84    bl 0x08003580 <__aeabi_d2uiz>
44
    4603        mov r3, r0
45
    607B        str r3, [r7, #4]
46
--- main.c -- 146 ------------------------------------------
47
if (cycles > 0)
48
    687B        ldr r3, [r7, #4]
49
    2B00        cmp r3, #0
50
    D0CB        beq 0x0800061A
51
    687B        ldr r3, [r7, #4]
52
    603B        str r3, [r7]
53
--- main.c -- 147 ------------------------------------------
54
__asm__ volatile (".syntax unified" "\n\t"
55
    683B        ldr r3, [r7]
56
    F3AF8000    nop.w
57
    F3AF8000    nop.w
58
    3B01        subs r3, #1
59
    2B00        cmp r3, #0
60
    D1FC        bne 0x08000690
61
    603B        str r3, [r7]
62
--- main.c -- 186 ------------------------------------------
63
GPIOSET(GPIOA,11);
64
DelayuS(10);
65
GPIOCLR(GPIOA,11);
66
delay_us(10);
67
}

und das mit Macro":
1
while (1)
2
{
3
GPIOSET(GPIOA,11);
4
    4B0D        ldr r3, 0x08000650
5
    F44F6200    mov.w r2, #0x800
6
    611A        str r2, [r3, #16]
7
--- main.c -- 187 ------------------------------------------
8
DelayuS(10);
9
    200A        movs r0, #10
10
    F000FE84    bl 0x08001330 <DelayuS>
11
--- main.c -- 188 ------------------------------------------
12
GPIOCLR(GPIOA,11);
13
    4B09        ldr r3, 0x08000650
14
    F44F6200    mov.w r2, #0x800
15
    615A        str r2, [r3, #20]
16
    2374        movs r3, #0x74
17
    607B        str r3, [r7, #4]
18
--- main.c -- 189 ------------------------------------------
19
__asm__ volatile (".syntax unified" "\n\t"
20
    687B        ldr r3, [r7, #4]
21
    BF00        nop
22
    F3AF8000    nop.w
23
    F3AF8000    nop.w
24
    3B01        subs r3, #1
25
    2B00        cmp r3, #0
26
    D1FC        bne 0x08000640
27
    607B        str r3, [r7, #4]
28
--- main.c -- 186 ------------------------------------------
29
GPIOSET(GPIOA,11);
30
DelayuS(10);
31
GPIOCLR(GPIOA,11);
32
delay_us(10);
33
}


Das inlinen hat zwar funktioniert, aber die Berechnungen werden noch mit 
double gemacht. Ich bleibe dabei: inline ist für diesen Zweck nur die 2. 
Wahl. Es ist jedenfalls eine ziemliche Fehlerquelle, wenn etwas so 
dramatisch von den Optimierungseinstellungen abhängig ist. Noch dazu 
wenn der Leidensdruck das nicht erfordert. Trotzdem, es war 
aufschlussreich.

von Jörg W. (dl8dtl) (Moderator) Benutzerseite


Lesenswert?

temp schrieb:
> das ist jedenfalls das Ergebnis mit "inline":

Danke fürs Testen!

Ein bisschen mysteriös ist es schon, dass der GCC den
Gleitkommaausdruck (wie er aus dem Präprozessor dann rausfällt) ohne
Optimierung trotzdem zur Compilezeit ausrechnet, die inline-Funktion
jedoch nicht.  Naja, lässt sich dann wohl nicht ändern.

> Noch dazu wenn der Leidensdruck das nicht erfordert.

Inline-Funktionen sind ein wenig netter anzusehen, man hat lokale
Variablen und nicht diese grässliche Backslashitis.  Aber es ist
natürlich richtig, wenn man damit Funktionalität einbüßt, die man
mit den Makros bekommen kann, dann sind sie die zweite Wahl.  Ich
schau's mir heute abend nochmal an, und werde dann die
Makro-Implementierung nehmen.

Ich hatte übrigens gar nicht damit gerechnet, dass der Compiler
diese Ausdrücke ohne Optimierung jemals zur Compilezeit umsetzt,
daher hatte ich den Fall „Optimierung ist aus“ gedanklich für so
ein Vorhaben ausgeklammert.  (Wer nicht optimiert, dem ist die
Ausführungszeit ja sowieso egal. ;-)

Was mich übrigens noch stutzig macht: warum musst du das eigentlich
durch 6 teilen?  Ach, hmm, hast du wait states für den Zugriff auf
den Flash?  Wäre noch die Frage, wie man diese in die Formel Einzug
halten lässt.

von temp (Gast)


Lesenswert?

Jörg W. schrieb:
> Was mich übrigens noch stutzig macht: warum musst du das eigentlich
> durch 6 teilen?  Ach, hmm, hast du wait states für den Zugriff auf
> den Flash?  Wäre noch die Frage, wie man diese in die Formel Einzug
> halten lässt.

So ganz genau kann ich dir das auch nicht sagen, da kenne ich mich mit 
dem arm-Kern und dem Assembler dazu doch zu wenig aus. Da ich bei meinen 
Tests vor langer Zeit diese merkwürdige Abhängigkeit vom alignment 
dieser paar Assembleranweisungen hatte, habe ich mir das mit dem 
Flash-Zugriff erklärt. Man könnte jetzt noch untersuchen ob die selbe 
Routine im RAM ausgeführt anders läuft, aber da steht für mich der 
Aufwand in keinem vernünftigen Verhältnis mehr. Mir reicht es wenn es 
reproduzierbar passt.

von Jörg W. (dl8dtl) (Moderator) Benutzerseite


Lesenswert?

temp schrieb:
> Man könnte jetzt noch untersuchen ob die selbe Routine im RAM ausgeführt
> anders läuft

Vermutlich schon.

Die Ausführungszeiten der Befehle des Cortex-M0+ sind ja durch ARM
so vorgegeben, da können die einzelnen Siliziumhersteller nichts
ändern.  Damit kann es aber nur an Flash-Waitstates liegen.  Der
SAMD20, den ich hier gerade in den Fingern habe, kann bis 24 MHz
ohne Waitstates arbeiten (dann passt das auch mit dem Teilen durch
4), darüber müsste man da auch einen Waitstate einschieben.

Allerdings müssten es mit einem Waitstate eigentlich 7 CPU-Zyklen
pro Iteration werden (vier für die Befehlsausführung, drei
Waitstates für drei Befehle).

Bei Ausführung aus dem RAM geht's natürlich bis zu maximalem Takt
ohne Waitstates.

von temp (Gast)


Lesenswert?

Ich habe eben mal etwas interessantes reproduzieren können auf einem 
stm32f334:

Nehme ich das align 4 raus kriege ich bei folgendem Code:

  while (1)
    {
    aLed.Set();
    delay_us(10);
    aLed.Clr();
    delay_us(10);
    }

10us Low und 15us Heigh!

1
Im Debugger finde ich 2 mal folgenden Code:
2
3
    6C7B        ldr r3, [r7, #0x44]
4
    3B01        subs r3, #1
5
    2B00        cmp r3, #0
6
    D1FC        bne 0x08000FC6
7
    647B        str r3, [r7, #0x44]

das subs liegt einmal auf Adresse 0x08000FC6 (10us) und einmal auf 
0x08000FDA. Mit dem align 4 erzeugt er:
1
             6C7B        ldr r3, [r7, #0x44]
2
             BF00        nop
3
             F3AF8000    nop.w
4
             F3AF8000    nop.w
5
             F3AF8000    nop.w
6
08000FE0:    3B01        subs r3, #1
7
             2B00        cmp r3, #0
8
             D1FC        bne 0x08000FE0
9
 
10
    .....
11
12
             6C3B        ldr r3, [r7, #0x40]
13
             F3AF8000    nop.w
14
             F3AF8000    nop.w
15
             F3AF8000    nop.w
16
08001000:    3B01        subs r3, #1
17
             2B00        cmp r3, #0
18
             D1FC        bne 0x08001000

damit komme ich auf 2x 10us und es passt. Ich wollte eigentlich nicht 
soviel Zeit hier rein investieren, finde aber das schon interessant ist 
und für Verwirrung sorgen kann. Die Cortexe sind eben keine AVRs wo 
zählen allein immer stimmt.

von Jörg W. (dl8dtl) (Moderator) Benutzerseite


Lesenswert?

Ja, klar, der Flash ist natürlich 32-bittig organisiert bei einem
ARM.  Das erklärt dann auch den Teilerfaktor 6: du holst die ersten
beiden Befehle mit einem zusätzlichen Waitstate (+ 2 Takte für die
Ausführung), danach wird der Sprungbefehl ebenfalls mit einem Waitstate
geholt plus weitere zwei Takte für die Ausführung.

Hmm, ok, danke für den Tipp!  Ich werde das Alignment da ebenfalls
noch reinsetzen.

von Gerd E. (robberknight)


Lesenswert?

Jörg W. schrieb:
> Allerdings müssten es mit einem Waitstate eigentlich 7 CPU-Zyklen
> pro Iteration werden (vier für die Befehlsausführung, drei
> Waitstates für drei Befehle).

so einfach ist es glaube ich nicht: die meisten Cortexe haben ja noch 
einen Cache, der den Effekt der Waitstates mindern soll. Ob und wie 
effektiv der Cache ist, hängt dann aber vom Alignment und von Sprüngen 
ab. Sieht man ja an den Ergebnissen von temp oben.

Und es hängt natürlich von der genauen Implementation des Caches ab. Und 
soweit ich weiß, implementiert jeder Hersteller hier seinen eigenen 
Cache, der ist nicht von ARM vorgegeben. Auch unterscheiden sich die 
Caches zwischen den unterschiedlichen Linien eines Herstellers.

von (prx) A. K. (prx)


Lesenswert?

Ich hatte anno LPC2000 eine ähnliche Variante selbstkalibierend 
implementiert. Bei Start des Systems wird über einen bekannten Timer, 
Systick oder RTC, das Zeitverhalten der Delay-Routine gemessen und ein 
Faktor abgeleitet. Der zur Kalibrierung verwendete Timer ist danach frei 
für anderweitige Verwendung.

Damit sind dann auch dynamische Zeiten möglich, solange der Core einen 
Multiplier hat und nicht zu langsam getaktet wird. Und der Code 
funktioniert unabhängig von Waitstates, Caches und dergleichen. Dafür 
handelt man sich durch den Referenz-Timer eine Abhängigkeit von der 
Controller-Familie ein.

: Bearbeitet durch User
von Jörg W. (dl8dtl) (Moderator) Benutzerseite


Lesenswert?

A. K. schrieb:
> Ich hatte anno LPC2000 eine ähnliche Variante selbstkalibierend
> implementiert.

Magst du die hier vorstellen?  Klingt auch interessant.

von (prx) A. K. (prx)


Angehängte Dateien:

Lesenswert?

Jörg W. schrieb:
> Magst du die hier vorstellen?  Klingt auch interessant.

Da der LPC2129 sicherlich nicht mehr viele Freunde hat und der Code 
ausserdem schlecht aus meiner Devicelib rausgezupft werden kann hier mal 
die STM32 Variante. Viel verwendet worden ist die allerdings nicht, also 
Vorsicht damit. Nicht adaptiv ist bisher die Angabe des Overheads, da 
könnte man noch dran feilen.

: Bearbeitet durch User
von Ingo Less (Gast)


Lesenswert?

Gut, da muss erstmal einer durchsteigen ;)

von (prx) A. K. (prx)


Lesenswert?

Sind auch Definitionen drin, die ich mir zu den CMSIS Includes 
hinzugefügt habe. Sowas wie SysTick_LOAD_RELOAD_Msk findet sich dort 
nicht. Also direkt übersetzen lässt sich das für euch nicht. Müsst ihr 
schon selbst was draus machen.

von (prx) A. K. (prx)


Lesenswert?

Bisschen Erklärung: Der Trick liegt darin, in der Schleife nicht 
Durchläufe zu zählen, sondern Nanosekunden zu addieren bis die Zeit 
erreicht ist. Wär mit 16 Bits etwas eng, aber der ARM hat genug davon.

Die Kalibrierung bestimmt, wieviele Nanosekunden ein Durchlauf benötigt. 
Ein Faktor ist in dieser Variante nicht erforderlich und der Multiplier 
wird nur für dynamische µs/ms gebraucht, oder beim unoptimierten 
Debugging.

Es kann natürlich sein, dass der Durchlauf nicht genau in ns 
spezifizierbar ist. Gibt dann eine systematische Abweichung. Aber bei 
ohnehin ungenauen Spinloops sollte man damit leben können, solange die 
verwendeten ARMe weit genug von den GHz weg sind.

Die Schleife ist mit Absicht nicht inlined, um in jedem Fall immer 
denselben Code an derselben Stelle zu haben.

: Bearbeitet durch User
von Jörg W. (dl8dtl) (Moderator) Benutzerseite


Lesenswert?

Danke, klingt gut!

Ja, dass die 32 Bits da manche Dinge einfacher machen, ist 
offensichtlich.

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.