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 ไบต์ ทั้งหมด (ไม่ใช่กิโลไบต์หลายร้อย) การเลือกขนาดพอดีจึงมีผลจริง
| ชนิด | ไบต์ | ช่วงค่า | ตัวอย่างในโค้ดชมรม |
|---|---|---|---|
bool | 1 | true / false | bool to_right ใน yaw_soft_stop |
uint8_t | 1 | 0 – 255 | uint8_t speed ของ move_forward_cm |
int | 2 | −32,768 – 32,767 | int forwardDistanceCM[4] |
uint16_t | 2 | 0 – 65,535 | HEIGHT_SHOOT, pulse |
int16_t | 2 | −32,768 – 32,767 | set_pos(int16_t x, int16_t y) (ติดลบได้) |
uint32_t | 4 | 0 – 4,294,967,295 | uint32_t t0 = millis() |
float | 4 | ทศนิยม ~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 PORT4const/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 แค่ครั้งแรกเท่านั้น
ลองทำ
- ใน Serial Monitor ลอง
uint8_t x = 250; x = x + 10; Serial.println(x);ควรได้ 4 (250 + 10 = 260, 260 − 256 = 4) - คำนวณด้วยมือ:
yaw_soft_stop(true, 90)แยกมุมเป็น a กับ b เท่าไร (คำตอบ: a = 90×7/10 = 63, b = 27) - หาในไฟล์จริงว่าตัวแปรโกลบอลอะไรบ้างอยู่ใน
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