🧩 ภาพรวม

Variable Scope (ขอบเขตของตัวแปร) + Variable Qualifiers (คุณสมบัติตัวแปร)
ใช้ได้ทั้ง Arduino UNO (C++) และ ESP32 (C++/FreeRTOS)


🔹 1. Variable Scope คืออะไร

Scope (ขอบเขต) หมายถึง “พื้นที่ในโปรแกรมที่สามารถเข้าถึงตัวแปรได้”
หรือพูดง่าย ๆ ว่า

ตัวแปรตัวนี้ “มองเห็นและใช้ได้” ที่ไหนบ้าง


🔸 ประเภทของ Scope

ประเภทอยู่ที่ไหนอายุการใช้งานใช้ได้ที่ไหน
Local Variableประกาศในฟังก์ชันเมื่อฟังก์ชันทำงาน → หายเมื่อออกใช้ได้เฉพาะในฟังก์ชันนั้น
Global Variableประกาศนอกฟังก์ชันอยู่ตลอดโปรแกรมใช้ได้ทุกฟังก์ชัน
Block Variableประกาศใน { ... } ภายในอยู่จนกว่าจะออกจากบล็อกใช้ได้เฉพาะในบล็อกนั้น
Static Local Variableประกาศในฟังก์ชัน + staticอยู่คงที่ตลอดโปรแกรม แต่ใช้ได้เฉพาะในฟังก์ชันเก็บค่าครั้งล่าสุดไว้แม้ออกจากฟังก์ชันแล้ว

🔹 1.1 ตัวอย่าง Local Variable

C++
void setup() {
  int a = 10;         // ตัวแปรภายใน setup()
  Serial.begin(9600);
  Serial.println(a);  // ใช้ได้
}

void loop() {
  // Serial.println(a);  ❌ error: มองไม่เห็นตัวแปร a
}

ตัวแปร a ใช้ได้เฉพาะใน setup() เท่านั้น


🔹 1.2 ตัวอย่าง Global Variable

C++
int counter = 0;   // ประกาศนอกฟังก์ชัน (global)

void setup() {
  Serial.begin(9600);
}

void loop() {
  counter++;                // ใช้ได้ทุกที่
  Serial.println(counter);  // แสดงค่าที่เพิ่มขึ้นเรื่อย ๆ
  delay(1000);
}

Global อยู่ในหน่วยความจำตลอดเวลา ใช้ร่วมกันได้ทุกฟังก์ชัน


🔹 1.3 ตัวอย่าง Block Variable

C++
void loop() {
  if (true) {
    int x = 5;          // อยู่ใน block นี้เท่านั้น
    Serial.println(x);
  }
  // Serial.println(x); ❌ ใช้ไม่ได้
}

🔹 1.4 ตัวอย่าง Static Local Variable

C++
void blinkCounter() {
  static int count = 0;   // ค่าคงอยู่แม้ออกจากฟังก์ชัน
  count++;
  Serial.println(count);
}

void loop() {
  blinkCounter();  // เรียกทุก 1 วินาที
  delay(1000);
}

ผลลัพธ์:

Zsh
1
2
3
4
...

ถ้าไม่ใส่ static ค่านับจะเริ่มใหม่ทุกครั้งที่เรียกฟังก์ชัน


🔹 2. การซ่อนตัวแปร (Variable Shadowing)

ถ้าประกาศชื่อเดียวกันทั้งใน local และ global → ตัวแปร “ในบล็อก” จะถูกใช้งานแทน

C++
int num = 100;  // global

void loop() {
  int num = 5;  // local ซ้อนชื่อกัน
  Serial.println(num);  // แสดง 5 (ไม่ใช่ 100)
}

ถ้าอยากเข้าถึง global จริง ๆ ให้ใช้ :: เช่น Serial.println(::num);


🔹 3. การเก็บตัวแปรไว้ในหน่วยความจำ (Storage Duration)

คีย์เวิร์ดพื้นที่เก็บอายุของตัวแปรตัวอย่าง
(ปกติ)Stackหายเมื่อออกจากฟังก์ชันint x=0;
staticData Segmentอยู่ตลอดโปรแกรมstatic int x=0;
externData Segmentอยู่ตลอดโปรแกรม (ประกาศไว้ที่อื่น)extern int count;
registerRegister CPU (อาจจะจริงหรือไม่)ชั่วคราวในฟังก์ชันregister int i;

🧠 4. Variable Qualifiers คืออะไร

Qualifiers คือ “คุณสมบัติพิเศษ” ที่บอก วิธีการจัดการ ตัวแปร เช่น
ไม่ให้เปลี่ยนค่า, อยู่ในหน่วยความจำพิเศษ, หรือแชร์ระหว่างไฟล์


🔸 ตัวอย่างตัวสำคัญ (ใช้ได้ทั้ง Arduino / ESP32)

Qualifierความหมายตัวอย่างหมายเหตุ
constค่าคงที่ เปลี่ยนไม่ได้const int led = 2;ปลอดภัย
volatileบอก compiler ว่าค่านี้ “อาจเปลี่ยนได้เอง”volatile bool flag;ใช้กับ interrupt
staticคงอยู่ตลอดโปรแกรม แต่ใช้ได้เฉพาะขอบเขตนั้นstatic int count;นับครั้งเรียกฟังก์ชัน
externตัวแปรประกาศไว้ในไฟล์อื่นextern int sensorValue;ใช้ข้ามไฟล์
registerขอให้เก็บในรีจิสเตอร์ CPU (เร็วขึ้น)register byte i;แนะนำให้ปล่อยให้ compiler จัดการเอง
constexpr (ESP32/modern C++)ค่าคงที่คำนวณได้ตอนคอมไพล์constexpr float PI = 3.14159;เร็วมาก

