PICの勉強

13196
PSX用のMOD Chipのソースコードを教材にしてPICマイコンのアセンブラの 勉強をします。
MOD Chipに使用されている石はPIC12C508です。PICアセンブラは石によって若干 仕様が異なりますがニーモニック等はだいたい似ているので参考になるでしょう。
背景が水色の部分は実際のpsxmod.asmのソース、背景がピンクの部分は 説明のための例です。

  1:	;------------------------------------------------------------------------
  2:	;PSX_MOD_CHIP
  3:	;------------------------------------------------------------------------
まず、出てきたのが";"(セミコロン)。これはここから行末まではコメントって事です。 単純ですね。
ソースリストの先頭には最低次の事は書いておきましょう。 プログラム名、作成日、作者、バージョンなどなど。
このソースはちょっとやばいのでその辺のことが書いてないようです。
  4:		list    P=12C508
  5:		radix   dec
  6:		include "p12c508.inc"
4行目の"list"は使用しているチップを指定しています。
5行目の"radix"は出力の形式を指定。"dec"はINHEX8M(8bit Merged HEX Format)を指定しています
6行目はインクルードファイルの指定です。
初心者のために追記。行頭の" 4:"はこの説明の為に付けた行番号です。 実際のソースファイルには付いていません。ついでにもうひとつ追記、ソースファイル中の 半角スペースやタブはいくら入っていても関係ありません。 ただしひとつの単語はスペースで別けないように。つまり"list"を"l i s t"と書くことは できません。
  7:	;------------------------------------------------------------------------
  8:		__FUSES _MCLRE_OFF & _CP_OFF & _WDT_OFF & _XT_OSC
  9:	;------------------------------------------------------------------------
8行目でフューズの指定をしています。マスクオプションと呼ばれる物と同じと 考えていいでしょう。オシレータタイプやウォッチドックタイマ等の設定をしてます。
クロックはPSXからもらっています。4.45MHzらしいです。
 10:		cblock	0x07
 11:			i		;ループカウンタ
 12:			j		;ループカウンタ
 13:			k       	;ループカウンタ
 14:			x       	;ウエイトを作るときのループカウンタ
 15:			y       	;ウエイトを作るときのループカウンタ
 16:			xmit    	;送信データ
 17:			index   	;ルックアップテーブルのインデックス
 18:			mode    	;4MHzと4.45MHzの切り替え
 19:		endc
"cblock" - "endc"で変数を定義しています。
 20:	;------------------------------------------------------------------------
 21:		org	0x00		;スタートアドレス
"org"とはアドレスを指定するものです。21行の様に書くとこれ以降のアセンブルされた コードは0x00番地から格納される事になります。
 22:		movwf	OSCCAL		;オシレータキャリブレーションレジスタ
 23:		goto	start		;ジャンプ
やっと命令が出てきました。アセンブラのソースってのはこんなもんです。 ソースの初めの方はこんな事ばかりです。
"movwf"命令は Wレジスタの内容をfで指定されるアドレスに格納する命令です。
例えばWレジスタに0x12が入っていた場合に22行を実行すると(OSCAL)にWの内容が格納 されます。"OSCCAL"はこのソースファイルの中では定義されていませんが、インクルードファイル "p12c508.inc"で"0x05"と定義されています。普通にユーザーが定義して使うことの出来る 領域と区別してここはSpecial Function Registerと呼ばれています。
"OSCAL"とはオシレータのキャリブレーション関係のレジスタです。
この命令の前にWレジスタになにを入れるかは書いていないので、リセット時のWレジスタ の内容が"OSCCAL"に入ります。マニュアルを読むとWの初期値は"不定"なのでなにが入るか わからない・・・でもオシレータのキャリブレーションはこうやれって書いてあるから まあいいか。
23行の"goto"は指定されるアドレスにジャンプするって命令です。だから23行の場合は "start"ってラベルのついたとこまで飛ぶって訳です。
 24:	;------------------------------------------------------------------------
 25:	;サブルーチン
 26:	;------------------------------------------------------------------------
 27:	; 名称 dly50
 28:	; 機能 50msのディレイ
 29:	; 引数 なし
 30:	; 戻値 なし
 31:	;------------------------------------------------------------------------
 32:	; 名称 dly_ms
 33:	; 機能 "w"msのディレイ
 34:	; 引数 w = ディレイ(ms)
 35:	; 戻値 なし
 36:	;------------------------------------------------------------------------
