วันจันทร์ที่ 23 ตุลาคม พ.ศ. 2566

Bitwise operation

     ที่หายไปนานเพราะความสนใจไปอยู่กับสิ่งอื่น นานๆ ก็กลับสักที มารอบนี้ก็เพราะได้เจองานที่เกี่ยวข้องกับเรื่อง MCU

    เป็นบอร์ดควบคุมเกียร์ของรถตัก XCMG ZL50G มันใช้ ATMEGA328PU แบบ 28ขา คอยสั่งโซลินอยด์ของชุดเกียร์

แต่เรื่องที่จะมาเขียนกันลืมป็นเรื่อง bitwise operation บางคนก็ว่า bit mask ของ MEGA328P

บรรดา IO ของ MCU 328 มี register ที่เกี่ยวข้องประมาณนี้

DDRx  มันเป็นการกำหนดว่า IO นั้นจะเป็น input หรือ output โดยที่ 0 คือ input และ 1 คือ output
PORTx มันเป็นตัวสั่ง output ของ IO นั้น โดยค่าของมัน เป็น 0 หรือ 1 (0 ไม่มี output และ 1 มี output 3.3,5v)
PINx เป็นตัวบอกสถานะของ IO ที่เป็น  input ว่ามี input หรือยัง
* x คือ กลุ่มของ IO โดยมันมี B, C, D  เช่น DDRB, PORTB, PINB ดูได้จาก Pinout

ตัวอย่าง
DDRB=0b11111111;
กำหนดให้ PORTB ทุกขาเป็น output

DDRB=0b10000001;
กำหนดให้ PORTB ขา ที่ 0, 7 เป็น output ที่เหลือเป็น Input

PORTB=0b00000001;
สั่ง output high ออกที่ขา 0

ส่วน PINB มันเป็นรีจิสเตอร์ที่อ่านได้อย่างเดียว
ตัวอย่าง
byte i = PINB;
Serial.println(i, BIN);
แล้วถ้าแสดงค่าออกมา 101 จะหมายถึง มี input high ที่ขา 2 และขา 0

แล้ว bitwise มันใช้ตอนไหน เขียนมาตั้งนาน?
มีบางทีที่เราต้องการให้บางขาทำงานโดยไม่ให้ขาอื่นได้รับผลกระทบด้วย
อย่างเช่น
-ต้องการให้ขา 0-3 เป็น Output โดยขาอื่นเราจะไม่แตะต้อง
-ต้องการให้ขา 2 กระพริบไปมา โดยขาอื่นทำหน้าที่ input/output ในงานบางอย่างตามปกติ
แบบหลังจะเขียน PORTx = 0xxxxxxx ได้ยาก

การสั่งให้บางบิททำงานต้องอาศัย bitwise operation
bitwise เอาที่ใช้บ่อยก็มี NOT, AND, OR, XOR (สัญลักษณ์ ~, &, |, ^)
พื้นฐาน
NOT:
NOT 0101
        = 1010
ตรงข้ามกันเลย

AND:
          1010
AND   1101
=         1000
*untouch bit คือ 1 จะเห็นว่าหลังจากทำ AND แล้วค่าที่ได้ไม่เปลี่ยน (ส่วนอันอื่นถูกบังคับให้เป็น 0)

OR:
         1101
OR    1011
=       1111
*untouch bit คือ 0 จะเห็นว่าหลังจากทำ OR แล้วค่าที่ได้ไม่เปลี่ยน (ส่วนอันอื่นถูกบังคับให้เป็น 1)

XOR:
         1010
XOR 1100
=        0110
*flip bit คือ 1 จะเห็นว่าหลังจาก XOR แล้วค่าจะตรงข้ามกัน ใช้ในการ Toggle bit

ทีนี้มาดูที่ใช้งานกัน

DDRC|=0b00011111;  เป็นการกำหนดให้ ขา 4 ถึง 0 เป็นขา output
|= หมายถึง DDRC = ( DDRC | 0b00011111 )
0 เป็น untouch bit  คือที่ 0 ค่าที่ได้ไม่เปลี่ยน ส่วนที่เป็น 1 สุดท้ายก็จะได้1 (บังคับเป็น 1)

