Serielle Datenstring vom Computer im Adruino Mega einlesen und aufteilen

Genau deswegen lohnt sich ja ein vernüftiges Konzept!

Gerne:

const char HM[][13] PROGMEM = {"Hauptmenue","Turm-Anzeige","Turm-Licht","Helligkeit","RGB-1 Werte","Fahrspuren","Startanzeige","IDNummer","Funkkanal","WerksWerte","Speichern"};
char menuetxt[17];

char * lcdHauptMenu(byte index)
{
  strcpy_P(menuetxt, HM[ index ]);
  char * ptr = menuetxt;
  return ptr;
}

void setup()
{
  Serial.begin(115200);
  Serial.println(F("Start..."));
  const uint8_t gesamt = 14;
  uint8_t vor = 0;
  uint8_t nach = 0;

  for (uint8_t i = 0; i <= 10; i++)
  {
    vor = 0; nach = 0;
    if ((gesamt - strlen(lcdHauptMenu(i))) > 1)
    {
      vor = (gesamt - strlen(lcdHauptMenu(i))) / 2;
      nach = gesamt - (vor + strlen(lcdHauptMenu(i)));
    }
    //Serial.print(vor); Serial.print(" "); Serial.println(nach);
    Serial.print("<");
    while (vor >= 1)
    {
      Serial.print(" ");
      vor--;
    }
    Serial.print(lcdHauptMenu(i));
    while (nach >= 1)
    {
      Serial.print(" ");
      nach--;
    }
    Serial.println(">");
  }
}

void loop() {}

Oder wie in der Referenz vorgeschlagen:

const char HM00[] PROGMEM = {"Hauptmenue"};
const char HM01[] PROGMEM = {"Turm-Anzeige"};
const char HM02[] PROGMEM = {"Turm-Licht"};
const char HM03[] PROGMEM = {"Helligkeit"};
const char HM04[] PROGMEM = {"RGB-1 Werte"};
const char HM05[] PROGMEM = {"Fahrspuren"};
const char HM06[] PROGMEM = {"Startanzeige"};
const char HM07[] PROGMEM = {"IDNummer"};
const char HM08[] PROGMEM = {"Funkkanal"};
const char HM09[] PROGMEM = {"WerksWerte"};
const char HM10[] PROGMEM = {"Speichern"};

const char * const hm_table[] PROGMEM = {HM00, HM01, HM02, HM03, HM04, HM05, HM06, HM07, HM08, HM09, HM10};
char menuetxt[17];

char * lcdHauptMenu(byte index)
{
  strcpy_P(menuetxt, (char*)pgm_read_word(&(hm_table[ index ])));
  char * ptr = menuetxt;
  return ptr;
}

void setup()
{
  Serial.begin(115200);
  Serial.println(F("Start..."));
  const uint8_t gesamt = 14;
  uint8_t vor = 0;
  uint8_t nach = 0;

  for (uint8_t i = 0; i <= 10; i++)
  {
    vor = 0; nach = 0;
    if ((gesamt - strlen(lcdHauptMenu(i))) > 1)
    {
      vor = (gesamt - strlen(lcdHauptMenu(i))) / 2;
      nach = gesamt - (vor + strlen(lcdHauptMenu(i)));
    }
    //Serial.print(vor); Serial.print(" "); Serial.println(nach);
    Serial.print("<");
    while (vor >= 1)
    {
      Serial.print(" ");
      vor--;
    }
    Serial.print(lcdHauptMenu(i));
    while (nach >= 1)
    {
      Serial.print(" ");
      nach--;
    }
    Serial.println(">");
  }
}

void loop() {}

Auch diese Variante funktioniert und sieht für mich besser aus, bin mir aber nicht sicher, ob da nicht möglicherweise Speicherlücken drohen:

char * lcdHauptMenu(byte index)
{
  char * ptr = new char[17];
  strcpy_P(ptr, (char*)pgm_read_word(&(hm_table[ index ])));
  return ptr;
}

Kann ich Dich überzeugen?

Du meinst Speicherlecks?
Abhilfe: Ressourcenbelegung ist Initialisierung – Wikipedia

Wobei ich mich frage: Warum muss der Text, auf Teufel komm raus, ins RAM verfrachtet werden?
Mir leuchtet der Sinn nicht ein.

Vorschlag:

#include <Streaming.h> // die Lib findest du selber ;-)
Stream &cout = Serial; // cout Emulation für AVR Arduinos

const unsigned menueStrLen {17};
const unsigned menuePunkte {11};

using ConstStrPtr  = const char *;
using FlashStrPtr  = const __FlashStringHelper *;
using MenuePunkt   = const char[menueStrLen+1];

MenuePunkt menue[menuePunkte] PROGMEM
{
   {"Hauptmenue"},
   {"Turm-Anzeige"},
   {"Turm-Licht"},
   {"Helligkeit"},
   {"RGB-1 Werte"},
   {"Fahrspuren"},
   {"Startanzeige"},
   {"IDNummer"},
   {"Funkkanal"},
   {"WerksWerte"},
   {"Speichern"},
};  



void setup() 
{
  Serial.begin(9600);
  cout << F("Start: ") << F(__FILE__) << endl;

  for(MenuePunkt &m:menue) 
     cout << strlen_P(ConstStrPtr(&m)) << " " << FlashStrPtr(&m) << endl;

  cout << "-----------" <<endl;

  for(unsigned i=0;i<menuePunkte;i++) 
    cout << i << " " << FlashStrPtr(&menue[i]) << endl; 

   // ein LCD habe ich gerade nicht dran, aber da ist die Ausgabe genau so einfach
   // lcd.print(FlashStrPtr(&menue[5]));
   // oder eben auch:
   // lcd << FlashStrPtr(&menue[5]);
}

void loop() 
{

}
2 Likes

OT: Im standard HD44780 gibts die Zeichen mit Unterlängen in einer zweiten Variante. Da brauchst nur +0x80 dazuzählen und kommst auf die andere Variante.

z.B.:

lcd.write('g' + 0x80);

"Schöner" sind sie aber imho auch nicht.

auf Arduino: Deutsche Umlaute äöü am LCD (Liquid Crystal I2C) ist ganz unten ein Bild mit einem direkten Vergleich.

P.S.: eigentlich müsste es möglich sein, deine Sonderzeichen in einen Converter zu packen, dann kannst du die Zeichen ganz normal im Fließtext schreiben... bei Interesse PM/separter Thread.

Ja, meine ich, hatte was von "leaking" gelesen.

Wer einen kurzen Weg gefunden hat, stellt manchmal erstaunt fest, es gibt noch einen kürzeren :roll_eyes:

Danke!


@my_xy_projekt: Dein Programm in der Combie-Variante:

const unsigned menueStrLen {12};
const unsigned menuePunkte {11};

using ConstStrPtr  = const char *;
using FlashStrPtr  = const __FlashStringHelper *;
using MenuePunkt   = const char[menueStrLen+1];

MenuePunkt menue[menuePunkte] PROGMEM
{
 {"Hauptmenue"},
 {"Turm-Anzeige"},
 {"Turm-Licht"},
 {"Helligkeit"},
 {"RGB-1 Werte"},
 {"Fahrspuren"},
 {"Startanzeige"},
 {"IDNummer"},
 {"Funkkanal"},
 {"WerksWerte"},
 {"Speichern"},
};  

FlashStrPtr lcdHauptMenu(byte index)
{
   return FlashStrPtr(&menue[index]);
}

uint8_t txtlaenge(byte index)
{
   return strlen_P(ConstStrPtr(&menue[index]));
}

