Forum: Mikrocontroller und Digitale Elektronik Konzept Interrupts und Klassen


von Dustin Lehmann (Gast)


Lesenswert?

Hallo,
ich bin bei weitem kein Profi in der objektorientierten Programmierung, 
daher hoffe ich, dass mir jemand mit mehr Erfahrung auf die Sprünge 
helfen kann.
Szenario: Ich habe einen Sensor (Lidar), der über UART kontinuierlich 
einen Datenframe von 9 Byte, inkl. 2 Startbytes am Anfang sendet.
Dieser Sensor wird mehrfach verbaut und daher habe ich dafür eine Klasse 
angelegt. Jedem der Sensoren wird ein Hardware USART Port zugeordnet

Nun könnte ich, getriggert von einem Timer, kontinuierlich den USART 
Ringbuffer auf Länge testen und wenn diese größer als 9 Bytes ist, den 
Header suchen und das Payload umwandeln. Ich möchte aber keinen Timer 
dafür hergeben, sondern das ganze getriggert vom USART Interrupt 
ausführen, den ich sowieso benutze um die Daten aus dem USART FIFO in 
den Ringbuffer zu schreiben.

Mein Problem:

Ich kann im USART Interrupt Handler natürlich keine Memberfunktionen 
ausführen, also habe ich eine gemeinsame Funktion "miniLidarRead()" für 
alle Objekte der Klasse gemeinsam und ein Array mit Pointern auf alle 
instanziierten Objekte als globale Variable. Jeder der Hardware USART 
Interrupts führt diese Funktion aus und sendet die Nummer des Interrupts 
mit. Anhand dieser Nummer bestimme ich das Objekt innerhalb des 
Objekt-Arrays und kann dann die Funktionen auf Objektebene benutzen. Das 
funktioniert so auch alles.

Meine Frage: Geht das irgendwie eleganter? Das ganze produziert 
natürlich viel Overhead für relativ triviale Funktionen und die 
Verständlichkeit wird sicherlich auch nicht unterstützt. Vielleicht gibt 
es ja einen sehr viel besseren Weg, den ich noch nicht in Betracht 
gezogen habe.

Die Klasse:
1
class MiniLidar {
2
public:
3
  MiniLidar();
4
  void init(USART_TypeDef* USARTx, GPIO_TypeDef* txPort, uint32_t txPin,
5
      GPIO_TypeDef* rxPort, uint32_t rxPin);
6
7
  /* data */
8
  uint16_t getDistance();
9
  uint16_t getStrength();
10
private:
11
  USART_TypeDef* _USARTx; // Hardware USART
12
  Usart _serial; // aufgesetzte Klasse auf den Hardware USART
13
  uint8_t _frameArray[7]; // Hält die 7 bytes Payload
14
  uint16_t _distance; // Abstand
15
  uint16_t _strength; // Signalstärke
16
  uint8_t _objectNumber; // Nummer des instanziierten Lidar Objekts
17
  friend void miniLidarRead(uint8_t objectNumber); // Funktion die nach jedem USART Interrupt ausgeführt wird
18
  friend void convertData();
19
};
(Usart _serial ist lediglich eine aufgesetzte Klasse auf einen Hardware 
USART mir integriertem RingBuffer und einfacherem Interface)

