§6.PICプログラミングMANIACS(PICプログラミングの高度なテクを満載)99/4/4
そんなわけで、PICプログラミングですが、基礎的な命令の紹介とかは、後閑さんのホームページ
http://www.bekkoame.ne.jp/~gokan/
で行われているわけで、少なくともそれと重複しないようなことを書いてゆきたいと思います。いま、話を具体的に進めるためPICは、16F84とします。あまり、16F84には高度な命令は存在しませんが、800円前後で買えますし、何度も書き換えて遊べるので、このへんがお手頃なのではないかと考えています。
16F84で動作するということは、それ以下の12C508あたりでも動作する可能性が高いので、それほど一般性がない議論ではないと思います。
☆ 1命令で2ウエイトを得る
時間待ちを行なう必要がある場合、割り込みを使うか適当な回数だけループで時間つぶしを行なうかのどちらかでしょう。ここでは、後者について考えてみましょう。
こいつは、1命令 = 1word = 12ビットとなっています。16F84は最大10MHzで動作するわけですが、あまり速い設定にしても電池を食うだけなので、適当な遅さにした方が良いかも知れません。ともかく、いま、10MHzで動作しているものとしまうしょう。
PICはRISCマシンなので、どの命令でも同じ時間で実行できます。ただし、分岐を伴う場合、プリフェッチしていたものが無駄になるので2倍の時間を要します。
それでは、非分岐系の命令1つの実行に要する時間は...?
これが、1/10M秒(1M=1,000,000)ではないのです。4/10M秒なのです。1命令の実行は4サイクルから形成されていて、クロックパルスの4回分の時間を必要とするのです。逆に言えば、10MHzのPICでは、非分岐系の命令は1秒間に2.5M回だけ実行できるというわけですね。
さて、ウエイトルーチンに必要なのが、
nop
です。これは、何もしない命令です。これで4/10M秒だけ消費できますね。
問題1)しかし、実はnopの2倍の時間を消費することの出来る、何もしない命令が存在するのです。(もちろん、1wordで)
答え)
goto next
next:
です。分岐命令を使うことによって、2倍の時間を潰すわけですね。他の人のPICのプログラムでこのテクを使ってあるのを見たことが無いので、私のオリジナルのつもりです。どんどん使ってやってください。(笑)
☆ ウエイトルーチンを作る
PICの命令についておさらいしておきますが、レジスタはwだけで、あとはファイルレジスタと言って、SRAMに入ってます。たいていの命令はwに対してもファイルレジスタに対しても行えますし、どちらを使っても1word1wait(ただし、1waitを非分岐命令を1つ実行するのに必要な時間と規定する)なので、どちらでも良いでしょう。一部、ファイルレジスタに対してしか行えないものがあるので、面倒なのですが...
clrf i ; i=0 すなわち 256
loop:
decfsz i,f ; if(−−i!=0) goto loop
goto loop
これが典型的なウエイトルーチンです。
問題2)この場合、何wait消費するでしょうか?
答え)1+3*(256-1) + 2 waitです。decfszは、スキップする時のみ2waitで、それ以外は、1wait。つまり、ループ中は、1ループが1+2(gotoの分) wait必要なのです。ループ回数×3wait+最後のskipで2waitという計算です。この計算さえ出来れば、あとは、多重ループにするなり、係数を調整するなりして、1ミリ秒や1秒単位のウエイトルーチンも作れるかと思います。
☆ 16ビット変数
wレジスタも、ファイルレジスタも8ビットです。しかし、変数として、この倍の値を使いたいことも珍しくはありません。そこで、2つのファイルレジスタを疑似的に結合して、16ビット確保したりすることがあります。
iH equ 0x15 ; High byte of i
iL equ 0x16 ; Low byte of i
問題3)この疑似的な16ビット変数iをインクリメントするには?
PIC17Cシリーズなら、キャリーごと加算する命令などがあるのですが、とりあえず、16F84で出来る方法を考えてみましょう。
答え)
incfsz iL,f
goto skip
incf iH,f
skip:
の3命令で可能です。incfsz/decfszは、inc/decしたのちにゼロであればスキップする命令なのですが、意外と使いにくい気がします。個人的には、ノンゼロでスキップしてくれた方が、よっぽど有効な気がするのですが...。(ノンゼロでスキップする命令さえあれば、この場合も2命令で済みますし...)
問題4)この疑似的な16ビット変数iをデクリメントするには?
これは、やや難問です。何故かと言うと、下位バイトであるiLをデクリメント後に0xFFになっていれば、上位バイトiHをデクリメントしなければならないのですが、decではゼロフラグしか変化しないのです。すなわち、ボローフラグのようなものに反映しないのです。ということは、あらかじめiLが0であるかどうかを調べる必要がありそうです。
回答例1)
movf iL,f ; iL=iLだが、これでゼロフラグが変化する
btfsc STATUS,Z ; ゼロフラグがクリアされていればスキップ
decf iH,f ; iL=0の場合、上位バイトもデクリメント
decf iL,f
さきほども書いたように、もし、incfszのノンゼロでスキップするような命令incfsnzが存在すれば、無理やり0xffかどうかを調べに行って、
decf iL,f
incfsnz iL,w ; 実際はこんな命令は存在しない
decf iH,f
という技も使えるのですが、惜しい限りです。仕方ないので、これのincfszに書き換えたやつを使うとすると、
回答例2)
decf iL,f ; iL−−
incfsz iL,w ; if(iL+1!=0)goto skip
goto skip
decf iH,f
skip:
となりますね。回答例1.でも、2でも、共に5waitで完了するので、こちらも正解とします。
問題5)この疑似的な16ビット変数iが0なら、リターンするコードは?
これは、朝飯前ですね??
答え)
movf iL,w ; w=iL
iorwf iH,w ; w|= iH
btfsc STATUS,Z
return
問題6)この疑似的な16ビット変数iが0x1234ならwに0、そうでないならwに非0を入れてリターンするコードは?
これは、ちょっと難しいかも知れないですね。素直に書くなら、
movlw 0x34
subwf iL,w
btfss STATUS,Z
retlw 1
movlw 0x12
subwf iH,w
btfss STATUS,Z
retlw 1
retlw 0
ですね。retlwってのが、リテラル持ってリターンする命令です。これも1命令なので、関数からの戻り値として、ディフォルト値を設定したいときに使えます。
ただ、これは、xorを利用して、こう書いて欲しいところですね...
回答)
movlw 0x12
xorwf iH,w
movwf temp ; 一時的に保管
movlw 0x34
xorwf iL,w
andwf temp
return
もとのiを破壊して良いなら、
movlw 0x12
xorwf iH,f
movlw 0x34
xorwf iL,f
andwf iH
return
とも書けますが...まあ、邪道なんでしょう。
☆ 一番時間のかかる命令(コーヒーブレイク。単なる息抜きです)
問題)一番時間のかかる命令は何でしょう?
えっ?非分岐命令1wait、分岐命令2waitでなかったの?とか言わないでください(笑)
答え)sleep 無限wait
うわー。そんなの詐欺だ〜。(笑)
☆ wに関する命令
レジスタは、wしかないわけで、wに関する技を知っているのと知っていないのとでは、自ずと生まれてくるコードも違ったものになってきます。そんなわけで、今月は、wレジスタ強化月間であります。(何のこっちゃ...)
問題7)wレジスタが0x34ならwに0、そうでないならwに非0を入れてリターンするコードを書け
もう、ここまで読んできた方なら、簡単でしょう。
答え)
xorlw 0x34
return
もはや、基本ですね。
問題8)wレジスタが0ならラベル_jmpAに飛びたい。どうすれば良いか?
wの値による条件分岐です。よく使います。
答え)
iorlw 0
btfsc STATUS,z
goto _jmpA
iorlw 0の部分は、xorwf 0とか、andlw 0xffとかも考えられると思います。特定の数値と比較して〜って場合ならxorwfが一番使いやすいでしょう。
問題9)wが0なら50,wが1なら100,wが2なら200をwに代入してリターンするコードを書け。ただし、wには、0,1,2のいずれかの値が入っていると仮定して良い。
これ、やっかいなんですよ(笑) でも、こういう処理、意外と必要なことって多いので困ってしまいます。一番、素直なコードは、
iorlw 0
btfsc STATUS,z
retlw 50
iorlw 1
btfsc STATUS,z
retlw 100
retlw 200
でしょうか。しかし、これ、数が増えてきたら、大変なんです。wでなくファイルレジスタに対してなら、decfszを連ねて行く方法も考えられなくもないですが、それもかなりダサイ部類のプログラムと言えるでしょう。
回答)
addwf PCL,f ; プログラムカウンタに加算する!!
retwl 50
retwl 100
retwl 200
です。retwlの部分は、dtという疑似命令を使用して、
dt 50,100,200
とも書けます。ただし、PCLは、プログラムカウンタの下位8ビットなので、この4命令の配置されるアドレスが256wordの境界を渡っているときは、この方法は使えないので注意しましょう。
問題10)wの値が0ならラベル_aに、1ならラベル_bに、2ならラベル_cにジャンプするプログラムを書け。
いわゆるジャンプテーブルですね。先ほどのテクを使えば簡単でしょう。
回答)
addwf PCL,f
goto _a
goto _b
goto _c
くれぐれも、境界には気をつけて...。
しかし、ハードウェア工作で、まさかこんなにプログラムの話を書くとは思わなかったです(笑)
要望があれば、また続き書きます...>PICテク
とりあえず、パーフェクトダンスマシーンを完成させないことには...(笑)