C in ModusToolBox (MTB)

1. โครงสร้างโปรแกรม C ใน ModusToolbox

1.1 Header Files และ Include

การจัดการ #include ให้เป็นระบบเป็นพื้นฐานที่สำคัญมากในการเขียน Embedded C โดยเฉพาะบน Infineon PSoC™ Edge E84 ที่ใช้ ModusToolbox ซึ่งมีทั้ง HAL, BSP และ Peripheral Driver Library (PDL)

การจัดกลุ่ม header ที่ดี จะช่วยให้:

  • โค้ดอ่านง่ายและดูเป็นมืออาชีพ

  • ลดการพึ่งพาไฟล์ที่ไม่จำเป็น

  • ลดโอกาสเกิดปัญหา compile และ dependency ที่ซับซ้อนในอนาคต


circle-info

แนวคิด (Concept)

ใน ModusToolbox ควรแบ่ง #include ออกเป็น 2 กลุ่มหลัก อย่างชัดเจน:

  1. System headers เป็น header จาก C standard library ใช้กับภาษา C ทั่วไป

  2. Infineon / PSoC platform headers เป็น header ที่เกี่ยวข้องกับ hardware, board และ middleware ของ Infineon

การเรียงลำดับที่แนะนำคือ 👉 System headers ก่อนPlatform headers ตามหลัง


ตัวอย่างโครงสร้าง #include ที่แนะนำ

/* ===================== System headers ===================== */
#include <stdio.h>      /* printf */
#include <stdint.h>     /* uint32_t, int16_t */
#include <stdbool.h>    /* bool, true, false */
#include <string.h>     /* memcpy, strlen */

/* ================= Infineon / PSoC headers ================= */
#include "cy_pdl.h"         /* Peripheral Driver Library */
#include "cyhal.h"          /* Hardware Abstraction Layer */
#include "cybsp.h"          /* Board Support Package */
#include "cy_retarget_io.h" /* UART printf redirection */

Best Practice

  • Include เฉพาะ header ที่ใช้งานจริงเท่านั้น

  • แยกกลุ่ม System กับ Platform ให้ชัดเจนเสมอ

  • อย่า include header ซ้ำซ้อนโดยไม่จำเป็น

  • หลีกเลี่ยงการ include header ใน .h ถ้าไม่จำเป็น (ควร include ใน .c แทน)


circle-exclamation

ข้อควรระวัง (Pitfall)


📘 MISRA-C Note (สำหรับงานอุตสาหกรรม)

  • MISRA-C แนะนำให้ include header เท่าที่จำเป็นเท่านั้น

  • Header ควรมี include guard หรือ #pragma once

  • หลีกเลี่ยงการ include header ที่มี side effect (เช่น มี code execution)


สรุปสั้น ๆ

การจัด #include ที่ดี คือจุดเริ่มต้นของ firmware ที่ดูแลรักษาง่าย และขยายต่อได้ในระยะยาว