Und hier der Source-Code:
1
uint8_t activeObjects = 0;
2
MiniLidar* activeLidar[5];
3
4
MiniLidar::MiniLidar() {
5
  _frameArray = {0x00,0x00,0x00,0x00,0x00,0x00,0x00};
6
  activeObjects ++;
7
  _objectNumber = activeObjects - 1; // weise dem Objekt die nächste Nummer zu
8
  activeLidar[_objectNumber] = this;// setze den Pointer von diesem Objekt in das Array
9
}
10
11
void MiniLidar::init(USART_TypeDef* USARTx, GPIO_TypeDef* txPort,
12
    uint32_t txPin, GPIO_TypeDef* rxPort, uint32_t rxPin) {
13
14
  /* Initialize the UART */
15
  _serial.config(USARTx, 115200);
16
  _serial.setRX(rxPort, rxPin);
17
  _serial.setTX(txPort, txPin);
18
19
  //_serial.addRxFunction(&miniLidarRead);
20
  _serial.begin();
21
}
22
23
void miniLidarRead(uint8_t objectNumber) {
24
25
  static uint8_t lastByte = 0x00;
26
  static uint8_t currentByte = 0x00;
27
  static uint8_t counter = 0;
28
  static bool inFrame = false;
29
30
  /* Header finden, dieser besteht aus 0x59 0x59 */
31
  currentByte = activeLidar[objectNumber]->_serial.readByte();
32
  if (currentByte == LIDAR_HEADER_BYTE && lastByte == LIDAR_HEADER_BYTE) {
33
    inFrame = true; // Header gefunden, also sind die nächsten 7 Bytes Payload
34
  } else {
35
    if (inFrame) { // Lies die payload bytes aus
36
      activeLidar[objectNumber]->_frameArray[counter] = currentByte;
37
38
      if (counter == LIDAR_FRAME_SIZE - 2) {
39
        counter = 0;
40
        inFrame = false;
41
        convertData(activeLidar[objectNumber]); // konvertiere das frame array in die Werte für Distanz und Stärke
42
      } else {
43
        counter++;
44
      }
45
    }
46
  }
47
  lastByte = currentByte;
48
}
49
50
void convertData(MiniLidar* activeLidar) {
51
  activeLidar->_distance = activeLidar->_frameArray[0]
52
      << 8 + activeLidar->_frameArray[1];
53
  activeLidar->_strength = activeLidar->_frameArray[3]
54
      << 8 + activeLidar->_frameArray[4];
55
}

(Syntax-Fehler können noch vorhanden sein, ist eben hingeschrieben, es 
geht mir eher um das Konzept)

Ich hoffe ich konnte umreißen was mein Problem ist, falls nicht kann ich 
nochmal versuchen es etwas klarer darzustellen.

Vielen Dank!

Gruß

von Feldstecher (Gast)


Lesenswert?

Dustin Lehmann schrieb:
> Jeder der Hardware USART
> Interrupts führt diese Funktion aus und sendet die Nummer des Interrupts
> mit. Anhand dieser Nummer bestimme ich das Objekt innerhalb des
> Objekt-Arrays [...]
Mir ist nicht ganz klar, wie du die Objektadressen aus der 
Interruptroutine übergibst, bzw. wie die Interruptroutine aussieht. Die 
Liste hört sich nicht so gut an, das deutet auf eine (aufwendige) Suche 
durch die Liste hin. Wenn du jeder ISR bei der Initialisierung die 
Objektadresse zuweisen kannst, dann würde sich die Liste ja erübrigen.
Deinem Lidar-Ojbekt kannst du noch eine Variable "LetzterInListe" geben, 
welche am Ende von "miniLidarRead()" abgefragt wird und ggf. die 
Verarbeitung aufruft.

von Dustin L. (d_l)


Lesenswert?

So, jetzt eingeloggt.

Die Objektnummern sind momentan hartkodiert, an diesem Punkt hab ich 
Stop gemacht und mich dazu entschieden nachzufragen wie man das 
konzeptuell am Besten löst. Ich könnte natürlich alles hartkodieren in 
meinem Projekt, dann wär das kein Problem, ich würds nur ganz gern 
modular gestalten und Funktionen in anderen Projekten wiederbenutzen. An 
die ISR kann ich ja leider nichts übergeben, ist ja ein void ISR(void).

Die Frage ist ja quasi:

Wie kann ich in einer ISR am Besten die Member eines Objektes 
manipulieren, wenn das ganze reusable in einer Bibliothek geschrieben 
werden soll?

von Jack (Gast)


Lesenswert?

Ja, OOP und Hardware-Verhalten passen nicht gut zusammen.

Was du brauchst ist ein Dispatching vom Interrupt auf die zuständige 
UART-Klasse. Das sollte man im Interrupt-Handler machen. Alles was der 
Interrupt-Handler machen sollte ist anhand des Interrupts die zuständige 
Instanz suchen und eine Behandlungs-Methode aufrufen. Alles andere, wie 
das Auslesen, die State-Machine, usw. gehören in die Klasse.

D.h.:

* Fast alles was du in miniLidarRead() hast gehört in eine (normale) 
Methode.

* Für den Schönheitspreis:

