วันเสาร์ที่ 5 กันยายน พ.ศ. 2558

Arduino Simple Calculator

ระบบการทำงาน
ให้ Arduino รับข้อความทาง Serial Monitor เป็นลักษณะของนิพจน์ทางคณิตศาสตร์
ประกอบด้วย "+"บวก, "-"ลบ, "*"คูณ, "/"หาร, "%"หารเอาเศษ, "^"ยกกำลัง, "( )"วงเล็บสำหรับจัดกลุ่มการคำนวณ, ตัวเลขจำนวนเต็มบวก (Integer) และ "="เครื่องหมายเท่ากับเพื่อบอกให้แสดงผลลัพธ์
จากนั้นไปคำนวณบนชิปไมโครคอนโทรเลอร์ของบอร์ด Arduino แล้วส่งผลลัพธ์กลับมาแสดงบน Serial Monitor โดยให้ขึ้นต้นด้วยเครื่องหมาย "="เท่ากับ

ออกแบบระบบการทำงาน
ส่วนที่ 1 การอ่านค่าจาก Serial Monitor
- กำหนด datarate เท่ากับ 9600 bps (bits/sec) ด้วยฟังก์ชัน Serial.begin(9600);
void setup() {                
  Serial.begin(9600);
  //...
}

- ตรวจสอบว่ามีข้อมูลให้อ่านโดยใช้ฟังก์ชัน Serial.available() แล้วใช้ฟังก์ชัน Serial.read() อ่านจาก Serial ทีละ 1 byte (เท่ากับ 1 ตัวอักษร(char)) ได้ข้อมูลชนิด byte
void loop() {
  while (Serial.available()) {
    byteRead = Serial.read();
    //...  
  }
}

- การรวมตัวเลขจากตัวเลขที่แยกเป็น byte ให้เป็นจำนวนเต็มบวกหลายๆหลักเช่น 1,234 ใช้วิธีดังนี้
อ่าน byte แรกได้ 49 คือ '1' ได้เป็น                  (49-48) = 1
อ่าน byte สองได้ 50 คือ '2' ได้เป็น     (1x10)+(50-48) = 12
อ่าน byte สามได้ 51 คือ '3' ได้เป็น   (12x10)+(51-48) = 123
อ่าน byte สี่    ได้ 52 คือ '4' ได้เป็น (123x10)+(52-48) = 1234
   โดย 48,49,50,51,52 นั้นคือรหัส ASCII ในฐานสิบ (DEC) ของตัวอักษร '0','1','2','3' และ '4' ตามลำดับซึ่งมีขนาด 1 byte โดยสามารถดูได้จาก ตาราง ASCII
if(byteRead>47 && byteRead< 58) {                
  num = (num*10)+(byteRead-48);
  //...
}

ที่มา http://arduinobasics.blogspot.com/2012/07/arduino-basics-simple-arduino-serial.html

ส่วนที่ 2 การคำนวณนิพจน์ทางคณิตศาสตร์ด้วยกองซ้อน (Stack)
โดยประกอบด้วย 2 Stack คือ Stackของตัวเลข (Operand) และ Stackของตัวดำเนินการ (Operator)
และมีการคำนวณตามเงื่อนไขดังต่อไปนี้

  1. อ่านนิพจน์จากซ้ายไปขวา
  2. อ่านตัวเลข (Operand) ให้ใส่ (Push) ลงไปใน Stackของตัวเลข (Operand)
  3. อ่านตัวดำเนินการ (Operator) ยกเว้นวงเล็บปิดและเครื่องหมาย"="เท่ากับ ให้ใส่ (Push) ลงไปใน Stackของตัวดำเนินการ (Operator)
  4. อ่านวงเล็บปิด หรือ เครื่องหมาย"="เท่ากับ
    • ให้ดึง (Pop) ตัวดำเนินการ (Operator) หนึ่งตัว 
    • หากไม่ใช่วงเล็บเปิด (สมมติเป็น "+"บวก) ให้ดึง (Pop) ตัวเลขออกมาสองตัว (สมมติดึงออกมาตัวแรกคือ 2 ตัวที่สองคือ 1) แล้วนำมาดำเนินการกันโดยตัวแรกอยู่ด้านหลังตัวดำเนินการและตัวที่สองอยู่หน้าตัวดำเนินการ (ตามตัวสมมติจะได้เป็น "1+2") แล้วนำผลลัพธ์ใส่ลงไปใน Stackของตัวเลข (Operand)
    • หากเป็นวงเล็บเปิด ไม่ต้องทำอะไร
    • วนซ้ำข้อ 4 ไปเรื่อยๆจนกว่า Stackของตัวดำเนินการ (Operator) จะว่างเปล่า (Empty)
