1/1/62

Arduino เบื้องต้น

อบรม 16-17 พฤศจิกายน 2562 วิทยากรโดย อาจารย์ชื่อ มานพ ปักษี
เริ่มต้นจากภาษา c++ จากโครงสร้างที่เราๆ เคยเขียนกันคือ
#include <iostream>int main(){    std::cout << "Hello World!\n";    return 0;}
ซึ่งใน Arduino ที่เราใช้งานกันจะมีหน้าตาคล้ายๆกันคือ
#include <Wire.h>#include "my.h"void setup(){     Serial.println("Hello World!");}void loop(){
}
ที่มีความต่างกันก็เพราะว่าทางผู้พัฒนา Arduino IDE ได้ช่วยอำนวยความสะดวกด้วยการแยกในส่วนของการทำงานในครั้งเดียวออกมา เพื่อให้เรากำหนดพวกค่าเริ่มต้นต่างๆ หรืองานที่จะต้องทำในครั้งเดียวไว้ในส่วนของ setup()
อ้อ...ลืมกล่าวในส่วนของ include ในส่วนเริ่มต้นของภาษาจะเรียกส่วนนี้ว่า Preprocessor Directive ซึ่งในส่วนนี้จะเอาไว้ใช้สำหรับการเรียก library และการประกาศตัวแปรต่างๆ
ในส่วนของการ include มีอยู่ 2 รูปแบบ
#include <> //เป็นการเรียกใช้ library จาก folder ของโปรแกรม Arduino#include "" //เป็นการเรียกใช้ library จาก folder ของโปรเจค
มาว่ากันด้วยเรื่องของ library จริงๆมันคือโปรแกรมเล็กๆที่ถูกเขียนขึ้นเพื่อความสะดวกแก่ผู้ใช้งาน นึกง่ายๆมันคือ driver ของ windows ที่เราๆต้องติดตั้งในเครื่องนั่นหละ เช่น driver printer สมมุติว่าถ้าไม่มี driver ของ printer ในเครื่องเรา แต่เราเสียบเครื่อง printer ไว้ แล้วเราใช้ word และอยากจะสั่งพิมพ์เอกสารที่เราพิมพ์ไว้ใน word ทางโปรแกรมเมอร์ของทาง microsoft office จะต้องเขียนโปรแกรมเพิ่มในส่วนของการติดต่อเพื่อสั่งให้เครื่อง printer พิมพ์เอกสารออกมาเอง และทุกโปรแกรมที่สามารถสั่งพิมพ์ได้จะต้องเขียนส่วนติดต่อนี้เอง ดังนั้นทางผู้ผลิต printer จึงพัฒนาโปรแกรม ที่เราเรียกติดปากกันว่า driver ขึ้นมาเพื่อให้แต่ละโปรแกรมสามารถสั่งพิมพ์ผ่าน driver ได้เลย ไม่ต้องไปเขียนโปรแกรมใหม่ (น่าจะพอมองภาพออกแล้ว) ที่ว่ามาข้างต้นคือ library ที่มากับ Arduino IDE
แต่ library ที่อยู่ใน folder ของโปรเจคคืออะไร มันคือโปรแกรมเล็กๆที่เราเขียนขึ้นเอง เพื่อแยกส่วนการทำงานออกมา เวลาแก้ไข เราไม่ต้องเปิดไฟล์หลักแล้วค่อยมองหาโปรแกรมเล็กๆเหล่านี้ ถ้าหากเราไม่แยกได้หรือไม่ คำตอบคือ “ได้” เพียงแค่เวลาเราเปิดเข้าโปรเจคอาจจะใช้เวลาหาชุดคำสั่งดังกล่าว และถ้าเรามีโปรเจคอื่น เราจะต้องมาเสียเวลามาเปิดหาร code ส่วนนี้จากโปรเจคเดิมทำให้เสียเวลา แต่ถ้าหากเราเขียนแยกไว้ก็แค่ copy ไฟล์ไปใส่ในโปรเจคใหม่ได้เลยแล้วค่อย #include “ชื่อไฟล์.h” แค่นี้เอง (พร่ำเพ้อเวิ่นเว้อจริงๆเรา 55+)
มาต่อๆ....มาว่าเรื่องของประกาศตัวแปรบ้าง (เอาคร่าวก็แล้วกัน)
#define led1 13 int led1=13;const int led1=13;
บรรทัดที่ 1 การ define เป็นการอ้างอิงค่า...อึ่ม...อธิบายแบบนี้ก็แล้วกัน มันเหมือน 13 คือคน 1 คน คนทุกคนก็ต้องมีชื่อ ดังนั้นการ define คือการกำหนดชื่อให้กับค่า 13 จากตัวอย่างคือ กำหนดชื่อ led1 ให้กับค่า 13 นั่นเอง (คล้ายๆการใช้งาน point ชี้จาก led1 ไปยังค่า 13 ทำให้เป็นการใช้ memory ที่น้อยลง) อ่ะ...อีกนิดการ define จะไม่สามารถเปลี่ยนค่า led1 ได้
บรรทัดที่ 2 คือการสร้างตัวแปรขึ้นมา เป็นตัวแปรชนิด integer ที่ชื่อว่า led1 และให้ led1 เก็บค่า 13 ไว้ในตัวแปร
บรรทัดที่ 3 คือการสร้างตัวแปรขึ้นมา เป็นตัวแปรชนิด integer ที่ชื่อว่า led1 และให้ led1 เก็บค่า 13 ไว้ในตัวแปร แต่ไม่สามารถกำหนดค่าใหม่ให้กับตัวแปร led1
การตั้งชื่อตัวแปร
  1. ต้องขึ้นต้นด้วยตัวอักษรเป็นตัวเล็ก/ใหญ่ได้หมด
  2. สามารถผสมได้ทั้งตัวอักษร(เล็ก/ใหญ่)และตัวเลข
  3. การเรียกใช้งานจะต้องพิมพ์ตามชื่อตัวแปรที่ตั้งไว้ เล็ก/ใหญ่ ตัวเลข ต้องถูกต้องตามที่ตั้งไว้