- Der Interrupt-Handler, der das Dispatching auf eine Instanz macht, 
wird eine static Methode der Klasse.
- Die Liste der Instanzen wird ein static Member der Klasse, keine 
globale Variable.
- Eine static Methode initialisiert die UARTs, erzeugt die Instanzen, 
füllt die Liste mit den Instanzen, initialisiert die Interrupts, usw.

von Jack (Gast)


Lesenswert?

Dustin L. schrieb:
> reusable in einer Bibliothek geschrieben
> werden soll?

Mist, da schaut man eine Minute nicht hin und schon werden Anforderungen 
nachgeschoben. Dieses stückchenweise Verraten was du wirklich willst ist 
scheiße.

von Dustin L. (d_l)


Lesenswert?

Entschuldige bitte, ich dachte ich hätte das in meinen Eingangspost 
geschrieben, war aber nicht so. Ich habe das momentan so:
Mein UART Interrupt Handler sieht wie folgt aus:
1
extern "C" void USART1_IRQHandler() {
2
3
  if (USART_GetITStatus(USART1, USART_IT_RXNE) == SET) {
4
    while (USART_GetITStatus(USART1, USART_IT_RXNE) == SET) {
5
      uint8_t inByte = USART_ReceiveData(USART1);
6
      usart1->_rxData.push(inByte);
7
      USART_ClearITPendingBit(USART1, USART_IT_RXNE);
8
    }
9
    usart1RxFun();
10
    return;
11
  }
12
13
  if (USART_GetITStatus(USART1, USART_IT_TXE) == SET) { // check TX interrupt
14
    if (!usart1->_txData.isEmpty()) {
15
      USART_SendData(USART1, usart1->_txData.get());
16
    }
17
    USART_ClearITPendingBit(USART1, USART_IT_TXE);
18
    if (usart1->_txData.isEmpty()) {
19
      USART_ITConfig(USART1, USART_IT_TXE, DISABLE); // disable transmit interrupt
20
    }
21
  }
22
}

usart1RxFun() ist ein Pointer auf eine Funktion vom Typ
1
typedef void (*isrFun)(void)
. Diese sind globale Variablen in meiner USART-Klasse:
1
isrFun usart1RxFun = &NOP;
2
isrFun usart2RxFun = &NOP;
3
isrFun usart3RxFun = &NOP;
4
isrFun usart4RxFun = &NOP;
5
isrFun usart5RxFun = &NOP;
6
isrFun usart6RxFun = &NOP;
Dieser Pointer kann vom jeweiligen Objekt, abhängig vom angehängten 
Hardware-USART verändert werden.

Für meine Lidar Objekte ist das ein Pointer auf die miniLidarRead() 
Funktion. Die Übergabe der Objektnummer ist nicht komplett implementiert 
gerade, ist aber machbar. Meine Frage ist ja eher ob es ein besseres 
Konzept gibt, das in OOP umzusetzen, oder ob ich mich damit abfinden 
muss, dass OOP, reusable und Hardware nicht wirklich zusammenpassen.

Gruß

von Dr. Sommer (Gast)


Lesenswert?

Ohne jetzt dein Problem detailliert auseinander genommen zu haben, ich 
mach das immer so in der Art (ob das jetzt ein Array ist oder nicht ist 
egal):
1
MiniLidar lidar1 (USART1);
2
MiniLidar lidar2 (USART2);
3
4
extern "C" USART1_IRQHandler () {
5
  lidar1.onUsartInterrupt ();
6
}
7
8
extern "C" USART2_IRQHandler () {
9
  lidar2.onUsartInterrupt ();
10
}
So kann man in der onUsartInterrupt-Funktion ganz normal auf alle 
Member-Variablen zugreifen. Der Funktions-Overhead wird ggf. 
wegoptimiert. Die Zuordnung USART-Modul zu Klasse ist ja sowieso fix, 
daher macht es nix in der ISR direkt das richtige Objekt zu nehmen. Ich 
packe die ISR's und das Anlegen der Instanzen immer zusammen in die 
main.cpp, da hat man dann einen Überblick was wie zusammenhängt. Alle 
anderen Dateien sind unabhängig von konkreten Zuordnungen.

