So implementieren Sie Verzögerungsfunktionen in Keil ARM MDK [geschlossen]

Wie implementiert man eine Verzögerungsfunktion in Keil ARM MDK, die eine bestimmte Zeit in Mikrosekunden wartet, die auf jede vernünftige Taktgeschwindigkeit neu kompiliert werden kann, die in den Keil-Projekteinstellungen definiert ist? Und das alles ohne Timer.

Warum benutzt du keinen Timer?
@Leon - Ich hatte Fälle, in denen keine Timer mehr verfügbar waren. Ich hatte auch Fälle, in denen die Interrupt-Latenz größer als die gewünschte Verzögerungszeit war.
Die Verwendung eines Timers bedeutet nicht unbedingt die Verwendung eines Interrupts - Sie können den Timer normalerweise für kurze Zeiten direkt lesen, und das ist viel besser als eine softwaregetaktete Schleife. Aber wenn Ihnen die Timer ausgegangen sind, ist das natürlich keine Option.
Zuvor habe ich IAR Embedded Workbench für AVR verwendet. Es hat eine sehr schöne intrinsische __delay_cycles() Art von Makrofunktion, die der Compiler durch eine Blockierungsverzögerung der genauen Prozessorzykluszeit ersetzt hat. Mit #define FCPU 5000000UL könnte ich beispielsweise taktunabhängige feste Verzögerungen wie diesen __delay_cycles(FCPU*1e-3)-Compiler implementieren, der die korrekte Verzögerung von 1 ms ersetzt. Wenn ich diesen Teil des Codes brauchte, um für eine andere Taktgeschwindigkeit zu arbeiten, habe ich einfach FCPU geändert und neu kompiliert. AVR-GCC hat delay.h mit den Funktionen _delay_us() und _delay_ms() mit ähnlicher Funktionalität, aber mit einigen Einschränkungen.
Also suche ich nach einer ähnlichen Möglichkeit, kleine Blocking-Delays in KEIL wie IAR oder AVR-GCC zu machen.
@x4mer, was ich geschrieben habe, ist, was sie im Hintergrund tun. Sie machen es vielleicht in Assembler, aber es ist fast dasselbe in C. Es ist ein anständiges bisschen Arbeit, so etwas hinzuzufügen und es perfekt zum Laufen zu bringen, das ist kein einfacher Befehl, den C hat.
Verzögerungsschleifen lassen sich nicht leicht portieren, sie sind spezifisch für den CPU-Kern und die Taktrate. Sie pflegen also entweder eine große Sammlung von #ifdefs mit kalibrierten und berechneten Verzögerungsschleifen. Oder Sie tun, was @supercat vorschlägt, und bauen auf einem Hardware-Timer auf.
@Roh - fügen Sie keine Tags hinzu, die nur zufällig sind, aber das Problem nicht wirklich betreffen, und fügen Sie sie insbesondere nicht zu 6 Jahre alten Fragen hinzu, die für die Site zunächst nicht wirklich spezifisch genug waren - das Poster war falsch Stellen Sie dies als eine Toolchain-Frage, da es zuvor sein kann, dass es sich um einen MCU- und Runtime-Software-Stack handeln muss, von denen keines spezifiziert wurde, was es unbeantwortbar macht.
@ChrisStratton Hey, Chris, ich habe es nur hinzugefügt, weil hier einige Antworten mit C/C++-Codes sind. sowie Keil ist ein C- und C++-Compiler. Einige Ihrer Argumente sind mir seltsam. Ich werde mit Moderatoren über Sie sprechen.
Tags sind dafür da, worum es bei einer Frage geht, nicht um das, was sie nur enthält. Bei dieser Frage geht es nicht um C, und im Übrigen erscheint auf dieser Seite nur sehr wenig tatsächliches C - meistens sehen Sie Anweisungen und Assembler, und das wahre Thema und Ziel ist nicht die Toolchain, sondern die Hardware.

Antworten (4)

Der beste Ansatz ist IMHO, einen Hardware-Timer zu haben, der nie geschrieben wird, sondern einfach frei läuft. Dies ermöglicht es, auf einfache Weise beliebige abrufbare Zeitgeber für Dauern bis zur Hälfte der Länge des Hardware-Zeitgebers zu emulieren. Um einen Timer zu "starten", berechnen Sie einfach den Wert des Hardware-Timers, wenn er ablaufen sollte. Um zu sehen, ob ein Timer bereits abgelaufen ist, subtrahieren Sie die erwartete Ablaufzeit von der aktuellen Zeit (betrachten Sie beide Größen als vorzeichenlos) und prüfen Sie, ob das (vorzeichenlose) Ergebnis die Hälfte des maximalen Timerwerts überschreitet. Wenn ja, ist der Timer noch nicht abgelaufen.

Wenn man alternativ einen Timer zum Messen der Zeit seit dem Eintreten eines bestimmten Ereignisses wünscht, kann man einfach den Timerwert speichern, wenn das Ereignis aufgetreten ist, und später die Differenz zwischen dem gespeicherten Wert und der gegenwärtigen Zeit berechnen.

