Hallo, beim Umstieg von 8-bit AVRauf die Cortex M ist mir jetzt das Problem untergekommen, daß es dort keine delay-Funktionen gibt. Der Grund soll der sein, daß prinzipbedingt wegen Pipelines, Sprungvorhersage usw. kein "sicheres" Vorhersagen der Laufzeit möglich sein soll. Für längere delays im ms-Bereich ist das kein Problem; da ist die "Unschärfe" akzeptabel. Aber im µs-Bereich wird es schon sehr schwierig. Ich habe da schon einige Varianten ausprobiert, aber bei 12 MHz, z.B 5 µs für einen DS18B20, halbwegs hinzubekommen nicht. Nun besann ich mich auf das gute alte asm volatile "nop", was auch an sich funktioniert. Nur kann man ja kaum z.B. 100 nop's in den Quelltext schreiben, zumal das ja auch nicht variabel ist. Und in einer Schleife durchzulaufen, macht alles noch viel ungenauer. Wie macht ihr das?
Was spricht dagegen, die _delay_us() Funktion bzw. _delay_ms() funktion nachzubauen? In der Doku der Lib-C steht viel dazu, in den Inludes der Rest.
Du meinst z.B. ?
1 | _delay_loop_1(uint8_t __count) |
2 | {
|
3 | __asm__ volatile ( |
4 | "1: dec %0" "\n\t" |
5 | "brne 1b"
|
6 | : "=r" (__count) |
7 | : "0" (__count) |
8 | );
|
9 | }
|
Werde ich mir mal genauer anschauen.
Falk Brunner schrieb: > Was spricht dagegen, die _delay_us() Funktion bzw. _delay_ms() funktion > nachzubauen? Dass die Laufzeit stark variieren wird, wegen eben der Pipeline und Out-of-Order-Execution. Deswegen macht man soetwas mit Timern; die kann man problemlos zyklengenau programmieren, und in einer "busy-loop" fragt man ab ob der Timer schon abgelaufen ist. Alternativ mit Interrupt&WFI. Besser ist natürlich, das ganze komplett in Hardware zu machen - mithilfe von UART/SPI -Modulen, Timer-PWM-Input oder Timer+DMA lassen sich schon so einige serielle Protokolle verwerten.
Haben die Dinger nicht tonnenweise 32Bit-Timer? Nimm einfach einen Timer. Den Timer initialisierst Du einmalig, dann läuft er durch. Zum Start des Delays lädst Du in ein Compareregister Timer + Delay, löscht das Pending-Flag und loopst, bis es wieder gesetzt ist.
Dr. Sommer schrieb: > Dass die Laufzeit stark variieren wird, wegen eben der Pipeline und > Out-of-Order-Execution. Cortex M3 kennt keine Out-of-Order-Execution, und die Pipline ist IIRC 3-stufig. Wenn man die eigentliche Warteschleife in Assembler packt, z.B. wie [Beitrag "Re: lpcxpreeso SystemFrequency LPC1768"], wird das normalerweise recht genau. Allerdings werden Interrupts die Wartezeit verlängern. Peter Dannegger schrieb: > Haben die Dinger nicht tonnenweise 32Bit-Timer? Das ist sehr stark von der konkreten Variante abhängig, da diese Peripherie vom Hersteller nach Gutdünken eingebaut wird. Der immer vohandene Systick ist nur 24-bittig.
Auch beim AVR sind die _delay Funktionen nicht ultimativ genau, Interrupts stören dort genau so. Kurze Verzögerungen im zweistelligen us Bereich kann man so schon machen, denn die Schleife läuft auch mit Cache mit konstanter Geschwindigkeit. Das man Verzögerungen im ms Bereich besser mit Timern macht ist klar, aber für Testzwecke geht es auch mal mit delay. Ist bei ARM nicht anders als bei AVR.
Peter Dannegger schrieb: > Den Timer initialisierst Du einmalig, dann läuft er durch. > Zum Start des Delays lädst Du in ein Compareregister Timer + Delay, > löscht das Pending-Flag und loopst, bis es wieder gesetzt ist. Dr. Sommer schrieb: > Deswegen macht man soetwas mit Timern; die kann > man problemlos zyklengenau programmieren, und in einer "busy-loop" fragt > man ab ob der Timer schon abgelaufen ist. So habe ich es eigentlich auch schon probiert: Einen 32 bit-Timer mit maximaler Geschwindigkeit (coreclock) frei laufen lassen. Zu Beginn der Funktion den aktuellen Wert lesen, delay hinzuaddieren und dann den Timer auf kleiner Zielwert pollen. So kommen sich z.B. verschachtelte delays (z.B. Interrupt) nicht in die Quere, auch wenn das Timing des ersten delays wohl dahin wäre. Leider hat das enormen Overhead (z.B. 206 statt 72 clocks bei 12 MHz; entspricht gut 17 statt 5 µs). Überlauf auch berücksichtigt, wobei das kaum etwas ausmacht und bei 72 zu über 4 Milliarden auch nicht sehr wahrscheinlich ist. Ich werde mal schauen, ob das in Assembler etwas bringt.
Pete K. schrieb: > Ich hab mir mal das hier zusammengesammelt für einen STM32. Damit > funktioniert auch OneWire. Ohne es jetzt getestet zu haben: 25 MHz / 8 ist gut 3,5 mal langsamer als 12 MHz. Ein clock wäre etwa 0,32 µs. Den Timer auf Null setzen, starten und am Ende wieder stoppen kostet auch zusätzlich Zeit.
Hartmut schrieb: > Leider hat das enormen Overhead Ein paar Zyklen muss man beim Timer-Abfragen halt abziehen um den Overhead auszugleichen...
Hartmut schrieb: > Leider hat das enormen Overhead (z.B. 206 > statt 72 clocks bei 12 MHz; entspricht gut 17 statt 5 µs). Dein Cortex M ist also erheblich langsamer als ein AVR? Auf einem AVR mit 5MHz funktioniert das 1-Wire mit einer solchen Delayfunktion nämlich: Beitrag "DS1820, DS18B20 in C"
Nun gebe ich ja ehrlich zu, kein sonderlicher Profi zu sein. Mit assembler schon gar nicht. Wie weit man dem Disassembler trauen kann (compiliert mit -S), weiß ich nicht. gcc und ohne Optimierung. Ich nutze die aktuelle LPCXpresso-Umgebung auf einem Cortex M0, 12 MHz. Mein bisher bester Versuch
1 | inline void delay_us(uint32_t us) |
2 | {
|
3 | LPC_TMR32B1 ->TC = 0; //debug |
4 | LPC_TMR32B1 ->TCR = 1; //debug |
5 | uint32_t tc = DELAY_TIMER ->TC; // get current timer value |
6 | uint32_t delayclocks = (us * 1000000) / delay_clktime_us; // convert us in (nanoseconds * 1000) as "clockbase" |
7 | uint32_t loopclocks; |
8 | if ((tc + delayclocks) < 0xFFFFFFFF) |
9 | {
|
10 | loopclocks = tc + delayclocks; // minus a few clocks to compensate calculation time for value of delay_clocks doesn't work |
11 | }
|
12 | else
|
13 | {
|
14 | loopclocks = delayclocks - (0xFFFFFFFF - tc); |
15 | }
|
16 | while (DELAY_TIMER ->TC < loopclocks) |
17 | {
|
18 | asm volatile ("nop"); //; |
19 | }
|
20 | LPC_TMR32B1 ->TCR = 0; //debug |
21 | }
|
Den Timer LPC_TMR32B1 nutze ich nur zum Messen, da der M0 leider nichts anderes zu bieten hat. Er verursacht sicher auch einen Overhead, aber der ist zum Vergleichen konstant. delay_clktime_us hat in einer init-Funktion nur den aktuellen coreclock in Nanosekunden * 1000 umgerechnet. Beide Timer laufen mit coreclock. Im Disassembler kommt da schon ordentlich lang was raus; hätte ich nicht gedacht. Interessanter Weise bringt in der letzten while das nop 15 clocks weniger als ein leeres statement; wieder was gelernt.
Hartmut schrieb: > uint32_t delayclocks = (us * 1000000) / delay_clktime_us; Ne, sowas läßt man natürlich nicht erst zur Laufzeit ausrechnen. Wenn Dein MC keine HW-Division hat, wird das ein richtig fetter Unterprogrammaufruf. Man nimmt ein Macro mit Konstanten, das schon zur Compilezeit ausgerechnet wird. Schau Dir einfach mal mein Beispiel an (Macro DELAY_US(x)). Zur Laufzeit ist dann nur noch eine Subtraktion mit Negativtest zu tun.
Vielen Dank, an 32 bit angepaßt klappt das mit einem kleinen konstanten Offset schon sehr gut. Im Disassembler ist es auch demensprechend deutlich kürzer. Habe da wieder gleich mehrere Dinge gelernt: - Wenn der Compiler es rechnen soll, müssen schon alle Zahlen zur Compilezeit bekannt sein. - Und man muß es dann auch tatsächlich so schreiben, daß der Compiler es auch erkennt. Nicht "nehme dir zur Laufzeit eine Variable und lese dort ...". Ich hatte ein wenig spekuliert, daß der Compiler das auch so erkennt und entsprechend optimiert. Aber natürlich Quatsch und ohne eingeschaltete Optimierung ja sowieso unmöglich. - Zuerst dachte ich, daß der mögliche Überlauf des frei laufenden Timers nicht berücksichtigt wäre. Ist aber ganz schön clever gemacht! Einziger Nachteil durch die Konstanten ist, daß ein Wechsel des coreclocks so nicht geht. Beim Cortex M0 in rechenintensiven Abschnitten schon eher eine Option, aber wann kommt es mal vor und dann weiß man auch, wo man aufpassen muß
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.