Friday, September 4, 2020

เริ่มต้น (ต้องเริ่มยังไง)



ใน Blog นี้ผมก็จะเขียนประสบการณ์ ที่ผมเรียนรู้มาเผยแพร่และเก็บไว้อ่านเอง เพราะผมเริ่มจากศูนย์ (ด้วยทักษะอิเล็กทรอนิกส์พื้นฐานสมัยเรียน 1 วิชา เรียนตัวเดียวเท่านั้น ส่วนโปรแกรมมิ่งไม่มีเลย) มันมีคำพูดหนึ่งที่ผมชอบมากๆ "เมื่อ อ่านออก-เขียนได้ ข้ออ้างที่ว่าไม่ได้เรียนมา ไร้ความหมายอย่างสิ้นเชิง"

ดังนั้นจึงเรียนให้ผู้ที่หลงเข้ามาอ่านรับรู้ไว้ว่าตัวข้าพเจ้าเริ่มจากไม่รู้อะไรเลย แล้วจากนั้นความจำเป็นที่อยากทำเครื่องมือ ของเล่นใช้เอง รวมไปถึงความอยากรู้ได้นำพาคนอย่างข้าพเจ้า ไปหาความรู้และปัญญาในที่สุด

 ในปัจจุบัน ผมได้หันมาใช้บอร์ด Arduino เนื่องจากมีเพื่อนร่วมงานแนะนำมาครับ ราคาไม่แพง มีคนใช้งานเยอะ ทั่วโลก กล่าวได้ว่ามีปัญหาติดขัดตรงไหน สามารถค้นหาวิธีแก้ไขได้ทันใจเลยครับ  ในเวบ blog นี้ผมจะเขียนไปตามงานแต่ละชิ้นที่ผมได้ทดลอง ไม่การลำดับอะไรทั้งนั้น (เขียนตามใจสั่ง)

สิ่งของจำเป็นต้องใช้ มีดังนี้
1. คอมพิวเตอร์ สำหรับเขียนโปรแกรม
2. บอร์ด Arduino
3. บอร์ดทดลองและชิ้นส่วนอิเล็กทรอนิกส์ต่างๆ เช่น ตัวต้านทาน LED มอเตอร์ รีเลย์ สวิทซ์ ตามต้องการ

Tuesday, April 2, 2019

การเรียนรู้ด้าน Machine learning สำหรับคัดแยกภาพ ตอนที่1 การดาวโหลดภาพจากเวบไซต์และเปลี่ยนชื่อไฟล์

 วัตถุประสงค์ของบทความนี้คือ บันทึกการศึกษา เกี่ยวกับการเรียนรู้เรื่อง Machine learning โดยใช้ Tensorflow ผ่าน Keras เพื่อคัดแยกวัตถุผ่านการเรียนรู้จากภาพถ่าย เมื่อได้โมเดลการคัดแยกจะนำไปใช้ประกอบกับ hardware เช่น บอร์ด Arduino หรือคอมพิวเตอร์อย่าง Raspberry Pi

ดังนั้นการเริ่มต้นเชิงเทคนิค ต้องมีข้อมูลภาพให้คอมพิวเตอร์เรียนรู้ ว่าภาพของวัตถุชนิดต่างๆ เป็นอย่างไร ข้อมูลภาพของวัตถุชนิดนั้นๆ จึงสำคัญ

สำหรับการดาวโหลดไฟล์ภาพ Google image search ใช้โปรแกรม Extreme Picture Finder จากนั้นเปลี่ยนชื่อไฟล์ให้รันตามลำดับ เช่น 1.jpg 2.jpg 3.jpg ..... เป็นต้น ด้วย

import os
import cv2
import numpy as np

OrginalFiledirectory = 'Libraries\Pictures' #เช่นเราดาวโหลดเก็บไว้ที่ Libraries\Pictures
count = 1
for img in os.listdir(OrginalFiledirectory):
    f1=cv2.imread('Libraries\Pictures/' + img)
    cv2.imwrite('Libraries\Pictures\newfloder1/' + '%d.jpg' %count, f1) #เปลี่ยนชื่อแล้วเก็บไว้ในโฟลเดอร์ใหม่ ที่ชื่อ newfolder1
    count +=1

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];
  }
}

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

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

Friday, October 9, 2015

การใช้งาน pulseIn ฟังก์ชัน ตอนที่ 1

หัวข้อนี้จะกล่าวถึงการใช้ฟังก์ชัน pulseIn และใช้บอร์ด Arduino UNO R3 เพื่อวัดความกว้างเพาส์สัญญาณ (Pulse width) หลักการทำงานจะอาศัยการตรวจวัดสัญญาณ HIGH หรือ LOW ที่ส่งเข้าขา digital และเซตขานั้นให้เป็น input port ฟังก์ชัน pulseIn มีหลักในการใช้ดังนี้

กรณีที่1

เมื่อตรวจพบสัญญาณขาเข้าเป็น HIGH ฟังก์ชันนี้จะเริ่มนับเวลาในหน่วยไมโครวินาที (microsec, $\mu$s) และรอจนกว่าจะเจอสัญญาณ LOW ถึงจะหยุดนับเวลา


