最近在寫一個操作Modbus的系統,碰到廠商使用自家的協定,同樣透過RS485接線傳遞byte資料進行設備的DI、DO操作及讀取,碰到了需要進行bit操作的情況,這邊將後來我所用的解法紀錄並分享如下:
要解的問題說明
DO的操作簡單來講就是設定開(ON)跟關(OFF),以輸入來說,每個bit的輸入1代表開,0代表關,譬如有8個DO(DO1~DO8),則00110001的輸入表示DO1跟DO5、DO6 三個DO為開,其餘為關。以byte來說,也就是輸入了49就有了上述的效果(00110001=49);問題是廠商僅提供一次一個byte的輸入模式,因此若今天我們要將DO5關掉,亦即00110001=>00100001,就必須進行bit操作。筆者找了一會並沒有找到golang已經有人寫好的bit操作套件(畢竟bit操作已經有定義好的operator),因此用以下程式碼來達成。
程式碼
先上程式碼,有些人讀程式碼就可以拿去用了,下面有說明:
byteValue := 49 //00110001 fmt.Printf("%08b \n", byteValue) //可以查看一下 //將byte中某一位設置為0 x := 5 //我們預計將第5位設置為0 newByte := byteValue &^(1<<(x-1)) fmt.Printf("%08b \n", newByte) //00100001 //將byte中某一位設置為1 x = 3 newByte = newByte | (1<<(x-1)) fmt.Printf("%08b \n", newByte) //00100101
程式說明
- fmt.Printf(“%08b”)能將一個byte值用01010101的方式顯示出來,方便我們確認我們的bit操作有成功。
- 將某一位設置為0,我們使用了 &^(1<<x)這樣的操作,這個需要一步步的拆解,就能清楚明白做了甚麼了:
- 1<<(x-1),首先<< 為golang裡面的位元操作符,表示左移n位數,1用bit表示出來為00000001,<<x-1 (本例中x=5),表示左移4位數,變成00010000 (順便一說,這也可以說是2的0次方變成2的4次方,每左移一位等於該數乘以2,因此1變成16)。
- ^ 此為bit的反操作符,0變成1,1變成0,因此我們的^(1<<(x-1))= ^(00010000) ,也就變成11101111。
- 最後跟原本的byte做一個&,&為and操作符,兩個byte中的同一個位置的bit操作,兩者皆為1,則&後為1,兩則皆為0或其中有一個為0則&後為0;因此任何byte & 11111111 = byte ,任何byte & 00000000 = 00000000。因此可以想像我們的原始byte & 11101111 後,除了第5位變成0,其餘位數保持不變。
- 因此&^(1<<x-1) 達成我們將第x位數變成0,其餘位數不變的需求。
- 同樣的,將某一位元設置為1,我們需要使用|(1<<(x-1))的操作:
- 其中1<<(x-1)同上所述,(此次我們將x設定為3),00000001變成00000100
- |為or操作符,兩個byte中的同一位置的bit操作時,兩者中有任一個為1或兩者皆為1則為1,若兩者皆為0才是0,因此任何byte | 00000000 = byte ,任何byte | 11111111 =11111111。
- 上述兩個操作進行起來,我們的原始byte = 00100001 | 00000100 ,大家可以自己做一下練習,自然變成00100101,也就成功將第3碼設為1,其他保持不變了。