switch mit struct kompiliert nicht mit Konstanten

Ich weiß mit switch case und struct nicht mehr weiter.

Habe den Sketch gekürzt für einen besseren Überblick.
Die Variablen MAX7221.DIGIT0 und MAX7221.DIGIT1 sind Konstanten.
Diese wollte ich in einem switch case zum Vergleich vewenden. Kompiliert jedoch nicht.
"the value of 'MAX7221' is not usable in a constant expression"
Bedeutet doch "in einem konstanten Ausdruck nicht verwendbar".
Ja warum denn nicht?
Ein case MAX7221.DIGIT0: funktioniert nicht.
Ein case 1: und case 2: sind in meinen Augen genauso konstant wie obige Variablen.
Wo liegt das Problem?

/*
  IDE 1.8.7
  Arduino Mega2560
*/

struct t_setup
{
  const byte SEGA  = 64;
  const byte DIGIT0 = 0x01;   // Datenblatt kompatible Schreibweise
  const byte DIGIT1 = 0x02;
  
} MAX7221;


struct t_led
{
  const byte digit;
  const byte segment;
};


t_led LED[] = {
  {MAX7221.DIGIT0, MAX7221.SEGA},  // LED  1
  {MAX7221.DIGIT1, MAX7221.SEGA},  // LED  2
};

const byte ANZAHL_LED = sizeof(LED) / sizeof(t_led);


void setup() {
  
}


void loop() {

  kompiliert_nicht();
  
  kompiliert();

}


// ****** Funktionen ******

void kompiliert_nicht ()
{
  static byte index = 0;
  static byte digit0_seg = 0;
  static byte digit1_seg = 0;
  
  if (index < ANZAHL_LED) {
    switch (LED[index].digit) {
      case MAX7221.DIGIT0:                                        // wenn bestimmtes Digit aktiv, dann
            digit0_seg |= LED[index].segment;                     // Segmentwert vom Digit aufaddieren
            break;
      case MAX7221.DIGIT1:
            digit1_seg |= LED[index].segment;
            break;
      default:
            break;      
    }
  }
  ++index;
}


void kompiliert ()
{
  static byte index = 0;
  static byte digit0_seg = 0;
  static byte digit1_seg = 0;
  
  if (index < ANZAHL_LED) {
    if (LED[index].digit == MAX7221.DIGIT0) {           // wenn bestimmtes Digit aktiv, dann
      digit0_seg |= LED[index].segment;                 // Segmentwert vom Digit aufaddieren
    }
    if (LED[index].digit == MAX7221.DIGIT1) {
      digit1_seg |= LED[index].segment;
    }    
  }
  ++index;
}

Sind bei switch/case nicht nur Datentyp int und char erlaubt?

Eine struct Variable ist da nur als constexpr erlaubt.

Hier ein noch vereinfachteres Beispiel:

struct Test
{
  const int a = 1;
  const int b = 2;  
};

Test Foo;

void setup() {
  int x = 1;

  switch(x)
  {
    case Foo.a:
      break;
    default:
      break;
  }
}

void loop() {
  // put your main code here, to run repeatedly:

}

Das liefert die gleiche Fehlermeldung.

Auch wenn man

const Test Foo;

schreibt.

Erst wenn man

constexpr Test Foo;

schreibt, ist der Compiler ruhig.

Scherheinz:
Sind bei switch/case nicht nur Datentyp int und char erlaubt?

Etwas mehr

any expression of integral or enumeration type, or of a class type contextually implicitly convertible to an integral or enumeration type

Ah ok, danke!

any expression of integral or enumeration type, or of a class type contextually implicitly convertible to an integral or enumeration type

Bei dem Satz kommen meine Englischkenntnisse aber schwer an die Grenze :astonished:

Hallo,

aha. Danke für die Hilfe.

@ Scherheinz: DeepL kann dir helfen.

Ehrlich gesagt, ich grüble gerade auch noch am genauen Grund. Immerhin sind sich die Compiler einig, hier ein Beispiel mit Visual Studio:

struct Test
{
 const int a = 1;
 const int b = 2;
};

constexpr Test Foo;
const int two = 2;

int main()
{
 int x = 1;

 switch (x)
 {
 case Foo.a:
 break;
 case two:
 break;
 default:
 break;
 }
}

Bei Strukturen oder Klassen scheinen die Regeln strenger zu sein, der int wird auch als normaler const akzeptiert.

Vielleicht hilft es, wenn Du in deinem Program die Konstanten aus dem struct in einen separaten enum class (wir sind ja in modernem C++) auslagerst.

Also so

enum class Constant
{
	a = 1,
	b = 2
};

struct Test
{
	Constant elem;
};

Test Foo;

int main()
{
	switch (Foo.elem)
	{
	case Constant::a :
		break;
	default:
		break;
	}
}

Das const in der Klasse erweckt den falschen Eindruck. Das sind keine Compile-Zeit Konstanten (das Objekt wird ja erst zur Laufzeit erstellt), sondern lediglich read-only Variablen

Durch constexpr Test teilt man dem Compiler mit dass er das zur Compile-Zeit erstellen soll

