phone: 02-954-2408-9, 089-514-8111

ความรู้เกี่ยวกับการเขียนโปรแกรมภาษา C ที่นักอิเล็กทรอนิกส์ไม่ค่อยรู้ ตอนที่ 7 ประโยชน์ของ Structure (ภาคต่อ)

Mar 11,2017

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

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

 

The C Programming Language logo

 

        การใช้ structure ยังมีข้อดีในการช่วยทำให้การส่งผ่าน input เข้าไปใน function มีความยืดหยุ่นมากขึ้น ยกตัวอย่างเช่น ถ้าเราต้องการสร้าง function ที่ใช้กำหนดค่าเริ่มต้นให้ serial port ใช้งานได้ ในตอนแรก เราอาจจะคิดง่ายๆแค่ว่าเราต้องกำหนด baud rate, จำนวน bit ในการส่ง, จะใช้ parity bit หรือไม่ และจะใช้ stop bit กี่ bit แค่นี้เราก็ต้องสร้าง function ที่มี input ถึง 4 ตัวด้วยกัน เราอาจจะเขียน prototype ไว้ดังนี้

void serial_init(int baud_rate, unsigned char nbit, unsigned char parity, unsigned char nstop_bit); //function กำหนดค่า serial port โดยรับค่า baud rate, จำนวน bit, การใช้ parity bit, จำนวน stop bit

        โดยในการใช้งาน เราอาจจะเรียกใช้ได้ดังนี้

serial_init(9600,8,0,1); //กำหนดค่า serial port โดยให้มี baud rate เป็น 9600, ส่งข้อมูลทีละ 8 bit, ไม่ใช้ parity bit และมี stop bit 1 bit

        เราสามารถใช้ function นี้ได้เรื่อยๆ ตราบใดที่เราไม่ต้องการกำหนด parameter อื่นๆของ serial port แต่ถ้าวันหนึ่งที่เราเกิดจะต้องใช้ interrupt ของ serial port ขึ้นมา เราอาจใช้การสร้าง function ขึ้นมาเพิ่ม เพื่อกำหนดเกี่ยวกับการใช้งาน interrupt โดยเฉพาะ, แก้ไข function เดิม หรือว่าจะสร้าง function ขึ้นมาอีกอันเพื่อทำงานทั้งหมด
        เราอาจจะใช้วิธีไหนก็ได้ สามารถใช้งานได้ทั้งสิ้น แต่บางครั้ง เราอาจจะต้องการใช้ library ที่เราสร้างขึ้นมา ร่วมกับ project อื่นๆที่เราเคยทำไว้ ซึ่งการแก้ไข function เดิมด้วยการเพิ่มตัวแปรเข้าไปอีก จะทำให้การเรียกใช้ที่มีอยู่เดิมใช้ไม่ได้ ทำให้เราต้องไปตามแก้ไขการเรียกใช้ด้วย ดังนั้นเราอาจจะเลือกที่จะสร้าง function ใหม่ขึ้นมาอีกอัน แต่ก็จะทำให้เรามี function เยอะเกินความจำเป็น
        แต่ถ้าเราเลือกที่จะเขียนโดยใช้ structure เราจะลดปัญหาเกี่ยวกับ compatibility ของโปรแกรมนี้ได้ โดยเราสามารถสร้าง struct ไว้ดังนี้

1
2
3
4
5
6
typedef struct {
    int baud_rate;
    unsigned char nbit;
    unsigned char parity;
    unsigned char nstop_bit;
} serial_struct;

        ในการสร้าง function ขึ้นมากำหนดค่า serial port เราจะสามารถเขียนได้ง่ายๆดังนี้

1
void serial_init(serial_struct a);

        โดยในการใช้งาน เราแค่ต้องกำหนดค่า field ต่างๆ ของ serial_struct ให้เรียบร้อย ก่อนที่จะเรียกใช้ serial_init

1
2
3
4
5
6
serial_struct serial;
serial.baud_rate = 9600;
serial.nbit = 8;
serial.parity = 0;
serial.nstop_bit = 1;
serial_init(serial);

        ทีนี้แม้ว่าภายหลังเราจะเพิ่ม parameter หรือเงื่อนไขอื่นๆเข้าไป เราก็แค่เพิ่ม field เข้าไปใน serial_struct โดยที่ไม่ต้องแก้ไข prototype ของ function
        อย่างไรก็ตาม ปัญหาที่จะตามมาก็คือเราไม่สามารถกำหนดค่าเริ่มต้นของ structure ไว้ก่อนได้ ดังนั้น field ที่เราเพิ่มขึ้นมาภายหลัง จะไม่ถูก set เอาไว้ก่อน วิธีแก้ก็คือ เราควรจะสร้าง function สำหรับกำหนดค่า default สำหรับ structure ไว้อีกทีหนึ่ง ซึ่งจะช่วยให้เราไม่ต้องคอยกำหนดค่าสำหรับ field ทุก field ในการใช้งาน structure นี้ทุกครั้ง ซึ่งหน้าตาของ function อาจจะเป็นดังนี้