サブルーチンを書くときはこのように簡単な説明を書いておくと後でメンテするときに 非常に役立ちます。必ず書くように心がけましょう。
あと、ソース中のコメントは出来るだけ多く入れましょう。
コメントのないソースは他人にはもちろん、書いた本人でさえ時間が経てば 訳がわからなくなります。捗っているときなどついついコメントを書くのを 怠ってしまいますがそういう時は一段落ついたら忘れないうちにコメントを書くように しましょう
 37:	dly50	movlw	50		;50 -> W
 38:	dly_ms	movwf	x		;W -> x
 39:	dy_0	movlw	249		;1msのループカウンタ(249)
 40:		movwf	y		;249 -> y
 41:	dy_1	nop			;4 * 249 = 996
 42:		decfsz	y,F		;yをデクリメントもし0ならスキップ
 43:		goto	dy_1		;
 44:		decfsz	x,F		;xをデクリメントもし0ならスキップ
 45:		goto	dy_0		;
"dly50ms","dly_ms"ルーチンを説明します。
"dly_ms"ルーチンは呼ばれたときにWレジスタに入っていた値×1msだけ何もしない(?)で 時間だけつぶすルーチンです。"dly50ms"はWに50を入れて"dly_ms"を実行しているので 50msのウェイトルーチンです。
まず37行目"movlw"命令です。"MOVe Literal to W"です。
これはある固定値(Literal)をWレジスタに格納する命令です。 37行の場合は値50をWレジに入れています。
あと、行頭にある"dly50"とはラベルと呼ばれるものです。ジャンプしてくるときの 目印です。
38行目は説明済の"movwf"命令ですね。Wの内容をfで指定するアドレスに格納します。
39、40行でyに249を代入してます。 そして41行は"nop"命令で何も実行していません。クロックサイクルのみ消費しているのです。 42行目の"decfsz"命令はちょっとややこしいかな?
"DECriment F,Skip if Zero"ってことです。fをデクリメント(-1すること)して結果が0なら 次の命令をスキップ(実行しない)する命令です。
書式は"decfsz f,d"です。dは0か1で、演算結果の格納先を指定します。 0の時は演算結果をWレジスタに、1の時は演算結果をfに格納します。
42行目では"F"と書かれていますね。これはインクルードファイルでF="1"、W="0"と定義されているので "decfsz y,1"と同じことです。yをデクリメントしてyに格納してるわけです。
43行目は説明済の"goto"です。"dy_1"にジャンプします。
この42行目と43行目の組み合わせはある回数同じ処理を繰り返したいときに 良く使いますので少し説明を
  1:			MOVLW	10
  2:			MOVWF	I
  3:	LABEL		・
  4:			・
  5:			処理
  6:			・
  7:			・
  8:			DECFSZ	I,F
  9:			GOTO	LABEL
