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

thank you for your access




 ※ 念願のプリント基板を製作する前に。

 【9】少し高度なプログラム・・・


 ではプログラムをもう少し改造して、LED8を点けたり消したりさせてみます。

基板の左端にあるのがLED8です。


 〝TSTLOP:〟のラベルを〝BSF PORT_C,7〟の前に移動させて次のように改造します。

		MOVWF	PORT_B
		MOVWF	PORT_C
TSTLOP:
		BSF	PORT_C,7
		BCF	PORT_C,7
		GOTO	TSTLOP

〝BSF PORT_C,7〟で点灯させた後〝BCF PORT_C,7〟で〝0〟を出力して消灯。そして〝GOTO TSTLOP〟で処理を繰り返して、LEDを点滅させようというものです。

 ソースコードが書けたら、〝Build〟ボタンを押してアセンブルします。前回のプログラムでブレークポイントを設定していましたので、逆アセンブラボックスのブレークポイントのラインを左クリックして、薄オレンジのラインにします。そして〝BreakPoint〟ボタンを押して、ブレークポイントを解除します。アセンブルにエラーが無ければ《Run》ボタンを押します。

 とりあえず、点滅らしきことはやっていますが、速すぎて点いたり消えたりとは程遠い動きになっています。Flash PICでこの速さですから、本物のPICだとおそらく薄く〝ぼや~〟と点灯するぐらいになると思います。原因は処理速度が速すぎるからです。クロックを落として全体の速度を落とすこともできますが、そんなことをやったら他の処理にまで影響がでてしまい使い物になりません。何らかの方法で、はっきり点灯と消灯を繰り返すぐらいにしなければだめです。

 では、どうするか・・・です。

 点灯と消灯はできているのですが、その間隔が速すぎるところに原因があります。点灯した後にある程度の時間が経ってから消灯させて、またある程度の時間が経ってから点灯をさせるようにすれば問題解決ですね。

 それにはある程度の時間をつぶす処理というのが2つ必要です。


▽ サブルーチン △

同じ処理を2回もソースコードに書くというのは、効率が悪いですね。数十回となればもう大問題です。そこで、同じ処理を何度も書く手間を省く方法があります。サブルーチン化という考え方です。

 LEDを点滅している処理、ここでは〝TSTLOP:〟の繰り返しの部分です。ここ以外に〝時間つぶしをする〟処理を別のプログラムエリアに作って、必要に応じてその処理を呼ぶというやり方です。ここでいう〝時間つぶしをする〟処理をサブルーチンと呼び、それを呼ぶことをサブルーチンコールと呼びます。

 まず、〝時間つぶしをする〟処理を考えてみましよう。

 〝時間つぶしをする〟のですから、何もしない命令を何回も繰り返し行うループを作って、その回数分が終わったらそのループから抜け出せるようにすればいいわけです。そこで回数を数える変数が必要になります。プログラムの世界ではこの回数を数える変数をカウンターと呼んでいます。このカウンターをユーザーRAMのどれかひとつに割り当てて使用します。ラベル名を〝CNT〟として、ソースコードの〝EQU〟擬似命令を書いている部分に追加します。ユーザーRAMは〝0x0B〟~〝0x14〟までありますので、ここでは〝0x0B〟を使用します。

 〝CNT EQU 0x0B〟という擬似命令を追加します。

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
;
CNT		EQU	0x0B
		org	0x00
;ここからプログラムコードを書きます。

これで、ソースコード内で〝CNT〟と書けば、アセンブラがレジスタのアドレスを自動的に〝0x0B〟にしてくれます。

 次にサブルーチンとなる、〝時間つぶし〟をする処理を考えます。

 35個の命令の中に〝DECF f,d〟と〝DECFSZ f,d〟がありましたが、これが使えそうです。どちらもレジスタ f を〝-1〟して〝d=0〟なら結果をWregに、〝d=1〟なら、もとの f レジスタに格納するとあります。〝DECFSZ f,d〟の場合は、まだおまけがついていて、その結果がゼロなら次の命令をスキップする、とあります。これを利用して〝時間つぶし〟処理を作ります。


		MOVLW	0x10		;Wreg=0x10(10進で16)
		MOVWF	CNT		;それを変数CNTへ入れる