1
2
3
4
5
6
void set_serial_ default(serial_struct *a) {
    a->baud_rate = 9600; //เราจะใช้ -> แทน . สำหรับ pointer ของ structure
    a->nbit = 8;
    a->parity = 0;
    a->nstop_bit = 1;
}

        

การเรียกใช้งานจะเป็นดังนี้?

 

1
2
3
serial_struct serial;
set_serial_ default(&serial);
serial_init(serial);

        ทีนี้จะเห็นว่าถ้าเราจะใช้ parameter แบบที่เหมือนกับ default เราก็ไม่ต้องมากำหนด field พวกนี้อีก และสมมติว่าเราต้องการเพิ่ม field เข้าไปภายหลัง ที่เราต้องทำก็แค่กำหนดค่าเริ่มต้นของ field ที่เพิ่มมาไว้ใน function set_serial_ default คราวนี้เราก็จะไม่ต้องตามไปแก้ไขกับ code เดิมที่เคยเรียกใช้
        ถึงตอนนี้เราก็สามารถ upgrade function ของเราได้เรื่อยๆแล้ว แต่ยังมีอีกจุดหนึ่งที่เราควรจะแก้ไขอยู่ นั่นก็คือ วิธีการส่ง structure เข้าไปใน function serial_init นั่นเอง
        ตอนนี้เราได้ใช้วิธีการส่งข้อมูลเข้าไปใน function ทั้ง 2 แบบ คือเราใช้การ pass data by value กับ function serial_init และเราใช้การ pass data by reference (หรือตำแหน่งของข้อมูล) กับ function set_serial_ default ซึ่งข้อดีของการ pass data by value ก็คือค่าของตัวแปรเดิมจะไม่ถูกเปลี่ยนแปลง ส่วนข้อเสียก็คือ ตัวแปรที่เป็นตัวรับค่า input ของ function จะต้องถูกสร้างขึ้นมาใหม่ อย่างใน function serial_init

1
void serial_init(serial_struct a);

        ตัวแปร serial_struct a จะถูกสร้างขึ้นมาเป็น local variable เมื่อมีการเข้ามาใน function นี้ และเนื่องจากมันเป็น structure ที่มี field ของข้อมูลหลาย field จึงกินพื้นที่ของ RAM หลาย byte ไปด้วย ซึ่งในที่นี้จะใช้ 8 byte ตามขนาดของ serial_struct (ดูวิธีการหาขนาดของ structure ได้จากบทความตอนที่ 6) ลองนึกดูว่าถ้าเราใช้ structure ที่มีขนาดใหญ่กว่านี้ ก็จะยิ่งกินพื้นที่ RAM เข้าไปอีก ทั้งๆที่เราต้องการแค่ใช้ค่าที่ set ไว้แล้ว ของตัวแปร serial ในการกำหนดค่า serial port ซึ่งตัวแปร serial ก็ได้ถูกจองพื้นที่ไว้แล้ว เราจึงไม่ต้องการให้ใช้พื้นที่ซ้ำซ้อนเข้าไปอีก (ถึงแม้พื้นที่ดังกล่าว จะถูกคืนหลังจากออกจาก function แต่หากเรามีการเรียก function อื่นๆข้างในอีก RAM ก็จะถูกกินพื้นที่เพิ่มขึ้นไปเรื่อยๆ จนอาจจะถึงขึ้นไม่พอ และทำให้เกิดการทำงานที่ผิดพลาดได้ เราจึงควรระมัดระวังในการสร้าง local variable ที่มีขนาดใหญ่เอาไว้ก่อน) นอกจากนี้ค่าของตัวแปร serial ยังต้องถูก copy มาไว้ใน a ซึ่งก็ต้องใช้การทำงานที่มากขึ้นของ MCU ดังนั้น ยิ่ง struct มีขนาดใหญ่ ก็จะยิ่งเปลือง RAM และเวลาในการ copy ข้อมูล 
        สิ่งที่เราควรทำ จึงเป็นการส่งข้อมูล structure ด้วยตำแหน่งเหมือนกับ function set_serial_ default ซึ่งเราจะสามารถเขียน function ได้ดังนี้

1
void serial_init(serial_struct *a);

        ทีนี้เราจะสามารถเรียกใช้งานได้ดังนี้
serial_struct serial; //ตัวแปร serial ถูกสร้างขึ้น
set_serial_ default(&serial); //สำหรับ function นี้เราใช้การส่งตำแหน่งอยู่แล้ว
serial_init(&serial); //เราส่งตำแหน่งของ serial เข้าไปใน function แทน ทำให้ไม่ต้องมีการจองที่ให้กับตัวแปร serial_struct ขึ้นมาอีกตัว
        และนี่คืออีกเหตุผลหนึ่งที่ทำให้การใช้ pointer ให้เป็นและชำนาญ มีความสำคัญมากกับการเขียนโปรแกรมบน microcontroller สำหรับในบทความตอนถัดไป ซึ่งเป็นตอนสุดท้าย จะเป็นการรวมเอาเกร็ดความรู้เล็กๆน้อยๆอื่นๆที่เกี่ยวกับการเขียนโปรแกรมภาษา C มาฝากกันอีก ฝากติดตามด้วยนะครับ

 

<< กลับไปสู่หน้าแรกสารบัญ