まず、繰り返したい回数を適当なループカウンタに代入します。例では"I"に10を入れてます。
そして適当なラベルを付けて繰返し実行したい処理を記述します。この処理の中ではループカウンタ の値を変化させないように注意してください。ループカウンタの値を参照することは問題ないでしょう。 そして後ろに"DECFSZ"と"GOTO"で上記8、9行目の様にします。8行目でループカウンタをデクリメント し、結果が0以外だったら9行目の"GOTO"文で繰返し処理の先頭にジャンプします。デクリメントした 結果が0ならば次の"GOTO"文をスキップしますので繰返し処理は終わります。
上記例の場合だと処理を始める前はI=10。1回処理を実行する毎に-1、10回処理を実行するとI="0"となって ループを抜ける訳です。
"dly_ms"ルーチンでは1msを作るために249回のループを、そしてxmsのディレイを作るために そのループをさらにx回ループしています。このようにネスティングも可能です。
1msを作るのに4×250じゃなくて4×249なのはループの外にもマシンクロックを数クロック消費 するのでその分少なくなっています。
 46:		btfss	mode,0		;ディレイモード(4MHz or 4.45MHz?)
 47:		retlw	3		;mode<0>が0なら 3 -> W してリターン
 48:		movlw	36		;4.45MHzモードなら1113
 49:		movwf	y		;36 -> y
 50:	dy_2	decfsz	y,F		;yをデクリメントもし0ならスキップ
 51:		goto	dy_2		;
 52:		retlw	3		;3 -> W してリターン
46行目の"btfss"はBit Test F,Skisp if Setです。
書式は"btfss f,b"です。fのビットbをチェックして1ならば次の命令をスキップ するというわけです。
modeのbit0が1ならば次の命令をスキップして48行目を実行します。 modeのbit1が0ならば47行目の命令を実行します。
47行目"retlw"はリターン命令ですがちょっと普通と違うのはRETurn with Literal to Wと 言うようにある固定値をWレジスタに格納してからリターンするということです。 これはなかなか便利ですよ。
47行目ではWレジスタに3を入れてリターンしています。
46行から52行ではメインクロックの種類(4MHzと4.45MHz)により1msのウェイトのループ数が 異なるので4MHzモード(mode<0>>="0")なら1000×4/4M=1msとし、4.45MHzモード(mode<0>=1)なら 1113×4/4.45M=1msとしているわけです。
PIC12C508自身の内部クロックを使用した場合は4MHzですが、PSからクロックを供給 してもらう場合は4.45MHzです。
だから今回の場合は4.45MHzの場合のみを記述すれば良いのですが、ある情報によると PSには4MHzクロックの物があるらしい(本当?)ので両方に対応しておいたわけです。
そしてこのディレイルーチンを抜けるときには"retlw 3"でWレジスタに3を入れています。 これは別にここに書く必要もないのですが、ここに書くと1ステップ節約になるので ここに書いています。3の意味は後述します。
 53:	;------------------------------------------------------------------------
 54:	; 名称 sendln
 55:	; 機能 4バイト毎にw回送信
 56:	;      ブロックの先頭に72msのウエイトを入れる
 57:	;      LSBファースト、4ms/bit、1スタートビット、2ストップビット
 58:	;      信号はLowの時はLow出力、Highの時はポートをハイインピーダンスにする
 59:	; 引数 w = ブロック数
 60:	; 戻値 なし
 61:	;------------------------------------------------------------------------
"sendln"ルーチンでは4文字を1ブロックとした文字列をWブロック送信しています。 送信はGP1を使って行います。GP2はLow固定です。
送信の際にLowビットはポートを出力にしてLow出力していますが、Highのときは ポートをハイインピーダンスにしてます。こうすることによりPSを壊すことなく CD種別情報を流すシリアルポートにPICからの出力信号を乗せる事が出来るわけです。
 62:	sendln	movwf	i		;W -> i
 63:	sl_0	movlw	72		;72msのウエイト
 64:		call	dly_ms		;
62行目でブロック数をiに格納します。そして各ブロックの先頭に72msのウェイトを入れます。
 65:		movlw	4		;
 66:		movwf	j		;4 -> j
 67:	sl_1	movf	index,W		;index -> W
 68:		call	lines		;今回送信するデータを取得
 69:		movwf	xmit		;xmitへ格納
 70:		comf	xmit,F		;送信用にインバート
