while() คำสั่งที่มือใหม่ต้องรู้จัก

สวัสดีครับ ~~ หลังจากที่ไม่ได้อัพบล็อกไปนาน ครั้งนี้กลับมาอัพบล็อกเรื่องเล็ก ๆ น้อย ๆ เกี่ยวกับคำสั่ง while() ใน Arduino หรือ ไมโครคอนโทรลเลอร์ครับ

รู้จักกับคำสั่ง while()

คำสั่ง while() เป็นคำสั่งที่ใช้วนรอบการทำงาน ตราบใดที่เงื่อนไขในวงเล็บเป็นจริงอยู่ ในภาษา C แบบไมโครคอนโทรลเลอร์ทั่ว ๆ ไป อย่าง MCS51 PIC AVR และตะกูลอื่น ๆ ที่ทำงานเริ่มต้นในฟังก์ชั่น main() เราจะเห็นการใช้ while(1) อยู่เสมอ นั่นเพราะหลักการทำงานใด ๆ ก็ตามแต่ ในปกติแล้วจะเป็นการทำงานแบบวนรอบ ทำให้ต้องใช้คำสั่ง while() เข้ามาเกี่ยวข้อง เช่น ต้องการทำโวล์มิเตอร์ ในส่วนแรกก็จะเป็นการตั้งค่ารีจิสเตอร์ต่าง ๆ แต่พอเข้าสู่ while(1) { …. } จะเป็นการอ่านค่าแรงดัน และแสดงผล ซึ่งจะเป็นการทำงานแบบวนรอบตรงที่จะมีการอ่านค่าแรงดัน และแสดงผลการอ่านค่าอยู่ตลอด ๆ นั่นเอง

while() กับการหยุดรออะไรบางอย่าง

จุดประสงค์หลักของบล็อกนี้เลยคือการแนะนำให้ใช้ while() ในการหยุดโปรแกรม ซึ่งถ้าเราเข้าใจมัน เราจะสามารถนำมันไปวางไว้ แล้วลดขั้นตอนการคิด การทำงานลงได้มาก

การใช้ while() ตามที่ได้กล่าวไว้ขั้นต้น คือ เงื่อนไขในวงเล็บเป็นจริง มันจะทำคำสั่งภายในปีกกาที่ตามหลังมันมา แล้วถ้าไม่มีปีกกา และไม่มีคำสั่งในปีกกาละ ? …… มันก็ไม่ทำอะไรเลยครับ วน วน และวน ใช้พลังงานอย่างเปล่าประโยชน์ไปเรื่อย ๆ จนกว่าเงื่อนไขในปีกกามันจะเป็นเท็จนั่นละ มันถึงจะเลิกวน และออกจากบรรทัดนั้นไปทำบรรทัดต่อไป

ตัวอย่าง การใช้ while() เพื่อหยุดโปรแกรม

เห้ย ๆ เรามาใช้ while() กันเหอะ แล้วทุกคนก็แบบ OK เราจะมาใช้ while() กัน …. แล้วจะใช้เมื่อไหร่ตอนไหนดีละ ?

มาเริ่มแบบเบสิก ๆ กันก่อน ตามปกติแล้วในการเขียนโค้ดเพื่อรอการกดสวิตซ์ เราจะ if (กดสวิตซ์) {  …. } ไม่ได้ สิ่งที่ต้องเพิ่มมา คือ

void loop() {
  if (กดสวิตซ์ ?) {
    while(กดสวิตซ์ ?) ;
    ....
  }
}

ซึ่งจะเห็นว่าเราต้องเพิ่ม while(กดสวิตซ์ ?) ; เข้ามา โดยมีจุดประสงค์เพื่อรอจนกว่าจะปล่อยสวิตซ์ (วน ถ้ามีการกดสวิตซ์อยู่) นั่นเพราะไมโครคอนโทรลเลอร์มันทำงานเร็วมาก ๆ ถ้าไม่มี while(กดสวิตซ์ ?) ; มันทำไปครบทุกบรรทัด แล้วเรายังไม่ทันปล่อยมือ มันก็หมดในปีกกาแล้ว พอย้อนกลับมาเช็คอีกรอบ (เจอ if (กดสวิตซ์ ?) อีกรอบ)  อ่าว ยังไม่ปล่อยสวิตซ์นิ ทำอีกรอบไปละกัน

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

