Serial print getting cut off [SOLVED]

Here is a calculator project that I have been working on for some time now:

When the equals sign is pressed, I expected to have the answer printed at the bottom of the screen along with original input kept at the top of the screen. The general workflow is:

  1. An array of characters is created based on the user's inputs
  2. When the equals sign is pressed, the evaluate() function is called.
  3. The char array is converted to an array of "Tokens" that specifies the type of the token (number or operator) and its value (a float or a character for operators) with tokenize().
  4. The infix Token array is converted to RPN with shuntingYard().
  5. The RPN expression is evaluated and returned.

Instead of printing the answer to the LCD, nothing gets printed and the display is cleared. With a few Serial.println commands, I found that the problem is probably with the button press detection. When it's detected that the equals sign is pressed, it's supposed to print "equals pressed" in the Serial monitor, but instead just the letter 'e' is printed. Why is this?

Read the pinned post re 'How to get the most from the forum'. There you will learn how to post your code and a wiring diagram.

Put Serial.flush(); after each Serial.print(), otherwise you will not see the last thing printed before the problem occurs.

Looks to me like the error occurs when tokenize() is called inside the evaluate() function. I'm thinking you are using too much ram for an UNO, have you tried simulating on a processor with more memory?

It's a low memory issue.

If you replace the UNO with a Mega, the code works.

Look

@mingminghu 's code:

#include <math.h>
#include <Wire.h>
#include <hd44780.h>
#include <hd44780ioClass/hd44780_I2Cexp.h>

hd44780_I2Cexp lcd;

//--------------------------------------------------DATA--------------------------------------------------

const int ROW_COUNT = 4;
const int COL_COUNT = 5;
int rowPins[ROW_COUNT] = {2, 3, 4, 5};
int colPins[COL_COUNT] = {8, 9, 10, 11, 12};
bool buttonStates[ROW_COUNT][COL_COUNT] = {false};
bool prevButtonStates[ROW_COUNT][COL_COUNT] = {false};
char buttonVals[ROW_COUNT][COL_COUNT] = {
  {'7', '8', '9', 'D', 'C'},
  {'4', '5', '6', '*', '/'},
  {'1', '2', '3', '+', '-'},
  {'0', '.', 'E', 'a', '='},
};

const int MAX_INPUT_SIZE = 100;
const int MAX_MEMORY_SIZE = 8;
const int MAX_STACK_SIZE = 100;

struct Input{
  char data[MAX_INPUT_SIZE];
  int len;
};
Input userInput = {};

struct Token{
  char type;

  union{
    float val;
    char op;
  };
};

enum ErrorCodes{
  SUCCESS,
  MULTIPLE_DECIMAL_POINTS,
};

struct TokenExpression{
  int tokenCount;
  ErrorCodes status;
  Token data[MAX_INPUT_SIZE];
};

struct Stack{
  int top;
  Token data[MAX_STACK_SIZE];

  void POP(){
    top--;
  };
  void PUSH(Token input){
    top++;
    data[top] = input;
  };
  Token PEEK(){
    return data[top];
  };
};

struct Memory{
  float ans[MAX_MEMORY_SIZE];
  char expr[MAX_INPUT_SIZE];
};
Memory memory = {};

struct Cursor{
  int x;
  int y;
};
Cursor cursor = {};

//-----------------------------------------------FUNCTIONS------------------------------------------------

bool isOperator(char input){
  const int OPERATOR_COUNT = 4;
  char operatorArray[OPERATOR_COUNT] = {'+', '-', '*', '/'};

  for(int i = 0; i < OPERATOR_COUNT; i++){
    if(input == operatorArray[i]){
      return true;
    }
  }

  return false;
}