Dustin Lehmann schrieb:
> void miniLidarRead(uint8_t objectNumber) {
>
>   static uint8_t lastByte = 0x00;
>   static uint8_t currentByte = 0x00;
>   static uint8_t counter = 0;
Wenn man schon OOP macht, würde ich funktionslokale "static"-Variablen 
vermeiden - versteckter Zustand.

Dustin Lehmann schrieb:
> USART_TypeDef* _USARTx; // Hardware USART
Bezeichner die mit Unterstrich +  Großbuchstabe anfangen, oder mit 2 
Unterstrichen, sind in C und C++ der Standard-Bibliothek vorenthalten. 
Ich würde daher auf Bezeichner die mit Unterstrich anfangen ganz 
verzichten.

Dustin Lehmann schrieb:
> Nun könnte ich, getriggert von einem Timer, kontinuierlich den USART
> Ringbuffer auf Länge testen und wenn diese größer als 9 Bytes ist, den
> Header suchen und das Payload umwandeln
Das geht eleganter: Die USART vom STM32 kann einen IDLE-Interrupt 
auslösen, wenn gerade nichts übertragen wird. In diesem Interrupt kannst 
du das DMA anweisen, 9 Bytes zu empfangen und in einen Puffer zu packen. 
Somit hast du, wenn der DMA-Interrupt kommt, genau 1 Paket im Puffer und 
musst keinen Anfang suchen. Das setzt natürlich vorraus, dass zwischen 
den Paketen eine Pause ist. Der Ringpuffer ist dann auch hinfällig.

von Torsten R. (Firma: Torrox.de) (torstenrobitzki)


Lesenswert?

Du brauchst doch überhaupt keine Laufzeitpolymorphie. Es steht doch 
bereits zur Compilerzeit fest, welche Uarts Du für welchen Zweck 
verwendest. Wenn Du ein Template mit statischen membern verwendest, 
kommst Du der Sache wahrscheinlich schnell viel näher:
1
template < USART_TypeDef* Uart, GPIO_TypeDef* TxPort, uint32_t TxPin, GPIO_TypeDef* TxPort, uint32_t TxPin >
2
class MiniLidar
3
{
4
public:
5
    static void init();
6
7
    static uint16_t getDistance();
8
    static uint16_t getStrength();
9
10
    static void irq_handler();
11
...
12
};
13
14
using mini_lidar0 = MiniLidar< USART0, PortA, 32, PortB, 44 >;
15
16
extern "C" void USART1_IRQHandler() 
17
{
18
    mini_lidar0::irq_handler();
19
}

von Dr. Sommer (Gast)


Lesenswert?

Torsten R. schrieb:
> template < USART_TypeDef* Uart, GPIO_TypeDef* TxPort, uint32_t TxPin,
> GPIO_TypeDef* TxPort, uint32_t TxPin >

Man kann leider nicht USART1 und Co per Template übergeben, weil dessen 
Definition einen reinterpret cast enthält, welcher somit keine constant 
Expression ist. Man könnte höchstens einen uintptr_t übergeben und in 
der Klasse casten. Oder einen Index übergeben und die Pointer in ein 
globales Array packen und darüber auflösen.

von Pandur S. (jetztnicht)


Lesenswert?

Du solltest dich von akademischen Fürzen trennen und dich dem Problem 
widmen.
Da das Projekt eh nie auf einen PC portiert werden wird, brauchst du die 
Portabilitaet nicht.
Dynamische Objekte bringen keinen Vorteil hier, also lass sie weg.
Bei einem normalen Losungsansatz ist das Problem von selbst weg.

von Torsten R. (Firma: Torrox.de) (torstenrobitzki)


Lesenswert?

Auf der letzten Embo++ hat der Emil Fresk einen Vortrag gehalten. Dabei 
hat er ein Task-System vorgestellt und am Ende aufgelöst, dass dieses 
Tasksystem komplett in der Hardware eines Cortex-M läuft und "fast" 
keine Unterstützung zur Laufzeit braucht: 
https://github.com/korken89/crect

Das klingt sehr spannend, finde ich :-)

von Vincent H. (vinci)


Lesenswert?

