Wie macht man eine ISR einer Klasse bekannt? bzw. anders herum

Hallo,

ich schreibe an einer SPI Lib mit FiFo. Grundlegend funktioniert das schon. Jetzt stehe ich vor dem Problem das die ISR irgendwie mit der Klasse verheiratet werden muss. Die ISR muss eine Klassenmethode aufrufen, den eigentlichen ISR Code. Darin wird Instanz abhängig der fifo und CS-Pin etc. verwendet.

Macht das überhaupt Sinn? Weil die Kapselung muss erhalten bleiben. Dabei muss sicherlich mit static gearbeitet werden, was ja die Kapselung negativ beeinflusst. Ich habe derzeit keine Idee wie man das am Besten angeht.

Ich würde die ISR unabhängig halten und nur die außerhalb angelegten Variable(n)/Struct an die Klasse übergeben.

Gruß Tommy

Edit: Evtl. könnte dieser Artikel für Dich interessant sein.

Für den ADC habe ich das durchexerziert.
Da ist die ISR eine Klassen Methode.
Schön ist das nicht…
Aber funktioniert.
(evtl. kannst du dir da was abschauen)

Eigentlich ist das Singleton Design Pattern an der Stelle angesagt, oder eine vergleichbare Exklusion.
Das ist da leider noch nicht implementiert.

CombieAdc.zip (11.3 KB)

Hallo,

Danke Euch. Ich guck mir das in Ruhe an. Es wird seine Zeit benötigen.

IMO kann es nur eine Instanz der Klasse geben, die mit dem Interrupt arbeitet. Das ginge dann mit einem Singleton (siehe Serial) und direktem Aufruf einer Methode in der ISR.

Serial ist kein Singleton (im strengen Sinn).
Man kann durchaus mehrere HardwareSerial Instanzen bilden.
Für den Mega wird das gemacht.
Man kann auch noch mehr davon bilden, die tuns nur nicht.

Ist halt etwas unglücklich, dass C++ dafür kein elegantes Schema anbietet.
Sehr µC und Kompiler spezifisch, das Ganze.

Zumindest ist 1 Serial vordefiniert, das sehe ich als ein Singleton. Von einer ISR kann Serial benutzt werden, entsprechend für jede explizite Instanz einer beliebigen Klasse.

Wenn man die Klasse, die ISR und eine Instanz in derselben Datei hat, können diese zusammenarbeiten. Wie der Interrupt initialisiert wird, das ist eine andere Geschichte, und IMO eine ganz wacklige in diesem Thread.

Hallo,

ich habe derzeit nicht in .h und .cpp getrennt. Deswegen muss ich jetzt fragen wie der Syntax lautet wenn ich die "ISR" Funktion gleichzeitig definiere und deklariere.

das wäre die reine Deklaration:

static void interruptServiceRoutine() __asm__("SPI_STC_vect") __attribute__((__signal__, __used__, __externally_visible__));

Ich bräuchte aber sowas

static void interruptServiceRoutine() __asm__("SPI_STC_vect") __attribute__((__signal__, __used__, __externally_visible__))
{
  // ... Code
}

Wo gehören die Optionen hin?
Oder muss man dafür zwingend in .h. und .cpp auftrennen?

Müssen muß man nicht, zumindest nicht wenn der Compiler oder Linker keine Fehler meldet.

Wo gehören die Optionen hin?

In die Deklaration.
Also in die *.h Datei.

Die Definition der ISR wird wohl in einer *.cpp landen müssen.

Andere Möglichkeiten sind mir nicht bekannt.

Ahmm:
Dein asm("SPI_STC_vect") wird nicht funktionieren, vermute ich mal....
Beim UNO wäre asm("__vector_18") richtig.

Alternativ:
Du bastelst den Vector so umständlich wie ich zusammen.

Hallo,

Müssen muß man nicht, zumindest nicht wenn der Compiler oder Linker keine Fehler meldet.

Er meckert aber mit "cannot be overloaded", darum frage ich ja. Hast du eine Lösung dafür wie der Syntax für Header only Lib lauten muss?

Andere Möglichkeiten sind mir nicht bekannt.

Dann werde ich das doch erstmal auftrennen müssen. Habe es diesmal extra Header only begonnen um später einfacher ein Template daraus basteln zu können. Das war der Plan zum Endausbau. Dann eben ohne Template Klasse.