CNTLOP:
		DECFSZ	CNT,1		;CNTを-1して再びCNTに再書き込み
		GOTO	CNTLOP		;CNTが〝0〟でなければCNTLOPへジャンプ

これが〝時間つぶし〟処理の全貌です。処理の初めで変数〝CNT〟へ〝0x10〟を入れています。〝0x10〟なので10進では16になります。次の〝CNTLOP:〟はこの処理のループの基点になるラベル名です。
 次の〝DECFSZ CNT,1〟が変数〝CNT〟の内容を〝-1〟して、指定先〝d〟が〝1〟になっていますので、減算した結果をもう一度〝CNT〟へ格納します。つまり、〝0x10〟→〝0x0F〟として〝CNT〟に書き込みます。当然まだ〝CNT〟はゼロではないので次の命令をスキップしません。次の命令は〝GOTO CNTLOP〟ですので,ラベル〝CNTLOP〟へジャンプします。そしてこれを繰り返します。

 〝DECFSZ CNT,0〟とやってしまうと、〝CNT〟を〝-1〟してそれを〝Wreg〟に書き込みますので、〝CNT〟の中はいつまで経っても〝0x10〟のままで変化しませんのでここでは〝d〟は〝1〟になるわけです。


 〝CNT=0x01〟まで繰り返したあと、〝DECFSZ CNT,1〟は今までとちょっと違った動きをします。まず、いつものように〝CNT〟を〝-1〟して〝0x00〟を〝CNT〟に格納します。〝-1〟したらちょうど〝0x00〟ゼロになりました。すると〝次の命令をスキップ〟ですので、〝GOTO CNTLOP〟を飛ばしてこのループから脱出します。これで〝時間つぶし〟処理が完成です。

 ところで、〝DECFSZ CNT,1〟を見てください。〝d=1〟なので〝CNT〝のあとに〝,1〟となっています。これについては間違いは無いのですが、〝1〟にすると、もとのレジスタに格納。〝0〟にするとWregに格納というのが非常に解りにくいと思いませんか。ソースコードを書いているときはまだしも、あとで見直したときに転送先が〝レジスタ〟なのか〝Wreg〟なのか、それともビット番号なのかピンと来ません。PICアセンブラの一番悪いところです。見通しが悪すぎるんです。なので、MPASMでは〝1〟とか〝0〟とか書かずに、〝W〟とか〝F〟と書くことができるように、前回説明した〝#include "P16F84.inc"〟などの設定ファイル内で定義されています。

 ところがそれでもまだ見にくいので、私はもっと解りやすいように〝d=0〟の時は〝to_W〟、〝d=1〟の時は〝to_F〟と書けるように、擬似命令を使ってカスタマイズしています。その方法は・・・。

 #include P16F84.inc (←使用するデバイスで変ります)と書いた次の行あたりに
 #define to_F  F
 #define to_W  W


 と書けば〝to_F〟や〝to_W〟が使えるようになります。
 これをやらないで、本物のMPASMで〝to_F〟と書くと
 〝Symbol not previously defined (to_F)〟というエラーになります。

 Flash PICでは〝include〟や〝define〟擬似命令が使えませんので、これらの定義は行わなくても〝W〟や〝F〟または〝to_W〟〝to_F〟と書いてもエラーが出ないようになっています。

 そこらあたりを踏まえて訂正しますと次のようになります。

		;ここから〝時間つぶし〟処理
TIMER:
                MOVLW   0x10        ;Wreg=0x10(10進で16)
                MOVWF   CNT         ;それを変数CNTへ入れる
CNTLOP:
                DECFSZ  CNT,to_F    ;CNTを-1して再びCNTに再書き込み
                GOTO    CNTLOP      ;CNTが〝0〟でなければCNTLOPへジャンプ
                RETURN