กรณีที่2 กำหนดให้ตรวจสัญญาณ LOW ก่อน ฟังก์ชันนี้จะเริ่มนับเวลาในหน่วยไมโครวินาที และรอจนกว่าจะเจอสัญญาณ HIGH ถึงจะหยุดนับเวลา


รูปที่1 แสดงสัญญาณคลื่นสี่เหลี่ยม (square wave) และตรวจวัดสัญญาณ HIGH ---> LOW และ ตรวจวัดสัญญาณ LOW ---> HIGH

สรุปได้ว่าฟังก์ชัน pulseIn ทำหน้าที่เป็นนาฬิกาจับเวลาโดยใช้สัญญาณ LOW หรือ HIGH เป็นตัวกำหนด start/stop นั่นเอง

ฟังก์ชัน pulseIn มีรูปแบบการใช้งาน (https://www.arduino.cc/en/Reference/PulseIn)

Syntax

1) ===> pulseIn(pin, value)

2) ===> pulseIn(pin, value, timeout)

pin คือ กำหนดสัญญาณเข้าไหน เช่น ขา digital3, pin คือ 3

value คือ พารามิเตอร์ที่เรากำหนดให้บอร์ดตรวจวัดสัญญาณ HIGH หรือ LOW ก่อน (กำหนดให้เริ่มนับเวลาดังกล่าวข้างต้น)

timeout คือช่วงเวลาสูงสุดที่ฟังก์ชันนี้ยังทำงานอยู่ หากไม่กำหนดพารามิเตอร์นี้ เช่นกรณีแรกจะกำหนด default ไว้ที่ 1 วินาที หรือ 1,000,000 ไมโครวินาที   นั่นหมายความว่าถ้าความกว้างเพาส์สัญญาณที่จะวัดมากกว่า 1 วินาที ฟังก์ชันนี้จะไม่ทำงานและส่งค่า 0 ออกมาแทน ดังนั้นหากต้องการกำหนดให้ timeout สูงสุด 5 วินาที จะเขียนได้ว่า

pulseIn(3, HIGH, 5000000)

ตัวอย่างการใช้งาน และแสดงผลทาง serial communication

int pin = 3;
unsigned long duration;    

void setup()

  // initialize serial communications at 9600 bps:
  Serial.begin(9600); 

  pinMode(pin, INPUT);
}

void loop()
{
    duration = pulseIn(pin, HIGH, 10000000);
    float time=duration/1000.00;
    Serial.print("DelT=");
    Serial.print(time);
    Serial.println(" ms");
}


ตัวอย่างการใช้งานและแสดงผลทางจอ LCD

#include
LiquidCrystal lcd(11, 12, 4, 5, 6, 7);

int pin = 3;
unsigned long duration;
 

void setup()

{
  lcd.begin(16, 2);
  lcd.clear();
  lcd.setCursor(0, 0);
  lcd.print("UP VeloMeterV1.0");
  delay(2500);
  pinMode(pin, INPUT);
}

void loop()

{
    duration = pulseIn(pin, HIGH, 10000000);
    float time=duration/1000.00;
    lcd.setCursor(0, 1);lcd.print("DelT=         ms");
    lcd.setCursor(6, 1);lcd.print(time);
}


ก่อนจะทดสอบกับอุปกรณ์จริงขอทำการจำลองในโปรแกรมก่อน ในภาพใช้สวิทซ์เป็นเพื่อกำหนดสัญญาณ HIGH เมื่อสวิทซ์ on และเป็น LOW เมื่อสวิทซ์ off ได้ผลลัพธ์ดังภาพ โดยต่อสัญญาณเข้าที่ขา digital 3 จากรูปกำหนดแกนเวลาใน oscilloscope เป็น 0.5 s/div หากอ่านค่าเวลาเทียบกับตัวเลขที่ได้จากฟังก์ชัน pulseIn ที่แสดงในจอ LCD สอดคล้องกันเป็นอย่างดี ดังนั้นการทดสอบครั้งนี้ถือว่าบรรลุวัตถุประสงค์ขั้นต้น


   รูปที่2 ภาพการจำลองการวัดความกว้าง pulse ในโปรแกรม proteus ด้วยฟังก์ชัน pulseIn ใน Arduino โดยวัดเวลาที่กดสวิทซ์ 


ลำดับต่อไปจะทดสอบกับอุปกรณ์จริงโดยใช้ function generator (GWINSTEK รุ่น SFG-1013) เป็นเครื่องกำเนิดสัญญาณคลื่นสี่เหลี่ยมความถี่ต่างๆ เพื่อทดสอบความถูกต้อง (accuracy) และความแม่นยำ (precise) จากนั้นใช้ oscilloscope (Tektronix รุ่น TDS-1002) เพื่อวัดความกว้างเพาส์ (pulse width) เทียบกับฟังก์ชัน pulseIn ของ Arduino เพื่อให้เห็นภาพที่ชัดเจนขึ้นขออธิบายเกี่ยวสัญญาณสี่เหลี่ยมและการคำนวณเล็กน้อย