Ahmm:
Dein asm("SPI_STC_vect") wird nicht funktionieren, vermute ich mal....
Beim UNO wäre asm("__vector_18") richtig.

Alternativ:
Du bastelst den Vector so umständlich wie ich zusammen.

Ich wollte das "umständlich" vermeiden und die vorhandenen #defines der iomxxxx.h verwenden. Beim Mega wäre es Vector 24. Die reine Deklaration meckert er nicht an bzw. noch nicht. Kann sein das er die gleich wegwirft, weil nicht verwendet. Das werde ich sehen wenn ich alles aufgetrennt habe.
Ich hatte auch im Netz gesucht. Ein paar Bsp. gibt es nur entweder kompilieren die erst gar nicht oder sind unklar geschrieben. Eines was kompiliert ist von Nick Gammon Gammon Forum : Electronics : Microprocessors : Calling an ISR from a class , nur nutzt er jedoch attachInterrupt, was ich nicht benötige, ich kann das jedenfalls nicht für mich adaptieren. Alle Bsp. zusammenwerfen geht auch nicht, also muss ich Ruhe bewahren und mich erstmal auf eines konzentrieren. Das wäre derzeit das mit dem asm Optionen was im Tommy Link und was combie verwendet. Funktioniert ja offenbar ... bis das Header only Problem auftauchte.

Ich trenne .h und .cpp auf ... außer DrDiettrich hat eine Lösung?

Doc_Arduino:
Er meckert aber mit "cannot be overloaded", darum frage ich ja. Hast du eine Lösung dafür wie der Syntax für Header only Lib lauten muss?

Dann gibt es irgendwo schon eine andere Deklaration. Wofür soll die gut sein?

DrDiettrich:
Dann gibt es irgendwo schon eine andere Deklaration. Wofür soll die gut sein?

Das weiß ich selber. Ich schlussfolgere daraus das du meine Frage nicht verstanden hast.
Die Frage war, wie lautet der Syntax zur Definition der Funktion inkl. der asm Optionen?
Möchtest du mir das zeigen oder nicht?

Ich habe keine Ahnung, was die Optionen bedeuten, deshalb kann ich dazu nichts sagen. Wenn Du auch nichts dazu sagen kannst, hast Du ihre Bedeutung und Verwendung wohl auch nicht verstanden. Wieso geht das, was Du vor hast, nicht auch mit Arduino Mitteln?

Wenn Du ohne Begründung darauf bestehst, Definition und Deklaration zu trennen, dann ist das erst mal Dein geheimes Problem.

Wenn Du ohne Begründung darauf bestehst, Definition und Deklaration zu trennen, dann ist das erst mal Dein geheimes Problem.

Ich sehe das als einen Sachzwang.

Wie man es auch anstellt…
Die ISR muss in eine *.cpp Datei.

Und wenn sie eine Methode sein soll, also im Kontext der Klasse laufen soll, muss man eben Deklaration und Definition trennen.

Der Alternative Weg, wie es z.B. Serial macht, ist auch nicht wirklich besser.


Man könnte die ISR auch in die *.h packen.
Das dürfte dann allerdings die dümmste Idee sein.
Weil *.h sollte man schon in mehreren Übersetzungseinheiten verwenden dürfen.

Hallo,

es gibt nur eine einzige SPI Interrupt Routine. Wenn ich mehrere SPI Devices als Objekte abbilden möchte, dann müssen sich diese Objekte alle diese eine ISR "teilen". Der Vergleich mit Serial passt hier nicht ganz, weil jede USART Einheit ihre eigene ISR hat. Da gibts kein durcheinander, deswegen müssen die USART-ISR nicht zwingend ein Teil der Klasse werden.

Hallo,

ich komme aktuell nicht weiter. Habs umgebaut, funktioniert derzeit mit heiß gestrickter Nadel. Problem ist, dass die ISR jetzt eigentlich Teil der Klasse sein müßte und scheinbar auch ist, aber die ISR kennt den Member csPin nicht? Deswegen musste ich erstmal mit dem zusätzlichen CSPIN tricksen damit ich am Oszi wieder etwas sehe. Warum ist der Member unbekannt?

Mit dem vielleicht noch benötigten Zeiger auf die Klasse komme ich auch nicht klar.

Vielleicht kann da jemand Licht ins Dunkel bringen?

