พื้นฐาน Embedded C - ตอนที่ 1

Embedded C Programming Part 1: พื้นฐานสำคัญ


ทำไม Embedded C ต่างจาก C ปกติ?

circle-info

Desktop C vs Embedded C

ดังนั้น: ต้องระวังเรื่อง size, timing, และ hardware access

  • มี OS จัดการให้

  • int = 32/64-bit

  • printf() ใช้ได้เลย

  • ไม่ค่อยสนใจ timing

ทำไมต้องเข้าใจ Embedded C สำหรับ IoT และ Edge AI?

การเขียนโปรแกรม Embedded C สำหรับ IoT และ Edge AI แตกต่างจากการเขียน C บนคอมพิวเตอร์ทั่วไป:

  1. ทรัพยากรจำกัด: RAM และ Flash memory มีจำกัด ต้องเลือกใช้ data types ที่เหมาะสม

  2. Real-time Requirements: ต้องควบคุม timing แม่นยำ เข้าใจ macro และ inline functions

  3. Hardware Interaction: ต้องจัดการ GPIO, Sensors, Peripherals ผ่าน HAL/PDL

  4. Power Efficiency: การเขียนโค้ดที่มีประสิทธิภาพช่วยประหยัดพลังงาน

Applications ในชีวิตจริง:

  • Smart Home: ควบคุม LED, อ่านสถานะปุ่ม, sensors

  • Wearables: อ่านข้อมูล heart rate, accelerometer

  • Industrial IoT: ควบคุม motors, valves, monitoring

  • Edge AI: pre-processing ข้อมูล sensor ก่อนส่ง ML model


ส่วนที่ 1: Data Types

ทำไมต้องใช้ Fixed-Width Types ให้ถูกต้อง?

เหตุผล:

  1. Memory Optimization: ใน Embedded system RAM มีจำกัด (เช่น 512KB - 2MB)

  2. Performance: การใช้ type ที่เล็กเกินไปอาจต้อง typecast บ่อย

  3. Overflow Prevention: ใช้ type ที่เล็กเกินไปทำให้เกิด overflow

  4. Sensor Data: ต้องเลือก type ที่รองรับ range ของ sensor

1.1 ปัญหาของ int ใน Embedded

1.2 Fixed-Width Integer Types (stdint.h)

Standard Integer Types (stdint.h)

Type
Size
Range
ใช้งาน
Real Application

int8_t

1 byte

-128 to 127

Signed 8-bit

Temperature offset (-50°C to +50°C)

uint8_t

1 byte

0 to 255

Unsigned 8-bit

I2C register, LED brightness (0-255)

int16_t

2 bytes

-32768 to 32767

Sensor raw data

IMU accelerometer raw values

uint16_t

2 bytes

0 to 65535

ADC values

12-bit ADC (0-4095), timer counts

int32_t

4 bytes

-2B to 2B

Large signed

Audio samples, GPS coordinates

uint32_t

4 bytes

0 to 4B

Timestamps

System tick count (ms), memory addresses

float

4 bytes

±3.4e38

Decimal

Sensor values in physical units (m/s², °C)

การเปรียบเทียบ: int vs uint

Feature
int16_t (Signed)
uint16_t (Unsigned)

Range

-32768 to +32767

0 to 65535

Use Case

Temperature, accel

ADC values, counters

Overflow

Wraps to negative

Wraps to 0

When to use

ต้องการค่าลบ

ต้องการ range กว้างขึ้น 2 เท่า

Best Practices:

  • ใช้ stdint.h types แทน int, short, long (portable)

  • เลือก type ที่พอดีกับ data range (ไม่เล็กไป ไม่ใหญ่เกิน)

  • ใช้ uint สำหรับ count, index, bit operations

  • ใช้ int สำหรับค่าที่อาจเป็นลบ

  • ใช้ float เฉพาะเมื่อจำเป็น (ช้ากว่า integer)

Common Pitfalls:

1.3 ตัวอย่างจากโค้ดจริง (Hello World)

1.4 Boolean Type (stdbool.h)

1.5 Cypress Result Type (cy_rslt_t)

Pattern: Error Checking


ส่วนที่ 2: Macros

ทำไมต้องใช้ Define/Macro?

ข้อดี (ค่าคงที่และการทำให้โค้ดอ่านง่าย):

  1. Magic Number Elimination: ไม่มีตัวเลขลึกลับกระจายอยู่ในโค้ด

  2. Easy Maintenance: เปลี่ยนค่าที่เดียว แก้ทั้งโปรเจค

  3. Performance: Macro expand ตอน compile-time (ไม่เสีย runtime)

  4. Hardware Configuration: กำหนดค่า pins, addresses, timing parameters

การประยุกต์ใช้จริง:

  • GPIO Pin Configuration (LED, Button, Sensor pins)

  • I2C/SPI Addresses และ Registers

  • Timing Constants (delays, sampling rates)

  • Buffer Sizes และ Thresholds

  • Bit Masks สำหรับ register manipulation

2.1 #define Basics

2.2 ทำไมต้องใส่วงเล็บ?

2.3 ตัวอย่างจากโค้ดจริง

2.4 Function-Like Macros

ทำไมใช้ Macro แทน Function?

  • Performance: ไม่มี function call overhead

  • Type-generic: ทำงานกับหลาย types

  • Compile-time: Expand ตอน compile (ไม่เสีย RAM)

ข้อควรระวัง: Macro ไม่ใช่ Function

2.5 Macro สำหรับ Array Size


ส่วนที่ 3: Functions ใน Embedded C

3.1 Function Declaration และ Definition

3.2 static Keyword

3.3 Function Parameters

3.4 ตัวอย่างจากโค้ดจริง

3.5 Return Values


ส่วนที่ 4: Conditional Compilation

4.1 #if, #ifdef, #ifndef

4.2 การใช้งานจริง


ส่วนที่ 5: Bit Operations

5.1 Bit Shifting

5.2 ใช้กับ Register Configuration


ส่วนที่ 6: Type Casting

6.1 Explicit Casting

6.2 Casting สำหรับ printf


ส่วนที่ 7: การเก็บเวลาด้วย Tick Count

7.1 Tick-Based Timing

7.2 Non-Blocking Delay Pattern


ส่วนที่ 8: Volatile Keyword

8.1 ทำไมต้องใช้ volatile?

  • ปัญหา: Compiler optimize ตัวแปรไว้ใน register ทำให้ main loop ไม่เห็นการเปลี่ยนแปลงจาก ISR

  • ประโยชน์: บังคับให้อ่าน/เขียนจาก memory จริงทุกครั้ง

  • Hardware: ISR และ main code ทำงานแยก context กัน

HOW: ประยุกต์ใช้จริงในอุตสาหกรรม

  • Smart Home: Flag ตรวจจับการเปิดประตู/หน้าต่าง (Interrupt-driven)

  • Wearable: Flag บอกว่าผู้ใช้กดปุ่มบน Smart Watch

  • Industrial: Status flags จาก Emergency Stop button

CAUTION: ข้อควรระวัง

ข้อผิดพลาด
ผลกระทบ
วิธีป้องกัน

ลืมใช้ volatile

Main loop ไม่เห็นการเปลี่ยนแปลง

ใช้กับทุก shared variable

ใช้ volatile มากเกินไป

Performance ลดลง

ใช้เฉพาะกับ ISR-shared variables

Non-atomic access

Race condition

ใช้ critical section สำหรับ multi-byte

8.2 ใช้กับ Hardware Registers


ส่วนที่ 9: สรุปภาพรวม

9.1 Embedded C Mindset: ทำไม Embedded C ถึงไม่เหมือน C บน PC

แม้ภาษาเป็น C เหมือนกัน แต่ “บริบท” ต่างกันมาก