〝to_F〟と書くだけで、途端に見やすいプログラムになります。キー入力が多いので面倒だという方は、〝DECFSZ CNT,F〟でもエラーにはなりません。

 〝GOTO CNTLOP〟の次に〝RETURN〟という新しい命令が入っていますが、これは〝サブルーチンコールされた次のアドレスへ戻る〟という命令です。そしてこの処理の先頭に書かれているラベル名、〝TIMER:〟がこのサブルーチンの名前であり、コールするときのアドレスになります。つまり、別の処理からこの〝TIMER〟というサブルーチンをコールすると、処理はこの〝TIMER:〟ルーチンである〝〝時間つぶし〟を行って、終わるとサブルーチンをコールした次のアドレスへ戻る、という仕組みになります。


 上記の〝TIMER〟サブルーチンを加えたプログラムの全体はこうなります。

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
;
CNT		EQU	0x0B
		org	0x00
;ここからプログラムコードを書きます。
		MOVLW	0x3F
		MOVWF	TRIS_A
		CLRW
		MOVWF	TRIS_B
		MOVWF	TRIS_C
		MOVWF	PORT_B
		MOVWF	PORT_C
TSTLOP:
		BSF	PORT_C,7	;LED8を点灯
		CALL	TIMER		;〝時間つぶし〟へ飛ぶ
		BCF	PORT_C,7	;LED8を消灯
		CALL	TIMER		;〝時間つぶし〟へ飛ぶ
		GOTO	TSTLOP		;繰り返す
;
;
		;ここから〝時間つぶし〟処理
TIMER:
		MOVLW	0x10		;Wreg=0x10(10進で16)
		MOVWF	CNT		;それを変数CNTへ入れる
CNTLOP:
		DECFSZ	CNT,to_F	;CNTを-1して再びCNTに再書き込み
		GOTO	CNTLOP		;CNTが〝0〟でなければCNTLOPへジャンプ
		RETURN			;終わったら戻る

上のリストをキーボードから打ち込むのが面倒くさい方は、リストをすべて選択コピーして、Flash PICのソースボックスに貼り付けても可能です。エラーさえ出なければ使用できます。
 《Build》ボタンを押してアセンブルします。エラーが無ければ《Run》ボタンを押します。
 こんどはLED8がはっきり点滅しています。



 このソースコードをアセンブルして1倍速のFlash PICで走らせると、ちょうどいい感じの点滅間隔になりますが、20倍速だとまだまだ速すぎます。〝TIMER〟サブルーチンで〝CNT〟に設定している数値を最大の〝0x00〟(256回ループ)で、ようやくちょうどいい速度になります。倍速変更ボタンはツールボックスの《+》《-》ボタンです。

 本物のPICの場合はFlash PICの千倍は速く走りますので、この〝TIMER〟処理でもまだ足りないほどです。このような時は、二重カウンターにします。

		;二重カウンターの例
TIMER:
		MOVLW	0xF0
		MOVWF	CNT		;CNT=0xF0
LOOP1:
		MOVLW	0x00		;Wreg=0x00(最大値)
		MOVWF	CNT2		;それを変数CNT2へ入れる
LOOP2:
		DECFSZ	CNT2,to_F	;CNTを-1して再びCNTに再書き込み
		GOTO	LOOP2		;CNTが〝0〟でなければCNTLOPへジャンプ
		DECFSZ	CNT,to_F
		GOTO	LOOP1
		RETURN

上の例では、256回のループを〝0xF0〟(240)回、繰り返して終了しますが、本物のPICではまだ足りないかもしれませんね。PICはそれほど速い処理能力を持っています。




 では先ほどのプログラムの動きがどうなっているか《Step》動作させみましよう。プログラムを停止させて、《Watch》ウィンドウを出してください。左のような状態で止まっています。逆アセンブラボックスの〝TSTLOP:〟から〝TIMER〟サブルーチンの終わりまでの部分が下の写真です。

 プログラムカウンターが〝0x009〟で停止しています。停止させたタイミングでどこで止まるかは未定ですが、バグが無い限り〝TSTLOP:〟の中か〝TIMER〟サブルーチンの中のはずです。

《Watch》ウィンドウには新しく作ったカウンター用の変数〝CNT〟がレジスタ〝0x0B〟に作られていることを確認してください。

 動きを勉強するのでLED8を点灯した直前にプログラムを停止させましよう。

 逆アセンブラボックスのアドレス〝0x007〟、〝TSTLOP〟のラベルの部分を左クリックして選択。そして《Break Point》ボタンを押してブレークポイントを設定します。
 
 《Run》ボタンを押して走らせます。プログラムがブレークポイントの〝TSTLOP〟に入ると停止します。この番地にある命令はまだ実行していませんので、まだLED8は消えています。



 《Step》ボタンをクリックします。



