การทำงานกับข้อมูลระดับบิตในภาษาซี

วันที่โพสต์: Dec 12, 2012 4:50:54 AM

การทำงานกับข้อมูลระดับบิตในภาษาซี

    โดยทั่วไปแล้ว งานเขียนโปรแกรมกับไมโครคอนโทรลเลอร์ งานหลักๆ ก็คือ การสั่ง ON-OFF บิตที่ต้องการบิตใดบิตหนึ่ง ในรีจิสเตอร์ นับว่า เป็นความโชคดีที่ภาษาซี มีการกระทำทางบิต ให้มาแล้ว โดยที่เราไม่ต้องไปกระทำทางภาษา Assembly (ซึ่งเข้าใจยากกว่า) เรามาทำความเข้าใจวิธีการกระทำทางบิต ด้วย Operation แบบต่างๆ ในภาษาซีกันครับ 

a | b  (Bitwise Or) 

เป็นการ OR logic กัน อันนี้ จำง่ายๆ บิตไหน ตรงกัน OR กัน จะได้ 1 เมื่อบิตใด บิตหนึ่งมีค่าเป็น "1" ตัวอย่าง   0x56 | 0x32 ได้ 0x76 เพราะว่า

  0 1 0 1 0 1 1 0 <----  0x56

| 0 0 1 1 0 0 1 0 <----- 0x32

  --------------------

  0 1 1 1 0 1 1 0 <---- 0x76

เรามักใช้ในกรณีที่ต้อง ON บิตใดบิตหนึ่ง หรือหลายๆบิต ในรีจิสเตอร์ที่เราต้องการ ตัวอย่างการนำไปใช้งาน เราเรียกว่า การเซทบิต

PORTA |= 0x80; // ทำให้บิตที่ 7 มีค่าเป็น 1 (บิตอื่นๆเหมือนเดิม ไม่ได้รับผลกระทบ)

หรือ 

PORTA |= (1<<n);  // โดยที่ n คือ ลำดับของบิต ตั้งบิต 0 จนถึงบิตสูงสุดของ PORTA มีค่าเป็น 1 เฉพาะตำแหน่ง 

a & b  (Bitwise And) 

เป็นการ And logic กัน อันนี้ ก็ไม่ยาก เจอบิตใด บิตหนึ่งเป็ศูนย์ ผลลัพธ์ออกมาเป็นลอจิก "0" ตัวอย่าง 0x56 & 0x32 ได้ 0x12  เพราะว่า

   0 1 0 1 0 1 1 0 <----  0x56

& 0 0 1 1 0 0 1 0 <----  0x32

   ----------------------

   0 0 0 1 0 0 1 0 <----  0x12

เรามักใช้ในกรณีต้องการเช็ค ว่า บิตนั้น เป็น "1" หรือไม่ ตัวอย่างการนำไปใช้งาน

if( (PORTA & 0x81) == 0 )  // เงืื่อนไขนี้ ทำการเช็คบิต 7 และบิต 0 ว่าเป็น 1 หรือไม่ ถ้าไม่เป็น 1 ทั้งคู่ให้กระทำอะไรบางอย่าง 

* จำไว้ว่า ต้องใส่วงเล็บ & logic ก่อน นำมาเปรียบเทียบกับ 0 ก่อนนะครับ อันนี้เป็นตกม้าตาย มาหลายคนแหละ เพราะลำดับความสำคัญ มันไม่เท่าก้ัน  เราเลยต้องใส่วงเล็บ ให้ Compiler เข้าไปคิดให้วงเล็บก่อน แล้วค่อยมาเปรียบเทียบกับศูนย์ อีกที 

หรือเรามักพบในตัวอย่างในการเช็คบิต nibble ( 4 บิตบน หรือ 4 บิตล่าง) ว่าเป็น 1 หมดหรือเปล่า หรือว่าเป็น 0 หมดไหม เช่น

if ((PORTA & 0xF0) == 0xF0)  // ทำให้ 4 บิตล่างเป็นศูนย์หมด แล้วทำการเช็คว่า 4 บิตบนเป็น 1 หมดหรือเปล่า จึงเข้าเงือนไข

หรือบางที เราใช้ในการเคลียร์บิต นั้นๆ 

PORTA &= ~(1<<n);  //  โดยที่ n คือ ลำดับของบิต ตั้งแต่บิต 0 จนถึงบิตสูงสุดของ PORTA มีค่าเป็น 0 เฉพาะตำแหน่ง

a ^ b  (Bitwise Exclusive or)

ท่องกันไว้เลยครับ  "เหมือนกันเป็นศูนย์ ต่างกันเป็นหนึ่ง"  ตัวอย่าง 0x56 ^ 0x32 ได้ผลลัพธ์ 0x64 เพราะว่า

   0 1 0 1 0 1 1 0  <----  0x56

^  0 0 1 1 0 0 1 0  <----  0x32

  ----------------------

   0 1 1 0 0 1 0 0  <----  0x64

Exclusive or นี้ มีประโยชน์มากเลยกับงานไมโครคอนโทรลเลอร์ เรามักใช้ในการกลับบิตไปมา โดยที่เราไม่ต้องสนใจว่า บิตปัจจุบัน มันเป็น หนึ่ง หรือ ศูนย์อยู่ รู้เพียงอย่างเดียว ถ้ากระทำการ Exclusive or แล้ว บิตมันจะกลับสภาวะตรงกันข้ามทันที ตัวอย่างการนำไปใช้งาน

