詳説 AT90S2313 半分理解
2001年7月1日

・ここで解説するソースコード(テキストファイル)をダウンロードして下さい ⇒	"standby.asm"
・このファイルは秋月のAVRライターキットに付属していたアセンブラで、アトメル社の
  ウェブサイトから最新版アセンブラをダウンロードできます。
・アトメル社のアセンブラマニュアルやAT90S2313の日本語訳PDFファイルをレディオテクニカの
  ウェブサイトからダウンロードできます(自作派のリンク)。
・このプログラムは「プログラミング基礎」で説明した、スイッチの判定→それによるLEDの点灯の
  ような単純な処理ですから、AVRを完全に理解する事はできません。

    1行〜  :コメント文。 アセンブラは何のマシンコードも生成しません。

.device AT90S2313
	これは擬似命令で、AT90S2313に、ではなく「アセンブラ」に対する命令(指示をして
	いるもの)です。 各種あるATシリーズの中でAT90S2313用のマシンコードを生成するよう
	アセンブラに対し、指示しています。
	また擬似命令は、AVRのプログラム(命令)ではありませんから、何のマシンコードも
	生成しません。

.equ	SREG	=$3F
	:
	:
  	これも擬似命令で、数値に名前を付けて、プログラムを書く時に数値の代わりに自分のわかり
	易い名前を使うことができ「ラベル」といいます。
	equ擬似命令もアセンブラが違えば記述の仕方も違います。 どのアセンブラでも使う目的は
	同じなのですがアトメルの場合、下のような記述をすることになっています。
		.equ	ラベル		=式
	また、説明書には「式」がどういうものかも説明してあります。アトメルの場合は擬似命令は
	全てピリオドで始まります。
		.equ	abc		=5
	と書くと、abc という名前(ラベル)は 5という値を持ちます。 既に定義したラベルは
	5という数値を書く代わりに abc と書いても良いのです。
		.equ   	cde		=abc+2
	と書くと、abcが既に(予め)5と分かっていますから、cde は 7の事です。
	abc+2が「式」です。 「式」とは、数値だと思って下さい。
	「式」の数値は十進でも二進でも十六進でも書く事ができますが、アトメルのアセンブラでは…
		1234 	… これは十進で、千二百三十四の事です
		$1234	… これは十六進の値です。 十進では4660の事です
		0b0001011… これは二進の値です。 十進では11の事です

.def	cr0	=r8
	:
	:
	これも擬似命令で、ラベルの定義と似ていますが、=の後がレジスタ名になっています。
	レジスタは8ビットで r0 からr31までの32本あります。
	r0〜r15 は、LDI命令のように即値を取り扱う命令では使えません。
	r16〜r31は即値を取り扱う命令が使えます。
	この点を除けばr0〜r31は全て同じように使う事ができます。
	更に r26とr27、r28とr29、r30とr31は2つまとめて16ビットレジスタ
	として使うこともできるようです。

.dseg	;Data segment
.org	$60
DDSc:	.byte 1
FreqW:	.byte 4
	:
	:
DIS	.byte 1	;must be after[mycall]
	これらも擬似命令で、.dseg は 16進の60番地から始まるRAMエリアの領域を
	バイト単位で区切って名前を付けています。ただし、この部分はDDS-VFO用の残りカスで
	消し忘れですから、スタンバイ遅延では何も使っていません。
	.org擬似命令はアセンブルする時、アセンブラが使うカウンタの初期値を指示するもの
	で、AT90S2313では $60〜$0DF がRAMエリアです。
	ここでは使っていない .eseg擬似命令というのがあって、内臓EEPROMのデータを
	設定するものです。

