Tipps für Klassen in einer Library (HT16K33) gesucht

Wird länger sorry:
Ausgangslage
Ich bastle aktuell an einer Display Library für den HT16K33 (I2C 16x8 LED Treiber von Holtek). Gibts unter anderem von Adafruit (Adafruit LED Backpack Library). Ich wollte aber eine Lib die voll auf die print-Klasse zurückgreift.

Aktuell werden vier verschiedene Hardwarevarianten unterstützt (7 Segment und 14 Segment Displays, mit 4 oder 8 Stellen), die von einer Basisklasse erben und dann ihre Hardware-Besonderheiten haben (andere Zeichensätze, Stellenanzahl, mit oder ohne zusätzlichem Doppelpunkt).
Objekt anlegen, display.begin, display.print ... funktioniert auch alles soweit wie gewünscht.

Dann habe noch eine Methode für die Ausgabe eines Scrolltext geschrieben - display.setScrolltext() - und ein paar Setter dazu.
Zum Scrollen brauche ich in meinem Objekt noch "ein paar zusätzliche private Variablen". Jetzt habe ich festgestellt, dass mir dabei der Speicherbedarf auch dann anwächst, wenn ich die Scrolltext-Methoden eigentlich gar nicht verwende. Soweit verständlich aber nicht schön.

Frageblock 1 Speicherbedarf reduzieren:
Wie bekomme ich den Speicherbedarf wieder runter, bzw. meine Lib dazu, nur jene Variablen beim kompilieren mitzunehmen, die tatsächlich notwendig sind?
das sind meine Problemkinder:

  // alle nur wegen scroll
    uint16_t intervall = 400;                              // shift intervall
    uint16_t wait = 3000;                                  // wait time after last letter
    uint32_t previousMillis = 0;                           // stores last print out
    uint8_t counter = 1;                                   // used as start position
    static const uint8_t scrollBufferSize = 65;            // maximum size of scroll text
    char myText[scrollBufferSize] = "\0";                  // buffer for the scrol text
    uint32_t rounds = 0;                                   // how many scroll overs where already performed with the current scroll text

in die .setScrolltext() kann ich sie nicht geben, sonst kennen sie die anderen setter nicht. Weiters möchte keine Precompiler #ifs in der .h setzen müssen. Das ganze soll ausschließlich "vom Sketch aus" steuerbar sein. Was im Sketch steht, soll kompiliert werden - aber nur was ich brauche.

Geht das nur mit weiteren child-Klassen die dann eben erst scrollen können? Gefällt mir halt auch nicht so besonders weil ich mir damit die Klassen wieder verdopple und das ja kaum jemanden erklärbar ist oder?

Was wäre da der richtige Weg?

Frageblock 2 Naming things ... im loop()
".begin()" hat sich ja imho fast durchgesetzt gegenüber .init() für Methoden die üblicherweise ins Setup() kommen. Nur was verwendet man am besten für Methoden die laufend im loop aufgerufen werden sollen?
.tick(), .run(), .handle(), .handler(), .update() ... gibts da wo Guidelines, Best practices wie derartige Methoden heißen sollen?

Frage 3: Wozu überhaupt ... im loop() etwas aufrufen?
Gäbe es eine Möglichkeit, eine Methode im loop() aufzurufen, ohne dass man das im Sketch hinschreiben müsste. Also das mit dem constructor oder mit dem .begin() feststeht, dass eine bestimmte .run() methode immer am Anfang oder am Ende von loop() einmal aufgerufen wird? Quasi wie ein Callback aus dem (Ende, Beginn von) Loop - wenn es sowas gibt ...

übrigens, um diese Displays geht es:


(4 Module bilden 3 logische Displays, alles über eine Lib).

Die ganze Lib mit allen Beispielen im ZIP (eigentlich sollten alle Beispiele funktionieren).

NoiascaHt16k33.zip (20.7 KB)

Für die Nachwelt:

hier gibts jeweils die aktuell gewartete HT16K33 Library
https://werner.rothschopf.net/201909_arduino_ht16k33.htm

noiasca:
Weiters möchte keine Precompiler #ifs in der .h setzen müssen. Das ganze soll ausschließlich “vom Sketch aus” steuerbar sein. Was im Sketch steht, soll kompiliert werden - aber nur was ich brauche.

In der Bibliothek I2C_EEPROM bin ich gerade über sowas gestolpert:

Im Sketch:

// Debugausgabe Stream festlegen
#define EEP_DEBUG Serial // Auskomentieren wenn Testphase fertig
#include <I2C_EEPROM.h>

In der Bibliothek:

#ifdef EEP_DEBUG
    #define eepdebug(...)   EEP_DEBUG.print(__VA_ARGS__)
    #define eepdebugln(...) EEP_DEBUG.println(__VA_ARGS__)