TokenExpression tokenize(Input input){
  TokenExpression infixExpression = {};
  int i = 0;

  while(i < input.len){
    if(isDigit(input.data[i]) || input.data[i] == '.'){
      float num = 0;
      bool seenDecimal = false;
      int digitsAD = 0;

      while(i < input.len && (isDigit(input.data[i]) || input.data[i] == '.')){
        if(input.data[i] == '.'){
          if(seenDecimal){
            infixExpression.status = MULTIPLE_DECIMAL_POINTS;
            return infixExpression;
          };
          seenDecimal = true;
          i++;
          continue;
        };

        if(isDigit(input.data[i]) && !seenDecimal){
          num = num * 10 + (input.data[i] - '0');
          i++;
          continue;
        };
          
        if(isDigit(input.data[i]) && seenDecimal){
          digitsAD++;
          num = num + (input.data[i] - '0') * pow(0.1, digitsAD);
          i++;
          continue;
        };
      }
      
      infixExpression.data[infixExpression.tokenCount].type = 'n';
      infixExpression.data[infixExpression.tokenCount].val = num;
      infixExpression.tokenCount++;
    }

    if(i < input.len && isOperator(input.data[i])){
      infixExpression.data[infixExpression.tokenCount].type = 'o';
      infixExpression.data[infixExpression.tokenCount].op = input.data[i];
      i++;
      infixExpression.tokenCount++;
    }
  }

  infixExpression.status = SUCCESS;
  return infixExpression;
}

int precedence(char op){
  if(op == '+' || op == '-'){
    return 1;
  }else if(op == '*' || op == '/'){
    return 2;
  };
}

TokenExpression shuntingYard(TokenExpression infixExpression){
  TokenExpression RPNExpression = {};
  int RPNLen = 0;
  Stack opStack = {
    .top = -1
  };

  for(int i = 0; i < infixExpression.tokenCount; i++){
    if(infixExpression.data[i].type == 'n'){
      RPNExpression.data[RPNLen].type = 'n';
      RPNExpression.data[RPNLen].val = infixExpression.data[i].val;
      RPNLen++;
    }else if(infixExpression.data[i].type == 'o'){
      RPNExpression.data[RPNLen].type = 'o';
      if(opStack.top > -1){
        while(precedence(opStack.PEEK().op) >= precedence(infixExpression.data[i].op)){
          RPNExpression.data[RPNLen] = opStack.PEEK();
          RPNLen++;
          opStack.POP();
          continue;
        }
      };
      opStack.PUSH(infixExpression.data[i]);
    };
  }
  for(int i = 0; opStack.top > -1; i++){
    RPNExpression.data[RPNLen] = opStack.PEEK();
    opStack.POP();
    RPNLen++;
  }

  RPNExpression.tokenCount = RPNLen;
  RPNExpression.status = SUCCESS;
  return RPNExpression;
}

float evaluateRPN(TokenExpression RPNExpression){
  Stack numberStack = {
    .top = -1
  };

  for(int i = 0; i < RPNExpression.tokenCount; i++){
    if(RPNExpression.data[i].type == 'n'){
      numberStack.PUSH(RPNExpression.data[i]);
    }else if(RPNExpression.data[i].type == 'o'){
      float num1 = numberStack.PEEK().val;
      numberStack.POP();
      float num2 = numberStack.PEEK().val;
      numberStack.POP();

      Token ans = {
        .type = 'n'
      };

      if(RPNExpression.data[i].op == '+'){
        ans.val = num2 + num1;
      }else if(RPNExpression.data[i].op == '-'){
        ans.val = num2 - num1;
      }else if(RPNExpression.data[i].op == '*'){
        ans.val = num2 * num1;
      }else if(RPNExpression.data[i].op == '/'){
        ans.val = num2 / num1;
      };

      numberStack.PUSH(ans);
    };
  }

  return numberStack.data[numberStack.top].val;
}

float evaluate(Input input){
  Serial.println(1);

  TokenExpression infixExpression = tokenize(input);
  Serial.println(2);

  TokenExpression RPNExpression = shuntingYard(infixExpression);
  Serial.println(3);

  float ans = evaluateRPN(RPNExpression);
  Serial.println(4);

  return ans;
}

void clearInput(){
  for(int i = 0; i < userInput.len; i++){
    userInput.data[i] = '\0';
  }
  userInput.len = 0;

  lcd.clear();
  cursor.x = 0;
  cursor.y = 0;
  lcd.setCursor(cursor.x, cursor.y);
}