เวลาที่คลื่นเคลื่อนที่ครบ 1 รอบ เรียกว่า คาบการเคลื่อนที่ หรือ period มักใช้สัญลักษณ์เป็น "T" มีหน่วยเป็น วินาที ปริมาณที่เป็นส่วนกลับของคาบเรียกว่า ความถี่ หรือ frequency มักใช้สัญลักษณ์เป็น "f" มีหน่วยเป็น รอบต่อวนาที หรือ Hertz (Hz) หรือจะกล่าวว่าความถี่กำลังบอกว่า ใน 1 วินาที คลื่นเคลื่อนที่ผ่านจุดใดจุดหนึ่งไปกี่ลูกนั่นเองเขียนความสัมพันธ์ทางคณิตศาสตร์ ระหว่าง T กับ f ได้เป็น

$T=\frac{1}{f}$

สำหรับคลื่นสี่เหลี่ยมมีค่า ดิวตี้ไซเคิล (duty cycle) 50 % ดังภาพด้านล่างนั้น คลื่น 1 ลูกจะประกอบด้วยสัญญาณค่าสูงครึ่งหนึ่ง และอีกครึ่งหนึ่งเป็นสัญญาณค่าต่ำ หรือกล่าวได้ว่าความกว้างของเพาส์สัญญาณสูงเท่ากับความกว้างเพาส์สัญญาณต่ำ สมมติสัญญาณสี่เหลี่ยมมีความถี่ 50 Hz คาบการเคลื่อนที่ T คำนวณได้ดังนี้

$T=\frac{1}{50\, Hz}=0.02\, s=20\, \, ms$

ดังนั้น pulse width จะเท่ากับ 10 ms


รูปที่ 3 แสดงคลื่นสี่เหลี่ยมและความกว้างสัญญาณ (ภาพจาก www.electronics-tutorials.ws)

ในการทดสอบครั้งนี้จะอ่านค่า ความกว้างเพาส์ในหน่วย มิลลิวินาที และขอยกตัวอย่างมาให้เห็นเพียงสองความถี่ คือ 5 และ 25 Hz รูปที่ 4 แสดงความถี่ของสัญญาณสี่เหลี่ยมที่ตั้ง 25 Hz (รูปที่4 ก) รูปสัญญาณและความกว้างเพาส์จากออสซิลโลสโคป (รูปที่4 ข) และค่าความกว้างเพาส์ที่อ่านได้จาก Arduino (รูปที่4 ค) จากการทดลองพบว่า Arduino อ่านค่าความกว้างเพาส์ได้สอดคล้องกับการคำนวณและค่าที่อ่านได้จากออสซิลโลสโคป เป็นอย่างดี

                      รูปที่ 4 แสดงความถี่ รูปสัญญาณ และความกว้างเพาส์ที่ความถี่ 25 Hz

สำหรับที่ความถี่ต่ำ พบว่าค่าที่อ่านได้จาก Arduino ไม่ตรงกับผลการคำนวณและค่าที่อ่านได้จากออสซิลโลสโคป ซึ่งความผิดเพี้ยนส่วนนี้จะได้ทำการทดลองและทดสอบต่อไปเพื่อหาความคลาดเคลื่อนที่เกิดขึ้น


                   รูปที่ 5 แสดงความถี่ รูปสัญญาณ และความกว้างเพาส์ที่ความถี่ 5 Hz

เมื่อทดสอบวัดความกว้างเพาส์เพื่อนำมาเทียบกับผลการคำนวณเชิงทฤษฎี ได้ผลลัพธ์ดังนี้พบว่าที่ความถี่สูงขึ้น (กรณีนี้ f สูงกว่า 10 Hz) ฟังก์ชัน pulseIn จะวัดได้ถูกต้อง แสดงดังกราฟด้านล่าง

 รูปที่ 6 ภาพซ้ายมือแสดง กราฟระหว่างความกว้างเพาส์ (positive pulse width) กับความถี่ โดยเปรียบเทียบระหว่าง ค่าการคำนวณกับการทดลอง ส่วนภาพด้านขวามือแสดง เปรียบเทียบผลต่างของ pulse width กับความถี่

ภาพที่ 6 บ่งบอกความคลาดเคลื่อนของฟังก์ชัน pulse in พบว่าความถี่ต่ำกว่า 10 Hz จะทำให้ความคลาดเคลื่อนมีค่ามากขึ้น  ดังนั้นจากผลการทดลองนี้การอ่านค่าความถี่ต่ำไม่ควรใช้ฟังก์ชัน pulseIn

นอกจากนี้ข้าพเจ้ายังนำ pusleIn ฟังก์ชัน ไปเขียนรวมกับวิธีการส่งข้อมูลไปให้ MS excel มีรายละเอียดของ source code ดังนี้ (การส่งข้อมูลถึง MS excel ลองติดตามในนี้เลย Arduino2Excel)

#include
LiquidCrystal lcd(8, 9, 4, 5, 6, 7);