🔹 4.1 ตัวอย่าง volatile

ใช้เมื่อค่าของตัวแปร ถูกเปลี่ยนโดย ISR หรือฮาร์ดแวร์
เช่น ตัวแปรที่อัปเดตใน attachInterrupt()

C++
volatile bool buttonPressed = false;

void IRAM_ATTR onPress() {  // ISR
  buttonPressed = true;
}

void setup() {
  pinMode(15, INPUT_PULLUP);
  attachInterrupt(digitalPinToInterrupt(15), onPress, FALLING);
}

void loop() {
  if (buttonPressed) {
    buttonPressed = false;
    Serial.println("Button Interrupt!");
  }
}

ถ้าไม่ใส่ volatile — Compiler อาจ optimize ทิ้ง เพราะไม่เห็นการเปลี่ยนค่าใน loop()


🔹 4.2 ตัวอย่าง extern

ใช้เมื่อมีหลายไฟล์ (โปรเจกต์ใหญ่) เช่น:

ไฟล์ main.ino

C++
#include "sensor.h"

void setup() {
  Serial.begin(9600);
}

void loop() {
  Serial.println(sensorValue);
  delay(1000);
}

ไฟล์ sensor.h

C++
extern int sensorValue;

ไฟล์ sensor.cpp

C++
int sensorValue = 42;

extern ช่วยให้ไฟล์หลายไฟล์ใช้ตัวแปรเดียวกันโดยไม่ประกาศซ้ำ


🔹 4.3 ตัวอย่าง static

C++
void counter() {
  static int n = 0;
  n++;
  Serial.println(n);
}

void loop() {
  counter();  // จะนับเพิ่มเรื่อย ๆ
  delay(500);
}

ตัวแปร n จะไม่หายไปหลังจบฟังก์ชัน


🔹 4.4 ตัวอย่าง constexpr (ESP32 รองรับ)

C++
constexpr float PI = 3.1415926;
constexpr int LED_PIN = 2;

void setup() {
  pinMode(LED_PIN, OUTPUT);
}

constexpr เหมือน const แต่คำนวณค่าได้ตั้งแต่ตอนคอมไพล์
ประสิทธิภาพสูง เหมาะกับโปรเจกต์ใหญ่ใน ESP32


🔹 5. ความแตกต่างของ “const” vs “#define”

ประเด็น#defineconst
ประมวลผลเมื่อก่อนคอมไพล์ (Preprocessor)ระหว่างคอมไพล์
มีชนิดข้อมูลไหม❌ ไม่มี✅ มี
ตรวจสอบข้อผิดพลาดได้ไหม❌ ไม่ได้✅ ได้
ใช้กับ pointer/struct ได้ไหม❌ ไม่ได้✅ ได้
ควรใช้ในโปรเจกต์ใหญ่ไม่แนะนำแนะนำมาก

🔹 6. ตารางสรุป Scope + Qualifier

ประเภทตัวอย่างอายุการใช้งานเข้าถึงได้จากหมายเหตุ
Localint a=0; ในฟังก์ชันชั่วคราวฟังก์ชันนั้นหายเมื่อออกจากฟังก์ชัน
Globalint a=0; นอกฟังก์ชันตลอดโปรแกรมทุกฟังก์ชันใช้ระวังชนกัน
Static Localstatic int c;ตลอดโปรแกรมฟังก์ชันนั้นจำค่าครั้งล่าสุดได้
Constconst int LED=2;ตลอดโปรแกรมทุกที่ (ตาม scope)ป้องกันการแก้ค่า
Volatilevolatile int x;ตลอดโปรแกรมทุกที่ใช้กับ interrupt
Externextern int x;ตลอดโปรแกรมไฟล์อื่นใช้ข้ามไฟล์
Registerregister int i;ชั่วคราวฟังก์ชันปล่อย compiler จัดการเอง
constexprconstexpr float PI=3.14;ตอนคอมไพล์ทุกที่เร็วและปลอดภัย (ESP32)

🎯 7. ตัวอย่างรวม Scope + Qualifier

C++
int globalCount = 0;             // Global
const int LED_PIN = 2;           // Constant global
volatile bool flag = false;      // ใช้ใน interrupt

void IRAM_ATTR onPress() {       // Interrupt
  flag = true;
}

void setup() {
  pinMode(LED_PIN, OUTPUT);
  pinMode(15, INPUT_PULLUP);
  attachInterrupt(digitalPinToInterrupt(15), onPress, FALLING);
  Serial.begin(115200);
}

void loop() {
  static int localCount = 0;     // Static local
  if (flag) {
    flag = false;
    localCount++;
    globalCount++;
    Serial.printf("Local=%d  Global=%d\n", localCount, globalCount);
    digitalWrite(LED_PIN, !digitalRead(LED_PIN));
  }
}

🧠 8. เพิ่มเติม

  1. ทดลอง “ประกาศตัวแปรชื่อเดียวกัน” ใน setup() กับ loop()
    → สังเกตว่า scope ต่างกัน
  2. สร้างฟังก์ชัน “นับครั้งกดปุ่ม” ด้วย static
  3. ใช้ volatile กับ interrupt แล้วอธิบายว่าทำไมต้องใช้
  4. เขียนโปรแกรม 2 ไฟล์ และใช้ extern ร่วมกัน
  5. ทดลอง const vs #define แล้วดู compile error