PICと決定的に違うのが、このRAMエリアです。PICの場合レジスタは「W」1つだけ。レジスタ
のように使っているのは63本(63バイト)のRAMです。命令体系がこのRAMに対するようになっ
ているため、RAMである事を意識せずに使っています。 だからイミディエート命令は、対Wだけ
でしょ? 無駄なやりくりステップが多くなるのよね。
AVRは 「r0」〜「r31」の32個がレジスタで、そのうち16個はどれもPICの「W」の様に
使えます。 他にRAMが128バイトあるのです。 どちらも350円、AVRのクロックはPICの
ように4分周しませんから、1マシンサイクルはPICの4倍の速さです。 賢いあなたならどっちが
得かすぐ分かる。 更に言えばI/Oポート数で言ってもPICが8+5=13。AVRは8+7=15 
ですよ〜〜ん。

.cseg	;Code segment
.org	$000
	rjmp	Init	;Power-on reset
	rjmp	Init	;INT0
	:
	:
	rjmp	Init	;Analog Comparator
	.cseg擬似命令が正味のプログラムの始まりを指示しています。 ゼロ番地のリラティブ
	ジャンプは電源オンでプログラムが始まるもの。 後のはコメントに書いたように、各種の
	割り込みサービスルーチンへのフックです。 割り込みは一切使っていないので全部Init
	に飛ばしてます(定石)。

Init:
	cli			;DI
	ldi	gr0,RAMEND
	out	SPL,gr0		;Set SP to the end of RAM
	まず全ての割り込みを禁止。それからスタックポインタをRAMの末尾にセットします。
	PICと違い、一丁前にスタックポインタが使えますね(PUSH・POP命令がある)。
	セットの方法が、アウト命令を使わなければいけないのがおかしいけど、どうでも良い事。

Iniport:	;initalizes ports
	ldi	W,0b01111000	;|x|Ry9|LED3…
	out	DDRD,W
	clr	W
	out	PORTD,W

	ser	W		;W=$FF
	out	DDRB,W
	clr	W
	out	PORTB,W
	ここは、ポートDとポートBを初期化します。
	ポートDに関してLDI命令で即値のビットが "1" なら、対応するポート(のピン)は
	出力に "0" なら入力になります。PDは7ビットで、PD6をRy9のオン・オフに、
	PD5〜PD3をLED3〜LED1に使いリレーを動作させる(アクティブな)バンドを
	示します(ここまで出力)。 PD2は遅延時間、50m秒・200m秒の選択スイッチに、
	PD1はアクティブバンド(リレーを制御するバンド)の変更スイッチに、PD0はマイクの
	PTTスイッチを読み取りに使います(最後の3ビットは入力)。最後にゼロを出力して
	出力ポートを全て "L" にします。→LEDやリレーがオフになる。
	後半は、ポートBの初期化です。 PB0から順にRy1、2…Ry8に対応しています。
	全て出力です。最後にゼロを出力して全てのリレーをオフにします。

	ldi	UF1,200
	clr	UF2
	ldi	UF3,0b00001000
	sbi	PORTD,3
	ldi	past,0b00000011
	ここは、メインルーチンに入る前のレジスタクリアです。 UF2は使いませんでした。
	UF1は各リレーの動作遅延時間で、200ミリ秒を初期値にしています。PD2の入力
	ポートが "L" なら200ミリ秒 "H" なら50ミリ秒の遅延時間です。
	UF3は第3、4、5ビットだけを使いました。それぞれバンド1、2、3に対応します。
	これらは BAND プロシジャで更新され、PTTon/PTToff プロシジャで参照されます。
	LDI命令とSBI命令により、まずはバンド1をアクティブにし、LED1を点灯して
	います。
	レジスタ past は、スイッチの状態を保存するもので、一旦 W に読み込まれたスイッチの
	状態が past と違えば W を past にコピーして、変化したスイッチに対する処理をして
	次回のスイッチ読み込みで再びWと比較されます。初期値としてptt=オフ、バンドSW
	=オフ、遅延SW=オンを与えています。

Main:	rcall	SW
	sbrc	past,0
	rcall	PTToff
	sbrs	past,0
	rcall	PTTon
M1:	sbrs	past,1
	rcall	Band