#else
    #define eepdebug(...)
    #define eepdebugln(...)
#endif

Verwendung:

  eepdebug("pageRead Addressueberschreitung ");
  eepdebugln(address);

Damit steuerst Du im Sketch, was in der Bibliothek gemacht werden soll.

EDIT 10.9.19 20:46: “Verwendung” ergänzt.

Das ist nach meinem Verständnis nicht das, was er machen will.
Das ist ein statisches deaktivieren von Debugausgaben (es gibt bessere Lösungen dafür).

Er will Teile der Klasse bei Nichtnutzung im Sketch (und dort besonders die privaten Membervariablen) nicht ins Kompilat überführen.
Da sehe ich auf den ersten Ansatz nur eine Kindklasse mit Scroll, wenn man es denn braucht.
Evtl. haben unsere OOP-Gurus noch eine andere Idee.

Gruß Tommy

agmue, ich bekomm das #define nicht rüber ...

im Sketch

#define HT16K33_EXTERN 1

in der .h

#ifdef HT16K33_EXTERN
  #define HT16K33_SCROLL 1
#else
  #define HT16K33_SCROLL 0
#endif

und dann die Code Abschnitte in .h und .cpp jeweils unter

#if HT16K33_SCROLL == 1
...
#endif

das wirft mir nur massig warnings und am Schluss einen Error

