Weiterentwicklung einer Software zur Funktionsreife

Oder ich habe die Frage nicht kapiert.
So wie ich das mal schon sehr viel frühererem Code testete, waren ja schon HID Codes hinterlegt, ich brauch nicht mehr als jeweils ein Buchstaben und ein Enter auf dem HID -Output zum PC hin, danach gehts mit Shell SKripten weiter..
Vielleicht habe ich daher auch nicht die Frage nicht verstanden und bezog das auf die Char :wink:
Es sollen also nicht mehr als nur je ein Zeichen und Enter versendet werden, genau wie in dem Excel angegeben.
Oder meintest Du noch was anderes?

Wenn das mit wenig Aufwand geht, auch auskomentieren wäre denkbar, aber für mich ist es nicht so relevant, da es ja im Setupmenü versteckt ist.

Derzeit gibt es diese Funktion:

void sendHid(char *zeichen)
{
  Serial.print(F("HID: "));
  Serial.println(zeichen);
}

Da wird nur ein Buchstabe in den seriellen Monitor geschrieben, mit dem Text "HID" davor. Mit dem von Dir gewünschten Human Interface Device hat das aber nur wenig zu tun. Du möchtest Tastaturdrücke simuliert haben, also als wenn jemand auf der Tastatur klimpert.

In meinem Beitrag im vorhergehenden Thema sehen die Tastaturdrücke im Programm so aus:

  const char * text[5] = {"e", "r", "t", "z", "y"};
  const uint8_t code[5][8] = {
    {0, 0,  8, 0, 0, 0, 0, 0},
    {0, 0, 21, 0, 0, 0, 0, 0},
    {0, 0, 23, 0, 0, 0, 0, 0},
    {0, 0, 28, 0, 0, 0, 0, 0},
    {0, 0, 29, 0, 0, 0, 0, 0}
  };

Das ist, wonach @my_xy_projekt Dich fragt.

In der Excel-Tabelle steht nur die Beschriftung der Tastatur
grafik
nicht aber der Code, also diese acht Bytes.

Auch hast Du im anderen Thema geschrieben, Du hast das mit "Enter" gelöst:

{0, 0,  8, 40, 0, 0, 0, 0}

Das müßte also "e" mit "Enter" sein.

Ja - Nein - Eventuell?

Achso...
Ich muss jetzt erst mal ausprobieren was ich da gesehen habe...
Ich meine aber ich hätte mal code aus den Thread hier auf den Uno geschrieben und hätte die Erfahrung gemacht, dass der PC abgeschmiert ist, genau wie wenn man den Tastaturbuffer vollschreibt, daher meine aussagen:

Mein Gedächtnis lässt mal wieder grüßen...
Ich war der Meinung, dass da schon HID raus kamen, aber ich muss mich erst mal auf einen aktuellen Stand bringen.

völlig ok, das funktionierte mit dem "Anhängen" im String, ich brauch kein extra Enter

Da @my_xy_projekt derzeit Pause machen möchte/muß, ändere ich das Programm in dieser Richtung:

//#include <Adafruit_CharacterOLED.h>

//Adafruit_CharacterOLED display(OLED_V2, 4, 6, 5, 7, 8, 9, 10);
#include <LiquidCrystal_I2C.h>
const byte lcdAddress = 0x27;
const byte lcdZeilen = 4;
const byte lcdSpalten = 20;
LiquidCrystal_I2C display(lcdAddress, lcdSpalten, lcdZeilen);

// Rotary
#include <rotary.h>
Rotary r = Rotary(45, 43);
const byte rotBut = 41;
const byte mutBut = 12;
const byte mutLed = 255;
enum {kurz = 1, lang};

const uint32_t bounceTime = 20; // zeit in ms

const char chars[] = {"ABCDEFGHIJKLMNOPQRSTUVWXYZ abcdefghijklmnopqrstuvwxyz1234567890"};

// Inhalt
struct inhalt
{
  char name[lcdSpalten + 1];
  char hid[2];
  uint8_t code[8];
};

inhalt MyFilter[]
{
  {"Filter 1", "a", {0, 0,  8, 40, 0, 0, 0, 0}}, {"Filter 2", "s", {0, 0, 21, 40, 0, 0, 0, 0}},
  {"Filter 3", "d", {0, 0, 23, 40, 0, 0, 0, 0}}, {"Filter 4", "f", {0, 0, 28, 40, 0, 0, 0, 0}}
};

inhalt MySetup[]
{
  {"Auswahl",       "",  {0, 0,  0,  0, 0, 0, 0, 0}}, {"Displayzeit",   "",  {0, 0,  0,  0, 0, 0, 0, 0}},
  {"Wlan",          "g", {0, 0,  0,  0, 0, 0, 0, 0}}, {"Filternamen",   "",  {0, 0,  0,  0, 0, 0, 0, 0}},
  {"Platzhalter 1", "j", {0, 0, 29, 40, 0, 0, 0, 0}}, {"Platzhalter 2", "k", {0, 0, 29, 40, 0, 0, 0, 0}},
  {"Platzhalter 3", "l", {0, 0, 29, 40, 0, 0, 0, 0}}, {"Platzhalter 4", "q", {0, 0, 29, 40, 0, 0, 0, 0}},
  {"Platzhalter 5", "w", {0, 0, 29, 40, 0, 0, 0, 0}}, {"exit",          "",  {0, 0,  0,  0, 0, 0, 0, 0}}
};

uint16_t displayZeit = 20; // Zeit in Sekunden
uint32_t displayTick = 0;

bool isMenu = true;
bool isSetup = !isMenu;
bool isMute = false;

void setup()
{
  Serial.begin(115200);
  Serial.println(F("Start..."));
  //startSequenz();
  pinMode (rotBut, INPUT);
  pinMode (mutBut, INPUT_PULLUP);
  pinMode (mutLed, OUTPUT);
  display.begin();
}
//
void loop()
{
  if (isMute)
  {
    heartbeat(500);
  }
  else
  {
    if (digitalRead(mutLed))
    {
      digitalWrite(mutLed, LOW);
    }
    if (isMenu) myMenu();
    if (isSetup) mySetup();
  }
  getMute();
  isDisplay();
}
//
void myMenu()
{
  const char displayZeileOben[] = "Filter waehlen";
  const uint8_t maxPos = sizeof(MyFilter) / sizeof(inhalt) - 1;
  static int8_t rotaryPos = 0;
  static int8_t lastRotaryPos = maxPos + 1;
  if (isDisplay())
  {
    rotaryPos = getRotary(rotaryPos);
    if (rotaryPos != lastRotaryPos)
    {
      if (rotaryPos > maxPos) rotaryPos = 0;
      else if (rotaryPos < 0) rotaryPos = maxPos;
      lastRotaryPos = rotaryPos;
      Serial.print(F("menu "));
      Serial.println(rotaryPos);
      ausgabe(displayZeileOben, MyFilter[rotaryPos].name);
    }
  }
  else rotaryPos = 0;
  switch (getTaste())
  {
    case kurz: sendHid(MyFilter[rotaryPos].hid, MyFilter[rotaryPos].code); break;
    case lang:
      {
        isMenu = false; isSetup = true;
        lastRotaryPos = maxPos + 1;
      }
      break;
  }
}
//
void displayTime(char *zeile1, uint16_t &sekunde)
{
  setupZahl(sekunde);
  if (sekunde < 5) sekunde = 5;
  char buf[10] = {'\0'};
  sprintf(buf, "%d", sekunde);
  ausgabe(zeile1, buf);
}
//
void menuEnde(int8_t &menuPos, char *zeile1, char *zeile2)
{
  menuPos = 0;
  ausgabe(zeile1, zeile2);
}
//
void menuAuswahl(uint8_t &rotaryPos, uint8_t &lastRotaryPos, const uint8_t &maxPos, const uint8_t &minPos, const char *zeile1, char *zeile2)
{
  rotaryPos = getRotary(rotaryPos);
  if (rotaryPos != lastRotaryPos)
  {
    if (rotaryPos > maxPos) rotaryPos = minPos;
    if (rotaryPos < minPos) rotaryPos = maxPos;
    lastRotaryPos = rotaryPos;
    Serial.print(F("setup lastRotaryPos2: "));
    Serial.print(lastRotaryPos); Serial.println(' ');
    Serial.print(F("setup RotaryPos: "));
    Serial.print(rotaryPos); Serial.print(' ');
    Serial.println(zeile2);
  }
  ausgabe(zeile1, zeile2);
}
//
void mySetup()
{
  const uint8_t maxPos = (sizeof(MySetup) / sizeof(inhalt)) - 1;
  const uint8_t minPos = 1;
  static uint8_t rotaryPos = minPos;
  static uint8_t lastRotaryPos = maxPos;
  static int8_t menuPos = 0;
  static uint16_t sekunde = 0;
  switch (menuPos)
  {
    case 0:
      menuAuswahl(rotaryPos, lastRotaryPos, maxPos, minPos, MySetup[menuPos].name, MySetup[rotaryPos].name);
      if (getTaste() == kurz) menuPos = rotaryPos;
      if (menuPos == 1)
      {
        sekunde = displayZeit;
      }
      break;
    case 1:
      displayTime(MySetup[menuPos].name, sekunde);
      if (getTaste() == kurz)
      {
        displayZeit = sekunde;
        menuEnde(menuPos, MySetup[0].name, MySetup[menuPos].name);
      }
      break;
    case 2:
      {
        const char zeile2[][12] = {"umschalten", "exit"};
        static int8_t auswahl = 0;
        auswahl = getRotary(auswahl);
        if (auswahl > 1) auswahl = 0;
        else if (auswahl < 0) auswahl = 1;
        ausgabe(MySetup[menuPos].name, zeile2[auswahl]);
        if (getTaste() == kurz)
        {
          if (auswahl == 0) sendHid(MySetup[menuPos].hid, MySetup[menuPos].code);
          menuEnde(menuPos, MySetup[0].name, MySetup[menuPos].name);
        }
      }
      break;
    case 3:
      Serial.println(F("Filternamen"));
      menuEnde(menuPos, MySetup[0].name, MySetup[menuPos].name);
      break;
    case 4 ... 8:
      Serial.println(F("Platzhalter"));
      sendHid(MySetup[rotaryPos].hid, MySetup[rotaryPos].code);
      menuEnde(menuPos, MySetup[0].name, MySetup[menuPos].name);
      break;
    case maxPos:
      menuPos = 0;
      rotaryPos = 0;
      isMenu = true;
      isSetup = false;
      break;
  }
  if (getTaste() == lang)
  {
    isMenu = true; isSetup = false;
  }
  if (!isDisplay())
  {
    menuPos = 0;
    rotaryPos = 0;
    lastRotaryPos = maxPos + 1;
    isMenu = true; isSetup = false;
  }
}
//
void setupZahl(uint8_t &aenderZahl)
{
  uint32_t myZahl = aenderZahl;
  setupZahl(myZahl);
  if (myZahl > 255) myZahl = 255;
  aenderZahl = myZahl;
}
void setupZahl(uint16_t &aenderZahl)
{
  uint32_t myZahl = aenderZahl;
  setupZahl(myZahl);
  if (myZahl > 65535) myZahl = 65535;
  aenderZahl = (uint16_t)myZahl;
}
void setupZahl(uint32_t &aenderZahl)
{
  static uint32_t lastZahl = 0;
  aenderZahl = getRotary(aenderZahl);
  if (aenderZahl != lastZahl)
  {
    lastZahl = aenderZahl;
  }
}
//
void sendHid(char *zeichen, uint8_t *c)
{
  Serial.print(F("HID: "));
  Serial.print(zeichen);
  Serial.print(F("\tCode: "));
  for (byte j = 0; j < 8; j++)
  {
    Serial.print(c[j], HEX);
    Serial.print(' ');
  }
  Serial.println("in hex");

  //Serial.write(c, 8);  // nur diese Zeile bleibt für HID stehen!
}
//
void printDisplay(char *zeile, byte number)
{
  display.setCursor(0, number);
  uint8_t laenge = strlen(zeile);
  for (byte b = 0; b < (lcdSpalten - laenge) / 2; b++)
    display.print(' ');
  display.print(zeile);
  for (byte b = (lcdSpalten - laenge) / 2 + laenge; b < lcdSpalten; b++)
    display.print(' ');
  Serial.print(F("Zeile "));
  Serial.print(number + 1);
  Serial.print(": ");
  Serial.println(zeile);
}
//
void ausgabe(const char *zeile1, const char *zeile2)
{
  static char lastZeileOben[lcdSpalten + 1] = {'\0'};
  static char lastZeileUnten[lcdSpalten + 1] = {'\0'};
  if (strcmp(lastZeileOben, zeile1))
  {
    memset(lastZeileOben, '\0', lcdSpalten + 1);
    strcpy(lastZeileOben, zeile1);
    printDisplay(lastZeileOben, 0);
  }
  if (strcmp(lastZeileUnten, zeile2))
  {
    memset(lastZeileUnten, '\0', lcdSpalten + 1);
    strcpy(lastZeileUnten, zeile2);
    printDisplay(lastZeileUnten, 1);
  }
}
//
bool isDisplay()
{
  static bool isOn = true;
  //Serial.println(displayTick);
  if (millis() - displayTick > displayZeit * 1000UL && !isMute)
  {
    if (isOn)
    {
      display.noDisplay();
      Serial.println(F("Display aus!"));
      isOn = false;
    }
  }
  else
  {
    if (!isOn)
    {
      display.display();
      Serial.println(F("Display an!"));
    }
    isOn = true;
  }
  return isOn;
}
//
void getMute()
{
  static uint32_t lastmillis = 0;
  static bool isPressed = false;
  if (!digitalRead(mutBut) && !isPressed)
  {
    isPressed = true;
    displayTick = millis();
    lastmillis = millis();
    isMute = !isMute;
    if (isMute) ausgabe("Mute aktiv", "");
    Serial.print(F(""));
    char z[] = {"e"};
    uint8_t c[] = {0, 0, 0, 0, 0, 0, 0, 0};
    sendHid(z, c);
  }
  else if ((millis() - lastmillis > bounceTime) &&
           (digitalRead(mutBut) && isPressed))
  {
    isPressed = false;
  }
}
//
uint8_t getTaste()
{
  uint8_t butPress = 0;
  static uint32_t pressTime = 0;
  static byte schritt = 0;
  bool isPressed = !digitalRead(rotBut);
  switch (schritt)
  {
    default:
      if (isPressed)
      {
        pressTime = millis();
        schritt = 1;
      }
      break;
    case 1:
      if (millis() - pressTime > bounceTime)
      {
        isPressed ? schritt = 2 : schritt = 0;
      }
      break;
    case 2:
      if (!isPressed)
      {
        if (millis() - pressTime > 500)
        {
          isDisplay() ? butPress = 2 : displayTick = millis();
          Serial.print(F("lange"));
          schritt = 3;
        }
        else
        {
          isDisplay() ? butPress = 1 : displayTick = millis();
          Serial.print(F("kurz"));
          schritt = 3;
        }
        Serial.println(F(" gedrückt"));
      }
      break;
    case 3:
      if (!isPressed)
      {
        displayTick = millis();
        schritt = 0;
      }
      break;
  }
  return butPress;
}
//
uint32_t getRotary(uint32_t posValue)
{
  unsigned char val = r.process();
  if (val)
  {
    if (val == r.clockwise() && isDisplay())
    {
      Serial.print(F("rechts"));
      posValue++;
    }
    else if (val == r.counterClockwise() && isDisplay())
    {
      Serial.print(F("links"));
      posValue--;
    }
    Serial.println(F(" gedreht"));
    displayTick = millis();
  }
  return posValue;
}
//
void heartbeat(const uint32_t tik)
{
  static uint32_t lasttik = 0;
  if (millis() - lasttik >= tik)
  {
    lasttik += tik;
    digitalWrite(mutLed, !digitalRead(mutLed));
  }
}
//
void startSequenz()
{
  uint32_t startTime = millis();
  ausgabe("Willkommen", "");
  while (millis() - startTime < 1500) {}
  startTime = millis();
  ausgabe("Booting", "");
  char zeile2[lcdSpalten + 1] = {'\0'};
  uint8_t pos = 0;
  while (pos < lcdSpalten - 1)
  {
    if (millis() - startTime > 1000)
    {
      displayTick = millis();
      zeile2[pos] = '*';
      ausgabe("Booting", zeile2);
      startTime = millis();
      pos++;
    }
  }
}

