ชมรมโดรน

5. C++ พื้นฐาน: ตัวแปรและชนิดข้อมูล

ตัวแปรคือกล่องเก็บค่า เลือกขนาดกล่องให้พอดีกับ RAM 2 KB ของ Nano พร้อมค่าคงที่ ตัวดำเนินการ และกับดักการหาร

ยังไม่เคยเขียนโปรแกรม?

คำว่าตัวแปร ฟังก์ชัน วัตถุ และเมธอด อธิบายจากศูนย์ไว้ที่ อ่านโค้ดให้ออก อ่านหน้านั้นก่อนจะง่ายกว่ามาก

ตัวแปร (variable) คือกล่องที่มีชื่อ ใช้เก็บค่าไว้ใช้ทีหลัง ก่อนใช้ต้องบอกว่า "กล่องนี้เก็บอะไร" เรียกว่า ชนิด (type) เพราะชิปต้องจองที่ใน RAM ให้ตรงขนาด

int distance = 100;            // เลขเต็ม เช่น ระยะ cm
float speed = 16.39;           // ทศนิยม
bool isOpen = false;           // จริง/เท็จ
unsigned long startTime = 0;   // เก็บเวลาจาก millis()

อ่านว่า "ชนิด ชื่อ = ค่าเริ่มต้น" ชื่อตั้งเองได้ ห้ามขึ้นต้นด้วยตัวเลข ห้ามเว้นวรรค และพิมพ์เล็ก/ใหญ่ถือว่าต่างกัน (Height ไม่เท่ากับ height)

ตัวอย่างชีวิตจริง: กล่องเก็บของมีหลายขนาด

ซองใส่เหรียญ กล่องดินสอ และลังผลไม้เก็บของต่างขนาด ถ้าเก็บเหรียญบาทเดียวใส่ลังผลไม้ ก็เปลืองที่ ชนิดข้อมูลก็เหมือนกัน ความสูง 100 cm ไม่ต้องใช้กล่องใหญ่เท่าเวลา millis()

1. ขนาดของแต่ละชนิด

Nano มี RAM 2,048 ไบต์ ทั้งหมด (ไม่ใช่กิโลไบต์หลายร้อย) การเลือกขนาดพอดีจึงมีผลจริง

ชนิดข้อมูลที่ใช้ในโค้ดชมรมและขนาดเป็นไบต์uint8_t 1 ไบต์ uint16_t 2 ไบต์ uint32_t และ float 4 ไบต์uint8_t1 ไบต์ · 0 – 255speed, เลขโหมดuint16_t2 ไบต์ · 0 – 65,535ความสูง cm, pulse µsuint32_t4 ไบต์ · 0 – 4,294,967,295เวลาจาก millis()float4 ไบต์ · ทศนิยมMS_PER_CM_FWD = 15.46Nano มี RAM รวมแค่ 2,048 ไบต์ เลือกกล่องให้พอดี
กล่องแต่ละช่องคือ 1 ไบต์ ชนิดที่ชมรมใช้บ่อยกินตั้งแต่ 1 ถึง 4 ไบต์
ชนิดไบต์ช่วงค่าตัวอย่างในโค้ดชมรม
bool1true / falsebool to_right ใน yaw_soft_stop
uint8_t10 – 255uint8_t speed ของ move_forward_cm
int2−32,768 – 32,767int forwardDistanceCM[4]
uint16_t20 – 65,535HEIGHT_SHOOT, pulse
int16_t2−32,768 – 32,767set_pos(int16_t x, int16_t y) (ติดลบได้)
uint32_t40 – 4,294,967,295uint32_t t0 = millis()
float4ทศนิยม ~7 หลักMS_PER_CM_FWD = 15.46f

ตัวอักษร u นำหน้าคือ unsigned แปลว่าไม่มีค่าติดลบ ตัวเลขต่อท้าย 8, 16, 32 คือจำนวนบิต (8 บิต = 1 ไบต์) ชื่อแบบนี้บอกขนาดตรงๆ ชัดกว่า int ที่ขนาดเปลี่ยนตามบอร์ด

ล้น (overflow)

กล่องมีขนาดจำกัด ใส่เกินจะวนกลับ

uint8_t speed = 255;
speed = speed + 1;   // ได้ 0 ไม่ใช่ 256

เหมือนมาตรวัดไมล์ที่เลข 99999 แล้วกลับไป 00000 กับดักที่เจอบ่อยคือเอา unsigned มาลบกันแล้วเก็บกลับใส่ unsigned ผลติดลบจะกลายเป็นเลขใหญ่

uint16_t a = 5, b = 8;
uint16_t d = a - b;   // ได้ 65533 ไม่ใช่ -3

โค้ดชมรมหลบเรื่องนี้ด้วยการเก็บผลต่างลง int ซึ่งติดลบได้ แล้วค่อยใช้ abs()

