Kritische Abschnitte zu Cortex-M3

10

Ich frage mich ein wenig über die Implementierung kritischer Codeabschnitte auf einem Cortex-M3, bei denen Ausnahmen aufgrund von Zeiteinschränkungen oder Parallelitätsproblemen nicht zulässig sind.

In meinem Fall verwende ich einen LPC1758 und habe einen TI CC2500-Transceiver an Bord. Der CC2500 verfügt über Pins, die als Interrupt-Leitungen für Daten im RX-Puffer und als freier Speicherplatz im TX-Puffer verwendet werden können.

Als Beispiel möchte ich einen TX-Puffer im SRAM meiner MCU haben, und wenn im TX-Puffer des Transceivers freier Speicherplatz vorhanden ist, möchte ich diese Daten dort schreiben. Die Routine, die Daten in den SRAM-Puffer legt, kann jedoch offensichtlich nicht durch den Interrupt für freien Speicherplatz im TX unterbrochen werden. Was ich also tun möchte, ist, Interrupts vorübergehend zu deaktivieren, während dieser Vorgang zum Füllen dieses Puffers ausgeführt wird, aber alle Interrupts, die während dieses Vorgangs auftreten, nach Abschluss ausführen.

Wie geht das am besten mit Cortex-M3?

Emil Eriksson
quelle

Antworten:

11

Der Cortex M3 unterstützt ein nützliches Operationspaar (das auch bei vielen anderen Computern üblich ist), das als "Load-Exclusive" (LDREX) und "Store-Exclusive" (STREX) bezeichnet wird. Konzeptionell führt die LDREX-Operation ein Laden durch und stellt auch eine spezielle Hardware ein, um zu beobachten, ob der Speicherort, der geladen wurde, möglicherweise von etwas anderem geschrieben wurde. Wenn Sie einen STREX für die vom letzten LDREX verwendete Adresse ausführen, wird diese Adresse nur geschrieben , wenn sie zuerst von nichts anderem geschrieben wurde . Der STREX-Befehl lädt ein Register mit 0, wenn der Speicher stattgefunden hat, oder 1, wenn er abgebrochen wurde.

Beachten Sie, dass STREX oft pessimistisch ist. Es gibt eine Vielzahl von Situationen, in denen möglicherweise entschieden wird, das Geschäft nicht durchzuführen, selbst wenn der betreffende Ort tatsächlich nicht berührt wurde. Beispielsweise führt ein Interrupt zwischen einem LDREX und STREX dazu, dass der STREX davon ausgeht, dass der beobachtete Ort möglicherweise getroffen wurde. Aus diesem Grund ist es normalerweise eine gute Idee, die Codemenge zwischen LDREX und STREX zu minimieren. Betrachten Sie beispielsweise Folgendes:

inline void safe_increment (uint32_t * addr)
{
  uint32_t new_value;
  tun
  {
    new_value = __ldrex (addr) + 1;
  } while (__ strex (new_value, addr));
}}

was zu etwas kompiliert wie:

;; Angenommen, R0 enthält die betreffende Adresse. r1 verwüstet
lp:
  ldrex r1, [r0]
  füge r1, r1, # 1 hinzu
  strex r1, r1, [r0]
  cmp r1, # 0; Testen Sie, ob nicht Null ist
  bne lp
  .. Code geht weiter

Die überwiegende Mehrheit der Zeit, in der der Code ausgeführt wird, passiert nichts zwischen LDREX und STREX, um sie zu "stören", so dass der STREX ohne weiteres erfolgreich sein wird. Wenn jedoch unmittelbar nach dem LDREX- oder ADD-Befehl ein Interrupt auftritt, führt der STREX den Speicher nicht aus, sondern der Code liest den (möglicherweise aktualisierten) Wert von [r0] zurück und berechnet einen neuen inkrementierten Wert basierend darauf.

Die Verwendung von LDREX / STREX zum Erstellen von Operationen wie safe_increment ermöglicht es, nicht nur kritische Abschnitte zu verwalten, sondern in vielen Fällen auch die Notwendigkeit dieser zu vermeiden.

