Hallo zusammen, ich habe angefangen mich ein bisschen über das Thema C++ und Interrupts zu informieren. Folgenden Artikel habe ich darüber gefunden: https://www.mikrocontroller.net/articles/AVR_Interrupt_Routinen_mit_C++ Den Ansatz finde ich schon nicht schlecht, die ISR kann auf das Objekt (mit dem es registriert wurde) direkt zugreifen ohne das das Objekt global sein muss. Außerdem ist auch die Initialisierung des Interrupts direkt am richtigen Ort. Das einzige was mir daran nicht gefällt ist, dass ein Interrupt auf ein Objekt beschränkt ist. Als Beispiel hat man eine Klasse Servo. Mit den Atributten aktuellePosition und ZielPosition. Nun hat man zum Beispiel sechs servos. Alle 20ms soll überprüft werden, ob jeder Servo an der richtigen Stelle ist und ggf. eben ein Stück in die richtige Richtung bewegt werden. In C hätte ich hier jetzt ein globales Array mit fünf Servos. Dazu einen Timer bzw. eine ISR für einen Timer, die alle 20msm aufgerufen wird (CTC mode oder so). Dort wird das Array durchgegangen und das oben beschriebene auf alle Servos ausgeführt. In C++ stelle ich mir vor, dass die ISR im Grunde fünf mal hintereinander aufgerufen wird. Und dafür aber eben nur auf einen Servo ansteuert. Nun gibt es aber ja nur einen Timer, daher ist das wohl kaum realisierbar. Eine Idee, die ich mir aber zumindest überlegt hatte, war das Beispiel aus dem Artikel so zu erweitern, dass man statt einer owner Variable ein owner Array hat. In der ISR wird dann wie in C das ganze Array durchgegangen. Vorteile: - Die Servo Objekte müssen nach wie vor nicht global sein, der Code ist zumindest so halb an der richtigen Stelle - Der Aufwand wäre vermutlich nicht so hoch. Es wäre wohl immer noch gut lesbar Nachteile: - Man muss immer noch das ganze Array durchgehen, außerdem muss die Größe bekannt sein (bzw. bei Ändernung muss daran gedacht werden das auch zu ändern - Die Initialisierung des Timers/des Interrupts geschieht mehrmals (für jedes Objekt) Ist das sinnvoll? Gibt es was besseres? Vielen Dank!
??? Warum nicht eine Callback-Funktion aus jeder Instanz heraus registrieren und dann bei Interrupt abarbeiten?
Moin, Leon D. schrieb: > Gibt es was besseres? Interrupt-Gedoens in einer dafuer geeigneten Sprache schreiben? iirc meint sogar der gute Bjarne, dass die Sprache zum Problem passen sollte... Gruss WK
Hättest du ein Code Beispiel für deine Idee? Ich verstehe nicht so wirklich wie du dir die Umsetzung vorstellst.
Dergute W. schrieb: > Moin, > > Leon D. schrieb: >> Gibt es was besseres? > > Interrupt-Gedoens in einer dafuer geeigneten Sprache schreiben? > iirc meint sogar der gute Bjarne, dass die Sprache zum Problem passen > sollte... > > Gruss > WK Du hast recht, die Sprache sollte zum Problem passen. Meiner Meinung nach ist das Problem aber nicht, dass C++ nicht für Microcontroller geignet ist, sondern das der Compiler für avr noch nicht genug support bietet. Aber hier möchte ich jetzt keine Dikussion beginnen über "C++ vs C bei avr". Das gabs schon oft genug. Sagen wir einfach mal die bisherige Lösung ist, dass die Objekte global sind und eine update Methode besitzen. In der isr wird dann diese update Methode aufgerufen. Das ist nicht wirlich schön, ich hätte die Objekte gerne nicht global. Die Möglichkeit einfach eine andere Programmiersprache zu Nutzen steht nicht zur Option.
Leon D. schrieb: > Ich verstehe nicht so > wirklich wie du dir die Umsetzung vorstellst. Ganz einfach: Wie macht das dein PC, mit den BIOS-Interuppts? Es gibt eine (z. B. auch verkettete) Liste, in der die Adressen der Callbacks eingetragen werden. Also bei der Instanzierung registriert. Beim Interrupt werden alle Callbacks nacheinander aufgerufen. Wie die Liste geführt und verwaltet wird, hängt von deinem System und den Resourcen ab: statisch, dynamisch, ...
Leon D. schrieb: > Das ist nicht wirlich schön, ich hätte die Objekte > gerne nicht global. Sehe ich das richtig? Es gibt eine ISR, und es sollen eine Handvoll Klassen bedient werden. Dann muss irgendein Container geschaffen werden, z.B. Array, Liste Die ISR "gehört" dem Container Der Container darf/sollte(?) Singleton sein. Das einzige, was dann global sichtbar/angelegt ist: Container::getInstance()
mal einfach schrieb: > Leon D. schrieb: >> Ich verstehe nicht so >> wirklich wie du dir die Umsetzung vorstellst. > > Ganz einfach: Wie macht das dein PC, mit den BIOS-Interuppts? > > Es gibt eine (z. B. auch verkettete) Liste, in der die Adressen der > Callbacks eingetragen werden. Also bei der Instanzierung registriert. > Beim Interrupt werden alle Callbacks nacheinander aufgerufen. > > Wie die Liste geführt und verwaltet wird, hängt von deinem System und > den Resourcen ab: statisch, dynamisch, ... Das System ist ein atmega328p ... das hätte ich vlt noch erwähnen sollen. Irgendwie bin ich davon ausgegangen, dass das Standard ist. In der Theorie als quasi ein Array mit Referenz auf Funktionen, die aufgerufen werden sollen (in dem Fall statisch, da es sich ja nicht ändert). Das ist dann global und jeder Servo steckt in das Array eine Referenz auf seine update Methode. Die ISR geht dann das array durch und ruft die Funktionen auf. Realisierung mit einem Array von Funktionspointern? Porblem hierbei wäre aber das man dann ein globales Array mit Funktionspointern hätte. Ein Fortschritt gegenüber de globalen Servos direkt, aber letzendes ist so immer noch ungewollter Zugriff von außen möglich. Da scheint mir die von mir beschrieben Lösung eleganter ...letzendes könnte man statt einem Besitzer auch Funktionspointer übergeben. Die Interrupt Klasse hat dann ein Array an Funktionspointern, die dann abgearbeitet werden. Das wäre wohl sogar genau das, was ich ursprünglich wollte. Abgesehen davon, dass ein zusätzlicher Zwischenschritt benötigt wird. Sprich, nicht die ISR ist direkt eine MemberFunktion (und wird für jedes Objekt alle 20ms aufgerufen), sondern jedes Objekt hat eine update methode. Und eine ISR alle 20ms ruft diese update Methode entsprechend 5 mal (auf jedes Objekt, dass die update Methode vorher registriert hat ... oder auch irgendeine andere Methode) auf. Sollte das selbe sein wie dein Vorschlag, nur mit dem Unterschied, dass das Funktionspointer Array nicht global sein muss, sondern gekapselt ist
Arduino Fanboy D. schrieb: > Leon D. schrieb: >> Das ist nicht wirlich schön, ich hätte die Objekte >> gerne nicht global. > > Sehe ich das richtig? > Es gibt eine ISR, und es sollen eine Handvoll Klassen bedient werden. > > Dann muss irgendein Container geschaffen werden, z.B. Array, Liste > Die ISR "gehört" dem Container > Der Container darf/sollte(?) Singleton sein. > > Das einzige, was dann global sichtbar/angelegt ist: > Container::getInstance() Es gibt nur eine Klasse. Aber es gibt mehrere Objekte, die von dieser ISR bedient werden sollen. Deine idee entspricht glaube ich in etwa meinem Vorschlag: Eine Klasse mit einem Array auf alle Objekte. Dieser Klasse gehört die ISR und sie führt auf jedes Objekt dann irgendeine Methode aus z.B. servos[i].update() bzw. der Code in der update Methode kann auch direkt in der ISR stehen. Je nachdem, ob die Klasse friend ist und was schneller oder sauberer ist. Unterschied ist nur, dass du die Container Klasse global als Singelton machen würdest, während ich sie lokal als innere Klasse machen würde. Ich denke die innere Klasse wäre die bessere Wahl.
Leon D. schrieb: > während ich sie lokal als > innere Klasse machen würde. Ich lebe in einer Arduino gefärbten Welt, und da gehen alle nicht statischen lokalen Variablen der setup() und loop()verloren. Die main() sehe ich nicht. Hast du allerdings eine main() vor dir, dann geht das natürlich.
Das Beispiel benötigt für eine saubere Trennung mindestens 3 Klassen. Einen Timer der ATMega Register setzt und den Interrupt empfängt, einen Servo der deine gewünschte Logik beinhaltet und einen Verwalter der die Servos enthält.
1 | struct Timer { |
2 | // do whatever an ATMega timer does...
|
3 | };
|
4 | |
5 | struct Servo { |
6 | void doStuff() {} |
7 | };
|
8 | |
9 | struct ServoManager { |
10 | private:
|
11 | void callback() { |
12 | for (auto& s : servos) |
13 | s.doStuff(); |
14 | }
|
15 | std::array<Servo, 5> servos; |
16 | };
|
Leon D. schrieb: > as ist dann global und jeder Servo steckt in das Array eine > Referenz auf seine update Methode. Die ISR geht dann das array durch und > ruft die Funktionen auf. Nö, warum zwingend global? Du kannst ein Feld, eine verkettete Liste, einen Container-Objekt, oder sonst was nehmen. Darum schrieb ich registrieren! Entscheidend ist das Stichwort Callback. Damit bekommst du die Verbindung zwischen der ISR und den Objekten. Leon D. schrieb: > Eine Klasse mit einem Array auf alle Objekte. Und wie groß soll das Feld dann sein? Das ist statisch! Wenn die Daten in der Servo-Instanz gehalten und nur verlinkt werden. Hast du maximale Flexibilität/Abstraktion. Instanzen können dann auch nur temporär vorhanden sein (z. B. lokal in einer Funktion). Du musst nur des Feld, die Liste, Container-Objekt, ... verwalten.
Wobei man aufpassen muss, wenn die ISR eine Methode aufruft, dass sie das im Interruptkontext tut - das gesamte restliche System sich also in einem unbestimmten Zustanden befinden darf. Es gibt einen Grund, warum Interrupt-Handler in Betriebssystemen bestimmte Dinge schlicht nicht dürfen. Und genau das Betriebssystem bastelt man sich hier ja.
Okay also gibt vlt noch ein paar erwähenswerte Sachen (die mir auch selbst so nicht bewusst sind). Zum einen musss (soweit ich das jetzt verstanden und ausprobiert habe) die ISR eine static Methode sein (wenn sie eine member funktion ist). Daher muss denke ich ein Konzept gewählt werden mit einem static pointer zum "Besitzer"... da man ja sonst aus der ISR auch nur static methoden aufrufen könnte. Auch über die callback Sache habe ich hier mir nochmal Gedanken gemacht und hatte eig einen guten Entwurf. Problem hierbei ist, dass ich nicht wusste, dass man nicht einen Funktionspointer auf eine member Funktion haben kann ... zumindest nicht ohne das eig. Objekt. Hier wäre nochmal die Frage nach einem Code Beispiel an @mal einfach. Ich sehe nicht wie man in einer ISR die (nicht static) Methode der Klasse Servo aufrufen kann. Für Funktionspointer bräuchte man wie gesagt, das objekt selbst. Und wenn ich das Objekt auch registriere, brauche ich den Funktionspointer ja net mehr. Mein jetziger Entwurf, der zumindestens kompiliert (getestet in Echt habe ich ihn noch nicht) ist eine Mischung aus verschiedenen Vorschlägen. Einerseits die Aufteilung von Vincent H. mit einem zusätzlichen ServoManager. Der Unterschied hier ist nur, dass die Servos bereits existieren, d.h. die Servos müssen sich beim ServoManager registrieren. Aus diesem Grund habe ich den Vorschlag von Arduino Fanboy aufgenommen und Singelton daraus gemacht. Allerdings in einem anonymen Namespace, sodass die Klasse nur für die Servos sichtbar sein sollte. Außerdem habe ich den Timer nach dem ursprünglichem Entwurf als lokale Klasse gestaltet. Die callback Methode habe ich direkt in die ISR gelegt, um eine zusätzlichen aufruf zu ersparen.
1 | |
2 | #ifndef SERVO_HPP |
3 | #define SERVO_HPP |
4 | |
5 | #include "Array.hpp" |
6 | |
7 | class Servo; |
8 | |
9 | namespace { |
10 | |
11 | class ServoManager { |
12 | public: |
13 | static ServoManager& getInstance(); |
14 | void attach(Servo* servo); |
15 | |
16 | private: |
17 | ServoManager(); |
18 | ServoManager(const ServoManager&) = delete; |
19 | ServoManager& operator=(const ServoManager&) = delete; |
20 | |
21 | class Timer { |
22 | public: |
23 | static void attach(ServoManager* servomanager); |
24 | |
25 | private: |
26 | static void serviceRoutine() __asm__("__vector_5") __attribute__((__signal__, __used__, __externally_visible__)); |
27 | |
28 | static ServoManager* owner; |
29 | }; |
30 | |
31 | Array<Servo* , 2> servos; |
32 | }; |
33 | } |
34 | |
35 | class Servo { |
36 | public: |
37 | Servo(); |
38 | void update(); |
39 | }; |
40 | |
41 | #endif //SERVO_HPP |
1 | #include "Servo.hpp" |
2 | |
3 | #include <stdint.h> |
4 | #include <avr/interrupt.h> |
5 | #include <avr/io.h> |
6 | |
7 | ServoManager* ServoManager::Timer::owner = nullptr; |
8 | |
9 | Servo::Servo() { |
10 | ServoManager::getInstance().attach(this); |
11 | } |
12 | |
13 | void Servo::update() { |
14 | //doSomething |
15 | } |
16 | |
17 | ServoManager::ServoManager() { |
18 | //initializeTimer |
19 | Timer::attach(this); |
20 | sei(); |
21 | } |
22 | |
23 | ServoManager& ServoManager::getInstance() { |
24 | static ServoManager servoManager {}; |
25 | return servoManager; |
26 | } |
27 | |
28 | void ServoManager::attach(Servo* servo) { |
29 | static uint8_t i = 0; |
30 | servos[i] = servo; |
31 | ++i; |
32 | } |
33 | |
34 | void ServoManager::Timer::attach(ServoManager* servoManager) { |
35 | owner = servoManager; |
36 | } |
37 | |
38 | void ServoManager::Timer::serviceRoutine() { |
39 | if(!owner) return; |
40 | for(uint8_t i = 0; i < owner->servos.size(); ++i) { |
41 | owner->servos[i]->update(); |
42 | } |
43 | } |
Vorteile: - Die Servos sind nicht global sichtbar - Das ganze ist noch relativ übersichtlich - Es gibt nach wie vor nur einen Besitzer. Objektorientiert(er) durch den ServoManager - Die Timer Initalisierung erfolgt auch nur einmal Nachteile: - Statisches Array für die Servos. Grundsätzlich eig. kein Problem, da die Anzahl fix ist. Unschön finde ich aber, dass die Servos nacheinander hinzugefügt werden. Das bedeudet ich brauche zum Beispiel den static int, um zu wissen, wie viele Plätze schon gefüllt sind Spontan fällt mir hierzu ein: - Dynamisches Array -> Wäre natürlich optimal, aber auf dem avr möchte ich darauf wirklich gerne verzichten - Die Servos doch in ServoManager erzeugen. Wäre theoretisch möglich. Aktuell findet die Klasse Einsatz in einem Roboter. Struktur ist also in etwa (Roboter besitzt mehrere Beine besitzen mehrere Servos). Hier müssten also die Beine (die die Servos als Attribute haben) per servoManager.getServo(index) Zugriff auf die Servos bekommen. Da stellt sich natürlich die Frage wie macht man das. a) der ServoManager ist global -> dann hat man eig wieder nix gewonnen b) Der ServoManager wird in der Main oder im Roboter erzeugt und eine Referenz an die Beine durchgegeben. Inklusive einer Nummer, sodass die Beine einen Index haben, um an die Servos zu kommen. Wäre vermutlich sinnvoll Bin gespannt, ob jemand noch weitere Vorschläge hat, Verbesserungen, Kritik etc. Grüße Leon
:
Bearbeitet durch User
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.