void deleteChar(){
  userInput.len--;
  userInput.data[userInput.len] = '\0';

  cursor.x--;
  lcd.setCursor(cursor.x, cursor.y);
  lcd.print(' ');
  lcd.setCursor(cursor.x, cursor.y);
}

void printChar(char input){
  userInput.data[userInput.len] = input;
  userInput.len++;

  lcd.print(input);
  cursor.x++;
  lcd.setCursor(cursor.x, cursor.y);
}

//---------------------------------------------SETUP AND LOOP---------------------------------------------

void setup() {
  for(int row = 0; row < ROW_COUNT; row++){
    pinMode(rowPins[row], OUTPUT);
  }
  for(int col = 0; col < COL_COUNT; col++){
    pinMode(colPins[col], INPUT_PULLUP);
  }

  lcd.begin(20, 4);
  lcd.setCursor(cursor.x, cursor.y);
  lcd.blink();

  Serial.begin(9600);
}

void loop() {
  //SCAN
  for(int currentRow = 0; currentRow < ROW_COUNT; currentRow++){
    for(int row = 0; row < ROW_COUNT; row++){
      digitalWrite(rowPins[row], HIGH);
    }
    digitalWrite(rowPins[currentRow], LOW);

    for(int currentCol = 0; currentCol < COL_COUNT; currentCol++){
      prevButtonStates[currentRow][currentCol] = buttonStates[currentRow][currentCol];
      buttonStates[currentRow][currentCol] = !digitalRead(colPins[currentCol]);
    }
  }

  //OUTPUT
  for(int currentRow = 0; currentRow < ROW_COUNT; currentRow++){
    for(int currentCol = 0; currentCol < COL_COUNT; currentCol++){
      if(buttonStates[currentRow][currentCol] && !prevButtonStates[currentRow][currentCol]){
        char val = buttonVals[currentRow][currentCol];

        if(val == '='){
            Serial.println("equals pressed");
            float ans = evaluate(userInput);
            cursor.x = 3;
            cursor.y = 3;
            lcd.setCursor(cursor.x, cursor.y);
            lcd.print(ans);
        }else if(val == 'C'){
          clearInput();
        }else if(val == 'D'){
          deleteChar();
        }else{
          printChar(val);
        }
      }
    }
  }

  delay(15);
}

The Token struct is going to be 5 bytes (at best). So an array of 100 of these is going to be at least 500 bytes.

Uno has only 2048 bytes of memory, and you never want to exceed 80% usage, or your code will be unstable.

So you need to decide whether you want to swap to an Arduino model with more memory, or challenge yourself to learn to write code in a way that treats memory as a precious resource, like programmers were forced to back in the '60s & '70s.

And '80s!

where does userInput get captured?

        if(val == '='){
            Serial.println("equals pressed");
            float ans = evaluate(userInput);
            cursor.x = 3;
            cursor.y = 3;
            lcd.setCursor(cursor.x, cursor.y);
            lcd.print(ans);
        }else if(val == 'C'){
          clearInput();
        }else if(val == 'D'){
          deleteChar();
        }else{
          printChar(val);
        }

In printChar()

It would save a lot of memory if you passed the structs to functions by reference. The way you have it now, whenever you pass a struct such as userInput to a function, a copy of the struct is created, while passing by reference will just create a pointer to the original struct.

A 100-character input for a display that can show a maximum of 80 characters seems a bit generous.

it may help if you developed your code on a laptop once verified, it can be moved to the Arduino with it's keypad and LCD.

confused why there's no "equals pressed" in your wiki.

i get the following output from a laptop version of your code (with some other changes).

i provide "12.3+5.6=" input

> 12.3+5.6=

lcdPrint: 1
lcdPrint: 2
lcdPrint: .
lcdPrint: 3
lcdPrint: +
lcdPrint: 5
lcdPrint: .
lcdPrint: 6
evaluate:
  tokenize:
  shuntingYard:
  evaluateRPN:
    evaluateRPN: 11.000000  15.000000  +
 evaluate: 26.00

lcdPrint: 26.000000>
#if 0
#include <math.h>
#include <Wire.h>
#include <hd44780.h>
#include <hd44780ioClass/hd44780_I2Cexp.h>

