Gibt es eine gute Möglichkeit, die Kommunikation zwischen einem ISR und dem Rest des Programms für ein eingebettetes System zu implementieren, das globale Variablen vermeidet?
Es scheint, dass das allgemeine Muster darin besteht, eine globale Variable zu haben, die zwischen dem ISR und dem Rest des Programms geteilt und als Flag verwendet wird, aber diese Verwendung globaler Variablen widerspricht mir. Ich habe ein einfaches Beispiel mit ISRs im avr-libc-Stil beigefügt:
volatile uint8_t flag;
int main() {
...
if (flag == 1) {
...
}
...
}
ISR(...) {
...
flag = 1;
...
}
Ich kann nicht wegsehen, was im Wesentlichen ein Scoping-Problem ist. Müssen Variablen, auf die sowohl der ISR als auch der Rest des Programms zugreifen können, von Natur aus global sein? Trotzdem habe ich oft Leute gesehen, die Dinge im Sinne von "Globale Variablen sind eine Möglichkeit, die Kommunikation zwischen ISRs und dem Rest des Programms zu implementieren" (Schwerpunkt Mine) gesagt haben, was zu implizieren scheint, dass es andere Methoden gibt. Wenn es andere Methoden gibt, welche?
quelle
Antworten:
Es gibt einen De-facto-Standardweg, um dies zu tun (vorausgesetzt, C-Programmierung):
extern
Schlüsselwort oder einfach versehentlich aufgerufen werden können .static
. Eine solche Variable ist nicht global, sondern auf die Datei beschränkt, in der sie deklariert ist.volatile
. Hinweis: Dies ermöglicht keinen atomaren Zugriff und löst keinen Wiedereintritt!quelle
inline
veraltet ist, werden Compiler bei der Optimierung von Code immer intelligenter. Ich würde sagen, dass die Sorge um den Overhead eine "vorzeitige Optimierung" ist - in den meisten Fällen spielt der Overhead keine Rolle, wenn er überhaupt im Maschinencode vorhanden ist.Das ist das eigentliche Problem. Komm darüber hinweg.
Bevor die Knie-Ruckler sofort darüber schimpfen, wie unrein das ist, lassen Sie mich das ein wenig näher erläutern. Es besteht sicherlich die Gefahr, dass globale Variablen übermäßig verwendet werden. Sie können aber auch die Effizienz steigern, was manchmal bei kleinen Systemen mit begrenzten Ressourcen von Bedeutung ist.
Der Schlüssel ist, darüber nachzudenken, wann Sie sie vernünftigerweise verwenden können und es unwahrscheinlich ist, dass Sie in Schwierigkeiten geraten, im Gegensatz zu einem Fehler, der nur darauf wartet, passiert zu werden. Es gibt immer Kompromisse. Während das generelle Vermeiden globaler Variablen für die Kommunikation zwischen Interrupt- und Vordergrundcode eine verständliche Richtlinie ist, ist es kontraproduktiv, sie wie die meisten anderen Richtlinien auf ein extremes Religionsprinzip zu übertragen.
Einige Beispiele, bei denen ich manchmal globale Variablen verwende, um Informationen zwischen Interrupt- und Vordergrundcode zu übergeben, sind:
Ich stelle sicher, dass die Ticks von 1 ms, 10 ms und 100 ms eine Wortgröße haben, die in einer einzelnen atomaren Operation gelesen werden kann. Wenn Sie eine Hochsprache verwenden, müssen Sie dem Compiler mitteilen, dass sich diese Variablen asynchron ändern können. In C deklarieren Sie sie beispielsweise als extern flüchtig . Dies ist natürlich etwas, das in eine vordefinierte Include-Datei aufgenommen wird, sodass Sie sich das nicht bei jedem Projekt merken müssen.
Ich mache manchmal den 1 s Tick Counter zum Zähler für die gesamte verstrichene Zeit, also mache diesen 32 Bit breit. Das kann bei vielen der von mir verwendeten kleinen Mikrogeräte nicht in einer einzigen atomaren Operation gelesen werden, sodass dies nicht global gemacht wird. Stattdessen wird eine Routine bereitgestellt, die den Mehrwortwert liest, mögliche Aktualisierungen zwischen den Lesevorgängen behandelt und das Ergebnis zurückgibt.
Natürlich hätte es auch Routinen geben können, um die kleineren Tick-Zähler von 1 ms, 10 ms usw. zu erhalten. Das tut Ihnen jedoch sehr wenig, fügt viele Anweisungen hinzu, anstatt ein einzelnes Wort zu lesen, und verbraucht einen anderen Aufrufstapel.
Was ist der Nachteil? Ich nehme an, jemand könnte einen Tippfehler machen, der versehentlich auf einen der Zähler schreibt, was dann das andere Timing im System durcheinander bringen könnte. Das absichtliche Schreiben an einen Schalter würde keinen Sinn ergeben, daher müsste diese Art von Fehler etwas Unbeabsichtigtes wie ein Tippfehler sein. Scheint sehr unwahrscheinlich. Ich erinnere mich nicht, dass dies jemals in weit über 100 kleinen Mikrocontroller-Projekten passiert ist.
Beispielsweise kann der A / D den 0 bis 3 V-Ausgang eines Spannungsteilers lesen, um die 24 V-Versorgung zu messen. Die vielen Messwerte werden durch eine Filterung durchlaufen und dann so skaliert, dass der Endwert in Millivolt angegeben wird. Wenn die Versorgung bei 24,015 V liegt, beträgt der Endwert 24015.
Der Rest des Systems sieht nur einen aktualisierten Live-Wert, der die Versorgungsspannung anzeigt. Es weiß nicht und muss sich auch nicht darum kümmern, wann genau das aktualisiert wird, zumal es viel häufiger aktualisiert wird als die Einschwingzeit des Tiefpassfilters.
Wieder eine Schnittstellenroutine könnte verwendet werden, aber man bekommt sehr wenig Nutzen davon. Die Verwendung der globalen Variablen, wann immer Sie die Versorgungsspannung benötigen, ist viel einfacher. Denken Sie daran, dass Einfachheit nicht nur für die Maschine gilt, sondern dass Einfachheit auch weniger menschliches Versagen bedeutet.
quelle
extern int ticks10ms
durchinline int getTicks10ms()
absolut keinen Unterschied in der kompilierten Baugruppe, während es andererseits schwierig ist, ihren Wert in anderen Teilen des Programms versehentlich zu ändern, und Sie dies auch zulassen eine Möglichkeit, sich an diesen Aufruf zu "binden" (z. B. die Zeit während des Komponententests zu verspotten, den Zugriff auf diese Variable zu protokollieren oder was auch immer). Selbst wenn Sie argumentieren, dass die Wahrscheinlichkeit, dass ein San-Programmierer diese Variable auf Null ändert, keine Kosten für einen Inline-Getter entstehen.Jeder bestimmte Interrupt ist eine globale Ressource. Manchmal kann es jedoch nützlich sein, mehrere Interrupts denselben Code zu verwenden. Beispielsweise kann ein System mehrere UARTs haben, die alle eine ähnliche Sende- / Empfangslogik verwenden sollten.
Ein guter Ansatz besteht darin, die vom Interrupt-Handler verwendeten Dinge oder Zeiger darauf in einem Strukturobjekt zu platzieren und dann die eigentlichen Hardware-Interrupt-Handler so aussehen zu lassen:
Die Objekte
uart1_info
,uart2_info
würde usw. globale Variablen sein, aber sie würden die sein nur globale Variablen durch die Interrupt - Handler verwendet. Alles andere, was die Handler berühren werden, wird in diesen behandelt.Beachten Sie, dass alles, auf das sowohl der Interrupt-Handler als auch der Hauptleitungscode zugreifen, qualifiziert sein muss
volatile
. Es mag am einfachsten sein, einfachvolatile
alles zu deklarieren, was vom Interrupt-Handler überhaupt verwendet wird. Wenn jedoch die Leistung wichtig ist, möchten Sie möglicherweise Code schreiben, der Informationen in temporäre Werte kopiert, diese bearbeitet und dann zurückschreibt. Zum Beispiel anstatt zu schreiben:schreiben:
Der erstere Ansatz mag leichter zu lesen und zu verstehen sein, ist jedoch weniger effizient als der letztere. Ob dies ein Problem darstellt, hängt von der Anwendung ab.
quelle
Hier sind drei Ideen:
Deklarieren Sie die Flag-Variable als statisch, um den Bereich auf eine einzelne Datei zu beschränken.
Machen Sie die Flag-Variable privat und verwenden Sie Getter- und Setter-Funktionen, um auf den Flag-Wert zuzugreifen.
Verwenden Sie ein Signalisierungsobjekt wie ein Semaphor anstelle einer Flagvariablen. Der ISR würde das Semaphor setzen / posten.
quelle
Ein Interrupt (dh der Vektor, der auf Ihren Handler zeigt) ist eine globale Ressource. Selbst wenn Sie eine Variable auf dem Stapel oder auf dem Heap verwenden:
oder objektorientierter Code mit einer 'virtuellen' Funktion:
… Der erste Schritt muss eine tatsächliche globale (oder zumindest statische) Variable beinhalten, um diese anderen Daten zu erreichen.
Alle diese Mechanismen fügen eine Indirektion hinzu, sodass dies normalerweise nicht erfolgt, wenn Sie den letzten Zyklus aus dem Interrupt-Handler herausdrücken möchten.
quelle
Ich codiere derzeit für Cortex M0 / M4 und der Ansatz, den wir in C ++ verwenden (es gibt kein C ++ - Tag, daher ist diese Antwort möglicherweise nicht zum Thema gehörend), lautet wie folgt:
Wir verwenden eine Klasse,
CInterruptVectorTable
die alle Interrupt-Serviceroutinen enthält, die im tatsächlichen Interrupt-Vektor der Steuerung gespeichert sind:Die Klasse
CInterruptVectorTable
implementiert eine Abstraktion der Interruptvektoren, sodass Sie zur Laufzeit verschiedene Funktionen an die Interruptvektoren binden können.Die Schnittstelle dieser Klasse sieht folgendermaßen aus:
Sie müssen die Funktionen erstellen, die in der Vektortabelle gespeichert sind,
static
da der Controller keinenthis
Zeiger bereitstellen kann, da die Vektortabelle kein Objekt ist. Um dieses Problem zupThis
umgehen, haben wir den statischen Zeiger in derCInterruptVectorTable
. Bei Eingabe einer der statischen Interrupt-Funktionen kann auf denpThis
Zeiger zugegriffen werden, um Zugriff auf Mitglieder des einen Objekts von zu erhaltenCInterruptVectorTable
.Jetzt können Sie im Programm
SetIsrCallbackfunction
mit den einen Funktionszeiger auf einestatic
Funktion setzen, die aufgerufen werden soll, wenn ein Interrupt auftritt. Die Zeiger werden in der gespeichertInterruptVectorTable_t virtualVectorTable
.Und die Implementierung einer Interrupt-Funktion sieht folgendermaßen aus:
Das ruft also eine
static
Methode einer anderen Klasse auf (die es sein kannprivate
), die dann einen anderenstatic
this
Zeiger enthalten kann, um Zugriff auf Mitgliedsvariablen dieses Objekts zu erhalten (nur eine).Ich denke, Sie könnten ähnliche
IInterruptHandler
Zeiger auf Objekte erstellen und mit diesenstatic
this
verknüpfen und speichern, sodass Sie den Zeiger in all diesen Klassen nicht benötigen . (Vielleicht versuchen wir das in der nächsten Iteration unserer Architektur)Der andere Ansatz funktioniert gut für uns, da die einzigen Objekte, die einen Interrupt-Handler implementieren dürfen, diejenigen innerhalb der Hardware-Abstraktionsschicht sind und wir normalerweise nur ein Objekt für jeden Hardware-Block haben, so dass es gut funktioniert, mit
static
this
-zeigern zu arbeiten. Und die Hardware-Abstraktionsschicht bietet noch eine weitere Abstraktion für Interrupts,ICallback
die dann in der Geräteschicht über der Hardware implementiert wird.Greifen Sie auf globale Daten zu? Sicher, aber Sie können die meisten benötigten globalen Daten wie die
this
Zeiger und die Interrupt-Funktionen privat machen .Es ist nicht kugelsicher und erhöht den Overhead. Mit diesem Ansatz haben Sie Schwierigkeiten, einen IO-Link-Stack zu implementieren. Wenn Sie jedoch nicht sehr zeitlich begrenzt sind, funktioniert dies recht gut, um eine flexible Abstraktion von Interrupts und Kommunikation in Modulen zu erhalten, ohne globale Variablen zu verwenden, auf die von überall aus zugegriffen werden kann.
quelle