วันจันทร์ที่ 7 กันยายน พ.ศ. 2558

Simple Calculator with Arduino

ขอบเขตการทำงาน
  ออกแบบโปรแกรมคำนวนสมการทางคณิตศาสตร์ ประกอบด้วย + - * / % ^ ( ) = และตัวเลข โดยรับข้อความทาง Serial Monitor และนำมาคำนวณบนบอร์ด Arduino และแสดงผลลัพธ์กลับมายัง Serial Monitor

แนวทางการออกแบบ
 -รับข้อความจาก user ในรูปแบบ infix
 -แปลง infix ให้อยู่ในรูปของ Postfix โดยใช้ String Array (ทำหน้าที่คล้ายกับ Stack )เข้ามาช่วยในการแปลง
 -นำ Postfix ที่ได้มาคำนวณค่าทางคณิตศาสตร์

อธิบายช่วงการทำงานของ Code
 -ช่วง State0 : รับค่าจาก Serial Monitor
 -ช่วง State1 : แปลง infix เป็น postfix
 -ช่วง State2 : นำ postfix ที่ได้มาคำนวน

Code Arduino

#define MAX 20 //Max digit

char ch; //each character in string
int state = 0; //State Cycle
int pr = 0; //Priority Value
String token = ""; //Debug input Value for state 0
/*  (10-3/2+5)*2             */ //28
/*  ((100+1)*50)/100         */ //50
/*  15-((10-5)-3)            */ //13
/*  (10^2 + 2^10) % (100^2)  */ //1124
/*  2^3-5*2^2-3*2+10         */ //-8
String postfix = ""; //Postfix Statement
String stacker = ""; //Collect to stack
String st = ""; //string temp
String sa[MAX], *sap; //Structure to collect data, use pointer to point the string array

// startup point entry (runs once).
void setup () {
  // start serial communication.
  Serial.begin (9600); //9600 buadrate
  Serial.println("__Arduino Calculator__");
  sap = sa; // pointer to String array
}

// loop the main sketch.
void loop () {
  if (state == 0) { // Input State
    //Comment if u want to use DEBUG STATEMENT//
    //>>>> /*
    if (Serial.available() > 0) { // available
      token = Serial.readString(); // readString from serial monitor
      //<<<< */
      if (!token.equals("")) { // check it not empty
        Serial.println("\n__STATE 0 : Input__");
        state = 1; // next state
        Serial.println("INPUT: " + token); //show input
      }
      //>>>> /*
    }
    //<<<< */
  }
  else if (state == 1) { // infix->postfix State
    Serial.println("\n__STATE 1 : Infix to Postfix__");
    // loopfor check each character
    for (int index = 0; index < token.length(); index++) {
      ch = token.charAt(index); // define each character from input String
      if (ch == ' ') { //if found ' ' (space) it can be skip this loop
        continue; // skip this loop
      }
      st += ch; // store in string temp
      pr = priority(ch); // check priority of character in ch

      if (pr == 0) { // same as isalnum() [it is a num?]
        stacker += ch; // collect to stacker
      }
      else {
        if (!stacker.equals("")) { // if stacker is not empty
          postfix += stacker + ' '; // collect data to postfix statement
        }
        stacker = ""; // clear stacker
        if (ch == ')') { // end bracket case
          // use to pop data from stack until it found "("
          while (!top(sap).equals("("))
            stacker += pop(sap) + " "; // pop data and collect to stacker
          pop(sap); // remove "("
        }
        // open bracket or stack is empty case
        else if (ch == '(' || top(sap).equals("")) {
          push(sap, st); // push + to stack
        }
        // top of stack priority value
        // less than priority value of this character
        else if (priority(top(sap).charAt(0)) < pr) {
          push(sap, st);
        }
        // top of stack priority value
        // more than (and Equal) priority value of this character
        else if (priority(top(sap).charAt(0)) >= pr) {
          // use for loop until stack is empty
          // and priority value of top of stack is more
          while (priority(top(sap).charAt(0)) >= pr && !top(sap).equals("")) {
            // pop stack data to collect in stacker
            stacker += pop(sap) + " ";
          }
          push(sap, st);
        }
      }
      st = ""; // clear stack temp
    }
    if (!stacker.equals("")) { // check stacker is not empty
      postfix += stacker + " "; // collect stacker date into postfix
    }
    while (!top(sap).equals("")) { // looping until stack is empty
      postfix += pop(sap) + " "; // pop stack data to collect into postfix
    }
    Serial.print("POSTFIX: ");
    Serial.println(postfix);
    token = ""; // clear input data
    stacker = ""; // clear stacker
    state = 2; // next state
  }
  else if (state == 2) { // Calculate State
    Serial.println("\n__STATE 2 : Calculate__");
    // looping for each character in postfix statement
    for (int index = 0; index < postfix.length(); index++) {
      ch = postfix.charAt(index); // get each character to char variable
      pr = priority(ch); // priority value of that character
      // check character is space and stacker is not empty
      if (ch == ' ' && !stacker.equals("")) {
        push(sap, stacker); // push stacker string to stack
        stacker = ""; // clear stacker
      }
      // if priority meaning is NUMBER
      else if (pr == 0) {
        stacker += ch; // collect that character into stacker
      }
      // check for not NUMBER
      else if (pr > 0) {
        long second = pop(sap).toInt(); //2nd value in integer
        long first = pop(sap).toInt(); //1st value in integer
        long answer = 0; // answer in long variable
        if (ch == '+') { // plus case
          answer = first + second; // summation answer
        }
        else if (ch == '-') { // minus case
          answer = first - second; // minus answer
        }
        else if (ch == '*') { // multiply case
          answer = first * second; // product answer
        }
        else if (ch == '%') { // modulation case
          answer = first % second; // mod answer
        }
        else if (ch == '/') { // divide case
          if (second != 0) { // check 2nd number (answer will be infinite)
            answer = first / second; // divide answer
          }
          else // if 2nd is zero => answer not have a value (infinite)
            Serial.println("ERROR"); // Error Statement

        }
        else if (ch == '^') { // Power case
          // power answer (have +1 becuase it calculate in binary)
          answer = pow(first, second) + 1;
        }
        push(sap, (String)answer); // push answer in String type to stack
      }
    }
    if (!stacker.equals("")) // check stacker isn't empty
      push(sap, stacker); // push stacker date into stack

    Serial.print("ANSWER: ");
    Serial.println(pop(sap)); // Final Answer
    Serial.println("----- DONE! PLEASE EXIT OR NEXT INPUT -----");
    postfix = ""; // clear postfix statement
    state = 0; // next state to zero-state
  }
}