// shooting.ino (reach_height_shoot_stable)
int diff = (int)h - (int)TARGET;
if (abs(diff) <= TOL) { ... }

(int)h คือ cast แปลงชนิดชั่วคราว ที่นี่มีไว้บอกชัดว่า "ผลต่างตั้งใจให้ติดลบได้"

2. ค่าคงที่: const, constexpr, #define

เลขที่ไม่เปลี่ยนควรมีชื่อ อ่านง่าย และแก้ที่เดียว

// config.hpp
constexpr uint16_t HEIGHT_SHOOT = 120;
const int S_CLOSE = 1500;
#define SHOOT1_PIN PORT4
  • const / constexpr มีชนิด คอมไพล์เจอการแก้ค่าจะ error ให้ (ปลอดภัย)
  • #define แทนที่ข้อความตอนก่อนคอมไพล์ ไม่มีชนิด ไม่กิน RAM ชมรมใช้กับชื่อขา

3. ตัวดำเนินการที่ใช้บ่อย

กลุ่มสัญลักษณ์ตัวอย่างจริง
คำนวณ+ - * / %deg * 7 / 10 ใน yaw_soft_stop
เปรียบเทียบ== != < > <= >=h >= ALT_GUARD_MIN_CM
ตรรกะ&& || !h == 0 || h > 300
กำหนดค่า= += -=h += step
เพิ่ม/ลด 1++ --count++

กับดักการหาร

float f = 10 / 3;      // ได้ 3.0  (หารเลขเต็มก่อน ค่อยแปลงเป็นทศนิยม)
float g = 10.0 / 3;    // ได้ 3.33

เลขเต็มหารเลขเต็มได้เลขเต็ม เศษถูกทิ้ง โค้ดชมรมระวังจุดนี้ใน yaw_soft_stop

// config.hpp
const uint16_t a = (deg * 7) / 10;   // 70% ของมุม คูณก่อน แล้วค่อยหาร
const uint16_t b = deg - a;          // ที่เหลือ

ถ้าเขียน deg * (7 / 10) จะได้ 7 / 10 = 0 มุมหายหมด คูณก่อนหาร แล้วเศษอยู่ตรงที่เหลือ b ไม่หาย

= กับ ==

= คือใส่ค่า == คือถามว่าเท่ากันไหม if (h = 0) ใส่ 0 ลง h แล้วถือว่าเท็จเสมอ คอมไพล์ผ่านแต่ผิด

4. ตัวแปรที่ตัวมันอยู่ที่ไหน

  • โกลบอล ประกาศนอกฟังก์ชัน อยู่ตลอดโปรแกรม ทุกฟังก์ชันเห็น เช่น forwardDistanceCM ใน Drone_Mission.ino
  • โลคอล ประกาศในฟังก์ชัน หายเมื่อฟังก์ชันจบ เช่น h ใน advance_and_poke_straight()
  • static ในฟังก์ชัน อยู่ตลอดโปรแกรม แต่เห็นเฉพาะในฟังก์ชัน เช่น static uint16_t current_height ใน Arduino::smooth_set_height()
// protocol.cpp
void Arduino::smooth_set_height(uint16_t target_height, uint16_t step, uint16_t delay_ms) {
  static uint16_t current_height = 85;
  ...
  current_height = target_height;
}

static ทำให้ current_height จำค่าข้ามการเรียก ครั้งหน้าเรียกใหม่จะเริ่มจาก 85 แค่ครั้งแรกเท่านั้น

ลองทำ

  1. ใน Serial Monitor ลอง uint8_t x = 250; x = x + 10; Serial.println(x); ควรได้ 4 (250 + 10 = 260, 260 − 256 = 4)
  2. คำนวณด้วยมือ: yaw_soft_stop(true, 90) แยกมุมเป็น a กับ b เท่าไร (คำตอบ: a = 90×7/10 = 63, b = 27)
  3. หาในไฟล์จริงว่าตัวแปรโกลบอลอะไรบ้างอยู่ใน Drone_Mission.ino บรรทัดแรกๆ (คำตอบ: forwardDistanceCM, backwardDistanceCM, payload_round10, k2_round)

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

  • ใช้ int เก็บ millis() ค่าล้นที่ 32,767 ms ≈ 33 วินาที ใช้ unsigned long หรือ uint32_t
  • หารเลขเต็มแล้วได้ 0 ใส่ .0 หรือ f ท้ายตัวเลข
  • ใช้ = ใน if ผ่านคอมไพล์แต่เงื่อนไขผิด ตั้งคอมไพเลอร์ให้เตือน (File → Preferences → Compiler warnings: All)
  • ตัวแปรโลคอลแล้วคาดว่าจำค่า ออกจากฟังก์ชันค่าหาย ย้ายเป็นโกลบอลหรือ static
  • RAM เต็ม ขึ้น Low memory available ลดอาร์เรย์ใหญ่หรือย้ายข้อความไป PROGMEM

On this page