void setup()
{
  Serial.begin(115200);
  Serial.println(F("Start..."));

  const uint8_t gesamt = 14;
  uint8_t vor = 0;
  uint8_t nach = 0;

  for (uint8_t i = 0; i <= 10; i++)
  {
    vor = 0; nach = 0;
    if ((gesamt - txtlaenge(i)) > 1)
    {
      vor = (gesamt - txtlaenge(1)) / 2;
      nach = gesamt - (vor + txtlaenge(i));
    }
    //Serial.print(vor); Serial.print(" "); Serial.println(nach);
    Serial.print("<");
    while (vor >= 1)
    {
      Serial.print(" ");
      vor--;
    }
    Serial.print(lcdHauptMenu(i));
    while (nach >= 1)
    {
      Serial.print(" ");
      nach--;
    }
    Serial.println(">");
  }
}

void loop() {}

Die Funktionen lcdHauptMenu und txtlaenge dienen nur dem Komfort.

Dann nochmal mit Streaming:

#include <Streaming.h>  // http://arduiniana.org/libraries/streaming/
#include <LiquidCrystal_I2C.h>
LiquidCrystal_I2C lcd(0x27, 16, 2);
const unsigned menueStrLen {12};
const unsigned menuePunkte {11};

using ConstStrPtr  = const char *;
using FlashStrPtr  = const __FlashStringHelper *;
using MenuePunkt   = const char[menueStrLen+1];

MenuePunkt menue[menuePunkte] PROGMEM
{
 {"Hauptmenue"},
 {"Turm-Anzeige"},
 {"Turm-Licht"},
 {"Helligkeit"},
 {"RGB-1 Werte"},
 {"Fahrspuren"},
 {"Startanzeige"},
 {"IDNummer"},
 {"Funkkanal"},
 {"WerksWerte"},
 {"Speichern"},
};  

FlashStrPtr lcdHauptMenu(byte index)
{
   return FlashStrPtr(&menue[index]);
}

uint8_t txtlaenge(byte index)
{
   return strlen_P(ConstStrPtr(&menue[index]));
}

void setup()
{
  Serial.begin(115200);
  Serial.println(F("Start..."));
  //lcd.begin(); // Für XY Projekt
  lcd.init(); // Für Benziner und agmue
  lcd.backlight();
  
  const uint8_t gesamt = 14;
  uint8_t vor = 0;
  uint8_t nach = 0;

  for (uint8_t i = 0; i <= 10; i++)
  {
    lcd.setCursor(0, 0);
    vor = 0; nach = 0;
    if ((gesamt - txtlaenge(i)) > 1)
    {
      vor = (gesamt - txtlaenge(1)) / 2;
      nach = gesamt - (vor + txtlaenge(i));
    }
    //Serial.print(vor); Serial.print(" "); Serial.println(nach);
    Serial << '<';
    lcd << '<';
    while (vor >= 1)
    {
      Serial << ' ';
      lcd << ' ';
      vor--;
    }
    Serial << lcdHauptMenu(i);
    lcd << lcdHauptMenu(i);
    while (nach >= 1)
    {
      Serial << ' ';
      lcd << ' ';
      nach--;
    }
    Serial << '>' << '\n';
    lcd << '>';
    delay(1000);
  }
}

void loop() {}

Das Menü liegt bereit, picke Dir die Rosinen raus :smiley:

1 Like

Gerne doch!
Schön, dass es dir gefällt.

Aufgefallen sind mir noch diese Schleifen:

Eigentlich gibt es nichts dagegen zu sagen.
Ist voll kommen ok, das so zu tun.

Aus meiner Sicht ist es allerdings eine zählende Schleife (wg. i--), und dafür wurde eigentlich for erfunden.
Eine alternative Schreibweise wäre somit:

    for ( ; nach >= 1; i--) Serial.print(" ");

Abweichend von der üblichen for Schleife ist nur, dass der Initialisierungsteil leer ist, denn das wird woanders getan.

Wie schon gesagt, ist überhaupt nicht wichtig.
Denn die beiden Varianten sind sonst in allen Belangen identisch.
Unterscheiden sich nur in der/meiner "Vorstellung" bzw. "Idee"

Ich hole mal aus!

Dies ist nicht mein Thema, sondern das von @Benziner. @my_xy_projekt wollte nun meine Ideen aus #356 "Passend zum Code aus #355" haben, wie er in #357 schrieb. Diesem Wunsch bin ich nachgekommen und habe mich daher möglichst nahe an seinem Vorbild orientiert. Darum fühle ich mich für while nicht verantwortlich.

Allerdings kann man wegen der wohl unvermeidlich festen Länge const unsigned menueStrLen {12}; die Leerzeichen auch gleich zusammen mit dem Text speichern, weshalb die Berechnung der fehlenden Zeichen entfallen sollte.

Dann ist auch while verschwunden und alle lebten glücklich bis ans Ende ihrer Tage.

Leute, kommt doch mal wieder runter!
Es gibt immer wieder auch Nebenschauplätze, ein "ich kann es besser" ist aber vollkommen unangebracht.
Und ja, ich ärgere mich auch manchmal.
War gestern Abend auch so. - nicht hier. Einfach aufhören. und gut ist.

So und nu zum Tagesgeschehen.
Spätestens seit #353 ist doch klar, das sich vom eigentlichen Tun abhebend noch Unmengen an Aufgaben entwickeln.

In #355 - mit dem dortigen Verweis auf einen selbst aufgemachten Post - habe ich deutlich gemacht, das ich am Anfang stehe um mögliche später entstehende Speicher-Probleme anzugehen.
Nicht mehr und nicht weniger.
Ob da ein while oder for oder gar nichts zum Schluß raus kommt ist wurscht.
Der Code war gedacht, das den JEDER den es interessiert nachvollziehen kann.
Das war ein ganz simpler quick & dirty!
Aufgabe: Die Ausgabe - ggfls. zentriert - voll zu bekommen. Und als erstes und letztes Zeichen immer ein '<' und '>' vs. '*' - also die Wiederverwendung von Zeichenkletten!
Auch hier: Nicht mehr und nicht weniger.

Und wie @agmue schon schrub: Ich wollte eine abweichende Kurzfassung.
Lässt sich nachlesen:
Ich in #357

Das ist dann genau das, was @agmue in #363 als zweiten Code eingestellt hat.
So sah meiner auch aus :wink:

@combie Deiner aus #364 ist wirklich kurz.
Ich zolle Dir wie immer Respekt für solche Schnipsel.
Der muss verstanden werden.
Ob das Buch das so hergibt kann ich derzeit nicht beurteilen.
Ich bin nicht allein was die Verständnisfrage zu for und deren Initialisierung angeht.
Hab das als Lesezeichen gesetzt. Der entscheidende Moment ist, ob, wenn es tatsächlich gebraucht wird und nicht gefunden wird. :wink:

@noiasca, das mit dem wirklich geilen Code zur Länge der Zeichenketten mit Umlauten hat mich echt fasziniert. Und tut's noch immer. Der ist auch verstanden, mit der Auflösung aus Frage zu CharArray strlen() und Umlaut - #10 by noiasca macht das tasächlich Spass.

Und nu denn hier:

Genau das wollte ich vermeiden.

const char *lcdHauptMenu[] // siehe #355

belegt nur den tatsächlich genutzten Speicher.(-)
Im Hauptmenu ist das noch relativ unproblematisch. Später habe ich aber viel weniger Zeichen auf der Zeile (z.B. 'RGB-')und lege dafür den ganzen Rest mit Leerzeichen an?
Dann kann ich auch jeden Menupunkt wieder einzeln beschreiben.
(-) - Wenn ich da falsch liege, ziehe ich das zurück.

Die Abwägung nach Aufwand und Nutzen findet erst statt, wenn das Funktionale durch ist.
Ich - und so ich nicht ganz falsch liege - auch @benziner wird die Verwendung der Untermenus und des RotaryEncoder die nächsten Tage massiv beschäftigen.