#else
#include <stdio.h>
#include <stdlib.h>
#include <ctype.h>

// -------------------------------------
enum { OUTPUT, INPUT, INPUT_PULLUP };
enum { LOW, HIGH };
void pinMode (int p, int mode) { }

struct hd44780_I2Cexp  {
    void begin (int x, int y) { }
    void blink () { }
    void clear () { }
    void setCursor (int x, int y) { }
    void print (int   i)  { printf ("\nlcdPrint: %d", i); }
    void print (char  c)  { printf ("\nlcdPrint: %c", c); }
    void print (float f)  { printf ("\nlcdPrint: %f", f); }
};

// -------------------------------------
void delay (long)  { }
int  digitalRead (int p) { return 1; }
void digitalWrite (int p, int val) { }

bool isDigit (char c) { return isdigit (c); }
int  pow (float c, int p) { return 1; }

// -------------------------------------
struct HW {
  void begin (int bps) { }
  void println (int i)   { printf ("%d\n", i); }
  void println (const char *s) { printf ("%s\n", s); }
} Serial;

// -------------------------------------
void setup ();
void loop ();
int main () {
    setup ();
    while (1)
        loop ();
}
#endif

// -----------------------------------------------------------------------------
hd44780_I2Cexp lcd;

//--------------------DATA--------------------------------------------------

const int ROW_COUNT = 4;
const int COL_COUNT = 5;
int rowPins[ROW_COUNT] = {2, 3, 4, 5};
int colPins[COL_COUNT] = {8, 9, 10, 11, 12};
bool buttonStates[ROW_COUNT][COL_COUNT] = {false};
bool prevButtonStates[ROW_COUNT][COL_COUNT] = {false};
char buttonVals[ROW_COUNT][COL_COUNT] = {
  {'7', '8', '9', 'D', 'C'},
  {'4', '5', '6', '*', '/'},
  {'1', '2', '3', '+', '-'},
  {'0', '.', 'E', 'a', '='},
};

const int MAX_INPUT_SIZE = 100;
const int MAX_MEMORY_SIZE = 8;
const int MAX_STACK_SIZE = 100;

struct Input{
  char data[MAX_INPUT_SIZE];
  int len;
};
Input userInput = {};

struct Token{
  char type;

  union{
    float val;
    char op;
  };
};

enum ErrorCodes{
  SUCCESS,
  MULTIPLE_DECIMAL_POINTS,
};

struct TokenExpression{
  int tokenCount;
  ErrorCodes status;
  Token data[MAX_INPUT_SIZE];
};

struct Stack{
  int top;
  Token data[MAX_STACK_SIZE];

  void POP ()  {
    top--;
  };
  void PUSH (Token input)  {
    top++;
    data[top] = input;
  };
  Token PEEK ()  {
    return data[top];
  };
};

struct Memory{
  float ans[MAX_MEMORY_SIZE];
  char expr[MAX_INPUT_SIZE];
};
Memory memory = {};

struct Cursor{
  int x;
  int y;
};
Cursor cursor = {};

//-----------------------------------------------FUNCTIONS------------------------------------------------

bool isOperator (char input)  {
  const int OPERATOR_COUNT = 4;
  char operatorArray[OPERATOR_COUNT] = {'+', '-', '*', '/'};

  for (int i = 0; i < OPERATOR_COUNT; i++)  {
    if (input == operatorArray[i])  {
      return true;
    }
  }

  return false;
}

