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 
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 
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}, /* ~ */
};