ปล1. การ include และการ define จะต้องอยู่ก่อนการกำหนดตัวแปรแบบอื่นๆเสมอ
ต่อมา จากตัวอย่างที่ผมพิมพ์มาก่อนหน้านี้จะเห็น // ไว้ก่อนที่ผมจะอธิบายใช่หรือเปล่า // ที่เห็นคือแท็กคอมเมนท์นั่นเอง ถ้ามี // นำหน้าตรงไหนหลังจากนั้นจะคอมเมนท์ทั้งหมดจนหมดบรรทัด และจะไม่ถูกนำไปประมวลผล แต่ก็มีแท็กคอมเมนท์อีกแบบเหมือนกัน คือ
//จะเป็นคอมเมนท์จนจบบรรทัด
/**แบบนี้จะเป็นคอมเมนท์ทั้งแต่แท็กเปิด จนปิดจะกี่บรรทัดก็ได้ขึ้นกับเรา*/
ถ้าหากสังเกตภาษา c++ แบบปกติ และแบบที่ใช้ใน Arduino IDE จะมี function เริ่มต้นไม่เหมือนกัน
int main(){
}
แบบข้างบนนี้คือ c++ ปกติ
void setup(){
}void loop(){
}
ส่วนตัวนี้คือใน Arduino IDE ซึ่งจริงๆแล้วใน Arduino IDE จะเรียกใช้งาน function setup() และ loop() จากไฟล์ C:\Program Files (x86)\Arduino\hardware\arduino\avr\cores\arduino\main.cpp
#include <Arduino.h>
int atexit(void (* /*func*/ )()) { return 0; }
void initVariant() __attribute__((weak));void initVariant() { }
void setupUSB() __attribute__((weak));void setupUSB() { }
int main(void){ init();
 initVariant();
#if defined(USBCON) USBDevice.attach();#endif  setup();     for (;;) {  loop();  if (serialEventRun) serialEventRun(); }         return 0;}
จะเห็นว่าจริงๆแล้ว Arduino IDE ช่วยให้เราใช้งานง่ายขึ้น แยกส่วนง่ายขึ้นเท่านั้นเอง
แล้วเราสามารถสร้าง function ขึ้นมาใช้งานเองได้หรือไม่ ได้สิครับ!!!
void setup(){    Serial.begin(9600);    led_on1();    int y=led_on2(3);    Serial.println(y);}void loop(){
}void led_on1(){     //โปรแกรมย่อย     Serial.println("Test Function");}
int led_on2(int x){     //โปรแกรมย่อย     x=x+1;     return x;}
ถ้าดูจากใน setup() จะเห็นว่ามีการเรียกใช้งาน function led_on1 และ led_on2 แต่ทั้ง 2 แบบเรียกใช้ไม่เหมือนกัน โดยที่ led_on1 จะไม่มีการส่งค่าใดๆไป และมีแค่สั่งพิมพ์ Test function ออกทาง Serial แต่ led_on2 มีการส่งค่า 3 ไปยัง function และมีการส่งค่า x กลับมาโดยเราสร้างตัวแปร y เป็นตัวแปรชนิด integer รับค่า x จาก function
หากสังเกตข้อแตกต่างระหว่าง 2 function
void led_on1(){...} //void คือการกำหนดว่า function นี้จะไม่มีการส่งค่าอะไรกลับออกไปint led_on2(...){...} //int คือการกำหนดว่า function นี้จะมีการส่งค่ากลับเป็นค่าชนิด integer
ปล.2 ในการเขียนโปรแกรม หากเขียนคำสั่งเสร็จ เช่น “x=x+1;” หรือ “Serial.println("Test Function");” เป็นต้น จะต้องมี ; ปิดบรรทัดเสมอ
ปล.3 ในการเขียน function จะต้องมี “{”เพื่อบอกว่าเป็นจุดเริ่ม function และมี “}” เพื่อบอกว่าเป็นจุดสิ้นสุด function
มาลองสั่งงานไฟกระพริบกันเถอะ เริ่มจาก c++ ในส่วนของ Arduino IDE ก่อน โดยเชื่อมต่อหลอดไฟ led โดยต่อขา + ไปที่ขา 13 ไว้ (อย่าลืมต่อ r นะ หลอดขาดไม่รู้ด้วย) และอีกขาลงไปที่ gnd (ground)
void setup() {  pinMode(13, OUTPUT); //กำหนดให้ขา 13 เป็นขา output}
void loop() {  digitalWrite(13, HIGH); //สั่งให้จ่ายไฟออกที่ขา 13  delay(100);             //สั่งหน่วงเวลา 100 มิลลิวินาที  digitalWrite(13, LOW);  //สั่งให้งดจ่ายไฟออกที่ขา 13  delay(100);}
ทีนี้ลองมาดูแบบภาษา c++ แบบปกติบ้าง
#include <util/delay.h>int main(){  DDRB = 0x20;   //กำหนดให้ port B ขา 5 เป็นขา output  while(1){    PORTB |= (1<<PORTB5);   //สั่งให้จ่ายไฟออกที่ขา PB5     _delay_ms(100);         //สั่งหน่วงเวลา 100 มิลลิวินาที    PORTB &= ~(1<<PORTB5);  //สั่งให้งดจ่ายไฟออกที่ขา PB5    _delay_ms(100);    }  }
มาดู DDRB = 0x20; หน่อยหละกัน DDRB คือสั่งให้ registor ในส่วนของ port B เตรียมรับการกำหนดเป็น input และ output ในตำแหน่งของขาใดบ้าง....มาอธิบายต่อในส่วน 0x คือจะบอกตำแหน่งขาเป็นตัวเลขฐาน 16 ซึ่ง 20 ต้องแยกออกเป็นแบบนี้ 20 คือเลขฐาน 16 ซึ่งเวลามันสั่งงานจะเป็นเลขฐาน 2 หน้าตาแบบนี้ 2 = 0010 และ 0=0000 แล้วนำมาต่อกันเป็นแบบนี้ 00100000 ลองนับจากด้านขวามือสุด โดยกำหนดให้ขวามือสุดคือตำแหน่งของขาเริ่มต้นจากตำแหน่งที่ 0 โดยเลข 1 หมายถึงเปิดขาเป็น output และ 0 เป็น input จะเห็นว่าตำแหน่งของเลข 1 คือตำแหน่งที่ 5 นั่นคือ PB5 นั่นเองและกำหนดให้เป็นขา output แล้วถ้าอยากจะสั่งเป็นตำแหน่งขาแบบฐาน 2 หละ ก็เปลี่ยนจาก 0x20 เป็น 0b000100000 แบบนี้
งงกันไม๊หละ คือถ้าจะสั่งแบบปกติ เราจะต้องไปเปิด datasheet ของ ATmega328P เอ้า...แล้วมันคืออะไร มันคือ microcontroller ที่อยู่ใน Arduino UNO R3 นั่นเอง (แต่ถ้าใช้ Board อื่นก็ไปตามดูเอาหละกันครับ) เอางี้มาดู pinout ของ Arduino UNO R3 เอาก็แล้วกัน ไปดู datasheet แล้วเดียวงง ตอนแรกผมก็งงๆ เหมือนกัน ตอนนี้งงไม่ งงเหมือนเดิม 55+ ต่อๆ
keyword google : arduino uno r3 pinout
ลองสังเกตที่ขาที่เค้าทำสีม่วงที่เขียนว่า 13 มันคือขา 13 ที่เราอ้างใน Arduino IDE ส่วน PB5 ที่เค้าทำสีเหลืองไว้ มันคือตัวเดียวกัน แต่ต้องไปอ้างใน c++ ปกติ พูดง่ายๆ Arduino IDE พัฒนา library เพื่อให้เราใช้งานง่ายๆ นั่นเอง น่าจะเริ่มเข้าใจขึ้นเยอะแล้วมั๊ง
ตอนนี้เราสามารถสั่งไฟกระพริบได้แล้ว ต่อมาเรามาลองสั่งเปิดปิดผ่านปุ่มกันบ้าง เริ่มต้นจากกดติดปล่อยดับก่อนหละกัน
#define led1 13#define btn 2void setup(){  pinMode(led1,OUTPUT);   pinMode(btn,INPUT_PULLUP); //ใช้ r ภายในระบบของ MCU และไม่มี PULLDOWN ใน board  }void loop(){  if(digitalRead(btn)==LOW){    digitalWrite(led1,HIGH);    }else{    digitalWrite(led1,LOW);      }  }
ต่อ led ขาเดิม แบบเดิม (ถ้าหลอดขาดหรือไม่ติด แสดงว่าต่อผิด 55+) ต่อปุ่มกัน ขาข้างนึงต่อ 5v อีกข้างต่อลงขา 2 ทีนี้ลองสังเกตที่ pinMode จะเห็นว่าไม่ได้พิมพ์ว่า INPUT เฉยจริงปะ แต่กำหนดเป็น INPUT_PULLUP หละแล้วมันคืออะไร ปกติถ้าเราจะต่อแบบ pull up จะต้องต่อแบบภาพด้านล่าง
ถ้าจะต่อแบบ pull up เอง
แต่ใน Arduino UNO R3 นั้นมี pull up ให้ใช้งานอยู่แล้วเพราะงั้นเราไม่ต้องต่อ R1 ตามภาพ มี pull up เพราะฉนั้นเมื่อต่อแบบ pull up สถานะปุ่มหากไม่กดจะเป็น HIGH ถ้ากดจะเป็น LOW แล้วมีการต่อแบบ pull down ด้วยตามภาพข้างล่าง และถ้าต่อแบบ pull down สถานะหากไม่กดปุ่มจะเป็น LOW ถ้ากดจะเป็น HIGH
เปรียบเทียบการต่อแบ pull up กับ pull down
แล้วมีคำสั่ง INPUT_PULLDOWN ไม๊ คำตอบคือ “ไม่มี!!!” (อย่าไม่สั่งมันนะ เดียวมันจะ error)
เมื่อกี้เราลองกดติดปล่อยดับไปแล้ว มาลองแบบกดติด กดดับก็แล้วกัน (นึกถึงสวิทซ์ไฟบ้าน)
#define led1 13#define btn 2     
int btnState = 1;         //สถานะปุ่มใหม่int lbtnState = 1;        //สถานะปุ่มเดิมvoid setup() {  pinMode(led1, OUTPUT);  pinMode(btn, INPUT);}
void loop() {  btnState = digitalRead(btn);  if (btnState != lbtnState) {    if (btnState == LOW) {        digitalWrite(led1, HIGH);    }else{      digitalWrite(led1, LOW);    }  lbtnState = btnState; //กำหนดให้สถานปุ่มใหม่ครั้งนี้เป็นสถานะปุ่มเดิม}
ต้องบอกแนวความคิดก่อน เราจะต้องมีการเปรียบเทียบสถานะว่ามีการกดหรือไม่ โดยเทียบระหว่างสถานะปุ่มใหม่กับสถานะปุ่มเดิม ถ้ามีการเปลี่ยนค่าแสดงว่ามีการกด ก็จะประมาณนี้
ปล4. HIGH = 1 และ LOW = 0 แล้วทำ HIGH กับ LOW มาทำไม เพื่อให้ผู้ใช้แยกออกง่ายขึ้นว่าสั่งให้ไฟเปิดหรือปิด เท่านั้นเอง เพราะถ้า 1 หรือ 0 มือใหม่อาจจะสับสนได้ว่าเป็นการกำหนดค่าหรือสั่งเปิดปิดไฟ
ทีนี้แล้วถ้าเรามีขาที่ต่อ led หลายขาหลายหลอดหละ สมมุติสัก 11 หลอด โดยต่อตั้งแต่ขา 3 ถึง 13 จะต้องเขียนยังไงหละ หรือว่าต้องมาแบบนี้
#define led1 3#define led2 4...#define led11 13
void setup(){    pinMode(led1,OUTPUT);    pinMode(led2,OUTPUT);...    pinMode(led11,OUTPUT);}void loop(){    digitalWrite(led1,HIGH);    digitalWrite(led2,HIGH);...    digitalWrite(led11,HIGH);    delay(1000);    digitalWrite(led1,LOW);    digitalWrite(led2,LOW);...    digitalWrite(led11,LOW);    delay(1000);}
แบบนี้ก็เหนื่อยตายกับแค่สั่งไฟกระพริบ งั้นลองดูแบบนี้
const int led_run[]={13,12,11,10,9,8,7,6,5,4,3};void setup() {  for (int i=0;i<11;i++){    pinMode(led_run[i], OUTPUT);  }}
void loop() {  for (int i=0;i<11;i++){    digitalWrite(led_run[i], HIGH);  }  delay(1000);   for (int j=0;j<11;j++){    digitalWrite(led_run[j], LOW);  }  delay(1000);}
เป็นไง สั้นกว่าไม๊ แล้ว led_run[] คืออะไร คือการสร้างตัวแปรแบบ array นั่นเอง array สามารถทำให้ 1 ตัวแปรเก็บค่าได้หลายค่า แล้วใช้วิธีการ led_run[ตำแหน่งการเก็บ] โดยที่ตำแหน่งการเก็บจะเริ่มจากตำแหน่งแรกคือ 0 ไล่ไปเรื่อยๆ แล้วถ้าเรา led_run[5] นั่นคือตำแหน่งลำดับที่ 6 ที่เก็บเลข 9 นั่นเอง แต่ถ้าตอนสร้างตัวแปร array ตอนแรกเรากำหนดไว้แบบนี้ led[9] หมายความว่าเป็น array ขนาด 9 ตำแหน่ง โดยมีตำแหน่ง 0 1 2 ... 8 รวม 9 ตำแหน่ง
อธิบายต่อ คำสั่ง
for(int i=0;i<11;i++)
  • for คือการสั่งให้วนทำงานตามจำนวนรอบที่กำหนด
  • int i=0 คือสร้างตัวแปรนับจำนวนรอบ
  • i<11 คือการจะวนเข้าไปทำงาน i จะต้องน้อยกว่า 11 เท่านั้น
  • i++ คือ i จะเพิ่มทีละ 1 เมื่อจบรอบนั้นๆ
จากคำสั่งดังกล่าวแสดงว่าจะวนการทำงานจำนวน 11 รอบโดยเริ่มจาก 0-10 จากนั้นจบการทำงาน
นอกจาก for แล้วเรายังมีคำสั่งอื่นๆที่สามารถใช้ในการวนลูปการทำงานได้
  • for เช็คเงื่อนไขก่อนค่อยทำงาน จากนั้นจึงเพิ่มค่า
  • do...while ทำงานก่อนค่อยเช็คเงื่อนไข จะทำต่อก็ต่อเมื่อเงื่อนไขเป็นจริง
  • while เช็คเงื่อนไขเป็นจริงก่อนจึงทำ
คำสั่งไฟกระพริบยังดูยาวไปเน๊อะสำหรับไฟกระพริบ งั้นลองลดดู
const int led_run[]={13,12,11,10,9,8,7,6,5,4,3};void setup() {  for (int i=0;i<11;i++){    pinMode(led_run[i], OUTPUT);    }}void loop() {  for(int i=0;i<11;i++){    digitalWrite(led_run[i], !digitalRead(led_run[i]));    delay(1000);    }}
งงมะ digitalRead(led_run[i]) คือการอ่านค่าของขาว่าตอนนี้เป็น HIGH หรือ LOW แล้ว ! คือ NOT หรือการกลับค่านั่นเอง สมมุติว่าอ่านขาได้ค่า HIGH จะทำการ NOT HIGH นั่นคือให้ค่าเป็น LOW นั่นเอง...แต่เริ่มขี้เกียจมากำหนดจำนวนรอบจัง เช่น led_run เก็บค่าไว้สัก 5 ตัวไม่ถึง 11 เหมือนเดิมหละ แสดงว่าเราต้องมาแก้ค่า i<11 เป็น i<5 ใช่ไม๊...คำตอบคือใช่ แต่ผมมีวิธีที่แก้สำหรับคนขี้เกียแบบผม
const int led_run[]={13,12,11,10,9,8,7,6,5,4,3};const int leng=sizeof(led_run) / sizeof(int);void setup() {  for (int i=0;i<leng;i++){    pinMode(led_run[i], OUTPUT);    }}void loop() {  for(int i=0;i<leng;i++){    digitalWrite(led_run[i], !digitalRead(led_run[i]));    delay(100);    }}
ดูนี่ leng=sizeof(led_run) / sizeof(int);
แล้ว sizeof คือการอ่านขนาดของตัวแปร ไอเดียคือ อ่านขนาดของตัวแปร array led_run ซึ่งเป็นตัวแปร integer และอ่านค่าตัวแปรที่เป็น integer แบบ 1 ตัว เอาทั้ง 2 มาหารกัน ก็จะได้จำนวนที่อยู่ใน led_run นั่นเอง เท่ากับว่าเราจะแก้ led_run ให้เก็บกี่ตัวก็ได้ แล้วเราก็ไม่ต้องไปแก้ i<11 หรือ i<5 แบบก่อนหน้านี้...อิอิ
ปล.5 คำสั่ง delay จะทำให้ระบบทั้งหมดหยุดชงัก คือต้องรอให้ครบเวลาก่อนจึงจะทำงานอื่นต่อ นั่นหมายความว่าเราไม่สามารถให้ทำงานที่ซ้อนกันได้
มาลองแก้ไขปัญหา delay ดูดีกว่า เอาไฟกระพริบเหมือนเดิมก็แล้วกัน โดยที่ให้มี led 2 ชุด โดยชุดแรกให้ติดดับทุก 5 วินาที ส่วนชุดที่ 2 ให้ติดดับทุก 3 วินาที ถ้าเขึยนแบบเดิมจะไม่สามารถทำได้
const int led_run1[]={13,12,11,10};const int led_run2[]={6,5,4,3};const int leng1=sizeof(led_run) / sizeof(int);    const int leng2=sizeof(led_run) / sizeof(int);    unsigned long previousMillis = 0;unsigned long previousMillis2 = 0;   const long interval = 3000;  const long interval2 = 5000;     void setup() {  for (int i=0;i<leng1;i++){    pinMode(led_run1[i], OUTPUT);  }  for (int j=0;j<leng2;j++){    pinMode(led_run2[i], OUTPUT);    }}void loop() {  unsigned long currentMillis = millis();  if (currentMillis - previousMillis >= interval) {    previousMillis = currentMillis;    for(int i=0;i<leng;i++){      digitalWrite(led_run1[i], !digitalRead(led_run1[i]));    }  }  if (currentMillis - previousMillis2 >= interval2) {    previousMillis2 = currentMillis;    for(int j=0;j<leng;j++){      digitalWrite(led_run2[j], !digitalRead(led_run2[j]));    }      }}
รอบนี้มีคำสั่งเพิ่มขึ้นคือ unsigned long และ millis()
  • unsigned long คือ ชนิดตัวแปรชนิดหนึ่งที่เก็บค่าได้เยอะ (ลองไปค้นดูในเนตเพิ่มเรื่องชนิดตัวแปรนะครับ)
  • millis() คือ function ที่นับเวลาที่เริ่มต้นการทำงานของ Arduino เป็นเวลาจำลองหน่วยเป็นมิลลิวินาที (1 วินาที = 1000 มิลลิวินาที)
จนถึงตอนนี้ผมจะไม่พูดถึงเรื่องของ Serial Monitor ไม่ได้สินะ...Serial Monitor คือหน้าจอสำหรับเอาไว้อ่านค่าที่ส่งออกมาแสดงเพื่อ debug โปรแกรม
debug คือการแสดงค่าตัวแปรหรือค่าอะไรบางอย่างเพื่อตรวจสอบการทำงานของโปรแกรมที่พัฒนาขึ้น ถ้าหากมี error ไม่แสดงค่าที่ควรจะได้ เราก็จะทำการ debug โปรแกรมของเราเพื่อตรวจสอบค่าตัวแปรต่างๆ ในช่วงของโปรแกรมที่ละช่วง (สำหรับพวกที่ copy paste บอกเลยถ้าไม่หัดทำความเข้าใจโปรแกรม และหัด debug คุณจะไม่มีทางเขียนโปรแกรมสำเร็จเลย)
ตรงมุมขวามือด้านบนของโปรแกรม Arduino IDE
คำสั่งสำหรับแสดงค่าออกมาทาง Serial Monitor
Serail.print("ข้อความ"); //สั่งแสดงค่าแล้วค่าต่อไปต่อบรรทัดเดิมSerail.println("ข้อความ"); //สั่งแสดงค่าแล้วค่าต่อไปขึ้นบรรทัดใหม่
แล้ว Serial Monitor นอกจากจะอ่านค่าเพื่อ debug โปรแกรมแล้ว จะส่งค่าเข้าไปเพื่อ debug ได้ไม๊ คำตอบคือได้ แต่จะเป็นลักษณะส่งค่าเข้าไปแบบ character (char) แล้วค่อยไปเขียนคำสั่งเพื่อรับค่าในโปรแกรมอีกที
void setup() {  Serial.begin(9600);}
void loop() {  if (Serial.available() > 0) {    char incomingByte = Serial.read();    Serial.print("I received: ");    Serial.println(incomingByte);  }}
คำสั่งที่เกี่ยวข้อง
  • Serial.begin(9600); คือการกำหนดความเร็วในการรับส่งข้อมูลซึ่งจะต้องกำหนดให้ตรงกันในหน้าต่าง Serial Monitor
  • Serial.read(); คือการรับค่าจาก Serial Monitor เมื่อกดปุ่ม send
เหนื่อยจัง...แต่ยังไม่หมดนะ...55+
มาต่อกันที่ การควบคุมเส้นทางการทำงานของโปรแกรมสามารถใช้คำสั่งต่อไปนี้
  • if else
  • switch case
if ผมขอไม่อธิบายเพิ่มก็แล้วกันเพราะเห็นวิธีการใช้งานก่อนหน้านี้ไปแล้ว ส่วน switch case ถูกออกแบบมาเพื่อช่วยไม่ให้เขียน if else เยอะ เพราะบางครั้งถ้าเราเขียน if else เยอะ การตามหาว่าส่วนนี้คือ if ตรงไหนมันงง...มือใหม่มันเป็น แต่มือเก่าก็เป็นนะ 55+
int num;#define led1 10#define led2 6void setup() {  Serial.begin(9600);  pinMode(led1,OUTPUT);  pinMode(led2,OUTPUT);}
void loop() {   num++;  switch(num){ //ตรวจสอบค่าจากตัวแปร num    case 2: {      digitalWrite(led2,HIGH); break; //ถ้า num = 2 ไฟติด    }    case 4: {      digitalWrite(led2,LOW); break; //ถ้า num = 4 ไฟดับ    }    case 8: {      digitalWrite(led2,HIGH); break; //ถ้า num = 8 ไฟติด    }    case 10: {      digitalWrite(led2,LOW); break; //ถ้า num = 10 ไฟดับ    }    default :{      digitalWrite(led2,LOW); break; //ถ้าไม่เข้าเงื่อนไขใดเลยให้ไฟดับ    }  }  if(num>10){  //ถ้า num>10    num=0;  //ให้ num = 0  }  delay(1000);}
จะเห็นว่าทุกๆ 2 วินาทีไฟจะติดและดับสลับกัน และใน case แต่ละ case ต้องใส่คำสั่ง break; ด้วยเพื่อให้ออกจาก case ส่วน default นั้นเหมือนกรณี else จากกรณีทั้งหมด (น่าจะพอเข้าใจนะ)
มาว่ากันด้วยเรื่องของ sensor มีด้วยกัน 2 แบบคือ
  • แบบที่เป็นลักษณะ resistor (จริงๆ มันก็คือ r แบบนึง) โดยจะเปลี่ยนไปตามสภาพแวดล้อมที่ต้องการวัด ซึ่งค่าที่ออกมาจะเป็น analog
  • แบบที่เป็นลักษณะ chip (ถ้าจำไม่ผิดน่าจะเป็น transistor) โดยภายในจะเป็น r แต่มี transistor หรือ chip ใช้แปลงค่าหรือ calibrate ให้ค่าออกมาเป็น digital
ยกตัวอย่างที่เป็น resistor เช่น LDR (Light Dependent Resistor) หรือที่เรียกกันว่า ตัวต้านทานปรับค่าตามแสง ซึ่งจะต้องต่อขาไปที่ analog in
การต่อ LDR

int light;#define pin 14 //จะกำหนดเป็น A0 ก็ได้#define led1 5 //จากภาพด้านบนไม่ได้ต่อไว้นะ ขอให้ต่อไว้ด้วย อย่าลืมต่อผ่าน rint mi=100;int mx=180;void setup(){  Serial.begin(9600);  pinMode(led1,OUTPUT);}void loop(){  int lig=analogRead(pin);  Serial.print("before map ");  Serial.print(lig);  Serial.print("\t");  int lu=map(lig,mi,mx,0,255);  if(lu<0) lu=0; if(lu>255) lu=255;  Serial.print("after map ");  Serial.println(lu);  analogWrite(led1,lu);  }
อธิบายคำสั่ง
  • analogRead(pin); เป็นการอ่านค่าผ่านขา analog A0 หรือขา 14 โดยค่าที่อ่านได้จะอยู่ระหว่า 0-1023 ลองอ่านค่าแล้วลองเอามือปิดและเปิดดูความเปลี่ยนแปลงของค่า
  • map(lig,mi,mx,0,255); เป็นคำสั่งที่เอาไว้ใช้ปรับค่าตามอันตราส่วน รูปแบบ map(ตัวแปร,ค่าต่ำสุดที่อ่าน,ค่าสูงสุดที่อ่าน,แปลงเป็นค่าต่ำสุด,แปลงเป็นค่าสูงสุด)
  • analogWrite(led1,255); เป็นการสั่งให้ขาที่เป็น PWM (Pulse Width Modulation) ถ้าดูจาก Arduino UNO R3 จะมีสัญลักษณ์ ~ ข้างหน้าตัวเลขขา ค่าที่สามารถกำหนดได้คือระหว่า 0-255 จากตัวอย่างเป็นการปรับความสว่างของ led
พูดง่ายๆคือถ้าใช้ sensor ที่เป็น resistor ก็คือการใช้งาน resistor 2 ตัวนั่นเอง
ลักษณะการต่อ Sensor ซึ่งจะต่อแทน R1 หรือ R2 แต่ก็ขึ้นอยู่กับ Sensor ด้วย
เรื่องสุดท้ายคือเรื่องของ Interrupt
Interrupt เป็นเรื่องของการขัดจังหวะเพื่อทำงานอะไรบางอย่าง สมมุติถ้าเราเขียนโปรแกรมในนั้นมี delay ก็จะทำให้ไม่สามารถทำงานอย่างอื่นได้ เช่นระหว่างไฟกระพริบ จะไม่สามารถกดปุ่มได้ เป็นต้น แต่ Interrupt จะเป็นการเรียกใช้งาน function แต่ก็มีข้อจำกัดเรื่องการใช้งาน delay ใน function ด้วยเช่นกัน แล้วขาที่สามารถเป็นขาที่รับการสั่งงาน interrupt ได้ใน Arduino คือขา 2 และ 3 โดยเรียงความสำคัญตามลำดับ
#define led1 13#define led2 3#define btn 2// interrup สามารถใช้ขา 2 และ 3void setup(){  pinMode(btn,INPUT_PULLUP);  pinMode(led1,OUTPUT);  pinMode(led2,OUTPUT);  attachInterrupt(digitalPinToInterrupt(btn),bnk,LOW); //Falling ถ้าเปลี่ยนจาก 0 เป็น 1, LOW ถ้าค่าเป็น 0, RISING ถ้าเปลี่ยนจาก 1 เป็น 0 ส่วน INPUT_PULLUP ควรใช้ RISING หรือ LOW}
void bnk(){  //ในนี้ใช้ delay ไม่ได้  static unsigned long t=0;  unsigned long lt = millis();  if(lt-t>200){    digitalWrite(led2,!digitalRead(led2);  }  t=lt  }
void loop(){  digitalWrite(led1,!digitalRead(led1));  delay(3000);}
อธิบายเพิ่มเติม
  • attachInterrupt(digitalPinToInterrupt(btn),bnk,LOW); เป็นการแทรกคำสั่ง Interrupt โดยใช้เป็นขา digital ที่ขา 2 เรียกใช้ function ชื่อ bnk โดยจะทำงานก็ต่อเมื่อขา 2 เป็น LOW

ช่วงท้ายๆอาจจะอธิบายไม่เยอะนะครับ เพราะตอนที่พิมพ์ตอนนี้บอกตามตรงเบลอมาก 55+ นั่งพิมพ์ทั้งหมดนี่ประมาณ 3-4 ชม.
ทั้งหมดทั้งมวลนี้ต้องขอขอบคุณอาจารย์ชื่อ มานพ ปักษี มากๆครับ ที่ได้ให้ความรู้โดยไม่คิดมูลค่าใดๆ ทั้งอุปกรณ์ต่างๆ ขอบคุณเจ้าภาพสถานที่อบรบ พี่ๆเพื่อนๆน้องๆ ในห้องบรรยายที่เป็นมิตรทุกท่านมากๆด้วยครับ
credit : TUNTIGON NONSRI