ทีนี้ โค้ดด้านบนคนปกติเขาใช้กันครับ มาดูอีกแบบกันดีกว่า แบบนี้เหมือนจะสั่นกว่า และผมชอบใช้กับการทำงานที่ไม่ซับซ้อน

void setup() {
  while(ปล่อยสวิตซ์ ?) ;
  while(กดสวิตซ์ ?) ;
  ...
}

โค้ดมันแปลก ๆ ไหมครับ 555 โค้ดด้านบนผมมักจะใช้ในกรณีที่ปกติแล้ว จะมีค่า ๆ หนึ่งอยู่แล้ว พอเกิดเหตุการณ์ จึงค่อยออกจากการวนเปล่าประโยชน์เพื่อไปทำงานต่อ

ในโค้ดด้านบนนี้ เราอาจจะนำไปประยุกต์ทำบางอย่างได้ เช่น ผมต้องการทำให้กดสวิตซ์ก่อน ถึงจะค่อยเริ่มโปรแกรม ซึ่งโค้ดแบบด้านบนนั้นตอบโจทย์ทีเดียวครับ

เริ่มต้น while(ปล่อยสวิตซ์ ?) ; มีหน้าที่ใช้รอการกดสวิตซ์ (วน ถ้าไม่มีการกดสวิตซ์) พอเรากดสวิตซ์ปุ๊บ มันจะเด้งออกจากการรอนั้น แล้วไปเจอบรรทัดต่อไป while(กดสวิตซ์ ?) ; เป็นการรอจนกว่าจะปล่อยสวิตซ์ (วน ถ้ามีการกดสวิตซ์อยู่) จากนั้นจึงไปทำบรรทัดต่อไปเรื่อย ๆ

จากโค้ดด้านบนนี้ เมื่อจ่ายไฟให้ไมโครคอนโทรลเลอร์แล้ว เราจะต้องต้องกดสวิตซ์ก่อน 1 ครั้ง ถึงจะเริ่มทำงานได้ต่อไป ซึ่งเป็นไปตามวัตถุประสงค์ที่ผมต้องการเลยครับ

มาดูโค้ดด้านล่างกันบ้าง

int count = 0;
void loop() {
  while(ปล่อยสวิตซ์ ?) ;
  while(กดสวิตซ์ ?) ;
  count++;
}

พอจะเดาได้ไหมครับ ว่าโค้ดด้านบนนี้ไว้ทำอะไร ?

.

.

.

.

อาจจะเดาได้แล้ว หรือเดาไม่ได้ ไม่เป็นไรครับ มาลองดูอธิบายกันครับ โค้ดด้านบนนี้ มันเหมือนกับโค้ดที่แล้วนั่นละ แต่ในโค้ดที่แล้วผมเอาไว้ใน void setup() ทำให้มันทำงานครั้งเดียว ครั้งนี้ผมยกมันมาไว้ใน void loop() มันก็วนทำงานหลาย ๆ ครั้ง หลักการเหมือนเดิม รอกดสวิตซ์ และรอปล่อยสวิตซ์ เพิ่มค่าในตัวแปร count พอกลับมาอีกรอบ ก็อีหรอบเดิม รอกดสวิตซ์ รอปล่อยสวิตซ์ เพิ่มค่า count ไปเรื่อย ๆ แบบนี้ไม่สิ้นสุด ดังนั้นโค้ดนี้ใช้สำหรับนับจำนวนการกดสวิตซ์นั่นเองครับ

ทีนี้เรามาดูการใช้งานแบบจริง ๆ จัง ๆ กันบ้างแล้วครับ ในกลุ่ม Arduino Thailand ผมไปเจอโพสหนึ่งที่จะมาเป็นตัวอย่างให้เรา ๆ ได้เข้าใจกันว่า การใช้ while() มันดียังไง ใช้ยังไง ผลที่ได้เป็นยังไง

ผมไม่แน่ใจในสภาพแวดล้อม และอุปกรณ์ของเขามากนัก ดังนั้นผมจึงขอมโนข้อมูลขึ้นมาดังนี้

  • ต่อขาสัญญาณจากตัวนับเหรียญไว้ที่ขา 2
  • ตัวนับเหรียญทำงานแบบ Active LOW คือเมื่อมีการหยอดเหรียญ จะให้สถานะทางลอจิกเป็น 0
  • ตัวนับเหรียญสามารถรับเหรียญได้แบบเดียว จึงให้สัญญาณแบบเดียวเท่านั้น
  • เครื่องจะรับเฉพาะเหรียญ 10 บาท

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

