前回はビット演算のお話をしましたが、今回はビット演算の一部ともいえるビットシフトのお話をします。ビットシフトというのはその名の通り、ビットの位置を移動させる機能です。この機能は主に、値を読み込んだ時に高いビットの値のみを使いたいときや、通信データの作成の時に使います。ですが、使う機会はそんなに多くないのでさらっと行きたいと思います。
ではビットシフトをすると、どのようになるのかをお見せしましょう
操作前の値 10101010
右に4bitシフト
00001010
1回目の例は右向きに4bitシフトさせた例です。このように右向きにシフトさせると高い位置のビットにあったデータを低いビットに移動させることができます。ここで気を付けないといけないのは、ビットシフトさせて消えるビットと新たに出てくるビットがあることです。この例の場合は操作前の右側(下位)4ビットはシフトさせると消えていることがわかると思います。逆にシフトさせたあとの左側(上位)4ビットが新たに出てきてすべて0が入っていることがわかります。つまり、ビットシフトをさせるとシフトさせた方向寄りのシフトさせた数のビットが消えるというわけです。
次に左向きにシフトする例です。
操作前の値 10101010
左に4bitシフト
10100000
左向きにシフトすると左側(上位)4ビットが消えていることがわかりますね。
ちなみに、左向きのシフトはシフトしたビット分2倍されている(例のように右に4ビットシフトする場合は値を16倍している)とも考えることもできます。このように考えた場合、範囲外に出て消えるビットは「値がオーバーフローした」と考えることができます。同様に右向きの場合はシフトしたビット分1/2倍されていると言えます。
C言語でビットシフトを行う場合はこのように書きます。
右に4ビットシフトさせる場合
B = A >> 4; //(Aは操作前の値 Bはビットシフトさせた後の値)
左に4ビットシフトさせる場合
B = A <<4; //(Aは操作前の値 Bはビットシフトさせた後の値)
C言語の表記は見た目のままなので簡単かなと思います。
なお、int型などで負の数を表したときはいろいろとややこしいことになるようですが、マイコン関連の処理で使うことはまずないと思うので特に詳しいことは書きません。
では実際にビットシフトを使う例を示してみましょう。
例えば、PD5からPD7にスイッチが接続されていて、3つのスイッチで値を設定していると考えましょう。今回はPD5とPD6のスイッチがHIGHになっていると考えます。そして、最終的に値は下位3ビット(0からまで)で表したいとします。
Atmega328PのデータシートのP67を見ると、ポートDのピンの状態を読み込こむレジスタはPINDであることがわかります。
まずは上位3ビットのみを取り出します。
PIND 10101010 (Aとする)
取り出すビット11100000 (Bとする)
A & B 10100000 (Cとする)
続いてビットシフトを行って上位3ビットにいる値を下位3ビットに移動させます。
C 10100000
右に5bitシフト
00000101
こうすると上位3ビットにスイッチを接続して値を設定しても、下位3ビットの値として取り出すことができます。これをC言語で表すとこのように書けます。
uint8_t B = PIND & 0xE0;//0xE0 =
0x11100000 (上位3ビット取り出し)
B= B >> 5;//左に5ビットシフト
(uint8_tは符号なしの8ビットの整数型(Arudinoでいうbyte型)を示しています。)
C言語でもそのままに表せますね。ちなみに、先に行っているビットを取り出す作業は今回の場合は必要ありませんが、必要となる場合も多いので安全のために処理に含めています。(本当に必要になるのは中間のビットを取り出してシフトさせる場合などです)
次に通信データ作成の場合を考えてみましょう。
通信の場合できる限り送信のデータ数を少なくしたい場合が多いです。特に1バイトで完結させることができるとプログラムがとても楽に書くことができます。そこで、1バイト(8ビット)にビット数の少ない複数のデータを挿入する方法を紹介します。
AとBはそれぞれ4ビットのみで表せる値として、送信データはAとBの両方を含めて1バイトにしたい場合を考えます。
最初に、AとBから下位4ビットのみを取り出します。(もしも上位4ビットにゴミが混ざっていた場合送信データがおかしくなるため)
A 00001110
取り出すビット00001111 (Abとする)
A & Ab 00001110 (A’とする)
B 10001010
取り出すビット00001111 (Bbとする)
B & Bb 00001010 (Dlとする)
この例の場合Bの最上位ビットにゴミの値が入っていますが、下位4ビットのみを取り出すことでこのゴミの値を除去できます。(普通はゴミの値なんて入らないと思いますが…)続いて、Aを上位ビットとするためにAを4ビット左側にシフトします。
A’ 00001110
左に4itシフト
11100000 (Dhとする)
こうすることでDl(元のB)とDh(元のA)でビットの位置が被らないことになります。そのため、DhとDlをOR(+でもいい)することで1つのデータに入れれます。
Dl 11100000
(Aとする)
Dh 00001010 (Bとする)
Dh | Dl
11101010 →DATA
こうすることでDATAの上位4ビットはゴミを取り除いたAのデータが入っており、下位4ビットにはごみを取り除いたBのデータが入っていることがわかりますね。こうすることで桁が少ない複数の値を1つの値に収めることができるというわけです。これをC言語で書くとこのようになります。
A &= 0x0F;//下位4ビット取り出し(上位4ビットにあるゴミを除去)
B &= 0x0F;。
A = A << 4;
uint8_t DATA = A | B;//DATA = A+B;も可
次にこのデータをもとのA,Bに戻す方法を紹介します。
まずはAを取り出すために、データAがある上位4ビットのみを抽出します。
DATA 11101010
取り出すビット 11110000 (Abとする)
DATA & Ab
11100000 →A’
これで、上位4ビットを抽出できたのでこれを下位4ビットにビットシフトさせます。
A’ 11100000
右に4itシフト
00001110
→A
こうすることで元のデータAが取り出すことができました。続いてデータBを取り出します。データBは下位4ビットにいるので下位4ビットのみを抽出します。
DATA 11101010
取り出すビット00001111 (Bbとする)
DATA & Bb 00001010 →B
データBはビットシフトを行っていないので抽出した値が元のデータBとなります。これらもC言語で表すとこのようになります。
uint8_t A = DATA & 0xF0;//データ抽出
A = A >> 4;//右に4ビットシフト
uint8_t B = DATA & 0x0F;//データ抽出
前回と今回でマイコンのプログラムで重要なビットの処理のお話をしてきました。このビットの処理は慣れるまではちょっと大変ですが、慣れると簡単に扱えるようになると思います。次回はAVRマイコンのプログラミングを行う環境設定のお話をしたいと思います。