PS: Das mit dem Vector Definenamen klappte dann doch nicht, habe combies Methode verwendet.

.ino

#include <util/atomic.h>
#include "DocSpi.h"
#include <CombiePin.h>
using namespace Combie::Pin;
OutputPin<53> SSpin;
OutputPin<52> CLKpin;
OutputPin<51> MOSIpin;

DocSpi spi(11);  // CS Pin, muss auch in .h Zeile 56 gesetzt sein

void setup(void)
{  
  SSpin.init();
  CLKpin.init();
  MOSIpin.init();
  SSpin.setHigh();
    
  spi.init();
  spi.setFrequency(16);
  spi.setMaster();   
}

void loop(void)
{
  // schreibt in den FiFo
  spi.transferByte('U');
  spi.transferByte(24);
  spi.transferByte(24);
  spi.transferByte('U');
  
  delay(1);
}

.h

#pragma once

#include <avr/io.h>
#include <avr/interrupt.h>
#include "AvrRegister.h"

#define ISR_STR(x) #x
#define ISR_HLP_STR(x) ISR_STR(x)

class DocSpi
{
  private:
    const uint8_t csPin; 
    //static DocSpi *owner; // = nullptr;
    static void interruptServiceRoutine() __asm__(ISR_HLP_STR(SPI_STC_vect)) __attribute__((__signal__, __used__, __externally_visible__));
  
  public:
  // Default Konstruktor
  DocSpi (const uint8_t);
        
  // Methoden
  void init (void);
  uint8_t getRegStatus (void);
  uint8_t getRegControl (void);
  uint8_t getRegData (void);
  void setMaster (void);
  void setSlave (void);
  void setFrequency (uint8_t const divider);
  void transferByte(uint8_t const data);  
};

DocSpi.zip (2.92 KB)

.cpp

#include <avr/io.h>
#include <avr/interrupt.h>
#include "Arduino.h"
#include "AvrRegister.h"
#include "DocSpi.h"


// Size of the circular rx/tx buffer, must be power of 2.
constexpr uint8_t BUFFERSIZE = 16;

// Size of buffer
constexpr uint8_t BUFFER_MASK = BUFFERSIZE-1;


//* module global variables *//
bool readyForNewTransfer = true;

template <typename T>
struct ring
{
    T fifo[BUFFERSIZE];
    T head = 0;
    T tail = 0;
    T lastError = 0;
};
ring <volatile uint8_t> buffer;

// konstanter Zeiger auf Variable, Registeradresse zuweisen
uint8_t *const ptrControl = reinterpret_cast<uint8_t*> (addr.SPI_SPCR);
uint8_t *const ptrStatus  = reinterpret_cast<uint8_t*> (addr.SPI_SPSR);
uint8_t *const ptrData    = reinterpret_cast<uint8_t*> (addr.SPI_SPDR);


// derzeitige Testhilfe in ISR
const uint8_t CSPIN = 11;

// ------ hier beginnt die Klasse -----------------------------------
       
// Konstruktor
DocSpi::DocSpi (const uint8_t p) : 
  // Initialisierungsliste
  csPin{p}
  //DocSpi::owner {nullptr}
  { }
        
// Methoden  
void DocSpi::init (void)
{    
  pinMode(csPin, OUTPUT);
               
  buffer.head = 0;
  buffer.tail = 0;
                      
  // Registerinhalte löschen
  *ptrStatus  = static_cast<uint8_t> (0);
  *ptrControl = static_cast<uint8_t> (0);
  *ptrData    = static_cast<uint8_t> (0);
          
  *ptrControl |= static_cast<uint8_t> (bitmask.SPI_SPE);   // SPI Enable
  
  // einmal lesen zum löschen
  while (SPSR & (1<<SPIF)) { ; }
}

// zum debuggen
uint8_t DocSpi::getRegStatus (void)
{
  return static_cast<uint8_t> (*ptrStatus);
}

// zum debuggen
uint8_t DocSpi::getRegControl (void)
{
  return static_cast<uint8_t> (*ptrControl);
}

// zum debuggen
uint8_t DocSpi::getRegData (void)
{
  return static_cast<uint8_t> (*ptrData);
}

void DocSpi::setMaster (void)
{
  *ptrControl |= static_cast<uint8_t> (bitmask.SPI_MSTR);
}

