วันที่โพสต์: Dec 12, 2012 4:50:54 AM
การทำงานกับข้อมูลระดับบิตในภาษาซี
โดยทั่วไปแล้ว งานเขียนโปรแกรมกับไมโครคอนโทรลเลอร์ งานหลักๆ ก็คือ การสั่ง ON-OFF บิตที่ต้องการบิตใดบิตหนึ่ง ในรีจิสเตอร์ นับว่า เป็นความโชคดีที่ภาษาซี มีการกระทำทางบิต ให้มาแล้ว โดยที่เราไม่ต้องไปกระทำทางภาษา Assembly (ซึ่งเข้าใจยากกว่า) เรามาทำความเข้าใจวิธีการกระทำทางบิต ด้วย Operation แบบต่างๆ ในภาษาซีกันครับ
เป็นการ 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 เฉพาะตำแหน่ง
เป็นการ 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 เฉพาะตำแหน่ง
ท่องกันไว้เลยครับ "เหมือนกันเป็นศูนย์ ต่างกันเป็นหนึ่ง" ตัวอย่าง 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
อันนี้ ทำหน้าที่ กลับบิต ให้ตรงกันข้าม จากหนึ่งเป็นศูนย์ หรือ จากศูนย์เป็นหนึ่ง ดังตัวอย่าง ข้างล่างนี้
~ 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) มันคืออะไร ไม่ใช่การเปรียบเทียบมากกว่า หรือน้อยกว่า นะครับ แต่เป็นการเลื่อนบิต เรามาดูหลักการมันก่อน
คือการทำให้ลำดับบิตเลื่อนไปทางซ้ัาย แล้วนำศูนย์มาต่อบิตทางขวามือ เช่นถ้าเราต้องการเลื่อนข้อมูล 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
ในทางตรงกันข้าม เราสามารถเลื่อนบิตไปทางขวาได้เช่นกัน โดยใช้เครื่องหมายตรงกันข้ามกับ เลื่อนบิตไปทางซ้าย
จะทำให้ลำดับบิตเลื่อนไปทางขวา แล้วนำศูนย์มาต่อบิตทางซ้ายมือ เช่นถ้าเราต้องการเลื่อนข้อมูล 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 */
ทั้งหมดนี้ เป็นเพียงพื้นฐานนะครับ ยังมีรูปแบบอื่นๆ อีกมากมาย แต่ก็จะใช้รูปแบบพื้นฐานเหล่านี้ประกอบกัน หากเราพบเห็นโค๊ดของคนอื่น เราก็จะได้เข้าใจ และัสามารถแก้ไข หรือนำไปประยุกต์ใช้ได้ต่อไป หวังว่า คงได้รับประโยชน์กันบ้างนะครับ