Desktop / PC

  • ทรัพยากรเยอะ (RAM/CPU/Storage)

  • OS ช่วยจัดการหลายอย่าง

  • printf() ใช้ได้ง่าย

  • timing ไม่ค่อย critical

Embedded

  • ทรัพยากรจำกัด (RAM/Flash)

  • บางระบบไม่มี OS (bare-metal) หรือใช้ RTOS

  • timing สำคัญ (real-time behavior)

  • ต้องคุม hardware, interrupt, power

สิ่งสำคัญ: Embedded code ที่ดีต้อง “คาดเดาได้ (predictable)” และ “ควบคุมได้ (controllable)”


9.2 สิ่งที่ต้องจำให้ขึ้นใจจากตอน 1 (Key Takeaways)

(1) Types: ใช้ให้ถูก ตั้งแต่บรรทัดแรก

  • ใช้ stdint.h เพื่อให้ type มีขนาดแน่นอน เช่น uint32_t, int16_t

  • ใช้ stdbool.h สำหรับ bool

  • ระวัง overflow โดยเฉพาะ uint8_t, int16_t

  • float ใช้เมื่อจำเป็นจริง ๆ (เพราะ cost สูงกว่า integer)

(2) Macros: ทรงพลัง แต่ต้อง “ระวัง”

  • #define เหมาะกับ constant และ configuration

  • Function-like macro อันตรายเรื่อง side effect (เช่น x++)

  • ใส่วงเล็บรอบ parameter และ expression เสมอ

  • ถ้า logic เริ่มซับซ้อน → ควรขยับไปใช้ static inline หรือ function

(3) Functions: แยกส่วนเพื่อ maintain ได้

  • Prototype กับ definition ต้องชัดเจน

  • ใช้ static เพื่อจำกัด scope และลด naming conflict

  • เข้าใจ pass-by-value vs pointer

  • return type ที่เหมาะสมช่วยลด bug (เช่นใช้ cy_rslt_t)

(4) Conditional compilation: แยก debug / feature ให้เป็นระบบ

  • #ifdef DEBUG ช่วยให้ debug code ไม่หลุดเข้า production

  • ใช้ #if defined(...) เพื่อรองรับหลาย board / feature

(5) Timing: เลี่ยงการ “หยุดโลก” ด้วย blocking delay

  • Blocking delay ทำให้ระบบไม่ responsive

  • Tick-based timing และ non-blocking pattern คือพื้นฐานของระบบที่ดี

  • ใน RTOS: ใช้ tick count ของ RTOS ตาม pattern ที่เหมาะสม

(6) Volatile: ต้องใช้เมื่อ “ค่าถูกเปลี่ยนจากภายนอก”

  • ISR / hardware register / multi-thread → ต้องพิจารณา volatile

  • volatile ไม่ได้แปลว่า thread-safe แต่ช่วยกัน compiler optimize ผิด


9.3 Checklist ก่อนขยับไปตอน 2

ก่อนเรียนตอน 2 ผู้อ่านควร “ทำได้จริง” อย่างน้อย:


9.4 Next: ตอน 2 จะพาไปอะไร

ตอน 2 จะต่อยอดจากพื้นฐานนี้ไปสู่ “เครื่องมือที่ใช้จริงใน firmware” ได้แก่:

  • struct / typedef เพื่อจัดระเบียบ data

  • array + pointer แบบใช้งานจริง

  • parsing / packing data (protocol, sensor frame)

  • memory layout และ safety basics

ถ้าตอน 1 คือ “อ่านภาษา C ให้คล่อง” ตอน 2 คือ “เริ่มเขียน firmware แบบเป็นระบบ”

สรุป: Quick Reference

Data Types

Macros

Functions

Error Checking

Conditional Compilation

Timing


กลับไปที่: Session 01: GPIO Basics ถัดไป: C02: Structs, Pointers, Arrays

Last updated

Was this helpful?