// derzeit nur definiert
void DocSpi::setSlave (void)
{
  *ptrControl &= ~(static_cast<uint8_t> (bitmask.SPI_MSTR));
}


void DocSpi::setFrequency (uint8_t const divider)
{        
  switch (divider)
  {
    case   2:   *ptrStatus  |= static_cast<uint8_t> (bitmask.SPI_SPI2X);
                break;
    
    case   4:   break;
    
    case   8:   *ptrStatus  |= static_cast<uint8_t> (bitmask.SPI_SPI2X);
                *ptrControl |= static_cast<uint8_t> (bitmask.SPI_SPR0);
                break;

    case  16:   *ptrControl |= static_cast<uint8_t> (bitmask.SPI_SPR0);
                break;
    
    case  32:   *ptrStatus  |= static_cast<uint8_t> (bitmask.SPI_SPI2X);
                *ptrControl |= static_cast<uint8_t> (bitmask.SPI_SPR1);
                break;
    
    case  64:   *ptrControl |= static_cast<uint8_t> (bitmask.SPI_SPR1);
                break;
    
    case 128:   *ptrControl |= static_cast<uint8_t> ((bitmask.SPI_SPR1)|(bitmask.SPI_SPR0));
                break;

    default :   break; // Default Teiler 4
  }
}


// write one Byte in the buffer for transmitting via SPI
void DocSpi::transferByte(uint8_t const data) 
{                   
  uint8_t const tempHead = (buffer.head + 1) & BUFFER_MASK;
      
  // wait for free space in buffer
  while ( tempHead == buffer.tail )  
  { ; } 
  
  buffer.fifo[tempHead] = data;
  buffer.head = tempHead;
        
  if (readyForNewTransfer) 
  {
    if (buffer.head != buffer.tail)
    {
      digitalWrite(csPin, LOW);
            
      // calculate and store new buffer index
      uint8_t const tempTail = (buffer.tail + 1) & BUFFER_MASK;
      buffer.tail = tempTail;
              
      // get one byte from buffer and write it to SPI
      *ptrData = static_cast<uint8_t> ( buffer.fifo[tempTail] );  
      
      // enable Serial Transfer Complete Interrupt
      *ptrControl |= static_cast<uint8_t> (bitmask.SPI_SPIE);
      
      readyForNewTransfer = false;
    }
  }  
}


// das was eigentlich die 'ISR (SPI_STC_vect)' Routine wäre
void DocSpi::interruptServiceRoutine(void)
{
  if (buffer.head != buffer.tail)
  {
      // calculate and store new buffer index
      uint8_t const tempTail = (buffer.tail + 1) & BUFFER_MASK;
      buffer.tail = tempTail;
      
      // get one byte from buffer and write it to SPI
      *ptrData = static_cast<uint8_t> ( buffer.fifo[tempTail] );  
  }
  else
  {        
      digitalWrite(CSPIN, HIGH);      // Modul global
      //digitalWrite(csPin, HIGH);   // Member unbekannt ?
      
      // disable Serial Transfer Complete Interrupt
      *ptrControl &= ~(static_cast<uint8_t> (bitmask.SPI_SPIE));
      
      readyForNewTransfer = true;
  }
}

aber die ISR kennt den Member csPin nicht?

Was ja auch klar ist, da csPin eine Objekteigenschaft ist, und keine Klasseneigenschaft.
Sie ist nicht im Sichtbarkeitsbereich.
static würde helfen....

Bedenke:
Die ISR kann auch ohne Instanz deiner SPI Klasse leben.

Ich würde das so machen:

Die SPI ISR bekommt einen Puffer (oder zwei?) in statischen Variablen. Diese Puffer werden per Interrupt ausgegeben bzw. gefüllt. Die Instanzen der Klasse machen unter sich aus, wer als nächstes die SPI Hardware benutzen darf, und setzen die Puffer-Variablen entsprechend. Zur Verwaltung kann in der Klasse eine (verlinkte) LIste angelegt werden, hier ist keinerlei Interruptverarbeitung notwendig, die Listen-Operationen sind dann alle unteilbar und benötigen damit keine weiteren Semaphore o.ä. zur Synchronisation.

Der Unterschied zur klassischen SPI Bibliothek liegt eigentlich nur darin, daß die Benutzung der Hardware nicht zwangsläufig blockierend sein muß.