上の左が《Watch》ウィンドウの〝PORT_C〟と〝CNT〟レジスタの内容で、右が逆アセンブラのリスト部分です。そして下がその時のLED8の様子です。

 ブレークポイントにあった命令、〝BSF PORT_C,7〟を実行したので、〝PORT_C〟のビット7(ビット番号は0~7の8個です)がHになって16進の〝0x80〟になり、LED8が点灯しました。




 再び《Step》ボタンをクリックします。


 〝CALL TIMER〟が実行され、サブルーチン〝TIMER:〟へ飛びました。《Watch》ウィンドウの〝PORT_C〟や〝CNT〟には変化がありませんが、《Watch》ウィンドウの上、〝Stack1〟が〝0x009〟に変化しています。

ここがサブルーチンコールのすごいところです。〝Stack(スタック)〟と呼ばれるメモリにコールされた次の番地。すなわち戻り番地が記録されているのです。
 〝0x009〟となっているのでプログラムのアドレス〝0x009〟すなわち〝CALL TIMER〟命令の次の番地を指しています。
 このスタックエリアはFPICでは2個までしかありません。本物のPICでも2個~8個しかありませんので注意が必要です。
 スタックエリアが2個ということは、サブルーチンの入れ子(ネスティング)が2回までということです。サブルーチンの中から別のサブルーチンへ飛ぶことはできますが、さらにその中から次のサブルーチンへ飛ぶことはできません。もし、そのようなプログラムを書いたら、戻り番地が狂ってしまい暴走状態になります。




 再び《Step》ボタンをクリックします。

 〝MOVLW 0x10〟が実行されて〝Wreg〟に〝0x10〟が書き込まれます。《Watch》ウィンドウの〝Wreg〟の値が赤くなっていないのは前回書き込まれてから変化していないからです。Wregに異なる値を書き込む命令が他にありませんので、変化せずにそのまま残っています。




 再び《Step》ボタンをクリックします。

 〝MOVWF CNT〟が実行されて〝Wreg〟の〝0x10〟が変数〝CNT〟に書き込まれました。




 再び《Step》ボタンをクリックします。

 〝DECFSZ CNT,to_F〟が実行されて、変数〝CNT〟が-1され、〝CNT〟に再書き込みされますので、〝CNT〟が〝0x0F〟と、ひとつ数値が減っています。




 再び《Step》ボタンをクリックします。


 〝DECFSZ CNT,to_F〟が実行されて、変数〝CNT〟が-1されましたが〝CNT〟はゼロになっていないので、〝GOTO CNTLOP〟が実行されて再び〝DECFSZ CNT,to_F〟の場所に飛んでいます。そのころLED8は当然ですが、点灯したままです。



 プログラムは〝CNT〟がゼロになるまで、しばらく〝CNTLOP〟をループしますので、〝CNT〟が〝0x01〟になるまで《Step》ボタンを連打します。


  :





 さてこれが、CNT=0x01になった時の〝DECFSZ CNT,to_F〟を実行する前です。





 《 Step》ボタンをクリックしてみましよう。



〝DECFSZ CNT,to_F〟が実行されて、変数〝CNT〟が-1され、〝CNT〟が〝0〟になリましたので、次の〝GOTO CNTLOP〟をスキップして、その次の〝RETURN〟命令に進みました。




 続けます。《 Step》ボタンをクリックすると。

 
 〝TIMER〟サブルーチンをコールした次の番地〝0x009〟へ戻りました。LED8はとうぜんまだ点灯中です。



 さて、次です。《 Step》ボタンをクリックすると。




BCF PORT_C,7〟が実行されて、LED8が消えました。PORT_C〟が〝0x00〟になってビット7がLになっています。

 プログラムは続いて、もう一度〝TIMER〟処理を実行、その後〝TSTLOP〟へジャンプして、再び、LED8を点灯させてと、繰り返しになります。



 【9】少し高度なプログラム・・・----------(ここまで)