ชมรมโดรน

6. C++ พื้นฐาน: เงื่อนไขและลูป

if, else, switch, for, while, break และ continue พร้อมผังลูปรอความสูงนิ่งจากโค้ดจริงของชมรม

เงื่อนไขและลูป สรุปก่อนอ่าน

  • คืออะไร if คือ "ถ้า...ก็ทำ..." และลูป (for, while) คือ "ทำซ้ำจนกว่า..."
  • ทำไมต้องมี ถ้ามีแค่ลำดับตายตัว โปรแกรมจะไม่ตอบสนองอะไรเลย ต้องมีทางเลือกและการทำซ้ำ เหมือนเราตัดสินใจ "ถ้าฝนตกให้พกร่ม" และ "ตักข้าวซ้ำจนอิ่ม"
  • ในโค้ดโดรน ใช้ตอน "ถ้าสูงเกินไปให้ลง" และ "รอวนไปจนความสูงนิ่งแล้วค่อยไปต่อ"

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

1. if / else

int height = 85;
if (height > 100) {
  Serial.println("สูงมาก");
} else if (height >= 70) {
  Serial.println("สูงพอบินได้");
} else {
  Serial.println("ต่ำเกิน");
}

คอมพิวเตอร์ตรวจเงื่อนไขจากบนลงล่าง ตัวแรกที่เป็นจริงทำงาน ที่เหลือข้ามหมด ตัวอย่างจริงจาก Drone_Mission.ino ที่เลือกโหมดของ KEY 1

// Drone_Mission.ino
uint8_t KEY1_MODE = 2;

void DroneMission_KEY_1_handle(void) {
  if (KEY1_MODE == 1) {
    KEY1_mode1();
  } else if (KEY1_MODE == 2) {
    KEY1_mode2();
  } else {
    // ถ้าใส่โหมดผิด ให้ไม่ทำงานอันตราย
    drone.calibrateAndDelay(3000);
    ...
  }
}

else ตัวสุดท้ายดักค่าที่ไม่คาดคิด เป็นนิสัยที่ดี ถ้าใครพิมพ์ KEY1_MODE = 5 โดรนยังทำอะไรที่ปลอดภัย

ตัวอย่างชีวิตจริง: ป้ายไฟจราจร

ไฟแดงหยุด ไฟเหลืองชะลอ นอกนั้นไป เป็นลำดับ if ... else if ... else ตรงๆ

รวมเงื่อนไข

&& คือ "และ" || คือ "หรือ" ! คือ "ไม่"

// shooting.ino
if (h == 0 || h > 300) { ... }               // ค่าเพี้ยนอย่างใดอย่างหนึ่ง
if (current_h < (HEIGHT_SHOOT - 10) || current_h > (HEIGHT_SHOOT + 10)) { ... }

เงื่อนไขที่สองอ่านว่า "ต่ำกว่า 110 หรือสูงกว่า 130" ซึ่งคือ "อยู่นอกช่วง 120 ± 10"

2. switch

เมื่อเทียบตัวแปรตัวเดียวกับหลายค่า switch อ่านง่ายกว่า else if ต่อกันยาว โค้ดชมรมใช้กับรอบการปล่อยบอล

// Servo.ino
void drop_payload_by_round_all(int round) {
  switch (round) {
    case 0:
      release_dual_servo_angle(PORT3, 1000,  PORT6, 1000);
      break;
    case 1:
      release_dual_servo_angle(PORT3, 2550, PORT6, 2550);
      break;
  }
}

break สำคัญมาก ถ้าลืม โปรแกรมจะไหลต่อลงไปทำ case ถัดไปด้วย

3. while และ for

while ทำซ้ำตราบใดที่เงื่อนไขยังจริง ใช้เมื่อไม่รู้จำนวนรอบ

// Servo.ino
uint32_t t = micros();
while (micros() - t < 1500000) {
  // ส่งพัลส์ซ้ำ นาน 1.5 วินาที
}

for ใช้เมื่อรู้จำนวนรอบ ประกอบด้วยสามส่วนคือ เริ่ม; เงื่อนไข; ก้าวต่อไป

// protocol.cpp (smooth_set_height) — ขยับความสูงทีละ step
for (uint16_t h = current_height + step; h <= target_height; h += step) {
  set_height(h);
  delay(delay_ms);
}

ถ้า current_height = 100, target_height = 140, step = 10 ลูปรันที่ h = 110, 120, 130, 140 รวม 4 รอบ แต่ละรอบสั่งความสูงใหม่ แล้วรอ จึงขึ้นเป็นขั้นบันไดนิ่มๆ แทนกระโดดทีเดียว