Ganz langsam angehen.
Man liest sich.

Bleibt gesund und passt auf Euch auf!

@benziner morgen Pixel-tab, sobald ich dem Nachmittags habhaft werde. (Ich den Code tatsächlich nicht am Mann ;( )
So und fritz

Beste Grüße!

Ja, ich auch. Andererseits ist bei PROGMEM die Speicherverschwendung nicht ganz so kritisch zu werten. Man muß halt die Balance finden zwischen Datenkomprimierung und Programmieraufwand.

Bleibe gesund und entspannt!

Man sollte in Betracht ziehen, dass die Pointertabelle auch Speicher benötigt.
Pro Pointer, macht das 2 char. Zusammen 22 in dem Beispiel.
Es ist dort also eher eine Geschmacksfrage, als eine des Platzbedarfs.

Naja...
... zählende Schleifen ....

Die ganzen Fachbücher, sind sich da aber schon einig. Incl. C.
Der for Schleifenkopf muss 3 Anweisungen enthalten.
Auch eine leere Anweisung ist gültig.

Du darfst ja auch

c = a + b; ; ; ;
Schreiben.
Das sind immerhin 4 Anweisungen, davon 3 leer.

Also durchaus auch sowas:

for(;;) {/* eine endlosschleife*/}
// identisch mit
for(;true;) {/* eine endlosschleife*/}
// oder
while(1) {/* eine endlosschleife*/}

Ja. Das wird immer mal wieder vergessen.
Darum macht ja auch der Einsatz des F()-Makros erst ab 3 Zeichen Sinn.

Ich versuch mich ja zu trennen.
Wahr hast. Wenn man den Kopf als Einzel betrachtet, ist das auch nicht schwer. Da wo es fehlt ist das weiterdenken:

Das ist nicht einfach mit lesen getan.

Danke für die Anregung. Es wird evtl. noch anderen helfen.

Doch schon.....

Eigentlich sollten sich in jedem etwas modernem C++ Buch, die Iteratoren und ihre Verwendung mit Containern finden lassen, wozu gerade eben auch der von dir genannte "Range based for loop" gehört.
Nach dem Lesen sind using, Referenzen, und auch dass C++ automatisch Iteratoren für Standard Arrays zur Verfügung stellt, kein echtes Geheimnis mehr.

Manches ist mit "denken" nicht erreichbar.
Denn mit nur "denken" buddelt man immer nur im selben Sumpf, des schon bekannten.

Keiner sagt, dass es einfach ist.
Aber der Weg führt über das lesen.
Denn darüber erreicht man das ein oder andere "Neue", was man dann in sein "denken" einbauen kann.
Dadurch kommt man zu einem anderen "denken".

Frei nach:

Der Kopf ist rund, damit das Denken seine Richtung ändern kann.

Nö ganz bestimmt nicht.
Ich mach nur mit.

So mein lieber. Dann jetzt mal was fürs Gemüt:
Was haben wir:
Das LED-Menu macht jetzt genau für drei WS drei Farben.
Das, und nur das LED-Menu, bitte einmal durchspielen.
Wenn das auf dem SerMon und dem LCD funktioniert -> Und nur dann(!) darfst Du im void loop() in Zeile 57 den Kommentar entfernen und mir sagen, ob es das ist, was Du willst.

Bitte wie immer drauf achten: Pins müssen ggfls. angepasst werden - ja das änder ich noch...

Erstmal wieder die Tabs einzeln:
Hauptmenue.ino (4,1 KB)
Turm_Empfaenger_v50.0B.ino (3,3 KB)
Submenu_Grundausstattung.ino (1,7 KB)
Turmlicht.ino (1,6 KB)
Turmanzeige.ino (1,7 KB)
RGB.ino (5,3 KB)
(Beim ersten Mal ist was schief gegangen...)
Und für alle, die einfach nur mal mitlesen möchten das als Gesamtcode.
Ich habe wie immer markiert, wo die tabs anfangen.

// Tower-Control auf MEGA 2560
const char ver[] = {"TC-V1.0.6 rotary"};

// LCD
#include <LiquidCrystal_I2C.h>
LiquidCrystal_I2C lcd(0x27, 16, 2);

// Licht
#include <Adafruit_NeoPixel.h>  // Einbinden der Bibliothekt zum ansteuern der WS2812 LEDs
const uint8_t ledPin = 45;      // Pin für WS2812 LEDs
const uint16_t numPixel = 7;    // Anzahl der WS2812 LEDs
Adafruit_NeoPixel pixel(numPixel, ledPin, NEO_GRB + NEO_KHZ800);

byte rgbLicht[3][3] =
{
  {55, 20, 0},                  // Auswahl RGB Beleuchtung Eingang
  {0, 50, 0},                   // Auswahl RGB Beleuchtung Unten
  {0, 0, 50}                    // Auswahl RGB Beleuchtung Oben
};

// Uhr
#include <DS1307RTCB.h>         // auf die TimeLibB angepasste 1307 von Paul Stoffregen
#include <TimeLibB.h>           // auf deutsch umgeschriebene TimeLib von Paul Stoffregen
byte minutealt = 61;            // Zwangsaktuallisierung Uhrzeit! - nur alle Minute neue Abfrage


// RotaryEncoder
#include <rotary.h>
Rotary r = Rotary(5, 3, 23);    // Rotary(Encoder Pin 1, Encoder Pin 2, Button Pin)
const uint8_t bounce = 10;

void setup()
{
  Serial.begin(115200);
  Serial.println(F("Start..."));
  lcd.begin(); // Für XY Projekt
  // lcd.init(); // Für Benziner
  lcd.backlight();
  //  RTC.begin();
  // ************ Anfang LCD Start-Anzeige ***********
  lcd.setCursor(0, 0);
  lcd.print(ver);
  // *************Ende LCD Start-Anzeige *************
    // Start Pixel
  pixel.begin();
  pixel.show();
  
}// Ende Setup

void loop()
{
  //Serial.println("tak"); //debug non-Blocking
  if (!menue())  // fragt das menu ab
  {
    displayKalender(); // wenn kein menu dann Kalender
  }
//    neoPixels();
}

bool menue() // gibt true zurück, wenn ein Menu aufgemacht - sonst false
{
  static uint8_t nextMenu = 0;
  if (nextMenu == 0 && r.buttonPressedReleased(bounce))   // Kein Menu? aber auf Button gedrückt
  {
    Serial.println(F("Button pressed Aufruf Hauptmenue"));
    lcdMenuLineOne(0);                                    // Erste Zeile Hauptmenue
    nextMenu = Hauptmenue(nextMenu);                      // übergebe Startzeichen für Menu
  }
  else  if (nextMenu != 0)                                // wenn doch Menu
  {
    nextMenu = Hauptmenue(nextMenu);                      // ... dann einmal durchs Menu
  }
  return nextMenu;                                        // gib Zustand zurück (0=false - alles andere ist true)
}

uint8_t getRotary(uint8_t rotaryWert, const uint8_t rotaryMin, const uint8_t rotaryMax)
{
  // *INDENT-OFF*
  //uint8_t lastRotary = rotaryWert;  // fuer debugausgabe
  uint8_t val = r.process();     // Check the encoder
  //Serial.println(val);
  if (val == r.clockwise()) { rotaryWert ++;
  }
  else if (val == r.counterClockwise())
  {
    rotaryWert --;
  }
  if (rotaryWert > rotaryMax) { rotaryWert = rotaryMax; }
  else if (rotaryWert < rotaryMin) { rotaryWert = rotaryMin; }
  // if (lastRotary != rotaryWert) Serial.println(rotaryWert); // debugausgabe
  return (rotaryWert);
  // *INDENT-ON*
}

void displayKalender() //
{
  if (minute() != minutealt)
  {
    minutealt = minute();
    char zeileAnzeige[16] = {0};
    memset(zeileAnzeige, 0, sizeof(zeileAnzeige - 1));
    sprintf(zeileAnzeige, "%s %02u.%02u.  %02u:%02u", dayShortStr(weekday()), day(), month(), hour(), minute());
    lcd.setCursor(0, 1);
    lcd.print(zeileAnzeige);
    Serial.println(zeileAnzeige);
  }
}

// TAB Hauptmenu
uint8_t Hauptmenue(uint8_t menuRotary) // Auswahl Hauptmenue...
{
  const uint8_t rotaryMin = 1;          // erster Eintrag
  const uint8_t rotaryMax = 11;         // letzer Menueintrag -> Menu verlassen
  static uint8_t subMenu = 0;           // Merker auf naechstes Untermenu
  static bool lcdInitMerker = false;    // fuer erstmalige Anzeige des UnterMenu
  if (!subMenu)                         // bisher kein SubMenu ausgewählt
  {
    uint8_t tikRotary = getRotary(menuRotary, rotaryMin, rotaryMax);// Abfrage RotaryEncoder
    if (menuRotary != tikRotary)        // neuer Wert? dann zeige an
    {
      menuRotary = tikRotary;           // merke den aktuellen RotaryWert
      lcdMenuLineTwo(menuRotary - 1);     // untere Zeile im lcd
    }
    if (r.buttonPressedReleased(bounce))// EncoderButton ausgelöst?
    {
      lcdMenuLineOne(menuRotary);       // schreibt erste Zeile mit nächstem Menupunkt
      subMenu = menuRotary;             // übernimmt den Wert zur Weitergabe
      Serial.print(F("switch Menu: "));
      Serial.println(subMenu);
    }
  }
  if (subMenu)
    switch (menuRotary)
    {
      case rotaryMin:                    // Gehe zu Turmanzeige-Menue
        if (!lcdInitMerker)lcdSubTurmLineTwo(0);
        subMenu = turmAnzeige(subMenu);
        break;
      case 2:                           // Gehe zu Turmlicht-Menue
        if (!lcdInitMerker)lcdSubLichtLineTwo(0);
        subMenu = turmLicht(subMenu);
        break;
      case 3:                           // Gehe zu Helligkeit-Menue
        lcdMenuLineOne(3);
        subMenu = Helligkeit(subMenu);
        break;
      case 4:                           // Gehe zu LEDFarben-Menue
        if (!lcdInitMerker)ledFarben(0);
        subMenu = ledFarben(subMenu);
        break;
      case 5:                           // Gehe zu Fahrspuren-Menue
        lcdMenuLineOne(6);
        subMenu = Fahrspuren(subMenu);
        break;
      case 6:                           // Gehe zu  Startanzeige-Menue
        lcdMenuLineOne(7);
        subMenu = Startanzeige(subMenu);
        break;
      case 7:                           // Gehe zu  IDNummer-Menue
        lcdMenuLineOne(8);
        subMenu = IDNummer(subMenu);
        break;
      case 8:                           // Gehe zu  Funkkanal-Menue
        lcdMenuLineOne(9);
        subMenu = Funkkanal(subMenu);
        break;
      case 9:                           // Gehe zu  WerksWerte-Menue
        lcdMenuLineOne(10);
        subMenu = WerksWerte(subMenu);
        break;
      case 10:                          // Gehe zu  Speichern-Menue
        lcdMenuLineOne(11);
        subMenu = Speichern(subMenu);
        break;
      case rotaryMax:                   // Hauptmenue verlassen
        Serial.println(F("Button pressed Hauptmenue verlassen"));
        lcd.setCursor(0, 0);
        lcd.print(ver);
        minutealt = 61;
        subMenu = false;
        menuRotary = 0;
        break;
    }
  if (subMenu) lcdInitMerker = true; else lcdInitMerker = false; //
  return (menuRotary);
}





void lcdMenuLineOne(const uint8_t auswahl)  // Inhalt der ersten Zeile des LCD-Menu
{
  lcd.setCursor(0, 0);
  const char *line1[] = {"*  Hauptmenue  *" , "* Turm-Anzeige *" , "*  Turm-Licht  *" ,
                         "*  Helligkeit  *" , "*  RGB-1 Werte *" , "*  Fahrspuren  *" , "* Startanzeige *" ,
                         "*   IDNummer   *" , "*   Funkkanal  *" , "*  WerksWerte  *" , "*   Speichern  *"
                        };
  lcd.print(line1[auswahl]);
  //Serial.print(F("Hauptmenu1 "));
  //Serial.println(line1[auswahl]);
}
void lcdMenuLineTwo(const uint8_t auswahl) // Inhalt der zweiten Zeile des Lcd-Menu im Hauptmenu
{
  lcd.setCursor(0, 1);
  const char *line2[] = {"  Turm-Anzeige >" , "<  Turm-Licht  >" , "<  Helligkeit  >",
                         "<  LED-Farben  >" , "< Spurenanzahl >" , "< Startanzeige >" , "<   ID-Nummer  >",
                         "<   Funkkanal  >" , "<  Werks-Werte >" , "<  Speichern   >" , "<   Verlassen   "
                        };
  lcd.print(line2[auswahl]);
  Serial.print(F("Hauptmenu2 "));
  Serial.println(line2[auswahl]);
  //  printLcdLine(1,line2[auswahl]);
}

// TAB RGB
uint8_t ledFarben(uint8_t menuRotary)
{
  // my_xy_Erinnerung: 3 WS RGB -> 2D Array!
  const uint8_t rotaryMin = 1;
  const uint8_t rotaryMax = sizeof(rgbLicht) / 3 + 1; // Wenn das mal erweitert wird...
  static uint8_t colorType = 0;    // 1=rt 2=gn 3=bl 4=Weiter/Ende
  static uint8_t ledType = 0;      // zaehlt die Einsatzorte durch
  uint8_t tempRotary = 0;          // temporäre Aufnahme RotaryWert
  if (menuRotary == 0)             // erster Aufruf der Einstellung
  {
    colorType = 1;
    ledType = 0 ;
    lcd.clear();
    lcdSubLedOne(ledType);
    lcd.blink();
    lcd.setCursor(0, 1);
    lcdSubLedTwo(ledType, 0);      // 0 löst Init für LCD aus!
    menuRotary++;                  // setzt != 0 für Dauerschleife
    Serial.println(colorType);
    Serial.print(F("RGB-"));
    Serial.println(ledType);
  }
  /*
    // holt die aktuellen Werte des Stripe
    uint32_t pixelRGB = pixel.getPixelColor(i);
    uint8_t rgbLicht[ledType][0] = (uint8_t)((pixelRGB >> 16) & 0xff),
          rgbLicht[ledType][1] = (uint8_t)((pixelRGB >> 8) & 0xff),
          rgbLicht[ledType][2] = (uint8_t)(pixelRGB & 0xff);
  */
  switch (colorType)
  {
    case rotaryMin:
      tempRotary = getRotary(rgbLicht[ledType][0], 0, 255); // rot für WS Nummer: ledType
      if (rgbLicht[ledType][0] != tempRotary)
      {
        rgbLicht[ledType][0] = tempRotary;
        lcdSubLedTwo(ledType, colorType);
      }
      break;
    case 2:
      tempRotary = getRotary(rgbLicht[ledType][1], 0, 255); // gruen für WS Nummer: ledType
      if (rgbLicht[ledType][1] != tempRotary)
      {
        rgbLicht[ledType][1] = tempRotary;
        lcdSubLedTwo(ledType, colorType);
      }
      break;
    case 3:
      tempRotary = getRotary(rgbLicht[ledType][2], 0, 255); // blau für WS Nummer: ledType
      if (rgbLicht[ledType][2] != tempRotary)
      {
        rgbLicht[ledType][2] = tempRotary;
        lcdSubLedTwo(ledType, colorType);
      }
      break;
    case rotaryMax:
      if (ledType < sizeof(rgbLicht) / 3 - 1) // noch nicht alle WS durch...
      {
        colorType = 1;  // fange an von vorn
        ledType++;     // naechste WS
        lcdSubLedOne(ledType);
        lcdSubLedTwo(ledType, 0);
        Serial.print(F("RGB-"));
        Serial.println(ledType);
        Serial.println(colorType);
      }
      else
      {
        colorType = 0;
        ledType = 0;
        menuRotary = 0;
        lcdMenuLineOne(0);
        lcdMenuLineTwo(3);
      }
      break;
  }
  if (r.buttonPressedReleased(bounce))
  {
    colorType++;
    lcdSubLedTwo(ledType, colorType);
  }
  serialColor(ledType, rgbLicht[ledType][0], rgbLicht[ledType][1], rgbLicht[ledType][2]);
  if (!menuRotary)lcd.noBlink(); // wenn menuRotary wieder auf 0 gesetzt, ist Ende lcd und damit Ende blink
  return menuRotary;
}


void lcdSubLedOne(const uint8_t menuNumber) // lcd Zeile 1 fuer ledFarben
{
  const uint8_t row = 0;
  const char line1[] = {"*    RGB-      *"};
  if (menuNumber == 0)
  {
    lcd.print(line1);
  }
  lcd.setCursor(9, row);
  lcd.print(menuNumber);
}

void lcdSubLedTwo(const uint8_t farbenWert, const uint8_t farbenType) // lcd Zeile 2 fuer ledFarben
{
  const uint8_t row = 1;
  uint8_t col = 3;
  if (farbenType == 1) col = 3;
  if (farbenType == 2) col = 7;
  if (farbenType == 3) col = 11;
  if (farbenType == 0 || farbenType == 1) // alle oder rot
  {
    lcd.setCursor(0, row);
    lcd.print(" ");
    if (rgbLicht[farbenWert][0] < 100) lcd.print(" ");
    if (rgbLicht[farbenWert][0] < 10 ) lcd.print(" ");
    lcd.print(rgbLicht[farbenWert][0]);
  }
  if (farbenType == 0 || farbenType == 2) // alle oder gruen
  {
    lcd.setCursor(4, row);
    lcd.print(" ");
    if (rgbLicht[farbenWert][1] < 100) lcd.print(" ");
    if (rgbLicht[farbenWert][1] < 10 ) lcd.print(" ");
    lcd.print(rgbLicht[farbenWert][1]);
  }
  if (farbenType == 0 || farbenType == 3) // alle oder blau
  {
    lcd.setCursor(8, row);
    lcd.print(" ");
    if (rgbLicht[farbenWert][2] < 100) lcd.print(" ");
    if (rgbLicht[farbenWert][2] < 10 ) lcd.print(" ");
    lcd.print(rgbLicht[farbenWert][2]);
  }
  lcd.setCursor(col, row);
}

void serialColor(const uint8_t t, const uint8_t r, const uint8_t g, const uint8_t b)
{
  static uint32_t oldColor = {0};
  uint32_t myColor = pixel.Color(r, g, b);
  if (oldColor != myColor)
  {
    oldColor = myColor;
    Serial.print(F("RGB: "));
    for (uint8_t i = 0; i < 3; i++)
    {
      Serial.print(rgbLicht[t][i]);
      Serial.print("\t");
    }
    Serial.println();
  }
}


void neoPixels()
{
  static unsigned long rgbLastColor[sizeof(rgbLicht) / 3] = {0}; // Farbwertmerker
  for (uint8_t i = 0; i < sizeof(rgbLicht) / 3 ; i++)
  {
    if (rgbLastColor[i] != pixel.Color(rgbLicht[i][0], rgbLicht[i][1], rgbLicht[i][2]))
    {
      rgbLastColor[i] = pixel.Color(rgbLicht[i][0], rgbLicht[i][1], rgbLicht[i][2]);
      if (i == 0)
      {
        pixel.fill(rgbLastColor[i], 0, 1);         // Stripe setzen - Farbe, ab LED 0
        Serial.println(F("Pixel 0: "));
      }
      else if (i == 1)
      {
        pixel.fill(rgbLastColor[i], 1, 3);         // Stripe setzen - Farbe, ab LED 1
        Serial.println(F("Pixel 1: "));
      }
      else if (i == 2)
      {
        pixel.fill(rgbLastColor[i], 4, 3);         // Stripe setzen - Farbe, ab LED 3
        Serial.println(F("Pixel 2: "));
      }
      pixel.show();
      Serial.println(rgbLastColor[i]);
    }
  }
}

// TAB Grundausstattung

uint8_t Helligkeit(uint8_t menuRotary)
{
  if (r.buttonPressedReleased(bounce))
  {
    Serial.println(F("Button pressed Helligkeit -> zurück ins Hauptmenu"));
    lcdMenuLineOne(0);
    lcdMenuLineTwo(2);
    return false;
  }
  return menuRotary;
}


uint8_t Fahrspuren(uint8_t menuRotary)
{
  if (r.buttonPressedReleased(bounce))
  {
    Serial.println(F("Button pressed Fahrspuren -> zurück ins Hauptmenu"));
    lcdMenuLineOne(0);
    lcdMenuLineTwo(4);
    return false;
  }
  return menuRotary;
}

uint8_t Startanzeige(uint8_t menuRotary)
{
  if (r.buttonPressedReleased(bounce))
  {
    Serial.println(F("Button pressed Startanzeige -> zurück ins Hauptmenu"));
    lcdMenuLineOne(0);
    lcdMenuLineTwo(5);
    return false;
  }
  return menuRotary;
}

uint8_t IDNummer(uint8_t menuRotary)
{
  if (r.buttonPressedReleased(bounce))
  {
    Serial.println(F("Button pressed ID-Nummer -> zurück ins Hauptmenu"));
    lcdMenuLineOne(0);
    lcdMenuLineTwo(6);
    return false;
  }
  return menuRotary;
}


uint8_t Funkkanal(uint8_t menuRotary)
{
  if (r.buttonPressedReleased(bounce))
  {
    Serial.println(F("Button pressed Funkkanal -> zurück ins Hauptmenu"));
    lcdMenuLineOne(0);
    lcdMenuLineTwo(7);
    return false;
  }
  return menuRotary;
}


uint8_t WerksWerte(uint8_t menuRotary)
{
  if (r.buttonPressedReleased(bounce))
  {
    Serial.println(F("Button pressed Werks-Werte -> zurück ins Hauptmenu"));
    lcdMenuLineOne(0);
    lcdMenuLineTwo(8);
    return false;
  }
  return menuRotary;
}

uint8_t Speichern(uint8_t menuRotary)
{
  if (r.buttonPressedReleased(bounce))
  {
    Serial.println(F("Button pressed Speichern -> zurück ins Hauptmenu"));
    lcdMenuLineOne(0);
    lcdMenuLineTwo(9);
    return false;
  }
  return menuRotary;
}

// Tab Turmanzeige
uint8_t turmAnzeige(uint8_t menuRotary)         // Turmanzeige-menue "Was soll Turm anzeigen werden: Rundenzeit / Rundenanzahl / Uhr_Datum / Extern "
{
  const uint8_t rotaryMin = 1;
  const uint8_t rotaryMax = 5;
  uint8_t tikRotary = getRotary(menuRotary, rotaryMin, rotaryMax);
  if (menuRotary != tikRotary)
  {
    menuRotary = tikRotary;
    lcdSubTurmLineTwo(tikRotary-1);               // untere Zeile im lcd
  }
  if (r.buttonPressedReleased(bounce))
  {
    Serial.print(F("Button pressed Turmanzeige -> Wert: "));
    Serial.println(tikRotary);
    switch (menuRotary)
    {
      case rotaryMin:   // Turmanzeige  "Rundenzeit"
      Serial.print(F("Rundenzeit"));
        break;
      case 2:   // Turmanzeige  "Rundenanzahl"
      Serial.print(F("Rundenanzahl"));
        break;
      case 3:   // Turmanzeige "Datum / Uhrzeit"
      Serial.print(F("Datum / Uhrzeit"));
        break;
      case 4:   // Turmanzeige "Extern-Auswahl"
      Serial.print(F("Extern-Auswahl"));
        break;
      case rotaryMax:   // Turmanzeige gehe zurück zu Hauptmenue
      Serial.print(F("-> ins Hauptmenu"));
        break;
    }
    Serial.println(F(" ausgewaehlt"));
    Serial.println();
    lcdMenuLineOne(0);
    lcdMenuLineTwo(1);
    return 0;
  }
  return menuRotary;
}

void lcdSubTurmLineTwo(const uint8_t auswahl) // Inhalt der zweiten Zeile des Lcd-Menu im Turmmenu
{
  static uint8_t oldAuswahl = 0;
  lcd.setCursor(0, 1);
  const char *line2[] = {" Zeiten-Anzeige>" , "<Runden-Anzeige>" , "<  Uhr/Datum   >" , "< Ex-Steuerung >" , "<    Zurueck    "};
  lcd.print(line2[auswahl]);
  if (oldAuswahl != auswahl)
  {
    oldAuswahl = auswahl;
    Serial.print(F("SubMenu Tumranzeige: "));
    Serial.println(line2[auswahl]);
  }
}
// TAB Turmlicht
uint8_t turmLicht(uint8_t menuRotary)         // Was soll Turm leuchten..... EG-Licht an / OG-Licht an / EG/OG-Licht an / Licht Aus
{
  const uint8_t rotaryMin = 1;
  const uint8_t rotaryMax = 5;
  uint8_t tikRotary = getRotary(menuRotary, rotaryMin, rotaryMax);
  if (menuRotary != tikRotary)
  {
    menuRotary = tikRotary;
    lcdSubLichtLineTwo(tikRotary-1);            // untere Zeile im lcd
  }
  if (r.buttonPressedReleased(bounce))
  {
    Serial.print(F("Button pressed Turmlicht -> Wert: "));
    Serial.println(tikRotary);
    switch (tikRotary)
    {
      case 1:   // Turmlicht "Licht EG" an
        Serial.print(F("Licht EG an"));
        break;
      case 2:   // Turmlicht "Licht OG" an
        Serial.print(F("Licht OG an"));
        break;
      case 3:   // Turmlicht "Licht EG / OG" an
        Serial.print(F("Licht EG / OG an"));
        break;
      case 4:   // Turmlicht "Licht aus"
        Serial.print(F("Licht aus"));
        break;
      case 5:   // zurück zum Hauptmenue
        Serial.print(F("-> ins Hauptmenu"));
        break;
    }
    Serial.println(F(" ausgewaehlt"));
    Serial.println();
    lcdMenuLineOne(0);
    lcdMenuLineTwo(1);
    return 0;
  }
  return (menuRotary);
}

void lcdSubLichtLineTwo(const uint8_t auswahl) // Inhalt der zweiten Zeile des Lcd-Menu im Lichtmenu
{
  static uint8_t oldAuswahl = 0;
  lcd.setCursor(0, 1);
  const char *line2[] = {"   Licht EG    >", "<  Licht OG    >", "<  Licht EG/OG >", "<  Licht-Aus   >", "<    zurueck    "};
  lcd.print(line2[auswahl]);
  if (oldAuswahl != auswahl)
  {
    oldAuswahl = auswahl;
    Serial.print(F("SubMenu Lichtanzeige: "));
    Serial.println(line2[auswahl]);
  }
}

Na denne.

Abend Männer's

An alle die sich so arrangiert an meinem Projekt beteiligen, möchte ich meinen Dank aussprechen.

Das kann ich heute gut gebrauchen.....hab den ganzen Tag versucht das Menue auf die Matrix-Anzeige zu übertragen. Nicht ganz einfach ohne Lcd :wink:

Hab ich gemacht, und auch nur das.Hat im SerMon und LCD funktioniert weiter mit Schritt 3

Im Prinzip ja, jetzt beim spielen mit dem Farben ist mir aufgefallen das es schön wäre wenn man im z.B. RGB-0 menue zwischen den 3 Werte wählen könnte und dann mit 4´ten Menuepunkt das RGB-0 Menue verlässt oder zum nächsten RGB-1 Menue kommt. Aber das ist jammern auf hohen Niveau. Aber Du hast Recht mit den Wert durchlaufen lassen, zumindest im RGB Menue macht das Sinn :wink:
Aber weißt Du wer wieder da ist....? Der Zufallsgenerator :grinning:

Aber unglaublich was für ein Umfang das ganze im Laufe der Zeit angenommen hat. Ich bin Dir unendlich Dankbar und ich weiß nicht wie ich mich dafür revanchieren kann.
Sag doch Bitte mal was....

Ja.

Damit ist alles gesagt.
Achso. Da gestern der LiveStream ja irgendwann auch mal ein Ende fand, hier zum nachhören für 1(?) Woche - Keine Ahnung wie lange der online ist - ich hab das hier lokal.
Samstags Rummelbumms (Original so auf der Homepage)
Mugge zum aufwärmen
Das war dann mein Tagesausklang...

So und nu ab in die heia: https://www.youtube.com/watch?v=G_qdVnZlqj8

Der war gemein.... :wink:
Schade :cry:
Euch allen ein Gut Nächtle, und wie immer, bleibt gesund.
Das Tal ist leider z.Zeit Spitzenreiter in der Inzidenz-werten :sob:

Alter!
Das ist ja der Hammer!

Der Code von gestern:

Der Sketch verwendet 13678 Bytes (5%) des Programmspeicherplatzes. Das Maximum sind 253952 Bytes.
Globale Variablen verwenden 1236 Bytes (15%) des dynamischen Speichers, 6956 Bytes für lokale Variablen verbleiben. Das Maximum sind 8192 Bytes.

Ich habe heute einfach nur gespielt mit dem Hauptmenu:

Der Sketch verwendet 13648 Bytes (5%) des Programmspeicherplatzes. Das Maximum sind 253952 Bytes.
Globale Variablen verwenden 820 Bytes (10%) des dynamischen Speichers, 7372 Bytes für lokale Variablen verbleiben. Das Maximum sind 8192 Bytes.

Das ist ja ein echtes Dingens...
Na mal schaun, was ich noch bastel.

Und ich beschäftige mich mit den Nebenthemen, in diesem Beitrag mit der Harmonie zwischen IDE, seriellem Monitor und LCD.

UTF-8

  • Die IDE verwendet in der aktuellen Version UTF-8 als Zeichensatz. in der UTF-8-Codetabelle mit Unicode-Zeichen findet man beispielsweise "ü" als 0xc3bc kodiert, benötigt also zwei Byte.
  • Der serielle Monitor gibt UTF-8 richtig aus.
  • Auf LCDs mit HD44870 wird kein UTF-8 unterstützt, weshalb man für eine gute Darstellung auf dem LCD die Buchstaben so "umbiegt", daß der serielle Monitor Müll anzeigt.

In der Bibliothek von @noiasca gibt es die Möglichkeit, eine Umkodierung von UTF-8 auf ein auf dem LCD darstellbares Zeichen vorzunehmen. Damit kann die selbe Zeichenkette sowohl zum seriellen Monitor wie auch zum LCD geschickt werden.

Zeichenkettenlänge

Um beispielsweise zentrierten Text auf dem LCD darstellen zu können, benötigt man die Anzahl der sichtbaren Zeichen. Im Thema Frage zu CharArray strlen() und Umlaut wurde dies erfolgreich gelöst.

Darstellung kleiner Buchstaben mit Nutzung der Unterlänge

Die kleinen Buchstaben g, j, p, q und y werden von HD44870 ohne Unterlänge dargestellt, was die Lesbarkeit erschwert. Mit Hilfe der benutzerdefinierten Zeichen kann man zumindest eine leichte Verbesserung erreichen.

Lösung

Zusammengefaßt sieht das dann so aus:

Anzeige im seriellen Monitor:

Start...
←  Hauptmenü   →
← Turm-Anzeige →
←  Turm-Licht  →
←  Helligkeit  →
← RGB-1 Werte  →
←  Fahrspuren  →
← Startanzeige →
←   IDNummer   →
←  Funkkanal   →
←  WerksWerte  →
←    zurück    →

Weil beispielsweise "Hauptmenü" ein Byte mehr im Speicher benötigt, als Buchstaben sichtbar sind, macht die Berechnung der Zentrierung plötzlich richtig Sinn!

Das Programm stammt überwiegend von @noiasca, die Bibliothek ja sowieso, also Spot an und Applaus!

Wer es mal probieren möchte:

#include <Streaming.h>                           // http://arduiniana.org/libraries/streaming/
#include <Wire.h>
#include <NoiascaLiquidCrystal.h>                // download library from https://werner.rothschopf.net/202009_arduino_liquid_crystal_intro.htm
#include <utility/NoiascaCustomCharacters.h>     // file with several predefined custom characters
#include <NoiascaHW/lcd_PCF8574.h>               // I2C PCF8574 expander

using ConstStrPtr  = const char *;
const unsigned menueStrLen {12};
const unsigned menuePunkte {11};
using FlashStrPtr  = const __FlashStringHelper *;
using MenuePunkt   = const char[menueStrLen + 1];

uint8_t convert_local (uint32_t &special, uint8_t &value)
{
  uint8_t isWriteable = NOPRINT;
  if ((value & 0b11000000) == 0b11000000)        // start byte
  {
    special = value;
  }
  else if ((value & 0b11000000) == 0b10000000)   // sequence byte (Folgebyte)
  {
    special = special << 8;            // MISSING should get improvement against 3rd following byte
    special += value;
    if (special > 0xC000 && special < 0xE000)    // if 2 byte character
    {
      isWriteable = PRINT;
    }
    else if (special > 0xE00000 && special < 0xF0000000)   // 3 byte character: 0b11100000 00000000 00000000 im dritten byte von hinten
    {
      isWriteable = PRINT;
    }
    else if (special > 0xF0000000)               // 4 byte character: 0b11110000 ganz vorne
    {
      isWriteable = PRINT;
    }
  }
  else                                           // not a sequence byte - reset special
  {
    special = 0;
  }
  if (special == 0)          // Single Character
  {
    switch ( value)          // overwrite missmatching characters between UTF-8 and charset of ROM Code A00
    {
      case 0x5c :            // REVERSE SOLIDUS (better known as backslash)
        value = '|';         // no better alternativ in char set A00
        break;
      case 0x7e :            // tilde ~
        value = '-';
        break;
    }
    // check against defined character array
    for (byte i = 0; i < 8; i++)
    {
      if (value == utf8_to_customChar[i])
      {
        value = i;
        break;               // found, no need to continue the search
      }
    }
    isWriteable = PRINT;
  }
  else if (isWriteable)      // was UTF-8 2 bytes or more
  {
    // Step 1: general ROM Mapping
    // Lookup within UTF8->ROM Mapping overrides the character to be send to the LCD
    uint16_t index = 0;
    for (size_t i = 0; i < sizeof(ROM_A00) / sizeof(ROM_A00[0]); i++)
    {
      index = pgm_read_dword_near(&ROM_A00[i].utf8);
      if (index == (special & 0xFFFF))
      {
        memcpy_P(&value, &ROM_A00[i].c, 1);
        break;
      }
    }
    // Step 2: Special character overrule
    for (byte i = 0; i < 8; i++)
    {
      if (special == utf8_to_customChar[i])
      {
        value = i;
        break;               // found, no need to continue the search
      }
    }
    // Step 3: Send
    // any other character just send to display
    special = 0;
    // Step 4:
  }
  return isWriteable;                  // assume success
}

const byte addr = 0x27;
const byte cols = 16;                            // columns/characters per row
const byte rows = 2;                             // how many rows
LiquidCrystal_PCF8574 lcd(addr, cols, rows, convert_local);         // create lcd object I2C

// definiert kleine Buchstaben mit Nutzung der Unterlänge g, j, p, q und y
const byte latin_small_g_descender[] PROGMEM = {0b00000, 0b00000, 0b01110, 0b10001, 0b10001, 0b01111, 0b00001, 0b01110};
const byte latin_small_j_descender[] PROGMEM = {0b00001, 0b00000, 0b00001, 0b00001, 0b00001, 0b00001, 0b01001, 0b00110};
const byte latin_small_p_descender[] PROGMEM = {0b00000, 0b00000, 0b01110, 0b10001, 0b10001, 0b11110, 0b10000, 0b10000};
const byte latin_small_q_descender[] PROGMEM = {0b00000, 0b00000, 0b00110, 0b01001, 0b01001, 0b00111, 0b00001, 0b00001};
const byte latin_small_y_descender[] PROGMEM = {0b00000, 0b00000, 0b01001, 0b01001, 0b01001, 0b00111, 0b00001, 0b01110};

void customCharacterMapping_local()
{
  //  This UTF8 code will become  -->   that custom character
  utf8_to_customChar[0] = 'g';       lcd.createChar_P(0, latin_small_g_descender);
  utf8_to_customChar[1] = 'j';       lcd.createChar_P(1, latin_small_j_descender);
  utf8_to_customChar[2] = 'p';       lcd.createChar_P(2, latin_small_p_descender);
  utf8_to_customChar[3] = 'q';       lcd.createChar_P(3, latin_small_q_descender);
  utf8_to_customChar[4] = 'y';       lcd.createChar_P(4, latin_small_y_descender);
  utf8_to_customChar[5] = 0xC384;    lcd.createChar_P(5, latin_capital_a_diareses);      // Ä comes with the library
  utf8_to_customChar[6] = 0xc396;    lcd.createChar_P(6, latin_capital_o_diareses);      // Ö comes with the library
  utf8_to_customChar[7] = 0xc39c;    lcd.createChar_P(7, latin_capital_u_diareses);      // Ü comes with the library
}

MenuePunkt menue[menuePunkte] PROGMEM
{
  {"Hauptmenü"},
  {"Turm-Anzeige"},
  {"Turm-Licht"},
  {"Helligkeit"},
  {"RGB-1 Werte"},
  {"Fahrspuren"},
  {"Startanzeige"},
  {"IDNummer"},
  {"Funkkanal"},
  {"WerksWerte"},
  {"zurück"}
};

FlashStrPtr lcdHauptMenu(byte index)
{
  return FlashStrPtr(&menue[index]);
}

uint8_t txtlaenge(byte index)
{
  char buf[menueStrLen];
  strcpy_P(buf, ConstStrPtr(&menue[index]));
  int len = utf8_strlenV4(buf);
  return len;
}

size_t utf8_strlenV4(const char * str)  // https://forum.arduino.cc/t/frage-zu-chararray-strlen-und-umlaut/897224/12
{
  int len = 0;
  while (*str) len += (*str++ & 0xc0) != 0x80;
  return len;
}

void setup()
{
  Serial.begin(9600);
  Serial.println(F("Start..."));
  lcd.begin();
  lcd.backlight();
  customCharacterMapping_local();
  lcd.setCursor(0, 0);

  const uint8_t gesamt = 14;
  uint8_t txtl = 0;
  uint8_t vor = 0;
  uint8_t nach = 0;

  lcd.setCursor(0, 1);
  lcd << lcdHauptMenu(0);
  lcd.setCursor(0, 2);
  lcd << lcdHauptMenu(1);
  lcd.setCursor(0, 3);
  lcd << "ÄÖÜäöüßgpq";

  for (uint8_t i = 0; i <= 10; i++)
  {
    lcd.setCursor(0, 0);
    txtl = txtlaenge(i);
    vor = 0; nach = 0;
    if ((gesamt - txtl) > 1)
    {
      vor = (gesamt - txtl) / 2;
      nach = gesamt - (vor + txtl);
    }
    Serial << "←";
    lcd << "←";
    while (vor >= 1)
    {
      Serial << ' ';
      lcd << ' ';
      vor--;
    }
    Serial << lcdHauptMenu(i);
    lcd << lcdHauptMenu(i);
    while (nach >= 1)
    {
      Serial << ' ';
      lcd << ' ';
      nach--;
    }
    Serial << "→" << '\n';
    lcd << "→";
    delay(1000);
  }
}

void loop() {}

Wegen der Vergleichbarkeit habe ich es bewußt im Originalzustand gelassen. Versteckt man also das Vorgeplänkel in einer eigenen Datei, ändert sich nichts beim Menü.

Fazit

Meine Empfehlung an Dich ist, die Verwendung der Bibliothek von @noiasca wegen der Gleichbehandlung der UTF-8-Zeichen in Erwägung zu ziehen. Ob der höhere Speicherverbrauch den Nutzen aufwiegt, muß im Gesamtzusammenhang beurteilt werden, da halte ich mich zurück.

Die Darstellung der kleinen Buchstaben durch benutzerdefinierte Zeichen als Teil der Bibliothek wurde mir für eine spätere Version versprochen. Dadurch würde convert_local() entfallen.

Ich habe durch die Beschäftigung mit diesem Thema schön was gelernt und wollte das Ergebnis nicht für mich behalten, auch wenn Du eventuell nichts übernimmst.


@combie: Ich habe es schon wieder getan, ich habe strcpy_P() verwendet, weil ich es nicht besser hinbekommen habe :flushed:


Antwort zu #380:

Aus #379 kann die Funktion utf8_strlenV4() gelöscht und txtlaenge() wie folgt ersetzt werden:

uint8_t txtlaenge(byte index)  // https://forum.arduino.cc/t/frage-zu-chararray-strlen-und-umlaut/897224/12
{
  const char * str = ConstStrPtr(&menue[index]);
  int len = 0;
  while (pgm_read_byte(str)) len += (pgm_read_byte(str++) & 0xc0) != 0x80;
  return len;
}

@combie: Ich dachte schon, ich hätte alles probiert, aber pgm_read_byte(str) ist mir leider nicht in den Sinn gekommen.

Danke!

1 Like

Ich verzeihe dir! :japanese_ogre:

Zudem ist "Besser" immer so eine Sache.....

#include <Streaming.h> // die Lib findest du selber ;-)
Stream &cout {Serial}; // cout Emulation für AVR Arduinos

using FlashStrPtr  = const __FlashStringHelper *;


size_t utf8_strlenV4(const char * str)  // https://forum.arduino.cc/t/frage-zu-chararray-strlen-und-umlaut/897224/12
{
  int len = 0;
  while (*str) len += (*str++ & 0xc0) != 0x80;
  return len;
}

size_t utf8_strlenV4_P(const char * str)  // https://forum.arduino.cc/t/frage-zu-chararray-strlen-und-umlaut/897224/12
{
  int len = 0;
  while (pgm_read_byte(str)) len += (pgm_read_byte(str++) & 0xc0) != 0x80;
  return len;
}

size_t utf8_strlenV4(FlashStrPtr str)  // https://forum.arduino.cc/t/frage-zu-chararray-strlen-und-umlaut/897224/12
{
  char *ptr = (char *) str;
  int len = 0;
  while (pgm_read_byte(ptr)) len += (pgm_read_byte(ptr++) & 0xc0) != 0x80;
  return len;
}


const char testP[] PROGMEM = "öoükoßujzöücc";
const char testR[] = "öoükoßujzöücc";


void setup() 
{
  Serial.begin(9600);
  cout << F("Start: ") << F(__FILE__) << endl;
  cout << strlen(testR) << endl;
  cout << strlen_P(testP) << endl;
  cout << utf8_strlenV4(testR) << endl;
  cout << utf8_strlenV4_P(testP) << endl;
  cout << utf8_strlenV4(FlashStrPtr(testP)) << endl;
}

void loop() 
{

}
1 Like

Es gibt nur 0-10 Menu-Einträge, aber 1-11 cases. Ich wollte ja auch nur sehen, ob das mit der Einstellerei geht :wink:

Mal ne ganz andere Frage: Wenn Du die Helligkeit stellen willst, warum machst Du das einzeln und nicht im RGB-Menu? Wenn ich inhaltlich 4 byte anzeigen lassen mit einem Space zwischendurch, sind das 4*3+3 Char.

Jaja. :slight_smile:

Dann musste mal warten - ich seh mal zu, was sich machen lässt.
Momentan bin ich auf Sparkurs.

Man liest sich.

Abend miteinander.....

Weil die Helligkeit für die TM1637 Anzeigen 1 bis 8 sind, und die RGB für die Farben der WS im Turm. Die Farben haben ja nichts mit den 7 Segmentanzeigen zu tun, daher würde ich nicht bei den RGB-Farben die Möglichkeit suchen die Helligkeit der TM1637 zu ändern. Oder siehst Du das anders?

das war, oder vielmehr ist jammern auf hohen Niveau....sollte nicht heißen Du sollst. :wink:

Alles gut xy Projekt, alles kann nichts muss. Bin Dir ja ehh schon tausendfach Dankbar. Ohne Dich wäre das Projekt aller wahrscheinlich nach, eh nicht so schön geworden wie es jetzt schon ist.

Aber ich habe mal grundsätzlich noch ein paar Fragen......
Warum ist es so wichtig Speicherschonend zu programmieren?
Kann man nicht den zu Verfügung gestellten Speicher des Boards ausnutzen?

Das programmieren gefällt mir als Hobby recht gut und macht mir Spaß wenn´s andere machen
neee eben nicht, ich möchte es selber können. Wie sollte ein Anfänger wie ich es bin....
Null Vorkenntnisse, "C" für den dritten Buchstaben im Alphabet gehalten... :wink:
am besten anfangen oder bzw. weitermachen?

Ein Buch lesen, ja kann mir einer da eins empfehlen?
Oder besser irgendwie / wo ein Grundkurs belegen? Für die Uni bin ich ein paar Tage zu alt.
Obwohl praktisch wär`s......50 Schritte und ich wäre da. :smiley:

Gut, man lernt auch durch abgucken und ausprobieren.....aber dieser weg macht mich nicht glücklich weil ich die ganzen "Kurz Schreibweisen" nicht immer nachvollziehen kann.
Wäre für jeden ernst gemeinten Tipp Dankbar.