#define PIN 2 // แทนที่คำว่า PIN ในโค้ดทั้งหมดด้วย 2
long baht = 0; // ประกาศตัวแปร baht ให้มีค่าเป็น 0

void setup() {
  pinMode(PIN, INPUT_PULLUP); // ให้ขาใน PIN มีสถานะเป็นอินพุต และใช้ตัวต้านทาน Pull-up ภายใน
  Serial.begin(9600); // ตั้งความเร็ว UART ไว้ที่ 9600
}

void loop() {
  if (digitalRead(PIN) == LOW) { // เทียบได้กับ ถ้ามีการกดสวิตซ์
    ... // เทียบได้กับ รอปล่อยสวิตซ์
    baht += 10; // เพิ่มค่าในตัวแปร baht ขึ้น 10 ค่า (เอาตัวแปร baht มาบวก 10 แล้วเก็บไว้ในตัวแปร baht)
    Serial.println(baht); // แสดงผลค่าในตัวแปร baht ใน Serial Monitor
  }
}

ตรง … ก็ไปเพิ่มเอาเองนะครับ 555 เป็นการวัดตัวเองด้วย ที่อุส่าอ่านมา เข้าใจหรือเปล่าครับ

ตัวอย่างการใช้ while() เพื่อหยุดรอเวลา

ใน Arduino จะมีฟังก์ชั่น millis() สำหรับดึงค่าเวลาตั้งแต่ไมโครคอนโทรลเลอร์ถูกจ่ายไฟ จนมาถึงตอนนี้ ผ่านไปกี่มิลิวินาทีแล้ว เช่น เสียบไฟไว้ใน Arduino 10 วินาทีแล้ว ก็จะคืนค่า 10,000 ออกมาให้

ทีนี้เราจะมาลองใช้คู่กับ while() ดูครับ ว่าเราจะเล่นอะไรกันได้บ้าง

สิ่งที่ผมคิดออกเลย คือผมอยากรู้ว่า ใน 1 วินาทีเนี่ย ไมโครคอนโทรลเลอร์ หรือ Arduino มันจะนับเลขได้เท่าไหร่กันนะ ? สิ่งที่ผมจะต้องใช้ คือฟังก์ชั่น millis() แน่ ๆ แต่ว่าจะใช้ยังไงละ

เรามาทำความเข้าใจเรื่อง ดิฟ กันก่อน …. ไม่มีอะไรครับ เราแค่จะหาผลต่างของเวลาเฉย ๆ สมมุติว่า คำสั่ง A เราไม่รู้ละ ว่ามันใช้เวลาทำงานเท่าไร แต่เราอยากรู้ละตอนนี้ เราก็ทดสอบง่าย ๆ โดยการเก็บเวลาก่อนทำคำสั่ง และเก็บเวลาหลังทำคำสั่ง เอาปลาย ลบต้น ได้เวลาที่คำสั่ง A ทำงาน

เช่น สมมุติ เริ่มต้นก่อนทำคำสั่ง A ฟังก์ชั่น millis() ให้ค่ามา 1,000 แล้วหลังทำคำสั่ง A ฟังก์ชั่น millis() ให้ค่ามา 2,000 เอาปลายลบต้น 2,000 – 1,000 = 1,000mS หรือ 1 วินาที จึงสรุปได้ว่า คำสั่ง A ใช้เวลาทำงาน 1 วินาทีนั่นเองครับ โค้ดก็จะประมาณด้านล่าง

void setup() {
  long start = millis(); // เก็บเวลาก่อนทำคำสั่ง
  A(); // ทำคำสั่ง
  long end = millis(); // เก็บเวลาหลังทำคำสั่ง
  long time = end - start; // คำนวณหาเวลาที่ใช้ทำคำสั่ง
}

ในโค้ดด้านบน เป็นการนับเวลาที่ผ่านไปแบบง่าย ๆ แต่ตอนนี้เราจะมาลองทำแบบ รอเวลา กันครับ

คำสั่ง while() เป็นคำสั่งที่ใช้วนรอบ สิ่งที่จะทำได้ คือการวนรอบ หรือรอจนกว่าจะถึงเวลา ระหว่างรอ ก็ทำอย่างอื่นไปด้วย ซึ่งโค้ดจะเป็นไปตามด้านล่างนี้