Dustin L. schrieb:
> Die Frage ist ja quasi:
>
> Wie kann ich in einer ISR am Besten die Member eines Objektes
> manipulieren, wenn das ganze reusable in einer Bibliothek geschrieben
> werden soll?

Mit einem Callback der in etwa die Funktionalität von std::function 
abdeckt. Durch dessen interne type erasure lässt sich dort alles 
reinstopfn was callable is...

Eine UART hat in einer Lidar Klasse meiner Meinung nach überhaupt nichts 
verloren, schon gar nicht dessen Interrupt.

von Torsten R. (Firma: Torrox.de) (torstenrobitzki)


Lesenswert?

Vincent H. schrieb:

> Mit einem Callback der in etwa die Funktionalität von std::function
> abdeckt. Durch dessen interne type erasure lässt sich dort alles
> reinstopfn was callable is...

Warum? Ich würde mal behaupten, in 99% der Fälle ist bereits zur 
Compile-Zeit bekannt, welche Funktion auf welchem Objekt aus dem ISR 
heraus zu rufen ist.

von Vincent H. (vinci)


Lesenswert?

Torsten R. schrieb:
> Vincent H. schrieb:
>
>> Mit einem Callback der in etwa die Funktionalität von std::function
>> abdeckt. Durch dessen interne type erasure lässt sich dort alles
>> reinstopfn was callable is...
>
> Warum? Ich würde mal behaupten, in 99% der Fälle ist bereits zur
> Compile-Zeit bekannt, welche Funktion auf welchem Objekt aus dem ISR
> heraus zu rufen ist.

Weil da steht es soll eine Bibkiothek werden und die sollte imho mit 
allem funktionieren was callable is.

std::function selbst erzeugt bei Übergabe eines Lambdas mit this capture 
auf einem Cortex M4 ~24 Instruktionen bei -O2.

Will und braucht man wirklich das Minimum von 2 Instruktionen könnte man 
den Callback natürlich auch als Pointer übergeben. Das wär dann wohl mal 
ein Fall wo sich Template Class Type Deduction lohnt, damit man den 
Pointer Typ nicht explicit übergeben muss.

Und ja, für all diese Überlegungen müsste man Lidar und UART trennen.

von W.S. (Gast)


Lesenswert?

Torsten R. schrieb:
> Das klingt sehr spannend, finde ich :-)

O ja, Zitat:
"crect (pronounced correct) is a C++ library for generating a scheduler 
(at compile time) for Cortex-M series MCUs, which guarantees dead-lock 
free and data-race free execution. It utilizes the Nested Vector 
Interrupt Controller (NVIC) in Cortex-M processors to implement a Stack 
Resource Policy (SRP) based scheduler. Thanks to the compile time 
creation of the scheduler, the resource requirements at run-time are 
minimal with:..."

Also thanks to the compile time_scheduler... Schönen Dank auch. Nee, das 
klingt fast so grandios wie Dunkels Protothreads...

W.S.

von my2ct (Gast)


Lesenswert?

Dustin Lehmann schrieb:
> Nun könnte ich, getriggert von einem Timer, kontinuierlich den USART
> Ringbuffer auf Länge testen und wenn diese größer als 9 Bytes ist, den
> Header suchen und das Payload umwandeln. Ich möchte aber keinen Timer
> dafür hergeben, ...

Nicht für jede Aufgabe, die innerhalb einer TimerISR ausgeführt wird, 
brauchst du einen eigenen Timer. Da können durchaus mehrere Dinge 
abgearbeitet werden.

von Dr. Sommer (Gast)


Lesenswert?

W.S. schrieb:
> Also thanks to the compile time_scheduler... Schönen Dank auch. Nee, das
> klingt fast so grandios wie Dunkels Protothreads...

Also gar nicht ausprobiert oder mit irgendwas verglichen, aber erstmal 
lästern? :)

von Dustin L. (d_l)


Lesenswert?

Hallo,
danke für die vielen Antworten, ich habe jetzt auf Anregung hin den 
USART aus der Klasse herausgenommen und fülle jetzt per DMA und 
Idle-Line-Detection ein Array innerhalb der Klasse.

Generell habe ich mal versucht eine Memberfunktion über std::function 
als Pointer zu übergeben um innerhalb einer Bibliothek die 
Memberfunktion aufrufen zu können. Das funktioniert sogar, ist aber 
leider 80kB groß im Flash, von daher nicht so praktikabel. Geht das auch 
kleiner?