Keil gibt ein Beispiel dafür in seiner Dokumentation keil.com/support/man/docs/gsac/gsac_gpio.htm . Ich denke, der Hardware-SysTick-Timer ist allen Cortex-M0 und Cortex-M3 gemeinsam, daher ist der Ansatz portabel.
Ein Problem bei der Verwendung von SysTick für diesen Zweck besteht darin, dass es häufig mit einem programmierbaren Periodenwert verwendet wird, der oft kürzer ist als die Intervalle, die man messen möchte, und keine Zweierpotenz ist. Wenn andererseits die Rate, die man dem SysTick-Timer zugewiesen hat, eine akzeptable Genauigkeit für einen längerfristigen Timer darstellen würde, kann man einfach SysTick selbst einen freilaufenden 32-Bit-Tick-Zähler führen lassen und diesen verwenden, um andere zu timen Dinge.

Sie müssen die Länge eines NOP bestimmen und es dann mit #Defines so machen, dass es basierend auf der Taktgeschwindigkeit die richtige Zahl einfügt.

Insbesondere möchten Sie die genaue Anzahl der Taktzyklen wissen, die erforderlich sind, um eine Schleife des Stils abzuschließen:

for(long i=0;i<NUMBER_OF_LOOPS_REQUIRED;i++)
{
  __NOP();
}

Indem Sie #define for number of loops required rekonfigurieren, so dass es automatisch ist, basierend auf der angeforderten Verzögerung und der Taktgeschwindigkeit, mit der Sie gerade laufen. Das ganze lässt sich in ein Makro packen.

In diesem Sinne, wenn Sie in einem Modul, das jemand geschrieben hat, eine völlige Gleichgültigkeit der Taktrate feststellen, liegt dies daran, dass sie viel Zeit damit verbracht haben, diese Funktion für Sie zu entwickeln.
Dies hängt stark vom Compiler ab, wenn Sie beispielsweise die Version ändern, kann sich auch die Verzögerung ändern.
Dies würde wahrscheinlich besser funktionieren, wenn Sie die Schleife auch in Assembly codieren würden. Da der Optimierer möglicherweise nicht immer die gleichen Verzweigungsanweisungen auswählt
@Thomas O, Ja, wenn Sie Versionen eines Compilers aktualisieren, können auch viele andere Dinge aus dem Fenster gehen, normalerweise wird diese Art von Dingen konsistent gehalten, aber der erste Schritt nach dem Upgrade von Compilern (wofür ich einen Grund für eine Projekt) testen, ob das System noch funktioniert.
@JobyTaffey, das ist eine Option, die meisten schöneren Compiler, wie zum Beispiel iar.com, erlauben es Ihnen, die Optimierungseinstellungen pro Datei zu ändern. Ich würde sie für diese Funktion deaktivieren. Wenn Sie es per Makro einfügen, müssen Sie sicherstellen, dass Ihr Compiler es nicht herausoptimiert. Wenn IAR auf maximale Optimierung eingestellt ist, erkennt es diese Schleife und entfernt sie. Der beste Weg, das Timing durchzuführen, ist eine Timer-Funktion, auch wenn der Fragesteller dies aus irgendeinem Grund vermeiden wollte.
#if _Main_Crystal == 25000000
   #define LOOP_DELAY 400
#elif  _Main_Crystal == 16000000
   #define LOOP_DELAY 256
#else
   #error microsecond delay must be adjusted!
#endif
void usDelay( void )
{
   for (int i = 0; i < LOOP_DELAY; i++)
   {
      SERVICE_WATCH_DOG();
   }
}

wo SERVICE_WATCH_DOG()ist ein Makro, um den Watchdog-Timer zu warten. Diese kann auch durch eine ersetzt werden NOP.

Verwenden Sie eine Testfunktion, um herauszufinden, was Ihre LOOP_DELAY-Konstanten für jede Taktfrequenz sein müssen:

void TestDelay( void )
{
   SetIOPin();    // Use a scope to start measuring elapsed time here.
   usDelay();
   ResetIOPin();  // use a scope to end measuring elapsed time here.
}
Danke, aber es ist offensichtlich und unbequem, wenn Sie ein Schnittstellenmodul schreiben, das in Zukunft für unbekannte und verschiedene Taktraten kompiliert werden soll.
@x4mer Könntest du nicht einfach verwenden: #Define LOOP_DELAY (_Main_Crystal / 1000000)

Eine Hardware-Timer-Referenz wird bevorzugt, da ein guter Optimierer die Schleife ganz entfernen oder sie basierend auf den Einstellungen ändern könnte. Das andere hier nicht erwähnte Problem mit Zeitschleifen ist, dass es viel länger dauern kann, wenn während Ihrer Zeitschleife ein Interrupt auftritt. Unterbrechungen sollten sowieso kurz sein, aber nicht jedes System folgt diesem Mantra.