// -----------------------------------------------------------------------------
TokenExpression tokenize (Input input)
{
  printf ("  %s:\n", __func__);

  TokenExpression infixExpression = {};
  int i = 0;

  while (i < input.len)  {
    if (isDigit(input.data[i]) || input.data[i] == '.')  {
      float num = 0;
      bool seenDecimal = false;
      int digitsAD = 0;

      while (i < input.len && (isDigit(input.data[i]) || input.data[i] == '.'))  {
        if (input.data[i] == '.')  {
          if (seenDecimal)  {
            infixExpression.status = MULTIPLE_DECIMAL_POINTS;
            return infixExpression;
          };
          seenDecimal = true;
          i++;
          continue;
        };

        if (isDigit(input.data[i]) && !seenDecimal)  {
          num = num * 10 + (input.data[i] - '0');
          i++;
          continue;
        };
          
        if (isDigit(input.data[i]) && seenDecimal)  {
          digitsAD++;
          num = num + (input.data[i] - '0') * pow(0.1, digitsAD);
          i++;
          continue;
        };
      }
      
      infixExpression.data[infixExpression.tokenCount].type = 'n';
      infixExpression.data[infixExpression.tokenCount].val = num;
      infixExpression.tokenCount++;
    }

    if (i < input.len && isOperator(input.data[i]))  {
      infixExpression.data[infixExpression.tokenCount].type = 'o';
      infixExpression.data[infixExpression.tokenCount].op = input.data[i];
      i++;
      infixExpression.tokenCount++;
    }
  }

  infixExpression.status = SUCCESS;
  return infixExpression;
}

// -----------------------------------------------------------------------------
int precedence (char op)  {
  printf ("\n%s:\n", __func__);

  if (op == '+' || op == '-')  {
    return 1;
  }
  else if (op == '*' || op == '/')  {
    return 2;
  }
  return 0;
}

// -----------------------------------------------------------------------------
TokenExpression shuntingYard (TokenExpression infixExpression)  {
  printf ("  %s:\n", __func__);

  TokenExpression RPNExpression = {};
  int RPNLen = 0;
  Stack opStack = {
    .top = -1
  };

  for (int i = 0; i < infixExpression.tokenCount; i++)  {
    if (infixExpression.data[i].type == 'n')  {
      RPNExpression.data[RPNLen].type = 'n';
      RPNExpression.data[RPNLen].val = infixExpression.data[i].val;
      RPNLen++;
    }else if (infixExpression.data[i].type == 'o')  {
      RPNExpression.data[RPNLen].type = 'o';
      if (opStack.top > -1)  {
        while (precedence(opStack.PEEK().op) >= precedence(infixExpression.data[i].op))  {
          RPNExpression.data[RPNLen] = opStack.PEEK ();
          RPNLen++;
          opStack.POP ();
          continue;
        }
      };
      opStack.PUSH (infixExpression.data[i]);
    };
  }
  for (int i = 0; opStack.top > -1; i++)  {
    RPNExpression.data[RPNLen] = opStack.PEEK ();
    opStack.POP ();
    RPNLen++;
  }

  RPNExpression.tokenCount = RPNLen;
  RPNExpression.status = SUCCESS;
  return RPNExpression;
}

// -----------------------------------------------------------------------------
float evaluateRPN (TokenExpression RPNExpression)  {
  printf ("  %s:\n", __func__);

  Stack numberStack = {
    .top = -1
  };

  for (int i = 0; i < RPNExpression.tokenCount; i++)  {
    if (RPNExpression.data[i].type == 'n')  {
      numberStack.PUSH (RPNExpression.data[i]);
    }
    else if (RPNExpression.data[i].type == 'o')  {
      float num1 = numberStack.PEEK ().val;
      numberStack.POP ();
      float num2 = numberStack.PEEK ().val;
      numberStack.POP ();

      Token ans = {
        .type = 'n'
      };

    printf ("    %s: %f  %f  %c\n",
            __func__, num1, num2, RPNExpression.data[i].op);

      if (RPNExpression.data[i].op == '+')  {
        ans.val = num2 + num1;
      }
      else if (RPNExpression.data[i].op == '-')  {
        ans.val = num2 - num1;
      }
      else if (RPNExpression.data[i].op == '*')  {
        ans.val = num2 * num1;
      }
      else if (RPNExpression.data[i].op == '/')  {
        ans.val = num2 / num1;
      };

      numberStack.PUSH (ans);
    };
  }

  return numberStack.data[numberStack.top].val;
}