Weiterhin habe ich mal die Klasse "FunctionPointer" aus dem mBed-OS 
eingebunden, damit funktioniert es ebenfalls und belegt kaum Platz im 
Flash, der Nachteil ist natürlich dass es keine wirklichen Zeiger auf 
Funktionen sind, sondern Objekte und ich habe bisher noch nicht bestimmt 
wie viel Overhead das erzeugt und ob dadurch sehr viel Zeit verloren 
geht.
Wie bestimmt man so etwas am Besten? ASM-Code ansehen? Einen Timer 
laufen lassen und schauen wieviel Zeit vergangen ist?

Gruß

von Peter D. (peda)


Lesenswert?

Dustin Lehmann schrieb:
> Szenario: Ich habe einen Sensor (Lidar), der über UART kontinuierlich
> einen Datenframe von 9 Byte, inkl. 2 Startbytes am Anfang sendet.

Gibt es zu dem Sensor eine Bezeichnung, Datenblatt, 
Protokollbeschreibung?
Sendet der wirklich kontinuierlich oder ist zwischen den Paketen eine 
Pause?

von Dustin L. (d_l)


Lesenswert?

Hallo,
es ist dieser Sensor:
http://benewake.com/en/tfmini.html

Der Sensor sendet mit 100hz ein Datenpaket von 9 Bytes bei 115200 bit/s. 
Es ist also zwischendurch genug Zeit für Verarbeitung.

Ich habe jetzt einen DMA eingerichtet, der nach jedem IDLE auf dem UART 
getriggert wird, und dann 9 Bytes in ein Array schreibt. Beim DMA TC 
Interrupt wird die Berechnung der physikalischen Werte ausgeführt.

von Torsten R. (Firma: Torrox.de) (torstenrobitzki)


Lesenswert?

Dustin L. schrieb:
> Generell habe ich mal versucht eine Memberfunktion über std::function
> als Pointer zu übergeben um innerhalb einer Bibliothek die
> Memberfunktion aufrufen zu können. Das funktioniert sogar, ist aber
> leider 80kB groß im Flash, von daher nicht so praktikabel. Geht das auch
> kleiner?

Das wird wahrscheinlich daran liegen, dass std::function<> den heap 
verwendet. Wenn Du einfach einen function pointer verwendest, dann kann 
das nicht passieren. Ich würde aber einfach auf jede Form von runtime 
dispatching verzichten.

von Karl der erste von oben (Gast)


Lesenswert?

Dustin L. schrieb:
> der Nachteil ist natürlich dass es keine wirklichen Zeiger auf
> Funktionen sind, sondern Objekte und ich habe bisher noch nicht bestimmt
> wie viel Overhead das erzeugt und ob dadurch sehr viel Zeit verloren
> geht.
> Wie bestimmt man so etwas am Besten? ASM-Code ansehen? Einen Timer
> laufen lassen und schauen wieviel Zeit vergangen ist?

Wenn du das nicht mit  mehreren hundert kHz aufrufst, vergiss den 
Overhead.

Weiter eine  allgemeine Empfehlung: Wann immer du merkst, dass dich die 
Struktur deines Programms behindert, mach einen Prototypen der sich 
einige Dinge einfach festlegt. Werde konkret. Nur so kannst du die 
Erkenntnisse sammeln, für dir später erlauben die Struktur so anzupassen 
wie es für dich passt.
Keep it simple.

von Vincent H. (vinci)


Lesenswert?

Dustin L. schrieb:
> Generell habe ich mal versucht eine Memberfunktion über std::function
> als Pointer zu übergeben um innerhalb einer Bibliothek die
> Memberfunktion aufrufen zu können. Das funktioniert sogar, ist aber
> leider 80kB groß im Flash, von daher nicht so praktikabel. Geht das auch
> kleiner?

Torsten R. schrieb:
> Das wird wahrscheinlich daran liegen, dass std::function<> den heap
> verwendet. Wenn Du einfach einen function pointer verwendest, dann kann
> das nicht passieren. Ich würde aber einfach auf jede Form von runtime
> dispatching verzichten.