Später bleibt Serial.write(c, 8); als einzige Ausgabe der seriellen Schnittstelle übrig, alle anderen Ausgaben sind nur Debuganzeigen.

Diese habe ich um eine Hex-Anzeige der zu sendenden Codes erweitert.

Ich hoffe, das hilft irgendwie :slightly_smiling_face:

Ich kucke, dass ich am Montag eventuell während der Mittagspause das testen kann, falls ich bis dahin kein Corona habe, meinen Frau ist grad positiv und ich hänge gerade mit der kleinen alleine rum, da geht leider nichts..

Ist alles ok ? Ich hoffe nichts ernstes..

Ja, nur nervende technische Probleme, weshalb ich mich rücksichtslos austoben darf :crazy_face:

Die Kleine ist bald groß und Du würdest Dich ärgern, wenn Du schöne Programme, aber wenig Zeit mit ihr verbracht hättest. Also mach' das Beste draus!

Ich wünsche Euch gute Besserung und sanfte Symptome :face_with_thermometer:

Ich habs gerade mal versucht zu kompilieren, da kommt die Fehlermeldung:

Arduino: 1.8.15 (Windows 10), Board: "Arduino Uno"





















C:\Users\georg\Documents\Arduino\Post_164\Post_164.ino: In function 'void setup()':

Post_164:60:17: error: no matching function for call to 'LiquidCrystal_I2C::begin()'

   display.begin();

                 ^

In file included from C:\Users\georg\Documents\Arduino\Post_164\Post_164.ino:4:0:

C:\Users\georg\Documents\Arduino\libraries\LiquidCrystal_I2C/LiquidCrystal_I2C.h:58:8: note: candidate: void LiquidCrystal_I2C::begin(uint8_t, uint8_t, uint8_t)

   void begin(uint8_t cols, uint8_t rows, uint8_t charsize = LCD_5x8DOTS );

        ^~~~~

C:\Users\georg\Documents\Arduino\libraries\LiquidCrystal_I2C/LiquidCrystal_I2C.h:58:8: note:   candidate expects 3 arguments, 0 provided

Mehrere Bibliotheken wurden für "LiquidCrystal_I2C.h" gefunden

 Benutzt: C:\Users\georg\Documents\Arduino\libraries\LiquidCrystal_I2C

 Nicht benutzt: C:\Users\georg\Documents\Arduino\libraries\Arduino-LiquidCrystal-I2C-library-master

exit status 1

no matching function for call to 'LiquidCrystal_I2C::begin()'



Dieser Bericht wäre detaillierter, wenn die Option
"Ausführliche Ausgabe während der Kompilierung"
in Datei -> Voreinstellungen aktiviert wäre.

Verwendest Du eine andere LiquidCristal_I2C ?

ok, dann gehts ja noch :slight_smile:

Klar, deshalb sag ich ja ich habe keine Zeit mehr dafür, aber das wollte ich noch abschließen, dann fange ich mir nichts neues mehr an..

danke!

Für den Betroffenen ist das unangenehm und ich mache mich dann auch noch drüber lustig, ganz gemein :crazy_face:

Da hast Du wohl was Grundlegendes nicht verstanden.

  1. Du verwendest ein spezielles OLED und die passende Bibliothek Adafruit_CharacterOLED dazu.
  2. @my_xy_projekt und ich haben Dein Display nicht, also nutzen wir stattdessen ein 20x4 LCD und LiquidCrystal_I2C als Bibliothek.

Daher gibt es im Programm Zeilen, die sind nur bei Dir sinnvoll, andere nur bei uns. Du müßtest also "unsere" Zeilen kommentieren und "Deine" Zeilen wirksam machen. Weil das fehleranfällig ist, wollte ich Dir mit Hilfe der bedingten Kompilierung das Leben etwas erleichtern. Leider ist mein Vorschlag nicht auf fruchtbaren Boden gefallen. Soll ich den nochmal einbauen?

Dann haste was zu tun.
Ich war fleissig....

hehe!
austoben ja, aber nicht rücksichtslos.
Ich versuch wenigstens 1x am Tag meine Tour zu machen und nen Hotspot zu nutzen....

Kannste gerne probieren, aber ich möchte mal was zur Diskussion stellen.

//#include <Adafruit_CharacterOLED.h>

//Adafruit_CharacterOLED display(OLED_V2, 4, 6, 5, 7, 8, 9, 10);
#include <LiquidCrystal_I2C.h>
const byte lcdAddress = 0x27;
const byte lcdZeilen = 4;
const byte lcdSpalten = 20;
LiquidCrystal_I2C display(lcdAddress, lcdSpalten, lcdZeilen);

// Rotary
#include <rotary.h>
Rotary r = Rotary(45, 43);
const byte rotBut = 41;
const byte mutBut = 12;
const byte mutLed = 255;
enum {kurz = 1, lang};

const uint32_t bounceTime = 20; // zeit in ms
// Inhalt
struct inhalt
{
  char name[lcdSpalten + 1];
  char hid[2];
};

inhalt MyFilter[]
{
  {"Filter 1", "a"}, {"Filter 2", "s"},
  {"Filter 3", "d"}, {"Filter 4", "f"}
};

inhalt MySetup[]
{
  {"Auswahl", ""       }, {"Displayzeit", ""   },
  {"Wlan", "g"         }, {"Bezeichner", ""   },
  {"Platzhalter 1", "j"}, {"Platzhalter 2", "k"},
  {"Platzhalter 3", "l"}, {"Platzhalter 4", "q"},
  {"Platzhalter 5", "w"}, {"exit", "" },
};

uint8_t displayZeit = 20;                        // Zeit in Sekunden bis abschalten
uint32_t displayTick = 0;                        // Merker für letzte Aktion am Encoder oder Button

char displayZeileOben[lcdSpalten + 1] = {'\0'};  // Globale Variablen für DisplayZeilen
char displayZeileUnten[lcdSpalten + 1] = {'\0'};

bool isMenu = true;
bool isSetup = !isMenu;
bool isMute = false;

