Hi ich habe einen Tascschenrechner mit vielen Features auf einen Arduino Uno gequetscht.
Features des Codes
1. Touchscreen-Bedienung
- 5x5 Tastenfeld auf 3,5" TFT mit MCUFriend
(Zahlen, Operatoren, Funktionen, Klammern, Löschen usw.) - Große Tastenflächen und klare Labels für einfaches Tippen
- Jede Taste reagiert direkt auf Berührung
2. Serieller Modus (USB/Serial Monitor)
- Rechnen per Serial Monitor (z.B. Arduino IDE → Werkzeuge → Serieller Monitor)
- Komplette Rechnungen eingeben (
12+34/2,sqrt(16)+2,1/0) - Auch Operatoren-Fortsetzung:
Wenn ein Ergebnis im Display steht, reicht z.B./2, um mit diesem Wert weiterzurechnen
(Anzeige: 20, Serial:/2→ Anzeige:10)
3. Autokorrektur von Eingaben
- Automatische Korrektur typischer Syntaxfehler und unvollständiger Eingaben:
- Komma wird zu Punkt (
1,5→1.5) - Doppelte Operatoren werden entfernt (
1++2→1+2) - Operatoren direkt nach
(werden entfernt ((+)5→(5)) - Leere Klammern entfernt (
()→ entfernt) - Zahlen direkt vor einer Klammer (
2(3+4)) → Multiplikationszeichen eingefügt (2*(3+4)) - Klammer gefolgt von Zahl (
(2)3→(2)*3) - Fehlende schließende Klammern werden ergänzt
- Ausdruck ohne Zahl (
++--((()))) wird zu"0" - Leere Eingabe wird zu
"0" - End-Operatoren werden entfernt (
2+3+→2+3) - Doppelte Plus/Minus am Anfang werden reduziert (
--4→4)
- Komma wird zu Punkt (
- Ergebnis:
Sehr viel weniger „ERR“ – Eingaben werden wie bei modernen Rechnern interpretiert
4. Mathematik-Parser
- Eigener Parser für Ausdrücke mit Klammern, Punkt-vor-Strich, mehrere Operatoren
- Operatoren:
+,-,*,/,% - Klammern beliebig verschachtelt
- Funktionen:
sqrt(…)(Wurzel)sq(…)(Quadrat)inv(…)(Kehrwert, 1/x)
- Direkte Quadrat-/Wurzel-Taste am Touchscreen (
x^2,sqrt,1/x)
5. Komfortables Fehlerhandling
- Division durch Null, zu große/kleine Ergebnisse, Stacküberlauf → Anzeige „ERR“
- Nach „ERR“ kann weitergerechnet werden (Touch oder Serial, keine Blockade mehr)
- **„C“ löscht alles und setzt auf „0“ zurück
- Watchdog-Timer (2s):
Sollte dennoch ein Fehler im Code (Endlosschleife, Stacküberlauf) passieren, startet das System automatisch neu
6. Ergebnisformatierung
- Ganze Zahlen werden als Integer angezeigt (ohne
.0) - Kommazahlen werden ohne unnötige Nullen ausgegeben
- Sehr große Zahlen (Überlauf) führen zu „ERR“
7. Synchronisierte Anzeige
- Egal ob über Touch oder Serial gerechnet wird:
Das Ergebnis ist immer sofort auf dem Display und im Serial Monitor sichtbar
8. Zusätzliche Komfortfunktionen
- Löschen einzelner Zeichen („<-“)
- Vorzeichen umschalten („+/-“)
- Prozent-Umrechnung
- Automatisches Weiterrechnen nach Touch oder Serial
9. Speichersicher, stabil und absturzfest
- Alle Array-Kopien und -Längen werden korrekt geprüft (Buffer Overflow-Schutz)
- Eingabegrößen (Touch und Serial) sind begrenzt (
INPUT_MAX) - Alle Stackoperationen geprüft:
Tiefe Klammerausdrücke führen zu „ERR“, nicht zum Absturz
10. Benutzerfreundlichkeit
- Fehlerkorrektur statt Fehlermeldung
- Keine Angst mehr vor Syntaxfehlern
- Funktioniert wie ein smarter Taschenrechner am Smartphone
Funktioniert mit Touchschreen oder Seriel (115200 baud)
#include <MCUFRIEND_kbv.h>
#include <Adafruit_GFX.h>
#include <TouchScreen.h>
#include <avr/wdt.h>
#define MINPRESSURE 200
#define MAXPRESSURE 1000
const int XP = 8, XM = A2, YP = A3, YM = 9;
const int TS_LEFT = 125, TS_RT = 920, TS_TOP = 948, TS_BOT = 97;
TouchScreen ts = TouchScreen(XP, YP, XM, YM, 300);
MCUFRIEND_kbv tft;
#define INPUT_MAX 40
const char* button_labels[5][5] = {
{"7", "8", "9", "/", "C"},
{"4", "5", "6", "*", "<-"},
{"1", "2", "3", "-", "("},
{"0", ".", "=", "+", ")"},
{"sqrt", "x^2", "1/x", "%", "+/-"}
};
#define ROWS 5
#define COLS 5
int pixel_x, pixel_y;
#define STACK_MAX 12
float numStack[STACK_MAX];
char opStack[STACK_MAX];
int ns = 0, os = 0;
char input[INPUT_MAX+1] = "0";
bool errorFlag = false;
void serialPrintInput() {
Serial.print("Display: ");
Serial.println(input);
}
// --- Erweiterte Autokorrektur ---
void autoCorrect(char* in) {
// 1. Kommas in Punkte umwandeln
for (int i = 0; in[i]; i++) if (in[i] == ',') in[i] = '.';
// 2. Doppelte Operatoren ersetzen
int len = strlen(in), j = 0;
char tmp[INPUT_MAX + 1];
for (int i = 0; i < len && j < INPUT_MAX; i++) {
if (i > 0 && strchr("+-*/%", in[i]) && strchr("+-*/%", in[i-1]))
continue;
tmp[j++] = in[i];
}
tmp[j] = 0;
strncpy(in, tmp, INPUT_MAX); in[INPUT_MAX] = 0;
// 3. Entferne Operator direkt nach '('
len = strlen(in); j = 0;
for (int i = 0; i < len && j < INPUT_MAX; i++) {
if (in[i] == '(' && strchr("+-*/%.", in[i+1])) continue;
tmp[j++] = in[i];
}
tmp[j] = 0;
strncpy(in, tmp, INPUT_MAX); in[INPUT_MAX] = 0;
// 4. Entferne leere Klammern "()"
len = strlen(in); j = 0;
for (int i = 0; i < len && j < INPUT_MAX; i++) {
if (in[i] == '(' && in[i+1] == ')') { i++; continue; }
tmp[j++] = in[i];
}
tmp[j] = 0;
strncpy(in, tmp, INPUT_MAX); in[INPUT_MAX] = 0;
// 5. Zahl vor '(' → '*' ergänzen
len = strlen(in); j = 0;
for (int i = 0; i < len && j < INPUT_MAX; i++) {
tmp[j++] = in[i];
if (isdigit(in[i]) && in[i+1] == '(' && j < INPUT_MAX) tmp[j++] = '*';
}
tmp[j] = 0;
strncpy(in, tmp, INPUT_MAX); in[INPUT_MAX] = 0;
// 6. ')' gefolgt von Zahl → '*' ergänzen
len = strlen(in); j = 0;
for (int i = 0; i < len && j < INPUT_MAX; i++) {
tmp[j++] = in[i];
if (in[i] == ')' && isdigit(in[i+1]) && j < INPUT_MAX) tmp[j++] = '*';
}
tmp[j] = 0;
strncpy(in, tmp, INPUT_MAX); in[INPUT_MAX] = 0;
// 7. Fehlende schließende Klammern ergänzen
int bal = 0;
for (int i = 0; in[i]; i++) {
if (in[i] == '(') bal++;
if (in[i] == ')') bal--;
}
while (bal > 0 && strlen(in) < INPUT_MAX - 1) {
strcat(in, ")");
bal--;
}
in[INPUT_MAX] = 0;
// 8. Ausdruck nur Operatoren/Klammern → "0"
bool hasDigit = false;
for (int i = 0; in[i]; i++) if (isdigit(in[i])) { hasDigit = true; break; }
if (!hasDigit) { strcpy(in, "0"); }
// 9. Leere Eingabe als "0"
if (strlen(in) == 0) strcpy(in, "0");
// 10. Wenn mit Operator (außer '-') begonnen wird, 0 davorstellen
if (strchr("+*/%", in[0])) {
if (strlen(in) < INPUT_MAX - 1) {
char tmp2[INPUT_MAX + 1];
snprintf(tmp2, sizeof(tmp2), "0%s", in);
strncpy(in, tmp2, INPUT_MAX); in[INPUT_MAX] = 0;
}
}
// 11. Letztes Zeichen: Operator/Punkt/Klammer entfernen
int n = strlen(in);
while (n > 0 && strchr("+-*/%.(", in[n-1])) {
in[--n] = 0;
}
// 12. Doppelte Plus/Minus am Anfang reduzieren
while ((in[0] == '+' || in[0] == '-') && (in[1] == '+' || in[1] == '-')) {
memmove(in, in+1, strlen(in));
in[INPUT_MAX] = 0;
}
}
// --- Sicherheitsprüfung ---
bool isSafeInput(const char* in) {
int len = strlen(in);
if (len == 0) return false;
if (strchr("+-*/%", in[len-1])) return false;
if (strchr("+*/%)", in[0])) return false;
for (int i = 1; i < len; i++) {
if (strchr("+-*/%", in[i]) && strchr("+-*/%", in[i-1])) return false;
}
int bal = 0;
for (int i = 0; i < len; i++) {
if (in[i] == '(') bal++;
if (in[i] == ')') bal--;
if (bal < 0) return false;
if (in[i] == '(' && in[i+1] == ')') return false;
}
if (bal != 0) return false;
for (int i = 1; i < len; i++) {
if (in[i] == ')' && strchr("+-*/%(", in[i-1])) return false;
}
for (int i = 1; i < len; i++) {
if (in[i] == '.' && (strchr("+-*/%()", in[i-1]) || in[i-1] == 0)) return false;
}
return true;
}
int precedence(char op) {
if (op == '+' || op == '-') return 1;
if (op == '*' || op == '/' || op == '%') return 2;
return 0;
}
bool applyOp() {
if (ns < 2) return false;
float b = numStack[--ns], a = numStack[--ns];
char op = opStack[--os];
switch(op) {
case '+': numStack[ns++] = a + b; break;
case '-': numStack[ns++] = a - b; break;
case '*': numStack[ns++] = a * b; break;
case '/': if (b == 0) return false; numStack[ns++] = a / b; break;
case '%': if (b == 0) return false; numStack[ns++] = fmod(a, b); break;
}
return true;
}
float evalExpr(const char* expr, bool& error) {
ns = os = 0;
int i = 0;
while (expr[i]) {
if (expr[i] == ' ') { i++; continue; }
if (!strncmp(&expr[i], "sqrt(", 5)) {
i += 5;
int start = i, par = 1;
while (expr[i] && par > 0) {
if (expr[i] == '(') par++;
if (expr[i] == ')') par--;
i++;
}
int tocopy = min(i-start-1, (int)INPUT_MAX);
char inner[INPUT_MAX+1]; strncpy(inner, &expr[start], tocopy); inner[tocopy] = 0;
bool err = false;
float x = evalExpr(inner, err);
if (err || x < 0) { error = true; return 0; }
if (ns >= STACK_MAX) { error = true; return 0; }
numStack[ns++] = sqrt(x);
continue;
}
if (!strncmp(&expr[i], "sq(", 3)) {
i += 3;
int start = i, par = 1;
while (expr[i] && par > 0) {
if (expr[i] == '(') par++;
if (expr[i] == ')') par--;
i++;
}
int tocopy = min(i-start-1, (int)INPUT_MAX);
char inner[INPUT_MAX+1]; strncpy(inner, &expr[start], tocopy); inner[tocopy] = 0;
bool err = false;
float x = evalExpr(inner, err);
if (err) { error = true; return 0; }
if (ns >= STACK_MAX) { error = true; return 0; }
numStack[ns++] = x * x;
continue;
}
if (!strncmp(&expr[i], "inv(", 4)) {
i += 4;
int start = i, par = 1;
while (expr[i] && par > 0) {
if (expr[i] == '(') par++;
if (expr[i] == ')') par--;
i++;
}
int tocopy = min(i-start-1, (int)INPUT_MAX);
char inner[INPUT_MAX+1]; strncpy(inner, &expr[start], tocopy); inner[tocopy] = 0;
bool err = false;
float x = evalExpr(inner, err);
if (err || x == 0) { error = true; return 0; }
if (ns >= STACK_MAX) { error = true; return 0; }
numStack[ns++] = 1.0 / x;
continue;
}
if (isdigit(expr[i]) || expr[i] == '.' || (expr[i] == '-' && (i == 0 || expr[i-1] == '('))) {
char buf[16]; int bi=0;
if (expr[i] == '-') buf[bi++] = expr[i++];
while (isdigit(expr[i]) || expr[i] == '.') buf[bi++] = expr[i++];
buf[bi]=0;
if (ns >= STACK_MAX) { error = true; return 0; }
numStack[ns++] = atof(buf);
}
else if (expr[i] == '(') { opStack[os++] = '('; i++; }
else if (expr[i] == ')') {
while (os > 0 && opStack[os-1] != '(') {
if (!applyOp()) { error = true; return 0; }
}
if (os == 0) { error = true; return 0; }
os--; i++;
}
else if (strchr("+-*/%", expr[i])) {
char currOp = expr[i];
while (os > 0 && precedence(opStack[os-1]) >= precedence(currOp)) {
if (!applyOp()) { error = true; return 0; }
}
opStack[os++] = currOp;
i++;
}
else { error = true; return 0; }
}
while (os > 0) {
if (!applyOp()) { error = true; return 0; }
}
if (ns != 1) { error = true; return 0; }
return numStack[0];
}
#define BTN_W 62
#define BTN_H 56
#define BTN_SX 3
#define BTN_SY 8
#define BTN_X0 5
#define BTN_Y0 140
void drawButtons() {
tft.setTextSize(2);
for (int row = 0; row < ROWS; row++) {
for (int col = 0; col < COLS; col++) {
int x = BTN_X0 + col * (BTN_W + BTN_SX);
int y = BTN_Y0 + row * (BTN_H + BTN_SY);
tft.fillRoundRect(x, y, BTN_W, BTN_H, 14, 0xC618);
tft.drawRoundRect(x, y, BTN_W, BTN_H, 14, TFT_BLACK);
int labelLen = strlen(button_labels[row][col]);
int fontW = 12;
int fontH = 16;
int xOffset = BTN_W/2 - (labelLen*fontW)/2;
int yOffset = BTN_H/2 - fontH/2;
tft.setTextColor(TFT_BLACK);
tft.setCursor(x + xOffset, y + yOffset);
tft.print(button_labels[row][col]);
}
}
tft.setTextSize(2);
}
void handleTouch(int px, int py) {
for (int row = 0; row < ROWS; row++)
for (int col = 0; col < COLS; col++) {
int x = BTN_X0 + col * (BTN_W + BTN_SX);
int y = BTN_Y0 + row * (BTN_H + BTN_SY);
if (px >= x && px < x + BTN_W && py >= y && py < y + BTN_H) {
handleButton(button_labels[row][col]);
serialPrintInput();
}
}
}
void drawScreen() {
tft.fillScreen(TFT_WHITE);
tft.fillRoundRect(10, 40, 300, 65, 8, 0xFFFF);
tft.drawRoundRect(10, 40, 300, 65, 8, 0x0000);
drawButtons();
}
void showInput() {
tft.fillRoundRect(15, 50, 280, 55, 6, 0xFFFF);
tft.setTextColor(TFT_BLACK);
tft.setTextSize(3);
tft.setCursor(22, 67);
int len = strlen(input);
if (len > 12) tft.print("..." + String(&input[len-12]));
else tft.print(input);
serialPrintInput();
}
void resetCalc() {
strcpy(input, "0");
errorFlag = false;
serialPrintInput();
}
void setup() {
Serial.begin(115200);
wdt_enable(WDTO_2S);
uint16_t ID = tft.readID();
tft.begin(ID);
tft.setRotation(0);
drawScreen();
resetCalc();
showInput();
Serial.println("Touch-Taschenrechner bereit! Eingabe per Touch ODER Serial Monitor.");
Serial.println("Beispiele:");
Serial.println("1+2*3 -> 7");
Serial.println("sqrt(9) -> 3");
Serial.println("inv(2) -> 0.5");
Serial.println("sq(5) -> 25");
Serial.println("C -> Reset");
}
// --------- BUTTON HANDLER -----------
void handleButton(const char* btn) {
int len = strlen(input);
if (errorFlag && strcmp(btn,"C") && !(isdigit(btn[0]) && strlen(btn)==1)) return;
if (!strcmp(btn,"C")) {
resetCalc();
}
else if (!strcmp(btn,"<-")) {
if (len > 1) { input[len-1]=0; }
else strcpy(input, "0");
}
else if (!strcmp(btn,"=")) {
autoCorrect(input);
if (!isSafeInput(input)) {
strcpy(input, "ERR");
errorFlag = true;
Serial.println("ERR");
return;
}
bool error = false;
float res = evalExpr(input, error);
if (error || isinf(res) || isnan(res) || fabs(res) > 999999999) {
strcpy(input, "ERR");
errorFlag = true;
Serial.println("ERR");
return;
}
if (fabs(res - (long)res) < 1e-6) sprintf(input, "%ld", (long)res);
else {
dtostrf(res, 1, 6, input);
char* p = input+strlen(input)-1;
while (*p == '0' && p > input && *(p-1) != '.') *p-- = 0;
if (*p == '.') *p = 0;
}
Serial.println(input);
}
else if (!strcmp(btn,"+/-")) {
if (strcmp(input, "0") && strcmp(input, "ERR")) {
if (input[0]=='-') memmove(input, input+1, strlen(input));
else if (len < INPUT_MAX) { memmove(input+1, input, len+1); input[0]='-'; }
}
}
else if (!strcmp(btn,"sqrt")) {
float val = atof(input);
if (val<0) { strcpy(input, "ERR"); errorFlag=true; Serial.println("ERR"); return; }
val = sqrt(val);
if (fabs(val - (long)val) < 1e-6) sprintf(input, "%ld", (long)val);
else dtostrf(val, 1, 6, input);
Serial.println(input);
}
else if (!strcmp(btn,"x^2")) {
float val = atof(input); val *= val;
if (fabs(val - (long)val) < 1e-6) sprintf(input, "%ld", (long)val);
else dtostrf(val, 1, 6, input);
Serial.println(input);
}
else if (!strcmp(btn,"1/x")) {
float val = atof(input);
if (val==0) { strcpy(input, "ERR"); errorFlag=true; Serial.println("ERR"); return; }
val = 1.0/val;
if (fabs(val - (long)val) < 1e-6) sprintf(input, "%ld", (long)val);
else dtostrf(val, 1, 6, input);
Serial.println(input);
}
else if (!strcmp(btn,"%")) {
float val = atof(input);
val = val / 100.0;
if (fabs(val - (long)val) < 1e-6) sprintf(input, "%ld", (long)val);
else dtostrf(val, 1, 6, input);
Serial.println(input);
}
else if (!strcmp(btn,".") && !strchr(input, '.') && len < INPUT_MAX-1) {
input[len] = '.'; input[len+1]=0;
}
else if (strlen(btn)==1 && isdigit(btn[0])) {
if (!strcmp(input,"0") || !strcmp(input,"ERR")) { input[0]=btn[0]; input[1]=0; }
else if (len < INPUT_MAX-1) { input[len]=btn[0]; input[len+1]=0; }
}
else if (strchr("+-*/()", btn[0]) && len < INPUT_MAX-1) {
char last = (len>0) ? input[len-1] : 0;
if ((strchr("+-*/%", last) && btn[0] != '(' && btn[0] != ')') ||
(len == 0 && btn[0]!='(' && btn[0]!='-') ||
(last == '(' && strchr("*/%)", btn[0]))) {
return;
}
input[len]=btn[0]; input[len+1]=0;
}
}
bool Touch_getXY(void)
{
TSPoint p = ts.getPoint();
pinMode(YP, OUTPUT);
pinMode(XM, OUTPUT);
digitalWrite(YP, HIGH);
digitalWrite(XM, HIGH);
bool pressed = (p.z > MINPRESSURE && p.z < MAXPRESSURE);
if (pressed) {
pixel_x = map(p.x, TS_LEFT, TS_RT, 0, tft.width());
pixel_y = map(p.y, TS_TOP, TS_BOT, 0, tft.height());
}
return pressed;
}
// --- SERIELLER INTERPRETER ---
void interpretSerial(char* buf) {
int l = strlen(buf);
while (l > 0 && (buf[l-1]=='\n'||buf[l-1]=='\r')) buf[--l]=0;
if (l==0) return;
// Fehlerflag nach ERR zurücksetzen, falls kein C
if ((errorFlag || strcmp(input, "ERR") == 0) && strcmp(buf, "C") && strcmp(buf, "c")) {
errorFlag = false;
if (strchr("+-*/%", buf[0])) strcpy(input, "0");
}
if (!strcmp(buf, "C") || !strcmp(buf, "c")) { resetCalc(); showInput(); return; }
if (!strcmp(buf, "exit")) { asm volatile ("jmp 0"); return; }
// Operator am Anfang → aktuellen Wert voranstellen
if (strchr("+-*/%", buf[0]) && !errorFlag && strcmp(input, "ERR") != 0 && strlen(input) > 0) {
char combined[INPUT_MAX*2+2];
snprintf(combined, sizeof(combined), "%s%s", input, buf);
strncpy(buf, combined, INPUT_MAX);
buf[INPUT_MAX] = 0;
}
autoCorrect(buf);
if (!isSafeInput(buf)) {
strcpy(input, "ERR"); errorFlag = true;
showInput();
Serial.println("ERR");
return;
}
bool error = false;
float res = evalExpr(buf, error);
if (error || isinf(res) || isnan(res) || fabs(res) > 999999999) {
strcpy(input, "ERR"); errorFlag = true;
showInput();
Serial.println("ERR");
return;
}
if (fabs(res - (long)res) < 1e-6)
sprintf(input, "%ld", (long)res);
else {
dtostrf(res, 1, 6, input);
char* p = input+strlen(input)-1;
while (*p == '0' && p > input && *(p-1) != '.') *p-- = 0;
if (*p == '.') *p = 0;
}
showInput();
Serial.println(input);
}
void loop() {
wdt_reset();
static bool prevTouch = false;
bool currTouch = Touch_getXY();
if (prevTouch && !currTouch) {
handleTouch(pixel_x, pixel_y);
showInput();
}
prevTouch = currTouch;
static char serBuf[INPUT_MAX + 1];
static uint8_t serIdx = 0;
while (Serial.available()) {
char c = Serial.read();
if (c == '\r' || c == '\n') {
serBuf[serIdx] = 0;
if (serIdx > 0) {
interpretSerial(serBuf);
serIdx = 0;
}
} else if (serIdx < INPUT_MAX && (isprint(c) || c == '\t')) {
serBuf[serIdx++] = c;
}
}
}
Viel spass damit :)