65、66行目でjに4を入れています。この4は1ブロックが4バイトからなっている事を 示しています。
変数"index"はこれから送る文字が何文字目かを表す変数です。 "sendln"ルーチンを呼ぶときはindexを0にして最初から送信するようにします。 62行目と63行目の間にでもindexをクリアする命令を入れておいても良いでしょう。 というかそちらの方が自然でしたね。
67行目でindexをWに代入して68行目で"lines"ルーチンを呼んでいます。 "lines"ルーチンは後述しますがWレジスタにこれから送る文字コードを入れて 戻ってきます。
69行目にPCが進んだときにはWレジスタには送信する文字コードが入っています。 その送信する文字コードを"movwf"命令で変数xmitに格納してます。
送信は不論理なので70行目で反転させています。
"comf"命令はCOMplement Fの事で、書式は"comf f,d"です。dで演算結果の格納場所を 指定します。これは前述の"decfsz"命令などと同じです。 70行目ではxmitを反転して結果をxmitに入れています。
後で気がついたのですが、何もここで反転させなくても送信の時のビットチェック の論理を逆にすれば同じことだったので1ステップ短く出来ましたね。
 71:
 72:		movlw	8		;8ビット
 73:		movwf	k		;8 -> k
 74:		movlw	b'11111011'	;スタートビット
 75:		tris	GPIO		;GP1をハイインピーダンスへ GP2は出力
 76:		movlw	4		;4ms待ち
 77:		call	dly_ms		;
72行目73行目は8ビット送信するのでそのためのループカウンタkに8を入れました。 74、75行目は説明が必要でしょう。74行目でWレジスタにb'11111011'を代入します。 b'????????'は2進数を表していてb'11111011'は0xfbと同じことです。または251とも。 でも75行目の"tris"命令ではそれぞれのビットがポートに対応しているために 2進数で記述した方がわかりやすいのです。
75行目の"tris"命令はload TRIS registerといってTRISと言う特殊なレジスタに Wレジスタの内容を格納する命令です。書式は"tris f"でfはPIC12C508の場合は "GPIO"のみが入ります。
TRISレジスタとはTRI-Stateの事でポートを出力かまたは出力トランジスタをオフして ハイインピーダンス状態にするかの設定をするレジスタです。1にしたビットに対応する ポートはハイインピーダンス状態に、0にしたビットに対応するポートは出力ポートとなり GPIOレジスタの内容が出力されます。
ハイインピーダンスにしたポートは出力ラッチの内容にかかわらずポートの状態を 読み込むことが可能ですので入力ポートとして使用するときも対応するTRISレジスタのビット を1にします。
GPIO、TRISともビット0がポートGP0へビット5がポートGP5へ対応しています。6、7ビットは 空きです。
だんだんハード絡みの話になってきたので難しくなって来たと思いますがここが 山場なので頑張って下さい。
ポートの出力の仕方を例を使って説明します。
  1:	MOVLW	B'11111110'
  2:	MOVWF	GPIO
  3:	MOVLW	B'11111110'
  4:	TRIS	GPIO
  5:
  6:	MOVLW	B'11111100'
  7:	MOVWF	GPIO
  8:	MOVLW	B'11111101'
  9:	TRIS	GPIO
