デジタル降魔録TOPページへ

thank you for your access





 【12】実験しよう(2)・・・

論理演算は学校で習ってきた数学とは少し異なっていて、取っ付きにくいかもしれませんが、プログラムを勉強する上では避けて通れませんので、もう少しやってみましよう。
 ところで演算系の処理は動きが見えず、おもしろくもなんともありませんね。でもFlash PICのWatchウインドウを使いますと、レジスタの内容が表示されますので、紙の上で計算するよりはおもしろいと思いますし理解もしやすくなります。

▽ 論理和 △


 それでは、Flash PICを使って、もう少し論理演算を実験してみましよう。
前回は論理演算命令の論理積“AND”である“ANDWF”を使用したマスク処理でした。これは1バイトの数値をビットで表した2進数に置き換えて、必要無い部分を“0”にして使おうという方法でした。
 これとは逆に必要無い部分を“1”にしたり、あるいは、あるビットだけを“1”にしたい場合の方法です。
 たとえば“REG”というメモリの第3番目のビットだけを“1”にするのなら、“bsf REG,2”とすればいいことになります。(ビット番号は0から始まるので3番目は2になります)
 しかしこの方法には欠点があります。プログラムであらかじめ何番目のビットと決めているので、プログラムを変更しない限り自由になりません。これでは拡張性の悪い優柔の利かないプログラムができ上がってしまいます。

 そこで、出てくるのが“IORWF”命令、 OR演算です。

 ORは論理和と呼ばれるもので、演算されるもの両方が“0”の時だけ結果が“0”になる性質があります。
具体的には・・・。

  1 OR 1=1
  1 OR 0=1
  0 OR 1=1

  0 OR 0=0

このようになります。

この場合の“0”と“1”は2進数ですので、“2”や“3”という数字は出てきません。従って1バイトの数値でORをとる場合は次の例のようになります。

  10 0011(→16進で0xA3)
  0001 11(→16進で0x1D)
OR_______
  1011 1111(→0xBF)

となります。


任意のビットを“1”にするために利用するのは、“1”でORを取ると、相手が“0”であろうと“1”であろうと、必ず結果が“1”になるという赤色の部分です。

 それでは任意のビットを“1”にする論理和、ORの実験をしてみましょう。

;
INDF		EQU	0x00
TMR0		EQU	0x01	;(TMR0は、まだ使用できません)
PCL		EQU	0x02
STATUS		EQU	0x03
FSR		EQU	0x04
TRIS_A		EQU	0x05
TRIS_B		EQU	0x06
TRIS_C		EQU	0x07
PORT_A		EQU	0x08
PORT_B		EQU	0x09
PORT_C		EQU	0x0A
REG		EQU	PORT_C+1;←論理和の実験のために使用します


		org	0x00
;
;★ 論理和“OR”の実験プログラム ★ 
;  ここからプログラム

		movlw	0x3F	;Wreg ←0x3F="0011_1111"
		movwf	REG	;これでREGが0x3Fになりました

		movlw	0x66	;Wreg ←0x66="0110_0110"
		iorwf	REG,to_F	;WregとREGをORしてREGに入れる
LOOP:
		goto	LOOP	;結果はWatchウインドウで見てください


			

 やろうとしているのは、下記の計算式です。

  0011 1111(0x3F)
  0110 0110(0x66)
OR__________

 実際にFlash PICにやらせてみましよう。いつものように上記プログラムの上端から下まで、マウスの左ボタンを押しながら全部選択して、右クリック“コピー”をします。
 次に左欄外の“Flash PICを起動”のリンクを押してFlash PICを起動して・・・。
ツールウィンドウの[編集]を押し、EDITウインドウの右上端の[消去]を押します。すると消去確認ウインドウが出ますので、完全削除[はい]を押します。
これでソースボックスがクリアされましたので、その中をマウス左クリックしてから、右クリック[貼り付け]を押してプログラムのコピー・貼り付けを完了させます。

 次に今入れたプログラムをアセンブルしてマシン語に変換させるために[Build]ボタンを押します。 もしエラーが出た場合は、ErrorListウインドウを消して、入れたプログラムがすべてコピーされているか、へんな文字列が入っていないか確認してください。