int x = 0;
int row = 0;
int pin = 3;
unsigned long duration;

void setup()

{
  Serial.begin(128000); // opens serial port, sets data rate to 9600 bps
  Serial.println("CLEARDATA");
  Serial.println("LABEL,Time,Index,SensorValueA0");
  lcd.begin(16, 2);
  lcd.clear();
  lcd.setCursor(0, 0);
  lcd.print("UP VeloMeterV1.0");
  delay(2500);
  pinMode(pin, INPUT);
}

void loop()

{
    row++;
    x++;

    Serial.print("DATA,TIME");
    Serial.print(","); Serial.print(x);
    duration = pulseIn(pin, HIGH, 10000000);
    float time=duration/1000.00;
    Serial.print(","); Serial.println(time);
    lcd.setCursor(0, 1);lcd.print("DelT=         ms");
    lcd.setCursor(6, 1);lcd.print(time);
}


และได้ผลดังภาพด้านล่างนี้ (ส่วนรายละเอียดจะมาเล่าให้ฟังในโอกาสต่อไป)



Saturday, February 21, 2015

How to send data from Arduino to Python

จากบทความที่แล้ว Arduino+MS Excel ที่ว่าผมมีความคิดอยากส่งข้อมูลไปให้ MS Excel ซึ่งทำไปแล้วเอาไปใช้งานให้เกิดประโยชน์มาแล้ว ถึงตอนนี้ผมเริ่มสนุกเลยคิดลองติดต่อผ่าน Python ดูบ้าง นี่เป็นครั้งแรกเลยแต่มาขอปักธงไว้ก่อน ทั้งนี้ไม่มีเหตุผลอื่นครับ แต่มันเป็นเพราะเป็นเรื่องใหม่สำหรับข้าพเจ้าอีกแล้ว (แต่อยากเล่นสนุกๆ) อย่างไรก็ดีผมจะเกาะติดตามคุณลุง Paul (ผมว่าแกเจ๋งดี) ในเวบนี้ครับ Technology tutorials    ผมยืม source code แกมาปรับนิดๆหน่อยๆ (แกคงไม่ทราบหรอก) ก็ใช้ได้เลยในการทดลองนี้ผมอ่านค่าจาก IR sensor ที่ขาอะนาลอก A0 ผลการทดลองมีหลักฐานดังภาพเลยครับ (ต้องขออภัยไม่สามารถลง source code ให้ได้เพราะพึ่งเริ่ม ถ้าผมเข้าใจอย่างดีแล้วจะนำมาลงและอธิบายในภานหน้า)
 


ส่วนบอร์ดผมใช้ Arduino Uno R3 และเซนเซอร์เป็นแบบสะท้อนครับ ดูจากรูปได้เลยครับ แต่นี่คือเริ่มต้นเท่านั้นครับ หลังจากบทความนี้ผมคงจะห่างหายไปสักระยะเพราะต้องไปฝึกใช้งานอย่างจริงจังก่อน


ขอขอบคุณ คุณลุง Paul (แกไม่รู้หรอก แต่จะเป็นครูออนไลน์ของผมแน่นอน)

http://www.toptechboy.com/using-python-with-arduino-lessons/

Tuesday, February 10, 2015

How to send data from Arduino to MS.Excel

           ผมไม่ค่อยคุ้นเคยกับการทำ data logger ไม่มีความรู้เลย ไม่มีเงินซื้อชอฟต์แวร์บางตัวที่มีลิขสิทธิ์ (ปัจจุบันใช้ชอฟต์แวร์ผ่านมหาวิทยาลัย) และมีความต้องจะเก็บข้อมูลจากบอร์ด Arduino UNO R3 (จริงๆ รุ่นไหนได้ทั้งนั้นขอให้สั่งมันเป็น) จึงตั้งคำถามกับตัวเองว่าจะส่งข้อมูลมาเขียนกราฟใน Excel ได้หรือไม่ ทำอย่างไร (ไม่รู้ทำไงครับ) เพราะส่วนตัวคิดว่าเครื่องคอมพิวเตอร์ที่ใช้ Windows ย่อมต้องมี Excel ติดไว้บ้างแน่นอนหากใช้โปรแกรมที่มีอยู่ในเครื่องอยู่แล้วน่าจะสะดวก ประหยัดเงิน จึงค้นหาจากพี่ google ปรากฎว่าชาวบ้านชาวเมืองเขาทำกันเยอะแยะครับ โปรแกรมหนึ่งที่น่าสนใจ มันทำหน้าที่เชื่อมต่อระหว่างบอร์ด Arduino กับ Computer เพื่อส่งข้อมูลไปยังโปรแกรม Excel โปรแกรมที่กล่าวถึงชื่อว่า PLX-DAQ เป็นของค่าย Parallax ก็ดาวโหลดมาแล้วติดตั้งใช้งานได้เลย (เครื่องที่ใช้เป็น Windows 7 (64-bit) MS2010 ) ผลการใช้งานดังรูปเลยครับ