リセット時にはTRISはb'11111111'になっており全ポートとも全てハイインピーダンス 状態になっています。 1、2行目でGPIOのビット0をクリアしています。これによりポートGP0のラッチは0になります。 しかし、TRISのビット0はまだ1なのでGP0のピンはまだハイインピーダンスのままです。
3,4行目でTRISのビット0が出力になり、ポートGP0がLow出力されます。
6,7行目でGPIOのビット0、1がクリアされました。ピンの状態はまだGP0がLow、GP1はHi-zのままです。
8,9行目でGP1が出力に設定されました。結果、GP1はLowにGP0はHi-zになります。
なんとなくわかっていただいたでしょうか?
psxmod.asmの74〜77行目ではGP0をハイインピーダンス状態にして4ms待っています。
これはスタートビットと言うもので、調歩同期通信の際の「これから送るよ!」って合図の事です。 送る速度はあらかじめお互いに決めてあるのでこのスタートビットを頼りにあとはお互いに時分の 持っているクロックで1バイト分送受信するわけです。PSの場合は速度は4ms/bitです。
この調歩同期通信方式の場合、クロックが共有でないために少しづつずれて来ます。また 次の1バイトを送り出すタイミングも受信側にはわからないので1バイト毎にこのスタートビット (1ビット分のHigh)と後述するストップビットをつけて送信しています。
 78:
 79:	sl_2	rrf	xmit,F		;送信するビットを取得(Cに入っている)
 80:		movlw	b'11111001'	;ポートをロウに
 81:		movwf	GPIO		;GP1="0"、GP2="0"
 82:		btfsc	STATUS,C	;ロウならスキップ
 83:		movlw	b'11111011'	;11111011 -> W (GP2出力、GP1はHi-z)
 84:		btfss	STATUS,C	;ハイならスキップ
 85:		movlw	b'11111001'	;11111001 -> W (GP1=GP2=出力)
 86:		tris	GPIO		;ポートセット
79行目に新しい命令が出てきました。"rrf"命令です。これはRotate Right F命令です。 書式は"rrf f,d"です。dは"comf"命令などと同様です。
fの内容を1ビット右シフトします。bit0の内容はCarry Flagに、bit7にはCarry Flagの内容が 入ります。
簡単な例をあげます。
  1:	movlw	b'00110011'
  2:	movwf	abc
  3:	rrf	abc,F
この例を実行する前にCarry Flagは0だったとします。 1、2行目で変数abcにb'00110011'を入れました。そして3行目の"rrf"命令で右シフト1回を 行った結果は、
abc = b'00011001' Carry Flag Onとなります。
3行目を実行する前のabcのbit0の内容がCarryに、Carryの内容がabcのbit7へ入ったのです。
PICにはこのrrfと左シフトのrlfの二つのシフト命令があります。z80などになれている人にとったら たった2つ〜と言いたくなるでしょうが・・ そういえば富士通のマイコンなんかもシフト命令は2個だけですね。
psxmod.asmに戻ります。
80、81行目でポートGP1の出力ラッチを0にしています。ポートGP2も0です。 GP2は常にLowを出力している必要があります。
ポートGP1は前にスタートビットの所でHi-zにしていたので80、81行を実行したところで 今のところポートのピンの所では何も変化はありません。理解できない方は少し上(↑)の TRISとGPIOのところをもう一度読んでください。
82行でCarryフラグをチェックしています。
命令は"btfsc"、"Bit Test F,Skip if Clear"です。書式は"btfsc f,b"です。fのビットbを チェックしてクリアならば次の命令をスキップします。
82行目ではSTATUS<C>をチェックしています。STATUSレジスタは特殊レジスタの一種で bitCはCarryフラグです。だから82行目はCarry FlagがOffならば次の命令をスキップ。 というわけです。
84行目の"btfss"は"Bit Test F,Skip if Set" でビットチェックしてセットならスキップ と言うわけです。
82行目〜85行目でCarryの状態によってTRISに入れる値を決めているわけです。 Carry Onならb'11111011'をoffならb'11111001'をWレジスタに入れています。 86行目でTRISレジスタを設定します。 つまり次に送信するビットが1ならばGP1はHi-zに、0ならばLow出力にセットしているのです。
ここでひとつ気づきました。
	sl_2	rrf	xmit,F		;送信するビットを取得(Cに入っている)
		movlw	b'11111001'	;ポートをロウに
		movwf	GPIO		;GP1="0"、GP2="0"
		movlw	b'11111001'	;11111001 -> W (GP1=GP2=出力)
		btfsc	STATUS,C	;ロウならスキップ
		movlw	b'11111011'	;11111011 -> W (GP2出力、GP1はHi-z)
		tris	GPIO		;ポートセット
このように書けば1ステップ節約になりましたね。
 87:		movlw	4		;4ms待ち
 88:		call	dly_ms		;