std::function verwendet eine small buffer optimization (im libstdc++ 
Fall glaub ich 8 Bytes). Selbst wenn man "vanilla" std::function 
verwendet läuft man bei Interrupts quasi nie Gefahr auf Heap zugreifen 
zu müssen. Trotzdem würde ich eher zur Nutzung von "inplace_function" 
raten, einem Proposal zu einer "Heap"-freien std::function, die nie 
allokieren darf. Da gibts wohl die ein oder andere Implementierung auf 
github.


Die 80kB klingen eher nach <iostream>...
Ich hab grad eben ausprobiert wie viel Platz vanilla std::function für 
den Aufruf eines "Member Interrupts" braucht, sprich einer Member 
Funktion die maximal 1x Parameter schluckt.

Fazit:
-Og Programm wurde um 96B größer
-Os Programm wurde um 24B größer

Und das ist jetzt der Vergleich OHNE Interrupt <-> mit Interrupt wohl 
gemerkt, nicht verglichen zu einem Raw Pointer oder ähnlichem...

von Dustin L. (d_l)


Lesenswert?

Echt? Wieso ist das bei dir soviel kleiner? Ich habe folgendes Beispiel 
ausprobiert:
1
#include "stm32f4xx.h"
2
#include "F4_Drivers.hpp"
3
#include <functional>
4
5
class testClass {
6
public:
7
  void testFunction() {
8
    int i = 4;
9
    i++;
10
  }
11
  ;
12
};
13
14
std::function<void(void)> testIntFun;
15
16
int main(void) {
17
18
  SystemInit();
19
  initSysTick();
20
21
/* Interrupt initialisieren, sind nur die StdPeriph Funktionen */
22
  attachPin(PC1, INPUT);
23
  addInterrupt(PC1, FALLING, &NOP);
24
25
/* Test Objekt anlegen und Memberfunktion binden */
26
  testClass testObject;
27
  testIntFun = std::bind(&testClass::testFunction,&testObject);
28
29
  while (1) {
30
  }
31
}
32
33
extern "C" void EXTI1_IRQHandler(void) {
34
  if (EXTI_GetITStatus(EXTI_Line1)) {
35
    testIntFun();
36
    EXTI_ClearITPendingBit(EXTI_Line1);
37
  }
38
}

Mit dem Aufruf von
1
 testIntFun();
 im Interrupt-Handler wird mein Code etwa 70kB größer, lasse ich den 
Aufruf weg bleibt das Programm klein. Mache ich was falsch? Wenn ich 
zuhause bin, kann ich mal ein kompilierbares Minimalbeispiel erstellen, 
meine Bibliotheksfunktionen zum Interruptpin habe ich gerade nicht hier.

Gruß

von Vincent H. (vinci)


Lesenswert?

Hm, keine Ahnung. ;)
Das reine Anlegen der Klasse und der bind erzeugt auch kaum Code...

Kleiner Tip am Rande, std::bind lässt sich quasi immer durch Lambdas 
substituieren. Das hat bezüglich Code-Größe und Geschwindigkeit nur 
Vorteile.
1
testIntFun = std::bind(&testClass::testFunction,&testObject);
2
testIntFun = [&testObject] { testObject.testFunction(); };

Soweit ich weiß gibts eigentlich keinen Use Case mehr, der nach 
std::bind verlangen würde.

von Dustin L. (d_l)


Lesenswert?

Das habe ich auch versucht gestern, hat die selbe Codegröße erzeugt.

von Dustin L. (d_l)


Lesenswert?

