Uniostar
Published © GPL3+

Arduino Touch Screen Calculator

A touch-screen calculator that works on the Arduino, great for beginners.

BeginnerFull instructions provided1 hour70
Arduino Touch Screen Calculator

Things used in this project

Story

Read more

Schematics

Top View

Side View

Code

Main Code

C/C++
Download and use it with arduino ide.
#include <MCUFRIEND_kbv.h>
#include <TouchScreen.h>

#define BLACK   0x0000
#define BLUE    0x001F
#define RED     0xF800
#define GREEN   0x07E0
#define CYAN    0x07FF
#define MAGENTA 0xF81F
#define YELLOW  0xFFE0
#define WHITE   0xFFFF

// Replace These Lines
const int XP = 8, XM = A2, YP = A3, YM = 9;
const int TS_LEFT = 115, TS_RT = 938, TS_TOP = 74, TS_BOT = 909;

MCUFRIEND_kbv tft;
TouchScreen ts = TouchScreen(XP, YP, XM, YM, 300);

int maxLength = 15;
int currentLength = 0;
char expression[24] = "\0";

// Button struct
struct Button 
{
  int x, y, w, h;        // button rectangle
  bool state;            // button state
  bool alreadyTouched;   // for toggle debounce
  const char* buttonLabel;   // label for button
  const char* displayLabel;  // label for character
};

// Define your buttons
#define NUM_BUTTONS 22
int size = 48;

Button buttons[NUM_BUTTONS] = 
{

  {  0, 224, size, size, false, false, "0", "0"},
  {  0,  80, size, size, false, false, "1", "1"},
  { 48,  80, size, size, false, false, "2", "2"},
  { 96,  80, size, size, false, false, "3", "3"},
  {  0, 128, size, size, false, false, "4", "4"},
  { 48, 128, size, size, false, false, "5", "5"},
  { 96, 128, size, size, false, false, "6", "6"},
  {  0, 176, size, size, false, false, "7", "7"},
  { 48, 176, size, size, false, false, "8","8"},
  { 96, 176, size, size, false, false, "9", "9"},

  {144, 128, size, size, false, false, "+", "+"},
  {192, 128, size, size, false, false, "-", "-"},
  {144, 176, size, size, false, false, "x", "x"},
  {192, 176, size, size, false, false, "/", "/"},
  { 48, 224, size, size, false, false, "^", "^"},
  { 96, 224, size, size, false, false, ".", "."},
  {144, 224, size, size, false, false, "(", "("},
  {192, 224, size, size, false, false, ")", ")"},
  {0, 272, size*2, size, false, false, "ANS", "A"},

  
  {96, 272, size*3, size, false, false, "=", "="},
  {144, 80, size, size, false, false, "AC", "AC"},
  {192, 80, size, size, false, false, "CE", "CE"},
};

// Define Stack
template <typename T, int SIZE>
class Stack 
{
  private:
    T data[SIZE];
    int topIndex = -1;

  public:
    bool push(T value) 
    {
      if (topIndex >= SIZE - 1) return false;
      data[++topIndex] = value;
      return true;
    }

    T pop() 
    {
      return data[topIndex--];
    }

    T peek() 
    {
      return data[topIndex];
    }

    bool isEmpty() 
    {
      return topIndex < 0;
    }

    bool isFull() 
    {
      return topIndex >= SIZE - 1;
    }
};

Stack<float, 64> numbers;
Stack<char, 64> operators;

// Text Display Region
int displayX = 0;
int displayY = 0;
int displayW = 240;
int displayH = 80;

float answer = 1.0f;

// 0 - Error, 1 - Divide By Zero, 2 - Check (), 3 - Invalid Use ANS, 4 - Invalid Format
int errorCode = 0;

int averagePressure(TouchScreen &ts, int samples);
int precedence(char op);
bool applyOp();
bool computeResult(char* expression);
void drawButton(const Button &b);
void updateButtons();
void drawText(const Button &b, int buttonNumber);

void setup() 
{
  Serial.begin(9600);
  tft.begin();
  tft.fillScreen(BLACK);
  tft.setRotation(0);

  for (int i = 0; i < NUM_BUTTONS; i++) 
  {
    drawButton(buttons[i]);
  }
}

