前回はマイコンのハードウェアの設計のお話をしましたが、今回からはマイコンのプログラミングについて書いていきたいと思います。まずは、マイコンのレジスタを叩く際に必須と言えるビット演算のお話をしたいと思います。

 

 マイコンをはじめとするコンピュータの世界はデジタル、つまり01しかない世界です。マイコンも例外ではなく大量の01の組み合わせで動いています。コンピュータの世界では1つの01のデータを1ビット、これを8つ組み合わせたものを1バイトと呼びます。つまり1バイト=8ビットというわけです。一般的にはビットはコンピュータのビット数(パソコンだと32ビットだとか64ビットとか言いますね)やシリアル系の通信速度(wifiとかLANの通信速度の〇〇bpsといいますね)で使われて、バイトは容量系の単位(RAM16GBとかROM64GBとかいいますね)に使われることが多いです。

 前置きはこの程度にしておきます。マイコンには変数などで使われる通常のメモリのほかに、レジスタという特殊なメモリ領域があります。このレジスタを使ってマイコンに搭載された機能である入出力などを操作します。今回使用するマイコンは8ビットのAVRマイコンなのでレジスタは基本的に8ビットになっています。例えばポートの出力状態を設定するレジスタにおいても最大8つのポートごとにレジスタが割り当てられているのです。このうち1つのポートだけを操作したい場合などに必要な概念がビット演算というわけです。

ビット演算の基礎の基礎としてまずは論理演算を紹介します。

AND(論理積)

and
and

ANDは論理積ともいえ、日本語で言うと「AかつB」、論理式で表すと”AB” ”AB"などとなります。つまり、掛け算なので入力にどれか1つでも0があった場合は出力が0となります。例えば01ANDすると出力は011ANDすると出力は1となります。C言語のプログラムでは“&&”と表します。

 

OR(論理和)

OR
or

ANDは論理和ともいえ、日本語で言うと「AまたはB」、論理式で表すと”A+B” ”AB”などとなります。つまり、足し算なので入力にどれか1つでも1があった場合は出力が1となります。例えば01ORすると出力は100ORすると出力は0となります。C言語のプログラムでは“||”と表します。

 

NOT(否定)

 NOT
not

NOTは否定ともいえ、日本語で言うと「Aではない」、論理式で表すと”Ā”などと表します。0が入力された場合は1が出力され、10以外)が入力されると0が出力されます。入力に対して出力が反転されるので、論理回路では反転を意味する”inverter”とも言われます。C言語のプログラムではでは”!”と表します。

 

NAND(否定論理積)
NAND
nand

NANDANDの出力にNOTをかけたものです。日本語で言うと「(AかつB)ではない」、論理式で表すと1などとなります。つまり、掛け算の否定なので入力にどれか1つでも0があった場合は出力が1となります。例えば01NANDすると出力は111NANDすると出力は0となります。C言語ではANDしたあとにNOTをかけて「例えば:!(A&&B)」実行します。論理回路ではよく使われますが、マイコンのプログラムの場合は使う機会は少ないかもしれないです。(特定のビットを0にするために入力にNOTをかけてからANDすることは多いですが…)

 

NOR(否定論理和)
NOR
nor

NORORの出力にNOTをかけたものです。日本語で言うと「(AまたはB)ではない」、論理式で表すと2などとなります。つまり、足し算の否定なので入力にどれか1つでも1があった場合は出力が0となります。例えば01NORすると出力は000NORすると出力は1となります。C言語ではORしたあとにNOTをかけて「例えば:!(A||B)」実行します。NANDと同じく論理回路ではよく使われますが、マイコンのプログラムの場合は使う機会は少ないかもしれないです。

 

XOR(排他的論理和)
XOR
xor

XORは排他的論理和と言われる、少し変わった論理演算です。論理式で表すと3となります。入力の1の数が奇数の時に1を出力し、1の個数が偶数の時に0を出力します。こちらは論理回路では使う機会が比較的少ないですが、マイコンのプログラムではそこそこ使います。

 

以上の6つが論理演算ではよく使われてます。基本的には前半の3つであるANDORNOTですべての論理演算が可能です。また、NANDNOR2つでもすべての論理演算が可能です。

 

次に今回の本題であるビット演算のお話をしたいと思います。ビット演算では先ほどまで紹介した論理演算をビット単位で行います。ビット単位でのビット演算を考えるため、まずは演算を行う数値を2進数で表します。2進数表記した値を1桁ずつ論理演算するとビット演算になります。まずは、ANDORNOTXORのプログラミングにおける記号とビット演算するとどのようになるかを紹介します。

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の状態(HIGHLOWか)を設定するレジスタを使ってみたいと思います。

Atmega328Pのデータシート(https://avr.jp/user/DS/PDF/mega328P.pdf)のページ65の図の部分より、ポートBの出力状態を切り替えるレジスタは”PORTB”であることがわかります。
2

続いて下部のビットの一覧部を拡大します。
3

図のようにビット0PB0の状態 を制御、ビット1PB1の状態を制御…といったようになっており、各ビットに0を入れると対応するポートがLOW1を入れると対応するポートがHIGHになります。このように、1つのレジスタでPB0からPB7までの8つのピン(厳密にはPB6PC7はたいてい水晶を接続するので6ピンになることが多い)の状態を制御する仕様になっていることがわかると思います。そのため、1つのピンのみを制御しようとして、レジスタに直接値を入力するとPB0からPB7までのすべての状態が変わってしまい不都合が生じてしまいます。ここで必要なのがビット演算というわけです。特定のビットのみを操作したいときは、現在のレジスタの状態と操作するビットが立てられた(1にされた)値をビット演算で比較をします。現在のレジスタの状態はデータシートのビット一覧(上の図)のアクセス種別の表記より読み込みが可能であることがわかります。そのためこのような処理が可能というわけです。

特定のポート(ビット)のみを操作する方法を、例を使って紹介していきたいと思います。それぞれ操作する前の状態はわからない(テキトーな値)になっているとします。

1        PB4HIGHにしたいとき(ビット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;”と書くことは少ないです。)

この方法はマイコンのレジスタから値を取り出す際や通信の際によく使う手法です。

ビット演算は以上として次回はビットシフトのお話をしたいと思います。