M2:	sbrc	past,2
	rcall	DLY50
	sbrs	past,2
	rcall	DLY200
	rjmp	Main
	この12命令がメインプロシジャです。 SWでスイッチ(PTTなど)を読み取り、past に
	結果を得て、それぞれの処理をします。
	past の第0、1、2ビットにチャタリング除去後のPD0、1、2の3ビットがコピーされて
	います。3ビットともプルアップされていますから、PTTが押されたら "L",放されたら"H"
	バンドボタン(スイッチ)が押されたら "L",放されたら "H" 、遅延時間選択ジャンパーが
	刺さっていたら "L",外されてたら"H"です。

	まずPTTのビットがゼロかを調べ、ゼロなら rcall   PTToff をスキップし、ゼロでなけ
	れば実行します。つまり、このビットがゼロと言うことは"H"にプルアップしてあるピンが
	PTTスイッチを握る事でGNDに落ちて"L"になり、送信する場合です。逆にPTTを放し
	たら、このビットはプルアップが効いて"H"になり、受信になります。次は今の逆のチェックを
	します。

	次にバンドボタン(スイッチ)が押された場合、現在アクティブなバンドを1つ繰り上げます。
	このスイッチはH/Lの「レベル」ではなく、1回押す毎に繰り上げ処理をしたらそれで終わらな
	いといけませんから"H"の場合はスキップして何もしません。また、連続動作を防止するため、
	Band プロシジャの最後で、このスイッチが放される("H"に戻る)まで待ってからリターンし
	ます。

	最後に遅延時間選択のショートピンの状態を調べ処理します。PTTスイッチの場合と
	同じです。そしてMainに戻ります。

PTToff:	;control relays from TX to RX
	sbrs	UF3,3
	rjmp	Poff1
			;turn Ry3,Ry2 and Ry1 off
	cbi	PORTB,5		;無線機(バンド1)のPTTをオフ。受信にする
	rcall	Delay
	cbi	PORTB,6		;リニア1のオフ。スルーにする(スタンバイオン)。
	rcall	Delay
	cbi	PORTB,7		;プリアンプ1の(スタンバイ)オフ。受信アンプを動作にする。
	ret
Poff1:	sbrs	UF3,4
	rjmp	Poff2
			;turn Ry6,Ry5 and Ry4 off
	cbi	PORTB,2
	rcall	Delay
	cbi	PORTB,3
	rcall	Delay
	cbi	PORTB,4
	ret
Poff2:	sbrs	UF3,5
	ret
			;turn Ry9,Ry8 and Ry7 off
	cbi	PORTD,6
	rcall	Delay
	cbi	PORTB,0
	rcall	Delay
	cbi	PORTB,1
	ret
基板を作ったらアップしているソースコードとリレーの対応が変わったため、処理順序が異なります。
	ここは、UF3の状態を調べ、該当するバンドのリレーを順にオフにします。
		RY1→PortB5:バンド1の無線機PTT
		RY2→PortB6:バンド1のリニア・スタンバイ
		RY3→PortB7:バンド1のプリ・スタンバイ
		RY4→PortB2:バンド2の無線機PTT
		RY5→PortB3:バンド2のリニア・スタンバイ
		RY6→PortB4:バンド2のプリ・スタンバイ
		RY7→PortD6:バンド1の無線機PTT
		RY8→PortB0:バンド1のリニア・スタンバイ
		RY9→PortB1:バンド1のプリ・スタンバイ

PTTon:			;受信から送信に切り替える
	sbrs	UF3,3
	rjmp	Pon1
			;turn Ry1,Ry2 and Ry3 on
	sbi	PORTB,7		;プリアンプ1の(スタンバイ)オン。受信アンプを切り、
	rcall	Delay		;						スルーにする。
	sbi	PORTB,6		;リニア1のオン。動作させる。
	rcall	Delay
	sbi	PORTB,5		;無線機(バンド1)のPTTをオン。送信にする
	ret