PORTA ^= 0x80;  // จะทำให้บิต 7 สลับกลับไปมา (บิตอื่นๆ ไม่ได้รับผลกระทบ)

หรือ 

PORTA ^= (1<<n); // โดยที่ n คือ ลำดับของบิต ตั้งบิต 0 จนถึงบิตสูงสุดของ PORTA 

~a (Bitwise Complement)

อันนี้ ทำหน้าที่ กลับบิต ให้ตรงกันข้าม จากหนึ่งเป็นศูนย์ หรือ จากศูนย์เป็นหนึ่ง ดังตัวอย่าง ข้างล่างนี้

~ 0 0 0 0 0 0 0 0 0 1 0 1 0 1 1 0

   -------------------------------------------

   1 1 1 1 1 1 1 1 1 0 1 0 1 0 0 1

จะเห็นได้ว่า ทุกบิต ถูกกลับบิตหมดเลย  ในการนำไปใช้งานจริง เราสามารถประยุกต์การใช้งานร่วมกับ & logic จะมีประโยชน์มากยิ่งขึ้น ดูตัวอย่างครับ

PORTA &= ~0x80; // ทำให้บิต 7 เป็นศูนย์

หรือ 

PORTA &= ~(1<<n);  // โดยที่ n คือ ลำดับของบิต ตั้งแต่บิต 0 จนถึงบิตสูงสุดของ PORTA กลับบิตเฉพาะตำแหน่ง 

หลายๆ คนคงสับสนว่า เจ้า  (1<<n) มันคืออะไร ไม่ใช่การเปรียบเทียบมากกว่า หรือน้อยกว่า นะครับ แต่เป็นการเลื่อนบิต เรามาดูหลักการมันก่อน 

<< (Shift Bit Left)

คือการทำให้ลำดับบิตเลื่อนไปทางซ้ัาย แล้วนำศูนย์มาต่อบิตทางขวามือ เช่นถ้าเราต้องการเลื่อนข้อมูล PORTA = 0x06 ไปทางซ้าย 2 บิต จะได้ว่า

     0 0 0 0 0 1 1 0    <----  0x06

0 0 0 0 0 1 1 0 _ _   <----  0x06<<2

     ----------------------

     0 0 0 1 1 0 0 0    <----  0x18

ในทางตรงกันข้าม เราสามารถเลื่อนบิตไปทางขวาได้เช่นกัน โดยใช้เครื่องหมายตรงกันข้ามกับ เลื่อนบิตไปทางซ้าย 

>> (Shift Bit Right)

จะทำให้ลำดับบิตเลื่อนไปทางขวา แล้วนำศูนย์มาต่อบิตทางซ้ายมือ เช่นถ้าเราต้องการเลื่อนข้อมูล PORTA = 0x06 ไปทางขวา 2 บิต จะได้ว่า

     0 0 0 0 0 1 1 0       <----  0x06

     _ _ 0 0 0 0 0 1 1 0 <----   0x06>>2

     ----------------------

     0 0 0 0 0 0 0 1       <----  0x01

แต่เท่าที่พบเห็นส่วนมาก การใช้ Shift bit left หรือ Shift bit right เราจะนำ 0x01<<n เลื่อนไปเป็นจำนวน n บิต แล้วนำไป &= หรือ |= กับข้อมูลเดิม เพื่อให้เกิดผลกับบิตในตำแหน่ง n ที่ต้องการ เหมือนตัวอย่าง ที่ยกให้เห็นข้างต้น 

สรุปกันอีกครั้ง ตัวอย่างที่พบเห็นบ่อยๆ ในการเขียนโปรแกรมภาษาซีในการกระทำข้อมูลกับบิตในไมโครคอนโทรลเลอร์ 

การเซตบิต (Set Bit)

uint8_t a = 0x08;   /* 00001000 */

                    /* เซต บิตที่2 */

a |= (1<<2);        /* 00001100 */

การเคลียร์บิต (Clear Bit)

uint8_t a = 0x0F;  /* 00001111 */

                   /* เคลียร์บิตที่2 */

a &= ~(1<<2);      /* 00001011 */

uint8_t a = 0x0F;      /* 00001111 */

                       /* เคลียร์บิตที่1 และบิตที่2 */

a &= ~((1<<2)|(1<<1)); /* 00001001 */

การกลับบิต (Toggle Bit)

uint8_t a = 0x0F; /* 00001111 */

                  /* สลับบิืตที่2 */

a ^= (1<<2);      /* 00001011 */

a ^= (1<<2);      /* 00001111 */

ทั้งหมดนี้ เป็นเพียงพื้นฐานนะครับ ยังมีรูปแบบอื่นๆ อีกมากมาย แต่ก็จะใช้รูปแบบพื้นฐานเหล่านี้ประกอบกัน หากเราพบเห็นโค๊ดของคนอื่น เราก็จะได้เข้าใจ และัสามารถแก้ไข หรือนำไปประยุกต์ใช้ได้ต่อไป หวังว่า คงได้รับประโยชน์กันบ้างนะครับ