ชมรมโดรน

7. C++ พื้นฐาน: ฟังก์ชันและอาร์เรย์

แยกโค้ดเป็นฟังก์ชันที่รับค่าและคืนค่า ค่าเริ่มต้นของพารามิเตอร์ ส่งฟังก์ชันให้ฟังก์ชันอื่น และอาร์เรย์

ฟังก์ชันและอาร์เรย์ สรุปก่อนอ่าน

  • ฟังก์ชัน ชุดคำสั่งที่ตั้งชื่อไว้ เหมือนปุ่ม "ซักผ้า" ที่กดทีเดียวทำหลายขั้นตอน (อธิบายจากศูนย์ใน อ่านโค้ดให้ออก)
  • อาร์เรย์ แถวของกล่องหลายใบที่ใช้ชื่อเดียวกัน แยกกันด้วยเลขลำดับ เหมือนตู้ล็อกเกอร์ที่มีเลขกำกับ
  • ในโค้ดโดรน ใช้เก็บระยะบินหลายรอบ เช่น forwardDistanceCM[4] แล้วเรียกด้วยเลขรอบ

1. ฟังก์ชัน

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

ฟังก์ชันรับค่าเข้าและคืนค่าออกservo_degree_to_pulse รับองศา คืนความกว้างพัลส์angle = 90ค่าเข้า (พารามิเตอร์)servo_degree_to_pulse1) จำกัด angle 0–1802) map() เป็น 490–2550int คือชนิดที่คืน1520ค่าที่ returnฟังก์ชันที่ไม่ต้องคืนค่าใช้ void ที่หน้าชื่อ เช่น void hold_now()
ฟังก์ชันคือกล่อง: ค่าเข้า → ทำงาน → ค่าออก ตัวอย่างจาก Servo_Degree.ino
// Servo_Degree.ino
int servo_degree_to_pulse(int angle) {
  angle = constrain(angle, SERVO_DEGREE_MIN, SERVO_DEGREE_MAX);
  return map(angle, SERVO_DEGREE_MIN, SERVO_DEGREE_MAX,
             SERVO_DEGREE_MIN_PULSE, SERVO_DEGREE_MAX_PULSE);
}

ส่วนประกอบ

  • int หน้าชื่อ คือ ชนิดที่คืนกลับ (return type)
  • servo_degree_to_pulse คือชื่อ
  • (int angle) คือ พารามิเตอร์ ค่าที่รับเข้ามา
  • return ส่งผลกลับไปที่ผู้เรียก และจบฟังก์ชันทันที

เรียกใช้

int us = servo_degree_to_pulse(90);   // us = 1520

constrain(angle, 0, 180) ตัดค่าให้อยู่ในช่วง ใครส่ง 200 มา ก็ถูกปรับเป็น 180 map แปลงจากช่วงหนึ่งไปอีกช่วงหนึ่ง (0–180 เป็น 490–2550) เป็นฟังก์ชันที่ Arduino มีให้อยู่แล้ว

ตัวอย่างชีวิตจริง: เครื่องชงกาแฟกดปุ่มเดียว

คุณไม่ต้องรู้ว่าเครื่องบดเมล็ดยังไง กดปุ่ม "ลาเต้" ก็ได้กาแฟ ฟังก์ชันคือปุ่ม ชุดคำสั่งภายในคือขั้นตอนที่ซ่อนอยู่ drone.takeOff() ก็เหมือนกัน

void: ไม่คืนค่า

ถ้าฟังก์ชันทำงานเฉยๆ ไม่มีผลลัพธ์ ใช้ void

// config.hpp
static inline void hold_now(uint16_t ms = 250){
  pArduino->set_direction(7,2);
  delay(ms);
}

ค่าเริ่มต้น (default argument)

uint16_t ms = 250 แปลว่าถ้าไม่ใส่ค่า ให้ใช้ 250

hold_now();      // รอ 250 ms
hold_now(400);   // รอ 400 ms

คลาส Arduino ใช้เทคนิคนี้ทุกที่ ดู protocol.hpp

void move_forward_cm(uint16_t distance_cm, uint8_t speed = 2);

move_forward_cm(100) = บินหน้า 100 cm ที่ความเร็ว 2

static inline คืออะไร

เจอบ่อยหน้าฟังก์ชันใน config.hpp ไม่ต้องกังวลมาก จำสั้นๆ ว่า static = ใช้ได้เฉพาะในไฟล์นี้ inline = ขอให้คอมไพเลอร์แปะโค้ดลงตรงที่เรียกเลย ไม่ต้องกระโดด ไฟล์ .hpp ที่ถูก include หลายที่ควรใส่สองคำนี้ เพื่อไม่ให้เจอ error นิยามซ้ำ

