Verzögerungsfunktion in der ARM-Programmierung

Ich habe gerade angefangen, auf ARM zu programmieren, ich hatte einige Erfahrung mit AVR, aber nicht so viel. Die Frage ist wahrscheinlich zu trivial, aber das Material über ARM ist zu wenig im Netz ... sorry trotzdem.

Ich weiß, dass wir für die Implementierung von Verzögerungen in ARM Timer oder Busy-Loops verwenden können. Ich habe diese Methoden ausprobiert. Aber einer meiner Freunde schlägt vor, dass es eine "delay.h"-Headerdatei für arm gibt, die alle notwendigen Verzögerungsfunktionen enthält, genau wie avr, delay_ms und ... .

Zunächst einmal, so dumm diese Frage auch klingen mag, gibt es wirklich eine eingebaute Verzögerungsfunktion in der ARM-Programmierung. Ist es in delay.h?

Wenn ja, warum ist die Verwendung von Timern zur Implementierung von Verzögerungen so beliebt? Warum verwenden sie nicht einfach die eingebaute Funktion?

Ich studiere die LPC17xx-Serie.

Stellen Sie sich eine ARM-CPU als eine vollwertige CPU vor, die Sie für ein Betriebssystem wie Linux verwenden könnten. Stellen Sie sich nun vor, was passieren würde, wenn Sie die Ausführung auf dem gesamten Chip blockieren würden, wann immer Sie eine Zeitverzögerungsfunktionalität wünschen. Das Ganze würde während der Verzögerung sperren. Deshalb werden Timer bevorzugt.
@Polynomial bitte konvertieren, um zu antworten. +1
@ChrisK Soll ich machen :)
Auf welcher Plattform arbeitest du? "NXP Leaflet" oder so? Einige der Board-Familien werden Foren und eine Community haben, um Menschen mit Fragen zu unterstützen. Im chipKIT-Code habe ich eine for()Schleifenzahl von 100 gesehen und der Kommentar sagte, es sei eine Verzögerung von 10 us.
Sie scheinen Funktionen des Prozessors mit Funktionen einer bestimmten Toolchain oder Bibliothek zu verwechseln , wo Sie möglicherweise eine Header-Datei finden. In Bezug auf den Prozessor selbst haben die meisten aktuellen ARM-Varianten eine Systemzeit, die aktiviert und zum Ausführen von Softwareereignis-Timern verwendet werden kann, deren Verwendung jedoch optional ist.

Antworten (4)

Viele Compiler und Bibliotheken unterstützen die ARM-Familie. Ich würde keine Zeit damit verschwenden, nach einer einzelnen Lösung zu suchen, die in einem einzelnen Dateinamen definiert ist. Das ist sicher nicht „die“ Lösung für „ARM“.

Wie bei AVR und den meisten anderen Prozessoren, sicherlich Mikrocontrollern, haben Sie die Wahl, in einer Vordergrundschleife entweder eine kalibrierte Schleife zu verwenden oder einen Timer abzufragen. Sie können bei Bedarf auch einen Timer-basierten Interrupt verwenden. Es hängt davon ab, wofür Sie eine Verzögerung durchführen müssen und was sonst noch während dieser Verzögerung vor sich gehen könnte. Abhängig von dem spezifischen Chip und der Zeitspanne, die Sie verzögern möchten, können Sie den Chip möglicherweise für diesen Zeitraum in den Ruhezustand versetzen.

Wenn Sie eine Bibliothek für andere Dinge wie SPI, I2C usw. für den Zielprozessor und die Toolchain verwenden, haben Sie wahrscheinlich auch Timer-Routinen und können diese einfach verwenden. Wenn Sie Ihre eigenen rollen (am portabelsten, aber auch am meisten Arbeit), dann schauen Sie sich die Timer an. Auf lange Sicht wird es einfacher sein, Code zu warten, der auf einem Timer basiert, anstatt auf einer kalibrierten (Count to N) Schleife .

Es gibt eine Reihe von Toolchains und Bibliotheken, die speziell auf die NXP LPC-Familie zugeschnitten sind, wenn Sie diesen Weg einschlagen möchten.

In keiner Weise, Gestalt oder Form. Gibt es eine einzige Lösung, auf die Sie sich beschränken sollten? Nicht für ARM, nicht für AVR, nicht für einen von ihnen.

