Tastatur-Manager
auf dem Pico-Geek



C++ mit Arduino IDE






Das Waveshare RP2040-GEEK Entwicklungsboard basiert auf dem RP2040 Mikrocontroller, mit 1.14 Zoll 65K Farb-LCD, TF-Kartensteckplatz, USB-Debugging-Downloader, im weißen Kunststoffgehäuse. Darüber hinaus ist es ausgestattet mit einem USB-A-Interface. Es bietet verschiedene Firmware für SWD-Port, UART-Port und I2C-Port. An der vierpoligen I2C/ADC Buchse ist der GPIO 28 nach außen geführt. Was der Pico-Geek nicht besitzt, sind Tasten für direkte Eingaben. Damit könnte man z.B. einen Pin eingeben, um etwas freizuschalten o.ä. In diesem Beitrag habe ich gezeigt, dass man mit dem analogen Keyboard Modul bei nur einem ADC-Port viele neue Möglichkeiten hat.

Hardware

- Pico-Geek (z.B. von Botland )
- Analoges Keyboard Modul mit fünf Tasten(z.B. von Funduino )


Software

- aktuelle Firmware Arduino IDE

Am Ende dieses Beitrags sollen Sie in der Lage sein, einen selbst gewählten sechsstelligen Pin in den EEPROM des Pico-Geek zu schreiben, der bei jeder Inbetriebnahme ausgelesen wird. Sie merken sich den, nur Ihnen bekannten Pin, und geben ihn auf Anforderung ein. Stimmt er mit dem abgespeicherten überein, dann wird eine 'Aktion' freigeschaltet.

Zuerst zeige ich, wie Sie den EEPROM beschreiben bzw. auslesen. Der Raspberry Pi Pico RP2040 hat keinen integrierten EEPROM-Speicher, sondern nutzt dafür einen Teil des Flash-Speichers. Deshalb muss dieser Bereich zunächst festgelegt werden. Ausserdem ist es wichtig, dass man den Schreibvorgang mit 'EEPROM.commit()' abschließt. Die meisten 'Hilferufe' im Netz, dass es nicht funktioniert, haben diesen Fehler. Hier ist der Quellcode, um sechs Werte (unseren Pin) zu schreiben. Die Pause in Zeile 15 gibt Ihnen Zeit, nach dem Hochladen in den seriellen Monitor zu wechseln, um die Ausgabe zu sehen. Die Loop-Schleife (30, 31) ist leer, muss aber vorhanden sein, damit fehlerfrei kompiliert wird.

  
  
1  #include <EEPROM.h>
2
3  // ================= EEPROM Layout =================
4  #define EEPROM_SIZE 512
5  #define EEPROM_PIN_START 0
6
7  int a = 0;
8  int valEE;
9
10 // Pin festlegen
11 int myPin[6] = {6,5,4,3,2,1};
12
13 void setup(){
14   Serial.begin(9600);
15   delay(5000);
16
17   EEPROM.begin(EEPROM_SIZE);
18
19   Serial.print("EEPROM schreiben\n");
20   for (a = 0; a < 6; a++){
21     // Adresse mit Adresswert beschreiben
22     EEPROM.write(EEPROM_PIN_START + a, myPin[a]);
23     Serial.print(a+1);
24     Serial.print(" . Wert:  ");
25     Serial.println(myPin[a]);
26     delay(100);
27   }
28   EEPROM.commit();
29 }
30 void loop(){
31 }
  

Mit diesem Quellcode wird also einmalig ein Pin '654321' in den Speicher geschrieben, den Sie später durch einen eigenen ersetzen. Immer wenn Sie den Pin ändern wollen, führen Sie diesen kurzen Sketch aus (d.h. in der Arduino-IDE öffnen und hochladen).
Nun zum Auslesen. Wichtig ist, dass der gleiche EEPROM Bereich (Zeilen 4 u. 5) wie beim Schreiben genutzt wird. Damit die Zuordnung der gelesenen Werte funktioniert, wird in Zeile 8 ein 'leerer' Pin definiert. Der Lesevorgang wird später in einen Sketch integriert, der mit einem einzugebenden Pin verglichen wird und den Geek freischaltet bzw. sperrt.

  
  
1  #include <EEPROM.h>
2
3  // ================= EEPROM Layout =================
4  #define EEPROM_SIZE 512
5  #define EEPROM_PIN_START 0
6
7  // leeren Pin definieren
8  int myPin[6] = {0,0,0,0,0,0};
9
10 void setup(){
11   Serial.begin(9600);
12   delay(5000);
13
14   EEPROM.begin(EEPROM_SIZE);
15
16  Serial.print("EEPROM lesen\n");
17  for (int i = 0; i < 6; i++) {
18    int val = EEPROM.read(EEPROM_PIN_START + i);
19    Serial.print(val);
20   }
21 }
22 void loop(){
23 }
  

