前回はマイコンのハードウェアの設計のお話をしましたが、今回からはマイコンのプログラミングについて書いていきたいと思います。まずは、マイコンのレジスタを叩く際に必須と言えるビット演算のお話をしたいと思います。
マイコンをはじめとするコンピュータの世界はデジタル、つまり0と1しかない世界です。マイコンも例外ではなく大量の0と1の組み合わせで動いています。コンピュータの世界では1つの0か1のデータを1ビット、これを8つ組み合わせたものを1バイトと呼びます。つまり1バイト=8ビットというわけです。一般的にはビットはコンピュータのビット数(パソコンだと32ビットだとか64ビットとか言いますね)やシリアル系の通信速度(wifiとかLANの通信速度の〇〇bpsといいますね)で使われて、バイトは容量系の単位(RAM16GBとかROM64GBとかいいますね)に使われることが多いです。
前置きはこの程度にしておきます。マイコンには変数などで使われる通常のメモリのほかに、レジスタという特殊なメモリ領域があります。このレジスタを使ってマイコンに搭載された機能である入出力などを操作します。今回使用するマイコンは8ビットのAVRマイコンなのでレジスタは基本的に8ビットになっています。例えばポートの出力状態を設定するレジスタにおいても最大8つのポートごとにレジスタが割り当てられているのです。このうち1つのポートだけを操作したい場合などに必要な概念がビット演算というわけです。
ビット演算の基礎の基礎としてまずは論理演算を紹介します。
AND(論理積)
ANDは論理積ともいえ、日本語で言うと「AかつB」、論理式で表すと”A・B” や”A∩B"などとなります。つまり、掛け算なので入力にどれか1つでも0があった場合は出力が0となります。例えば0と1をANDすると出力は0、1と1をANDすると出力は1となります。C言語のプログラムでは“&&”と表します。
OR(論理和)
ANDは論理和ともいえ、日本語で言うと「AまたはB」、論理式で表すと”A+B” や”A∪B”などとなります。つまり、足し算なので入力にどれか1つでも1があった場合は出力が1となります。例えば0と1をORすると出力は1、0と0をORすると出力は0となります。C言語のプログラムでは“||”と表します。
NOT(否定)
NOTは否定ともいえ、日本語で言うと「Aではない」、論理式で表すと”Ā”などと表します。0が入力された場合は1が出力され、1(0以外)が入力されると0が出力されます。入力に対して出力が反転されるので、論理回路では反転を意味する”inverter”とも言われます。C言語のプログラムではでは”!”と表します。
NAND(否定論理積)
NANDはANDの出力にNOTをかけたものです。日本語で言うと「(AかつB)ではない」、論理式で表すとなどとなります。つまり、掛け算の否定なので入力にどれか1つでも0があった場合は出力が1となります。例えば0と1をNANDすると出力は1、1と1をNANDすると出力は0となります。C言語ではANDしたあとにNOTをかけて「例えば:!(A&&B)」実行します。論理回路ではよく使われますが、マイコンのプログラムの場合は使う機会は少ないかもしれないです。(特定のビットを0にするために入力にNOTをかけてからANDすることは多いですが…)
NOR(否定論理和)
NORはORの出力にNOTをかけたものです。日本語で言うと「(AまたはB)ではない」、論理式で表すとなどとなります。つまり、足し算の否定なので入力にどれか1つでも1があった場合は出力が0となります。例えば0と1をNORすると出力は0、0と0をNORすると出力は1となります。C言語ではORしたあとにNOTをかけて「例えば:!(A||B)」実行します。NANDと同じく論理回路ではよく使われますが、マイコンのプログラムの場合は使う機会は少ないかもしれないです。
XOR(排他的論理和)
XORは排他的論理和と言われる、少し変わった論理演算です。論理式で表すととなります。入力の1の数が奇数の時に1を出力し、1の個数が偶数の時に0を出力します。こちらは論理回路では使う機会が比較的少ないですが、マイコンのプログラムではそこそこ使います。
以上の6つが論理演算ではよく使われてます。基本的には前半の3つであるAND、OR、NOTですべての論理演算が可能です。また、NANDとNORの2つでもすべての論理演算が可能です。
次に今回の本題であるビット演算のお話をしたいと思います。ビット演算では先ほどまで紹介した論理演算をビット単位で行います。ビット単位でのビット演算を考えるため、まずは演算を行う数値を2進数で表します。2進数表記した値を1桁ずつ論理演算するとビット演算になります。まずは、AND、OR、NOT、XORのプログラミングにおける記号とビット演算するとどのようになるかを紹介します。
AND
C言語での記号は”&” です。論理演算の記号である”&&”と間違えないように注意してください
A
10101010
B
11110000
A & B 10100000
OR
C言語での記号は”|” です。論理演算の記号である”||”と間違えないように注意してください
A 10101010
B 11110000
A | B 11111010
NOT
C言語での記号は”~” です。
A 10101010
~A 01010101
XOR
C言語での記号は”^” です。
A 10101010
B 11110000
A ^ B 01011010
C言語ではこの4つのビット演算を使用することができます。これらを使ってレジスタの特定のビットのみの抽出などを行っていきます。
まずはレジスタの例として、atmega328PのポートBの状態(HIGHかLOWか)を設定するレジスタを使ってみたいと思います。
Atmega328Pのデータシート(https://avr.jp/user/DS/PDF/mega328P.pdf)のページ65の図の部分より、ポートBの出力状態を切り替えるレジスタは”PORTB”であることがわかります。
図のようにビット0はPB0の状態
を制御、ビット1はPB1の状態を制御…といったようになっており、各ビットに0を入れると対応するポートがLOW、1を入れると対応するポートがHIGHになります。このように、1つのレジスタでPB0からPB7までの8つのピン(厳密にはPB6とPC7はたいてい水晶を接続するので6ピンになることが多い)の状態を制御する仕様になっていることがわかると思います。そのため、1つのピンのみを制御しようとして、レジスタに直接値を入力するとPB0からPB7までのすべての状態が変わってしまい不都合が生じてしまいます。ここで必要なのがビット演算というわけです。特定のビットのみを操作したいときは、現在のレジスタの状態と操作するビットが立てられた(1にされた)値をビット演算で比較をします。現在のレジスタの状態はデータシートのビット一覧(上の図)のアクセス種別の表記より読み込みが可能であることがわかります。そのためこのような処理が可能というわけです。
特定のポート(ビット)のみを操作する方法を、例を使って紹介していきたいと思います。それぞれ操作する前の状態はわからない(テキトーな値)になっているとします。
1
PB4をHIGHにしたいとき(ビット4のみを1にする)
現在の状態に操作するビットを立てた(1にした)値をORします。
操作前のPORTB 10101010 (Aとする)
操作するビット 00010000
(Bとする)
A | B 10111010 →PORTB
このようにすると元の状態が何であったとしても操作したいビットが確実に1に設定され、ほかのビットは何も影響を受けないことがわかると思います。これをC言語で省略せずに記述するとこのようになります。
PORTB = PORTB | 0b00010000;
ですが、操作するビットを2進数(プログラムでは”0b****”で示す)書いていては入力が大変で、読みにくいです。そのためこれを16進数(プログラムでは”0x**”で示す)に変換します。また、C言語での省略表記も合わせて使うとこのようになります。
PORTB |= 0x10;
先ほどの書き方に比べてとてもスマートになりますね。ですので、基本的にはこの書き方を使っていきたいと思います。
2
PB4のみをLOWにしたいとき(ビット4のみを0にする)
操作するビットを立てた(1にした)値にNOTをかけて反転させた値と現在の状態をANDします。
操作するビット 00010000 (Bとする)
~B 11101111 (操作するビットの反転)
操作前のPORTB 10111010 (Aとする)
操作するビットの反転 11101111 (~Bとする)
A & ~B 10101010 →PORTB
こうすることで、操作したいビットが確実に0になり、ほかのビットは影響を受けません。C言語で書くと
PORTB = PORTB & (~0b00010000);
となり、省略系にして
PORTB &= ~0x10;
と表せます。
3
PB4のみ状態を入れ替える(ビット4が1だった場合0、0だった場合1にする)
現在の状態と操作するビットを立てた(1にした)値のXORをかけます。
操作前のPORTB 10101010 (Aとする)
操作するビット 00010000
(Bとする)
A ^ B 10111010 →PORTB
元のビットの状態が逆のパターン
操作前のPORTB 10111010 (Aとする)
操作するビット 00010000
(Bとする)
A ^ B 10101010 →PORTB
このようにすると操作したいビットの状態が入れ替わり、ほかのビットは影響を受けません。C言語で書くと
PORTB = PORTB ^
0b00010000;
省略して
PORTB ^= 0x10;
と表せます。
レジスタの特定のビットのみを操作するときは基本的にはこの3つを使うことが多いと思います。ビット演算ではほかにも特定のビットのみを取り出したいときにANDを使うときもあります。方法としては元の値に取り出したいビットを立てた(1にした)値をANDします。
例えば、元の値から下4ビットのみを取り出す場合はこのようにします。
元の値
10101010 (Aとする)
取り出すビット00001111 (Bとする)
A & B 00001010
このようにすると上4ビットはすべて0で下4ビットは元の値がそのまま取り出せることがわかります。C言語で書くと以下のようになります。
C = A &
0b00001111; (元の値をA 下4ビットを取り出した値をCとしている)
省略して
C = A & 0x0F;
と表せます。(元の値がレジスタであったり、元の値を保持することが多いので、”A&=0x0F;”と書くことは少ないです。)
この方法はマイコンのレジスタから値を取り出す際や通信の際によく使う手法です。
ビット演算は以上として次回はビットシフトのお話をしたいと思います。