Hier habe ich mal ein kompilierbares Beispiel:
1
#include "stm32f4xx.h"
2
#include <functional>
3
4
class testClass {
5
public:
6
  void testFunction() {
7
    int i = 4;
8
    i++;
9
  }
10
  ;
11
};
12
13
std::function<void(void)> testIntFun;
14
15
int main(void) {
16
17
  SystemInit();
18
19
  /* first enable the gpio clock */
20
  RCC_AHB1PeriphClockCmd(RCC_AHB1Periph_GPIOC, ENABLE);
21
22
  /* now fill the gpio typestruct */
23
  GPIO_InitTypeDef gpioInit;
24
  GPIO_StructInit(&gpioInit);
25
  gpioInit.GPIO_Mode = GPIO_Mode_IN;
26
  gpioInit.GPIO_Pin = GPIO_Pin_1;
27
  gpioInit.GPIO_Speed = GPIO_Speed_50MHz;
28
  GPIO_Init(GPIOC, &gpioInit);
29
30
  /* activate the interrupt */
31
  RCC_APB2PeriphClockCmd(RCC_APB2Periph_SYSCFG, ENABLE);
32
  SYSCFG_EXTILineConfig(EXTI_PortSourceGPIOC, EXTI_PinSource1); // set the Source of the Line to the chosen pin
33
34
  // Initialize the EXTI-Struct
35
  EXTI_InitTypeDef extiInitStruct;
36
  extiInitStruct.EXTI_Line = (uint32_t) GPIO_Pin_1;
37
  extiInitStruct.EXTI_LineCmd = ENABLE;
38
  extiInitStruct.EXTI_Mode = EXTI_Mode_Interrupt;
39
  extiInitStruct.EXTI_Trigger = EXTI_Trigger_Falling;
40
  EXTI_Init(&extiInitStruct);
41
42
  NVIC_EnableIRQ(EXTI1_IRQn);
43
  NVIC_SetPriority(EXTI1_IRQn, 0);
44
45
  testClass testObject;
46
  testIntFun = [&testObject]{testObject.testFunction();};
47
48
  while (1) {
49
  }
50
}
51
52
extern "C" void EXTI1_IRQHandler(void) {
53
  if (EXTI_GetITStatus(EXTI_Line1)) {
54
    testIntFun();
55
    EXTI_ClearITPendingBit(EXTI_Line1);
56
  }
57
}

Ohne den Aufruf von testIntFun():
1
text    data    bss     dec
2
3932    1092    1108    6132
Mit dem Aufruf von testIntFun():
1
   text     data      bss      dec
2
  73124     2536     1384    77044
Ich benutze Eclipse CDT mit AC6 Plugin für STM32, kompiliert mit GCC.
Kann ich irgendwas an den Einstellungen ändern damit das nicht so 
exorbitant groß ist? Oder sollte ich das Konstrukt lieber vergessen?

Gruß

von Torsten R. (Firma: Torrox.de) (torstenrobitzki)


Lesenswert?

Dustin L. schrieb:
> Oder sollte ich das Konstrukt lieber vergessen?

Vergiss std::function<> einfach, wenn Du keinen heap verwendest. Selbst 
wenn es small object optimizations gibt: es gibt Dir keiner eine 
Garantie, dass dein Compiler so eine Optimierte Library mitbringt und 
auch keine Garantie, wie groß small ist.

Wenn es nun unbedingt ein Binding zu Laufzeit sein muss, dann kannst Du 
Dir eine Art std::function<> selbst bauen (so in etwa):
1
#include <iostream>
2
#include <ostream>
3
#include <cassert>
4
5
template < class T, void (T::*Fct)() >
6
void invoke( void* obj )
7
{
8
    assert( obj );
9
10
    T& ref  = *static_cast< T* >( obj );
11
    (ref.*Fct)();
12
}
13
14
class latetest_binding
15
{
16
public:
17
    latetest_binding()
18
        : obj_( nullptr )
19
        , type_( nullptr )
20
    {
21
    }
22
23
    latetest_binding( void* obj, void (*type)( void* ) )
24
        : obj_( obj )
25
        , type_( type )
26
    {
27
        assert( obj );
28
        assert( type );
29
    }
30
31
    void operator()()
32
    {
33
        assert( obj_ );
34
        assert( type_ );
35
36
        type_( obj_ );
37
    }
38
39
private:
40
    void* obj_;
41
    void (*type_)( void* );
42
};
43
44
template < class T, void (T::*Fct)() >
45
latetest_binding bind( T& obj )
46
{
47
    return latetest_binding( &obj, &invoke< T, Fct > );
48
}
49
50
struct foo {
51
52
    void irq()
53
    {
54
        std::cout << "Hello from Hell" << std::endl;
55
    }
56
} foo1;
57
58
latetest_binding binding;
59
60
int main()
61
{
62
    binding = bind< foo, &foo::irq >( foo1 );
63
    binding();
64
}

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.