void setup()
{
  Serial.begin(115200);
  Serial.println(F("Start..."));
  //startSequenz();
  pinMode (rotBut, INPUT);
  pinMode (mutBut, INPUT_PULLUP);
  pinMode (mutLed, OUTPUT);
  display.begin();
}
//
void loop()
{
  if (isMute)
  {
    heartbeat(500);
  }
  else
  {
    if (digitalRead(mutLed))     // wenn nicht Mute
      digitalWrite(mutLed, LOW); // sicherstellen, das aus
    if (isMenu) filterMenu();
    if (isSetup) setupMenu();
  }
  setMute();
  isDisplay();
}
//
// Die folgende Funktion ist für die Auswahl und Übergabe jeweiliger
// Menueinträge vorgesehen. Die sich ständig wiederholenden Abfragen
// des RotaryEncoders in den unterschiedlichen Menus zusammengefasst
// rotaryPos ist die aktuelle Position, die einzige die veränderlich ist ;)
void menuAuswahl(uint8_t &rotaryPos, const uint8_t &minPos,
                 const uint8_t &maxPos, const char *zeile1, const char *zeile2)
{
  unsigned char val = r.process();
  if (val && isDisplay())
  {
    if (val == r.clockwise() )
    {
      if (rotaryPos == maxPos) rotaryPos = minPos;
      else rotaryPos++;
    }
    else if (val == r.counterClockwise())
    {
      if (rotaryPos == minPos) rotaryPos = maxPos;
      else rotaryPos--;
    }
    displayTick = millis();
  }
  ausgabe(zeile1, zeile2);
}
//
void filterMenu()                                                // filterauswahl ist default
{
  memset(displayZeileOben, '\0', lcdSpalten + 1);                // Zeile leeren
  strcpy(displayZeileOben, "Filter waehlen");                    // und neu befüllen
  const uint8_t maxPos = sizeof(MyFilter) / sizeof(inhalt) - 1;
  const uint8_t minPos = 0;
  static uint8_t rotaryPos = 0;
  if (isDisplay())                                               // Nur wenn Display an ist
    // Übergeben wird die (sich nicht mehr ändernde) obere Zeile und der Inhalt
    // aus dem Struct. Darstellung wird dann in der Funktion menuAuswahl() gebaut
    menuAuswahl(rotaryPos, minPos, maxPos, displayZeileOben, MyFilter[rotaryPos].name);
  switch (getTaste())                                            // Tastenabfrage
  {
    case kurz: sendHid(MyFilter[rotaryPos].hid); break;
    case lang: {isMenu = false; isSetup = true;} break;
  }
}
//
void updateEEprom()
{
  // HIER WIRD SPÄTER WEITER GEMACHT
}
//
bool editBezeichner()   // Ein Versuch - wird ggfls. noch aufgelöst
{
  const uint8_t num = 3;
  const char bezeichner[num + 1][lcdSpalten + 1] = {"Filtertexte", "Filterkey", "Setuptexte", "Setupkey"};
  static uint8_t schritt = 0;
  static uint8_t rotaryPos = 0;
  static uint8_t auswahl = 0;
  const uint8_t minPos = 0;
  static bool returnWert = true;
  static uint8_t spalte = 0;
  static char editZeile[lcdSpalten + 1] = {'\0'};
  if (!returnWert)    // Wenn erster Aufruf, init der Var
  {
    returnWert = true;
    schritt = 0;
    rotaryPos = 0;
    memset(editZeile, '\0', lcdSpalten + 1);
    auswahl = 0;
  }
  switch (schritt)
  {
    case 0:
      {
        menuAuswahl(rotaryPos, minPos, num, "Editieren", bezeichner[rotaryPos]);
        switch (getTaste())
        {
          case kurz:
            memset(displayZeileOben, '\0', lcdSpalten + 1);
            strcpy(displayZeileOben, bezeichner[rotaryPos]);
            auswahl = rotaryPos;
            rotaryPos = 0;
            schritt = 1;
            break;
          case lang:
            schritt = 9;
            break;
        }
      }
      break;
    case 1:
      switch (auswahl)
      {
        case 0:
          menuAuswahl(rotaryPos, minPos, sizeof(MyFilter) / sizeof(inhalt) - 1, displayZeileOben, MyFilter[rotaryPos].name);
          strcpy(editZeile, MyFilter[rotaryPos].name);
          break;
        case 1:
          memset(displayZeileOben, '\0', lcdSpalten + 1);
          strcpy(displayZeileOben,  MyFilter[rotaryPos].name);
          menuAuswahl(rotaryPos, minPos, sizeof(MyFilter) / sizeof(inhalt) - 1, displayZeileOben, MyFilter[rotaryPos].hid);
          strcpy(editZeile, MyFilter[rotaryPos].hid);
          break;
        case 2:
          menuAuswahl(rotaryPos, minPos, sizeof(MySetup) / sizeof(inhalt) - 1,  displayZeileOben, MySetup[rotaryPos].name);
          strcpy(editZeile, MySetup[rotaryPos].name);
          break;
        case 3:
          memset(displayZeileOben, '\0', lcdSpalten + 1);
          strcpy(displayZeileOben,  MySetup[rotaryPos].name);
          menuAuswahl(rotaryPos, minPos, sizeof(MySetup) / sizeof(inhalt) - 1, displayZeileOben, MySetup[rotaryPos].hid);
          strcpy(editZeile, MySetup[rotaryPos].hid);
          break;
      }
      switch (getTaste())
      {
        case kurz:
          spalte = 0;
          display.blink();
          display.setCursor(spalte, 1);
          display.print(editZeile);
          for (uint8_t b = strlen(editZeile); b < lcdSpalten; b++) display.print(' ');
          display.setCursor(spalte, 1);
          schritt = 2;
          break;
        case lang:
          schritt = 9;
          break;
      }
      break;
    case 2:
      {
        uint8_t buchstabe = uint8_t(editZeile[spalte]);
        setupZahl(buchstabe, 32, 126);
        if (uint8_t(editZeile[spalte]) != buchstabe)
        {
          editZeile[spalte] = char(buchstabe);
          display.setCursor(spalte, 1);
          display.print(char(buchstabe));
          display.setCursor(spalte, 1);
        }
        switch (getTaste())
        {
          case kurz:
            editZeile[spalte] = char(buchstabe);
            spalte++;
            if (auswahl == 0 || auswahl == 2)
            {
              if (spalte >= lcdSpalten)  spalte = 0;
            }
          else
            spalte = 0;
          display.setCursor(spalte, 1); break;
          case lang:
            display.noBlink();
            switch (auswahl)
            {
              case 0: memcpy(MyFilter[rotaryPos].name, editZeile, sizeof(MyFilter[rotaryPos].name) - 1); break;
              case 1: memcpy(MyFilter[rotaryPos].hid, editZeile, sizeof(MyFilter[rotaryPos].hid) - 1); break;
              case 2: memcpy(MySetup[rotaryPos].name, editZeile, sizeof(MySetup[rotaryPos].name) - 1); break;
              case 3: memcpy(MySetup[rotaryPos].hid, editZeile, sizeof(MySetup[rotaryPos].hid) - 1); break;
            }
            updateEEprom();
            schritt = 0;
            break;
        }
      }
      break;
    case 9:
      returnWert = false;
      break;
  }
  if (!isDisplay())
  {
    display.noBlink();
    returnWert = false;
  }
  return returnWert;
}
//
void setupMenu()
{
  const uint8_t maxPos = (sizeof(MySetup) / sizeof(inhalt)) - 1;  // Anzahl der Elemente ermitteln
  const uint8_t minPos = 1;                                       // Der Eintrag 0 ist kein Menueintrag
  static uint8_t rotaryPos = minPos;
  static uint8_t menuPos = 0;
  static uint8_t subMenuPos = 0;                                  // Hilfsvariable für Untermenu
  switch (menuPos)
  {
    case 0:
      menuAuswahl(rotaryPos, minPos, maxPos, MySetup[0].name, MySetup[rotaryPos].name);
      if (getTaste() == kurz) menuPos = rotaryPos;                // Wenn ausgewählt: menuPos setzen
      else if (menuPos == 2) subMenuPos = 0;                      // wenn Submenu gebraucht wird
      break;
    case 1:                                                       // Die displayZeit wird
      displayTime(MySetup[menuPos].name, displayZeit);            // wieder direkt geändert
      if (getTaste() == kurz)                                     // verlassen des Menupunktes
        menuEnde(menuPos, MySetup[0].name, MySetup[menuPos].name);// zurücksetzen auf eine Ebene höher
      break;
    case 2:                                                       // das dürfte klar sein
      {
        const char zeile2[][12] = {"umschalten", "exit"};
        menuAuswahl(subMenuPos, 0 , 1, MySetup[menuPos].name, zeile2[subMenuPos]);
        if (getTaste() == kurz)
        {
          if (subMenuPos == 0) sendHid(MySetup[menuPos].hid);
          menuEnde(menuPos, MySetup[0].name, MySetup[rotaryPos].name);
        }
      }
      break;
    case 3:                                                       // Ein Versuch die Texte zu ändern
      if (!editBezeichner())
      {
        Serial.println("ENDE");
        menuEnde(menuPos, MySetup[0].name, MySetup[menuPos].name);
      }
      break;
    case 4 ... 8:
      Serial.println(F("Platzhalter"));
      sendHid(MySetup[rotaryPos].hid);
      menuEnde(menuPos, MySetup[0].name, MySetup[menuPos].name);
      break;
  }                                                  // Es fehlt absichtlich das case auf maxPos!!
  if (!isDisplay() || (menuPos == maxPos))           // Wenn der Display aus geht ODER maxPos (exit) ausgelöst
  {
    menuPos = 0;                                     // alles zurücksetzen
    rotaryPos = minPos;
    isMenu = true; isSetup = false;
  }
}
//
void displayTime(char *zeile1, uint8_t &sekunde)     // Einstellen der DisplayTime
{
  setupZahl(sekunde);                                // Sekunde verstellen
  if (sekunde < 5) sekunde = 5;
  memset(displayZeileUnten, '\0', lcdSpalten + 1);   // untere DisplayZeile leeren
  sprintf(displayZeileUnten, "%d", sekunde);         // und mit Zahl füllen
  ausgabe(zeile1, displayZeileUnten);                // und ausgeben
}
//
void setupZahl(uint8_t &aenderZahl, const uint8_t &minZahl, const uint8_t &maxZahl)
{
  uint32_t myZahl = aenderZahl;
  setupZahl(myZahl);
  if (myZahl > maxZahl) myZahl = minZahl;
  if (myZahl < minZahl) myZahl = maxZahl;
  aenderZahl = myZahl;
}
void setupZahl(uint8_t &aenderZahl)                  // Je nach Größe der
{
  uint32_t myZahl = aenderZahl;
  setupZahl(myZahl, 255);
  aenderZahl = myZahl;
}
void setupZahl(uint16_t &aenderZahl)                 // übergebenen Zahl
{
  uint32_t myZahl = aenderZahl;
  setupZahl(myZahl, 65535);                          // begrenzen des Zählers
  aenderZahl = (uint16_t)myZahl;
}
void setupZahl(uint32_t &aenderZahl, const uint32_t &maxZahl)
{
  setupZahl(aenderZahl);
  if (aenderZahl > maxZahl) aenderZahl = 0;
}
void setupZahl(uint32_t &aenderZahl)
{
  unsigned char val = r.process();
  if (val && isDisplay())                            // und hier einfach nur auf/ab zaehlen
  {
    if (val == r.clockwise())
      aenderZahl++;
    if (val == r.counterClockwise())
      aenderZahl--;
    displayTick = millis();                          // und dem Merker sagen, das sich was bewegt hat
  }
}
//
void menuEnde(uint8_t &menuPos, char *zeile1, char *zeile2)
{
  menuPos = 0;
  ausgabe(zeile1, zeile2);
}
//
void sendHid(char *zeichen)
{
  Serial.print(F("HID: "));
  Serial.println(zeichen);
}
//
void printDisplay(char *zeile, byte number)          // Displayzeile und welche Zeilennummer
{
  display.setCursor(0, number);                      // cursor setzen
  uint8_t laenge = strlen(zeile);                    // wie lang ist die Zeichenkette?
  for (byte b = 0; b < (lcdSpalten - laenge) / 2; b++) // Errechnen der ersten Hälfte die
    display.print(' ');                              // mit Leerzeichen aufgefüllt
  display.print(zeile);                              // Ausgabe der Zeile
  for (byte b = (lcdSpalten - laenge) / 2 + laenge; b < lcdSpalten; b++)
    display.print(' ');                              // Auffüllen der Zeile bis Ende mit Spaces
  Serial.print(F("Zeile "));
  Serial.print(number + 1);
  Serial.print(": ");
  Serial.println(zeile);
}
//
void ausgabe(const char *zeile1, const char *zeile2)  // Übernahme beider Zeilen
{
  static char lastZeileOben[lcdSpalten + 1] = {'\0'}; // Merker der letzten Ausgabe
  static char lastZeileUnten[lcdSpalten + 1] = {'\0'};
  if (strcmp(lastZeileOben, zeile1))                  // Wenn sich der Inhalt geändert hat
  {
    memset(lastZeileOben, '\0', lcdSpalten + 1);      // Merker löschen
    strcpy(lastZeileOben, zeile1);                    // neu füllen
    printDisplay(lastZeileOben, 0);                   // und ausgeben
  }
  if (strcmp(lastZeileUnten, zeile2))
  {
    memset(lastZeileUnten, '\0', lcdSpalten + 1);
    strcpy(lastZeileUnten, zeile2);
    printDisplay(lastZeileUnten, 1);
  }
}
//
bool isDisplay()
{
  if (millis() - displayTick > displayZeit * 1000UL && !isMute)
  {
    display.noDisplay();
    return false;
  }
  display.display();
  return true;
}
//
void setMute()
{
  static uint32_t lastmillis = 0;
  static bool isPressed = false;
  if (!digitalRead(mutBut) && !isPressed)
  {
    isPressed = true;
    displayTick = millis();
    lastmillis = millis();
    isMute = !isMute;
    if (isMute) ausgabe("Mute aktiv", "");
    Serial.print(F(""));
    char e[] = "e";
    sendHid(e);
  }
  else if ((millis() - lastmillis > bounceTime) &&
           (digitalRead(mutBut) && isPressed))
  {
    isPressed = false;
  }
}
//
uint8_t getTaste()
{
  uint8_t butPress = 0;
  static uint32_t pressTime = 0;
  static byte schritt = 0;
  bool isPressed = !digitalRead(rotBut);
  switch (schritt)
  {
    default:
      if (isPressed)
      {
        pressTime = millis();
        schritt = 1;
      }
      break;
    case 1:
      if (millis() - pressTime > bounceTime)
        isPressed ? schritt = 2 : schritt = 0;
      break;
    case 2:
      if (!isPressed)
      {
        if (!isDisplay())
          displayTick = millis();
        else if (millis() - pressTime > 500)
          butPress = 2;
        else
          butPress = 1;
        schritt = 3;
      }
      break;
    case 3:
      if (!isPressed)
        displayTick = millis();
      schritt = 0;
      break;
  }
  return butPress;
}
//
void heartbeat(const uint32_t tik)
{
  static uint32_t lasttik = 0;
  if (millis() - lasttik >= tik)
  {
    lasttik += tik;
    digitalWrite(mutLed, !digitalRead(mutLed));
  }
}
//
void startSequenz()
{
  uint32_t startTime = millis();
  ausgabe("Willkommen", "");
  while (millis() - startTime < 1500) {}
  startTime = millis();
  ausgabe("Booting", "");
  char zeile2[lcdSpalten + 1] = {'\0'};
  uint8_t pos = 0;
  while (pos < lcdSpalten)
  {
    if (millis() - startTime > 1000)
    {
      displayTick = millis();
      zeile2[pos] = '*';
      ausgabe("Booting", zeile2);
      startTime = millis();
      pos++;
    }
  }
}

Hier ist einiges passiert.
Zum einen aufegräumt, was die Funktionen mit dem rotary angeht.
Alles was irgendwie gedoppelt war, ist raus.
Der Rotary wird jetzt auch anders ausgewertet - einfach reinschauen.

Dazu kommt, das ich mich schon mit dem editieren befasst habe.
Das ist vielleicht noch aufzuräumen, aber es sollte ein Ansatz sein.

Im setup-Menu, einfach mit nem kurzen Klick in die Bezeichner.
Dann geht: drehen, für die Auswahl der Bezeichner aus den beiden Struct und mit einem kurzen klick dann der jeweilige Inhalt.
Ein kurzer Klick, geht eine Position weiter, ein langer Klick speichert den Inhalt ab und geht eine Menustufe höher...

Einfach mal probieren.

Wenn Fragen sind....Bis 14:30 werd ich vielleicht noh sitzen.