int count = 0; // ประกาศตัวแปร count ให้เป็น 0

void setup() {
  Serial.begin(9600); // เปิดใช้ Serial Monitor
  int wait_time = 1000; // กำหนดเวลาที่รอ
  long start = millis(); // เก็บค่าเวลาก่อนทำคำสั่ง
  while((millis() - start) <= wait_time) { // วนรอบ ถ้าเวลาผ่านไปน้อยกว่าหรือเท่ากับ wait_time
    count++; // เพิ่มค่าในตัวแปร count
  }
  Serial.println(count); // แสดงผลค่าในตัวแปร count ใน Serial Monitor
}

void loop() {

}

ใจเย็น ๆ ไว้ก่อนนะครับ โค้ดในแต่ละบรรทัดด้านบนไม่ยากครับ

เริ่มต้นที่ผมกำหนดให้ตัวแปร wait_time เก็บค่า 1,000 ไว้ก่อน พอมาบรรทัดที่ 5 เก็บค่าเวลาเริ่มต้นไว้ก่อนในตัวแปร start สมมุติให้ตอนนี้ตัวแปร start เก็บค่าไว้ 1000 พอมาเจอ while((millis() – start) <= wait_time) ให้นึกไว้อย่างเดียวเลยว่ามันจะวนรอบ หรือทำคำสั่งในปีกกา เมื่อเงื่อนไขในวงเล็บเป็นจริงเท่านั้น แยกส่วนในวงเล็บออกมาได้ 2 ส่วน ส่วนแรก (millis() – start) ให้คิดว่า ตอนนี้คำสั่ง millis() คืนค่า 1001 ออกมา แล้ว start มีค่า 1000 เอาค่าพวกนี้มาแทนได้ (1001 – 1000) ซึ่งจะได้ 1 ต่อมาเจอ <= wait_time ให้แทน wait_time ด้วย 1000 ได้รวม ๆ 1 <= 1000 ซึ่งเป็น จริง จึงเข้าไปทำงานในปีกกา เพิ่มค่าในตัวแปร count ขึ้น 1 ค่า เดิมมีอยู่ 0 เพิ่ม 1 ค่า เป็น 1 แล้ว จากนั้นก็วนกลับไปตรวจสอบเงื่อนไขอีก ครั้งนี้สมมุติให้ millis() คืนค่า 1002 ซึ่ง (1002 – 1000) <= 1000 เป็น จริง อีก ค่าในตัวแปร count ก็เพิ่มขึ้นอีก เป็นแบบนี้ไปเรื่อย ๆ จนเวลาผ่านไปตามที่ตั้งไว้ในตัวแปร wait_time ครั้งนี้คำสั่ง millis() คืนค่า 2004 แทนค่าได้ (2004 – 1000) <= 1000 ซึ่งเป็น เท็จ ก็ออกจากลูป while() ไป จากนั้นเราก็ได้ตัวแปร count เก็บค่าไว้ว่า 1 วินาที ไมโครคอนโทรลเลอร์สามารถนับเลขได้กี่เลขแล้วครับ

ผลจากการรันโค้ดด้านบนได้ …

การรอเวลา สามารถนำไปประยุกต์ได้หลายอย่าง เช่น สามารถนำไปใช้ติดต่อกับอุปกรณ์ต่าง ๆ ถ้ามีการส่งข้อมูลไปแล้ว และไม่มีการตอบกลับภายใน …. วินาที จะถือว่าอุปกรณ์นั้นใช้ไม่ได้ ก็จะสามารถดัก Error ได้ครับ

ส่งท้าย

ท่านที่หัดเขียนโปรแกรมอยู่ ถ้าทำบ่อย ๆ ก็จะเก่งขึ้นเองครับ และสำหรับท่านที่เก่งแล้ว ถ้าศึกษาไปเรื่อย ๆ จะพบว่าภาษา C++ ใน Arduino เนี่ย มันมีอะไรมากมายซ่อนอยู่ครับ ส่วนตัวผมชอบความที่มันมีอะไรหลาย ๆ อย่างใน C++ มาก ๆ ครับ

ขอบคุณที่ติดตามอ่านมาถึงตอนนี้นะครับ ขอบคุณครับ สวัสดี