Im anschließenden Sketch wollen wir auch das Display des Pico-Geek verwenden. Dort soll beim Einschalten zur Eingabe des Pins aufgefordert werden. Mit den Tasten (SW2) 'hoch' und (SW3) 'runter' werden die einzelnen 'Digits' gewählt. Mit den Tasten (SW4) 'rechts' bzw. (SW1) 'links' ändert man die Cursorposition. Sichtbar ist immer nur das gerade bearbeitete 'Digit'. Alle anderen werden als Sternchen dargestellt. Zum Abschluss der Eingabe wird die Taste (SW5) 'OK' gedrückt. Im unteren Kasten sehen Sie den kompletten Quellcode:

  
  
1   #include <Adafruit_GFX.h>
2   #include <Adafruit_ST7789.h>
3   #include <SPI.h>
4   #include <Fonts/FreeSansBold12pt7b.h>
5   #include <EEPROM.h>
6
7   // ======== Display (Pins für Pico-Geek angepasst) ======
8   #define TFT_CS   9
9   #define TFT_RST  12
10  #define TFT_DC   8
11  #define TFT_BLK  13
12
13  // ================= Display & PinManager =================
14  Adafruit_ST7789 tft = Adafruit_ST7789(&SPI1, TFT_CS, TFT_DC, TFT_RST);
15
16  // ======== ADC Pin des Pico========
17  const int analogPin = 28;
18
19  // ================= EEPROM Layout =================
20  #define EEPROM_SIZE 512
21  #define EEPROM_PIN_START 0
22
23  // ======== Button Definition ========
24  enum Button {
25    BTN_NONE,
26    BTN_LEFT,
27    BTN_UP,
28    BTN_DOWN,
29    BTN_RIGHT,
30    BTN_OK
31  };
32
33  struct ButtonRange {
34    int minVal;
35    int maxVal;
36    Button button;
37  };
38
39  ButtonRange buttons[] = {
40    {0,     500,   BTN_LEFT},
41    {1500,  4500,  BTN_UP},
42    {5000,  7000,  BTN_DOWN},
43    {10000, 15000, BTN_RIGHT},
44    {20000, 30000, BTN_OK}
45  };
46
47  const int buttonCount = sizeof(buttons) / sizeof(buttons[0]);
48
49  // ======== PIN System ========
50  int pinDigits[6] = {0,0,0,0,0,0};
51  int correctPin[6] = {0,0,0,0,0,0};   // Beispiel-PIN
52  int cursorPos = 0;
53  bool pinActive = true; // true = Eingabe aktiv
54
55  // ======== Button State ========
56  Button lastButton = BTN_NONE;
57
58  // ======================================================
59  // Setup
60  // ======================================================
61  void setup() {
62    Serial.begin(115200);
63    delay(300);
64
65    EEPROM.begin(EEPROM_SIZE);
66    // ======== PIN einlesen ===========
67    for (int i = 0; i < 6; i++) {
68      int val = EEPROM.read(EEPROM_PIN_START + i);
69      correctPin[i] = val;
70    }
71
72    SPI1.setSCK(10);
73    SPI1.setTX(11);
74    SPI1.begin();
75    // Auflösung Analogbereich: 0-65535
76    analogReadResolution(16);
77    // Anzeige vorbereiten und starten
78    tft.init(135,240);
79    tft.setRotation(1);
80    tft.fillScreen(ST77XX_BLACK);
81    tft.setFont(&FreeSansBold12pt7b);
82    tft.setCursor(50,30);
83    tft.setTextColor(ST77XX_YELLOW);
84    tft.print("Enter PIN");
85    drawPin();
86  }
87
88  // ======================================================
89  // Loop
90  // ======================================================
91  void loop() {
92    Button current = getButton();
93    // Flankenerkennung (nur bei neu gedrückter Taste reagieren)
94    if (current != BTN_NONE && lastButton == BTN_NONE) {
95      handleButton(current);
96    }
97    lastButton = current;
98    delay(30);
99  }
100
101 // ======================================================
102 // ADC ? Button
103 // ======================================================
104 Button getButton() {
105   int value = analogRead(analogPin);
106   for (int i = 0; i < buttonCount; i++) {
107     if (value >= buttons[i].minVal &&
108         value <= buttons[i].maxVal) {
109       return buttons[i].button;
110     }
111   }
112   return BTN_NONE;
113 }
114
115 // ======================================================
116 // Button Handling
117 // ======================================================
118 void handleButton(Button btn) {
119   switch (btn) {
120     case BTN_UP:
121       pinDigits[cursorPos]++;
122       if (pinDigits[cursorPos] > 9)
123         pinDigits[cursorPos] = 0;
124       break;
125     case BTN_DOWN:
126       pinDigits[cursorPos]--;
127       if (pinDigits[cursorPos] < 0)
128         pinDigits[cursorPos] = 9;
129       break;
130     case BTN_LEFT:
131       if (cursorPos > 0)
132         cursorPos--;
133       break;
134     case BTN_RIGHT:
135       if (cursorPos < 5)
136         cursorPos++;
137       break;
138     case BTN_OK:
139       checkPin();
140       return;
141     default:
142       break;
143   }
144
145   drawPin();
146 }
147
148 // ======================================================
149 // PIN prüfen
150 // ======================================================
151 void checkPin() {
152   bool correct = true;
153   for (int i = 0; i < 6; i++) {
154     if (pinDigits[i] != correctPin[i]) {
155       correct = false;
156       break;
157     }
158   }
159   // keine Digits mehr anzeigen
160   pinActive = false;
161   tft.fillScreen(ST77XX_BLACK);
162   tft.setCursor(50, 90);
163   if (correct) {
164     tft.setTextColor(ST77XX_GREEN);
165     tft.print("PIN OK");
166     // Hier kann später Aktion ausgelöst werden, z.B. HID senden
167   } else {
168     tft.setTextColor(ST77XX_RED);
169     tft.print("FALSCH");
170   }
171   delay(1500);
172   // Reset für neue Eingabe
173   pinActive = true;
174   cursorPos = 0;
175   for (int i = 0; i < 6; i++) pinDigits[i] = 0;
176   tft.fillScreen(ST77XX_BLACK);
177   tft.setCursor(50,30);
178   tft.setTextColor(ST77XX_YELLOW);
179   tft.print("Enter PIN");
180   drawPin();
181 }
182
183 // ======================================================
184 // Anzeige
185 // ======================================================
186 void drawPin() {
187   if (!pinActive) return;
188   int y = 90;
189   int digitWidth = 16;   // Breite eines Digits
190   int digitHeight = 20;  // Höhe des Digit-Bereichs
191   int spacing = 20;      // Abstand zwischen Digits
192   int xStart = 50;
193   for (int i = 0; i < 6; i++) {
194     int drawX = xStart + i * spacing;
195     if (i == cursorPos) {
196       // Aktives Digit: kompletten Bereich löschen
197       tft.fillRect(drawX, y - 18, digitWidth, digitHeight + 6, ST77XX_BLACK);
198       // Zahl zeichnen
199       tft.setTextColor(ST77XX_YELLOW);
200       tft.setCursor(drawX, y);
201       tft.print(pinDigits[i]);
202     } else {
203       // Abgeschlossenes Digit als Sternchen darstellen
204       tft.fillRect(drawX, y - 16, digitWidth, digitHeight, ST77XX_BLACK);
205       tft.setTextColor(ST77XX_WHITE);
206       tft.setCursor(drawX, y);
207       tft.print("*");
208     }
209   }
210 }
  