Ja, funktioniert, aber meine wenigen Änderungen sind wieder futsch :worried:

Ich nehm den aus #166 mit.
Das bastel ich dann irgendwie zusammen...
Ok?

Gut, dann ergänze doch bitte auch gleich die bedingte Kompilierung, damit der TO mittesten kann. Ob es mir gefällt, ist ja letztlich wurscht.

Ich kümmere mich jetzt um eine Garagenbeleuchtung :slightly_smiling_face:

Es wird Sommer, da bleibt die Tür auf, wenn Du kein Fenster hast :wink:

Dann bis (hoffentlich) morgen.

Erst mal wollte ich es so kompilieren, ohne was auszukommentieren, damit ich sehe ob der Code bei mir erst mal sich so kompilieren lässt.
Danach habe ich versucht mein Display einzukommentieren und das eure auszukommentieren, dann kam ein Fehler.

Inzwischen habs ich rausgefunden, display.begin gibt es in meiner Bibliothek nicht, stattdessen display.display

Gut soweit tut alles, es wird aber noch immer wild Zeichen über HID gesendet, auch wenn es {0, 0, 0, 0, 0, 0, 0, 0} gibt.
Ich verstehe leider nicht, warum die Zeichen nach dem Senden nicht gelöscht werden.

Ähnliches Verhalten mit dem Code aus #172, (aus meiner Sicht) irgendwas wird gesendet, der PC schmiert aber ab oder will ständig irgendwelche Programme völlig random öffnen, genau das Problem wie am Anfang im alten Thread...

Dann haben wir das im alten Thread gemacht und es hat funktioniert:

const byte encoderPinA = 2;
const byte encoderPinB = 3;
const byte encoderPinSW = 10;
//-------------------------
#include <LiquidCrystal.h>
const int rs = 4, en = 5, d4 = 6, d5 = 7, d6 = 8, d7 = 9;
LiquidCrystal lcd(rs, en, d4, d5, d6, d7);
//-------------------------

volatile int8_t encoderPos = 0;     // a counter for the dial
int8_t lastReportedPos = -1;        // change management
bool rotating = false;              // debounce management
bool neuePosition = false;

// interrupt service routine vars
boolean A_set = false;
boolean B_set = false;


void setup() {
  //---------------------------
  lcd.begin(20, 2);
  //---------------------------
  lcd.setCursor(0, 0);
  lcd.print("12345678901234567890");

  pinMode(encoderPinA, INPUT_PULLUP);
  pinMode(encoderPinB, INPUT_PULLUP);
  pinMode(encoderPinSW, INPUT_PULLUP);

  attachInterrupt(0, doEncoderA, CHANGE); // encoder pin on interrupt 0 (pin 2)
  attachInterrupt(1, doEncoderB, CHANGE); // encoder pin on interrupt 1 (pin 3)

  Serial.begin(9600);  // output
}

void loop()
{
  const char * text[5] = {"e", "r", "t", "z", "y"};
  const uint8_t code[5][8] = {
    {0, 0,  8, 0, 0, 0, 0, 0},
    {0, 0, 21, 0, 0, 0, 0, 0},
    {0, 0, 23, 0, 0, 0, 0, 0},
    {0, 0, 28, 0, 0, 0, 0, 0},
    {0, 0, 29, 0, 0, 0, 0, 0}
  };
  rotating = true;  // reset the debouncer

  if (lastReportedPos != encoderPos)
  {
    lastReportedPos = encoderPos;
    lcd.setCursor(0, 1);
    lcd.print("                    ");
    lcd.setCursor(7, 1);
    if (encoderPos < 0) encoderPos = 0;
    if (encoderPos > 4) encoderPos = 4;
    lcd.print(text[encoderPos]);
    neuePosition = true;
  }
  if ( neuePosition && !digitalRead(encoderPinSW) )  // nur beim Tastendruck des Encoders werden die Zeichen verschickt
  {
    neuePosition = false;
    Serial.write(code[encoderPos], 8);
    releaseKey();
  }
}

void releaseKey() {
  uint8_t keyNone[8] = { 0, 0, 0, 0, 0, 0, 0, 0 };
  Serial.write(keyNone, 8); // Send Release key
}

// Interrupt on A changing state
void doEncoderA()
{
  if ( rotating ) delay (1);  // wait a little until the bouncing is done
  if ( digitalRead(encoderPinA) != A_set ) { // debounce once more
    A_set = !A_set;
    // adjust counter + if A leads B
    if ( A_set && !B_set )
      encoderPos += 1;
    rotating = false;  // no more debouncing until loop() hits again
  }
}

// Interrupt on B changing state, same as A above
void doEncoderB() {
  if ( rotating ) delay (1);
  if ( digitalRead(encoderPinB) != B_set ) {
    B_set = !B_set;
    //  adjust counter - 1 if B leads A
    if ( B_set && !A_set )
      encoderPos -= 1;
    rotating = false;
  }
}

Vergesst mal die Display Lib und die Ports, es geht nur die Vorgehensweise zum ansteuern der HID Befehlen, vielleicht könnt Ihr da was rauslesen was euch hilft.

oder das Original vom Kollegen der Erfindung dieses Konzeptes:

uint8_t buf[8] = { 0 }; //Keyboard report buffer

#define PIN_W 10 // Pin for w

void setup() {
pinMode(PIN_W, INPUT_PULLUP);
Serial.begin(9600); // Setup Serial communication
//Set pinmode of Input pins
pinMode(PIN_W, INPUT);
}

void loop() {

//When button representing W is pressed
if (digitalRead(PIN_W) == HIGH) {
buf[2] = 26; // W keycode
Serial.write(buf, 8); // Send keypress
releaseKey();
delay(250);
}
}

// Function for Key Release
void releaseKey() {
buf[0] = 0;
buf[2] = 0;
Serial.write(buf, 8); // Send Release key
}

Ich weiß nicht, wie ich es besser erklären könnte mit meiner Programmiertechnischen Untechnischheit :slight_smile:

Eventuell versuchen wir mal nur eine Taste auf Pin 13 zu drücken und auch wieder loszulassen ohne dass der PC abschmiert, damit man sieht dass das tut? Ohne die ganze Logic mit Menü und Rotary?
(Ist nur ein Vorschlag)

Ganz nebenbei:
Zwei Dinge sind mir beim Testen aufgefallen/ eingefallen, beim drücken des Rotary SW auf Pin 13 geht die eingebaute LED auf dem Board mit an und aus.
Und kann es sein, dass Befehle des Serial Monitor mit denen für HID eventuell kollidieren?

Ja, das ist der springende Punkt, das tun sie!

Entweder textliche Debug-Ausgaben zur Darstellung auf dem seriellen Monitor oder Codes für HID. Beides gleichzeitig geht nicht.

Anders beim Teensy 3.2, der schafft es beispielsweise, auf einer physischen USB-Schnittstelle logisch zwischen Text auf dem seriellen Monitor und MIDI-Code für ein Musikprogramm zu unterscheiden.

Schmeiß mal von meinem Programm #166 alles mit Serial raus, nur

...
  Serial.begin(9600);
...
  Serial.write(c, 8);
  uint8_t keyNone[8] = { 0, 0, 0, 0, 0, 0, 0, 0 };
  Serial.write(keyNone, 8); // Send Release key
...

dürfen aktiv sein. Dann sollte es mit HID besser aussehen (ungetestet).

Taste an Pin 12, LED an Pin 13, mit bedingter Kompilierung (ich bergeistere Dich schon noch dafür :wink:), Debug getestet mit UNO, HID ungetestet:

const byte tasterPin = 12, ledPin = 13;

#define debug 1  // Debug-Ausgabe auf dem seriellen Monitor; als Kommentar für HID

struct inhalt
{
  char taste[2];
  uint8_t hid[8];
};

inhalt MyFilter[]
{
  {"a", {0, 0,  8, 40, 0, 0, 0, 0}}, {"s", {0, 0, 21, 40, 0, 0, 0, 0}},
  {"d", {0, 0, 23, 40, 0, 0, 0, 0}}, {"f", {0, 0, 28, 40, 0, 0, 0, 0}}
};
uint8_t keyNone[8] = { 0, 0, 0, 0, 0, 0, 0, 0 };

void setup()
{
  Serial.begin(9600);
#ifdef debug
  Serial.println(F("Start..."));
#endif
  pinMode (tasterPin, INPUT_PULLUP);
  pinMode (ledPin, OUTPUT);
}
//
void loop()
{
  const byte max = sizeof(MyFilter) / sizeof(MyFilter[0]);
  static byte index = 0;

  if (!digitalRead(tasterPin))
  {
    digitalWrite(ledPin, HIGH);
    sendHid(MyFilter[index].taste, MyFilter[index].hid);
    delay(200);
    digitalWrite(ledPin, LOW);
    if (++index >= max) index = 0;
  }
}

void sendHid(char *zeichen, uint8_t *code)
{
#ifdef debug
  Serial.print(F("HID: "));
  Serial.print(zeichen);
  Serial.print(F("\tCode: "));
  for (byte j = 0; j < 8; j++)
  {
    Serial.print(code[j], HEX);
    Serial.print(' ');
  }
  Serial.println("in hex");
#else
  zeichen[0] = zeichen[0];  // unterdrückt Kompilerwarnung "unused"
  Serial.write(code, 8);    // Tastaturcode
  Serial.write(keyNone, 8); // Send Release key
#endif
}

vorweg: ja.
und ich hab das versucht zu lösen, weil mir das schon bewusst war.

Hallihallo @agmue

Ich hab versucht Deinem Vorschlag rund um den HID-Code zu folgen.
Das ist aus mindestens zwei Gründen verworfen worden.
In getMute()

    char z[] = {"e"};
    uint8_t c[] = {0, 0, 0, 0, 0, 0, 0, 0};
    sendHid(z, c);

Wenn sich das e ändert, muss der HID-Code ebenfalls geändert werden.
Das mag für den einen Eintrag bei einem HID-Code noch abzuhandeln sein, aber was passiert wenn die Editierfunktion genutzt wird? Der char wird geändert, aber HID nicht?

Wenn ein Platzhalter einen festen Eintrag bekommt, benötigt der einen entsprechenden HID-Code.
Derzeit ändere ich den Bezeichner, der bekommt einen dazu gewählten char-ASCII-Code.
Es müsste dazu ebenfalls auch der 8byte-HID-Code angepasst werden. Und genau da sehe ich das größte Problem.

Zudem: Wenn in der Struktur zwingend auch noch ein 8byte-Array für den Inhalt drin ist und nicht durchgängig genutzt wird: irgendwo Overkill :wink:
Darüber hinaus müssen dann immer zwingend zwei Parameter übergeben werden.

Ein printable(char) hat immer den selben HID-Code.
Ich habe mir daher erlaubt das insgesamt ganz anders aufzubauen und bitte um ein Statement.
Für die HID-Codes gibt es eine Tabelle, in der das erste byte den entsprechenden char abbildet.
Bitte auch im Kommentar nachlesen.

Um für die Zukunft gewappnet zu sein, ist die Tabelle bereits jetzt in den Progmem verlagert worden.
Die Funktion sendHid() ist umgebaut worden.
Ich vergleiche nur das byte des Chars auf das erste byte des HID-Arrays und baue daraus die zu sendende Folge aus dem im Progmem liegenden Element.
In dem beigefügten Archiv ist der Sketch mit einem ausgelagertem TAB, der eine HID-Table enthält, die gefüllt werden müsste.
Wenn wir uns auf die ausgelagerte hid-Table einigen, dann braucht nur der Code aus dem hauptTab ausgetauscht werden.

Zum Thema bedingte Formatierung: Ist drin und kompiliert hier für ff und mich fehlerfrei.
@fftransformation Was angepasst werden muss: Die Angaben zum Rotary und zum Button, ab Zeile 20. Sag mal die Pins bei Dir, dann gehört das eingearbeitet.

@agmue, das lcd kann auf 20x2 stehen bleiben :wink:
Für die Ausgaben auf dem SerMon habe ich eine debug-Ausgabe eingestellt, die unterbleiben muss, wenn HID genutzt wird.
Nun nur noch die HID-Ausgabe vervollständigen und dann EEProm?

Das sollte es gewesen sein, oder gehört noch mehr rein?

#define debug
//#define ff true
//#define ag true

#ifdef ff
  #undef ag
  #undef debug
#endif

const byte lcdZeilen = 2;
const byte lcdSpalten = 20;

// Rotary
#include <rotary.h>

// bedingte Kompilierung - Zuweisung der Konfigurationen
#ifdef ff
  #include <Adafruit_CharacterOLED.h>
  Adafruit_CharacterOLED display(OLED_V2, 4, 6, 5, 7, 8, 9, 10, LCD_EUROPEAN_II);
  Rotary r = Rotary(, );  // Hier muss ff seine Konfig rein
  const byte rotBut = ;
  const byte mutBut = ;
  const byte mutLed = 255;
