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
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 | } |
Nette Sache, aber wer auf nem Coretex den Systick-Timer nicht standardmäßig mitlaufen lässt macht was falsch...
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.)
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
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
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 | }
|
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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
A. K. schrieb: > Ich hatte anno LPC2000 eine ähnliche Variante selbstkalibierend > implementiert. Magst du die hier vorstellen? Klingt auch interessant.
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
Gut, da muss erstmal einer durchsteigen ;)
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.
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
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
Mit Google-Account einloggen
Noch kein Account? Hier anmelden.