/*
* PicoCalc-Inspired Calculator for Wokwi
* Raspberry Pi Pico + ILI9341 Display + 4x4 Keypad
* Arduino C++ with Adafruit_ILI9341 library
*/
#include "SPI.h"
#include "Adafruit_GFX.h"
#include "Adafruit_ILI9341.h"
// Display pins
#define TFT_CS 17
#define TFT_DC 21
#define TFT_RST 20
#define TFT_MOSI 19
#define TFT_SCK 18
// Keypad pins
#define ROW1 2
#define ROW2 3
#define ROW3 4
#define ROW4 5
#define COL1 6
#define COL2 7
#define COL3 8
#define COL4 9
// Display object
Adafruit_ILI9341 tft = Adafruit_ILI9341(TFT_CS, TFT_DC, TFT_RST);
// Keypad configuration
const byte ROWS = 4;
const byte COLS = 4;
byte rowPins[ROWS] = {ROW1, ROW2, ROW3, ROW4};
byte colPins[COLS] = {COL1, COL2, COL3, COL4};
// Key mapping (Wokwi membrane keypad layout)
char keys[ROWS][COLS] = {
{'1', '2', '3', '/'}, // A mapped to /
{'4', '5', '6', '*'}, // B mapped to *
{'7', '8', '9', '-'}, // C mapped to -
{'C', '0', '=', '+'} // * mapped to C, # mapped to =, D mapped to +
};
// Calculator state
String displayValue = "0";
float firstOperand = 0;
char operation = '\0';
bool clearDisplay = false;
// Colors
#define COLOR_BG 0x2104 // Dark blue-gray
#define COLOR_DISPLAY 0xFFFF // White
#define COLOR_TEXT 0x0000 // Black
#define COLOR_BTN1 0x4A49 // Gray
#define COLOR_BTN2 0x39C7 // Light gray
#define COLOR_OPERATION 0xFD20 // Orange
#define COLOR_EQUALS 0x07E0 // Green
#define COLOR_CLEAR 0xF800 // Red
void setup() {
Serial.begin(115200);
Serial.println("\n===========================================");
Serial.println(" PicoCalc Calculator Simulator");
Serial.println("===========================================\n");
// Initialize display
Serial.println("Initializing ILI9341 display...");
tft.begin();
tft.setRotation(0); // Portrait mode
Serial.println("Display initialized!");
// Initialize keypad pins
Serial.println("Initializing keypad...");
for (byte i = 0; i < ROWS; i++) {
pinMode(rowPins[i], OUTPUT);
digitalWrite(rowPins[i], LOW);
}
for (byte i = 0; i < COLS; i++) {
pinMode(colPins[i], INPUT_PULLDOWN);
}
Serial.println("Keypad initialized!");
// Draw calculator interface
Serial.println("Drawing interface...");
drawInterface();
updateDisplay();
Serial.println("\n===========================================");
Serial.println("Calculator Ready!");
Serial.println("===========================================");
Serial.println("\nKeypad Layout:");
Serial.println(" 1 2 3 / (A = divide)");
Serial.println(" 4 5 6 * (B = multiply)");
Serial.println(" 7 8 9 - (C = minus)");
Serial.println(" C 0 = + (* = clear, # = equals, D = plus)");
Serial.println("===========================================\n");
}
void loop() {
char key = scanKeypad();
if (key != '\0') {
Serial.print("Key pressed: ");
Serial.println(key);
processKey(key);
updateDisplay();
Serial.print("Display: ");
Serial.println(displayValue);
delay(200); // Debounce
}
delay(50);
}
// Scan keypad for key press
char scanKeypad() {
for (byte r = 0; r < ROWS; r++) {
digitalWrite(rowPins[r], HIGH);
delayMicroseconds(10);
for (byte c = 0; c < COLS; c++) {
if (digitalRead(colPins[c]) == HIGH) {
digitalWrite(rowPins[r], LOW);
return keys[r][c];
}
}
digitalWrite(rowPins[r], LOW);
}
return '\0';
}
// Process key press
void processKey(char key) {
if (key == 'C') {
// Clear
displayValue = "0";
firstOperand = 0;
operation = '\0';
clearDisplay = false;
}
else if (key >= '0' && key <= '9') {
// Number key
if (clearDisplay || displayValue == "0") {
displayValue = String(key);
clearDisplay = false;
} else {
if (displayValue.length() < 12) {
displayValue += key;
}
}
}
else if (key == '/' || key == '*' || key == '-' || key == '+') {
// Operation key
firstOperand = displayValue.toFloat();
operation = key;
clearDisplay = true;
}
else if (key == '=') {
// Equals
if (operation != '\0') {
float secondOperand = displayValue.toFloat();
float result = 0;
switch (operation) {
case '+':
result = firstOperand + secondOperand;
break;
case '-':
result = firstOperand - secondOperand;
break;
case '*':
result = firstOperand * secondOperand;
break;
case '/':
if (secondOperand != 0) {
result = firstOperand / secondOperand;
} else {
displayValue = "Error";
operation = '\0';
return;
}
break;
}
// Format result
if (result == (int)result) {
displayValue = String((int)result);
} else {
displayValue = String(result, 4);
}
// Truncate if too long
if (displayValue.length() > 12) {
displayValue = displayValue.substring(0, 12);
}
operation = '\0';
firstOperand = 0;
clearDisplay = true;
}
}
}
// Draw calculator interface
void drawInterface() {
// Background
tft.fillScreen(COLOR_BG);
// Display area
tft.fillRoundRect(10, 10, 220, 60, 8, COLOR_DISPLAY);
tft.drawRoundRect(10, 10, 220, 60, 8, COLOR_TEXT);
// Title
tft.setTextColor(0x07FF); // Cyan
tft.setTextSize(1);
tft.setCursor(15, 75);
tft.print("PicoCalc");
// Button grid
const char* labels[16] = {
"7", "8", "9", "/",
"4", "5", "6", "*",
"1", "2", "3", "-",
"C", "0", "=", "+"
};
for (byte row = 0; row < 4; row++) {
for (byte col = 0; col < 4; col++) {
int x = 10 + col * 55;
int y = 95 + row * 55;
// Choose button color
uint16_t btnColor = COLOR_BTN1;
if (col == 3) {
btnColor = COLOR_OPERATION; // Operations
} else if (row == 3 && col == 0) {
btnColor = COLOR_CLEAR; // Clear
} else if (row == 3 && col == 2) {
btnColor = COLOR_EQUALS; // Equals
} else {
btnColor = (row + col) % 2 ? COLOR_BTN1 : COLOR_BTN2;
}
// Draw button
tft.fillRoundRect(x, y, 50, 50, 5, btnColor);
tft.drawRoundRect(x, y, 50, 50, 5, COLOR_TEXT);
// Draw label
tft.setTextColor(COLOR_DISPLAY);
tft.setTextSize(3);
int labelIndex = row * 4 + col;
const char* label = labels[labelIndex];
// Center text
int textX = x + 17;
int textY = y + 15;
if (strcmp(label, "C") == 0 || strcmp(label, "=") == 0) {
textX = x + 15;
}
tft.setCursor(textX, textY);
tft.print(label);
}
}
}
// Update display value
void updateDisplay() {
// Clear display area
tft.fillRoundRect(12, 12, 216, 56, 6, COLOR_DISPLAY);
// Draw value (right-aligned)
tft.setTextColor(COLOR_TEXT);
tft.setTextSize(3);
// Calculate text width (approximate: 18 pixels per character at size 3)
int textWidth = displayValue.length() * 18;
int textX = 220 - textWidth;
// Ensure text doesn't go off screen
if (textX < 15) {
textX = 15;
}
tft.setCursor(textX, 30);
tft.print(displayValue);
// Show operation indicator if active
if (operation != '\0') {
tft.setTextSize(2);
tft.setTextColor(COLOR_OPERATION);
tft.setCursor(15, 15);
tft.print(operation);
}
}