87、88行目では1ビット分の4msのウェイトを置いています。
 89:		decfsz	k,F		;全ビット送信したか
 90:		goto	sl_2		;まだならジャンプ
89行目でkをデクリメントして8ビット全部送信したかをチェック。 まだ8ビット送り終えてない場合はsl_2へジャンプして次のビットを 送信します。8ビット送り終えた場合は90行をスキップして91行目へ。
 91:		movlw	b'11111001'	;ストップビット
 92:		tris	GPIO		;GP1=GP2=出力
 93:		movlw	8		;8ms待ち(2ビット分)
 94:		call	dly_ms		;
91、92行目でGP1、GP2を出力に設定しています。80、81行目でGPIOは b'11111001'になっているので92行目の時点でGP1はLow出力します。GP2もLow出力 のままです。
そして93、94行目で2ビット分8msのウェイトを置いています。
ストップビットとは1又は2ビット(それ以外のこともある)のLow信号で、PSXとの通信は 2ビットのLow信号となっています。受信側は自分のタイミングで各データのビットを読み、 そしてストップビットが正しいかを見てから次のスタートビットを待ちます。
 95:		incf	index,F		;indexをインクリメント
 96:		decfsz	j,F		;4(j)バイト送信したか
 97:		goto	sl_1		;まだならジャンプ
 98:		decfsz	i,F		;iブロック送信したか
 99:		goto	sl_0		;まだならジャンプ
100:		retlw	3		;3 -> W してリターン
95行目では次に送信するデータを示すindexをインクリメントし、 96、97行ではj(4)バイト送信終わったかをチェックしています。4バイト(1ブロック)送信 終わっていなかったら次の文字を送るためにsl_1へジャンプします。 1ブロック送信が終わっていたら、98行目でiブロック送信終わったかをチェックし 終わっていなかったらsl_0にジャンプしてつぎのブロックの送信を始めます。 iブロック送信が終わっていたら100行目でWレジスタに3を入れてリターンです。 3の意味は後述します。
101:	;------------------------------------------------------------------------
102:	;送信データ
103:	;------------------------------------------------------------------------
104:	lines	addwf	PCL,F		;PC + W -> PC
105:		dt	'S','C','E','I'	;日本/NTSC
106:		dt	'S','C','E','A'	;アメリカ/NTSC
107:		dt	'S','C','E','E'	;ヨーロッパ/PAL
サブルーチン"lines"はWレジスタにindexの値を入れてcallされます。 戻り値はW+1番目に送信する文字コードをWレジスタに入れてリターンします。
まず104行目ですが命令"addwf"が出てきました。 "ADD W and F"です。書式は"addwf f,d"です。 PCLとはプログラムカウンタの下位8ビットのことです。同一ページであればPCとして考えて 問題ありません。プログラムカウンタとは次に実行する命令の番地を表しています。 だから、addwf PCL,FとしてPCLにWレジスタの値を加算すると、加算した結果のPC番地まで ジャンプする事になります。
linesルーチンをコールするときはWレジスタには0〜11の値が入っています。 例えばWレジに3が入っていてlinesをコールした場合、104行を実行した結果、PCは'I'の 所になっているのです。
ここで"dt"の説明です。
PIC16/17の頃にはこのdtはありませんでした。これは命令と言うよりも疑似命令(orgとか)の ひとつです。 例えば105行は
	retlw	'S'
	retlw	'C'
	retlw	'E'
	retlw	'I'