#elif ag
  #include <Wire.h>
  #include <NoiascaLiquidCrystal.h>
  #include <NoiascaHW/lcd_PCF8574.h>
  LiquidCrystal_PCF8574 display(0x27, lcdSpalten, lcdZeilen);
  Rotary r = Rotary(45, 43);
  const byte rotBut = 41;
  const byte mutBut = 12;
  const byte mutLed = 255;
#else
  #include <LiquidCrystal_I2C.h>
  const byte lcdAddress = 0x27;
  LiquidCrystal_I2C display(lcdAddress, lcdSpalten, lcdZeilen);
  Rotary r = Rotary(45, 43);
  const byte rotBut = 41;
  const byte mutBut = 12;
  const byte mutLed = LED_BUILTIN;
#endif

enum {kurz = 1, lang};

#ifdef debug
  #define DBG_PRINT(x) Serial.print(x)
  #define DBG_PRINTLN(x) Serial.println(x)
#else
  #define DBG_PRINT(x)
  #define DBG_PRINTLN(x)
#endif

const uint32_t bounceTime = 20; // zeit in ms
// Inhalt
struct inhalt
{
  char name[lcdSpalten + 1];
  char hid[2];
};
// #include "hidTable.h"

// Die folgenden Zeilen decken nur die verwendeten Codes ab
// eine vollständige Tabelle, die ggfls. gefüllt werden muss,
// befindet sich im angehängten Archiv!
const uint8_t hidNums = 11;
const uint8_t hid[hidNums][9] PROGMEM
{
  // printable Zeichen beginnen ab DEC 32 und enden mit DEC 126!
  // Wenn der inhalt.id änderbar sein soll, muss die Tabelle
  // in jedem Fall vollständig sein! Dann evtl. auslagern!
  // Das erste byte ist der DEC-Code des printable Zeichen!
  { 97, 0, 0,  8, 40, 0, 0, 0, 0}, // a
  {100, 0, 0, 23, 40, 0, 0, 0, 0}, // d
  {101, 0, 0,  0,  0, 0, 0, 0, 0}, // e
  {102, 0, 0, 28, 40, 0, 0, 0, 0}, // f
  {103, 0, 0,  0,  0, 0, 0, 0, 0}, // g
  {106, 0, 0, 29, 40, 0, 0, 0, 0}, // j
  {107, 0, 0, 29, 40, 0, 0, 0, 0}, // k
  {108, 0, 0, 29, 40, 0, 0, 0, 0}, // l
  {113, 0, 0, 29, 40, 0, 0, 0, 0}, // q
  {115, 0, 0, 21, 40, 0, 0, 0, 0}, // s
  {119, 0, 0, 29, 40, 0, 0, 0, 0}, // w
};

inhalt MyFilter[]
{
  {"Filter 1", "a" }, {"Filter 2", "s" },
  {"Filter 3", "d" }, {"Filter 4", "f" }
};

inhalt MySetup[]
{
  {"Auswahl",       "" }, {"Displayzeit",    ""},
  {"Wlan",          "g"}, {"Bezeichner",     ""},
  {"Platzhalter 1", "j"}, {"Platzhalter 2", "k"},
  {"Platzhalter 3", "l"}, {"Platzhalter 4", "q"},
  {"Platzhalter 5", "w"}, {"exit",           ""}
};
uint8_t displayZeit = 20;                        // Zeit in Sekunden bis abschalten
uint32_t displayTick = 0;                        // Merker für letzte Aktion am Encoder oder Button

char displayZeileOben[lcdSpalten + 1] = {'\0'};  // Globale Variablen für DisplayZeilen
char displayZeileUnten[lcdSpalten + 1] = {'\0'};

bool isMenu = true;
bool isSetup = !isMenu;
bool isMute = false;

void setup()
{
  Serial.begin(115200);
  DBG_PRINTLN(F("Start..."));
  //startSequenz();
  pinMode (rotBut, INPUT);
  pinMode (mutBut, INPUT_PULLUP);
  pinMode (mutLed, OUTPUT);
#ifdef ff
  display.begin(lcdSpalten, lcdZeilen);
#elif ag
  Wire.begin();
  display.begin();
  display.backlight();
#else
  display.begin();
#endif
}
//
void loop()
{
  if (isMute)
  {
    heartbeat(500);
  }
  else
  {
    if (digitalRead(mutLed))     // wenn nicht Mute
      digitalWrite(mutLed, LOW); // sicherstellen, das aus
    if (isMenu) filterMenu();
    if (isSetup) setupMenu();
  }
  setMute();
  isDisplay();
}
//
// Die folgende Funktion ist für die Auswahl und Übergabe jeweiliger
// Menueinträge vorgesehen. Die sich ständig wiederholenden Abfragen
// des RotaryEncoders in den unterschiedlichen Menus zusammengefasst
// rotaryPos ist die aktuelle Position, die einzige die veränderlich ist ;)
void menuAuswahl(uint8_t &rotaryPos, const uint8_t &minPos,
                 const uint8_t &maxPos, const char *zeile1, const char *zeile2)
{
  unsigned char val = r.process();
  if (val && isDisplay())
  {
    if (val == r.clockwise() )
    {
      if (rotaryPos == maxPos) rotaryPos = minPos;
      else rotaryPos++;
    }
    else if (val == r.counterClockwise())
    {
      if (rotaryPos == minPos) rotaryPos = maxPos;
      else rotaryPos--;
    }
    displayTick = millis();
  }
  ausgabe(zeile1, zeile2);
}
//
void filterMenu()                                                // filterauswahl ist default
{
  memset(displayZeileOben, '\0', lcdSpalten + 1);                // Zeile leeren
  strcpy(displayZeileOben, "Filter waehlen");                    // und neu befüllen
  const uint8_t maxPos = sizeof(MyFilter) / sizeof(inhalt) - 1;
  const uint8_t minPos = 0;
  static uint8_t rotaryPos = 0;
  if (isDisplay())                                               // Nur wenn Display an ist
    // Übergeben wird die (sich nicht mehr ändernde) obere Zeile und der Inhalt
    // aus dem Struct. Darstellung wird dann in der Funktion menuAuswahl() gebaut
    menuAuswahl(rotaryPos, minPos, maxPos, displayZeileOben, MyFilter[rotaryPos].name);
  switch (getTaste())                                            // Tastenabfrage
  {
    case kurz: sendHid(MyFilter[rotaryPos].hid); break;
    case lang: {isMenu = false; isSetup = true;} break;
  }
}
//
void updateEEprom()
{
  // HIER WIRD SPÄTER WEITER GEMACHT
}
//
bool editBezeichner()   // Ein Versuch - wird ggfls. noch aufgelöst
{
  const uint8_t num = 3;
  const char bezeichner[num + 1][lcdSpalten + 1] = {"Filtertexte", "Filterkey", "Setuptexte", "Setupkey"};
  static uint8_t schritt = 0;
  static uint8_t rotaryPos = 0;
  static uint8_t auswahl = 0;
  const uint8_t minPos = 0;
  static bool returnWert = true;
  static uint8_t spalte = 0;
  static char editZeile[lcdSpalten + 1] = {'\0'};
  if (!returnWert)    // Wenn erster Aufruf, init der Var
  {
    returnWert = true;
    schritt = 0;
    rotaryPos = 0;
    memset(editZeile, '\0', lcdSpalten + 1);
    auswahl = 0;
  }
  switch (schritt)
  {
    case 0:
      {
        menuAuswahl(rotaryPos, minPos, num, "Editieren", bezeichner[rotaryPos]);
        switch (getTaste())
        {
          case kurz:
            memset(displayZeileOben, '\0', lcdSpalten + 1);
            strcpy(displayZeileOben, bezeichner[rotaryPos]);
            auswahl = rotaryPos;
            rotaryPos = 0;
            schritt = 1;
            break;
          case lang:
            schritt = 9;
            break;
        }
      }
      break;
    case 1:
      switch (auswahl)
      {
        case 0:
          menuAuswahl(rotaryPos, minPos, sizeof(MyFilter) / sizeof(inhalt) - 1, displayZeileOben, MyFilter[rotaryPos].name);
          strcpy(editZeile, MyFilter[rotaryPos].name);
          break;
        case 1:
          memset(displayZeileOben, '\0', lcdSpalten + 1);
          strcpy(displayZeileOben,  MyFilter[rotaryPos].name);
          menuAuswahl(rotaryPos, minPos, sizeof(MyFilter) / sizeof(inhalt) - 1, displayZeileOben, MyFilter[rotaryPos].hid);
          strcpy(editZeile, MyFilter[rotaryPos].hid);
          break;
        case 2:
          menuAuswahl(rotaryPos, minPos, sizeof(MySetup) / sizeof(inhalt) - 1,  displayZeileOben, MySetup[rotaryPos].name);
          strcpy(editZeile, MySetup[rotaryPos].name);
          break;
        case 3:
          memset(displayZeileOben, '\0', lcdSpalten + 1);
          strcpy(displayZeileOben,  MySetup[rotaryPos].name);
          menuAuswahl(rotaryPos, minPos, sizeof(MySetup) / sizeof(inhalt) - 1, displayZeileOben, MySetup[rotaryPos].hid);
          strcpy(editZeile, MySetup[rotaryPos].hid);
          break;
      }
      switch (getTaste())
      {
        case kurz:
          spalte = 0;
          display.blink();
          display.setCursor(spalte, 1);
          display.print(editZeile);
          for (uint8_t b = strlen(editZeile); b < lcdSpalten; b++) display.print(' ');
          display.setCursor(spalte, 1);
          schritt = 2;
          break;
        case lang:
          schritt = 9;
          break;
      }
      break;
    case 2:
      {
        uint8_t buchstabe = uint8_t(editZeile[spalte]);
        setupZahl(buchstabe, 32, 126);
        if (uint8_t(editZeile[spalte]) != buchstabe)
        {
          editZeile[spalte] = char(buchstabe);
          display.setCursor(spalte, 1);
          display.print(char(buchstabe));
          display.setCursor(spalte, 1);
        }
        switch (getTaste())
        {
          case kurz:
            editZeile[spalte] = char(buchstabe);
            spalte++;
            if (auswahl == 0 || auswahl == 2)
            {
              if (spalte >= lcdSpalten)  spalte = 0;
            }
            else
              spalte = 0;
            display.setCursor(spalte, 1); break;
          case lang:
            display.noBlink();
            switch (auswahl)
            {
              case 0: memcpy(MyFilter[rotaryPos].name, editZeile, sizeof(MyFilter[rotaryPos].name) - 1); break;
              case 1: memcpy(MyFilter[rotaryPos].hid, editZeile, sizeof(MyFilter[rotaryPos].hid) - 1); break;
              case 2: memcpy(MySetup[rotaryPos].name, editZeile, sizeof(MySetup[rotaryPos].name) - 1); break;
              case 3: memcpy(MySetup[rotaryPos].hid, editZeile, sizeof(MySetup[rotaryPos].hid) - 1); break;
            }
            updateEEprom();
            schritt = 0;
            break;
        }
      }
      break;
    case 9:
      returnWert = false;
      break;
  }
  if (!isDisplay())
  {
    display.noBlink();
    returnWert = false;
  }
  return returnWert;
}
//
void setupMenu()
{
  const uint8_t maxPos = (sizeof(MySetup) / sizeof(inhalt)) - 1;  // Anzahl der Elemente ermitteln
  const uint8_t minPos = 1;                                       // Der Eintrag 0 ist kein Menueintrag
  static uint8_t rotaryPos = minPos;
  static uint8_t menuPos = 0;
  static uint8_t subMenuPos = 0;                                  // Hilfsvariable für Untermenu
  switch (menuPos)
  {
    case 0:
      menuAuswahl(rotaryPos, minPos, maxPos, MySetup[0].name, MySetup[rotaryPos].name);
      if (getTaste() == kurz) menuPos = rotaryPos;                // Wenn ausgewählt: menuPos setzen
      else if (menuPos == 2) subMenuPos = 0;                      // wenn Submenu gebraucht wird
      break;
    case 1:                                                       // Die displayZeit wird
      displayTime(MySetup[menuPos].name, displayZeit);            // wieder direkt geändert
      if (getTaste() == kurz)                                     // verlassen des Menupunktes
        menuEnde(menuPos, MySetup[0].name, MySetup[menuPos].name);// zurücksetzen auf eine Ebene höher
      break;
    case 2:                                                       // das dürfte klar sein
      {
        const char zeile2[][12] = {"umschalten", "exit"};
        menuAuswahl(subMenuPos, 0 , 1, MySetup[menuPos].name, zeile2[subMenuPos]);
        if (getTaste() == kurz)
        {
          if (subMenuPos == 0) sendHid(MySetup[menuPos].hid);
          menuEnde(menuPos, MySetup[0].name, MySetup[rotaryPos].name);
        }
      }
      break;
    case 3:                                                       // Ein Versuch die Texte zu ändern
      if (!editBezeichner())
      {
        DBG_PRINTLN("ENDE");
        menuEnde(menuPos, MySetup[0].name, MySetup[menuPos].name);
      }
      break;
    case 4 ... 8:
      DBG_PRINTLN(F("Platzhalter"));
      sendHid(MySetup[rotaryPos].hid);
      menuEnde(menuPos, MySetup[0].name, MySetup[menuPos].name);
      break;
  }                                                  // Es fehlt absichtlich das case auf maxPos!!
  if (!isDisplay() || (menuPos == maxPos))           // Wenn der Display aus geht ODER maxPos (exit) ausgelöst
  {
    menuPos = 0;                                     // alles zurücksetzen
    rotaryPos = minPos;
    isMenu = true; isSetup = false;
  }
}
//
void displayTime(char *zeile1, uint8_t &sekunde)     // Einstellen der DisplayTime
{
  setupZahl(sekunde);                                // Sekunde verstellen
  if (sekunde < 5) sekunde = 5;
  memset(displayZeileUnten, '\0', lcdSpalten + 1);   // untere DisplayZeile leeren
  sprintf(displayZeileUnten, "%d", sekunde);         // und mit Zahl füllen
  ausgabe(zeile1, displayZeileUnten);                // und ausgeben
}
//
void setupZahl(uint8_t &aenderZahl, const uint8_t &minZahl, const uint8_t &maxZahl)
{
  uint32_t myZahl = aenderZahl;
  setupZahl(myZahl);
  if (myZahl > maxZahl) myZahl = minZahl;
  if (myZahl < minZahl) myZahl = maxZahl;
  aenderZahl = myZahl;
}
void setupZahl(uint8_t &aenderZahl)                  // Je nach Größe der
{
  uint32_t myZahl = aenderZahl;
  setupZahl(myZahl, 255);
  aenderZahl = myZahl;
}
void setupZahl(uint16_t &aenderZahl)                 // übergebenen Zahl
{
  uint32_t myZahl = aenderZahl;
  setupZahl(myZahl, 65535);                          // begrenzen des Zählers
  aenderZahl = (uint16_t)myZahl;
}
void setupZahl(uint32_t &aenderZahl, const uint32_t &maxZahl)
{
  setupZahl(aenderZahl);
  if (aenderZahl > maxZahl) aenderZahl = 0;
}
void setupZahl(uint32_t &aenderZahl)
{
  unsigned char val = r.process();
  if (val && isDisplay())                            // und hier einfach nur auf/ab zaehlen
  {
    if (val == r.clockwise())
      aenderZahl++;
    if (val == r.counterClockwise())
      aenderZahl--;
    displayTick = millis();                          // und dem Merker sagen, das sich was bewegt hat
  }
}
//
void menuEnde(uint8_t &menuPos, char *zeile1, char *zeile2)
{
  menuPos = 0;
  ausgabe(zeile1, zeile2);
}
//
void sendHid(char *zeichen)
{
  DBG_PRINT(F("HID: "));
  DBG_PRINT(zeichen);
  DBG_PRINT(F("\tCode: "));
  for (uint8_t h = 0; h < hidNums; h++)
  {
    uint8_t hidSeq = pgm_read_byte_near(hid + h);
    if (hidSeq == uint8_t(zeichen[0]))
    {
      for (uint8_t b = 1; b < 9; b++)
      {
        uint8_t hidByte = pgm_read_byte_near(hid[h] + b);
        Serial.print(' '); Serial.print(hidByte, HEX);
      }
      Serial.println();
    }
  }
}
//
void printDisplay(char *zeile, byte number)          // Displayzeile und welche Zeilennummer
{
  display.setCursor(0, number);                      // cursor setzen
  uint8_t laenge = strlen(zeile);                    // wie lang ist die Zeichenkette?
  for (byte b = 0; b < (lcdSpalten - laenge) / 2; b++) // Errechnen der ersten Hälfte die
    display.print(' ');                              // mit Leerzeichen aufgefüllt
  display.print(zeile);                              // Ausgabe der Zeile
  for (byte b = (lcdSpalten - laenge) / 2 + laenge; b < lcdSpalten; b++)
    display.print(' ');                              // Auffüllen der Zeile bis Ende mit Spaces
  DBG_PRINT(F("Zeile "));
  DBG_PRINT(number + 1);
  DBG_PRINT(": ");
  DBG_PRINTLN(zeile);
}
//
void ausgabe(const char *zeile1, const char *zeile2)  // Übernahme beider Zeilen
{
  static char lastZeileOben[lcdSpalten + 1] = {'\0'}; // Merker der letzten Ausgabe
  static char lastZeileUnten[lcdSpalten + 1] = {'\0'};
  if (strcmp(lastZeileOben, zeile1))                  // Wenn sich der Inhalt geändert hat
  {
    memset(lastZeileOben, '\0', lcdSpalten + 1);      // Merker löschen
    strcpy(lastZeileOben, zeile1);                    // neu füllen
    printDisplay(lastZeileOben, 0);                   // und ausgeben
  }
  if (strcmp(lastZeileUnten, zeile2))
  {
    memset(lastZeileUnten, '\0', lcdSpalten + 1);
    strcpy(lastZeileUnten, zeile2);
    printDisplay(lastZeileUnten, 1);
  }
}
//
bool isDisplay()
{
  if (millis() - displayTick > displayZeit * 1000UL && !isMute)
  {
    display.noDisplay();
    return false;
  }
  display.display();
  return true;
}
//
void setMute()
{
  static uint32_t lastmillis = 0;
  static bool isPressed = false;
  if (!digitalRead(mutBut) && !isPressed)
  {
    isPressed = true;
    displayTick = millis();
    lastmillis = millis();
    isMute = !isMute;
    if (isMute) ausgabe("Mute aktiv", "");
    DBG_PRINT(F(""));
    char e[] = "e";
    sendHid(e);
  }
  else if ((millis() - lastmillis > bounceTime) &&
           (digitalRead(mutBut) && isPressed))
  {
    isPressed = false;
  }
}
//
uint8_t getTaste()
{
  uint8_t butPress = 0;
  static uint32_t pressTime = 0;
  static byte schritt = 0;
  bool isPressed = !digitalRead(rotBut);
  switch (schritt)
  {
    default:
      if (isPressed)
      {
        pressTime = millis();
        schritt = 1;
      }
      break;
    case 1:
      if (millis() - pressTime > bounceTime)
        isPressed ? schritt = 2 : schritt = 0;
      break;
    case 2:
      if (!isPressed)
      {
        if (!isDisplay())
          displayTick = millis();
        else if (millis() - pressTime > 500)
          butPress = 2;
        else
          butPress = 1;
        schritt = 3;
      }
      break;
    case 3:
      if (!isPressed)
        displayTick = millis();
      schritt = 0;
      break;
  }
  return butPress;
}
//
void heartbeat(const uint32_t tik)
{
  static uint32_t lasttik = 0;
  if (millis() - lasttik >= tik)
  {
    lasttik += tik;
    digitalWrite(mutLed, !digitalRead(mutLed));
  }
}
//
void startSequenz()
{
  uint32_t startTime = millis();
  ausgabe("Willkommen", "");
  while (millis() - startTime < 1500) {}
  startTime = millis();
  ausgabe("Booting", "");
  char zeile2[lcdSpalten + 1] = {'\0'};
  uint8_t pos = 0;
  while (pos < lcdSpalten)
  {
    if (millis() - startTime > 1000)
    {
      displayTick = millis();
      zeile2[pos] = '*';
      ausgabe("Booting", zeile2);
      startTime = millis();
      pos++;
    }
  }
}

