Forum: Mikrocontroller und Digitale Elektronik Interrupts in C++


von Leon D. (leon_d)


Lesenswert?

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!

von mal einfach (Gast)


Lesenswert?

???
Warum nicht eine Callback-Funktion aus jeder Instanz heraus registrieren 
und dann bei Interrupt abarbeiten?

von Dergute W. (derguteweka)


Lesenswert?

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

von Leon D. (leon_d)


Lesenswert?

Hättest du ein Code Beispiel für deine Idee? Ich verstehe nicht so 
wirklich wie du dir die Umsetzung vorstellst.

von Leon D. (leon_d)


Lesenswert?

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.

von mal einfach (Gast)


Lesenswert?

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, ...

von Einer K. (Gast)


Lesenswert?

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()

von Leon D. (leon_d)


Lesenswert?

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

von Leon D. (leon_d)


Lesenswert?

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.

von Einer K. (Gast)


Lesenswert?

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.

von Vincent H. (vinci)


Lesenswert?

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
};

von mal einfach (Gast)


Lesenswert?

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.

von S. R. (svenska)


Lesenswert?

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.

von Leon D. (leon_d)


Lesenswert?

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
Noch kein Account? Hier anmelden.