と展開されます。 これでこのルーチンがリターンが記述されていない問題は解決です。
だからさっきの例では、PCが'I'のところというのは"retlw 'I'"を実行するのです。 前にも説明したように"retlw"はリテラルをWレジスタに代入してリターンでしたので、 コールしたときのW+1番目の文字をWレジスタに入れてリターンするということです。
108:	;------------------------------------------------------------------------
109:	;メイン
110:	;------------------------------------------------------------------------
111:		org	0x0100
112:	start	movlw	b'11000010'	;TMR0プリスケーラを1/8へ(f_osc=4MHz)
113:		option			;
いよいよメインルーチンです。メインルーチンは0x0100番地からとしてます。
112、113行目で特殊レジスタoptionを設定しています。optionレジスタはアドレスを 持っていない(変な表現)ので設定するには"option" 命令を使います。 タイマのプリスケーラの設定やウォッチドッグタイマの設定、プルアップの設定などは この"option"で設定します。
ハード絡みでひとつづつ説明するのはめんどいので今回は説明しません。 だんだん怠慢になってきた。
114:		movlw	b'11111111'	;ポート初期化
115:		tris	GPIO		;全ポートハイインピーダンス
TRISレジスタを全ポートハイインピーダンスに設定します。
リセット時の値は「不定」ですのでちゃんと設定します (さっきリセット時は0xffだとか言っちゃった様な気がしますがそれは嘘でした。)。
116:		clrf	mode		;0 -> mode
"clrf"命令は "CLeaR F"です。0x00をfに格納します。
クロックのモードを0にしています。0とは4MHzの事ですね。
117:	;
118:	;リセット後約50ms経ったらGP1をロウにする
119:	;
120:		call	dly50		;50ms待ち
121:		bcf	GPIO,1		;GP1をロウに
122:		movlw	b'11111101'	;11111101 -> W
123:		tris	GPIO		;GP1を出力ポートに
コメントにある通り、50msウェイトの後に121行目でGPIOのビット1をクリアして 122、123行目でGP1を出力ポートにしています。
あれ?命令"bcf"は説明していないな。 書式は"bcf f,b"でfのビットbをクリアします。 Bit Clear Fですね。
むむむ・・・またややこしい説明をする必要が・・・
"bcf"や"bsf"(Bit Set F) はリードモディファイライト命令と言って、一旦fの内容を読んでそして 指定されたビットをセットなりクリアしてそしてまた格納すると言った命令です。
普通のメモリに対して行うのなら問題ないのですが、ポートの設定をこれでやるときは 注意が必要なんです。例をあげて説明しましょう
  1:	BCF	GPIO,4
  2:	BCF	GPIO,5
  3:	MOVLW	B'11001111'
  4:	TRIS	GPIO