OK, da ich das hier nicht als .zip hochladen kann, muss improvisiere ich.
Das hier ist der zweite tab. Der heisst hidTable.h.
Dann in dem Code oben die hidTable rausschmeissen.

const uint8_t hidNums = 95;
const uint8_t hid[hidNums][9] PROGMEM
{
  /* printable Zeichen beginnen ab DEC 32 und enden mit DEC 126! */
  /* Wenn der inhalt.id änderbar sein soll, muss die Tabelle     */
  /* in jedem Fall vollständig sein!                             */
  /* Das erste byte ist der DEC-Code des printable Zeichen!      */


{ 32,  0,  0,  0,  0,  0,  0,  0,  0}, /*   */
{ 33,  0,  0,  0,  0,  0,  0,  0,  0}, /* ! */
{ 34,  0,  0,  0,  0,  0,  0,  0,  0}, /* " */
{ 35,  0,  0,  0,  0,  0,  0,  0,  0}, /* # */
{ 36,  0,  0,  0,  0,  0,  0,  0,  0}, /* $ */
{ 37,  0,  0,  0,  0,  0,  0,  0,  0}, /* % */
{ 38,  0,  0,  0,  0,  0,  0,  0,  0}, /* & */
{ 39,  0,  0,  0,  0,  0,  0,  0,  0}, /* ' */
{ 40,  0,  0,  0,  0,  0,  0,  0,  0}, /* ( */
{ 41,  0,  0,  0,  0,  0,  0,  0,  0}, /* ) */
{ 42,  0,  0,  0,  0,  0,  0,  0,  0}, /* * */
{ 43,  0,  0,  0,  0,  0,  0,  0,  0}, /* + */
{ 44,  0,  0,  0,  0,  0,  0,  0,  0}, /* , */
{ 45,  0,  0,  0,  0,  0,  0,  0,  0}, /* - */
{ 46,  0,  0,  0,  0,  0,  0,  0,  0}, /* . */
{ 47,  0,  0,  0,  0,  0,  0,  0,  0}, /* / */
{ 48,  0,  0,  0,  0,  0,  0,  0,  0}, /* 0 */
{ 49,  0,  0,  0,  0,  0,  0,  0,  0}, /* 1 */
{ 50,  0,  0,  0,  0,  0,  0,  0,  0}, /* 2 */
{ 51,  0,  0,  0,  0,  0,  0,  0,  0}, /* 3 */
{ 52,  0,  0,  0,  0,  0,  0,  0,  0}, /* 4 */
{ 53,  0,  0,  0,  0,  0,  0,  0,  0}, /* 5 */
{ 54,  0,  0,  0,  0,  0,  0,  0,  0}, /* 6 */
{ 55,  0,  0,  0,  0,  0,  0,  0,  0}, /* 7 */
{ 56,  0,  0,  0,  0,  0,  0,  0,  0}, /* 8 */
{ 57,  0,  0,  0,  0,  0,  0,  0,  0}, /* 9 */
{ 58,  0,  0,  0,  0,  0,  0,  0,  0}, /* : */
{ 59,  0,  0,  0,  0,  0,  0,  0,  0}, /* ; */
{ 60,  0,  0,  0,  0,  0,  0,  0,  0}, /* < */
{ 61,  0,  0,  0,  0,  0,  0,  0,  0}, /* = */
{ 62,  0,  0,  0,  0,  0,  0,  0,  0}, /* > */
{ 63,  0,  0,  0,  0,  0,  0,  0,  0}, /* ? */
{ 64,  0,  0,  0,  0,  0,  0,  0,  0}, /* @ */
{ 65,  0,  0,  0,  0,  0,  0,  0,  0}, /* A */
{ 66,  0,  0,  0,  0,  0,  0,  0,  0}, /* B */
{ 67,  0,  0,  0,  0,  0,  0,  0,  0}, /* C */
{ 68,  0,  0,  0,  0,  0,  0,  0,  0}, /* D */
{ 69,  0,  0,  0,  0,  0,  0,  0,  0}, /* E */
{ 70,  0,  0,  0,  0,  0,  0,  0,  0}, /* F */
{ 71,  0,  0,  0,  0,  0,  0,  0,  0}, /* G */
{ 72,  0,  0,  0,  0,  0,  0,  0,  0}, /* H */
{ 73,  0,  0,  0,  0,  0,  0,  0,  0}, /* I */
{ 74,  0,  0,  0,  0,  0,  0,  0,  0}, /* J */
{ 75,  0,  0,  0,  0,  0,  0,  0,  0}, /* K */
{ 76,  0,  0,  0,  0,  0,  0,  0,  0}, /* L */
{ 77,  0,  0,  0,  0,  0,  0,  0,  0}, /* M */
{ 78,  0,  0,  0,  0,  0,  0,  0,  0}, /* N */
{ 79,  0,  0,  0,  0,  0,  0,  0,  0}, /* O */
{ 80,  0,  0,  0,  0,  0,  0,  0,  0}, /* P */
{ 81,  0,  0,  0,  0,  0,  0,  0,  0}, /* Q */
{ 82,  0,  0,  0,  0,  0,  0,  0,  0}, /* R */
{ 83,  0,  0,  0,  0,  0,  0,  0,  0}, /* S */
{ 84,  0,  0,  0,  0,  0,  0,  0,  0}, /* T */
{ 85,  0,  0,  0,  0,  0,  0,  0,  0}, /* U */
{ 86,  0,  0,  0,  0,  0,  0,  0,  0}, /* V */
{ 87,  0,  0,  0,  0,  0,  0,  0,  0}, /* W */
{ 88,  0,  0,  0,  0,  0,  0,  0,  0}, /* X */
{ 89,  0,  0,  0,  0,  0,  0,  0,  0}, /* Y */
{ 90,  0,  0,  0,  0,  0,  0,  0,  0}, /* Z */
{ 91,  0,  0,  0,  0,  0,  0,  0,  0}, /* [ */
{ 92,  0,  0,  0,  0,  0,  0,  0,  0}, /* \ */
{ 93,  0,  0,  0,  0,  0,  0,  0,  0}, /* ] */
{ 94,  0,  0,  0,  0,  0,  0,  0,  0}, /* ^ */
{ 95,  0,  0,  0,  0,  0,  0,  0,  0}, /* _ */
{ 96,  0,  0,  0,  0,  0,  0,  0,  0}, /* ` */
{ 97,  0,  0,  0,  0,  0,  0,  0,  0}, /* a */
{ 98,  0,  0,  0,  0,  0,  0,  0,  0}, /* b */
{ 99,  0,  0,  0,  0,  0,  0,  0,  0}, /* c */
{100,  0,  0,  0,  0,  0,  0,  0,  0}, /* d */
{101,  0,  0,  0,  0,  0,  0,  0,  0}, /* e */
{102,  0,  0,  0,  0,  0,  0,  0,  0}, /* f */
{103,  0,  0,  0,  0,  0,  0,  0,  0}, /* g */
{104,  0,  0,  0,  0,  0,  0,  0,  0}, /* h */
{105,  0,  0,  0,  0,  0,  0,  0,  0}, /* i */
{106,  0,  0,  0,  0,  0,  0,  0,  0}, /* j */
{107,  0,  0,  0,  0,  0,  0,  0,  0}, /* k */
{108,  0,  0,  0,  0,  0,  0,  0,  0}, /* l */
{109,  0,  0,  0,  0,  0,  0,  0,  0}, /* m */
{110,  0,  0,  0,  0,  0,  0,  0,  0}, /* n */
{111,  0,  0,  0,  0,  0,  0,  0,  0}, /* o */
{112,  0,  0,  0,  0,  0,  0,  0,  0}, /* p */
{113,  0,  0,  0,  0,  0,  0,  0,  0}, /* q */
{114,  0,  0,  0,  0,  0,  0,  0,  0}, /* r */
{115,  0,  0,  0,  0,  0,  0,  0,  0}, /* s */
{116,  0,  0,  0,  0,  0,  0,  0,  0}, /* t */
{117,  0,  0,  0,  0,  0,  0,  0,  0}, /* u */
{118,  0,  0,  0,  0,  0,  0,  0,  0}, /* v */
{119,  0,  0,  0,  0,  0,  0,  0,  0}, /* w */
{120,  0,  0,  0,  0,  0,  0,  0,  0}, /* x */
{121,  0,  0,  0,  0,  0,  0,  0,  0}, /* y */
{122,  0,  0,  0,  0,  0,  0,  0,  0}, /* z */
{123,  0,  0,  0,  0,  0,  0,  0,  0}, /* { */
{124,  0,  0,  0,  0,  0,  0,  0,  0}, /* | */
{125,  0,  0,  0,  0,  0,  0,  0,  0}, /* } */
{126,  0,  0,  0,  0,  0,  0,  0,  0}, /* ~ */
};

