Tuesday, August 2, 2016

Arduino-Processing Interface (ทำลิเนียร์กราฟแบบ autoscale)

ผมต้องการข้อมูลจากเซนเซอร์ที่ติดต่ออยู่กับ Arduino แล้วนำข้อมูลมาแสดงผลบนคอมพิวเตอร์ PC ข้อมูลที่ว่าต้องการให้แสดงผลในรูปแบบกราฟขณะผู้ใช้กำลังใช้งานอยู่ และสามารถบันทึกข้อมูลเป็น text file ด้วยเพื่อนำข้อมูลไปใช้งานด้านอื่นๆต่อไป ก็เลยค้นหาว่าการเก็บข้อมูลจาก Arduino ลง PC เลยเขาทำยังไง ก็ไม่พบมีแต่แนะนำให้เก็บใน SD card และการติดต่อผ่านโปรแกรมอื่นๆ ซึ่งข้อแนะนำเหล่าผมเคยทดลองมาบ้างแล้ว ดังได้เขียนไว้ในห้อข้อก่อนๆ เช่น ติดต่อผ่าน Python และ Excel-Palalax เป็นต้น ก็เลยไม่แน่ในว่ามันทำได้หรือเปล่า (ด้วยความไม่รู้จริงๆ หลังจากค้นหาข้อมูล)

ก่อนหน้านี้ก็ได้ศึกษาชอฟต์แวร์ที่ชื่อ Processing มาระยะหนึ่งเพราะต้องใช้ ดังนั้นผมเลยถือโอกาศอันดีนี้พัฒนาตนเองในการใช้งาน Processing เพื่อรับค่าจาก Arduino โดยอ่านจาก serial communication แล้วนำมาเขียนกราฟแบบออโตสเกลด้วย บันทึกค่าลง text file ด้วย แม้ไม่ได้พัฒนาขึ้นเองทั้งหมดแต่การศึกษาครั้งทำให้ได้ความรู้เพิ่มขึ้นอีกขั้นหนึ่ง และนำมาเขียนเป็นบันทึกไว้ก่อนกันลืม

ขอขอบคุณไฟล์ต้นฉบับที่นำมาปรับปรุงมาจาก Interactive Matter Lab

ไฟล์ sketch ใน Arduino จาก example เลย ส่วนวงจรก็ใช้ตัวต้านทานปรับค่าได้ ปรับโวลต์จาก 0 ถึง 5 โวลต์ จำลองว่าเป็นค่าจากเซนเซอร์ (ในที่นี้จะไม่ปรับ ให้เป็นตัวเลข 5 โวลต์ตามแหล่งจ่ายยังคงใช้ตัวเลขจาก ADC 10-bit) ทดลองกันเลยครับ ดูภาพกราฟที่ได้เป็นตัวอย่าง (แต่ไม่ได้ใส่สเกลแกนนอน)

ภาพบนค่าที่รับมาเป็นช่วงกว้าง แกน y จะปรับให้ครอบคุมข้อมูลที่รับมา

ภาพด้านล่าง เกิดขึ้นเมื่อทิ้งค่าโวลต์ที่รับมาให้คงที่กราฟจะปรับสเกลให้อัตโนมัติ (คำนวณจากค่า max min ของข้อมูล) ไฟล์ต่างเข้าไปดาวโหลดที่นี่ได้เลย SerailRead_ArduinoProcessing

(ในรูปลืมเปลี่ยนหน่วย พอทดลองใช้วัดความถี่ของสัญญาณเพาส์เลยติดมา)

สำหรับรายละเอียดของ source code จะมาอธิบายเพิ่มเติมใน ภาคต่อไปครับ
/////////////////////////////////////////////////////////////////////////////
void setup() {
  // initialize serial communication at 9600 bits per second:
  Serial.begin(115200);
}

