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 ที่ซับซ้อนในอนาคต
แนวคิด (Concept)
ใน ModusToolbox ควรแบ่ง #include ออกเป็น 2 กลุ่มหลัก อย่างชัดเจน:
System headers เป็น header จาก C standard library ใช้กับภาษา C ทั่วไป
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แทน)
📘 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
แนวคิด (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)
📘 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 ที่พบบ่อยที่สุดเช่นกัน หากไม่เข้าใจข้อจำกัดและใช้งานอย่างระมัดระวัง
แนวคิด (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 จะเหมาะที่สุด
⚖️ Macro vs 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 และขยายระบบได้ง่ายในอนาคต
แนวคิด (Concept)
main() ในระบบ Embedded ไม่ใช่โปรแกรมที่จบแล้ว exit
แต่เป็นจุดที่:
Initialize hardware และ system
Enable interrupt
เข้าสู่ 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 เมื่อไม่มีงาน
📘 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
แนวคิด (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= Red32= Green33= Yellow34= Blue0= 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
⚖️ ANSI Escape vs GUI Debug
Resource
ต่ำมาก
สูง
Setup
ง่าย
ซับซ้อน
Flexibility
ปานกลาง
สูง
Production ready
❌
✅
สรุปสั้น ๆ
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)
แนวคิด (Concept)
จากตัวอย่างจริงของ Infineon จะเห็น pattern ชัดเจนว่า:
main.c→ ทำหน้าที่ orchestrationlogic งานจริง → แยกเป็น module
hardware config → มาจาก BSP / Config Tool
middleware → จัดการผ่าน Library Manager
โค้ด ไม่กระจุกอยู่ไฟล์เดียว
โครงสร้างที่พบได้บ่อย
Best Practice
main.cไม่ควรยาวApplication logic ควรอยู่ใน
app_xxx.cHardware-specific code ไม่ควรปนกับ business logic
ให้ Config Tool เป็นเจ้าของไฟล์ config (ไม่แก้มือ)
Pattern ของ main() จาก Hello-world (Bare-metal Style)
แนวคิด (Concept)
จาก hello-world ของ Infineon
main() มีหน้าที่ชัดเจน 3 อย่าง:
Initialize system
Enable interrupt
เข้าสู่ 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 ได้จริง”
Last updated