ส่งฟังก์ชันไปให้ฟังก์ชันอื่น

ฟังก์ชันก็เป็นค่าที่ส่งต่อได้ ส่ง "ชื่อฟังก์ชัน" ไม่ใส่วงเล็บ ไปให้อีกฟังก์ชันถือไว้เรียกทีหลัง นี่คือวิธีผูกปุ่มรีโมทของชมรม

// DroneEasy.hpp
void onKey1(void (*handler)(void)) const;

// Drone_Mission.ino
drone.onKey1(DroneMission_KEY_1_handle);

void (*handler)(void) อ่านว่า "handler คือตัวชี้ไปยังฟังก์ชันที่ไม่รับอะไรและไม่คืนอะไร" DroneMission_KEY_1_handle ไม่มีวงเล็บ เพราะต้องการส่งตัวฟังก์ชัน ไม่ใช่เรียกมัน ถ้าเขียน DroneMission_KEY_1_handle() โดรนจะลงมือบินทันทีตอนบูต

2. อาร์เรย์

อาร์เรย์ (array) คือแถวของกล่องชนิดเดียวกันที่เรียกด้วยเลขลำดับ (index) ที่ เริ่มจาก 0

// Drone_Mission.ino
int forwardDistanceCM[4] = { 352, 352, 352, 352 };
int backwardDistanceCM[4] = { 252, 252, 252, 252 };
อาร์เรย์ forwardDistanceCM มี 4 ช่องช่อง 0 ถึง 3 เก็บค่า 352 เหมือนกันทุกช่องint forwardDistanceCM[4] = { 352, 352, 352, 352 };352[0]352[1]352[2]352[3]นับเริ่มที่ 0 เสมอ ช่องสุดท้ายคือ [3] ไม่ใช่ [4]ใช้ [4] = อ่านหน่วยความจำนอกอาร์เรย์ ได้ค่ามั่ว
อาร์เรย์ forwardDistanceCM มี 4 ช่อง เลขลำดับ 0 ถึง 3 สีแดงใต้กล่องคือเลขลำดับ
int a = forwardDistanceCM[0];   // 352 ช่องแรก
forwardDistanceCM[2] = 360;     // แก้ช่องที่สาม

อาร์เรย์ไว้คู่กับ for ได้ดี ตัวอย่างบวกทุกช่อง (ทดสอบคอมไพล์สำหรับ Nano แล้ว)

int forwardDistanceCM[4] = { 352, 352, 352, 352 };

int total() {
  int sum = 0;
  for (int i = 0; i < 4; i++) {
    sum += forwardDistanceCM[i];
  }
  return sum;
}

void setup() {
  Serial.begin(9600);
  Serial.println(total());   // 1408
}

void loop() {}

ใช้ i < 4 ไม่ใช่ i <= 4 เพราะช่องสุดท้ายคือ [3]

ทำไมโค้ดชมรมใช้อาร์เรย์

ภารกิจมีหลายรอบ แต่ละรอบระยะเท่ากันหรือต่างกันก็ได้ เก็บในอาร์เรย์ แล้วเลือกด้วยเลขรอบ ปรับระยะจุดเดียวไม่ต้องไล่แก้หลายที่ ในไฟล์มีตัวนับรอบ k2_round และ payload_round10 อยู่ด้วย

ลองทำ

  1. เขียนฟังก์ชัน int half(int x) คืนครึ่งหนึ่งของ x เรียก half(300) ควรได้ 150
  2. เพิ่มพารามิเตอร์ที่มีค่าเริ่มต้น int half(int x, int divisor = 2) แล้วเรียก half(300, 3) (คำตอบ: 100)
  3. เปิดไฟล์ Test_ball.ino นับดูว่ามีการเรียก release_servo_angle กี่ครั้ง แล้วลองคิดว่าถ้าเก็บค่าพัลส์ไว้ในอาร์เรย์ จะสั้นลงอย่างไร

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

  • เลขลำดับเกิน forwardDistanceCM[4] อ่านนอกอาร์เรย์ ได้ค่ามั่ว ไม่มี error ให้
  • ลืม return ฟังก์ชันที่ต้องคืนค่า คอมไพล์ผ่านแต่ผลที่ได้เป็นค่าสุ่ม
  • ใส่วงเล็บตอนส่งฟังก์ชันไปผูกปุ่ม เรียกทันทีแทนที่จะรอปุ่ม
  • ประกาศฟังก์ชันหลังใช้ Arduino IDE จัดการให้ในไฟล์ .ino แต่ใน .cpp ต้องเขียนประกาศไว้ก่อน
  • ชื่อฟังก์ชันซ้ำข้ามไฟล์ .ino ดู โครงสร้างโปรเจกต์

On this page