Using precompiled core: C:\Users\werner\AppData\Local\Temp\arduino_cache_223485\core\core_arduino_avr_uno_0c812875ac70eb4a9b385d8fb077f54c.a
Linking everything together...
"C:\\Program Files (x86)\\Arduino\\hardware\\tools\\avr/bin/avr-gcc" -Wall -Wextra -Os -g -flto -fuse-linker-plugin -Wl,--gc-sections -mmcu=atmega328p -o "C:\\Users\\werner\\AppData\\Local\\Temp\\arduino_build_286890/21_scrolltext.ino.elf" "C:\\Users\\werner\\AppData\\Local\\Temp\\arduino_build_286890\\sketch\\21_scrolltext.ino.cpp.o" "C:\\Users\\werner\\AppData\\Local\\Temp\\arduino_build_286890\\libraries\\NoiascaHt16k33\\NoiascaHt16k33.cpp.o" "C:\\Users\\werner\\AppData\\Local\\Temp\\arduino_build_286890\\libraries\\Wire\\Wire.cpp.o" "C:\\Users\\werner\\AppData\\Local\\Temp\\arduino_build_286890\\libraries\\Wire\\utility\\twi.c.o" "C:\\Users\\werner\\AppData\\Local\\Temp\\arduino_build_286890/..\\arduino_cache_223485\\core\\core_arduino_avr_uno_0c812875ac70eb4a9b385d8fb077f54c.a" "-LC:\\Users\\werner\\AppData\\Local\\Temp\\arduino_build_286890" -lm
C:\daten\myrepository\Arduino\libraries\NoiascaHt16k33\src/NoiascaHt16k33.h:297:7: warning: type 'struct Noiasca_ht16k33' violates one definition rule [-Wodr]

 class Noiasca_ht16k33 : public Print {                     // Base class for all HT16K33 Displays

       ^

C:\daten\myrepository\Arduino\libraries\NoiascaHt16k33\src\NoiascaHt16k33.h:297:7: note: a different type is defined in another translation unit

 class Noiasca_ht16k33 : public Print {                     // Base class for all HT16K33 Displays

       ^

C:\daten\myrepository\Arduino\libraries\NoiascaHt16k33\src/NoiascaHt16k33.h:339:26: note: the first difference of corresponding definitions is field 'intervall'

     uint16_t intervall = 400;                              // shift intervall

                          ^

C:\daten\myrepository\Arduino\libraries\NoiascaHt16k33\src\NoiascaHt16k33.h:297:7: note: a type with different number of fields is defined in another translation unit

 class Noiasca_ht16k33 : public Print {                     // Base class for all HT16K33 Displays

       ^

C:\daten\myrepository\Arduino\libraries\NoiascaHt16k33\src/NoiascaHt16k33.h:368:7: warning: type 'struct Noiasca_ht16k33_hw_14' violates one definition rule [-Wodr]

 class Noiasca_ht16k33_hw_14 : public Noiasca_ht16k33 {     // 14 Segment Display

       ^

C:\daten\myrepository\Arduino\libraries\NoiascaHt16k33\src\NoiascaHt16k33.h:368:7: note: a type with different bases is defined in another translation unit

 class Noiasca_ht16k33_hw_14 : public Noiasca_ht16k33 {     // 14 Segment Display

       ^

C:\daten\myrepository\Arduino\libraries\NoiascaHt16k33\src/NoiascaHt16k33.h:378:7: warning: type 'struct Noiasca_ht16k33_hw_14_4' violates one definition rule [-Wodr]

 class Noiasca_ht16k33_hw_14_4 : public Noiasca_ht16k33_hw_14 {   // 14 Segment Display with 4 digits only

       ^

C:\daten\myrepository\Arduino\libraries\NoiascaHt16k33\src\NoiascaHt16k33.h:378:7: note: a type with different bases is defined in another translation unit

 class Noiasca_ht16k33_hw_14_4 : public Noiasca_ht16k33_hw_14 {   // 14 Segment Display with 4 digits only

       ^

C:\Users\werner\AppData\Local\Temp\ccaehqpn.ltrans0.ltrans.o: In function `setup':

C:\Users\werner\AppData\Local\Temp\arduino_modified_sketch_256463/21_scrolltext.ino:40: undefined reference to `Noiasca_ht16k33::setScrolltext(char const*)'

C:\Users\werner\AppData\Local\Temp\ccaehqpn.ltrans0.ltrans.o: In function `loop':

C:\Users\werner\AppData\Local\Temp\arduino_modified_sketch_256463/21_scrolltext.ino:52: undefined reference to `Noiasca_ht16k33::scroll()'

collect2.exe: error: ld returned 1 exit status

Bibliothek NoiascaHt16k33 in Version 1.0.0 im Ordner: C:\daten\myrepository\Arduino\libraries\NoiascaHt16k33  wird verwendet
Bibliothek Wire in Version 1.0 im Ordner: C:\Program Files (x86)\Arduino\hardware\arduino\avr\libraries\Wire  wird verwendet
exit status 1
Fehler beim Kompilieren für das Board Arduino/Genuino Uno.

andererseits wenn ich am Beginn meiner .h testweise zusätzlich ein

#define HT16K33_EXTERN

definiere, kommt zwar ein Redifine - Warning, aber HT16K33_SCROLL wird auf 1 gesetzt und der rest kompiliert.

(also hab ich wohl alle #iif richtig gesetzt).
ich checks nicht ... (will aber auch nicht so recht glauben, dass sowas überhaupt funktionieren kann)

im Sketch
#define HT16K33_EXTERN 1

Kannst du machen.

ich bekomm das #define nicht rüber …

in die *.cpp.

Ja, das ist so!

Denn:
Alle *.ino werden zusammengematscht und werden zu einer *.cpp
Jede *.cpp , *.C oder *.S Datei, ist eine eigene Übersetzungseinheit.

Preprozessordirektiven teleportieren nicht zwischen Übersetzungseinheiten.
Nicht freiwillig.


Warum baust du keine *.h only Lib?

Für die Übersicht: Für jede Klasse eine *.h Datei.

a) danke. abgehakt.

b) Warum baust du keine *.h only Lib?

?
Wie löst das mein Problem? ich brauch dann dennoch eine eigene Klasse sobald ich in den jeweiligen Objekten diesen scroller haben will oder?

edit ... Idee verworfen

Wie löst das mein Problem?

Welches?

Dieses?

Preprozessordirektiven teleportieren nicht zwischen Übersetzungseinheiten.
Wo keine *.cpp, da auch keine Übersetzungseinheit.


Im Moment setzt du stark auf Vererbung.
Das ist aber nicht die einzige mögliche Beziehung zwischen Klassen/Objekten, nur eine der möglichen.
Manchmal ist die Komposition (oder die ähnliche Aggregation) oder der Dekorierer die bessere Wahl.


ich brauch dann dennoch eine eigene Klasse sobald ich in den jeweiligen Objekten diesen scroller haben will oder?

Ja und?
Ist es wirklich so?
Und wenn ja: Was gibts dagegen einzuwenden?
Ungenutzte Klassen fressen doch kein Brot.

Wenn es zu kompliziert wird, kannste es ja immer noch hinter einer Fassade verstecken.


Ich muss zugeben, dass meine Vorschläge nicht sonderlich konkret sind. Zum Teil liegt es daran, dass ich deinen Plan noch nicht (ganz) verstanden habe, und auch diesen Baustein gar nicht kenne.

Ja und?
Was gibts dagegen einzuwenden?
Ungenutzte Klassen fressen doch kein Brot.

als Anwender finde ich es halt eingenartig, dass ich wegen der Nutzung einer weiteren/anderen Methode, mein Objekt under einer anderen Klasse anlegen soll.

Die Links muss ich erst mal verdauen.

Das ist aber wohl der einzige Weg, wie Du mit/ohne Scrolling sauber trennen kannst.
Das hatte ich in der Art ja auch schon geschrieben.

Gruß Tommy

Hallo,

deine Scrollingvariablen belegen 78 Byte. Ist das den Aufwand wert?
Wenn doch, dann schreib sie in die .cpp als allgemeine Variablen, also nicht in eine Klasse.
Dann bin ich der Meinung das dann nur das kompiliert wird was tatsächlich benötigt wird.

Edit:
habe das soeben getestet, habe ein 255 großes Bytes char array in meiner Header only Lib allgemein angelegt, an der Kompilierungsausgabe hat sich nichts geändert. Das gleiche wird passieren wenn das in der .cpp "privat" bleibt.

wird so sein, aber dann habe ich nur einen char buffer (counter, previousMillis ...) für alle eventuell angelegten display Objekte ... das funktioniert also ab dem zweiten display Objekt nicht mehr.

Ob es die 78 Byte Wert sind - imho am Uno auf alle Fälle.
(Es geht mir auch ein wenig um Grundlagenforschung, wie sehr das verwenden/vererben von der print-Klasse mit unter resourcenschonender ist, als "print" funktionen/methoden "komplett neu" in seinen Klassen zu implementieren. Also ja - es geht mir schon auch um flash und RAM ...)

(mit den Pattern bin ich noch nicht durch, still ongoing ...)

Emotional kann ich aber mittlerweile fast mit separaten Klassen leben ^^

Tommy56:
Das ist nach meinem Verständnis nicht das, was er machen will.

"Ich möchte mit Flip-Flops den Berg erklimmen, welche sind am besten geeignet?"

Meine Antwort: "Die wie Bersteigerschuhe aussehen." Diese Antwort wird nicht erwartet, ist aus meiner Sicht aber dennoch sinnvoll.

Tommy56:
Das ist ein statisches deaktivieren von Debugausgaben (es gibt bessere Lösungen dafür).

Ich bin hier aktiv, um zu lernen. Daher antworte ich im Rahmen meines Wissens und freue mich über bessere Lösungen :slight_smile:

noiasca:
wird so sein, aber dann habe ich nur einen char buffer (counter, previousMillis ...) für alle eventuell angelegten display Objekte ... das funktioniert also ab dem zweiten display Objekt nicht mehr.

stimmt auch wiederum

agmue:
Ich bin hier aktiv, um zu lernen. Daher antworte ich im Rahmen meines Wissens und freue mich über bessere Lösungen :slight_smile:

Die Lösung ist auch nicht von mir, sondern stammt auch hier aus dem Forum. Sie erzeugt weniger Schreiberei und vor allem sind im Sketch die ganzen #if/#endif-Klammern nicht notwendig.
Wie Du die Debugmakros nennst, ist Geschmachssache.

Achtung printf ist für Serial auf UNO nicht definiert!

// 1 oder 0 zum Ein-/Ausschalten
#define DEBUG 1

// Bei DEBUG 0 werden die Ausgaben nicht einkompiliert
#if (DEBUG == 0)
  #define dbgp(...)     // Serial.print
  #define dbgpn(...)    // Serial.println
#if defined ESP8266	|| defined ESP32
	#define dbgpf(...)    // Serial.printf nur auf ESP definiert
#endif	
  #define dbgbeg...)    // Serial.begin
#else
  #define dbgp(...) Serial.print(__VA_ARGS__)
  #define dbgpn(...) Serial.println(__VA_ARGS__)
#if defined ESP8266	
	#define dbgpf(...) Serial.printf(__VA_ARGS__)
#endif
  #define dbgbeg(...) Serial.begin(__VA_ARGS__)
#endif

void setup() {
  dbgbeg(115200);
#if defined ESP8266	|| defined ESP32
  dbgpf("\nEine Zahl: %02d\n\n",3);
#endif
  dbgp("Hallo ");
  dbgpn("Arduino");
}

void loop() {}

Gruß Tommy

Das ist doch genau das gleiche Prinzip. Kommt halt noch dazu dass man die serielle Schnittstelle gar nicht initialisiert was auch Speicher spart wenn man sie sind nicht braucht

vor allem sind im Sketch die ganzen #if/#endif-Klammern nicht notwendig.

Du verwechselst das mit der Variante die kein Makro verwendet und einfach das direkt in den Code schreibt:

#ifdef DEBUG
Serial.println("....");
#endif DEBUG

Genau diese Klammern meine ich, dass man die dann nicht mehr schreiben muss und damit der Quelltext übersichtlicher wird.

Gruß Tommy

Das ist aber in dem Code in #2 nicht so. Sondern da steht praktisch genau was du auch hast

Hallo,

verstehe ich jetzt auch nicht so ganz wo der Unterschied sein soll.

 dbgp("Hallo ");
 dbgpn("Arduino");
 
 Serial.print("Hallo ");
 Serial.println("Arduino");

@Serenifly: Du hast Recht. Was habe ich denn da gelesen?
Da war ich wohl nicht ganz bei der Sache. Sorry.

Gruß Tommy