はじめに
ここでは論理演算などについて説明します。数学的にはなじみの無い演算ですが、マイコン開発にはかかすことができないものです。
※このページで紹介する内容はあくまでも一例です。個別の作成のご相談ご質問はお答えできませんのでご了承下さい。このページと同じ内容についてのご質問についてはロボット掲示板にてお願いいたします。
※以下の情報は2007年11月現在のものです。ご注意ください。
※このページではC言語の用語が沢山出てきますが、ビギナーの方は各自C言語の初級本なども参照しながら読み進めてください。
論理演算
高校の数学で「論理」を学んだかと思います。高校生の時はこのようなものが何の役に立つのかわかりませんでしたがマイコン開発では非常に多く使います。
<真偽>
あることがらが正しく合っていることを「真」の状態、合っていないことを「偽」の状態とします。たとえば、「ボタンを押したかを判定」している場合、ボタンを押した状態が真、押していない状態が偽になります。「ボタンを押していないかを判定」している場合は逆になります。
判定内容 |
ボタンを押した |
ボタンを押していない |
ボタンを押したか? |
真 |
偽 |
ボタンを押していないか? |
偽 |
真 |
プログラムでは「真」の状態を「true(トゥルー)」、「偽」の状態を「false(ファルス)」というのが一般的です(コンピュータはアメリカで発明されたので)。
コンピュータ内部では真か偽という表現はありません。0か1かのデジタルな世界なので「trueを1、falseを1以外(1ビット表現なら0)」にしています。注意ですが、これはコンピュータ内部だけの話なので、コンピュータ以外の数学では真=1とは限りません。
<否定>
ある状態の逆を否定と言います。プログラムではNOT(ナット)と言います。ハードウェア的な回路ではインバータ(逆転機)と言います。1のNOTは0、0のNOTは1になります。
C言語で否定を演算する場合は、 「~」になります。半角でシフトキーを押しながらひらがなの「へ」キーを押すと出ます。
<論理和>
「どちらか一方がtrueならtrue」を論理和と言います。プログラムではOR(オア)と言います。C言語でORを演算する場合は「|」になります。半角でシフトキーを押しながら「¥」キーを押すと出ます。
A |
B |
A | B |
0 |
0 |
0 |
0 |
1 |
1 |
1 |
0 |
1 |
1 |
1 |
1 |
<論理積>
「両方がtrueならtrue」を論理積と言います。プログラムではAND(アンド)と言います。C言語でANDを演算する場合は「&」になります。半角でシフトキーを押しながら「6」キーを押すと出ます。
A |
B |
A & B |
0 |
0 |
0 |
0 |
1 |
0 |
1 |
0 |
0 |
1 |
1 |
1 |
<排他的論理和>
「両方が同じならfalse,両方が別ならtrue」を排他的論理和と言います。プログラムではXOR(エクスクルーシブオア)と言います。C言語でXORを演算する場合は「^」になります。半角でひらがなの「へ」キーを押すと出ます。
A |
B |
A ^ B |
0 |
0 |
0 |
0 |
1 |
1 |
1 |
0 |
1 |
1 |
1 |
0 |
上の表はそれぞれ一桁の2進数の場合になりますが、桁が複数になった場合は、それぞれの桁で演算します。例えば、
11110000 | 10101010 は 11111010 になります。
11110000 & 10101010 は 10100000 になります。
インクリメント演算子とデクリメント演算子
プログラムでは、ある変数の値を1増加させたり、1減少させたりということを頻繁に行います。1増加させることを「インクリメントする」と言い、1減少させることを「デクリメントする」と言います。四則演算で
A = A + 1;
A = A - 1;
とするとそれぞれインクリメント、デクリメントしますが、非常に頻繁に使うので簡略形として
A++;
A--;
とすると、それぞれ、インクリメント、デクリメントになります。
シフト演算子
あるビットの並びを右又は左にズラしたい場合というのもマイコン開発ではよく出てきます。C言語特有の演算にシフト演算子というものがあります。シフトはズラすという意味です。
<左シフト>
値 << シフトするビット数
<右シフト>
値 >> シフトするビット数
たとえば、2進数で 00010000 という値があり、右に1ビットシフトすると 00001000 になります。さらに右に1ビットシフトすると
00000100 になります。C言語では2進数を表現できないので、プログラム上ではあらかじめ10進数か16進数に直して演算します。この例の場合ははじめの数 00010000 は16進数に直すと
0x10 になります。ということでプログラム例で示すと、
int a,b,c;
a = 0x10;
b = a >> 1;
c = b >> 1;
の場合、bは 0x08 つまり2進数の 00001000 になります。 cは 0x04 つまり2進数の 00000100 になります。
シフトして、桁数からはみ出た場合はその値は消えてしまいます。例えば、0001 を右に1ビットシフトすると、一番右の1は桁から出てしまうのでなくなり、結果は
0000 になります。 1000 を左へ1ビットシフトすると、同じく最上位の1は消えてしまいます。
これらの演算をするのはなぜ?
これらの演算は、単に説明を読んだだけでは何に使うのかわからないかもしれません。しかし、論理演算やシフト演算は実際には非常によく使われます。ここではその一例を紹介します。
1)ビット数が違うデータ同士の計算
たとえば、dsPICにはADコンバータと言って、0〜3.3Vの電圧を数値に変換する機能がついています。これはセンサー入力などによく使われています。ADコンバータについては後で詳しく説明します。このADコンバータはdsPIC33Fの場合、モードによって結果の値が10bitで出る場合と12bitで出る場合があります。これらの結果をたとえばint型の変数、16bitの入れものに入れたとすると、
<10bit時>
最小値(0V)は 0000 0000 0000 0000 最大値(3.3V)は 0000 0011 1111 1111 になる。
<12bit時>
最小値(0V)は 0000 0000 0000 0000 最大値(3.3V)は 0000 1111 1111 1111 になる。
となり、値の解釈が違います。この2つのデータをお互いに計算させたい場合は10bitの方の値を左に2ビットシフトするか、12bitの方の値を右に2ビットシフトすれば近似計算ができます。
2)特定の入力端子だけを取り出したい場合
マイコンの端子にスイッチをつけて、その端子がONかOFFかを判定したいとします。ON/OFFの入力は汎用I/Oになりますので、結果は対応した特殊レジスタに反映されます。 今、ポートAの16本の端子の中で0番にスイッチがついていたとします(実際にはLEDが付いているので実験はできません。)。ポートAの入力状態は PORTA
というレジスタに入ります。PORTAの0番だけの状態を確認したい場合は、そのビットだけを1にした値とANDを取れば結果が求まります。
例えば、ポートAの0番がON,つまり1になっていた場合はPORTAの値を2進数で表すと
???? ???? ???? ???1
となります。0番以外は0なのか1なのかはわかりません(設定などによる。汎用IOにさえなっていない可能性もある)
ここで2進数の
0000 0000 0000 0001
とANDを計算したとします。ANDは両方1でないと1にならないので、?の部分は強制的に0になります。0番だけ、1なら1、0なら0になるということです。
このような演算は「マスク」といい、よく使われます。
たとえば、ポートAの4番〜7番が入力になっており、そこだけを抜き出したいとします。他の端子はどんな設定になっているかわかりません。この場合は
0000 0000 1111 0000
とANDを取ることで抜き出すことができます。
3)特定のビットだけを操作したい場合
特殊レジスタ(16bit幅がある)などであるとくていのビットだけ、強制的に0又は1にしたい場合があります。
???? ??x? ???? ????
たとえば、上のxの部分を強制的に0にしたいとします。これは
1111 1101 1111 1111 (マスク)
とANDを取るとできます。 マスクの1の部分は元が1なら1、0なら0になる、つまり元のままを維持できます。0の部分は強制的に0になります。
逆にxの部分を強制的に1にしたいとします。これは
0000 0010 0000 0000 (マスク)
とORを取るとできます。マスクの0の部分は元が1なら1、0なら0になる、つまり元のままを維持できます。1の部分はORなので強制的に1になります。
4)変数のビット幅が違う場合の変換
よくあるケースで、通信が8ビット単位なのに16ビットの値を扱いたいという場合があります。例えば、マイコン内で16ビット幅の変数に入っているデータを非同期シリアル通信で他のマイコンに送りたいとします。非同期シリアル通信は大抵8ビットづつしか送ることができません。この場合は16ビットのデータを2つの8ビットデータに分割します。
int a;
char b1,b2;
a = 0x2F3C;
b1 = (char)(a & 0x00FF);
b2 = (char)(a >> 8);
このプログラムはaの16ビットデータをb1とb2に分割しています。 int は16ビット型の変数宣言、charは8ビット型の変数宣言です(前ページ参照)。
4行目、a & 0x00FF で、下位8ビットを抽出しています。0x00FFは2進数に直すと0000 0000 1111
1111ですので下位8ビットがマスクされるというわけです。その前にある(char)は、計算結果をcharに無理やり変換しますという処理です。これを型変換と言います。この例のように大きい型を小さい型に変換すると、上位ビットはなくなります。ということで下位8ビットがb1に入ります。
最後の行で、a>>8 で、上位8ビットを下位8ビットへ移動しています。その後、(char)の型変換で上位8ビットをカットしています。
これらはほんの一例ですが、このように使うことがあります。
では実際にこれらの演算をして、モニタしてみましょう。
準備
<接続>
A33Fの電源を入れるため、USBケーブルをPCへつないで下さい。
A33Fにプログラムを転送するので、ICD2経由でPCへつないで下さい。
<COMポートの確認、ハイパーターミナルの起動>
前のページのようにCOM番号を確認して、ハイパーターミナルを起動して設定してください。
<プロジェクトの作成>
今回も、プログラムは前回作ったソースプログラムを流用してみます。今度は、前回作ったArithmetic.cの環境をそのまま開いてみましょう。
Project->Open を選択します。
「ファイルの場所」で、前回作ったフォルダを指定すると、プロジェクト名に.mcpが付いているファイルがありますのでそれを指定して「開く」を押しますと、前回のプロジェクトが開かれます。
前と同じ状態でMPLABの環境が設定されています。
プログラムの変更
では、ソースプログラムを下のように書き換えてください。これは先の4)のパターンである、16ビットの変数を8ビット2つに分割するもので、論理演算とシフト演算を使っています。
プログラム部分は半角にすることを忘れないで下さい。
変更点は行80〜81と行102〜109の部分です。
先の例4)と同じように16ビット幅で変数宣言したaに適当な値を入れ、b1に下位8ビット、b2に上位8ビットを入れて分割しています。
printf文の構文が前と違いますが、%X は変数値を16ビット表記の文字に変換するというものです。
結果は次のようになりました。
というようなことにこれらの演算を使います。
おわりに
次はC言語の制御文について解説します。
2007年11月28日
|