Bis zur Zeile 60 erfogen also die Initialisierung des Displays, die Definition der Button und des Pin-Systems. Zeile 65 bis 70 liest den im Speicher abgelegten Pin. Die Zeile 76 legt die Auflösung für die ADC-Werte auf 16-Bit fest. Dadurch liegen diese zwischen 0 - 65535, wodurch eine sehr gute Zuordnung zu den Tasten möglich ist, weil die Differenz für die Flankenerkennung groß ist. Die sich an die Loop-Schleife anschließende Funktion (Zeile 100 - 113) dient der Zuordnung der ADC-Werte zu den Button. Es folgt das Button Handling (Zeile 118 - 146); Digit erhöhen oder verringern bei 'up', 'down' bzw. der Position 1. bis 6. Digit. Danach stehen die Funktionen 'Pin prüfen' (Zeile 151 - 181) und 'Anzeige' (Zeile 186 - 210). Noch ein Hinweis: In Zeile 166 erfolgt der Verweis auf eine spätere Verzweigung zu einer entsprechenden 'Aktion'. Z.Z. erfolgt nur die Anzeige 'PIN OK' oder 'FALSCH'.

Diese s.g. 'Aktion' habe ich wie folgt realisiert. Wenn der Pin richtig eingegeben wurde, hat sich der Nutzer für das Ausführen von 'Shortcuts' autorisiert. So sind z.Z. die Tasten folgendermaßen belegt:

Taste 1: Explorer öffnen 'WINDOWS + e',
Taste 2: Desktop anzeigen 'WINDOWS + d',
Taste 3: Fenster schließen 'ALT + F4',
Taste 4: Screenshot 'WINDOWS + SHIFT + s',
Taste 5: PC sperren 'WINDOWS + l',

Sie können durch Änderung im Quellcode jederzeit andere Shortcuts festlegen. Den gesamten Quellcode finden Sie wieder auf meinem Github-Repository. Dort finden Sie auch noch einmal die beiden kurzen Sketche zum Lesen und Schreiben des EEPROMs.



Viel Spass und Erfolg mit dieser Anwendung.