int priority(char key) { // Function send integer priority value of key
  if (key > 47 && key < 58) { // is a number
    return 0;
  }
  else { // is a operator/others
    switch (key) {
      case '^': // Highest priority
        return 4;
      case '%':
      case '*':
      case '/':
        return 3;
      case '+':
      case '-':
        return 2;
      case '(':
      case ')':
        return 1; // Lowest priority
      default:
        return -1; // others
    }
  }
}

// use to push date to stack
// inputs are String Array in str pointer and Data String in s
void push(String* str, String s) {
  // loop for find space that can be collect the data
  for (int i = 0; i < sizeof(*str); i++) {
    if (str[i].equals("")) { //found space
      str[i] = s; // store in that slot
      break; // stop loop
    }
  }
}

// use to find lastest data of stack
// input is String Array in str pointer
String top(String* str) {
  if (str[0].equals("")) { // empty data
    return ""; // return empty string
  }
  // loop for find lastest data
  for (int i = 0; i < sizeof(*str) - 1; i++) {
    if (str[i + 1].equals("")) { // found next slot that is empty space
      return str[i]; // return lastest data
    }
  }
}

// use to get String value in stack
// input is String Array in str pointer
String pop(String* str) {
  if (str[0].equals("")) { // empty data
    return ""; // return empty string
  }
  // loop for find lastest data
  for (int i = 0; i < sizeof(*str) - 1; i++) {
    if (str[i + 1].equals("")) { // found next slot that is empty space
      String s = str[i]; // get lastest data into s String variable
      str[i] = ""; // clear that slot into empty space
      return s; // return data
    }
  }
}
อธิบายการทำงานของ Code
1.การรับค่าจาก Serial Monitor
  -กำหนดค่า bound rate =9600 bps ด้วยคำสั่ง Serial.begin()
void setup () {
  // start serial communication.
  Serial.begin (9600); //9600 buadrate
  //...
}
-ตรวจสอบว่ามีการป้อนข้อมูลเข้ามาหรือไม่ โดยใช้คำสั่ง Serial.available() และใช้คำสั่ง Serial.Read String() ในการอ่านค่า
void loop () {
  if (state == 0) { // Input State
    if (Serial.available() > 0) { // available
      token = Serial.readString(); // readString from serial monitor
      //...
    }
  }
 }
-เปลี่ยน State0 -> State1 เมื่อรับข้อมูลเข้ามา โดยเช็คจาก token != Empty (token คือตัวแปลที่เก็บข้อมูลที่รับค่าเข้ามา)
void loop () {
  if (state == 0) { // Input State
    //......
      if (!token.equals("")) { // check it not empty
        Serial.println("\n__STATE 0 : Input__");
        state = 1; // next state
        Serial.println("INPUT: " + token); //show input
      }  
   }   
}