Manchmal wird Code in einem Kontext ausgeführt, in dem sonst nichts passiert. Wenn der Prozessor ein E/A-Bit setzt, dann eine Reihe von Anweisungen ausführt, die insgesamt 1 ms zur Ausführung benötigen, und dann das E/A-Bit löscht, dann pulsiert die von diesem Bit gesteuerte Leitung für 1 ms hoch. Einfache Verzögerungsverfahren können in solchen Zusammenhängen nützlich sein. Sie sind jedoch weit weniger nützlich in Kontexten, in denen andere Dinge vor sich gehen können. Wenn während der oben genannten Verzögerung ein Interrupt auftritt, der 100 us für den Service benötigt, wird der Pin für 1,1 ms statt 1 ms hoch gepulst. Schlimmer noch, wenn man versucht, eine solche Verzögerung in einer Interrupt-Serviceroutine zu verwenden, werden der Hauptleitungscode und alle Interrupts mit niedrigerer Priorität ausgesetzt, bis eine solche Verzögerung abgeschlossen ist.

Im Allgemeinen ist der oben genannte Ansatz zur Implementierung geeignet, wenn entweder (1) man so etwas wie einen Bootloader schreibt, der in der Lage sein sollte, ohne Interrupts zu arbeiten, und nichts anderes passiert, während er läuft; (2) man braucht eine Verzögerung, die ausreichend kurz ist, es macht keinen Sinn, zu versuchen, etwas anderes zu tun. Wenn man beispielsweise einen 32-MHz-Prozessor verwendet, um mit einem Gerät zu kommunizieren, das 1,6 us Leerlaufzeit zwischen Datenbytes benötigt, würde das Einfügen von Code, der 52 Zyklen damit verbringt, nichts zu tun, bedeuten, dass der ARM gut positioniert sein könnte, um das nächste Byte um 1,6 zu senden uns später. Wenn der ARM dagegen versucht, etwas anderes zu tun, wäre er wahrscheinlich am Ende von 1.6us damit beschäftigt und würde das nächste Byte erst einige Zeit später senden.

Stellen Sie sich eine ARM-CPU als eine vollwertige CPU vor, die Sie für ein Betriebssystem wie Linux verwenden könnten. Stellen Sie sich nun vor, was passieren würde, wenn Sie die Ausführung auf dem gesamten Chip blockieren würden, wann immer Sie eine Zeitverzögerungsfunktionalität wünschen. Das Ganze würde während der Verzögerung abstürzen, was zu einer völlig unbrauchbaren Erfahrung für jede Art von benutzerinteraktivem System führen würde. Deshalb werden Timer bevorzugt.

Timer sind nützlich, weil sie die Blockierungssituation vermeiden. Die CPU fährt mit der Ausführung von Code fort und springt zurück zu einer Handler-Routine, sobald der Zeitgeber abgelaufen ist. Dies bietet eine Form des asynchronen Betriebs, der einen viel flexibleren Code ermöglicht.

In Architekturen, die interne Zeitgeber nicht direkt unterstützen, aber externe Interrupts unterstützen, kann ein externes Zeitgebergerät verwendet werden. Der Timer ist mit einem Zeitversatz (normalerweise ein Skalar und Multiplikator) und einer Unterbrechungsnummer programmiert. Wenn der externe Timer den Interrupt auslöst, liest die CPU eine Interrupt-Tabelle, um den Interrupt-Vektor zu finden , der eine lineare Adresse ist, an die die Ausführung übertragen wird, um das Timer-Ereignis zu handhaben.