エラーが無ければErrorListウインドウを消して、逆アセンブラボックスを見てください。今入れたプログラムがマシン語に変換されて入っていますので、ラベル名“LOOP”のラインを左クリックで選択して、上にある[Break Point]ボタンを押して、そのラインに“Break Point”を作成します。“Break Point”というのは、プログラムがそのラインに到達した時に自動的にプログラムを止めて、“Watch”ウインドウの表示内容を更新してくれる機能です。

 “Break Point”ができたら、[Watch]ボタンを押して“Watch”ウインドウを出して、[RUN]ボタンを押してプログラムを走らせます。短いプログラムですので、1倍速でFlash PICを走らせても瞬時に“Break Point”に到達して停止します。

 停止したら、“Watch”ウインドウの“REG”と書かれたメモリ内の数値を見てください。“0x7F”となっています。
これが計算結果です。

  0011 1111(0x3F)
  0110 0110(0x66)
OR__________
  0111 1111(0x7F)

 どちらかが、“1”となっている桁はすべて“1”になって、答えが0x7Fとなります。

 ということは、あるメモリ内の任意の桁を“1”にするにはその桁のビットを“1”にした数値でORを取ればいいということになります。

 実験してみましよう。

 あるメモリ内というのはメモリ・REGでよしとして、そのビット7を“1”にしてみましよう。