DDRB&=0b11110000; เป็นการกำหนดให้ขา 4 ถึง 0 เป็นขา input ส่วนขาอื่นไม่สน
&= หมายถึง DDRB = ( DDRB & 0b11110000 )
1 เป็น untouch bit คือที่ 1 ค่าที่ได้ไม่เปลี่ยน ส่วนที่เป็น 0 สุดท้ายก็จะได้ 0 (บังคับเป็น 0 )

PORTC=0b00001111; 
PORTC^=0b00001000;
บันทัดแรกเป็นคำสั่งให้ output low ที่ขา 7 ถึง 4 และมี output high ที่ขา 3 ถึง 0
ต่อมา จะได้ว่าขา 7-4 และขา 3 จะมี output low ส่วนขาอื่นเหมือนเดิม ( ขา 2, 1 และ 0 จะยังมี output high เหมือนเดิม) ท้ายที่สุด PORTC จะมีค่า 0b00000111

อีกเรื่องที่น่าจะต้องหัดไว้คือการเลื่อนบิท (shift bit) ใช้เครื่องหมาย <<, >>
มาดูตอนที่ใช้งานกัน
1<<5 มีค่าเท่ากับ 0b100000 (เลื่อนเลข 1 มา 5 ครั้งทางซ้าย และบิทที่ 5 มีค่าเท่ากับ 1)
0b101<<5 มีค่าเท่ากับ 0b10100000 (เลื่อนเลข 101 มา 5 ครั้งทางซ้าย)
0b10100>>2 มีค่าเท่ากับ 0b101
แล้ว 11>>2 มีค่าเท่าไหร่ ตอบ 0 เลื่อนจนหลุดไม่เหลือไรเลย

การ toggle bit บางขา
PORTC|=0b00100000; กำหนดบิทที่ 5 output high
PORTC^=(1<<5);    toggle bit 5 (ก่อนหน้านี้มัน high ก็จะเปลี่ยนเป็น low)

บางครั้งเราต้องการดูสถานะ input ของ IO แล้วมันก็มาจาก IO ต่างกลุ่มกัน (PINB, PINC หรือ PIND)
ตัวอย่างเช่น เราต้องการดู PINB เฉพาะ 3-0 และ PIND ขา 7-5 ว่าเป็นอย่างไรบ้าง และจะดูคราวเดียวกัน
ก็ประมาณนี้
byte i = (PINB<<3) | (PIND>>5) &0b01111111; 
โดยสมมติว่า
(PINB<<3)= xxxxx000  (xxxxx 5บิทนี้ เรายังไม่รู้ว่าสถานะมันเป็นอะไร แต่ที่แน่ๆ มันจะถูกเลื่อนมาทางซ้าย)
(PIND>>5)=yyy  (จะเหลือข้อมูล yyy ที่ยังไม่รู้ว่าเป็นอะไรของขา 7,6 และ 5)
(PINB<<3) | (PIND>>5) จะได้ข้อมูล xxxxxyyy แต่เราไม่เอาบิทที่ 7 ซึ่งก็ต้องเอาออก
เอาออกโดย &0b01111111; (บังคับเป็นศูนย์ในบิทที่ 7)


จริงๆ มันก็แค่เรื่อง pinMode, digitalRead, digitalWrite แค่นั้นนะ จะทำให้มันวุ่นวายทำไม
เวลาก็ไม่ช้าเร็วจนเห็นผลต่างที่ชัดเจนตอนใช้งาน อย่างน้อยก็ถือว่าเป็นการทบทวน เป็นการเรียนรู้วิธีอื่นดูบ้าง

ประมาณนี้ก่อน



MAPs

วันก่อนได้ลองเขียนแบบ แบบตัวนั้นมันต้องแนบแผนที่สังเขปของโครงการ ลองๆ แล้วก็ใช้ snazzymaps แล้วก็เอามาทำต่อใน inkscape มันก็ออกมาดูได้ระดับห...