void loop() 
{
  updateButtons(7);
  delay(33);
}

void updateButtons(int padding) 
{
  TSPoint p = ts.getPoint();
  int z = averagePressure(ts, 20);

  pinMode(XM, OUTPUT);
  pinMode(YP, OUTPUT);

  bool pressed = z > ts.pressureThreshhold;

  int x = -1, y = -1;

  if (pressed) 
  {
    x = map(p.x, TS_LEFT, TS_RT, 0, tft.width());
    y = map(p.y, TS_TOP, TS_BOT, 0, tft.height());
  }

  for (int i = 0; i < NUM_BUTTONS; i++) 
  {
    Button &b = buttons[i];
    bool isInside = pressed && x >= (b.x + padding) && x <= (b.x + b.w - padding) && (y >= b.y + padding) && y <= (b.y + b.h - padding);

    if (isInside && !b.alreadyTouched) 
    {
      b.state = !b.state;
      b.alreadyTouched = true;
      drawButton(b);
      drawText(b, i);
    } 
    else if (!isInside) 
    {
      b.alreadyTouched = false;
    }
  }
}

void drawText(const Button &b, int buttonNumber)
{
  if (buttonNumber >= 0 && buttonNumber <= 18)
  {
    if (currentLength < maxLength-1)
    {
      strcat(expression, b.displayLabel);
      currentLength++;
    }
  }

  // = : compute and display answer
  else if (buttonNumber == 19)
  {
    if (computeResult(expression)) 
    {
      dtostrf(answer, -6, 4, expression);
      currentLength = strlen(expression);
    }
    else 
    {
      if (errorCode == 1) strcpy(expression, "E1: DIVIDE BY 0");
      else if (errorCode == 2) strcpy(expression, "E2: CHECK ()");
      else if (errorCode == 3) strcpy(expression, "E3: Invalid ANS");
      else if (errorCode == 4) strcpy(expression, "E4: Invalid FORMAT");
      else strcpy(expression, "E0: UNKNOWN ERROR");

      currentLength = 1;
    }

  }

  // AC : clear everything
  else if (buttonNumber == 20)
  {
    expression[0] = '\0';
    currentLength = 0;
  }

  // CE : remove last character
  else if (buttonNumber == 21)
  {
    if (currentLength > 0)
    {
      expression[currentLength - 1] = '\0';
      currentLength--;
    }
  }

  //Display Text
  tft.fillRect(displayX, displayY, displayW, displayH, BLACK);

  tft.setTextSize(2);

  int16_t x1, y1;
  uint16_t w, h;
  tft.getTextBounds(expression, 0, 0, &x1, &y1, &w, &h);

  int cursorX = displayW - w - 10;
  if (cursorX < 5) cursorX = 5;

  int cursorY = displayY + (displayH - h) / 2;

  tft.setCursor(cursorX, cursorY);
  tft.print(expression);
  
  tft.setTextColor(WHITE);
}


void drawButton(const Button &b) 
{
  tft.fillRect(b.x, b.y, b.w, b.h, RED);
  tft.drawRect(b.x, b.y, b.w, b.h, BLACK);

  tft.setTextSize(2);
  tft.setTextColor(0xFFFF);

  const char* txt = b.buttonLabel;

  int16_t x1, y1;
  uint16_t w, h;
  tft.getTextBounds(txt, 0, 0, &x1, &y1, &w, &h);

  int textX = b.x + (b.w - w) / 2;
  int textY = b.y + (b.h - h) / 2;

  tft.setCursor(textX, textY);
  tft.print(txt);
}

int averagePressure(TouchScreen &ts, int samples) 
{
  long sum = 0;

  for (int i = 0; i < samples; i++) 
  {
    TSPoint p = ts.getPoint();
    sum += p.z;
    delay(1);
  }

  return (int) sum / samples;
}