การใช้งาน ต้องโปรแกรมลงในบอร์ด Arduino เพื่อสั่งให้ส่งข้อมูลผ่านโปรแกรม PLX-DAQ ก่อน โปรแกรมใช้งานที่ให้ผลลัพธ์ตามรูปด้านบนมีดังนี้

int x = 0;
int row = 0;

void setup() {
Serial.begin(128000); // opens serial port, sets data rate to 128000 bps
Serial.println("CLEARDATA");
Serial.println("LABEL,Time,Index,SensorValueA0,SensorValueA1,SensorValueA2,SensorValueA3");
}

void loop() {
  
  int sensorValue1 = analogRead(A0);
  int sensorValue2 = analogRead(A1);
  int sensorValue3 = analogRead(A2);
  int sensorValue4 = analogRead(A3);
  // Convert the analog reading (which goes from 0 - 1023) to a voltage (0 - 5V):
  float voltage1 = sensorValue1 * (5.0 / 1023.0);
  float voltage2 = sensorValue2 * (5.0 / 1023.0);
  float voltage3 = sensorValue3 * (5.0 / 1023.0);
  float voltage4 = sensorValue4 * (5.0 / 1023.0);
  
  // print out the value you read:

row++;
x++;  

Serial.print("DATA,TIME");
Serial.print(","); Serial.print(x); 
Serial.print(","); Serial.print(voltage1);
Serial.print(","); Serial.print(voltage2);
Serial.print(","); Serial.print(voltage3);
Serial.print(","); Serial.println(voltage4);

delay(1000);
}

ส่วนรูปด้านล่างแสดงรูปวงจรอย่างง่ายอ่านค่า voltage จากวงจรแบ่งแรงดันสำหรับการทดสอบในครั้งนี้ 



อธิบายเพิ่มเติม ผมรับค่าจาก Analog A0-A3 ซึ่งเป็นค่าความต่างศักย์ไฟฟ้าคงที่ทั้งสี่ขาสัญญาณ ดังนั้นไม่ว่าจะมีเซนเซอร์กี่ตัวก็ตาม สามารถรับค่าและเก็บไว้ใน Excel ได้หมดเลย ข้อจำกัดการรับสัญญาณของ PLX-DAQ น่าจะอยู่ที่ 26 ช่องสัญญาณ

สุดท้ายผมขอขอบคุณผู้ร่วมงาน ที่ให้คำแนะนำ และได้คำตอบทุกครั้งไป (มิต้องเอ่ยนาม เพราะท่านทั้งหลายรู้ในใจแล้ว)

แหล่งอ้างอิง (อ่านและฟังไม่รู้เรื่องแต่ ดูวิธีและแกะโปรแกรมจากเขา)

- ดูจากวีดิโอ ที่นี่ https://www.youtube.com/watch?v=LIMyz2GBW28
- ตามจากวีดิโอด้านบนที่บอกไว้ http://www.gioblu.com/tutorials/programmazione/189-arduino-e-i-grafici-in-tempo-reale-su-excel

Wednesday, August 13, 2014

DC Motor Speed Controller with Arduino

หลายคนอาจมองเป็นเรื่องง่าย แต่สำหรับผมแล้วเป็นเรื่องที่ท้าทาย (เพราะไม่มีความรู้ แต่จำเป็นต้องใช้เครื่องมือพร้อมๆ กับการพัฒนาขึ้นใช้เอง) ผมต้องการควบคุมความเร็วรอบของมอเตอร์ ในที่นี้ผมใช้ DC-motor ความเร็วตามระบุข้างตัวมอเตอร์ 5000 rpm กินไฟ 12 v, 7 W ผมใช้ IC ขับมอเตอร์เป็น L293d และใช้ Arduino Mega 2560 board และแสดงผลทาง LCD 2x16 ขอสรุป อุปกรณ์ที่ต้องใช้ดังนี้ครับ

1. Computer จำนวน 1 เครื่อง
2. Arduino MEGA 2560 จำนวน 1 บอร์ด
3. L293d จำนวน 1 ตัว
4. DC motor จำนวน 1 ตัว
5. 5k Potentiometer จำนวน 3 ตัว
6. 100 uF 25 v Capacitor จำนวน 1 ตัว
7. 1 nF Capacitor จำนวน 1 ตัว
8. 1N4007 diode  จำนวน 1 ตัว
9. Red-, Blue-LEDs  จำนวนอย่างละ 1 ตัว (แล้วแต่ชอบเรื่องสี)
10. Power supply 6.5 v, 1A adapter สำหรับ Arduino
11. Power supply 12 v, 1A adapter สำหรับ Motor driver
(สำหรับ power supply แล้วแต่ความถนัดครับใช้แบบไหนก็ได้ ใช้ adapter 2 ตัว ง่ายดีไม่ต้องทำเอง แต่ดูไม่มืออาชีพครับ)
12. Push switch จำนวน 2 ตัว พร้อมด้วย 220 โอห์ม จำนวน 2 ตัว

ความต้องการของผมคือ

1. สามารถปรับความเร็วรอบได้

2. สามารถกำหนดเวลา speed up ขณะเริ่มต้น และ slow down เมื่อสั่งหยุดการทำงาน