// the loop routine runs over and over again forever:
void loop() {
  // read the input on analog pin 0:
  int sensorValue = analogRead(A0);
  // print out the value you read:
  Serial.println(sensorValue);
  delay(20);        // delay in between reads for stability
}


///////////////////////////////////////////////////////////////////////////
และที่ยืดยาวคือส่วนของ processing ตามนี้เลย
///////////////// SerialRead_ArduinoProcessing.pde ////////////////
import processing.serial.*;
import processing.opengl.*;
import java.io.*;
Serial myPort;
int current;
float dataFreq;
Thread serialThread;
//int[] yValues;
//int w;

//color axisColor = 200;
//float axisStroke = 1;

//PrintWriter output;
/////////////////////////////////////
int viewHeight = 800;
int viewWidth = 1650;
float viewAreaX = 90;
float viewAreaY = 90;
color backLight=255; // original 0
color titleColor=0;//255;
int titleSize=18;
int titleY=20;

color axisColor = 20;//200;
float axisStroke = 1;

color gridTextColor =20;//200;
float leftBorder = 0.05; // original 0.035
float gridTextOffset = 7;
int gridTextSize=14;

color gridColor=150;//150;
float gridStart=-0.005;

color lineColor=0; // original 255
float lineStroke = 0.015;

String unit="Unit"; //ใช้หน่วยตามปริมาณที่วัด เช่น mV V Hz kHz MHz ms s

PFont font;

float changeThreshold = 0.001;
float lightThreshold = 0.5;

PrintWriter output;

float viewRatio = (viewWidth*viewAreaX/100.0)/(viewHeight*viewAreaY/100.0);
DataSet dataSet = new DataSet(500);
/////////////////////////////////////
void setup()
{
  size(1650, 800, OPENGL);
  String portName = Serial.list()[0];
  myPort = new Serial(this, portName, 115200); 
  output = createWriter( "TSL230_data.txt" );
  font = loadFont("LucidaGrande-48.vlw");
 
}