1.2 Simple Define (#define สำหรับค่าคงที่)

การใช้ #define สำหรับกำหนดค่าคงที่ (constant) เป็นสิ่งที่พบได้บ่อยมากในงาน Embedded C โดยเฉพาะบน PSoC™ Edge E84 ที่ต้องจัดการกับ GPIO, communication, sensor และ hardware configuration จำนวนมาก

หากใช้ #define อย่างเป็นระบบ จะช่วยให้โค้ด:

  • อ่านง่าย

  • แก้ไขง่าย

  • ลด bug จากการใช้ magic number


circle-info

แนวคิด (Concept)

#define เหมาะสำหรับ:

  • ค่าคงที่ที่ ไม่เปลี่ยนระหว่าง runtime

  • ค่า configuration ที่เกี่ยวข้องกับ hardware

  • ค่า parameter ที่ต้องใช้ซ้ำหลายจุดในโค้ด

ชื่อของ macro ควร:

  • ใช้ ตัวพิมพ์ใหญ่ทั้งหมด

  • ใช้ _ คั่นคำ

  • สื่อความหมายชัดเจน


ตัวอย่าง Simple Define พื้นฐาน

GPIO Pin Configuration

(ตัวอย่างจาก hello-world project)


Communication Settings


Sensor Configuration

(ตัวอย่างจาก BMI270 driver)


Application Constants


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

จาก ml-deepcraft-data-collection


Accelerometer Ranges (BMI270)


Gyroscope Conversion Factors


ตัวอย่างจาก wifi-udp-server


Best Practice

  • ใช้ U, UL, f ให้เหมาะกับชนิดข้อมูล (type)

  • หลีกเลี่ยง magic number ในโค้ด

  • ใช้ชื่อ macro ที่อธิบายความหมายได้ด้วยตัวมันเอง

  • กลุ่ม macro ตามหน้าที่ (GPIO / Sensor / Network / App)


circle-exclamation

ข้อควรระวัง (Pitfall)


📘 MISRA-C Note (สำหรับงานอุตสาหกรรม)

  • MISRA-C อนุญาตให้ใช้ #define สำหรับ constant

  • ควรหลีกเลี่ยง #define ที่ซับซ้อนหรือมี logic

  • ค่าคงที่ที่เป็นกลุ่มเดียวกัน ควรถูกรวมไว้ใน header เดียว


สรุปสั้น ๆ

ใช้ #define เพื่อแทนค่าคงที่เท่านั้น ถ้าเริ่มมี logic → พิจารณาใช้ static inline function แทน

1.3 การใช้งานจริงของ Function-like Macros

Function-like macro เป็นเครื่องมือที่ถูกใช้บ่อยมากในงาน Embedded C โดยเฉพาะงานที่เกี่ยวข้องกับ register control, sensor data processing และ performance-critical code

อย่างไรก็ตาม macro ประเภทนี้ก็เป็นหนึ่งในแหล่งกำเนิด bug ที่พบบ่อยที่สุดเช่นกัน หากไม่เข้าใจข้อจำกัดและใช้งานอย่างระมัดระวัง


circle-info

แนวคิด (Concept)

Function-like macro คือ macro ที่มีลักษณะเหมือน function แต่ถูกแทนค่าตั้งแต่ขั้นตอน preprocessing จึง ไม่มี type checking และ ไม่มีการป้องกัน side effect

จุดเด่นคือ:

  • เร็ว (ไม่มี function call overhead)

  • ใช้กับ type ใดก็ได้ (type-generic)

  • เหมาะกับ logic สั้น ๆ


ตัวอย่าง Function-like Macros ที่ใช้บ่อย


ตัวอย่างการใช้งานจริง (Real-world Usage)

Sensor data validation


Array iteration (ป้องกัน out-of-bounds)


GPIO register manipulation


Temperature range limit


FreeRTOS delay conversion


ตัวอย่างจากโค้ดจริงของ Infineon (IMU)


Best Practice

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

  • ใช้ macro กับ logic สั้นและชัดเจนเท่านั้น

  • ใช้ชื่อ macro เป็น ตัวพิมพ์ใหญ่ทั้งหมด

  • ใช้ macro สำหรับ register / bit manipulation จะเหมาะที่สุด


circle-exclamation

ข้อควรระวัง (Pitfall)


⚖️ Macro vs Inline Function

Aspect
Macro
Inline Function

Type safety

❌ No

✅ Yes

Debugging

❌ Hard

✅ Easy

Side effect

❌ Dangerous

✅ Safe

Performance

✅ Fast

✅ Fast


📘 MISRA-C Note (สำหรับงานอุตสาหกรรม)

  • MISRA-C แนะนำให้ หลีกเลี่ยง function-like macro

  • อนุญาตเฉพาะ macro ที่:

    • ไม่มี side effect

    • ใช้กับ constant หรือ register

  • Logic ที่ซับซ้อนควรใช้ static inline function แทน


สรุปสั้น ๆ

Function-like macro ทรงพลัง แต่ก็อันตราย ถ้าเริ่มคิดนาน → อย่าใช้ macro

1.4 โครงสร้าง main() ที่แนะนำสำหรับ PSoC™ Edge E84

ฟังก์ชัน main() คือจุดเริ่มต้นของทุกโปรแกรม C ในงาน Embedded โดยเฉพาะบน Infineon PSoC™ Edge E84 การจัดโครงสร้าง main() ให้ถูกต้องตั้งแต่ต้น จะช่วยให้ระบบ:

  • ทำงานเสถียร

  • ประหยัดพลังงาน

  • Debug และขยายระบบได้ง่ายในอนาคต


circle-info

แนวคิด (Concept)

main() ในระบบ Embedded ไม่ใช่โปรแกรมที่จบแล้ว exit แต่เป็นจุดที่:

  1. Initialize hardware และ system

  2. Enable interrupt

  3. เข้าสู่ main loop ที่ทำงานตลอดอายุของอุปกรณ์

ใน ModusToolbox โครงสร้าง main() ที่ดีควร:

  • แยกขั้นตอน init อย่างชัดเจน

  • ตรวจสอบ error ทุกครั้งที่ init

  • รองรับ Low power operation


โครงสร้าง main() พื้นฐานที่แนะนำ


คำอธิบายแต่ละขั้นตอน

1. cybsp_init()

  • Initialize clock, pins, peripherals ตาม BSP

  • ต้อง check return value เสมอ

  • ถ้าล้มเหลว → ระบบไม่ควรทำงานต่อ

2. __enable_irq()

  • เปิด global interrupt

  • ต้องเรียกหลังจาก init hardware เสร็จแล้ว

  • ถ้าลืมเรียก → interrupt ทั้งระบบจะไม่ทำงาน

3. cy_retarget_io_init() (optional)

  • ใช้ redirect printf() ไปที่ UART

  • เหมาะสำหรับ debug / development

  • ไม่จำเป็นใน production build เสมอไป

4. Application Init / Task

  • ควรแยก logic ออกเป็น function

  • หลีกเลี่ยงการเขียน code ยาว ๆ ใน main()


ตัวอย่างจริงจาก Infineon (hello-world project)


Best Practice

  • ตรวจสอบ return value ของ cybsp_init() ทุกครั้ง

  • เรียก __enable_irq() หลัง init เสมอ

  • แยก application logic ออกเป็น function (app_init(), app_task())

  • ใช้ Deep Sleep ใน main loop เมื่อไม่มีงาน


circle-exclamation

ข้อควรระวัง (Pitfall)


📘 MISRA-C Note (สำหรับงานอุตสาหกรรม)

  • main() ต้องมี signature ชัดเจน (int main(void))

  • ไม่ควรมี code ที่ไม่ reachable

  • Infinite loop เป็นสิ่งที่ ยอมรับได้ ใน Embedded

  • ควรหลีกเลี่ยง logic ซับซ้อนใน main()


สรุปสั้น ๆ

main() ที่ดี = init ชัดเจน + check error + sleep เมื่อว่าง คือหัวใจของ Embedded firmware ที่เสถียรและประหยัดพลังงาน

1.5 ANSI Escape Sequence สำหรับ Console Debug

ในระหว่างการพัฒนา Embedded firmware นักพัฒนามักใช้ printf() ผ่าน UART เพื่อ debug ค่า sensor, state machine หรือ system status

ANSI Escape Sequence คือชุดรหัสควบคุมพิเศษ ที่ทำให้ terminal สามารถ:

  • ล้างหน้าจอ

  • ย้ายตำแหน่ง cursor

  • เปลี่ยนสีตัวอักษร

  • จัด layout ข้อมูลแบบ real-time

ซึ่งมีประโยชน์มากในช่วง development & debugging

circle-info

แนวคิด (Concept)

ANSI Escape Sequence เป็น string ที่ขึ้นต้นด้วย ESC character (ASCII 27) ในภาษา C เขียนเป็น:

รูปแบบทั่วไป:

ตัวอย่าง:

Terminal ที่รองรับ: Tera Term, PuTTY, Linux terminal Terminal ที่อาจไม่รองรับ: บาง Serial Monitor แบบง่าย


ตัวอย่างพื้นฐานที่ใช้บ่อยที่สุด

ล้างหน้าจอ + ย้าย cursor กลับจุดเริ่มต้น

อธิบาย:


ตัวอย่างการใช้งานจริง (Real-world Usage)

แสดงค่า sensor แบบ refresh หน้าจอ

ผลลัพธ์:

  • หน้าจอไม่เลื่อนยาว

  • ดูเหมือน dashboard แบบ real-time


เปลี่ยนสีข้อความ (optional)

สีที่ใช้บ่อย:

  • 31 = Red

  • 32 = Green

  • 33 = Yellow

  • 34 = Blue

  • 0 = Reset


ทำไม ANSI Escape ถึงเหมาะกับ Embedded Debug

  • ไม่ต้องใช้ GUI

  • ใช้ resource น้อย

  • เหมาะกับ board ที่ไม่มีจอ

  • Debug ได้ผ่าน UART ธรรมดา

เหมาะมากกับ:

  • Sensor bring-up

  • Power consumption debug

  • State machine monitoring


Best Practice

  • ใช้ ANSI Escape เฉพาะใน debug build

  • แยก debug output ด้วย macro

  • ใช้ร่วมกับ Cy_SysLib_Delay() หรือ sleep เพื่อลด UART spam


circle-exclamation

ข้อควรระวัง (Pitfall)


⚖️ ANSI Escape vs GUI Debug

Aspect
ANSI Escape
GUI Tool

Resource

ต่ำมาก

สูง

Setup

ง่าย

ซับซ้อน

Flexibility

ปานกลาง

สูง

Production ready


circle-check

MISRA-C Note (สำหรับงานอุตสาหกรรม)


สรุปสั้น ๆ

ANSI Escape = เครื่องมือ debug ที่ดีมาก แต่ควรใช้ เฉพาะตอนพัฒนา ไม่ใช่ solution ระยะยาวสำหรับ production system

1.6 Coding Style จากโค้ดตัวอย่างจริงของ Infineon (Hello-world & MQTT Multi-tasking)

หัวข้อนี้เป็นการ สรุปแนวทางการเขียน C ที่ Infineon ใช้จริง โดยอ้างอิงจากตัวอย่างทางการ เช่น:

  • mtb-example-psoc-edge-hello-world

  • ตัวอย่าง IoT / MQTT client ที่ใช้ FreeRTOS multi-tasking

เป้าหมายไม่ใช่แค่ “โค้ดรันได้” แต่คือ เขียนให้ตรง mindset ของ Infineon และงานอุตสาหกรรม


โครงสร้างโปรเจกต์ (Project Structure Mindset)

circle-info

แนวคิด (Concept)

จากตัวอย่างจริงของ Infineon จะเห็น pattern ชัดเจนว่า:

  • main.c → ทำหน้าที่ orchestration

  • logic งานจริง → แยกเป็น module

  • hardware config → มาจาก BSP / Config Tool

  • middleware → จัดการผ่าน Library Manager

โค้ด ไม่กระจุกอยู่ไฟล์เดียว

โครงสร้างที่พบได้บ่อย


Best Practice

  • main.c ไม่ควรยาว

  • Application logic ควรอยู่ใน app_xxx.c

  • Hardware-specific code ไม่ควรปนกับ business logic

  • ให้ Config Tool เป็นเจ้าของไฟล์ config (ไม่แก้มือ)


circle-exclamation

Pitfall


Pattern ของ main() จาก Hello-world (Bare-metal Style)

circle-info

แนวคิด (Concept)

จาก hello-world ของ Infineon main() มีหน้าที่ชัดเจน 3 อย่าง:

  1. Initialize system

  2. Enable interrupt

  3. เข้าสู่ loop ที่ประหยัดพลังงาน


Pattern ที่พบจริง


Best Practice

  • check return value ทุกครั้ง

  • error ร้ายแรง → หยุดระบบทันที

  • idle loop ต้อง sleep เสมอ


MISRA-C Note

  • Infinite loop ใน main() → ยอมรับได้

  • หลีกเลี่ยง code ที่ unreachable

  • main() ไม่ควรมี logic ซับซ้อน


สรุป 1.6

ถ้าเขียนโค้ดให้ โครงสร้างเหมือนตัวอย่างของ Infineon แปลว่าโค้ดคุณ “พร้อมต่อยอดเป็น production ได้จริง”

triangle-exclamation

Common Mistakes ที่ควรหลีกเลี่ยง


Last updated

Was this helpful?