สามารถดาวน์โหลด Library สำหรับ StackArray ได้จาก http://playground.arduino.cc/Code/StackArray

ผลการออกแบบ
โค้ด Arduino
#include  <StackArray.h>

byte byteRead;
int num, num1, num2, answer;
byte op;
boolean haveNum = false; //ไว้กันสำหรับเครื่องหมายกับวงเล็บต่อกัน ห้ามใส่ num ลงไปใน stack
StackArray  number_stack;
StackArray  operator_stack;

void setup() {                
  Serial.begin(9600);
  num=0;
  num1=0;
  num2=0;
  op = 43;
}

void loop() {
  while (Serial.available()) {
    
    byteRead = Serial.read();
    
    
    //listen for numbers between 0-9
    if(byteRead>47 && byteRead<58){
      num=(num*10)+(byteRead-48);
      haveNum = true;      
    }
      
    if(byteRead==61){
      if(number_stack.count() == 1 && operator_stack.isEmpty()){
      }else{
          /********************************************************/        
        if(haveNum == true){
          number_stack.push(num); //push เลขล่าสุดลง stack
          num=0; //resetค่า
        }
      
        while(!operator_stack.isEmpty()){
          op = operator_stack.pop(); //ถ้ามี op ดึง op ออกมา
          if(op != 40){ //ถ้าไม่ใช่เครื่องหมายวงเล็บเปิดก็คำนวน ถ้าใช่ก็แค่ pop วงเล็บทิ้ง
            num2 = number_stack.pop(); //ใส่ทีหลังเป็น B
            num1 = number_stack.pop(); //ใส่ก่อนเป็น A
            answer = cal(num1,num2,op); //คำนวนพื้นฐาน
            number_stack.push(answer); //เอาผลกลับใส่ stack
           }
         }
          /********************************************************/       
      }
      answer = number_stack.pop(); //ตัวเลขที่เหลือออกมา
      
      Serial.print("=");
      Serial.println(answer);
      
      /* Reset the variables for the next round */
      while(!operator_stack.isEmpty()){
        op = operator_stack.pop();
      }
      while(!number_stack.isEmpty()){
        num = number_stack.pop();       
      }
      answer=0;
      num1=0;
      num2=0;
      num=0;
      op=43;

        
    }else{ 
        if (byteRead==43||byteRead==45||byteRead==42||byteRead==47||byteRead==37||byteRead==94||byteRead==40){
      //                +             -             *             /             %            ^            (
          operator_stack.push(byteRead);
          if(haveNum == true){//byteRead!=40 && 
            number_stack.push(num);
            num=0;
          }
          haveNum = false;
        }
        
        if (byteRead==41){//วงเล็บปิด
          if(!operator_stack.isEmpty()){ //ถ้าemptyคือเป็นวงเล็บซ้อนตอนจบ
          /********************************************************/
            if(haveNum == true){
              number_stack.push(num); //push เลขล่าสุดลง stack
              num=0; //resetค่า   
              haveNum = false;
            }       
          while(!operator_stack.isEmpty()){
            op = operator_stack.pop(); //ถ้ามี op ดึง op ออกมา
            if(op != 40){ //ถ้าไม่ใช่เครื่องหมายวงเล็บเปิดก็คำนวน ถ้าใช่ก็แค่ pop วงเล็บทิ้ง
              num2 = number_stack.pop(); //ใส่ทีหลังเป็น B
              num1 = number_stack.pop(); //ใส่ก่อนเป็น A
              answer = cal(num1,num2,op); //คำนวนพื้นฐาน
              number_stack.push(answer); //เอาผลกลับใส่ stack
            }
          }
           /********************************************************/         
        }
      }    
    }
  }
}

int cal(int num1,int num2,byte op){ //ฟังก์ชันคำนวนพื้นฐาน
      if (op==43){answer=num1+num2;}
      if (op==45){answer=num1-num2;}
      if (op==42){answer=num1*num2;}
      if (op==47){answer=num1/num2;}
      if (op==37){answer=num1%num2;}
      if (op==94){answer=powint(num1,num2);}      
      return answer;
}

int powint(int x, int y){ /*ref: http://forum.arduino.cc/index.php?topic=3056.0*/
   int val=x;
   for(int z=0;z<=y;z++){
     if(z==0){
       val=1;
     }else{
       val=val*x;
     }
   }
   return val;
}

ผลการทดลองการทำงาน