Pon1:	sbrs	UF3,4
	rjmp	Poff2
			;turn Ry4,Ry5 and Ry6 on
	sbi	PORTB,4
	rcall	Delay
	sbi	PORTB,3
	rcall	Delay
	sbi	PORTB,2
	ret
Pon2:	sbrs	UF3,5
	ret
			;turn Ry7,Ry8 and Ry9 on
	sbi	PORTB,1
	rcall	Delay
	sbi	PORTB,0
	rcall	Delay
	sbi	PORTD,6
	ret
基板を作ったらアップしているソースコードとリレーの対応が変わったため、処理順序が異なります。
	ここは、UF3の状態を調べ、該当するバンドのリレーを順にオンにします。

BAND:		;shift control band up
	cbi	PORTD,6		;turn Ry9 off
	clr	W
	out	PORTB,W		;turn Ry1-Ry8 off
	;
	lsl	UF3		;shift UF3
	sbrs	UF3,6		;check overrun
	rjmp	BND1
	ldi	UF3,0b00001000
BND1:	out	PORTD,UF3
BND2:	in	W,PIND
	in	W,PIND
	andi	W,0b00000011	;ignore dalay-sw bit
	brne	BND2
	ret
	ここは、マイクのPTTスイッチで送受信リレーを制御するバンド(1〜3)を変更するもの
	です。Band スイッチを押すと、現在リレーを制御するようになっているバンドが1つ繰り
	上がります。もし、Band 3 でスイッチが押されたら Band 1 に戻ります。

	何をおいても、先に全てのリレーをオフにします。
	それから、UF3を左に1ビットシフトしてからビットが左に行き過ぎたか調べます。 行き
	過ぎていたら第3ビットにセットし直します。
		UF3=00001000	Band 1 に対応するリレー(Ry1,2,3)を制御
		UF3=00010000	Band 2 に対応するリレー(Ry4,5,6)を制御
		UF3=00100000	Band 3 に対応するリレー(Ry7,8,9)を制御
	UF3の変更が終わったらバンドに対応するLEDを点灯します。 そしてスイッチを読み込み、
	バンドスイッチとマイクのPTTが押さていなければ、リターンします。

DLY50:
	ldi	UF1,50
	ret
DLY200:	
	ldi	UF1,200
	ret
	ここは、単純にUF1の値を50か200にしてリターンします。
		UF1=50		Delay time を 50mS にする。
		UF1=200		Delay time を 200mS にする。
	UF1は サブプロシジャ Delay の中で gr1 にコピーされて使われます。

SW:	;Check PTT inputs
	in	W,PIND
	andi	W,0b00000111
	cp	past,W
	brne	SW1
	ret
SW1:	mov	past,W
	clr	gr0
SW2:	in	W,PIND
	andi	W,0b00000111
	cp	past,W
	breq	SW3
	rjmp	SW1
SW3:	inc	gr0
	cpi	gr0,200
	brne	SW2
	retΩ
	初解説、世界最速のチャタリング除去ルーチン。
	とりあえず1回スイッチポートを読みます。 最前のポートの状態 past と今の状態を
	比較して同じならすぐにリターン。
	違っていたら…それがチャタリングの始まりであれ、作られた完璧なデジタル波形で
	あれ…SW1に飛びます。past には今読んだスイッチの状態がコピーされ新しい値に更新
	されます(SW1)。 引き続きポートの状態を読みpastと比較します。 違っていたら、
	past を更新して変化が無くなるまで「読み」、「比較」と「更新」を無限に繰り返し
	ます。「無限に」と言ってもチャタリングはそのうちなくなります。スイッチの種類で
	チャタリングの期間が違っても構いません。
	W=past になったら gr0 のカウンタを+1、W≠past ならgr0をクリアして最初から
	やり直し。 200回連続してポートの状態が同じだったら(gr0が200になったら)
	チャタリングが収まったと判断してリターンします。 
	ボロスイッチでも、フォトスイッチでも、他のTTLレベルの信号でも信号の状態に
	合わせたチャタリング除去をします。

NOPu:

Delay:
	解説省略。