ความรู้เกี่ยวกับการเขียนโปรแกรมภาษา C ที่นักอิเล็กทรอนิกส์ไม่ค่อยรู้ ตอนที่ 1 เขียนโปรแกรมให้ยืดหยุ่นด้วย Macro (#define)

ความรู้เกี่ยวกับการเขียนโปรแกรมภาษา C ที่นักอิเล็กทรอนิกส์ไม่ค่อยรู้

ตอนที่ 1 เขียนโปรแกรมให้ยืดหยุ่นด้วย Macro (#define)

นักอิเล็กทรอนิกส์ส่วนใหญ่ อาจจะไม่ได้ศึกษาการเขียนโปรแกรมภาษา C จากในห้องเรียน จึงอาจจะขาดความรู้หรือเทคนิคสำคัญๆ ที่ช่วยให้การเขียนโปรแกรมบน microcontroller มีประสิทธิภาพ ยืดหยุ่น ไม่ซับซ้อน หรือบางครั้ง การขาดความเข้าใจในบางเรื่องก็ทำให้อ่าน code คนอื่นไม่เข้าใจ ดังนั้นในเดือนเมษายน 2558 นี้ ทางทีมงาน ThaiEasyElec จึงได้คัดเอาความรู้และเทคนิคต่างๆในการเขียนโปรแกรมที่คนที่ศึกษาการเขียนโปรแกรมด้วยตนเองมักจะไม่ทราบ มาเขียนเป็นบทความจำนวน 8 ตอนด้วยกัน ซึ่งจะครอบคลุมตั้งแต่การใช้งาน preprocessor, casting, constant ไปจนถึง pointer ว่าแล้วก็มาเริ่มตอนแรกกันเลย

ตอนที่ 1 เขียนโปรแกรมให้ยืดหยุ่นด้วย Macro (#define)

ตอนที่ 2 การ Compile และการ Build คลิกอ่าน!

ตอนที่ 3 เริ่มต้นรู้จักกับตัวแปร Pointer คลิกอ่าน!

ตอนที่ 4 ตัวแปร Pointer (ภาคต่อ) คลิกอ่าน!

ตอนที่ 5 การใช้ Pointer กับ Function คลิกอ่าน!

ตอนที่ 6 การใช้งาน Structure เบื้องต้นคลิกอ่าน!

ตอนที่ 7 ประโยชน์ของ Structure (ภาคต่อ) คลิกอ่าน!

ตอนที่ 8 ความรู้อื่นๆ เกี่ยวกับการเขียนโปรแกรมภาษา C บน Microcontroller คลิกอ่าน!

  

ตอนที่ 1 เขียนโปรแกรมให้ยืดหยุ่นด้วย Macro (#define)

         preprocessor คือพวกชุดคำสั่งที่ขึ้นต้นด้วย ‘#’ เช่น #include, #define เป็นชุดคำสั่งที่ compiler จะทำการแปลข้อความก่อนที่จะ compile ชุดคำสั่งจริง (เช่นการใช้ #include ไฟล์ ก็คือการนำเอา source code ของไฟล์นั้นมาแปะไว้ตรงบรรทัดนั้น ก่อนจะทำการ compile) macro ก็เป็น preprocessor ตัวหนึ่งที่เรียกใช้ด้วย #define ใช้ในการแทนที่ (เหมือนการใช้ replace ใน Microsoft Word) โดย macro นี้ สามารถนำมาใช้ได้ 2 แบบ คือแบบ object-like macro กับ function-like macro

object-like macro ก็คือ macro ที่ใช้แทนที่คำ ตัวอย่างเช่น
#define BUF_SIZE 128 
เป็นการกำหนดขนาดของ buffer โดยคำว่า BUF_SIZE ใน code จะถูกจะแทนที่ด้วยคำว่า 128 ก่อนที่จะ compile 
ดังนั้นถ้าเราประกาศตัวแปรว่า 
unsigned char buf[BUZ_SIZE] 
คำว่า BUF_SIZE ก็จะถูกแทนที่ด้วย 128 ดังนั้นตอนที่ทำการ compile ตัว compiler จะเห็นเป็น
unsigned char buf[128]
หมายเหตุ โดยทั่วไปการ define แบบนี้ จะใช้คำที่เขียนด้วยตัวพิมพ์ใหญ่ เพื่อให้มีความแตกต่างจากตัวแปร

function-like macro ก็คือ macro ที่ใช้แทนที่ แต่จะมีลักษณะเหมือนกับ function ตัวอย่างเช่น
#define average(a,b) ((a+b)/2)
ถ้าใช้เขียน code ว่า
int avg = average(x1,x2)
ตอนที่ทำการ compile ตัว compiler จะเห็นเป็น
int avg = ((x1+x2)/2)

           บางคนอาจจะสงสัยว่า แล้วมันต่างกับ function ยังไง สิ่งที่ต่างกันก็คือ การใช้ function ต้องมีการกระโดดเข้าไปทำงาน แล้วจึง return กลับออกมาจึงใช้เวลามากกว่าแต่ก็จะประหยัด code กว่า ส่วนการใช้ macro แบบนี้จะเห็นว่าจะใช้การแทนที่ทุกๆครั้งที่เจอคำว่า average ดังนั้นเมื่อใช้งานใน code แล้วก็จะทำให้ code ยาวขึ้นแต่จะทำงานได้เร็วกว่า 
           นอกจากนี้ เราสามารถ define คำขึ้นมาเฉยๆ แล้วเขียน code เพื่อตรวจสอบว่ามีการ define คำนี้หรือไม่ก็ได้ เช่น
#define DEBUG_MODE
#ifdef DEBUG_MODE
… //code ส่วนที่ 1
#else
… //code ส่วนที่ 2
#endif

           ตัวอย่างการใช้งานในที่นี้คือ ถ้ามีการ define คำว่า DEBUG_MODE ไว้ ก็จะให้ทำงานตามที่เขียนใน code ส่วนที่ 1 แต่ถ้าไม่มีการ define ไว้ ก็จะทำงานใน code ส่วนที่ 2 หรือในทางตรงกันข้าม ถ้าเราไม่ทำการ #define DEBUG_MODE ไว้อาจจะใช้ #ifndef แทนเพื่อตรวจสอบเงื่อนไขที่จะเป็นจริงเมื่อไม่มีการ define 
           จากตัวอย่างนี้ ถ้าถามว่าต่างจากการสร้างตัวแปร debug_mode ขึ้นมาแล้วใช้ if (debug_mode) ยังไง ก็คือถ้าเราใช้ #ifdef DEBUG_MODE แล้ว define DEBUG_MODE ไว้ compiler ก็จะ compile เฉพาะ code ส่วนที่ 1 และจะตัดส่วนที่ 2 ออกไปเลย ทำให้ได้ code ที่สั้นกว่า แต่ถ้าเราใช้ debug_mode เป็นตัวแปร compiler ก็จะ compile code ทั้งหมด ดังนั้นวิธีการเลือกว่าจะใช้แบบไหน จึงอยู่ที่ว่าใน run-time (คือในขณะที่โปรแกรมทำงาน) เราจะมีการปิดเปิดการ debug หรือไม่ ถ้าไม่มีเลย การใช้ macro ก็จะประหยัดพื้นที่ของ code มากกว่า

สรุปประโยชน์ของการใช้ macro (#define)
1. ทำให้เราเข้าใจ code ได้ง่ายขึ้น ถ้าเราเขียนตัวเลขใน code เฉยๆ วันหลังเราอาจจะจำไม่ได้ว่าตัวเลขพวกนั้นมาจากไหน ทำให้เกิด bug ได้
2. แก้ไขเปลี่ยนแปลงในภายหลังได้ง่าย อย่างตัวอย่างที่เรากำหนด BUF_SIZE ถ้าเราต้องการจะเปลี่ยน เราก็ไม่ต้องไปตามเปลี่ยนค่า 128 เป็นค่าใหม่ทุกที่ ทั้งที่ตัวเลข 128 บางที่อาจจะไม่เกี่ยวข้องกับขนาดของ buffer ก็ได้ หรือถ้าใช้กับการกำหนด pin ของ I/O ที่ใช้ควบคุม ในอนาคตก็จะเปลี่ยนแปลงได้ง่าย
3. การใช้ function-like macro โดยส่วนใหญ่ จะใช้เพื่อให้เขียน code ได้ง่ายขึ้น ใช้คำสั่งที่สั้นลง ทำให้เขียน code ได้เร็วขึ้น
4. ใช้กับ code ที่มีการนำไปใช้งานหลายๆรูปแบบได้ดี ไม่ต้องมาคอย comment ด้วย /**/ เช่นตัวอย่างการใช้ #define DEBUG_MODE

ข้อควรระวังในการนำไปใช้งาน
ในการ define ค่าตัวเลขที่มาจากการบวก ลบ คูณ หรือหาร ควรใส่ ( ) ไว้เสมอ เพื่อให้การแทนที่ ไม่ผิดไปจากความตั้งใจ เช่น
#define WIDTH 5
#define HEIGHT 4
#define AREA (WIDTH* HEIGHT)
ค่า AREA อาจถูกนำไปใช้เป็นตัวหาร ถ้าเราไม่ใส่วงเล็บ ลำดับการคำนวณก็จะผิดไปจากที่ควรจะเป็น

           เข้าใจว่าคนที่เขียนโปรแกรมใหม่ๆก็จะเอาเร็ว ให้ทำงานได้ไว้ก่อน จึงมักจะเขียนค่าต่างๆ เช่น pin หรือขนาดของ array ลงไปตรงๆ แต่ถ้าต้องการจะพัฒนาการเขียนโปรแกรมให้เก่งขึ้นแล้วละก็ ลองหัดใช้ define เอาไว้บ้างนะครับ แล้วอย่าลืมติดตามบทความตอนต่อไป เร็วๆนี้ !!!