3.การคำนวณ
 -เมื่อเราได้แปลงค่า infix เป็น postfix แล้ว เราจะทำการคำนวณโดย เมื่อพบเจอเครื่องหมาย ที่ให้ทำ pop ตัวเลขออกมาคำนวณเป็นคู่ๆ ดังนี้
else if (state == 2) { // เข้าสู่ขั้นตอนการคำนวณ
    Serial.println("\n__STATE 2 : Calculate__");
    // สั่งวนลูปตามความยาวข้อความ
    for (int index = 0; index < postfix.length(); index++) {
      ch = postfix.charAt(index); // กำหนดตัวแปร ch ให้ชี้แต่ละตำแหน่งในตัวแปร postfix
      pr = priority(ch); // กำหนดค่าความสำคัญให้กับตัวแปร ch
      // ถ้า ch มีค่า '' และ stack ไม่ว่าง
      if (ch == ' ' && !stacker.equals("")) {
        push(sap, stacker); // push ค่าลงไปใน stack
        stacker = ""; // เคลีย stacker
      }
      // ถ้า pr มีค่าเป็น 0 (เป็นตัวเลข)
      else if (pr == 0) {
        stacker += ch; // ให้ stacker บวกเท่ากับ ch
      }
      // ถ้า pr มีค่ามากกว่า 0 (เป็นเครื่องหมาย)
      else if (pr > 0) {
        long second = pop(sap).toInt(); //popค่าออกมาเก็บไว้ในตัวแปร second
        long first = pop(sap).toInt(); //popค่าออกมาเก็บไว้ในตัวแปร first
        long answer = 0; 
        if (ch == '+') { // เงื่อนไขเป็นบวก
          answer = first + second; // นำตัวแปร first บวก second แล้วเก็บไว้ในตัวแปร answer
        }
        else if (ch == '-') { // เงื่อนไขเป็นลบ
          answer = first - second; // นำตัวแปร first ลบ second แล้วเก็บไว้ในตัวแปร answer
        }
        else if (ch == '*') { // เงื่อนไขเป็นคูณ
          answer = first * second; // นำตัวแปร first คูณ second แล้วเก็บไว้ในตัวแปร answer
        }
        else if (ch == '%') { // เงื่อนไขเป็นหารเอาเศษ
          answer = first % second; // นำตัวแปร first หาร second แล้วนำเศษส่วนเก็บไว้ในตัวแปร answer
        }
        else if (ch == '/') { // เงื่อนไขเป็นหาร
          if (second != 0) { // ถ้าส่วนไม่ใช่ 0
            answer = first / second; // นำตัวแปร first หาร second แล้วเก็บไว้ในตัวแปร answer
          }
          else // ถ้าส่วนเป็น 0 ค่าที่ได้จะเยอะมากๆ
            Serial.println("ERROR");

        }
        else if (ch == '^') { // เงื่อนไขเป็นยกกำลัง
          // นำตัวแปร first ยกกำลัง second แล้วเก็บไว้ในตัวแปร answer( +1 เพราะเป็นการคำนวณแบบ binary)
          answer = pow(first, second) + 1;
        }
        push(sap, (String)answer); // push answer ลงไปใน stack
      }
    }
    if (!stacker.equals("")) // ถ้า stack ไม่ว่าง
      push(sap, stacker); // push stacker ลงไปใน stack

    Serial.print("ANSWER: ");
    Serial.println(pop(sap)); // แสดงคำตอบที่คำนวณออกมา
    Serial.println("----- DONE! PLEASE EXIT OR NEXT INPUT -----");
    postfix = ""; // เคลีย postfix
    state = 0; // ให้ state เท่ากับ 0
  }
}
ฟังก์ชัน
int priority(char key) { // Function ลำดับความสำคัญ
  if (key > 47 && key < 58) { // เช็คว่า key เป็นตัวเลข 0-9
    return 0; //คืนค่า 0
  }
  else { // ถ้าไม่ใช่ตัวเลข
    switch (key) { //กำหนดเคส
      case '^': // ยกกำลัง มีค่าความสำคัญสูงสุด
        return 4; // คืนค่า 4
      case '%':
      case '*':
      case '/':
        return 3; // คืนค่า 3
      case '+':
      case '-':
        return 2; // คืนค่า 2
      case '(':
      case ')':
        return 1; // คืนค่า 1 มีค่าความสำคัญต่ำสุด
      default: // กำหนดให้ค่าเดิมตือ
        return -1; // คืนค่า -1
    }
  }
}