今回はArduinoでも使うビット演算子についてのご紹介です。
Arduinoを使っていてビット演算が必要になるタイミングといえば、
なにかのICを使うときに、レジスタをいじるときくらいでしょうか?
ただ、以前からArduinoをAVRのマイコンとして使用している記事を書いています。
そこでは頻繁にマイコン自体のレジスタを書き換える関係上、
どうしてもビット演算が必要になります。
まあ、毎回0b00000001のように8bit全て指定してもできますが…
ちょっとかっこ悪いというのと、他の機能を司るbitまで不用意に変更するのは
バグの原因となりますから、基本的には必要なところを必要なだけ変更したほうがいいです。
ただでさえ既にイベントで出力するようとしてピンが予約されていることもありますので、
そのピンを不用意にいじってしまうのは危ないですよね?
ということで今回はビット演算についてご紹介していきます。
Youtubeチャンネルにさまざまな動画を上げています。
↓↓↓こちらからYoutubeチャンネルにアクセス!! ↓↓↓
本ブログはアフィリエイトを用いた広告を掲載しています。
どんなときにビット演算が必要か?
それではどんな時にビット演算が必要か例を挙げていきましょう。
例えばこの2つは本ブログでも紹介している内容です。
どちらも以前から紹介している内容なので軽く触れていきましょう。
IC(シフトレジスタだったりIOエキスパンダだったり)を制御する場合。
IC関係を制御するときはそのICのレジスタに通信する必要があります。
その際、そのICのデータシートに書かれているレジスタ(いわゆる番地)
にビットを立てたり落としたりすることで、ICを意図通りに動かします。
具体的には、この記事で紹介したmcp23017という
IOエキスパンダーなんかがいい例です。
これがIOエキスパンダー
↓↓↓
このIOエキスパンダーで使用するピンを設定したり、
実際に出力させるときに0b00000011のように2進数で該当箇所にビットを
立てた状態で送信すると、IC側でその通りの動きをします。
単純な制御の場合はすべて8bitなら8bit丸ごと入力してしまっても問題ないことが多いです。
Arduinoの本体ともいうべきマイコン本体を制御する場合。
先ほどの例だと、8bitなら8bit丸ごと入力してしまえばいいと言いましたが、
このArduinoに載っているマイコンの制御の場合、
そうはいきません。
というのも、Arduinoの場合、すでにどこかにつながって、
動作させるのに重要なビットまで落としてしまうと、
正常な動作にならないためです。
例えば、PortAの0~7まですべてLOWに落とす。
といった指令を出した場合に、他の機能とバッティングして、
PORTAの○○番ピンをLOWに落とすと不具合が…なんてことが発生する可能性があるためです。
ビット演算とは具体的に何をする?
ビット演算でやることと言えば、次の3つくらいだと認識しています。
具体的にどういうときにその演算が必要なのかご紹介します。
bitのなかの、指定した桁数のbitのみを反転させる。
これは、入出力関係のレジスタをいじるときに発生します。
今まで出力していた5番pinを今度はLOWにして出力を止めたい。
なんてときですね。
その時、3番pinや4番pinはそのままHIGHのままで制御したい。
という場合、なにも考えずにすべてのbitを落とすと、
3番pinと4番pinにも影響を及ぼすわけです。
ですから、狙った桁のビットを反転させる必要があるわけです。
指定した桁のbitを強制的に1または0にする。
これは初期化の時なんかによく使います。
今現在のレジスタの状態は無視して、指定した状態にしたいときですね。
先ほども書きましたが、初期化の時はこのように強制的にビットの操作を行います。
取得したレジスタの状態から、目的の桁数のビットが立っているかどうか確認する。
レジスタの状態は確認する機会もあるはずです。
そんな時、8bitならば、8桁の01の数字の羅列が返ってくるはずです。
その羅列を見て、○○桁目だから….とやるとちょっとダサいですよね。
ですから、あらかじめ○○桁だけを見るように工夫するわけです。
ビット演算を実例と合わせて紹介する。
それでは実例と合わせて今回のビット演算の内容をご紹介していきましょう。
まず、使うのはC++
8bitのレジスタを操作することを前提とします。
C++としたのは、Arduinoの言語がC++の派生だからです。
この通りやってもらうと、Arduinoでも使えますので試してみてください。
ではやっていきましょう。
bitのなかの、指定した桁数のbitのみを反転させる。
指定した桁のところだけbitを反転させる場合をご紹介します。
例えば、0b00100100という状態になっていたとしましょう。
ここで、3bit目の1のみを0にしましょう。
つまり、3bit目を反転するわけです。そのほかのbitには影響が出てはいけません。
まずは悪い例です。
#include <iostream>
#include <string>
using namespace std;
int main() {
int test = 0b00100100;
test = 0b000;//ここで0にしているが、6bit目まで0になってしまう。
string testS = std::to_string(test);
printf(testS.c_str());
return 0;
}// 実行結果0
たしかに0b000とすれば、0bit目から3bit目まで0にすることはできましたが、
6bit目の1まで0になってしまっています。
しかも反転と言っているのに、強制的に0にしているので、反転とも言えないですね。
それではどうすればいいのか。
#include <iostream>
#include <string>
using namespace std;
int main() {
int test = 0b00100100;
test ^= 0b100;
string testS = std::to_string(test);
printf(testS.c_str());
return 0;
} // 実行結果32(0b00100000)
こうすることによって、6bit目に影響が出ないようになっています。
具体的にはここの行ですね。
test ^= 0b100;// ここで反転させている。
ここで何をしているかというと、
^=はXORです。
このXORは、両方のbitを確認して、どちらか一方でも1になっていたら1。
どちらも1になっていたら0にするという論理演算子です。
つまりXORを使うことで、
3bit目が1になっていたら0になるし、
3bit目が0だった場合は1になる。つまり反転するということですね。
試しにこのように2行にすると、元に戻ることが確認できます。
#include <iostream>
#include <string>
using namespace std;
int main() {
int test = 0b00100100;
test ^= 0b100;
test ^= 0b100;
string testS = std::to_string(test);
printf(testS.c_str());
return 0;
}// 実行結果36(0b00100100)
当然ですが、複数のbitを同時に反転させることも可能です。
それでは、1~4bit目を反転させましょう。
#include <iostream>
#include <string>
using namespace std;
int main() {
int test = 0b00100100;
test ^= 0b1111;
string testS = std::to_string(test);
printf(testS.c_str());
return 0;
}//実行結果43(0b00101011)
ちゃんと1~4bit目まで反転していることがわかりますね。
指定した桁のbitを強制的に1または0にする。
次に指定した桁のbitを強制的に1または0にしてみましょう。
ひとつ前の章では、反転でしたから、現在のbitの状態によって変化させていました。
今回は強制的に狙ったbitだけ0か1にしたいので、XOR演算子は使用できません。
では何を使うかというと、1にしたい場合はOR、0にしたい場合はANDとNOTです。
具体的にコードをお見せします。
1にしたい場合。
#include <iostream>
#include <string>
using namespace std;
int main() {
int test = 0b00100100;
test |= 0b1111;
string testS = std::to_string(test);
printf(testS.c_str());
return 0;
}// 実行結果47(0b00101111)
0にしたい場合。
#include <iostream>
#include <string>
using namespace std;
int main() {
int test = 0b00100100;
test &= ~0b1111;
string testS = std::to_string(test);
printf(testS.c_str());
return 0;
}// 実行結果32(00100000)
強制的に狙ったbitを1にする場合。
強制的に1にする場合は、ORを使用します。
ORは|この記号を使用します。
ORの役割は、両方のbitを比較して、どちらか一方でもbitに1が入っている場合は1にする。
という論理演算子です。
ですから、狙ったbitに1を立てたいなら、ORで1をぶつけてやれば、
たとえ元のbitが0でも1でも強制的に1になる。という原理です。
強制的に狙ったbitを0にする場合。
強制的に0にする場合は、ANDとNOTを使用します。
ANDの役割は、両方のbitを比較して、どちらも1になっていなければ0で、
どちらも1になっていれば1になります。
NOTは否定の論理演算子です。今回の場合は、
~0b1111となっているので、0b0000と同じ意味です。
では今回の場合はどうなっているかというと、
1bit目から4bit目まで0b0000とANDをとるということになります。
0とANDをとる⇒必ず0になる。
こういった原理をうまく利用しているということです。
取得したレジスタの状態から、目的の桁数のビットが立っているかどうか確認する。
今度は取得したレジスタの中から、必要な桁数のビットを確認する方法です。
具体的には、先ほどから使用している例を引き続き使用します。
例えば、test = 0b00100100となっていて、このtestの状態を読んだとします。
そして、5bit目が0なのか1なのかを確認したいとします。
もちろん人間はこの数字の羅列を目で確認して、右から5番目は0なのか1なのか?
と確認することが可能です。もちろん0ですね?
ただ、プログラム上でそこだけ必要な場合どうしたらいいでしょうか?
そのbitだけ使ってif文で分岐したいな…なんてときに結構面倒なはずです。
それではその面倒な問題を解決しましょう。
問題のキーとなるのは、ビットシフトというツールです。
まずはコードをお見せします。
#include <iostream>
#include <string>
using namespace std;
int main() {
int test = 0b00100100;
test = (test>>4 & 0b01);
string testS = std::to_string(test);
printf(testS.c_str());
return 0;
}// 実行結果0
たったこれだけで5bit目の状態がわかります。
ビットシフトは>>で表現されます。ビットシフトとは、
単純に説明すると、数字の羅列をそのままごっそり移動させる手法です。
test>>1とすると、1つ右にずれます。
つまりtest=0b0010010となります。
さらに続けてtest>>1とすると、
test=0b001001となります。
今回の例では5bit目の状態が知りたいので、4つ右にずらすことで、
5bit目の数値が1bit目に来ます。
さらにそこまで来たら、0b01とANDをとってあげれば、
5bit目よりも上のbitは無視して0なのか1なのかがわかるという原理です。
ビットシフトしただけだと、5bit目以降の値も一緒に出てきてしまうので、
&0b01は必ず必要になるのを忘れないように気をつけましょう。
ためしに&0b01を忘れるとこんな感じになります。
#include <iostream>
#include <string>
using namespace std;
int main() {
int test = 0b00100100;
test = test>>4;
string testS = std::to_string(test);
printf(testS.c_str());
return 0;
}// 実行結果2(0b10)
今回のまとめ。
今回はビット演算についてご紹介しました。
私自身、Arduinoに載っているatmega4809のレジスタを直接いじっている時に、
あれ、ビット演算子ってこれ何の意味だっけ?
なんて思い出しながらやっていた部分もあったので、
今回の記事は自分の備忘録的な意味合いもあります。
レジスタをいじったりマイコン関係で制御する人には当たり前の話かもしれないですが、
初心者でこれからビット演算どんどんやっていくぞ!という
場合には有用です。ぜひマスターしてみてください。
コメント