Vielen Dank für die nette Erklärung. Können wir trotzdem Funktionen wie delay_ms auch in ARM verwenden? So ineffizient sie auch sind, existieren sie in der offiziellen Syntax?
Hängt davon ab, was Sie unter "offizieller Syntax" verstehen. ARM ist eine CPU mit einem Befehlssatz. Ich weiß nicht, welche Bibliotheken Sie darauf ausführen, und die einzige Erfahrung, die ich mit ARM habe, ist die Rohbaugruppe. Ich würde es über den System Control Coprocessor (CP15) über das Cycle Count Register machen . Im Wesentlichen konfigurieren Sie das Performance Monitor Control Register (PMCR) so, dass das CCR auf einer 64-Zyklus-Skala tickt und dann eine Schleife durchläuft, bis eine bestimmte Anzahl von Ticks vergangen ist, basierend auf der Taktfrequenz des Prozessors.
(Fortsetzung vom letzten Kommentar) Wenn Ihre Taktfrequenz also 40 MHz beträgt und der CCR alle 64 Zyklen tickt, sind das 625.000 Ticks pro Sekunde. Das ergibt eine Auflösung von 1,6 Mikrosekunden und eine maximale Verzögerung von 3.435,97 Sekunden (da CCR ein 32-Bit-Register mit Vorzeichen ist).
ARM-CPUs decken eine breite Palette von Anwendungen ab, von winzigen Geräten, die direkt mit 8-Bit-Mikros konkurrieren, bis hin zu High-End-Geräten, die für leichte Laptops und energieeffiziente Servercluster geeignet sind. Abgesehen davon spielen sowohl beschäftigtes Warten als auch Timer-basierte Verzögerungen ihre Rolle, selbst in großen Systemen - wenn beispielsweise ein Betriebssystemkernel eine winzige Zeit auf Hardware warten muss, kann es sein, dass er beschäftigt wartet, anstatt einen Timer zu verwenden, der hat keine ausreichend feine Körnigkeit.
Was Chris sagte: "ARM" ist ein zu weit gefasster Begriff, um uns einen nützlichen Hinweis darauf zu geben, was Sie tun. Verwenden Sie einen Cortex-M0 mit 24 MHz? Oder ein A15 mit vier Kernen zu je 1,8 GHz? Was passiert sonst noch auf diesem System? Verwenden Sie ein Betriebssystem oder schlagen Sie einfach auf das Metall? Die Antwort auf diese Fragen wird die empfohlene Antwort auf Ihre Frage stark verändern.
-1 weil, was Chris und Jon gesagt haben - viele Dinge sind "ausgewachsene CPUs". Ich könnte argumentieren, dass das sogar ein winziger 6-Pin-PIC mit ~ 200 Bytes Programmspeicher ist (bitte in diesem Zusammenhang "CPU" definieren). Beschäftigtes Warten hat seinen Platz in jeder CPU-Architektur, wenn auch fast ausschließlich in der Bare-Metal-Programmierung. Die große Mehrheit der ARM-Geräte auf dem Markt führt jedoch Bare-Metal-Code aus. Wenn also Vermutungen angestellt werden sollten, fragt das OP danach .

Es ist natürlich möglich, einen ARM-Prozessor für eine einfache Verzögerung zu verwenden. Siehe meinen Code unten. Je nachdem, wofür Sie Ihren Prozessor sonst noch benötigen, ist dies jedoch möglicherweise nicht die beste Lösung. Dieser Code läuft auf einem Prozessor der LPC2000-Serie von NXP.

/**********************************************************************/
/* Timer.c                                                            */
/* Implements a delay :                                               */
/*   TMR0 is a mcrosecond timer capable of timing events of upto      */
/*   4294.967295 seconds (1 hour 11 min 36.967295 sec)                */
/*   includes both delay and stop watch functions                     */
/*       void Delay(unsigned int delay); delay in microseconds        */
/*   Does not use interupts.  Note timer may rollover during delay    */
/*   so code checks for this                                          */
/*   Note: delay is minimum delay as interupts can slow it down       */
/**********************************************************************/
/*
 * Note: Assumes TMR0 is already setup with 1 microsecond tick
 */

#include <LPC2103.h>

#include "Timer.h"

void Delay(unsigned int delay){
    unsigned int start;
    unsigned int stop;
    unsigned int now;

    start = T0TC;
    stop = start + delay + 1;           /* +1 because call could arrive just
                                       before TOTC changes */
    if (stop > start){                /* usually is */
        do{
            now = T0TC;
        }while (now < stop && now >= start);  /* Catch timer roll-over as done */
    }else{                                    /* Need timer to roll over */
        do{
            now = T0TC;
        }while (now < stop || now >= start);  
    }
}

Selbst wenn Sie gerne nur in einer Schleife sitzen und auf eine Verzögerung warten, ist ein Timer möglicherweise besser, da es mit einer einfachen Schleife wie z. B. schwieriger ist, Verzögerungszeiten abzuschätzen

for(i=1000; i >0; i--){
    ;
}

Weil ein guter optimierender Compiler dies einfach optimieren kann.

Selbst wenn es nicht optimiert wird, ist es schwierig, genau zu wissen, wie lange diese Verzögerung dauern wird, da die Prozessoren der NXP LPC-Serie Speicherbeschleuniger haben und der Prozessor Pipe-Lining hat.

Nur fürs Protokoll: Damit die Schleife mit eingeschalteter Optimierung funktioniert, muss sie aktiviert seinfor(volatile int i=1000; i >0; i--) ;