MAX7219/MAX721 Library Optimierung HW-SPI (Mehrere 7 Segment an einem Arduino)

edit:
Die nachfolgendenen Beiträge wurden von diesem Thread abgespalten:

Die Beiträge in diesem Thread behandeln Aktivitäten Rund um die Weiterentwicklungen der NoiascaLedControl
endOfEdit.

Points and dots are somehow anoying. As printing to the display is done character by character we have to find a way how to "activate" the decimal point of a previous printed digit. This is done by using an (existing) status array. The library stores each digit (up to 64) in RAM. Therefore we can "reprint" the previous digit with an activated dot.

Das Array ist bereits in der originalen Library auf die ich aufbaue. Ich will damit nur ausdrücken, dass meine Version nicht mehr Speicher benötigt als die originale. Ich verwende genau dieses Array dann um die vorhergehnde Position mit aktiviertem Punkt noch mal an die vorhergehende Position zu drucken. Du als Anwender bekommst nichts davon mit - außer du siehst dir die Lib an.

Wenn du eine tiefere Erklärung dazu willst: der Hintergrund ist wie die Print.h Library arbeitet. da wird nämlich Grundsätzlich Position für Position mittels write rausgeschubbst. Kommt nun ein Punkt dann muss man beim 7Segment eben noch mal zurück auf die vorhergehende Position und die ganze Position noch mal drucken.

Im Prinzip brauchst dir darüber aber keine Gedanken machen. Drucke so wie du es von der Seriellen Schnittstelle gewohnt bist.

Hi