bool computeResult(char* expression)
{
  while (!numbers.isEmpty()) numbers.pop();
  while (!operators.isEmpty()) operators.pop();

  int i = 0;
  char current;
  char numBuf[24] = "";
  int numIndex = 0;

  bool lastWasNum = false;
  bool lastWasANS = false;
  bool lastWasOp = false;
  
  Serial.println("Start");

  while ((current = expression[i]) != '\0')
  {
    // Check for ANS or A
    if (current == 'A')
    {
      // Return error
      if (lastWasNum) 
      {
        errorCode = 3;
        return false;
      }

      numbers.push(answer);
      lastWasANS = true;
      lastWasNum = false;
      lastWasOp = false;
    }

    // Check Number
    else if ((current >= '0' && current <= '9') || current == '.')
    {
      //Return error
      if (lastWasANS) 
      {
        errorCode = 3;
        return false;
      }

      numBuf[numIndex++] = current;
      lastWasNum = true;
      lastWasANS = false;
      lastWasOp = false;
    }

    // Check (
    else if (current == '(')
    {
      operators.push('(');
      lastWasNum = false;
      lastWasANS = false;
      lastWasOp = true;
    }

    // Check )
    else if (current == ')')
    {
      // Push any buffered number
      if (lastWasNum)
      {
        numBuf[numIndex] = '\0';
        numbers.push(atof(numBuf));
        Serial.println(numbers.peek());
        numBuf[0] = '\0';
        numIndex = 0;
        lastWasNum = false;
      }

      // Apply operations until '('
      while (!operators.isEmpty() && operators.peek() != '(')
      {
        if (!applyOp()) 
        {
          return false;
        }
      }

      if (operators.isEmpty()) 
      {
        errorCode = 2;
        return false;
      }

      operators.pop(); // remove '('
      
      lastWasNum = false;
      lastWasANS = false;
      lastWasOp = true;
    }

    // Operator
    else
    {
      char op = current;

      if (op == '-' && (i == 0 || lastWasOp || expression[i-1] == '('))
      {
        op = '~';
        
        if (lastWasNum)
        {
          numBuf[numIndex] = '\0';
          numbers.push(atof(numBuf));
          numBuf[0] = '\0';
          numIndex = 0;
        }

        operators.push('~');

        lastWasOp = true;
        lastWasNum = false;
        lastWasANS = false;

        i++; 
        continue;
      }

      
      if (lastWasNum)
      {
        numBuf[numIndex] = '\0';
        numbers.push(atof(numBuf));
        Serial.println(numbers.peek());
        numBuf[0] = '\0';
        numIndex = 0;
        lastWasNum = false;
      }

      while (!operators.isEmpty() && precedence(operators.peek()) >= precedence(op))
      {
        if (!applyOp()) 
        {
          return false;
        }
      }

      operators.push(op);

      lastWasNum = false;
      lastWasANS = false;
      lastWasOp = true;
    }

    i++;
  }

  Serial.println("End");

  // Push Final Number
  if (lastWasNum)
  {
    numBuf[numIndex] = '\0';
    numbers.push(atof(numBuf)); 
  }

  // Apply Any Operators Left
  while (!operators.isEmpty())
  {
    if (!applyOp()) 
    {
      return false;
    }
  }

  if (numbers.isEmpty()) 
  {
    errorCode = 0;
    return false;
  }

  answer = numbers.pop();

  return true;
}

int precedence(char op)
{
  if (op == '~') return 4;           
  if (op == '^') return 3;
  if (op == 'x' || op == '/') return 2;
  if (op == '+' || op == '-') return 1;
  return 0;
}

bool applyOp()
{
  if (operators.isEmpty()) return false;

  char op = operators.pop();

  if (op == '~')
  {
    if (numbers.isEmpty()) return false;
    float a = numbers.pop();
    numbers.push(-a);
    return true;
  }

  if (numbers.isEmpty()) return false;
  float b = numbers.pop();

  if (numbers.isEmpty()) return false;
  float a = numbers.pop();

  float r = 0.0;

  switch (op)
  {
    case '+': r = a + b; break;
    case '-': r = a - b; break;
    case 'x': r = a * b; break;
    case '/': 
      if (b == 0) 
      {
        errorCode = 1; 
        return false;
      }
      r = a / b; 
      break;
    case '^': r = pow(a, b); break;
    default: 
      errorCode = 4;
      return false;
  }

  numbers.push(r);
  return true;
}

Credits

Uniostar
11 projects • 8 followers
Electrical Engineering Undergrad Student specialized in low-level programming, IoT projects, and microcontroller electronics.

Comments