Superkatze
quelle
Es gibt also keine Möglichkeit, Interrupts zu "blockieren", damit sie nach dem Entsperren wieder zugestellt werden können. Mir ist klar, dass dies wahrscheinlich eine unelegante Lösung ist, auch wenn dies möglich ist, aber ich möchte nur mehr über die Behandlung von ARM-Interrupts erfahren.
Emil Eriksson
3
Es ist möglich, Interrupts zu deaktivieren, und auf dem Cortex-M0 gibt es oft keine praktische Alternative dazu. Ich halte den LDREX / STREX-Ansatz für sauberer als das Deaktivieren von Interrupts, obwohl dies in vielen Fällen zugegebenermaßen keine Rolle spielt (ich denke, das Aktivieren und Deaktivieren ist jeweils ein Zyklus, und das Deaktivieren von Interrupts für fünf Zyklen ist wahrscheinlich keine große Sache). . Beachten Sie, dass ein ldrex / strex-Ansatz funktioniert, wenn Code auf eine Multi-Core-CPU migriert wird, während ein Ansatz, der Interrupts deaktiviert, dies nicht tut. Außerdem führen einige RTOS-Codes Code mit reduzierten Berechtigungen aus, die keine Interrupts deaktivieren dürfen.
Supercat
Ich werde wahrscheinlich sowieso mit FreeRTOS arbeiten, also werde ich das nicht selbst machen, aber ich würde es trotzdem gerne lernen. Welche Methode zum Deaktivieren von Interrupts sollte ich verwenden, um die Interrupts wie beschrieben zu blockieren, anstatt alle während des Vorgangs auftretenden Interrupts zu verwerfen? Wie würde ich es tun, wenn ich sie verwerfen möchte?
Emil Eriksson
Der obigen Antwort kann nicht vertraut werden, da dem zugehörigen Code eine Klammer fehlt: while(STREXW(new_value, addr); Wie können wir glauben, dass das, was Sie sagen, richtig ist, wenn Ihr Code nicht einmal kompiliert wird?
@ Tim: Entschuldigung, meine Eingabe ist nicht perfekt. Ich habe nicht den tatsächlichen Code, den ich zum Vergleich geschrieben habe, zur Hand, daher kann ich mich nicht erinnern, ob das von mir verwendete System STREXW oder __STREXW verwendet hat, aber in der Compiler-Referenz wird __strex als intrinsisch aufgeführt (im Gegensatz zu STREXW, das auf beschränkt ist 32-Bit-STREX, die intrinsische Verwendung von __strex generiert je nach angegebener Zeigergröße STREXB, STREXH oder STREX)
Supercat
4

Es hört sich so an, als ob Sie einige kreisförmige Puffer oder FIFOs in Ihrer MCU-Software benötigen. Indem Sie zwei Indizes oder Zeiger zum Lesen und Schreiben in das Array verfolgen, können sowohl Vordergrund als auch Hintergrund ohne Interferenz auf denselben Puffer zugreifen.

Der Vordergrundcode kann jederzeit in den Ringpuffer geschrieben werden. Es fügt Daten am Schreibzeiger ein und erhöht dann den Schreibzeiger.

Der Hintergrundcode (Interrupt-Behandlung) verbraucht Daten vom Lesezeiger und erhöht den Lesezeiger.

Wenn die Lese- und Schreibzeiger gleich sind, ist der Puffer leer und der Hintergrundprozess sendet keine Daten. Wenn der Puffer voll ist, weigert sich der Vordergrundprozess, mehr zu schreiben (oder kann je nach Bedarf alte Daten überschreiben).

Durch die Verwendung von kreisförmigen Puffern zum Entkoppeln von Lesern und Schreibern müssen Interrupts nicht mehr deaktiviert werden.

Toby Jaffey
quelle
Ja, ich werde natürlich kreisförmige Puffer verwenden, aber Inkrementieren und Dekrementieren sind keine atomaren Operationen.
Emil Eriksson
3
@Emil: Das müssen sie nicht sein. Für einen klassischen Ringpuffer mit zwei Zeigern und einem "unbrauchbaren" Steckplatz ist lediglich erforderlich, dass die Speicherschreibvorgänge atomar sind und in der richtigen Reihenfolge erzwungen werden. Der Leser besitzt einen Zeiger, der Schreiber besitzt den anderen, und obwohl beide einen der beiden Zeiger lesen können, schreibt nur der Besitzer des Zeigers seinen Zeiger. Zu diesem Zeitpunkt benötigen Sie lediglich atomare Schreibvorgänge in der richtigen Reihenfolge.
John R. Strohm
2

Ich kann mich nicht an den genauen Speicherort erinnern, aber in den Bibliotheken, die von ARM stammen (nicht TI, ARM, es sollte sich unter CMSIS oder so etwas befinden, ich verwende ST, aber ich erinnere mich, dass ich irgendwo gelesen habe, dass diese Datei von ARM stammt, also sollten Sie sie auch haben ) gibt es eine globale Interrupt-Deaktivierungsoption. Es ist ein Funktionsaufruf. (Ich bin nicht auf der Arbeit, aber ich werde morgen die genaue Funktion nachschlagen). Ich würde das mit einem schönen Namen in Ihrem System abschließen und die Interrupts deaktivieren, Ihr Ding machen und wieder aktivieren. Die bessere Option wäre jedoch die Implementierung eines Semaphors oder einer Warteschlangenstruktur anstelle der globalen Interrupt-Deaktivierung.

Ktc
quelle