実行する前はTRISレジスタは全ビット1で全てのポートが ハイインピーダンスだとします。
この例ではGP4とGP5をロウ出力したかったのですが、結果はGP4がHigh、GP5がLow出力と なりました。なぜでしょう?順をおって説明します。
まず、1行目でGPIOのビット4をクリアします。これは良いでしょう。GPIOのビット4は0 になりました。しかし出力ラッチはクリアされましたがピンはまだハイインピーダンスのままです。 2行目の"BCF"命令を実行したとき、 "BCF"命令はまずfをリードします。この場合はGPIOです。 しかし、GPIOのラッチではなくてピンの状態そのものをリードするために、GP4のラッチは0なのに 外部の状態で例えばb'00111111'とかで読み込んでしまいます(bit6,7は常に0が読み出させます。)。 そしてその読み込んだ値のビット5をクリアしてその結果をGPIOに書き込むと GPIOの内容はb'00011111'となってしまいます。 つまり、GP5のラッチは0になりましたがGP4のラッチは1になってしまったのです。
そして4行目の"TRIS"を実行するとGP4、GP5が出力ポートになり、GP4、5の内容が出力されます。 この場合だとポートGP4はHighに、GP5はLowになるのです。
ちょっとややこしかったけどわかりましたか?
ポートを読むときはラッチの値に関係無しにポートの状態を読めると考えれば良いでしょう。 このおかげで入出力ポートの混在が可能なのですね。
124:	;
125:	;約850ms後にGP2をロウにする
126:	;
127:		movlw	17		;17*50ms=850ms
128:		movwf	i		;17 -> i
129:	m01	call	dly50		;
130:		decfsz	i,F		;
131:		goto	m01		;
132:		bcf	GPIO,2		;GP2をロウに
133:		movlw	b'11111001'	;11111001 -> W
134:		tris	GPIO		;GP1とGP2を出力ポートに
135:	;
もう説明しなくてもわかるでしょう。
50msのウェイトを17回(50ms×17=850ms)呼んだ後にポートGP2をLowにします。GP1は Lowのままです。
136:	;約314ms後にGP1よりデータを送信する
137:	;
138:		movlw	6		;6*50ms=300ms
139:		movwf	i		;
140:	m02	call	dly50		;
141:		decfsz	i,F		;
142:		goto	m02		;
143:		movlw	14		;14ms
144:		call	dly_ms		;
145:
50msのウェイト6回と14msのウェイトであわせて314msのウェイトです。
146:	m03	clrf	index		;0 -> index
147:		call	sendln		;送信
148:		incf	mode,F		;mode ++
149:		goto	m03		;ジャンプ
150:		end			;終わり
146行でindexをクリアした後に147行でサブルーチン"sendln"を呼んでいます。 たしか"sendln"は引数にWレジスタにブロック数を入れる事になっていましたね。 でもここでは入れていませんね。"movlw 3"を忘れたのでしょうか?答えはいいえです。 dly_msルーチンの戻りの所で"retlw 3"としていたのを覚えていますか?そうです。 あそこでWレジスタに3を入れていたのはこのためだったんです。 144行でdly_msを呼んでいるためにWレジスタは3になっているのです。 このおかげで1マシンサイクル節約されたのです。
でもこうゆうことはちゃんとコメントに書いておかないと後でわからなく なります。
データを送信した後に148行目の命令"incf" "Increment F"でmodeを+1しています。 incfの書式は"incf f,d"です。
modeは4MHzと4.45MHzの切り替えでした。実際はmodeのbit0が0か1かしか見ていないので 148行目はモードをトグルしているだけです。
149行でm03にジャンプしてまたデータの送信を始めます。でも今度はmodeが変わっているので 送信速度が変わっています。
そして送信が終わったらまたmodeをトグルしてまた送信・・ あとは電源が切れるかリセットがかかるまでこの繰返しです。
しかし、問題が・・・
リセットされて通信を開始するまでの間のいろいろな待ち時間は 全部modeが0の時に実行されます。4MHzモードです。 本当のクロックが4.45MHzなのにソフトタイマが4MHzと思って実行すると 約10%も時間が短くなります。
しかし、この辺のタイミングは十分に幅のある仕様になってますので全然問題じゃ なかったんですね。
実際にタイミングがシビアなのは送信速度の4ms/bitだけなのでそのほかのタイミングは だいたいでOKです。通信は始めに4MHzの方で行われます。実際のクロックが4.45MHzの 場合はPS側で正常に受信できない場合がありますがこれも問題ありません。 どうゆうわけかと言うと、PS側は通信に失敗してもリトライを行います。リトライを 行っているうちに正常なタイミングでデータが入ってくればそれでOKなんです。
ちなみに日本仕様のPSXはこの時に"SCEI"の文字列を受信したらそのCDはPSX日本仕様 のディスクと判定してます。だからアメリカ仕様の"SCEA"やヨーロッパの"SCEE"なんて コードは日本仕様のMOD Chipには不要です。が、一つで全部をカバーしようとした結果 3種類の全てのコードを送出する様になっているのです。

psxmod.asmを使用したソフトの勉強は以上です。
今度はこのソースのアセンブルの仕方、PICへの書込み方を・・と思ったけど 疲れちゃったからとりあえずここまで。続きは近いうちに作成します。 いつになるやら・・・・
参考資料
psxmod.asm (ソースファイル)
psxmod.hex (アセンブルしたヘキサファイル)
PIC12C5XX Instruction Set Summary
Microchip社のサイト

Back to software menu
Back to home
メールはこちらから