break และ continue

  • break ออกจากลูปทันที
  • continue ข้ามส่วนที่เหลือของรอบนี้ ไปรอบถัดไป

4. ผังลูปจริง: รอความสูงนิ่ง

ฟังก์ชันที่ซับซ้อนที่สุดในหน้านี้คือ reach_height_shoot_stable() ใน shooting.ino ใช้ทุกอย่างที่เรียนมาพร้อมกัน

// shooting.ino (ตัดเฉพาะส่วนลูป)
uint32_t t0 = millis();
uint32_t stableSince = 0;

while (millis() - t0 < MAX_WAIT_MS) {      // MAX_WAIT_MS = 3500
  pArduino->set_height(TARGET);
  delay(SAMPLE_DELAY);                      // 80

  pArduino->poll();
  uint16_t h = read_height_cm();

  if (h == 0 || h > 300) {
    stableSince = 0;
    continue;
  }

  int diff = (int)h - (int)TARGET;
  if (abs(diff) <= TOL) {                   // TOL = 4
    if (stableSince == 0) stableSince = millis();
    if (millis() - stableSince >= STABLE_NEED_MS) {   // 500
      break;
    }
  } else {
    stableSince = 0;
  }
}
ผังงานลูปรอความสูงนิ่งอ่านความสูง ถ้าเป็น 0 หรือเกิน 300 ทิ้งค่า ถ้าอยู่ในช่วง 120 บวกลบ 4 ให้นับเวลา ครบ 500 ms ออกจากลูปอ่าน h = ความสูงh = 0 หรือ h > 300 ?(ค่าเพี้ยน)ใช่ทิ้งค่า เริ่มนับใหม่stableSince = 0ไม่|h − 120| ≤ 4 ?(อยู่ในช่วงเป้า)ใช่เริ่มจับเวลา (ถ้ายังไม่เริ่ม)ครบ 500 ms → breakไม่รีเซ็ตเวลานิ่งทุกเส้นทางวนกลับไปอ่านใหม่จนกว่าเวลาจะหมด (3500 ms)
ผังงานของลูปด้านบน แต่ละเส้นทางกลับไปอ่านความสูงใหม่ จบเมื่อนิ่งครบ 500 ms หรือหมดเวลา 3,500 ms

อ่านเป็นภาษาคน: "ภายใน 3.5 วินาที สั่งความสูง 120 รออ่านทุก 80 ms ถ้าอ่านค่าเพี้ยนให้ทิ้ง ถ้าอยู่ในช่วง 116–124 cm ต่อเนื่องครบ 0.5 วินาที ถือว่านิ่ง ออกได้" หมดเวลาก่อนก็ออกเหมือนกัน จะได้ไม่ค้างตลอดไปถ้าเซนเซอร์เสีย

สังเกตตัวแปร stableSince ที่ใช้เป็นธง: ค่า 0 แปลว่า "ยังไม่เริ่มนับ" นี่คือเทคนิคที่เจอบ่อย

ลองทำ

  1. เขียนลูป for ที่พิมพ์ความสูง 100, 110, ... 150 ลง Serial Monitor (คำตอบ: for (int h = 100; h <= 150; h += 10) Serial.println(h);)
  2. ถ้า TOL เปลี่ยนจาก 4 เป็น 10 ช่วงที่ถือว่านิ่งเป็นกี่ cm (คำตอบ: 110–130)
  3. ในผังด้านบน ถ้าอ่านได้ 118, 0, 119, 121 ตามลำดับ stableSince ถูกรีเซ็ตกี่ครั้ง (คำตอบ: 1 ครั้ง ตอนอ่านได้ 0)

พังตรงไหนบ่อย

  • ลืม break ใน switch โปรแกรมทำหลาย case ติดกัน
  • ลูป while ไม่มีทางจบ ลืมปรับตัวแปรที่ใช้ในเงื่อนไข โค้ดชมรมกันด้วยเงื่อนไขเวลา millis() - t0 < MAX_WAIT_MS
  • for กับ uint16_t นับลง for (uint16_t h = 10; h >= 0; h--) ไม่มีวันจบ เพราะ unsigned ไม่เคยน้อยกว่า 0 โค้ด smooth_set_height จึงใส่ if (h <= step) break; กันไว้
  • ใช้ = แทน == ดูหน้าตัวแปร
  • ลืมปีกกา if (x) a(); b(); b() ทำเสมอ ไม่ได้อยู่ใน if

On this page