// -----------------------------------------------------------------------------
float evaluate (Input input)  {
  printf ("\n%s:\n", __func__);

 //  Serial.println (1);

  TokenExpression infixExpression = tokenize (input);
 //  Serial.println (2);

  TokenExpression RPNExpression = shuntingYard (infixExpression);
 //  Serial.println (3);

  float ans = evaluateRPN (RPNExpression);
 //  Serial.println (4);

  printf (" %s: %.2f\n", __func__, ans);
  return ans;
}

// -----------------------------------------------------------------------------
void clearInput ()  {
  printf ("%s:\n", __func__);

  for (int i = 0; i < userInput.len; i++)  {
    userInput.data[i] = '\0';
  }
  userInput.len = 0;

  lcd.clear ();
  cursor.x = 0;
  cursor.y = 0;
  lcd.setCursor (cursor.x, cursor.y);
}

// -----------------------------------------------------------------------------
void deleteChar ()  {
  printf ("%s:\n", __func__);

  userInput.len--;
  userInput.data[userInput.len] = '\0';

  cursor.x--;
  lcd.setCursor (cursor.x, cursor.y);
  lcd.print (' ');
  lcd.setCursor (cursor.x, cursor.y);
}

void printChar (char input)  {
  // printf ("%s: %c\n", __func__, input);

  userInput.data[userInput.len] = input;
  userInput.len++;

  lcd.print (input);
  cursor.x++;
  lcd.setCursor (cursor.x, cursor.y);
}

//---------------------------------------------SETUP AND LOOP---------------------------------------------

void setup () {
  for (int row = 0; row < ROW_COUNT; row++)  {
    pinMode (rowPins[row], OUTPUT);
  }
  for (int col = 0; col < COL_COUNT; col++)  {
    pinMode (colPins[col], INPUT_PULLUP);
  }

  lcd.begin (20, 4);
  lcd.setCursor (cursor.x, cursor.y);
  lcd.blink ();

  Serial.begin (9600);
    printf ("> ");
}

// -----------------------------------------------------------------------------
void scan () {
  printf ("%s:\n", __func__);

  //SCAN
  for (int cR = 0; cR < ROW_COUNT; cR++)  {
    for (int row = 0; row < ROW_COUNT; row++)  {
      digitalWrite (rowPins[row], HIGH);
    }
    digitalWrite (rowPins[cR], LOW);

    for (int cC = 0; cC < COL_COUNT; cC++)  {
      prevButtonStates[cR][cC] = buttonStates[cR][cC];
      buttonStates[cR][cC]     = ! digitalRead (colPins[cC]);
    }
  }

  //OUTPUT
  for (int cR = 0; cR < ROW_COUNT; cR++)  {
    for (int cC = 0; cC < COL_COUNT; cC++)  {
      if (buttonStates[cR][cC] && !prevButtonStates[cR][cC])  {
        char val = buttonVals[cR][cC];

        if (val == '=')  {
            Serial.println ("equals pressed");
            float ans = evaluate (userInput);
            cursor.x = 3;
            cursor.y = 3;
            lcd.setCursor (cursor.x, cursor.y);
            lcd.print (ans);
        }
        else if (val == 'C')  {
          clearInput ();
        }
        else if (val == 'D')  {
          deleteChar ();
        }
        else{
          printChar (val);
        }
      }
    }
  }

  delay (15);
}

// -----------------------------------------------------------------------------
void loop ()
{
    char c = getchar ();
 // printf ("%c", c);

    switch (c)  {
    case '=':
 //     Serial.println ("equals pressed");
        lcd.print ( evaluate (userInput) );
        printf ("> ");
        break;

    case 'C':
        clearInput ();
        break;

    case 'D':
        deleteChar ();
        break;

    case '\n':
        break;

    case 'q':
        exit (0);
        break;

    default:
        printChar (c);
        break;
    }
}

i see "equals pressed" if i add a return; before the call to evaluateRPN() in evaluate()

you might be interested to see how such processing is handled in a scipting language. The Program Developement chapter in The Unix Programming Environment starts with a simple calculator and evolves into a relatively powerful scripting language.

it show how input is parsed, broken into tokens and how it is processed. It handled various corner cases such as negative values. (would your code handle -12 *-6=?

Thanks a lot for your comment. After adjusting all of the parsing functions and lowering the max input size, the calculator now works as intended.