Eine schöne Lib, habe mir die ZIP auch Mal gegönnt. (Link zu #8 im Ursprungs-Thread, wo die Lib verlinkt ist)
Danke dafür - die Sache mit der Vererbung ... schwere Kost - wenn ich die Muße finde, werde ich Deine Lib Mal zerpflücken.

MfG

Edit
Link zur verlinkten Lib eingefügt

OT: naja ich traue mich folgende kritische Würdigung zu geben:

Die originale LedControl ist halt etwas gewöhnungsbedürftig/lästig für die Ausgabe von Zahlen und Worten. Auch verwendet sie nicht Hardware SPI sondern bitbangerei. Aber sie ist halt da - und ich habe sie auch selber jahrelang genutzt.

Die HKJ-lygte ist für Leute die "Eine für alles" haben wollen. Er hat eine großartige Seite für alle möglichen Displays. Die Implentierung der ganzen print-Funktionen sind aber alles separate Entwicklungen in der Lib. Damit hast du aber in letzter Konsequenz mehr Speicherbedarf als wenn du die print Klasse so oft wie möglich reused. Die "schönen" Funktionen die er hat für "Zahlen Rechtsbündig" schreiben - das mach ich lieber mit printf oder handgestrickt. Auch Missfällt mir das Ändern von .h dateien zum Anpassen an die verwendete Hardware. Das muss aus meiner Sicht immer über init/setter gehen.

Die oben verlinkte von HobbyComponents, da missfällt mir, dass der Zeichensatz im RAM gehalten wird und nicht in den Progmem verschoben wurde. Ich weis nicht ob das der Compiler wegoptimieren kann. Müsste man probieren. Auch sind die Print-Funktionen wieder separat implementiert. Methoden-Namen die groß beginnen ist auch nicht so meins. Und es gibt ein paar Konfigurationen die mit #defines in der .h gemacht werden sollen.

Meine NoiascaLedControl ist zwar ursprünglich auf der LedControl aber unterstützt aucht Hardware SPI. Für jeden der bisher mit der von Eberhard zufrieden war, wird mit meiner auch leben können. Außerdem kann der Ersteller auf östereichisch Deutsch-Support geben :wink:

noiasca:
Das "Problem" das ich an meiner NoiascaLedControl sehe, ist dass sie zu sehr von der LedControl kopiert ist und somit nicht SPI nutzt sondern die Bitbangerei wie es auch Eberhard gemacht hat.

Hallo,
das sollte nicht ein Problem sein. Falls du eine Idee dazu möchtest kann ich dir meine Lib zeigen. Damit wird mittels Initialisierung/Konstruktor festgelegt ob Software oder Hardware SPI zum Einsatz kommt. Gibt man nur den CS Pin an ist Hardware SPI aktiv. Gibt man alle weiteren Pins mit an ist Software SPI aktiv.

Macht bei Hardware SPI eingestellten 4MHz mit 4 übertragenen Bytes einen Unterschied von 820µs zu 50µs Übertragungszeit.

Das klingt doch gut. Das sollte man beides verheiraten.

Gruß Tommy

das sollte nicht ein Problem sein. Falls du eine Idee dazu möchtest kann ich dir meine Lib zeigen. Damit wird mittels Initialisierung/Konstruktor festgelegt ob

eh genau so macht man es gscheit. Die reine Theorie wäre mir klar. Ich habs nur momentan nicht am Radar

Zu viele andere Baustellen offen (und Faulheit ... genau diese Displays habe ich mehrmals verbaut - und alle sind an den gleichen Pins. Ich müsste also glatt 5 Geräte aufschrauben und umstecken. Das kommt jetzt nicht drann ). Nichts hält so lang wie ein Provisorium. :slight_smile:

noiasca:
Nichts hält so lang wie ein Provisorium. :slight_smile:

Das stimmt, aber Du brauchst doch die bestehenden Anzeigen nicht umbauen. Die laufen ja gut mit SoftSpi.
Auch bei Aktualisierung wäre das ja gegeben.

Die Lib mit beiden Ansteuerarten würde mich schon interessieren. :wink:

Gruß Tommy

Hallo,

genau, an deiner Hardware musst du nichts umbauen. Ich sehe da keinen Grund.
Das Bsp. interessiert euch sicherlich nicht, habs aber drin gelassen das etwas zum kompilieren vorhanden ist.

MAX7221.zip (4.22 KB)

der if für Hardware/Bitbang ist ja nun in mehreren Methoden. Schafft man das auch mit separaten Funktionen, eine Art overload abhängig vom angewendeten Constructor oder Initialisierungsliste?

oder optimiert den anderen (nicht benötigten) Zweig eh der Compiler weg und braucht man sich keine Gedanken über die ungenutzten Zeilen machen.

void MAX7221::transfer( const uint8_t address, const uint8_t value ) 
{ // MSB first 
  digitalWrite(CS, LOW);
  
  if (use_SPI == state::HARDWARE)
  {  // Hardware SPI
      SPI.beginTransaction( SPISettings(4000000, MSBFIRST, SPI_MODE0) );
      SPI.transfer(address);
      SPI.transfer(value);
  SPI.endTransaction();
  }
  else
  {  // Software SPI

Denk mir halt, dass könnte evtl. den Code ein wenig aufblasen. Andererseits machts Marco in seiner MD_72xx auch so ähnlich...

der if für Hardware/Bitbang ist ja nun in mehreren Methoden. Schafft man das auch mit separaten Funktionen, eine Art overload abhängig vom angewendeten Constructor oder Initialisierungsliste?

oder optimiert den anderen (nicht benötigten) Zweig eh der Compiler weg und braucht man sich keine Gedanken über die ungenutzten Zeilen machen.

Den generierten Code kannst du untersuchen.

Ansonsten kann der tote if() Zweig nur weg optimiert werden, wenn der Compiler davon überzeugt ist, dass er nie durchlaufen wird.
Das würde gehen, wenn man die benutzte SPI Schnittstelle (oder nur ihren Type) per Templateparameter übergibt.

Nur als Kontruktorparameter, oder Setter, könne gehen, wenn es eine gemeinsame Interface Klasse geben würde.
Ja, das ist ein kleines Arduino Problem. Betrifft auch die Verwendung diverser anderer Schnittstellen, wie Wire, Serial usw.

PS:
Gute Idee, mit der Print Implementierung.

Habe gerade mit der Noiasca Bibliothek rumprobiert und finde sie für meine Zwecke einfach genial. Erfüllt alles was ich haben wollte und das in sehr einfacher Weise. Vielen Dank dafür.

Eine Anfängerfrage habe ich noch: Wenn ich mehrere Anzeigen kaskadiere, komme ich dann mit dem Strom noch hin? Ich habe jetzt 2 Module á 8 Zeichen drann und versorge alles über USB. Das läuft stabil. Der Plan ist aber 4 Module á 4Stellen und ein Achter. Reicht es dann mit einem externen Netzteil für den Arduino oder soll ich lieber die Module extern versorgen?

Schön wenn es funktioniert.

Also 3 Module über USB -> vom Arduino versorgt laufen bei mir nicht mehr stabil.
2 Module - da sehe ich dass das zweite schon weniger Strom zur Verfügung hat, selbst wenn ich die Helligkeit nicht ganz aufdrehe.
Gemessen habe ich es nie, bedenke aber, es sind LEDs ... da braucht man schon mehr Strom als ein LCD

Hallo,

jetzt wurde combie’s Antwort wirklich ignoriert. :o

Von meiner Seite. Member “use_SPI” ist eine Konstante, damit wird alles was nicht wahr ist vom Compiler weggeschmissen.

Ich habe mal ein sinnloses Bsp. gemacht was die Seite aktzeptiert.
Ändere HARDWARE in SOFTWARE oder nimm das const weg oder … und schau was passiert.

Code Explorer

das hier links reinkopieren

enum state {HARDWARE, SOFTWARE};
const state useSPI = HARDWARE;

unsigned int transfer( const unsigned int address, const unsigned int value )
{
  unsigned int data = 0;

  if (useSPI == HARDWARE)
  {
    data |= value;
  }

  if (useSPI == SOFTWARE)
  {
    for (int i = 0; i < 16; i++)
    {
      data &= 0x8000;
      data &= 0x3F;
      data &= 0x3A;
    }
  }

  return data;
}

nicht ignoriert - noch zu wenig Wissen dazu.

Deinen Link kann ich probieren - bin mir aber nicht sicher ob das Beispiel ausreicht.
Meine (nicht wissenschaftlichen^^) Beobachtungen zu Kompiler Optimierungen zeigten mir bisher, dass sich der Kompiler bei Klassen eher schwerer tut (oder halt von mir falsch angewandt wird, ...)

dass sich der Kompiler bei Klassen eher schwerer tut (oder halt von mir falsch angewandt wird, ...)

Naja, er kann ja auch zu dem Zeitpunkt der Kompilation von *.cpp Dateien noch nicht wissen, was später benötigt wird, wenn die Abhängigkeit "variabel" ist.

Darum muss man diese Abhängigkeiten zur Kompilezeit schon unmissverständlich fest nageln, damit er eine Chance hat den überflüssigen Kram weg zu lassen.

Das geht eben wie Doc_Arduino schon sagte über externe Abhängigkeiten (Defines oder Konstanten), oder über die Abhängigkeitsinjektion durch einen Templateparameter.

Hallo,

wie combie schon schreibt. Entscheidend für das Code schrumpfen ist wirklich das konstant machen der enum Variable und auch aller anderen nicht veränderbaren. Hatte mich letztens erst Michael wieder daran erinnern müssen. Man glaubt es immer wieder nicht. Ist aber so. :wink:

Templates sind noch keine Spezialität von mir, aber ich arbeite daran. Hin- und wieder kommen sie zum Einsatz.

Ich habe mal kurzerhand die Lib umgebaut. Ich bitte um Tests mit eurer Hardware.
Ein neues Bsp. ist UmbauTest basiert auf LCDemo7Segment.
Die Software SPI Variante ist 4 Bytes größer wie die Hardware SPI Variante.
Wenn der Schuß ins Blaue klappt kann man noch diverse Datentypen aufräumen.

Eine Kleinigkeit mußte ich ergänzen, und zwar die begin() Methode.
Sonst hätte man doppelten Code im Konstruktor.
Der Konstruktor hat mir sowieso nicht gefallen.
Falls jemand eine Idee hat wie man den Member maxDevices konstant bekommt, her damit.
Problem ist der wird auf einen Wert 8 begrenzt.

NoiascaLedControl.zip (19.4 KB)

template<uint8_t maxDevices = 1>
class LedControl : public Print {
    private :
 uint8_t const SPI_MOSI; /* Data is shifted out of this pin*/
        uint8_t const SPI_CLK; /* The clock is signaled on this pin */
        uint8_t const SPI_CS; /* This one is driven LOW for chip selectzion */
      //  uint8_t maxDevices; /* The maximum number of devices we use */
        uint8_t spidata[16]; /* The array for shifting the data to the devices */
// schnipp

Was natürlich noch einige Änderungen im Code nach sich zieht

// LedControl lc = LedControl  (LEDDATA_PIN, LEDCLK_PIN, LEDCS_PIN, LED_MODULES); // default 1 Device
// LedControl<1> lc = LedControl  (LEDDATA_PIN, LEDCLK_PIN, LEDCS_PIN, LED_MODULES); // 1 Device
LedControl<12> lc = LedControl  (LEDDATA_PIN, LEDCLK_PIN, LEDCS_PIN, LED_MODULES); // 12 Devices

ungetestet

template <int i>
class Test
{
  static_assert(i<=8,"leider nur max. 8 Dinger erlaubt ");
};

Test<2>  t;
Test<12> u;

@NoiascaLedControl_DocArduino:
software-SPI läuft, hardware-SPI noch nicht
mit diesen Definitionen:

#define LEDCS_PIN             8       // 8 LED CS or LOAD //  8 CS
#define LEDCLK_PIN            13      // 7 LED Clock      // 13 CLK
#define LEDDATA_PIN           11      // 9 LED DATA IN    // 11 MOSI
uint8_t const LED_MODULES = 1;

LedControl lc = LedControl  (LEDDATA_PIN, LEDCLK_PIN, LEDCS_PIN, LED_MODULES); // Software SPI
//LedControl lc = LedControl  (LEDCS_PIN, LED_MODULES);                  // Hardware SPI

grad auch noch umgesteckt auf ein sonst leeres Board (UNO Clone).
Gegencheck mit der MD_72xx - das funktioniert soft und hard, also nehme ich an, ich hab mein Display richtig angeschlossen.

Ich teste weiter.

@combi ... versuche ich sobald ich den Hardware SPI am laufen habe.

edit 08:37: hardware SPI läuft. Code säubern ... (und ja da ist viel zu tun, hätt ich schon letztes Jahr tun sollen)
edit 09:51: doc, dein Code würde vermutlich funktionieren, wenn du im Konstruktor noch ein SPI.begin(); ergänzt. Karma+ dafür. Ich habe aber jetzt auch beim rumprobieren viele andere Änderungen gemacht, weswegen ich nicht weis ob das das auschließliche Problem war. Deine HW-Variante kompiliert bei mir mit 3608/290 Bytes . Daher arbeite ich doch wieder bei meinem weiter bei dem ich aktuell bei 2652/115 Bytes bin.
Ich mach jetzt mit dem Template weiter...
edit 13:47: Das mit dem Template bekomme ich nicht hin. Andererseits wegen einem Byte ^^. Wichter erscheint mir, das SPI.h abhängig vom Konstruktor zu includen. Wenn sich wer daran versuchen möchte, das Zip mit allen Aktualisierungen lege ich bei

edit: code gelöscht da veraltet

Hallo,

upps, ja das mit dem fehlenden SPI.begin() ist mir peinlich, hatte ich völlig vergessen. Dann fehlte noch den CS Pin auf High setzen. Hätte ich nur von meiner init() übernehmen müssen. Ging irgendwie unter. Beim Hardware SPI.transfer hatte ich fälschlicherweise die 16Bit Variante verwendet, weil ich dachte es werden 16Bits übertragen. Warum weiß ich auch nicht mehr.

Wegen der Codegröße. Ich kann nur mit meinem Compiler unterschiedliche Größen vergleichen. Mein 9.2er kann unter Umständen größeren Code erzeugen wie ältere Compiler. Mit ein Grund warum die avr-backend Renovierung so wichtig ist. Das am Rande. Deswegen kann ich nicht mit deinen Angaben direkt vergleichen. Ich nehme an du verwendest den 7.3er von der IDE.

Nach der Korrektur der Lib meinerseits habe ich nochmal verglichen.

Deine Lib mit dem UmbauTest Sketch: (die von “heute früh” von deiner Seite)
orignal Software SPI (3050 / 114 Bytes)

Mit meinen Lib-Änderungen:
Software SPI (3158 / 115 Bytes)
Hardware SPI (3156 / 115 Bytes)

(Ergänzung: mit 1 Modul, nicht 2)

Die Größen können bei dir anders sein nur die Differenzen sollten theoretisch ungefähr gleich sein.
Wenn du da auch bei nur ca. 2600 Bytes landest, dann wäre das ein wirklich krasser Unterschied.

Das man die Libs besser vergleichen kann, habe ich die umbenannt.

Eine Anmerkung noch. Du verwendest den Konstruktor nicht ganz korrekt. Die Initialisierung gehört nicht in die geschweiften Klammern. Es spricht nichts dagegen noch eine init() bzw. begin() aufzurufen und darin nochmals auf hardware spi abzufragen. Ich hatte deine Schreibweise nochmal gegengetestet und sie erzeugt bei mir nochmals etwas größeren Code. Wenn du die ganzen ungenutzten int Datentypen korrigierst sollten nochmals paar Bytes verschwinden. Auch wenn du alles konstant machst was konstant ist, Bsp. die Pins, sollte der Code noch etwas schrumpfen.

Als weitere Idee könnte man die Software Variante noch mittels combiePin Lib beschleunigen.

@ combie:
static_assert ist eine gute Idee. Danke.

@ noisca:
wegen der SPI.h inklude Frage. Davon wird in der Softwarevariante nichts benötigt, deswegen sollte davon nichts kompiliert werden, deswegen sollte das nicht stören. Ich versuche mich aber daran. Der Erste Weg wird sein den Konstruktor (wieder) zu ändern und begin() anzupassen. Habe deine aktuelle Version runterladen.

DocNoiascaLedControl.zip (10.9 KB)

schnell mal deine Version mit einer unveränderten IDE 1.8.10:

//                                                                                                                     IDE 1.8.10 original
                                                                      // orignal Software SPI (3050 / 114 Bytes)
LedControl lc = LedControl  (CS_PIN, DATA_PIN, CLK_PIN, LED_MODULES); // Software SPI (3158 / 115 Bytes)             2696/115
//LedControl lc = LedControl  (CS_PIN, LED_MODULES);                      // Hardware SPI (3156 / 115 Bytes)         2692/115

vs.
2578/114 und
2650/115 Bytes

also bei mir macht der Unterschied mit/ohne SPI viel mehr aus.

“heute früh” hatte ich noch nichts neues, erst gegen 14:00 hab ich einen Upload auf meine Seite gemacht.
die Ints sollten alle bereinigt sein,
das löschen mit memset war eine gute Idee, das hab ich auch zweimal übernommen,
die alten Absicherungen auf <0 gelöscht
und das maxDevices ist jetzt anders gelöst.
alles was den constructoren gleich ist, ist nun in einer begin(), aber ich rufe den begin() in den beiden constructoren auf.

Ob ich es noch Umbau auf saubere Initialisierungslisten mal sehen, muss ich erst nachlesen, warum es so klappt wie es ist und warum ich es dennoch umbauen soll… (Sturschädel - weist ^^)