3. ตั้งเวลาหมุนได้

หลักการทำงานจึงถูกออกแบบไว้ประมาณนี้

- การกำหนดค่าต่างๆ จากผู้ใช้งาน กำหนดโดยการหมุนปรับ Potentiometer ส่วนนี้จะให้ Arduino รับค่า analog เข้ามา

โดยใช้คำสั่ง  "analogRead(PIN);"

- แปลงค่าที่รับมาให้ตรงกับ ADC ในบอร์ด และความต้องการของเรา

-นำค่าที่รับมาไปกำหนดการทำงานของมอเตอร์ และแสดงผลทางหน้าจอ LCD ด้วย

-มีปุ่มกด Start และ Abort เมื่อกดปุ่ม Start มอเตอร์เริ่มทำงานทันทีและหยุดตามเวลาที่ตั้งไว้ หากต้องหยุดการทำงานระหว่างทาง ผู้ใช้สามารถกดปุ่ม Abort ได้เลย

รูปด้านล่างเอามาให้ดูก่อนว่าช่วงแรกๆ ที่คนมือใหม่ทั้ง อิเล็กทรอนิกส์ programming Arduino จะยุ่งขนาดไหนครับ ท่านดูแล้วอาจบอกว่าไม่มืออาชีพ (แอบด่าตัวเองแล้วกัน) แต่ผมคิดในใจว่า ขอแค่ผมเข้าใจมันและกำนหดให้มันทำงานตามที่ต้องการก็เพียงพอ สำหรับสถานะต้นๆ นี้ ความฉลาดค่อยพัฒนาในขั้นถัดไปก็แล้วกัน


ในรูปใช้ PWM ใน Arduino board จากนั้้นปรับค่า duty cycle จากตัวต้านทานปรับค่าได้ ทำให้ความเป็น DC มากหรือน้อยเพื่อไปกำหนดอัตราเร็วของ DC motor เบื้องต้นปรับความเร็วได้แต่ยังไม่ทราบว่า เท่าไร และมีปุ่มกด stop หยุดการทำงานของมอเตอร์ได้ (ในรูปนี้ผมยังใช้ Arduino UNO R3 อยู่นะครับ ตอนหลังเปลี่ยนเป็น Mega เพราะต้องการใช้พอร์ตเยอะขึ้น [ตามความเข้าใจของข้าพเจ้า])

ขั้นต่อไปที่ต้องการคือ

1. กำหนดค่า อัตราเร็ว เวลาในการหมุน เวลาสำหรับ speed up และ slow down จากผู้ใช้ (เพื่อรักษาและปรับการเคลื่อนที่ให้นุ่มนวล เพราะกฎของนิวตันทำงานเสมอ ต้องระวัง)

2. เพื่อปุ่ม Abort ในกรณีที่ผู้ใช้อยากให้มอเตอร์หยุดระหว่างการทำงาน

3. แสดงค่าที่ผู้ใช้กำหนดบน 16x2 LCD

4. หาวิธีวัดอัตราเร็วรอบ และป้อนค่ากลับเพื่อให้มอเตอร์หมุนเร็วเท่ากับค่าที่ผู้ใช้ตั้งไว้ (ข้อนี้คงอีกนาน แต่เห็นวิธีนึงละคือ PID control)


ส่วนรูปการต่อที่ดูง่ายจะเอามาให้ดูต่อไป แต่ไปหัดใช้โปรแกรมวาดวงจรก่อนนะ .......ผ่านไปหลายวันด้วยความขี้เกียจ เอาเป็นว่าผมขอยกวงจรที่จำลองในโปรแรกม Proteus มาให้ดูแล้วกันครับ ส่วนวงจรที่เป็น Schematic ค่อยว่ากัน รูปดังต่อไปนี้ผมใช้ Arduino Mega 2560 ครับ ซึ่งจะสอดคล้อง source code ที่เขียนใน sketch เลย ผมทดสอบแล้วทั้ง Hardware และ Software รวมไปถึงการจำลองใน Proteus ตามภาพครับ


ในรูปนี้แสดงการจำลองการทำงานของ DC motor สัญญาณเส้นสีเหลืองคือ PWM ที่ออกจาก Ardunio ส่วนเส้นสีฟ้าคือสัญญาณที่ออกจาก Motor driver IC, L293d ครับ 

อธิบายการทำงานสักนิดครับ

1. ผู้ใช้สามารถกำหนดค่าได้ดังต่อไปนี้ผ่าน ตัวต้านทานปรับค่าได้ "อัตราเร็ว รอบต่อนาที" "เวลาสำหรับ speed up และ slow down" "เวลาที่ต้องการให้มอเตอร์หมุน" Arduino จะรับค่าผ่าน analogRead แล้วแปลงเป็นข้อมูล 8-bit

2. Arduino จะรับค่า Digital เพื่อสั่งงานให้ มอเตอร์ทำงาน (กดปุ่ม Start) หรือหยุดทำงาน (Stop) ผ่าน สวิทซ์