void draw()
{
      String inString = myPort.readStringUntil('\n');
 
   if(inString != null){
    inString = trim(inString);
    dataFreq = float(inString);
    //println(dataFreq);
    output.println(dataFreq);
    current = int(dataFreq);//int(map(inByte, 0, 1023, 0, height));
    println(current);
    dataSet.addSample(current);
   }
 

  background(backLight);


  synchronized(dataSet) {
   
    //can this be done simpler?
    float border = min((viewWidth*(100-viewAreaX)/100.0)/2.0,(viewHeight*(100-viewAreaY)/100.0)/2.0);
    float left = (int)(border)+viewWidth*leftBorder;
    float right = viewWidth - border;
    float bottom = (int)(border);
    float top = viewHeight - border;

    float arrowLong = (float)bottom*0.5;
    float arrowShort = (float)bottom*0.2;

    stroke(axisColor);
    strokeWeight (axisStroke);

    textMode(MODEL);
    textFont(font,titleSize);
    textAlign(CENTER);
    fill(titleColor);
    text(" ",viewWidth/2,titleY);

    //draw the axis
    //viewport is mirrored – 0 is at top
    line (left,bottom,left,top);
    line ((float)left,(float)bottom,left+arrowShort,bottom+arrowLong);
    line ((float)left,(float)bottom,left-arrowShort,bottom+arrowLong);
    line (left,top,right,top);
    line ((float)right,(float)top,right-arrowLong,top+arrowShort);
    line ((float)right,(float)top,right-arrowLong,top-arrowShort);

    float mi = dataSet.getMin();
    float ma = dataSet.getMax();
    float span = ma-mi;
    double magnitude = round((float)Math.log10(span));
    float factor = (float)Math.pow(10,1-magnitude);
    float lower = round((float)(mi*factor)-1.0)/factor;
    float upper = round((float)(ma*factor)+1.0)/factor;
    int steps = round((float)(span*factor));
    int textSteps = 1;
    if (steps>10) {
      textSteps=round(((float)steps+1.5)/10.0);
    }

   // println(mi+" ("+lower+") - "+ma+" ("+upper+") = "+span+" ("+steps+" / "+textSteps+") @ "+magnitude+","+factor);

    float stepLength = (upper - lower) / steps;

    //Y-axis texts
    stroke(gridTextColor);
    textFont(font,gridTextSize);
    textAlign(RIGHT);

    //viewport is not normalized yet - we have to doit ourself
    //draw the grid
    for (int i=1; i      //we use ints to produce 'nice numbers'
      int valueText = (int)((lower+(stepLength*i))*factor);
      float value =(float)valueText/factor;
      //normalize between top & bottom
      float pos = map(value,lower,upper,top,bottom);

      if ((i % textSteps) == 0) {
        fill(gridTextColor);
        text(value+unit,left-gridTextOffset,pos);
      }
      stroke(gridColor);
      line (left+gridStart,pos,right,pos);
    }


    //prepare the viewport so that it is 0-1 with 0,0 at bottom
    rotateX(PI);
    translate(0,-viewHeight);
    translate (left,bottom);
    scale (right-left,top-bottom);

    //draw the actual data
   
    float sampleSteps = 1.0 / dataSet.getNumberOfSamples();
    for (int i=1; i < dataSet.getNumberOfSamples(); i++) {
      //float RawData = dataSet.getSample(i);
      float sample = norm(dataSet.getSample(i), lower, upper);
      float previous = norm(dataSet.getSample(i-1), lower, upper);
      float lineLeft = (i-1)*sampleSteps;
      float lineRight = i*sampleSteps;
      // save data to position filename
      //output.print(lineRight + " , ");  // Write the time to the file
      //output.println(RawData);  // Write the capacitance to the file
     
      stroke(255,0,0);//(lineColor);
      strokeWeight(lineStroke);
      line(lineLeft,previous,lineRight,sample);

    }
  }

  translate(viewWidth*viewAreaX/100.0, viewHeight*viewAreaY/100.0);
}


 และส่วนนี้ให้บันทึกเป็นชื่อไฟล์ "dataset.pde" แล้วเก็บไว้ในโฟลเดอร์เดียวกับไฟล์หลักด้านบน เมื่อเปิดไฟล์หลักขึ้นมาใน processing จะดึงไฟล์ย่อยที่ชื่อ dataset.pde มาเอง

//////////////////////////// dataset.pde //////////////////////////
public class DataSet {
 
  float[] numbers;
  float[] changes;
 
  int maxSamples;
  float minimum = 0;
  float maximum = 0;
  int samplePosition = 0;
 
  public DataSet(int numberOfSamples) {
    numbers = new float[numberOfSamples];
    changes = new float[numberOfSamples];
    maxSamples = numberOfSamples;
  }
 
  public synchronized void addSample(float sample) {

    //store the sample
    numbers[samplePosition] = sample;
   
    //calculate min & max
    minimum = Float.MAX_VALUE;
    maximum = - Float.MAX_VALUE;
    for (int i=0; i      minimum=Math.min(minimum,numbers[i]);
      maximum=Math.max(maximum,numbers[i]);
    }
    //println(minimum+" - "+maximum);
   
    //increase sample position
    samplePosition++;
    if (samplePosition==maxSamples) {
      samplePosition = 0;
    }
  }
 
  public float getMax() {
    return maximum;
  }
 
  public float getMin() {
    return minimum;
  }
 
  public int getNumberOfSamples() {
    return maxSamples;
  }
 
  public float getSample(int position) {
    int arrayPosition = samplePosition + position;
   
    //wrap around if too big
    if (arrayPosition>=maxSamples) {
        arrayPosition -=maxSamples;
    }
    return numbers[arrayPosition];
  }
}

/////////////////////////////////////////////////////////////// 

ลองเอาไปใช้กันครับ มีข้อแนะนำอะไรเขียนในคอมเมนต์ได้เลยครับ แจกฟรี