Du kannst wahrscheinlich auch im struct selbst constexp statt const schreiben

Mir erschliesst sich der Sinn des Ganzen nicht ganz,
aber meinst du mit deiner Sammlung von Konstanten evtl. so was?

enum MAX7221 :byte
{
  SEGA  = 64,
  DIGIT0 = 0x01,   // Datenblatt kompatible Schreibweise
  DIGIT1 = 0x02 
};


void setup() {
  static byte index = 0;
  byte x; 
  switch (index) {
   case MAX7221::DIGIT0:
      x = MAX7221::SEGA;
      break;
   default:
      x = 0;
  } 
} 


void loop() {}

Bei switch(expr) { sollte expr einen ganzzahligen Wert ergeben
Bei case i: muss i ein konstanter Ausdruck sein, der zur Kompilierzeit bestimmt wird

Sehr guter Tipp, vielen Dank! :slight_smile:

Doc_Arduino:
Hallo,

aha. Danke für die Hilfe.

@ Scherheinz: DeepL kann dir helfen.

Sehr guter Tipp, vielen Dank! :slight_smile:

Hallo,

mit const vs. constexpr wäre das Kompilierproblem für den reduzierten Sketch geklärt. Danke euch.

constexpr im struct funktioniert leider nicht.
"non-static data member 'DIGIT0' declared 'constexpr'"
Das wäre optimal gewesen. Da ich noch Methoden im struct habe, die mit constexpr für den Bezeichner angemeckert werden.

struct t_setup
{
  const byte SEGA  = 64;
  constexpr byte DIGIT0 = 0x01;   
  constexpr byte DIGIT1 = 0x02;
  
} MAX7221;

enums wie ich es kenne kann ich nicht verwenden. Ich wollte die ohnehin vorhandenen Konstanten mehrfach verwenden. Mit enums wäre alles doppelt - zu gefährlich bei Änderungen.

Diese Schreibweise ist mir nicht geläufig.

enum MAX7221 :byte
{
  SEGA  = 64,
  DIGIT0 = 0x01,   // Datenblatt kompatible Schreibweise
  DIGIT1 = 0x02
};

Ich sehe aktuell nur die Möglichkeit das struct aufsplitten. In Konstanten und Methoden. Dabei holt mich wieder mein Problem der Namensfindung ein. Alles unter MAX7221.xxx wäre schöner gewesen. Mir wird nichts anderes übrig bleiben? Dann kann ich für die Konstanten auch die enum Schreibweise von Michael verwenden, wenn das Sinn macht.

Der Plan war die Laufbalken Funktion von 4x if auf switch case umzubauen um dann den Effekt zu ändern.
Der gesamte Sketch liegt im Angang, wer sich das anschauen möchte.
Das struct sieht aktuell so aus.

struct t_setup
{
  const byte CLK  = 24;
  const byte CS   = 25;
  const byte MOSI = 26;
  const byte SEGDP = 128;     // Bit.7 - Datenblatt Tabelle 6
  const byte SEGA  = 64;
  const byte SEGB  = 32;
  const byte SEGC  = 16;
  const byte SEGD  =  8;
  const byte SEGE  =  4;
  const byte SEGF  =  2;
  const byte SEGG  =  1;      // Bit.0
  const byte DIGIT0 = 0x01;   // Datenblatt kompatible Schreibweise
  const byte DIGIT1 = 0x02;
  const byte DIGIT2 = 0x03;
  const byte DIGIT3 = 0x04;

  void shift_out( uint8_t adresse, uint8_t daten )
  { // Bits rausschieben, MSB first
    uint16_t data = 0;
    data = (data | adresse) << 8;
    data = data | daten;

    digitalWrite(CS, LOW);

    for (uint8_t i = 0; i < 16; i++) {
      if (data & 0x8000) {              // nur oberstes Bit betrachten
        digitalWrite(MOSI, HIGH);       // und Datenleitung entsprechend setzen
      }
      else {
        digitalWrite(MOSI, LOW);
      }
      digitalWrite(CLK, HIGH);          // Takt erzeugen
      data = data << 1;                 // nächstes Bit links schieben
      digitalWrite(CLK, LOW);
    }
    
    digitalWrite(CS, HIGH);
  }

  void clear_all ()
  { 
    shift_out(DIGIT0, 0);   // löschen
    shift_out(DIGIT1, 0);   // löschen
    shift_out(DIGIT2, 0);   // löschen
    shift_out(DIGIT3, 0);   // löschen
  }
  
}; 

constexpr t_setup MAX7221;
[/code

MAX7221_004.ino (7.95 KB)

enums wie ich es kenne kann ich nicht verwenden. Ich wollte die ohnehin vorhandenen Konstanten mehrfach verwenden. Mit enums wäre alles doppelt - zu gefährlich bei Änderungen.

Schau dir C++11 an

Da gibt es "strongly typed enums" bei denen man Konstanten mehrfach verwenden kann, da ihr Gültigkeitsbereich auf den enum Namen begrenzt ist

Hallo,

das Detail konnte ich für nun mich klären. Ich habe bisher immer die typdef enum C Schreibweise verwendet und nicht die enum class C++ Schreibweise. Daher die Unterschiede im Syntax und resultierenden Typsicherheiten.

Diese Schreibweise ist mir nicht geläufig.
Code: [Select]

enum MAX7221 :byte

{
 SEGA  = 64,
 DIGIT0 = 0x01,   // Datenblatt kompatible Schreibweise
 DIGIT1 = 0x02
};

"strongly typed enums" werde ich gleich lesen.
Um das aufsplitten des struct komme ich jedoch nicht drumrum? Das als Frage vorweg.

Edit:
jetzt im Zusammenhang gerafft. Mit "strongly typed enums" ist C++ enum class gemeint. :slight_smile:

Hallo,

mit enum class schaffe ich mir mehr Probleme. Habe die Konstanten ausgelagert. Wenn mir nicht eine fehlerhafte Konvertierung nach int vorgewurfen wird obwohl ich alles mit byte initialisiere, dann habe ich andere Fehler die ich im Code eingefügt habe. Ich werde damit nicht glücklich. Einzig switch ... case CONFIG::DIGIT0 funktioniert.

/*
  IDE 1.8.7
  Arduino Mega2560
*/

enum class CONFIG :byte  {
  CLK   = 24,
  CS    = 25,
  MOSI  = 26,
  SEGDP = 128,     // Bit.7 - Datenblatt Tabelle 6
  SEGA  = 64,
  SEGB  = 32,
  SEGC  = 16,
  SEGD  =  8,
  SEGE  =  4,
  SEGF  =  2,
  SEGG  =  1,      // Bit.0
  DIGIT0 = 0x01,   // Datenblatt kompatible Schreibweise
  DIGIT1 = 0x02,
  DIGIT2 = 0x03,
  DIGIT3 = 0x04
};


struct t_led
{
  const byte digit;
  const byte segment;
};


t_led LED[] = {
  {CONFIG::DIGIT0, CONFIG::SEGA},  // LED  1
  {CONFIG::DIGIT1, CONFIG::SEGA}   // LED  2
};  // error: cannot convert 'CONFIG' to 'const byte {aka const unsigned char}' in initialization


const byte ANZAHL_LED = sizeof(LED) / sizeof(t_led);


void setup() {
  Serial.begin(9600);
  Serial.println(CONFIG::DIGIT2);
  /* no known conversion for argument 1 from 'CONFIG' to 'const Printable&'
     exit status 1
     no matching function for call to 'HardwareSerial::print(CONFIG)'
  */   
}


void loop() {

}

Da ich um das aufsplitten, was mir nachwievor nicht gefällt, wohl nicht drum herum komme, habe ich es mit struct und constexpr umgebaut. Das kompiliert. Ein besserer Name wie MATRIX fiel mir nicht ein.

/*
  Doc_Arduino - german Arduino Forum
  IDE 1.8.7
  Arduino Mega2560
  03.11.2018

  MAX7221
  DATA: MSB first
        D15 ...D12  bleibt unbenutzt
        D11 ... D8  Registeradresse
        D7  ... D0  Daten

*/
struct t_setup  {
  const byte CLK  = 24;
  const byte CS   = 25;
  const byte MOSI = 26;
  const byte SEGDP = 128;     // Bit.7 - Datenblatt Tabelle 6
  const byte SEGA  = 64;
  const byte SEGB  = 32;
  const byte SEGC  = 16;
  const byte SEGD  =  8;
  const byte SEGE  =  4;
  const byte SEGF  =  2;
  const byte SEGG  =  1;      // Bit.0
  const byte DIGIT0 = 0x01;   // Datenblatt kompatible Schreibweise
  const byte DIGIT1 = 0x02;
  const byte DIGIT2 = 0x03;
  const byte DIGIT3 = 0x04;
};
constexpr t_setup MATRIX; 


struct t_methoden
{
  void shift_out( uint8_t adresse, uint8_t daten )
  { // Bits rausschieben, MSB first
    uint16_t data = 0;
    data = (data | adresse) << 8;
    data = data | daten;

    digitalWrite(MATRIX.CS, LOW);

    for (uint8_t i = 0; i < 16; i++) {
      if (data & 0x8000) {              // nur oberstes Bit betrachten
        digitalWrite(MOSI, HIGH);       // und Datenleitung entsprechend setzen
      }
      else {
        digitalWrite(MOSI, LOW);
      }
      digitalWrite(MATRIX.CLK, HIGH);          // Takt erzeugen
      data = data << 1;                 // nächstes Bit links schieben
      digitalWrite(MATRIX.CLK, LOW);
    }
    
    digitalWrite(MATRIX.CS, HIGH);
  }

  void clear_all ()
  { 
    shift_out(MATRIX.DIGIT0, 0);   // löschen
    shift_out(MATRIX.DIGIT1, 0);   // löschen
    shift_out(MATRIX.DIGIT2, 0);   // löschen
    shift_out(MATRIX.DIGIT3, 0);   // löschen
  }
  
} MAX7221; 


struct t_led
{
  const byte digit;
  const byte segment;
};


const t_led LED[] = {
  {MATRIX.DIGIT0, MATRIX.SEGA},  // LED  1
  {MATRIX.DIGIT1, MATRIX.SEGA},
  {MATRIX.DIGIT2, MATRIX.SEGA},
  {MATRIX.DIGIT3, MATRIX.SEGA},
  {MATRIX.DIGIT0, MATRIX.SEGB},  // LED  5
  {MATRIX.DIGIT1, MATRIX.SEGB},
  {MATRIX.DIGIT2, MATRIX.SEGB},
  {MATRIX.DIGIT3, MATRIX.SEGB},
  {MATRIX.DIGIT0, MATRIX.SEGC},
  {MATRIX.DIGIT1, MATRIX.SEGC},  // LED 10
  {MATRIX.DIGIT2, MATRIX.SEGC},
  {MATRIX.DIGIT3, MATRIX.SEGC},
  {MATRIX.DIGIT0, MATRIX.SEGD},
  {MATRIX.DIGIT1, MATRIX.SEGD},
  {MATRIX.DIGIT2, MATRIX.SEGD},  // LED 15
  {MATRIX.DIGIT3, MATRIX.SEGD},
  {MATRIX.DIGIT0, MATRIX.SEGE},
  {MATRIX.DIGIT1, MATRIX.SEGE},
  {MATRIX.DIGIT2, MATRIX.SEGE},
  {MATRIX.DIGIT3, MATRIX.SEGE},  // LED 20
  {MATRIX.DIGIT0, MATRIX.SEGF},
  {MATRIX.DIGIT1, MATRIX.SEGF},
  {MATRIX.DIGIT2, MATRIX.SEGF},
  {MATRIX.DIGIT3, MATRIX.SEGF},
  {MATRIX.DIGIT0, MATRIX.SEGG},  // LED 25
  {MATRIX.DIGIT1, MATRIX.SEGG},
  {MATRIX.DIGIT2, MATRIX.SEGG},
  {MATRIX.DIGIT3, MATRIX.SEGG},
  {MATRIX.DIGIT0, MATRIX.SEGDP},
  {MATRIX.DIGIT1, MATRIX.SEGDP}, // LED 30
  {MATRIX.DIGIT2, MATRIX.SEGDP}  // LED 31
};

const byte ANZAHL_LED = sizeof(LED) / sizeof(t_led);


void setup() {
  Serial.begin(9600);
  digitalWrite(MATRIX.CS, HIGH);
  pinMode(MATRIX.CS, OUTPUT);
  pinMode(MATRIX.CLK, OUTPUT);
  pinMode(MATRIX.MOSI, OUTPUT);

  MAX7221.shift_out(0x0C, 1);   // aufwecken
  MAX7221.clear_all();          // löschen
  MAX7221.shift_out(0x0F, 1);   // Test ein
  delay(500);
  MAX7221.shift_out(0x0F, 0);   // Test aus
  MAX7221.shift_out(0x0A, 9);   // Intensität
  MAX7221.shift_out(0x0B, 3);   // Scan Limit
  MAX7221.shift_out(0x09, 0);   // No Decode Mode

}


void loop() {

  radar();
  
}


// ****** Funktionen ******

void radar ()
{
  static unsigned long last_ms = 0;
  const unsigned int PAUSE = 100; 
  static unsigned int interval = PAUSE;
  static byte index = 0;
  static byte digit0_seg = 0;
  static byte digit1_seg = 0;
  static byte digit2_seg = 0;
  static byte digit3_seg = 0;

  if (millis() - last_ms < interval) return;
  
  last_ms = millis();

  if (index < ANZAHL_LED) {
   
    switch (LED[index].digit) {
      case MATRIX.DIGIT0:                                         // wenn bestimmtes Digit aktiv, dann
            digit0_seg = digit0_seg | LED[index].segment;         // Segmentwert vom Digit aufaddieren
            MAX7221.shift_out(LED[index].digit, digit0_seg);      // und ausgeben
            break;
      case MATRIX.DIGIT1:
            digit1_seg = digit1_seg | LED[index].segment;
            MAX7221.shift_out(LED[index].digit, digit1_seg);
            break;
      case MATRIX.DIGIT2:
            digit2_seg = digit2_seg | LED[index].segment;
            MAX7221.shift_out(LED[index].digit, digit2_seg);
            break;
      case MATRIX.DIGIT3:
            digit3_seg = digit3_seg | LED[index].segment;
            MAX7221.shift_out(LED[index].digit, digit3_seg);
            break;
      default:
            Serial.print(F("case error ")); Serial.print('\t');
            Serial.println(LED[index].digit);
            break;      
    }
    
    ++index;
  }
  
  
  if (index > ANZAHL_LED) {
    MAX7221.clear_all();    
    digit0_seg = 0;
    digit1_seg = 0;
    digit2_seg = 0;
    digit3_seg = 0;     
    interval = interval*10;       // Lückenpausenzeitkorrektur
    index = 0;
  }
  else {
    interval = PAUSE;
  }

  if (index == ANZAHL_LED) {
    ++index;
  }
}

Hallo,

wenn ich class von enum weglasse funktioniert auch alles mit enum. Wie im Bsp. von Michael. Nur verliert man dann die Typsicherheit und der Syntax sieht komplizierter aus. Ich bleibe bei struct mit constexpr.

Hallo,

mir lässt das noch keine Ruhe. Habe die Konstanten in 2x enum aufgeteilt innerhalb des struct. Soweit so gut - funktioniert. Was ich jedoch immer noch nicht verstehe ist, sobald ich class zur Typsicherheit ergänze meckert er meine shift_out Methode an.

error: cannot convert 't_setup::digit' to 'const byte {aka const unsigned char}' in initialization
could not convert 'D0' from 't_setup::digit' to 'int'
no matching function for call to 't_setup::shift_out(t_setup::digit, int)'

Der angegebene Datentyp ist überall byte. Warum hat er damit ein Problem?

/*
  IDE 1.8.7
  Arduino Mega2560

  MAX7221
  DATA: MSB first
        D15 ...D12  bleibt unbenutzt
        D11 ... D8  Registeradresse
        D7  ... D0  Daten
*/

struct t_setup
{
  const byte CLK  = 24;
  const byte CS   = 25;
  const byte MOSI = 26;

  enum seg :byte {
    DP = 128,     // Bit.7 - Datenblatt Tabelle 6
     A  = 64,
     B  = 32,
     C  = 16,
     D  =  8,
     E  =  4,
     F  =  2,
     G  =  1      // Bit.0
  };
  
  enum class digit :byte {       // class ergänzt
    D0 = 0x01,
    D1 = 0x02,
    D2 = 0x03,
    D3 = 0x04
  };
    
  void shift_out( byte adresse, byte daten )
  { // Bits rausschieben, MSB first
    uint16_t data = 0;
    data = (data | adresse) << 8;
    data = data | daten;

    digitalWrite(CS, LOW);

    for (uint8_t i = 0; i < 16; i++) {
      if (data & 0x8000) {              // nur oberstes Bit betrachten
        digitalWrite(MOSI, HIGH);       // und Datenleitung entsprechend setzen
      }
      else {
        digitalWrite(MOSI, LOW);
      }
      digitalWrite(CLK, HIGH);          // Takt erzeugen
      data = data << 1;                 // nächstes Bit links schieben
      digitalWrite(CLK, LOW);
    }
    
    digitalWrite(CS, HIGH);
  }

  void clear_all ()
  { 
    shift_out(digit::D0, 0);   // löschen
    shift_out(digit::D1, 0);   // löschen
    shift_out(digit::D2, 0);   // löschen
    shift_out(digit::D3, 0);   // löschen
  }
  
} MAX7221; 


struct t_led
{
  const byte digit;
  const byte segment;
};


const t_led LED[] = {
  {MAX7221.digit::D0, MAX7221.seg::A},  // LED  1
  {MAX7221.digit::D1, MAX7221.seg::A},
  {MAX7221.digit::D2, MAX7221.seg::A},
  {MAX7221.digit::D3, MAX7221.seg::A},
  {MAX7221.digit::D0, MAX7221.seg::B},  // LED  5
  {MAX7221.digit::D1, MAX7221.seg::B},
  {MAX7221.digit::D2, MAX7221.seg::B},
  {MAX7221.digit::D3, MAX7221.seg::B},
  {MAX7221.digit::D0, MAX7221.seg::C},
  {MAX7221.digit::D1, MAX7221.seg::C},  // LED 10
  {MAX7221.digit::D2, MAX7221.seg::C},
  {MAX7221.digit::D3, MAX7221.seg::C},
  {MAX7221.digit::D0, MAX7221.seg::D},
  {MAX7221.digit::D1, MAX7221.seg::D},
  {MAX7221.digit::D2, MAX7221.seg::D},  // LED 15
  {MAX7221.digit::D3, MAX7221.seg::D},
  {MAX7221.digit::D0, MAX7221.seg::E},
  {MAX7221.digit::D1, MAX7221.seg::E},
  {MAX7221.digit::D2, MAX7221.seg::E},
  {MAX7221.digit::D3, MAX7221.seg::E},  // LED 20
  {MAX7221.digit::D0, MAX7221.seg::F},
  {MAX7221.digit::D1, MAX7221.seg::F},
  {MAX7221.digit::D2, MAX7221.seg::F},
  {MAX7221.digit::D3, MAX7221.seg::F},
  {MAX7221.digit::D0, MAX7221.seg::G},  // LED 25
  {MAX7221.digit::D1, MAX7221.seg::G},
  {MAX7221.digit::D2, MAX7221.seg::G},
  {MAX7221.digit::D3, MAX7221.seg::G},
  {MAX7221.digit::D0, MAX7221.seg::DP},
  {MAX7221.digit::D1, MAX7221.seg::DP}, // LED 30
  {MAX7221.digit::D2, MAX7221.seg::DP}  // LED 31
};

const byte ANZAHL_LED = sizeof(LED) / sizeof(t_led);


void setup() {
  Serial.begin(9600);
  digitalWrite(MAX7221.CS, HIGH);
  pinMode(MAX7221.CS, OUTPUT);
  pinMode(MAX7221.CLK, OUTPUT);
  pinMode(MAX7221.MOSI, OUTPUT);

  MAX7221.shift_out(0x0C, 1);   // aufwecken
  MAX7221.clear_all();          // löschen
  MAX7221.shift_out(0x0F, 1);   // Test ein
  delay(500);
  MAX7221.shift_out(0x0F, 0);   // Test aus
  MAX7221.shift_out(0x0A, 9);   // Intensität
  MAX7221.shift_out(0x0B, 3);   // Scan Limit
  MAX7221.shift_out(0x09, 0);   // No Decode Mode
}


void loop() {

  radar();
  
}


// ****** Funktionen ******

void radar ()
{
  static unsigned long last_ms = 0;
  const unsigned int PAUSE = 100; 
  static unsigned int interval = PAUSE;
  static byte index = 0;
  static byte digit0_seg = 0;
  static byte digit1_seg = 0;
  static byte digit2_seg = 0;
  static byte digit3_seg = 0;

  if (millis() - last_ms < interval) return;
  
  last_ms = millis();

  if (index < ANZAHL_LED) {
   
    switch (LED[index].digit) {
      case MAX7221.digit::D0:                                     // wenn bestimmtes Digit aktiv, dann
            digit0_seg = digit0_seg | LED[index].segment;         // Segmentwert vom Digit aufaddieren
            MAX7221.shift_out(LED[index].digit, digit0_seg);      // und ausgeben
            break;
      case MAX7221.digit::D1:
            digit1_seg = digit1_seg | LED[index].segment;
            MAX7221.shift_out(LED[index].digit, digit1_seg);
            break;
      case MAX7221.digit::D2:
            digit2_seg = digit2_seg | LED[index].segment;
            MAX7221.shift_out(LED[index].digit, digit2_seg);
            break;
      case MAX7221.digit::D3:
            digit3_seg = digit3_seg | LED[index].segment;
            MAX7221.shift_out(LED[index].digit, digit3_seg);
            break;
      default:
            Serial.print(F("case error ")); Serial.print('\t');
            Serial.println(LED[index].digit);
            break;      
    }
    
    ++index;
  }
  
  
  if (index > ANZAHL_LED) {
    MAX7221.clear_all();    
    digit0_seg = 0;
    digit1_seg = 0;
    digit2_seg = 0;
    digit3_seg = 0;     
    interval = interval*10;       // Lückenpausenzeitkorrektur
    index = 0;
  }
  else {
    interval = PAUSE;
  }

  if (index == ANZAHL_LED) {
    index++;
  }
}

Strongly typed enums sind nicht in Integer konvertierbar. Eben (unter anderem) dadurch wird ja die Typ-Sicherheit garantiert. Soweit hatte ich nicht gedacht, dass du hier eigentlich die Integer-Werte willst. Dafür sind enums nicht wirklich gedacht und diese Variante verhindert dass man sie dazu missbraucht.

Für reine Zustände aber z.B. wie der Wert dahinter nicht interessiert ist das sehr gut

Hallo,

das macht nichts, zwingt es einem sich damit nochmal genauer zu beschäftigen. :slight_smile:

Ich möchte nicht zwingend Integer. Ich habe überall Datentyp Byte angegeben. Der Wertebereich Byte ist hierfür ausreichend.
Das class Typsicherheit darstellt ist soweit klar. Das er in Integer konvertieren möchte ist mir unklar. Warum will er in Integer konvertieren?
Meine enums sind mit Datentyp Byte initialisiert. Auch meine Methode shift_out hat nur Byte Parameter.
Meine Denkweise lautet. Alles was damit verwendet wird hat den Datentyp Byte. Alles i.O.
Vertut man sich und verwendet was anderes wie Byte, weil man eine Funktion/Methode( int adresse, long daten )
geschrieben hat, dann sollte der Compiler meckern.

Selbst Serial.print funktioniert mit enum class nicht. Man muss selbst zum debuggen static casten. Das verkompliziert alles, es erleichtert in meinen Augen nichts. Das bringt mein Verständnis generell durcheinander, weil ich bis zu einem Punkt dachte ich hätte enum class verstanden. Mit Serial.print will ich ja keine Variable verändern, nur ausgeben. Serial.print schluckte bisher alle Datentypen. Warum eine von enum class nicht mehr?

Wozu dient eine optionale Datentypangabe von enum class :typ {} dann überhaupt?
Legt der Compiler doch sowieso intern selbst fest wenn man kein Zugriff auf den dahinterliegenden Wert hat.
Wie möchte man sein Programm sinnvoll überprüfen wenn man es nur auf noch kompliziertererweise tun kann?
Es erzeugt bei mir leider mehr Fragen wie Antworten. An der Stelle steigt mein Verständnis für enum class aus. :confused:

hier ein Bsp.

/*
 * Der C++ Programmierer - Ulrich Breymann - 5. Auflage - Seite 85
 * http://en.cppreference.com/w/cpp/language/enum
 * https://www.codesdope.com/cpp-enum-class/
 */
 
enum class states :byte {STOP, START};    // Steuerzustände
states phase_detect = states::STOP;


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

void loop() {

  switch (phase_detect) {
    case states::STOP:  
                phase_detect = states::START;
                Serial.print("case STOP"); 
                break;
    case states::START: 
                phase_detect = states::STOP;
                Serial.print("case START");
                break;     
  }

  Serial.print('\t');  

  int n = static_cast<int>(phase_detect);
  Serial.println(n);
  //Serial.println(phase_detect);  // ohne 'class' gehts
}

Das er in Integer konvertieren möchte ist mir unklar.

Das sollte doch offensichtlich sein wenn du sowas machst:

struct t_led
{
  const byte digit;
  const byte segment;
};


t_led LED[] = {
  {MAX7221.DIGIT0, MAX7221.SEGA},  // LED  1
  {MAX7221.DIGIT1, MAX7221.SEGA},  // LED  2
};

Meine enums sind mit Datentyp Byte initialisiert.

: byte gibt die Größe an (ohne das wäre es int). Bei enum class ist das aber nicht der eigentliche Datentyp. Der darunterliegende Datentyp ist etwas versteckt. In Standard C++ gibt es in der STL auch ein Template um daran zukommen

Selbst Serial.print funktioniert mit enum class nicht

Weil das kein Integer ist...

Serial.print schluckte bisher alle Datentypen. Warum eine von enum class nicht mehr?

Schau dir an wie die Print Klasse funktioniert. Es gibt einfach keine Methode dafür. Und sie sind halt nicht in Integer konvertierbar

Nachtrag:

Es ist auch nicht schwer ein einfaches Template zu schreiben das enum class ausdrucken kann:

Templates.h:

#pragma once

template <typename E>
constexpr int enumVal(E e)
{
   return static_cast<int>(e);
}
#include "Templates.h"

enum class Test1 { test1, test2, test3, };
enum class Test2 { test1 = 5, test2 = 6, test3 = 7 };

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

  Serial.println(enumVal(Test1::test1));
  Serial.println(enumVal(Test1::test2));
  Serial.println(enumVal(Test1::test3));
  Serial.println();
  Serial.println(enumVal(Test2::test1));
  Serial.println(enumVal(Test2::test2));
  Serial.println(enumVal(Test2::test3));
}

void loop()
{
}

Es findet halt keine Überprüfung statt ob das wirklich ein enum class ist

mir lässt das noch keine Ruhe.

(ich weiß gar nicht ob meine Hilfe hier erwünscht ist)

Und ich kann nicht testen, da ich einen solchen Baustein nicht habe.
Deswegen müssen meine Vorschläge recht abstrakt bleiben!

Eins deiner Probleme scheint mir zu sein, dass du mehrere Schritte auf einmal gehen möchtest.

Genannt sei:
Der Unterschied zwischen (Daten)Modell und (Daten)Struktur.

Du möchtest, bzw. stolperst, über die Struktur.
Dabei habe ich noch nicht mal verstanden, was du modellieren möchtest.

Vergleichbares Beispiel:
Ich bemerke, dass ich in einem Programm eine Liste brauche.
Die Liste ist ein abstraktes Datenmodell.

Um mit Listen im Programm umgehen zu können, muss ich das in Strukturen gießen.
z.B. in die Datenstruktur: Die Verkettete Liste.

Das ist der Weg, von Modell zur Struktur.

Ich wäre dir also sehr dankbar, wenn du erst das Modell beschreiben würdest, bevor wir im Detail auf die konkreten Strukturen eingehen.
Was willst du wirklich erreichen?

z.B. Punktmatrix oder 7 Segment Display.
Die Modelle unterscheiden sich grundlegend.

Natürlich kann man beides auch getrennt von einander abhandeln, und das dann hinter einer gemeinsamen Fassade verbergen.
Wie bei einem Ärztehaus, wo sich Zahnarzt, Orthopäde und Augenarzt ein Treppenhaus teilen. (und im Untergeschoss ist noch eine Apotheke)

In Sachen SPI...
Du nutzt SoftSPI mit einer selbstgebauten shift_out() (ca50kHz), obwohl die meisten AVR Hardware SPI haben. und damit auch an die möglichen 10MHz ran kommen. Auch gibt es ja auch noch auf allen(?) Arduinos shiftOut().
Hier könnte eine "Adapter" Schnittstelle, zu den verschiedenen Treibern, Sinn machen.

Ins besondere auch unter dem Blickwinkel, dass mehrere MAX7221 verkettet vorliegen können.

struct t_setup

{
  const byte CLK  = 24;
  const byte CS  = 25;
  const byte MOSI = 26;
  const byte SEGDP = 128;    // Bit.7 - Datenblatt Tabelle 6
  const byte SEGA  = 64;
  const byte SEGB  = 32;
  const byte SEGC  = 16;
  const byte SEGD  =  8;
  const byte SEGE  =  4;
  const byte SEGF  =  2;
  const byte SEGG  =  1;      // Bit.0
  const byte DIGIT0 = 0x01;  // Datenblatt kompatible Schreibweise
  const byte DIGIT1 = 0x02;
  const byte DIGIT2 = 0x03;
  const byte DIGIT3 = 0x04;
....

Hier scheinen mir Dinge zusammengefasst zu sein, welche nicht zusammen gehören.
Erinnert mich an den Begriff "Gottklasse" oder "Big hairy object".
(ein beliebter OOP beginner Fehler)
Oft ist an solchen Punkten "sinnvoll aufteilen und delegieren" der bessere Weg.

// Datenblatt kompatible Schreibweise

Das könnte der falsche Ansatz sein.
Im Datenblatt ok, aber hier könnte es den Blick auf die wesentlichen/notwendigen Abstraktionen versperren.

const t_led LED[] = {

{MATRIX.DIGIT0, MATRIX.SEGA},  // LED  1
  {MATRIX.DIGIT1, MATRIX.SEGA},
  {MATRIX.DIGIT2, MATRIX.SEGA},
  {MATRIX.DIGIT3, MATRIX.SEGA},
  {MATRIX.DIGIT0, MATRIX.SEGB},  // LED  5
  {MATRIX.DIGIT1, MATRIX.SEGB},
  {MATRIX.DIGIT2, MATRIX.SEGB},
  {MATRIX.DIGIT3, MATRIX.SEGB},
  {MATRIX.DIGIT0, MATRIX.SEGC},
  {MATRIX.DIGIT1, MATRIX.SEGC},  // LED 10
  {MATRIX.DIGIT2, MATRIX.SEGC},
  {MATRIX.DIGIT3, MATRIX.SEGC},
  {MATRIX.DIGIT0, MATRIX.SEGD},
  {MATRIX.DIGIT1, MATRIX.SEGD},
  {MATRIX.DIGIT2, MATRIX.SEGD},  // LED 15
  {MATRIX.DIGIT3, MATRIX.SEGD},
  {MATRIX.DIGIT0, MATRIX.SEGE},
  {MATRIX.DIGIT1, MATRIX.SEGE},
  {MATRIX.DIGIT2, MATRIX.SEGE},
  {MATRIX.DIGIT3, MATRIX.SEGE},  // LED 20
  {MATRIX.DIGIT0, MATRIX.SEGF},
  {MATRIX.DIGIT1, MATRIX.SEGF},
  {MATRIX.DIGIT2, MATRIX.SEGF},
  {MATRIX.DIGIT3, MATRIX.SEGF},
  {MATRIX.DIGIT0, MATRIX.SEGG},  // LED 25
  {MATRIX.DIGIT1, MATRIX.SEGG},
  {MATRIX.DIGIT2, MATRIX.SEGG},
  {MATRIX.DIGIT3, MATRIX.SEGG},
  {MATRIX.DIGIT0, MATRIX.SEGDP},
  {MATRIX.DIGIT1, MATRIX.SEGDP}, // LED 30
  {MATRIX.DIGIT2, MATRIX.SEGDP}  // LED 31
};

Ein Datenfeld, welches sich eigentlich auch über Zugriffsmethoden eliminieren lassen würde, denke ich mal. Der Verzicht auf die Datenblattkonformen Bezeichner würde das ermöglichen.

   switch (LED[index].digit) {

case MATRIX.DIGIT0:                                        // wenn bestimmtes Digit aktiv, dann
            digit0_seg = digit0_seg | LED[index].segment;        // Segmentwert vom Digit aufaddieren
            MAX7221.shift_out(LED[index].digit, digit0_seg);      // und ausgeben
            break;
      case MATRIX.DIGIT1:
            digit1_seg = digit1_seg | LED[index].segment;
            MAX7221.shift_out(LED[index].digit, digit1_seg);
            break;
      case MATRIX.DIGIT2:
            digit2_seg = digit2_seg | LED[index].segment;
            MAX7221.shift_out(LED[index].digit, digit2_seg);
            break;
      case MATRIX.DIGIT3:
            digit3_seg = digit3_seg | LED[index].segment;
            MAX7221.shift_out(LED[index].digit, digit3_seg);
            break;
      default:
            Serial.print(F("case error ")); Serial.print('\t');
            Serial.println(LED[index].digit);
            break;     
    }

Auch hier scheint mir die Ähnlichkeiten der Fälle (Codeduplikate) so groß, dass eine Reduzierung (einheitliche Behandlung) möglich ist. Natürlich nur unter Verzicht der durchnummerierten Variablen/Konstanten


Da ich um das aufsplitten, was mir nachwievor nicht gefällt, wohl nicht drum herum komme,

Gerade solche Probleme lassen sich (vermutlich) leichter beheben, wenn man eine Schicht tiefer anfängt, die Dinge zu modellieren.
Andererseits ist aufsplitten nur dann böse, wenn es mit mit dem erzeugen von Duplikaten einhergeht.