ビット7だけが“1”の数値というのは、 1000 0000(0x80)です。(2進→16進変換

0x80でORを取ればいいのですから、プログラムを少し変更します。

ソースウインドウの行番号24番。
“movlw 0x66”となっているところを
“movlw 0x80”に書き換えます。

すべて半角文字で書きます。全角のスペースが混ざるとエラーが出ても、どこがエラーか分からなくなりますので注意してください。

 書き換えたら、[Build]ボタンを押して、エラーが無いことを確認後[RUN]ボタンを押してプログラムを走らせます。結果は・・・。 “0xBF”となります。2進数に直すと“1011 1111”となって“0011 1111(0x3F)”のビット7だけが“1”になっていることが分かります。

 この応用として、0~7の数値をビット位置として、そこを“1”にするという処理が作れます。
ようするに数値=0x04から、ビット位置を“1”にした2進数=“0001 0000(0x10)”を求めるというものです。

 初めに、ビット0だけを“1”にする数値“0000 0001(0x01)”を用意して、ビット番号分だけ、左シフトすればいいことになります。
 ただし、PICのシフト命令は必ずCarryフラグを引きずって来ますので、もしCarry=1の時に左シフトを行いますと、その“1”がビット0番に転送され、指定のビット番号以外のビットも“1”になるという不具合が出ます。そのため、シフトを行う前にCarry=0に上書きします。

Carry=0 にするには “bcf STATUS,0”命令です。






▽ 排他論理和 △

“XOR”と呼ばれる論理演算です。ORの時と同じように数値をXORしてみますと・・・。

  1 XOR 1=0
  1 XOR 0=1
  0 XOR 1=1

  0 XOR 0=0

このようになります。
ORでもANDでもない結果になっています。これがXOR演算です。

赤文字の部分を見てください。この演算は両方が同じ数値の場合、結果が“0”になるという性質があります。

 こんなもの、何の役に立つのかと思われますが、結構役にたっています。例えば、あるポートが“L”になったらLEDを点けるという処理を考えた時・・・。
 通常は、


btfss PORT_A,0		;PORT_Aのビット0が“H”の時次の命令をスキップ
goto JOB1		;PORT_Aのビット0が“L”の時ここからJOB1へ飛び…
			;そこでLEDを点ける等の仕事をする
			

などと記序して、条件分岐を作ります。これは「PORT_A,0が“L”になったら、~する」という固定された条件ですのでこれでいいわけですが、次のような条件の時はどうするか・・・です。

「PORT_A,0が“L”になったら、~する」時と「PORT_A,0が“H”になったら、~する」時を切り替えて使いたい。それにともなうプログラムの変更は避けたい。

 プログラムを2種類用意すれば簡単ですが、それはちょっとダサいですね。しかしこんな無理難題な条件があるのかとお思いでしょうが、現実はこのような仕様が結構求められます。

 例えば、普段は“H”状態だが、モノが通過すると“L”になるセンサーがあるとします。また逆に普段“L”だが、通過すると“H”になるセンサーもある。どちらのセンサーを使用してもちゃんと正しく条件分岐するものを作って欲しい・・・などという仕様があります。どちらのスイッチを使用するかは切り替えスイッチでPICに伝えるとしても、センサーを検知する処理が二系統にしなければいけなくなります。

ここで、排他論理和のXORが登場です。同じ数値なら結果が“0”になるというXORの性質を利用します。

メモリ・REGには、モノが通過すると“L”になるセンサーを使用する時は“0x00”が、
通過すると“H”になるセンサーを使用する時は“0000 0001(0x01)”が設定されるように初期設定しておきます。



movf	PORT_A,to_W	;PORT_Aの現在の状況をWregへ
xorwf	REG,to_W		;センサーの種類が入ったREGとXORする
andlw	0x01		;センサーの検知ポート以外をマスクする
btfsc	STATUS,2		;減算の結果 Zeroでなければ次の命令をスキップ
goto	JOB1		;Zeroなら、変化ありとしてここからJOB1へ飛ぶ
			

動きを追ってみます・・・。

 モノが通過すると“L”になるセンサーを使用中は、センサーの検知ポート、PORT_Aのビット0は普段“H”です。ですので、その“1”とメモリ・REGの“0”とでXORされるため答えは“1”です。
この時、PORT_Aの他のデータも一緒にXORされていますので、必要の無い部分をAND演算で“0”に上書きします。よって最終的にWregの中は“0000 0001(0x01)”になります。その後の条件分岐でZeroではないので、“goto JOB1”はスキップされます。

センサーにモノが通過するとPORT_Aの0ビットは“L”になりますので、この“0”と、REG内の“0”でXORされて答えが“0”になり、その後の条件分岐でZeroなので、“goto JOB1”が選択されます。

 今度はモノが通過すると“H”になるセンサーが取り付けられている時は、REG内を“0000 0001”にする約束ですので 0x01。そしてモノが通過すると、PORT_Aのビット0番が“1”になり、REGの“0000 0001(0x01)”とXORされて、答えは“0”になり“goto JOB1”へ向かいます。
 どうですか? 結果が同じになりました。プログラムの変更は一切せずに、メモリREG内を切り替えているだけです。


 XORはまだ他にも使い道があります。
Wregとメモリ・REGの中身を入れ替える時、通常なら別のメモリを用意してそこへ一度移動させてから、入れ替えを行います。


movwf	MEM1		;Wregを退避
movf	REG,to_W		;
movwf	MEM2		;REG → MEM2
movf	MEM1,to_W		;MEM1(もとWregの内容)→ Wreg
movwf	REG		;→ REGへ
movf	MEM2,to_W		;MEM2(もとREGの内容)→ Wreg
			



XORを使うと魔法のようにすっきりします。


xorwf  REG,to_F		;Wreg xor REG → REG(W xor REG)
xorwf  REG,to_W		;REG(W xor REG) xor W → W{(W xor REG) xor W}=W(REG)
xorwf  REG,to_F		;W(REG) xor REG(W xor REG) → W
			

一見して非常に分かりにくいですが、XORしたものにもう一度XORすると元に戻るという性質を利用しています。

この方法だと余分なメモリも必要とせず3命令で済みます。ぜひ、Flash PICで実験してみてください。

XORしたものにもう一度XORすると元に戻るという性質を利用すると、ビットの反転が簡単にできます。例えばLEDの点滅をさせたい時は、単純にLEDの出力ポートのビット位置を“1”でXORするだけで反転します。

LED        結果
 1 XOR 1 → 0
         
 0 XOR 1 → 1


ただし、PICを10MHz以上のクロックで走らせている時や、電流を極端に多く出力しているポートに対して、直接アクセスする時には注意が必要です。こちらをご覧ください
このような時は、出力ポートへ直接書き込むのではなく、専用のメモリを用意してそれに対してアクセスをした後、まとめてポートへ転送するような工夫が必要になります。


2009.09.06