3. หากผู้ใช้ต้องการหยุดมอเตอร์ ขณะที่ยังทำงานอยู่ สามารถกดปุ่ม Stop ได้ทันที Arduino จะรับค่าผ่าน Interrupt port (ส่วนนี้ทำอยู่นานเพราะไม่เข้าใจและหัดใช้เป็นครั้งแรก)

และนี่คือ source code ที่เขียนใน sketch ครับ แต่ยังไม่ได้ทำให้มันฉลาดนะครับ ยังขาดส่วนรักษาอัตราเร็ว เมื่อมีโหลดเข้ามา เทคนิคที่เล็งไว้คือ PID ซึ่งมีหลายที่พัฒนาไว้เรียบร้อยแต่สมองอันจำกัดของผมยังไม่เข้าใจ ลองไปดูกันครับ >>> PID

//%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%//
//------------------ UP-Spin_coater -------------------------//
//-----------------Designed by S.Unai -----------------------//
//-----Microfluidics and Image Processing Research Unit -----//
//------------- University of Phayao, Thailand --------------//
//----------------------- 2014 ------------------------------//
#include
//--------- LCD setPIN ------------//
LiquidCrystal lcd(8, 9, 4, 5, 6, 7);
//%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%//
int Ramp_time, Spin_speed, Spin_time, DynamicSpinTime,ii;
float ShowSpin_speed;
int Start, Stop;
volatile int State = HIGH;
int motor        = 3;     // PWM pin output signal send to motor
int SpinSpeedPIN = A8;    // Potentiometer Spin-speed adjustment by USER
int TimeDelayPIN = A9;    // Potentiometer Spin-time adjustment by USER
int SpinTimePIN  = A10;   // Ramp and Delay time for spin
int SwitchStart  = 31;    // Digital pin31= push Start switch
int SwitchAbort  = 20;    // Interrupt PIN
//%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%//
void setup()
{
  pinMode(motor,OUTPUT);
  pinMode(SpinSpeedPIN,INPUT);
  pinMode(SpinTimePIN,INPUT);
  pinMode(TimeDelayPIN,INPUT);
  pinMode(SwitchStart,INPUT);
  pinMode(SwitchAbort,INPUT);
  //Serial.begin(9600);
  //-----------------Set LCD ---------------//
  lcd.begin(16, 2);
  // Print a message on the specific pixels of LCD panel.
  lcd.setCursor(0, 0);  lcd.print("UP-SpinCoater V1.0");
  delay(3000);
  lcd.clear();
 
  lcd.setCursor(8,0);   lcd.print("Rp");
  lcd.setCursor(10,1);  lcd.print("s");
  lcd.setCursor(0,0);   lcd.print("RPMset");
  lcd.setCursor(12,0);  lcd.print("Time");
  lcd.setCursor(15,1);  lcd.print("s");
 
  // Interrupt action
  attachInterrupt(1, AbortAction, CHANGE);
}
//%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%//
void loop()
{
//=======================Input Unit================================//
  Start = digitalRead(SwitchStart); 
  Stop  = digitalRead(SwitchAbort);
  if (Stop==1)// pull up ==0, pull down=1
  {
    State=LOW;
  }
  //-------------------------------------
  Ramp_time  = analogRead(TimeDelayPIN);
  Ramp_time  = map(Ramp_time,0,1023,0,255);
  Ramp_time  = ((10*Ramp_time)/255);
  //-------------------------------------
  //analogReadResolution(8);
  Spin_speed = analogRead(SpinSpeedPIN); //Spin_speed  = map(Spin_speed,0,1023,0,255);
  Spin_speed = map(Spin_speed,0,1023,0,255);//((10000*Spin_speed)/255);
  //-------------------------------------
  Spin_time  = analogRead(SpinTimePIN);
  Spin_time  = map(Spin_time,0,1023,0,255);
  Spin_time  = (2.58823529411765)*Spin_time; // 1.17647058823529==>300, 2.352941176==>600 s,3.92156862745098 ==> 1000 s

  ShowSpin_speed = Spin_speed;//17.6470588235294*Spin_speed;
 
//=======================Speed Control Unit=======================//
analogWrite(motor,0);  //Set initialize motor on stop mode
ii = Spin_time;

if (Start==1 && Stop==0) // pull up= 0 1, pull down= 1 0
   {
     for (int a=1; a <= 10; a++) // Speed Up
        {
            analogWrite(motor,(0.1*a*Spin_speed));
            delay(Ramp_time*100);         
        }      
     for (int b=1; b <= Spin_time; b++) // Constant Speed
        { 
            if (Stop==1)// pull up=0, pull down= 1; check Abort push button //get Stop value from interrupt function
              {                      
                  break; //Kill the constant velocity for-loop
              }
                  //delay(100);
                 
            ii = ii--;
            analogWrite(motor,Spin_speed);
            delay(1000);
                  if(ii<10 10="" br="">{
                           lcd.setCursor(12, 1);lcd.print(ii);
                           lcd.setCursor(13, 1);lcd.print("  ");
                       }
                  else if(ii>=10 && ii<100 100="" br="">{
                           lcd.setCursor(12, 1);lcd.print(ii);
                           lcd.setCursor(14, 1);lcd.print(" ");
                       }  
                  else {
                           lcd.setCursor(12, 1);lcd.print(ii);
                       }
                                           
        }    
      for (int c=10; c <=1; c--) // Speed Down
        {
            analogWrite(motor,(0.1*c*Spin_speed));
            delay(Ramp_time*100);                     
        }        
   
  }
else
     {
       digitalWrite(motor,0);
     }  
  
//==================== Display Unit ==============================// 
        if(Ramp_time<10 br=""><10 br=""> <10) {
<10 10="" br=""><100 100="" br=""><10 br=""><10 br="">                lcd.setCursor(8, 1);lcd.print(Ramp_time);
                lcd.setCursor(9, 1);lcd.print(" ");
              }
        else    
              {
                lcd.setCursor(8, 1);lcd.print(Ramp_time);
              }
//----------------------------------------------------------------//            
              // Show spin speed
              lcd.setCursor(0, 1);
              lcd.print(ShowSpin_speed);            
//---------------------------------------------------------------//

        if(Spin_time <10)<10 br="">               
<10 10="" br=""><100 100="" br=""><10 br=""><10 br=""><10 br="">               lcd.setCursor(12, 1);lcd.print(Spin_time);
               lcd.setCursor(13, 1);lcd.print("  ");
             }
          else if(Spin_time>=10 && Spin_time<100 100="" br="">{
               lcd.setCursor(12, 1);lcd.print(Spin_time);
               lcd.setCursor(14, 1);lcd.print(" ");
             }  
          else {
               lcd.setCursor(12, 1);lcd.print(Spin_time);

             }           
    }

//%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%//
void AbortAction()
{
     State=!State;
     Stop=1; //pull up=0, pull down=1; sending this value to main for-loop    
}


ขยายความเรื่อง interrupt (ตามความเข้าใจของข้าพเจ้า) Arduino Mega2560 มี interrupt ports ดังนี้ครับ รายละเอียดติดตามได้ที่ arduino.cc ผมใช้ขา 20
Mega25602321201918

ในโปรแกรมที่ผมเขียนจะใช้ ตัวแปรชื่อ state เป็นตัวกำหนดการเปลี่ยนแปลงเพื่อให้ Arduino รับรู้ผ่านฟังก์ชัน attachInterrupt ทางขา interrupt port

การใช้งานประมาณนี้เลยครับ

// Interrupt action
  attachInterrupt(3, AbortAction, CHANGE);

// AbortAction คือฟังก์ชันที่เขียนขึ้นมาครับ มีรายละเอียดตามนี้
void AbortAction()
{
     State=!State;
//กำหนดให้กลับไปสถานะเดิมเพื่อรอรับค่าการเปลี่ยนแปลงครั้งต่อไป
     Stop=1;
// sending this value to main for-loop      ส่งค่า Stop เท่ากับ 1 ไปให้ฟังก์ชันหลักทำงาน (ในที่นี้คือสั่งให้มอเตอร์หยุดหมุน)
}


ซึ่งในคำสั่งจะเขียน if condition ไว้ว่าหลังจากที่ทำงานไปแล้ว (start==1 และ stop==0) หากเมื่อไรก็ตามถ้าตรวจพบว่า stop==1 ให้หยุด for loop ทันทีโดยใช้คำสั่ง break


สุดท้ายครับ หากท่านใดเข้ามาเห็นแล้วจะเอาไปใช้ก็ไม่ว่ากระไร หากเอาไปพัฒนาต่อกรุณาเอามาแชร์ให้ผมด้วย เพราะผมไม่มีความรู้จริงๆ ตอนนี้ที่ศึกษาผมก็จะหมกมุ่นกับวิธีการของตัวเอง จริงๆ มันอาจจะมีวิธีที่ฉลาดกว่านี้ก็ได้เอามาแบ่งปัน ชี้แนะให้กระผมด้วยนะครับ

มาอัพเดทข้อมูลกันนิดหน่อย เรื่องการรักษาระดับอัตราเร็วรอบ ของมอเตอร์ พบว่าที่เวบ arduino.cc  มีคนพัฒนา PID library ให้ใช้แล้วครับ แต่ส่วนตัวยังไม่มีเวลามากพอที่จะลอง แต่ผมได้นำโปรแกรมพร้อม บอร์ด Arduino ที่พัฒนาในงานนี้ไปใช้กับงานอื่นได้ด้วยนะครับ ประเด็นก็คือ การพัฒนาด้วยตัวเองมันจะมีผลพลอยได้จากเทคโนโลยีที่เราพัฒนา อีกหลายอย่างตามมาที่นำไปประยุกต์กับอะไรก็ได้ ความรู้ที่แตกออกมาก็เช่นกัน สามารถนำไปช่วยคนอื่นได้เช่นกัน โอกาสต่อไปคงได้นำผลงานอื่นมาลงอีก (ที่เป็นผลพวงจาก เรื่องนี้)