Nach Rücksprache mit Commander McLane stimme ich Dir zu.

Ich wollte nur schnell und schmutzig den Unterschied zwischen dem auf der Tastatur aufgedruckten Buchstaben und dem HID-Code verdeutlichen.

Grundsätzlich gut ... gut ... von mir aus gerne

Die Trennung von Buchstabe und HID-Code in einer Struktur gefiel mir besser, weil dann Serial.write(code, 8); möglich ist.

Mir fehlt Serial.write.

Vermutlich muß für die HID-Software im ATmega16U2 auch die Baudrate auf 9600 runter, zumindest hat es damit funktioniert.

Sehr freundlich, extra eine Option für mich einzurichten, aber mit LiquidCrystal_I2C geht es bei mir genauso gut. Wir können also gerne die selbe Konfiguration verwenden, mein Mega2560 freut sich.

Ja.

Ich war noch nicht in allen Ecken, das mache ich nach Abarbeitung meiner Anmerkungen :thinking:

Alter Schwede... -Brutal!
Irgendwie komme ich dennoch nicht klar mit den Zeicheneingaben.
Aber erst mal der Code wie er bei mir zumindest fast tut:

//#define debug
#define ff true
//#define ag true

#ifdef ff
  #undef ag
  #undef debug
#endif

const byte lcdZeilen = 2;
const byte lcdSpalten = 20;

// Rotary
#include <rotary.h>

// bedingte Kompilierung - Zuweisung der Konfigurationen
#ifdef ff
  #include <Adafruit_CharacterOLED.h>
  Adafruit_CharacterOLED display(OLED_V2, 4, 6, 5, 7, 8, 9, 10);//, LCD_EUROPEAN_II);
  Rotary r = Rotary(3, 2);  // Hier muss ff seine Konfig rein
  const byte rotBut = 13;
  const byte mutBut = 12;
  const byte mutLed = 255;
#elif ag
  #include <Wire.h>
  #include <NoiascaLiquidCrystal.h>
  #include <NoiascaHW/lcd_PCF8574.h>
  LiquidCrystal_PCF8574 display(0x27, lcdSpalten, lcdZeilen);
  Rotary r = Rotary(45, 43);
  const byte rotBut = 41;
  const byte mutBut = 12;
  const byte mutLed = 255;
#else
  #include <LiquidCrystal_I2C.h>
  const byte lcdAddress = 0x27;
  LiquidCrystal_I2C display(lcdAddress, lcdSpalten, lcdZeilen);
  Rotary r = Rotary(45, 43);
  const byte rotBut = 41;
  const byte mutBut = 12;
  const byte mutLed = LED_BUILTIN;
#endif

enum {kurz = 1, lang};

#ifdef debug
  #define DBG_PRINT(x) Serial.print(x)
  #define DBG_PRINTLN(x) Serial.println(x)
#else
  #define DBG_PRINT(x)
  #define DBG_PRINTLN(x)
#endif

const uint32_t bounceTime = 20; // zeit in ms
// Inhalt
struct inhalt
{
  char name[lcdSpalten + 1];
  char hid[2];
};
// #include "hidTable.h"

// Die folgenden Zeilen decken nur die verwendeten Codes ab
// eine vollständige Tabelle, die ggfls. gefüllt werden muss,
// befindet sich im angehängten Archiv!
const uint8_t hidNums = 11;
const uint8_t hid[hidNums][9] PROGMEM
{
  // printable Zeichen beginnen ab DEC 32 und enden mit DEC 126!
  // Wenn der inhalt.id änderbar sein soll, muss die Tabelle
  // in jedem Fall vollständig sein! Dann evtl. auslagern!
  // Das erste byte ist der DEC-Code des printable Zeichen!
  { 97, 0, 0,  8, 40, 0, 0, 0, 0}, // a
  {100, 0, 0, 23, 40, 0, 0, 0, 0}, // d
  {101, 0, 0,  0,  0, 0, 0, 0, 0}, // e
  {102, 0, 0, 28, 40, 0, 0, 0, 0}, // f
  {103, 0, 0,  0,  0, 0, 0, 0, 0}, // g
  {106, 0, 0, 29, 40, 0, 0, 0, 0}, // j
  {107, 0, 0, 29, 40, 0, 0, 0, 0}, // k
  {108, 0, 0, 29, 40, 0, 0, 0, 0}, // l
  {113, 0, 0, 29, 40, 0, 0, 0, 0}, // q
  {115, 0, 0, 21, 40, 0, 0, 0, 0}, // s
  {119, 0, 0, 29, 40, 0, 0, 0, 0}, // w
};

inhalt MyFilter[]
{
  {"Filter 1", "a" }, {"Filter 2", "s" },
  {"Filter 3", "d" }, {"Filter 4", "f" }
};

inhalt MySetup[]
{
  {"Auswahl",       "" }, {"Displayzeit",    ""},
  {"Wlan",          "g"}, {"Bezeichner",     ""},
  {"Platzhalter 1", "j"}, {"Platzhalter 2", "k"},
  {"Platzhalter 3", "l"}, {"Platzhalter 4", "q"},
  {"Platzhalter 5", "w"}, {"exit",           ""}
};
uint8_t displayZeit = 20;                        // Zeit in Sekunden bis abschalten
uint32_t displayTick = 0;                        // Merker für letzte Aktion am Encoder oder Button

char displayZeileOben[lcdSpalten + 1] = {'\0'};  // Globale Variablen für DisplayZeilen
char displayZeileUnten[lcdSpalten + 1] = {'\0'};

bool isMenu = true;
bool isSetup = !isMenu;
bool isMute = false;

