Frage zu Code vom Klassen Konstruktor (und Vererbung) - SPI Lib

Hallo,

ich hätte Fragen zu den 4 Code Zeilen.

class Basis
{
  public:
    virtual ~Basis() = default;  
    Basis() = default;           
    Basis(const Basis&) = default;
    Basis& operator = (const Basis&) = default;
};

Die erste Zeile ist der virtuell definierte Dekonstruktor.
Die zweite Zeile ist scheinbar eine andere Schreibweise für den Defaultkonstruktor.
Nur was bedeuten die letzten beiden Zeilen?
Was machen diese genau? Wofür verwendet man sowas?

default bedeutet dass man explizit die vom Compiler generierte Version verwenden will. Es gibt Situationen in denen die nicht generiert werden, aber man es trotzdem gerne möchte:

Das vorletzte ist der Copy Konstruktor und das letzte der Copy Assignment Operator:

Danke dir.

Hallo,

das sollte noch zum Thema Konstruktor passen. Hintergrund, ich bin immer noch dran am bauen meiner SPI Lib. Ich versuche die CombiePin Lib zum schalten des SlaveSelect Pins mittels Zeigerzugriff zu verwenden. Soll unabhängig von digitalWrite() sein. Dazu benötige ich den Datentyp “OutputPin” für den Zeiger aber ohne neues Objekt. Das habe ich nun realisiert indem ich eine Basisklasse in der CombiePin Lib “vorschalte” und die benötigten Methoden virtuell deklariere. Jetzt kann ich mittels Zeiger schalten und walten.

#include <CombiePin.h>
using namespace Combie::Pin;

OutputPin <13> objekt1;
BasisPin *ptr = nullptr;

void setup(void)
{
  ptr = &objekt1;
  ptr->init();
}

void loop(void)
{
   ptr->setHigh();
   delay(150);
   ptr->setLow();
   delay(850);
}

in der CombiePin.h habe ich vorangestellt und vererbt …

class BasisPin
{
  public:
    BasisPin() = default;            
    BasisPin(const BasisPin&) = default;
    BasisPin& operator = (const BasisPin&) = default;
        
    virtual void init();   
    virtual void setHigh();
    virtual void setLow();
};
// einfügen Ende

  template<byte arduinoPin>
  class Pin : public BasisPin
  {

Dafür muss ich allerdings noch die Vererbung der Pin Klasse von protected zu public ändern.

template<byte arduinoPin>
  class OutputPin : public Pin<arduinoPin>
  {

Gibt es eine Möglichkeit den Zugriffsschutz aufrechtzuerhalten?
Durch andere Verberbungsstrategie oder … ?

CombiePin.h (7.11 KB)

Es muss ja nicht public Vererbung sein:

https://de.wikibooks.org/wiki/C%2B%2B-Programmierung:_Vererbung#Zugriffskontrolle

Eigentlich würde ich dir ja gerne helfen…

Aber leider verstehe ich noch nicht das Endziel.

Ein paar Zwischenziele kann ich ausmachen.
Du möchtest schnelle Pin Manipulationen
Auch soll SPI nebenläufig (in einer ISR) abgehandelt werden.

Also erstmal nur ein Tipp:
Neben der Vererbung gibts noch das Aggregat und die Komposition um “Objekte” miteinander zu verquicken.

Oder meinst du sowas:

class BasisPin
{
  public:
    BasisPin() = default;            
    BasisPin(const BasisPin&) = default;
    BasisPin& operator = (const BasisPin&) = default;
        
    virtual void init();  
    virtual void setHigh();
    virtual void setLow();
};
// einfügen Ende

  template<byte arduinoPin>
  class Pin : private BasisPin
  {
     public:
     using BasisPin::setHigh;
  };
  
Pin<2> pin;

Hallo,

@ Serenifly: es war vorher protected und ich musste es public machen. Funktioniert zwar mit public, ist aber bestimmt nicht Sinn der Sache wegen meiner "Quernutzung" den gesamten Schutz aufzugeben. Deswegen meine Unsicherheit.

@ combie: ich werde die Idee testen

@ all:
danach werde ich mal alles bis jetzt von mir neu funktionierende, aber "nur" in Einzelteilen getestete zusammensetzen damit man einen Gesamtüberblick bekommt. Das kann etwas dauern, falls neue Stolpersteine lauern. :wink: Dann erkläre ich auch das Endziel warum ich den Aufwand mit dem Zeiger mache.

Erstmal Danke an Euch.

Hallo,

über Irrwege wurde wieder alles anders und damit einfacher. Die CombiePin.h kann unverändert bleiben. Die Basisklasse wird an die eigentliche SPI Klasse vererbt und diese nutzt Methoden der CombiePin Klasse. Die SPI ISR steht für jeden sichbar im Hauptsketch. Damit entfällt das Gewürge mit den Interrupt Optionangaben und damit ist man frei von Compilerabhängigkeiten. Und das ermöglicht eben überhaupt erst die Nutzung von template Klassen.

Der aktuelle Stand zeigt den neuen Grundstock.
Schaut euch das einmal bitte an.
Ist das als Basis zum Ausbau weiterer Funktionalitäten brauchbar?
Was würdet ihr vielleicht ändern?

In der Funktion 'transferGoOut()' habe ich bewusst keine Atomic Blocks drin, weil die Funktion laut meiner Einschätzung nur aufgerufen werden kann, wenn der Bus nicht benutzt wird und damit die ISR inaktiv ist. Die ISR wird ja erst durch diese Funktion immer wieder erneut in Gang gesetzt.

Die 'messPin's im Sketch und Lib hab ich für mich dringelassen. Die dienen mir am Oszi bzw. Datalogger zum prüfen ob der Ablauf stimmt bzw. ob es generell funktioniert. Oder für euch zum nachvollziehen des Ablaufes.

Desweiteren bin ich dabei als 3. neuen Objektparameter die Buffergröße anzugeben. Vorbereitungen dazu stecken im Code. Klappt aber noch nicht. virtual kann man nur Funktionen deklarieren, leider keine Member. Soll aber irgendwie mit virtueller return Funktion möglich sein. Hier hänge ich gerade fest. Gleichzeitig virtual deklarieren und aufrufen in der Basisklasse geht nicht. Der Objektparameter soll die FiFoBuffergröße des Array angeben. Das struct muss in der Basisklasse sein, weil ich damit im Sketch mittels Zeiger über alle Objekte hinweg damit zugreifen kann. Ansonsten müßte ich das 'struct Buffer' aufgeben und alle struct Member einzeln schreiben. Das wäre meine letzte Option die ich derzeit hätte.

Wo ich mir nicht sicher bin ist ob man den Dekonstruktor wirklich benötigt. Wir zerstören doch im Allgemeinen keine einmal instanzierten Objekte auf unseren µC.

Desweiteren müßte doch der Zeiger 'BasisDevice *ptrDevice' volatile sein, oder? Wird im Hauptprogramm und in der ISR ständig geändert. Nur wenn ich den volatile mache meckert der Compiler.

DocSpi.zip (4.3 KB)

Hallo,

.zip ist oben dran, hier die Vorschau für alle anderen

Header

#pragma once

#include <avr/io.h>
#include "AvrRegister.h"
#include <CombiePin.h>
using namespace Combie::Pin;

// Messpins zur visuellen Darstellung des Ablaufes auf Oszi oder Datalogger
OutputPin<2> messPin2; 
OutputPin<3> messPin3; 
OutputPin<4> messPin4; 

// Size of the circular buffer, must be power of 2.
constexpr uint8_t BUFFERSIZE = 16;
// Size of buffer
constexpr uint8_t BUFFER_MASK = BUFFERSIZE - 1;


// stellt Datentyp für Device-Zeiger zur Verfügung ohne Objekt mit Parametern erstellen zu müssen
class BasisDevice
{
  public:
    //virtual ~BasisDevice() = default;   // kostet 612 Bytes Flash
    BasisDevice() = default;            
    BasisDevice(const BasisDevice&) = default;
    BasisDevice& operator = (const BasisDevice&) = default;
 
    // '...() = 0' verhindert das mit der Klasse ein Objekt instanziert werden kann 
    virtual void setHigh() = 0;   // derzeit wird nur diese Methode mittels Zeiger im Hauptsketch benötigt
        
    virtual uint8_t bufferSize();  // soll mir den Wert 'fifoSize' reinholen und 'BUFFERSIZE' ersetzen
    
    struct Buffer
    {
      volatile uint8_t fifo[BUFFERSIZE];
      volatile uint8_t head;
      volatile uint8_t tail;
      volatile uint8_t lastError;
      volatile bool startNewTransfer;
    } buffer;                   // create a FiFo-Buffer
};


// ------ here begins the template class -----------------------------------

template<uint8_t const csPin, uint8_t const divider, uint8_t const fifoSize>  // slave select pin, SPI clock divider
class SpiSlave : public BasisDevice
{
  private:
    OutputPin<csPin> slaveSelectPin;
    
    void setFrequency (void)
    {        
      *ptrStatus  &= ~(static_cast<uint8_t> (bitmask.SPI_SPI2X));
      *ptrControl &= ~(static_cast<uint8_t> (bitmask.SPI_SPR1 | bitmask.SPI_SPR0));
      
      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 divider 4
      }
    }
             
    public:
    // ****** Default Konstruktor ******
    SpiSlave ()
    { }    
        
    uint8_t bufferSize (void)    
    {
      return fifoSize;
    }
    
    // ****** Methoden ******
        
    void setHigh (void) { slaveSelectPin.setHigh(); }   // virtual in class BasisDevice
    
    void init (void)
    {    
      messPin2.init();
      messPin3.init();
      messPin4.init();
        
      slaveSelectPin.init();
      slaveSelectPin.setHigh();
                   
      buffer.head = 0;
      buffer.tail = 0;
      buffer.lastError = 0;
      buffer.startNewTransfer = true;
                        
      // Register content delete
      *ptrStatus  = static_cast<uint8_t> (0);
      *ptrControl = static_cast<uint8_t> (0);
      *ptrData    = static_cast<uint8_t> (0);
              
      // SPI Settings
      *ptrControl |= static_cast<uint8_t> (bitmask.SPI_MSTR);   // set Master
      *ptrControl |= static_cast<uint8_t> (bitmask.SPI_SPE);    // SPI Enable
      
      // read once to delete flag
      while (SPSR & (1<<SPIF)) { ; }
    }
       
    // write one Byte in the buffer for transmitting via SPI
    void fillBuffer(uint8_t const data) 
    {              
      messPin2.setHigh();
      
      uint8_t tempHead = 0;
      bool freeSpace = true;       // Vorbereitung für Error Code
      
      ATOMIC_BLOCK (ATOMIC_RESTORESTATE)
      {   
        tempHead = (buffer.head + 1) & BUFFER_MASK;
        if (tempHead == buffer.tail)
        {
          freeSpace = false;   // no free space in buffer
        }         
      }
      
      if (freeSpace)
      {  
        ATOMIC_BLOCK (ATOMIC_RESTORESTATE)
        {
          buffer.fifo[tempHead] = data;
          buffer.head = tempHead;
        }
      }
      
      messPin2.setLow();
      
      /* vorheriger Code
      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;
      */
    }

    // write 'first' byte from buffer into SPDR and enable the interrupt for the following bytes
    void transferGoOut() 
    {              
      messPin3.setHigh();
              
      if (buffer.startNewTransfer) 
      {
        if (buffer.head != buffer.tail)
        { // ab hier 4,4µs 
          buffer.startNewTransfer = false;  // 
          setFrequency();                   // device specific frequency setting        
          slaveSelectPin.setLow();          // device specific cs pin
          
          // 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);
        }
      }  
      
      messPin3.setLow();
    }
};

Sketch

/*
  Doc_Arduino - german Arduino Forum
  IDE 1.8.12
  avr-gcc 9.3.0
  Arduino Mega2560
  02.05.2020
  License: GNU GPLv3
  CombiePin Library hat User combie zur Verfügung gestellt
*/

#include <util/atomic.h>
#include "DocSpi.h"
#include <CombiePin.h>
using namespace Combie::Pin;

// Messpins zur visuellen Darstellung des Ablaufes auf Oszi oder Datalogger (Debugging)
OutputPin<5> messPin5; 
OutputPin<6> messPin6; 
OutputPin<7> messPin7;

// notwendige SPI Konfiguration
OutputPin<53> SSpin;    // beim UNO Pin 10
OutputPin<52> CLKpin;   // beim UNO Pin 13
OutputPin<51> MOSIpin;  // beim UNO Pin 11

// stellt den notwendigen Datentyp zum Device Handling mittels Zeiger zur Verfügung
BasisDevice *ptrDevice = nullptr;

// gewünschte Objekte erstellen
SpiSlave <11, 32, 16> device1;  // Slave Select Pin, SPI Taktteiler, Buffergröße
SpiSlave <12,  4, 16> device2;


void setup(void)
{
  SSpin.init();
  SSpin.setHigh();
  CLKpin.init();
  MOSIpin.init();
  
  device1.init();
  device2.init();
  
  messPin5.init(); 
  messPin6.init(); 
  messPin7.init();
}

void loop(void)
{
  spiSchaufeltDatenRaus();    // Demo
}


// ****** Funktionen ******
void spiSchaufeltDatenRaus (void)
{
  messPin5.setHigh();
  
  static byte number = 0;
  
  switch (number)
  {
    case 0:
      if (ptrDevice == nullptr )    // wartet bis SPI-Bus frei ist
      {
        ptrDevice = &device1;       // dem Zeiger ein neues Device zuweisen
        device1.fillBuffer('U');
        device1.fillBuffer(24);
        device1.fillBuffer(24);
        device1.fillBuffer('U');
        device1.transferGoOut();
        number = 1;
      }
      break;

    case 1:
      if (ptrDevice == nullptr )
      {
        ptrDevice = &device2;
        device2.fillBuffer(153);
        device2.fillBuffer('U');
        device2.fillBuffer('U');
        device2.fillBuffer(153);
        device2.transferGoOut();
        number = 0;
      }
      break;

    default: break;
  }
  
  messPin5.setLow();
}


ISR(SPI_STC_vect)
{  
  if (ptrDevice->buffer.head != ptrDevice->buffer.tail)   // transmit from the second byte
  {
    messPin6.setHigh();  
    
      // calculate and store new buffer index
      uint8_t const tempTail = (ptrDevice->buffer.tail + 1) & BUFFER_MASK;
      ptrDevice->buffer.tail = tempTail;
      
      // get one byte from buffer and write it to SPI
      *ptrData = static_cast<uint8_t> ( ptrDevice->buffer.fifo[tempTail] );  
      
    messPin6.setLow();
  }
  else  // buffer is emtpy
  {       
    messPin7.setHigh();   
    
      // disable Serial Transfer Complete Interrupt
      *ptrControl &= ~(static_cast<uint8_t> (bitmask.SPI_SPIE));
      
      // log off the device  
      ptrDevice->setHigh();
      ptrDevice->buffer.startNewTransfer = true;
      ptrDevice = nullptr;
      
    messPin7.setLow();  
  }
}

Du kannst in der Basisklasse eine Konstruktor definieren der die Größe setzt und diesen Konstruktor per constructor delegation in der Kindklasse aufrufen

Dekonstruktor braucht man nur wenn man in der Klasse was mit dynamischen Speicher macht. Dann muss man das da aufräumen

Hallo,

Danke dir. Fang ich morgen mit an.

Hallo,

ich habe noch nicht verstanden wie ich in der abgeleiteten Klasse den Basis Konstruktor aufrufen muss. Alle Bsp. arbeiten immer nur mit ein und der selben Klasse und dann “intern” mit mehreren Konstruktoren.

Meine Logik scheitert das auf 2 Klassen anzuwenden. Es kompiliert bei mir wenn ich den Member ‘bufferSize’ im Array ‘Buffer’ nicht verwende. Ersetze ich BUFFERSIZE durch bufferSize erhalte ich wie bei allen anderen Versuchen immer “error: invalid use of non-static data member ‘BasisDevice::bufferSize’”

// Size of the circular buffer, must be power of 2.
constexpr uint8_t BUFFERSIZE = 16;
// Size of buffer
constexpr uint8_t BUFFER_MASK = BUFFERSIZE - 1;


// stellt Datentyp für Device-Zeiger zur Verfügung 
class BasisDevice 
{  
  protected:
  uint8_t bufferSize;
  
  public:   
    BasisDevice(uint8_t bs) : bufferSize{bs} {}
    
    BasisDevice(const BasisDevice&) = default;
    BasisDevice& operator = (const BasisDevice&) = default;
 
    virtual void setHigh();   // derzeit wird nur diese Methode mittels Zeiger im Hauptsketch benötigt
            
    struct Buffer
    {
      volatile uint8_t fifo[bufferSize];  // [BUFFERSIZE]
      volatile uint8_t head;
      volatile uint8_t tail;
      volatile uint8_t lastError;
      volatile bool startNewTransfer;
    } buffer;                   // create a FiFo-Buffer
};


// ------ here begins the template class -----------------------------------

template<uint8_t const csPin, uint8_t const divider, uint8_t fifoSize>  
class SpiSlave : public BasisDevice
{
  private:
    OutputPin<csPin> slaveSelectPin;
                 
  public:
    // ****** Konstruktor ******
    SpiSlave () :
    BasisDevice{fifoSize}
    { }
 public:

// ****** Konstruktor ******
    SpiSlave () :
    BasisDevice{fifoSize}
    { }

Ohne jetzt wirkich getestet zu haben:
(oder gar dein Problem verstanden zu haben)

  public:
    // ****** Konstruktor ******
    using BasisDevice::BasisDevice;

Hallo,

kompiliert leider mit gleicher Fehlermeldung.

template<uint8_t const csPin, uint8_t const divider, uint8_t fifoSize> 
class SpiSlave : public BasisDevice
{
  private:
    OutputPin<csPin> slaveSelectPin;
                 
  public:
    // ****** Konstruktor ******
    SpiSlave ()
    { }    

    using BasisDevice::BasisDevice;

Das hier erzeugt gleiche Fehlermeldung, immer sobald ich BUFFERSIZE mit bufferSize im struct ersetze.
Vom Gefühl her wird zu bufferSize zu spät initialisiert.

template<uint8_t const csPin, uint8_t const divider, uint8_t fifoSize>  
class SpiSlave : public BasisDevice
{
  private:
    OutputPin<csPin> slaveSelectPin;
             
  public:
    // ****** Konstruktor ******
    SpiSlave () :
    { 
      bufferSize = fifoSize; 
    }

Du machst in BasisDevice einen Konstruktor:

BasisDevice(uint8_t bufferSize) : bufferSize(bufferSize)
{
}

Und in der Kindklasse wird er so aufgerufen:

SpiSlave () : BasisDevice(fifoSize)
{
}

Wobei das fifoSize aus der Template-Deklaration kommt

Normal geht das. Das gibt es seit C++11. Vorher musste man da mit Hilfsmethoden arbeiten

using arbeitet so ähnlich. Auch da muss der Konstruktor in der Basisklasse existieren und durch using sagst du dass er von der Kindklasse geerbt wird. Dadurch kann man es sich u.U. sparen einen weiteren Konstruktor explizit hinzuschreiben

Ich vermute, ihm möchte fifoSize als Array Größe verwenden.
Aber nicht das Array dynamisch allozieren.

Vermeiden möchte er, vermutlich, die fifoSize als Template Parameter an die Basisklasse durchzureichen, da ihm dann das Template(Typisierung) an anderer Stelle ein Beinchen stellt.

Alles ohne jede Gewähr, nur im Glaskugelmodus

Hallo,

richtig erkannt. Der template Parameter ‘fifoSize’ soll in der BasisDevice die Arraygröße im struct festlegen. Dort soll er seine endgültige alleinige Verwendung finden.

Das funktioniert leider nicht. Hatte ich schon in folgender Form probiert.

class BasisDevice 
{  
  private:
  uint8_t bufferSize;
  
  public:   
    BasisDevice(uint8_t bs) : bufferSize(bs)
    {}
    
    BasisDevice(const BasisDevice&) = default;
    BasisDevice& operator = (const BasisDevice&) = default;
 
    virtual void setHigh();   
            
    struct Buffer
    {
      volatile uint8_t fifo[bufferSize];  // [BUFFERSIZE]
      volatile uint8_t head;
      volatile uint8_t tail;
      volatile uint8_t lastError;
      volatile bool startNewTransfer;
    } buffer;                   // create a FiFo-Buffer
};


template<uint8_t const csPin, uint8_t const divider, uint8_t const fifoSize> 
class SpiSlave : public BasisDevice()
{
  private:
    OutputPin<csPin> slaveSelectPin;
             
  public:
    // ****** Konstruktor ******
    SpiSlave () :
    BasisDevice{fifoSize}
    { }

Jetzt habe ich deins verwendet, was im Grunde 1:1 zu meinem ist, kompiliert immer noch nicht.
error: invalid use of non-static data member ‘BasisDevice::fifoSize’
volatile uint8_t fifo[fifoSize]; // [BUFFERSIZE]

class BasisDevice 
{  
  private:
  uint8_t fifoSize;
  
  public:   
    BasisDevice(uint8_t fifoSize) : fifoSize(fifoSize)
    {}
    
    BasisDevice(const BasisDevice&) = default;
    BasisDevice& operator = (const BasisDevice&) = default;
 
    virtual void setHigh();  
            
    struct Buffer
    {
      volatile uint8_t fifo[fifoSize];  // [BUFFERSIZE]
      volatile uint8_t head;
      volatile uint8_t tail;
      volatile uint8_t lastError;
      volatile bool startNewTransfer;
    } buffer;                   // create a FiFo-Buffer
};


// ------ here begins the template class -----------------------------------

template<uint8_t const csPin, uint8_t const divider, uint8_t const fifoSize>  
class SpiSlave : public BasisDevice()
{
  private:
    OutputPin<csPin> slaveSelectPin;
                 
  public:
    // ****** Konstruktor ******
    SpiSlave () :
    BasisDevice{fifoSize}
    { }

Ja, das Problem mit dem Arrays ist mir auch gerade aufgefallen. Ich hatte das etwas zu allgemein betrachtet. Deshalb geht es mit einer Konstante, aber sobald Variablen im Spiel sind passt es nicht. Das geht wiederum mit Templates, wobei dann halt jede Instanz mit einer anderen Größe eigentlich eine andere Klasse ist

template <size_t N>
class Base
{
public:
  size_t getSize()
  {
    return sizeof(data) / sizeof(data[0]);
  }
protected:
  Base() {}
  unsigned int data[N];
};

template <size_t N>
class Derived : public Base<N>
{
public:
private:
};
void setup()
{
  Serial.begin(9600);

  Derived<10> obj1;
  Derived<5> obj2;
  Serial.println(obj1.getSize());
  Serial.println(obj2.getSize());
}

Hallo,

das funktioniert alles für sich, jedoch kann ich dann von Base keinen Zeiger erstellen bzw. nicht allgemein verwenden. Den Zeiger benötige ich um darüber vom aktuell zugewissen SPI Slave die Member vom struct benutzen zu können. Base (BasisDevice) habe ich erstellt um den passenden Datentyp für den Zeiger zu erhalten.
Ist das Problem lösbar?
Oder gibt es eine andere Idee statt über Zeiger auf die struct Member vom aktuellen SPI Slave zugreifen zu können. Wird in der ISR benötigt um diese allgemein zu halten.

template <uint8_t N>
class Base
{
  public:
    uint8_t getSize()
    {
      return sizeof(buffer.data) / sizeof(buffer.data[0]);
    }
    
    Base() {}

    struct Buffer
    {
      unsigned int data[N];
    } buffer;
};

template <uint8_t N>
class Derived : public Base<N>
{
  public:
  private:
};

Base <0> *ptrBase = nullptr;

void setup()
{
  Serial.begin(9600);

  Derived<10> obj1;
  Derived<5>  obj2;
  Serial.println(obj1.getSize());
  Serial.println(obj2.getSize());

  ptrBase = &obj1;  // error: cannot convert 'Derived<10>*' to 'Base<0>*' in assignment
}

void loop (void)
{
}