void setup()
{
  Serial.begin(9600);
  DBG_PRINTLN(F("Start..."));
  //startSequenz();
  pinMode (rotBut, INPUT);
  pinMode (mutBut, INPUT_PULLUP);
  pinMode (mutLed, OUTPUT);
#ifdef ff
  display.begin(lcdSpalten, lcdZeilen);
#elif ag
  Wire.begin();
  display.begin();
  display.backlight();
#else
  display.begin();
#endif
}
//
void loop()
{
  if (isMute)
  {
    heartbeat(500);
  }
  else
  {
    if (digitalRead(mutLed))     // wenn nicht Mute
      digitalWrite(mutLed, LOW); // sicherstellen, das aus
    if (isMenu) filterMenu();
    if (isSetup) setupMenu();
  }
  setMute();
  isDisplay();
}
//
// Die folgende Funktion ist für die Auswahl und Übergabe jeweiliger
// Menueinträge vorgesehen. Die sich ständig wiederholenden Abfragen
// des RotaryEncoders in den unterschiedlichen Menus zusammengefasst
// rotaryPos ist die aktuelle Position, die einzige die veränderlich ist ;)
void menuAuswahl(uint8_t &rotaryPos, const uint8_t &minPos,
                 const uint8_t &maxPos, const char *zeile1, const char *zeile2)
{
  unsigned char val = r.process();
  if (val && isDisplay())
  {
    if (val == r.clockwise() )
    {
      if (rotaryPos == maxPos) rotaryPos = minPos;
      else rotaryPos++;
    }
    else if (val == r.counterClockwise())
    {
      if (rotaryPos == minPos) rotaryPos = maxPos;
      else rotaryPos--;
    }
    displayTick = millis();
  }
  ausgabe(zeile1, zeile2);
}
//
void filterMenu()                                                // filterauswahl ist default
{
  memset(displayZeileOben, '\0', lcdSpalten + 1);                // Zeile leeren
  strcpy(displayZeileOben, "Filter waehlen");                    // und neu befüllen
  const uint8_t maxPos = sizeof(MyFilter) / sizeof(inhalt) - 1;
  const uint8_t minPos = 0;
  static uint8_t rotaryPos = 0;
  if (isDisplay())                                               // Nur wenn Display an ist
    // Übergeben wird die (sich nicht mehr ändernde) obere Zeile und der Inhalt
    // aus dem Struct. Darstellung wird dann in der Funktion menuAuswahl() gebaut
    menuAuswahl(rotaryPos, minPos, maxPos, displayZeileOben, MyFilter[rotaryPos].name);
  switch (getTaste())                                            // Tastenabfrage
  {
    case kurz: sendHid(MyFilter[rotaryPos].hid); break;
    case lang: {isMenu = false; isSetup = true;} break;
  }
}
//
void updateEEprom()
{
  // HIER WIRD SPÄTER WEITER GEMACHT
}
//
bool editBezeichner()   // Ein Versuch - wird ggfls. noch aufgelöst
{
  const uint8_t num = 3;
  const char bezeichner[num + 1][lcdSpalten + 1] = {"Filtertexte", "Filterkey", "Setuptexte", "Setupkey"};
  static uint8_t schritt = 0;
  static uint8_t rotaryPos = 0;
  static uint8_t auswahl = 0;
  const uint8_t minPos = 0;
  static bool returnWert = true;
  static uint8_t spalte = 0;
  static char editZeile[lcdSpalten + 1] = {'\0'};
  if (!returnWert)    // Wenn erster Aufruf, init der Var
  {
    returnWert = true;
    schritt = 0;
    rotaryPos = 0;
    memset(editZeile, '\0', lcdSpalten + 1);
    auswahl = 0;
  }
  switch (schritt)
  {
    case 0:
      {
        menuAuswahl(rotaryPos, minPos, num, "Editieren", bezeichner[rotaryPos]);
        switch (getTaste())
        {
          case kurz:
            memset(displayZeileOben, '\0', lcdSpalten + 1);
            strcpy(displayZeileOben, bezeichner[rotaryPos]);
            auswahl = rotaryPos;
            rotaryPos = 0;
            schritt = 1;
            break;
          case lang:
            schritt = 9;
            break;
        }
      }
      break;
    case 1:
      switch (auswahl)
      {
        case 0:
          menuAuswahl(rotaryPos, minPos, sizeof(MyFilter) / sizeof(inhalt) - 1, displayZeileOben, MyFilter[rotaryPos].name);
          strcpy(editZeile, MyFilter[rotaryPos].name);
          break;
        case 1:
          memset(displayZeileOben, '\0', lcdSpalten + 1);
          strcpy(displayZeileOben,  MyFilter[rotaryPos].name);
          menuAuswahl(rotaryPos, minPos, sizeof(MyFilter) / sizeof(inhalt) - 1, displayZeileOben, MyFilter[rotaryPos].hid);
          strcpy(editZeile, MyFilter[rotaryPos].hid);
          break;
        case 2:
          menuAuswahl(rotaryPos, minPos, sizeof(MySetup) / sizeof(inhalt) - 1,  displayZeileOben, MySetup[rotaryPos].name);
          strcpy(editZeile, MySetup[rotaryPos].name);
          break;
        case 3:
          memset(displayZeileOben, '\0', lcdSpalten + 1);
          strcpy(displayZeileOben,  MySetup[rotaryPos].name);
          menuAuswahl(rotaryPos, minPos, sizeof(MySetup) / sizeof(inhalt) - 1, displayZeileOben, MySetup[rotaryPos].hid);
          strcpy(editZeile, MySetup[rotaryPos].hid);
          break;
      }
      switch (getTaste())
      {
        case kurz:
          spalte = 0;
          display.blink();
          display.setCursor(spalte, 1);
          display.print(editZeile);
          for (uint8_t b = strlen(editZeile); b < lcdSpalten; b++) display.print(' ');
          display.setCursor(spalte, 1);
          schritt = 2;
          break;
        case lang:
          schritt = 9;
          break;
      }
      break;
    case 2:
      {
        uint8_t buchstabe = uint8_t(editZeile[spalte]);
        setupZahl(buchstabe, 32, 126);
        if (uint8_t(editZeile[spalte]) != buchstabe)
        {
          editZeile[spalte] = char(buchstabe);
          display.setCursor(spalte, 1);
          display.print(char(buchstabe));
          display.setCursor(spalte, 1);
        }
        switch (getTaste())
        {
          case kurz:
            editZeile[spalte] = char(buchstabe);
            spalte++;
            if (auswahl == 0 || auswahl == 2)
            {
              if (spalte >= lcdSpalten)  spalte = 0;
            }
            else
              spalte = 0;
            display.setCursor(spalte, 1); break;
          case lang:
            display.noBlink();
            switch (auswahl)
            {
              case 0: memcpy(MyFilter[rotaryPos].name, editZeile, sizeof(MyFilter[rotaryPos].name) - 1); break;
              case 1: memcpy(MyFilter[rotaryPos].hid, editZeile, sizeof(MyFilter[rotaryPos].hid) - 1); break;
              case 2: memcpy(MySetup[rotaryPos].name, editZeile, sizeof(MySetup[rotaryPos].name) - 1); break;
              case 3: memcpy(MySetup[rotaryPos].hid, editZeile, sizeof(MySetup[rotaryPos].hid) - 1); break;
            }
            updateEEprom();
            schritt = 0;
            break;
        }
      }
      break;
    case 9:
      returnWert = false;
      break;
  }
  if (!isDisplay())
  {
    display.noBlink();
    returnWert = false;
  }
  return returnWert;
}
//
void setupMenu()
{
  const uint8_t maxPos = (sizeof(MySetup) / sizeof(inhalt)) - 1;  // Anzahl der Elemente ermitteln
  const uint8_t minPos = 1;                                       // Der Eintrag 0 ist kein Menueintrag
  static uint8_t rotaryPos = minPos;
  static uint8_t menuPos = 0;
  static uint8_t subMenuPos = 0;                                  // Hilfsvariable für Untermenu
  switch (menuPos)
  {
    case 0:
      menuAuswahl(rotaryPos, minPos, maxPos, MySetup[0].name, MySetup[rotaryPos].name);
      if (getTaste() == kurz) menuPos = rotaryPos;                // Wenn ausgewählt: menuPos setzen
      else if (menuPos == 2) subMenuPos = 0;                      // wenn Submenu gebraucht wird
      break;
    case 1:                                                       // Die displayZeit wird
      displayTime(MySetup[menuPos].name, displayZeit);            // wieder direkt geändert
      if (getTaste() == kurz)                                     // verlassen des Menupunktes
        menuEnde(menuPos, MySetup[0].name, MySetup[menuPos].name);// zurücksetzen auf eine Ebene höher
      break;
    case 2:                                                       // das dürfte klar sein
      {
        const char zeile2[][12] = {"umschalten", "exit"};
        menuAuswahl(subMenuPos, 0 , 1, MySetup[menuPos].name, zeile2[subMenuPos]);
        if (getTaste() == kurz)
        {
          if (subMenuPos == 0) sendHid(MySetup[menuPos].hid);
          menuEnde(menuPos, MySetup[0].name, MySetup[rotaryPos].name);
        }
      }
      break;
    case 3:                                                       // Ein Versuch die Texte zu ändern
      if (!editBezeichner())
      {
        DBG_PRINTLN("ENDE");
        menuEnde(menuPos, MySetup[0].name, MySetup[menuPos].name);
      }
      break;
    case 4 ... 8:
      DBG_PRINTLN(F("Platzhalter"));
      sendHid(MySetup[rotaryPos].hid);
      menuEnde(menuPos, MySetup[0].name, MySetup[menuPos].name);
      break;
  }                                                  // Es fehlt absichtlich das case auf maxPos!!
  if (!isDisplay() || (menuPos == maxPos))           // Wenn der Display aus geht ODER maxPos (exit) ausgelöst
  {
    menuPos = 0;                                     // alles zurücksetzen
    rotaryPos = minPos;
    isMenu = true; isSetup = false;
  }
}
//
void displayTime(char *zeile1, uint8_t &sekunde)     // Einstellen der DisplayTime
{
  setupZahl(sekunde);                                // Sekunde verstellen
  if (sekunde < 5) sekunde = 5;
  memset(displayZeileUnten, '\0', lcdSpalten + 1);   // untere DisplayZeile leeren
  sprintf(displayZeileUnten, "%d", sekunde);         // und mit Zahl füllen
  ausgabe(zeile1, displayZeileUnten);                // und ausgeben
}
//
void setupZahl(uint8_t &aenderZahl, const uint8_t &minZahl, const uint8_t &maxZahl)
{
  uint32_t myZahl = aenderZahl;
  setupZahl(myZahl);
  if (myZahl > maxZahl) myZahl = minZahl;
  if (myZahl < minZahl) myZahl = maxZahl;
  aenderZahl = myZahl;
}
void setupZahl(uint8_t &aenderZahl)                  // Je nach Größe der
{
  uint32_t myZahl = aenderZahl;
  setupZahl(myZahl, 255);
  aenderZahl = myZahl;
}
void setupZahl(uint16_t &aenderZahl)                 // übergebenen Zahl
{
  uint32_t myZahl = aenderZahl;
  setupZahl(myZahl, 65535);                          // begrenzen des Zählers
  aenderZahl = (uint16_t)myZahl;
}
void setupZahl(uint32_t &aenderZahl, const uint32_t &maxZahl)
{
  setupZahl(aenderZahl);
  if (aenderZahl > maxZahl) aenderZahl = 0;
}
void setupZahl(uint32_t &aenderZahl)
{
  unsigned char val = r.process();
  if (val && isDisplay())                            // und hier einfach nur auf/ab zaehlen
  {
    if (val == r.clockwise())
      aenderZahl++;
    if (val == r.counterClockwise())
      aenderZahl--;
    displayTick = millis();                          // und dem Merker sagen, das sich was bewegt hat
  }
}
//
void menuEnde(uint8_t &menuPos, char *zeile1, char *zeile2)
{
  menuPos = 0;
  ausgabe(zeile1, zeile2);
}
//
void sendHid(char *zeichen)
{
  DBG_PRINT(F("HID: "));
  DBG_PRINT(zeichen);
  DBG_PRINT(F("\tCode: "));
  for (uint8_t h = 0; h < hidNums; h++)
  {
    uint8_t hidSeq = pgm_read_byte_near(hid + h);
    if (hidSeq == uint8_t(zeichen[0]))
    {
      for (uint8_t b = 1; b < 9; b++)
      {
        uint8_t hidByte = pgm_read_byte_near(hid[h] + b);
        Serial.print(' '); Serial.print(hidByte, HEX);
      }
      Serial.println();
    }
  }
}
//
void printDisplay(char *zeile, byte number)          // Displayzeile und welche Zeilennummer
{
  display.setCursor(0, number);                      // cursor setzen
  uint8_t laenge = strlen(zeile);                    // wie lang ist die Zeichenkette?
  for (byte b = 0; b < (lcdSpalten - laenge) / 2; b++) // Errechnen der ersten Hälfte die
    display.print(' ');                              // mit Leerzeichen aufgefüllt
  display.print(zeile);                              // Ausgabe der Zeile
  for (byte b = (lcdSpalten - laenge) / 2 + laenge; b < lcdSpalten; b++)
    display.print(' ');                              // Auffüllen der Zeile bis Ende mit Spaces
  DBG_PRINT(F("Zeile "));
  DBG_PRINT(number + 1);
  DBG_PRINT(": ");
  DBG_PRINTLN(zeile);
}
//
void ausgabe(const char *zeile1, const char *zeile2)  // Übernahme beider Zeilen
{
  static char lastZeileOben[lcdSpalten + 1] = {'\0'}; // Merker der letzten Ausgabe
  static char lastZeileUnten[lcdSpalten + 1] = {'\0'};
  if (strcmp(lastZeileOben, zeile1))                  // Wenn sich der Inhalt geändert hat
  {
    memset(lastZeileOben, '\0', lcdSpalten + 1);      // Merker löschen
    strcpy(lastZeileOben, zeile1);                    // neu füllen
    printDisplay(lastZeileOben, 0);                   // und ausgeben
  }
  if (strcmp(lastZeileUnten, zeile2))
  {
    memset(lastZeileUnten, '\0', lcdSpalten + 1);
    strcpy(lastZeileUnten, zeile2);
    printDisplay(lastZeileUnten, 1);
  }
}
//
bool isDisplay()
{
  if (millis() - displayTick > displayZeit * 1000UL && !isMute)
  {
    display.noDisplay();
    return false;
  }
  display.display();
  return true;
}
//
void setMute()
{
  static uint32_t lastmillis = 0;
  static bool isPressed = false;
  if (!digitalRead(mutBut) && !isPressed)
  {
    isPressed = true;
    displayTick = millis();
    lastmillis = millis();
    isMute = !isMute;
    if (isMute) ausgabe("Mute aktiv", "");
    DBG_PRINT(F(""));
    char e[] = "e";
    sendHid(e);
  }
  else if ((millis() - lastmillis > bounceTime) &&
           (digitalRead(mutBut) && isPressed))
  {
    isPressed = false;
  }
}
//
uint8_t getTaste()
{
  uint8_t butPress = 0;
  static uint32_t pressTime = 0;
  static byte schritt = 0;
  bool isPressed = !digitalRead(rotBut);
  switch (schritt)
  {
    default:
      if (isPressed)
      {
        pressTime = millis();
        schritt = 1;
      }
      break;
    case 1:
      if (millis() - pressTime > bounceTime)
        isPressed ? schritt = 2 : schritt = 0;
      break;
    case 2:
      if (!isPressed)
      {
        if (!isDisplay())
          displayTick = millis();
        else if (millis() - pressTime > 500)
          butPress = 2;
        else
          butPress = 1;
        schritt = 3;
      }
      break;
    case 3:
      if (!isPressed)
        displayTick = millis();
      schritt = 0;
      break;
  }
  return butPress;
}
//
void heartbeat(const uint32_t tik)
{
  static uint32_t lasttik = 0;
  if (millis() - lasttik >= tik)
  {
    lasttik += tik;
    digitalWrite(mutLed, !digitalRead(mutLed));
  }
}
//
void startSequenz()
{
  uint32_t startTime = millis();
  ausgabe("Willkommen", "");
  while (millis() - startTime < 1500) {}
  startTime = millis();
  ausgabe("Booting", "");
  char zeile2[lcdSpalten + 1] = {'\0'};
  uint8_t pos = 0;
  while (pos < lcdSpalten)
  {
    if (millis() - startTime > 1000)
    {
      displayTick = millis();
      zeile2[pos] = '*';
      ausgabe("Booting", zeile2);
      startTime = millis();
      pos++;
    }
  }
}

Ich musste noch LCD_EUROPEAN_II auskommentieren, da hier ein Fehler beim kommentieren kam

Display.begin funktioniert wiedererwartend, ich hätte gedacht ich muss das wieder in display.display ändern

Serial.begin 9600, sonst passierte gar nichts beim absendenen von HID Befehlen, pc spielt verrückt

In der hidTable.h { 97, 0, 0, 8, 40, 0, 0, 0, 0}, /* a */ habe ich nur mal eine Zeile eingetragen, es kommt aber immer §§_§', bei weiteren drücken dann §§§'JG'_ permanet Enter, drücke ich dann wieder die Taste stopp Enter, aber man kann den PC erst bedienen, wenn man das USB abzieht.

:+1:

bzw., wenn man zB. ins SetupMenü rein geht würde man intuitiv erwarten, dass sich die Anzeige ändert, wenn man auf den RotBut drauf bleibt, die Anzeige springt aber erst beim loslassen um.

Sonst würde ich sagen, ist alles drin was rein gehört, wenn das mit den HID Tasten noch tut :+1: