;***** ElectronicMetronomeII.asm ******************************************
;
;	電子メトロノームⅡ
;
;	Original  http://www8.plala.or.jp/InHisTime/PIC-156/metronome.c
;
;	Copyright (C) 2011-2013   by M.Yamamoto. All rights reserved.
;
;     ( coding:  Ver. 0.00   2011/10/29  by M.Yamamoto )
;	coding:  Ver. 1.00   2011/11/04  初期バージョン
;	update:  Ver. 1.01   2011/11/05  割り込み順位の変更
;	update:  Ver. 1.02   2011/11/10  サウンドルーチンの変更
;	update:  Ver. 1.03   2011/11/12  スイッチ処理とEEPROM書き込みの改善
;	coding:  Ver. 1.04   2011/11/13  テストバージョン
;	update:  Ver. 1.05   2013/10/08  音程の見直し
;	bugfix:  Ver. x.xx
;
;******************************************************* PicAsm 0225 ******

;変更履歴

;Ver.1.00:  LCD へのスタートメッセージの表示前後でのサウンド(dl_mi_so_dh, dh_so_mi_dl)は正常に出力されるが、
;           メインルーチンに移ったとたんに、大きな "ジィーーー" という雑音が混ざってサウンドが出力される。
;           このサウンドはタイマー０オーバフロー割り込みを利用して作成しているが、メインルーチンに移る直前
;           に CCP1(コンペア)割り込みを許可している。割り込み処理内では、タイマー０オーバフロー割り込みの
;           方が優先順位が高く、CCP1(コンペア)割り込みよりも先に処理を行っている。

;Ver.1.01:  そこで割り込み処理内での優先順位を変更し、タイマー０オーバフロー割り込みよりも CCP1(コンペア)
;           割り込みの方が先に処理を行うように変更をしてみた。結果、みごとに雑音は消滅した。ただし、次の
;           Ver.1.02 での出力サウンドと聴き比べてみると、全体的に少々濁ったような音に聞こえる。

;Ver.1.02:  オリジナルのプログラムではどのような処理をしているのか興味があったので、HEXファイルを逆アセン
;           ブルして調べてみた。（オリジナルは mikroC PRO で作成されているためソースからは分からない）
;           オリジナルの処理では、割り込みを一切使用しない通常の処理でサウンドを出力していた。そこでこの
;           割り込みを使用しない通常の処理を真似て、そのサウンドルーチンを組み込んでみたところ、Ver.1.01
;           に比較して、実に済んだサウンドが出力されることが分かった。 ... さすが mikroC PRO !!

;Ver.1.03:  また、オリジナルのプログラムでは、各スイッチを必要以上に長時間押し続けた場合、その間中各LEDの
;           点滅やサウンドがストップし、メトロノームの機能が停止するようになっている。したがって Tempoを
;           大幅に変更したいような場合には、何十回となく Tempoの Upまたは DownスイッチのON／OFFを繰り返さ
;           なければならない。これはかなり操作性が悪くスムーズではない。
;           そこでこれを改め、スイッチの誤動作を防止するために押してから250mS間は続くON動作を受け付けない
;           が、それ以上に長時間押し続けた場合には、250mSに一回づつON動作を受け付けるように変更した。した
;           がって上述のように、特に Tempoを大幅に変更したいような場合には威力を発揮する。

;           また、オリジナルで最悪なのは、EEPROMへの書き込みタイミングである。Beatや Tempoを次回の電源投
;           入時に備えてEEPROMへ記憶するという発想はいいのだけれど、Beatや Tempoが変更された都度すぐに書
;           き込んでいる。たとえば Tempoを100から200に変更した場合には、100回書き込むことになる。 これは
;           書き込み回数に制限があると言われているEEPROMにとっては最悪である。
;           本来は、次回の電源投入時に備えて― という意味合いから、電源の切断の直前にその時の状態を記憶
;           する― というのが最も理想的ではあるが、そんなことは自動的にはとうていできない。
;           そこでEEPROMへの無駄な書き込みはしないように、極力書き込み回数を減らすように改めた。上述のよ
;           うに Tempoを大幅に変更した場合にも、最終的に変更されたときから約2.5秒間待って、それでも変更
;           がなければそこではじめて書き込むようにした。
;           また、書き込む前に前回書き込んだ内容を読み出し、これから書き込む内容と比較して同値の場合には
;           新たな書き込みは中止し、異なった場合のみ新しい内容を書き込むようにした。

;Ver.1.04:  Ver.1.03 で本当に書き込みの制限をしているかの確認をするためのテストバージョンである。
;           実際の書き込みの回数を同じくEEPROMに記録して、正しく動作をしているかの検証をする。(OK確認)
;           テストバージョンというものの、前バージョン + 書き込み回数記録 であり、メトロノーム機能として
;           は何ら問題はないので、このまま本番で使用するものとする。

;Ver.1.05:  外部サウンド・サブルーチン（Sound.sub）の変更にともなって、本ソースリストの記述も少々変更。
;	    ついでに本プログラム内で使用している音程も、一部変更した。

;	    オリジナルおよび本プログラムでは、すべての音を１オクターブ高く設定してある。・・・なぜか？
;	    サウンド・テストルーチンを本プログラム内に組み込んで、サウンド・サブルーチン（Sound.sub）の
;	    下限の発振可能周波数を調べたところ、Device: PIC16F648A、Clock: 8MHz の条件で、391Hz までで
;	    あることが分かった。（ちなみに、Device: PIC16F876、Clock: 12.8MHz の条件では、626Hz までで、
;	    MatrixLED_Clock.asm 内に同様なサウンド・テストルーチンを組み込んで調べた。）

;	    391Hz というのは、音階でいうとソ（392.00Hz）までで、ファ＃（369.99Hz）以前の低い音程は発振で
;	    きない。


;==========================================================================

;	Device: PIC16F648A	Clock: 8MHz (セラロック)

;	PIC16F648A のピンと用途

;	 (1) RA2: BEAT DOWN  スイッチ入力	(18) RA1: LED2 出力
;	 (2) RA3: BEAT UP    スイッチ入力	(17) RA0: LED1 出力
;	 (3) RA4: TEMPO DOWN スイッチ入力	(16) OSC1: オシレータ水晶入力
;	 (4) RA5: TEMPO UP   スイッチ入力	(15) OSC2: オシレータ水晶入力
;	 (5) VSS: 電源 GND			(14) VDD: 電源 +5V DC
;	 (6) RB0: LCD E   信号 出力制御		(13) RB7: LCD DB7 信号 入出力制御
;	 (7) RB1: LCD R/W 信号 出力制御		(12) RB6: LCD DB6 信号 入出力制御
;	 (8) RB2: LCD RS  信号 出力制御		(11) RB5: LCD DB5 信号 入出力制御
;	 (9) RB3: Sound 出力			(10) RB4: LCD DB4 信号 入出力制御

;	*注.  RA4 の出力はオープンドレインのため、外部抵抗でプルアップしないと、High が
;	      出力されない。
;	      RA5 は入力専用で、出力ポートには使えない。

;==========================================================================

	list		p=pic16f648a	;デバイス情報
	include		p16f648a.inc	;インクルードファイル
	errorlevel	-302		;Eliminate bank warning

	__config _CP_OFF & DATA_CP_OFF & _LVP_OFF & _BOREN_OFF & _MCLRE_OFF & _PWRTE_ON & _WDT_OFF & _HS_OSC

;	注. 最近 _DATA_CP_OFF が DATA_CP_OFF に変更されているので注意

;==========================================================================
;		変数、定数の定義とレジスタ割付
;==========================================================================

#define 	ver_no	"1.05"

		radix	dec

SW_LED		equ	PORTA		;各スイッチ入力, LED 出力ポート
TRIS_SW		equ	TRISA		;各スイッチ入力, LED 出力ポートの入出力切り替え
LCD		equ	PORTB		;LCD 制御用ポート
TRIS_LCD	equ	TRISB		;LCD 制御用ポートの入出力切り替え

_LED1		equ	0		;LED 赤
_LED2		equ	1		;LED 緑
_swBD		equ	2		;BEAT DOWN  スイッチ
_swBU		equ	3		;BEAT UP    スイッチ
_swTD		equ	4		;TEMPO DOWN スイッチ
_swTU		equ	5		;TEMPO UP   スイッチ

_E		equ	0		;LCD E:   動作起動信号
_RW		equ	1		;LCD R/W: 読み出し／書き込み選択信号
_RS		equ	2		;LCD RS:  レジスタ選択信号
_BZ		equ	3		;BEEP 音出力ポート

_BF		equ	7		;LCD ビジーフラグ

f_do		equ	d'262'		;ド	261.63Hz
f_do_		equ	d'277'		;ド＃	277.18Hz
f_re		equ	d'294'		;レ	293.66Hz
f_re_		equ	d'311'		;レ＃	311.13Hz
f_mi		equ	d'330'		;ミ	329.63Hz
f_fa		equ	d'349'		;ファ	349.23Hz
f_fa_		equ	d'370'		;ファ＃	369.99Hz
f_so		equ	d'392'		;ソ	392.00Hz
f_so_		equ	d'415'		;ソ＃	415.30Hz
f_ra		equ	d'440'		;ラ	440.00Hz
f_ra_		equ	d'466'		;ラ＃	466.16Hz
f_si		equ	d'494'		;シ	493.88Hz

f_doh		equ	d'523'		;ド	523.26Hz
f_doh_		equ	d'554'		;ド＃	554.36Hz
f_reh		equ	d'587'		;レ	587.32Hz
f_reh_		equ	d'622'		;レ＃	622.26Hz
f_mih		equ	d'659'		;ミ	659.26Hz
f_fah		equ	d'698'		;ファ	698.46Hz
f_fah_		equ	d'740'		;ファ＃	739.98Hz
f_soh		equ	d'784'		;ソ	784.00Hz
f_soh_		equ	d'831'		;ソ＃	830.60Hz
f_rah		equ	d'880'		;ラ	880.00Hz
f_rah_		equ	d'932'		;ラ＃	932.32Hz
f_sih		equ	d'988'		;シ	987.76Hz

f_do2		equ	d'1047'		;ド	1046.52Hz
f_do2_		equ	d'1109'		;ド＃	1108.72Hz
f_re2		equ	d'1175'		;レ	1174.64Hz
f_re2_		equ	d'1245'		;レ＃	1244.52Hz
f_mi2		equ	d'1319'		;ミ	1318.52Hz
f_fa2		equ	d'1397'		;ファ	1396.92Hz
f_fa2_		equ	d'1480'		;ファ＃	1479.96Hz
f_so2		equ	d'1568'		;ソ	1568.00Hz
f_so2_		equ	d'1661'		;ソ＃	1661.20Hz
f_ra2		equ	d'1760'		;ラ	1760.00Hz
f_ra2_		equ	d'1865'		;ラ＃	1864.64Hz
f_si2		equ	d'1976'		;シ	1975.52Hz
dsm_tim		equ	200		;時間(ms)

		cblock	h'20'
port_x					;5:PORTA, 6:PORTB, 7:PORTC, 8:PORTD, 9:PORTE
port_bit				;0:bit0,  1:bit1,  2:bit2,  3:bit3,  4:bit4,  5:bit5,  6:bit6,  7:bit7
freq_lo					;low  周波数(Hz)
freq_hi					;high
time_lo					;low  時間(ms)
time_hi					;high

addr_high				;読み出すデータの high アドレス
addr_low				;読み出すデータの low アドレス

lcdtmp					;LCD への入出力データの一時保存
disp_cnt				;スタートメッセージの表示回数

wt1cnt					;ウェイト用ループカウンタ
wt2cnt					;ウェイト用ループカウンタ
wt3cnt					;ウェイト用ループカウンタ
wt4cnt					;ウェイト用ループカウンタ

int_cnt					;CCP1割り込みカウンタ
beatcnt					;拍子カウンタ
sw_cntn					;連続誤動作防止カウンタ = 10 x 20 = 200 mS
sw_flag					;スイッチ入力の保存
beat					;拍子:    1-8
tempo					;テンポ: 40-210
up_beat					;EEPROM(beat) 更新カウンタ
up_tempo				;EEPROM(tempo) 更新カウンタ

gen_cnt					;汎用カウンタ
sftwk					;一時余り格納場所
hex8					;8ビット（１バイト）数値
buff		;(00)			;変換文字列の格納エリア
		;(01)
		;(02)
;
lpcnt
lpcnt2
cnt_low
cnt_high
		endc

		cblock	h'60'
sound00					;
sound01					;ポートアドレス (5:PORTA, 6:PORTB, 7:PORTC, 8:PORTD, 9:PORTE)
sound02					;Sound出力ポートの bit 位置 (反転値)
sound03					;Sound出力ポートの bit 位置
sound04					;
sound05					;
sound06					;
sound07					;
sound08					;
sound09					;
sound0a					;
sound0b					;
sound0c					;

f_save					;FSR レジスタの退避用
		endc

		cblock	h'70'
sound70					;
sound71					;
sound72					;
sound73					;
sound74					;
sound75					;
sound76					;
sound77					;
sound78					;
sound79					;
sound7a					;
sound7b					;
sound7c					;

w_save					;W レジスタの退避用
s_save					;STATUS レジスタの退避用
p_save					;PCLATH レジスタの退避用
		endc

;==========================================================================
;		マクロ命令定義
;==========================================================================

;		LCD メッセージデータ表示マクロ

Disp_Lcd	macro	@com,@msg,@x
		movlw	@com
		call	Lcd_Cmd		;LCD へコマンドを出力
	#if 	@x == 0
	#else
		movlw	high @msg	;メッセージデータの先頭アドレスの high アドレス
		movwf	addr_high	;
		movlw	low @msg	;メッセージデータの先頭アドレスの low アドレス
		movwf	addr_low	;
	#endif
		call	Lcd_Disp	;メッセージデータ１行表示
		endm

;		Sound_Play_M マクロ

Sound_Play_M	macro	@frq,@tim
		movlw	low  @frq	;出力周波数(Hz)
		movwf	freq_lo
		movlw	high @frq
		movwf	freq_hi
		movlw	low  @tim	;出力時間(ms)
		movwf	time_lo
		movlw	high @tim
		movwf	time_hi
		call	Sound_Play	;サウンドを鳴らす
		endm

;==========================================================================
;		プログラム開始
;==========================================================================

		org	0		;リセットベクタ
		goto	init
		org	4		;割り込みベクタ
		goto	interrupt

		org	h'08'

;==========================================================================
;		サブルーチンファイルのインクルード
;==========================================================================

sys_clk		equ	d'8000'		;システムクロック(KHz)を指定

		include		C:\Data\Project\PIC\_sub\LCD.sub
		include		C:\Data\Project\PIC\_sub\Sound.sub

;==========================================================================
;		初期化処理
;==========================================================================

init
		; PIC16F648A 初期設定

		bcf	STATUS,RP1
		bcf	STATUS,RP0	;バンク0 を選択
		clrf	PORTA		;
		movlw	b'00000111'	;RA3-0 を Digital I/O
		movwf	CMCON		;CMCON の設定

		bsf	STATUS,RP0	;バンク1 を選択
		movlw	b'00111100'	;RA5-2: 入力, RA1-0: を出力
       		movwf	TRIS_SW		;SW ポートの I/0 設定
       		clrf	TRIS_LCD	;RB7-0: 出力, LCD ポートの I/0 設定

		movlw	b'10000000'	;bit7:1 プルアップ抵抗を使用しない
		movwf	OPTION_REG	;OPTION_REG の設定

		bsf	PIE1,CCP1IE	;CCP1機能を使用する
		bcf	STATUS,RP0	;バンク0 を選択
		bcf	PIR1,CCP1IF	;

		movlw	b'00001011'	;コンペアモード：RB3/CCP1ピンの状態は変化しない。タイマー１をリセット
       		movwf	CCP1CON
		movlw	high 2500	;クロックが 8MHzの時
       		movwf	CCPR1H		;
		movlw	low  2500	;割り込み周期 = (1/8000000)*4*8*2500 = 0.01 sec
       		movwf	CCPR1L		;

		clrf	INTCON		;割り込み制御レジスタのクリア

		bsf	STATUS,RP0	;バンク1 を選択
		bcf	PIE1,TMR1IE	;タイマー１割り込みを禁止
		bcf	STATUS,RP0	;バンク0 を選択
		bcf	PIR1,TMR1IF	;

		clrf	TMR1H
		clrf	TMR1L

		movlw	b'00110001'	;bit7-6: 未使用
					;bit5-4:11 プリスケーラのスケール値 1:8
					;bit3:0 専用の発振器を使う場合に指定
					;bit2:0 外部入力クロック指定時に有効
					;bit1:0 内部クロックを使う
					;bit0:1 タイマー１を動作させる
		movwf	T1CON		;T1CON の設定

		clrf	LCD		;LCD 制御用ポート

		movlw	LCD		;6:PORTB
		movwf	port_x
		movlw	_BZ		;3:bit3
		movwf	port_bit
		call	Sound_Init	;サウンドの初期化

;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;#define	_debug			;サウンド・テストルーチンを使用時
#ifdef	_debug
		bcf	INTCON,GIE	;全割り込み禁止

		movlw	d'250'
		call	wait_xxms
		movlw	d'250'
		call	wait_xxms

;		サウンド・テストルーチン

		movlw	d'100'		;ループカウンタ（とりあえずの回数）
		movwf	lpcnt

		bcf	STATUS,RP1	;
		bsf	STATUS,RP0	;バンク1
		clrf	EEADR		;書き込み先頭アドレスを設定
		bcf	STATUS,RP0	;バンク0

;		movlw	low d'1000'	;開始周波数(Hz)
		movlw	low d'420'	;開始周波数(Hz)
		movwf	cnt_low
;		movlw	high d'1000'
		movlw	high d'420'
		movwf	cnt_high

xx1		movf	cnt_low,W	;出力周波数(Hz)
		movwf	freq_lo
		movf	cnt_high,W
		movwf	freq_hi
		movlw	low  d'1000'	;出力時間(ms)
		movwf	time_lo
		movlw	high d'1000'
		movwf	time_hi

		bsf	STATUS,RP0	;バンク 1
		bcf	TRISA,_BZ	;音出しの直前に出力ポートにする（雑音対策）
		bcf	STATUS,RP0	;バンク 0

		call	Sound_Play	;サウンドを鳴らす

		bsf	STATUS,RP0	;バンク 1
		bsf	TRISA,_BZ	;再び入力ポートにしておく（雑音対策）
		bcf	STATUS,RP0	;バンク 0

		movlw	d'100'
		call	wait_xxms	;

		; EEPROM へ 出力周波数の書き込み

		movlw	d'02'
		movwf	lpcnt2

		movf	cnt_high,W
		goto	$+2

xx2		movf	cnt_low,W
		bsf	STATUS,RP0	;バンク1
		movwf	EEDATA		;書き込みデータを設定
		bsf	EECON1,WREN	;書き込みイネーブル = ON
		movlw	h'55'
		movwf	EECON2		;h'55' を EECON2 に書き込む
		movlw	h'aa'
		movwf	EECON2		;h'aa' を EECON2 に書き込む
		bsf	EECON1,WR	;書き込み開始
		btfsc	EECON1,WR	;書き込み 終了か?
		goto	$-1		;No

		bcf	EECON1,WREN	;書き込みイネーブル = OFF
		incf	EEADR,F
		bcf	STATUS,RP0	;バンク0
		decfsz	lpcnt2,F	;ループカウンタ - 1 = 0 か？
		goto	xx2		;No.

;		movlw	d'10'		;更新周波数 = -10Hz
		movlw	d'01'		;更新周波数 = -1Hz
		subwf	cnt_low,F
		btfss	STATUS,C	;cnt_low >= 0 か？
		decf	cnt_high,F	;No.

		decfsz	lpcnt,F		;ループカウンタ - 1 = 0 か？
		goto	xx1		;No.

;	( テスト結果-1 (-10の時) )	.....	h'0190' = d'400' Hz までＯＫ

;		00	03	E8	03	DE	03	D4	03	CA
;		08	03	C0	03	B6	03	AC	03	A2
;		10	03	98	03	8E	03	84	03	7A
;		18	03	70	03	66	03	5C	03	52
;		20	03	48	03	3E	03	34	03	2A
;		28	03	20	03	16	03	0C	03	02
;		30	02	F8	02	EE	02	E4	02	DA
;		38	02	D0	02	C6	02	BC	02	B2
;		40	02	A8	02	9E	02	94	02	8A
;		48	02	80	02	76	02	6C	02	62
;		50	02	58	02	4E	02	44	02	3A
;		58	02	30	02	26	02	1C	02	12
;		60	02	08	01	FE	01	F4	01	EA
;		68	01	E0	01	D6	01	CC	01	C2
;		70	01	B8	01	AE	01	A4	01	9A
;		78	01	90	FF	FF	FF	FF	FF	FF

;	( テスト結果-2 (-1の時) )	.....	h'0187' = d'391' Hz までＯＫ

;		00	01	A4	01	A3	01	A2	01	A1
;		08	01	A0	01	9F	01	9E	01	9D
;		10	01	9C	01	9B	01	9A	01	99
;		18	01	98	01	97	01	96	01	95
;		20	01	94	01	93	01	92	01	91
;		28	01	90	01	8F	01	8E	01	8D
;		30	01	8C	01	8B	01	8A	01	89
;		38	01	88	01	87	FF	FF	FF	FF

;f_mi		equ	d'330'		;ミ	329.63Hz
;f_fa		equ	d'349'		;ファ	349.23Hz
;f_fa_		equ	d'370'		;ファ＃	369.99Hz

;f_so		equ	d'392'		;ソ	392.00Hz  <<<<<
;f_so_		equ	d'415'		;ソ＃	415.30Hz
;f_ra		equ	d'440'		;ラ	440.00Hz
;f_ra_		equ	d'466'		;ラ＃	466.16Hz

#endif
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;

		; S-10551D(LCD) 初期設定, スタートメッセージの表示

		call	Lcd_Init	;LCD 初期設定 

		movlw	b'00001100'	;表示ON
		call	Lcd_Cmd		;LCD へコマンドを出力

		call	dl_mi_so_dh	;"ドミソド" ブザー音出力

		movlw	2
		movwf	disp_cnt	;メッセージの表示回数
ini01
;		dt	"* Metron",0,"ome II *",0	を表示
		Disp_Lcd	b'00000010',start_msg,1	;カーソルホーム
		Disp_Lcd	b'11000000',         ,0	;DDRAM アドレスセット
		call	wait_1sec
		call	wait_500ms

;		dt	"    ver.",0,"x.xx    ",0	を表示
		Disp_Lcd	b'00000010',         ,0	;カーソルホーム
		Disp_Lcd	b'11000000',         ,0	;DDRAM アドレスセット
		call	wait_1sec
		call	wait_500ms

;		dt	"  by m.y",0,"amamoto ",0	を表示
		Disp_Lcd	b'00000010',         ,0	;カーソルホーム
		Disp_Lcd	b'11000000',         ,0	;DDRAM アドレスセット
		call	wait_1sec
		call	wait_500ms

		decfsz	disp_cnt,F	;メッセージの表示回数 = 終了 か？
		goto	ini01

		call	dh_so_mi_dl	;"ドソミド" ブザー音出力

		movlw	b'00000001'	;表示クリア
		call	Lcd_Cmd		;LCD へコマンドを出力
		call	wait_1sec

		; EEPROM データの読み出し

		bsf	STATUS,RP0	;バンク 1
		movlw	eep_size	;EEPROM 格納先頭アドレス
		movwf	EEADR		;EEPROM の記録アドレスを設定
		bsf	EECON1,RD	;読み出し制御 = ON
		movf	EEDATA,W	;データの読み出し (e_beat)

		bcf	STATUS,RP0	;バンク 0
		movwf	beat		;beat の保存

		bsf	STATUS,RP0	;バンク 1
		incf	EEADR,F		;記録アドレスの更新
		bsf	EECON1,RD	;読み出し制御 = ON
		movf	EEDATA,W	;データの読み出し (e_tempo)

		bcf	STATUS,RP0	;バンク 0
		movwf	tempo		;tempo の保存

		bsf	STATUS,RP0	;バンク 1
		incf	EEADR,F		;記録アドレスの更新
		clrw			;beat 書き込み回数の初期設定を
		call	eep_write	;記録

		bsf	STATUS,RP0	;バンク 1
		incf	EEADR,F		;記録アドレスの更新
		clrw			;tempo 書き込み回数の初期設定を
		call	eep_write	;記録

		; Beat, Tempo を LCD に表示

;		dt	"Beat:x T",0,"empo:xxx",0	  を表示
		Disp_Lcd	b'00000010',beat_tempo,1 ;カーソルホーム
		Disp_Lcd	b'11000000',          ,0 ;DDRAM アドレスセット

		call	lcd_beat	;LCD に Beat 値を表示
		call	lcd_tempo	;LCD に Tempo 値を表示

		clrf	sw_flag		;スイッチ入力保存をクリア

		bsf	INTCON,GIE	;GIE  グローバル割り込み許可
		bsf	INTCON,PEIE	;PEIE 周辺機器割り込みを許可
		goto	main

;==========================================================================
;		LCD（ S-10551D の場合 ）表示用 メッセージ
;==========================================================================

		; スタートメッセージ

start_msg	dt	"* Metron",0,"ome II *",0
		dt	"    ver.",0,ver_no,"    ",0
		dt	"  by m.y",0,"amamoto ",0
;			 00000000     44444444
;			 01234567     01234567

		; Beat, Tempo 表示テンプレート

beat_tempo	dt	"Beat:  T",0,"empo:   ",0
;			 00000000     44444444
;			 01234567     01234567

;==========================================================================
;		メインプログラム
;==========================================================================

main
		;１拍目の処理
		call	interrupt_no	;CCP1割り込み回数を求める
		bsf	SW_LED,_LED1	;LED 赤を点灯

		Sound_Play_M	f_ra, 100 ;サウンド出力

		bcf	SW_LED,_LED1	;LED 赤を消灯

m01		movf	int_cnt,W	;CCP1割り込みカウンタ
		btfsc	STATUS,Z	;int_cnt = 0 か？
		goto	m02		;Yes

		call	switch_check	;各スイッチの処理
		goto	m01

		;２拍目以降の処理
m02		movf	beat,W
		movwf	beatcnt		;拍子カウンタ

m03		decfsz	beatcnt,F	;beatcnt = 0 か？
		goto	m04		;No
		goto	m06		;Yes

m04		call	interrupt_no	;CCP1割り込み回数を求める
		bsf	SW_LED,_LED2	;LED 緑を点灯

		Sound_Play_M	f_ra2, 100 ;サウンド出力

		bcf	SW_LED,_LED2	;LED 緑を消灯

m05		movf	int_cnt,W	;CCP1割り込みカウンタ
		btfsc	STATUS,Z	;int_cnt = 0 か？
		goto	m03		;Yes

		call	switch_check	;各スイッチの処理
		goto	m05

m06		goto	main

;==========================================================================
;		CCP1 割り込み回数を求める (1拍あたりの割り込み回数)
;==========================================================================

;		tempo = 1分(60秒)間における拍の数
;		割り込み周期: 0.01 秒 = ( 4 / 発振周波数: 8 MHz ) x ﾌﾟﾘｽｹｰﾗ値: 8 x ﾊｰﾄﾞﾀｲﾏｰｶｳﾝﾄ値: 2500
;		1分(60秒)間の割り込み数: 6000 = 60 秒 / 割り込み周期: 0.01 秒

#define		div16	sp_sub_04	;サウンド・サブルーチン内で使用のものを流用
#define		a16_lo	sound70
#define		a16_hi	sound71
#define		b16_lo	sound74
#define		b16_hi	sound75
#define		x16_lo	sound70

interrupt_no
		movlw	high 6000
		movwf	a16_hi		;割られる数の上位
		movlw	low  6000
		movwf	a16_lo		;割られる数の下位

		clrf	b16_hi		;割る数の上位
		movf	tempo,W
		movwf	b16_lo		;割る数の下位

		call	div16		;16 ビットの割り算 (6000 / tempo の計算)

		movf	x16_lo,W	;答の下位
		movwf	int_cnt		;CCP1割り込みカウンタ
		return

;==========================================================================

#ifdef comment
		; 16 ビットの割り算ルーチン

		; a16_hi, a16_lo: 割られる数の上,下位	(終了時には０になって戻る)
		; b16_hi, b16_lo: 割る数の上,下位	(終了時には変化せず戻る)
		; x16_hi, x16_lo: 答えの上,下位		(答えが返る)
		; y16_hi, y16_lo: 余りの上,下位		(余りが返る、内部ワークにも使用)
		; lcnt1,  lcnt2:  ループカウンタ
		; diverr:	  エラーフラグ		(割る数が０であった場合に１をセットして戻る)
div16
		movlw	16
		movwf	lcnt1		;ループカウンタ1
		movf	b16_lo,W	;割る数下位をワークにコピー
		movwf	y16_lo
		movf	b16_hi,W	;割る数上位をワークにコピー
		movwf	y16_hi

d16_01		rlf	y16_lo,F	;左シフトする
		rlf	y16_hi,F
		btfsc	STATUS,C	;キャリフラグを見る
		goto	d16_02		;割る数の上位ビット位置検索

		decfsz	lcnt1,F
		goto	d16_01

		movlw	1
		movwf	diverr		;割る数が０である。エラーコードをセット
		goto	d16_09

d16_02		clrf	x16_lo		;答え用変数のクリア
		clrf	x16_hi
		clrf	y16_lo		;ワーク用変数のクリア
		clrf	y16_hi

		movlw	16
		movwf	lcnt2		;ループカウンタ2
		movf	lcnt1,W
		subwf	lcnt2,F		;残り、実ループの回数

d16_03		bcf	STATUS,C	;キャリフラグをクリア
		rlf	a16_lo,F
		rlf	a16_hi,F
		rlf	y16_lo,F
		rlf	y16_hi,F
		decfsz	lcnt1,F		;割られる数を初期位置までシフト
		goto	d16_03

d16_04		movf	b16_hi,W	;現位置での減算が可能かチェック
		subwf	y16_hi,W
		btfss	STATUS,C
		goto	d16_06

		movf	y16_hi,W
		subwf	b16_hi,W
		btfss	STATUS,C
		goto	d16_05

		movf	b16_lo,W
		subwf	y16_lo,W
		btfss	STATUS,C
		goto	d16_06

d16_05		movf	b16_lo,W
		subwf	y16_lo,F	;ワークから下位を引く
		btfss	STATUS,C	;キャリフラグが 1（正）なら次をスキップ
		decf	y16_hi,F	;上位 -1

		movf	b16_hi,W
		subwf	y16_hi,F	;ワークから上位を引く
		bsf	STATUS,C	;キャリフラグをセット
		goto	d16_07

d16_06		bcf	STATUS,C	;キャリフラグをクリア

d16_07		rlf	x16_lo,F	;キャリフラグの内容を答えにシフト
		rlf	x16_hi,F
		movf	lcnt2,F		;lcnt2 が 0 か検査
		btfsc	STATUS,Z
		goto	d16_08		;最下位まで処理したなら終了

		decf	lcnt2,F		;ビット位置を１つ下げる（右へ）
		bcf	STATUS,C	;キャリフラグをクリア
		rlf	a16_lo,F	;ワークへ１ビット左シフト
		rlf	a16_hi,F
		rlf	y16_lo,F
		rlf	y16_hi,F
		goto	d16_04

d16_08		clrf	diverr		;正常終了

d16_09		return
#endif

;==========================================================================
;		各スイッチの処理
;==========================================================================

;		sw_flag: 割り込み処理内で設定

;		beat  の範囲:  1 ～ 8
;		tempo の範囲: 40 ～ 210

switch_check
		btfss	sw_flag,_swBD	;BEAT DOWN  スイッチ = ON か？
		goto	swck01		;No

		movlw	1 + 1
		subwf	beat,W
		btfss	STATUS,C	;beat >= 2 か？
		goto	swck06		;No, Error

		decf	beat,F		;-1 更新
		goto	swck02

swck01		btfss	sw_flag,_swBU	;BEAT UP    スイッチ = ON か？
		goto	swck03		;No

		movlw	8
		subwf	beat,W
		btfsc	STATUS,C	;beat < 8 か？
		goto	swck06		;No, Error

		incf	beat,F		;+1 更新

swck02		call	lcd_beat	;LCD に Beat 値を表示

		movlw	h'ff'
		movwf	up_beat		;EEPROM(beat) 更新カウンタの初期設定
		goto	swck07

swck03		btfss	sw_flag,_swTD	;TEMPO DOWN スイッチ = ON か？
		goto	swck04		;No

		movlw	40 + 1
		subwf	tempo,W
		btfss	STATUS,C	;tempo >= 41 か？
		goto	swck06		;No, Error

		decf	tempo,F		;-1 更新
		goto	swck05

swck04		btfss	sw_flag,_swTU	;TEMPO UP   スイッチ = ON か？
		goto	swck07		;No

		movlw	210
		subwf	tempo,W
		btfsc	STATUS,C	;tempo < 210 か？
		goto	swck06		;No, Error

		incf	tempo,F		;+1 更新

swck05		call	lcd_tempo	;LCD に Tempo 値を表示

		movlw	h'ff'
		movwf	up_tempo	;EEPROM(tempo) 更新カウンタの初期設定
		goto	swck07

swck06		call	buzzer_pi	;Error: "ピッ" ブザー音出力

swck07		clrf	sw_flag		;スイッチ入力の保存をクリア

		decf	up_beat,W
		btfsc	STATUS,Z	;beat 最終更新から 約 2.5秒経過 か？
		call	eep_beat	;Yes, EEPROM へ Beat 値の書き込み

		decf	up_tempo,W
		btfsc	STATUS,Z	;tempo 最終更新から 約 2.5秒経過 か？
		call	eep_tempo	;Yes, EEPROM へ Tempo 値の書き込み

		return

;==========================================================================

		; LCD に Beat 値を表示

lcd_beat
		movf	beat,W
;		movwf	hex8
;		call	hexdec8c_cnv	;8 ビット数値を 10進数 3桁の文字列に変換
		movwf	buff + 2
		movlw	h'30'
		addwf	buff + 2,F	;文字コードに変換

		movlw	b'10000101'	;5(05) 桁目の DDRAM アドレスをセット
		call	Lcd_Cmd		;LCD へコマンドを出力

		movf	buff + 2,W	;変換文字
		call	Lcd_Data	;LCD へデータを１文字出力
		return

		; LCD に Tempo 値を表示

lcd_tempo
		movf	tempo,W
		movwf	hex8
		call	hexdec8c_cnv	;8 ビット数値を 10進数 3桁の文字列に変換

		movlw	b'11000101'	;13(45) 桁目の DDRAM アドレスをセット
		call	Lcd_Cmd		;LCD へコマンドを出力

		movlw	a'0'		;先頭文字のゼロサプレス
		subwf	buff,W
		btfss	STATUS,Z	;buff = '0' か？
		goto	lcdt01		;No

		movlw	a' '
		movwf	buff		;' ' と置き換える

lcdt01		call	lcd_disp_m	;LCD へ変換文字列を出力
		return

		; 8 ビット数値を 10進数 3桁の文字列に変換（文字コード 30H～39H)

hexdec8c_cnv
		movlw	buff + 2 + 1	;変換文字列の格納エリア
		movwf	FSR		;間接アドレスの設定
		call	hd8c		;(02) 最下位変換
		call	hd8c		;(01)
		call	hd8c		;(00) 最上位変換
		return

hd8c		movlw	8		;8ビット繰り返し
		movwf	gen_cnt		;シフトカウンタに設定
		clrf	sftwk		;一時余り格納場所のクリア

hd8c01		bcf	STATUS,C	;キャリーフラグのクリア
		rlf	hex8,F
		rlf	sftwk,F

		movlw	-10		;-10 = h'f6'
		addwf	sftwk,W
		btfsc	STATUS,C	;C = 0 か？
		movwf	sftwk		;No
		btfsc	STATUS,C	;C = 0 か？
		incf	hex8,F		;No
		decfsz	gen_cnt,F
		goto	hd8c01

		decf	FSR,F		;間接アドレスの更新
		movf	sftwk,W
		movwf	INDF		;余り
		movlw	h'30'
		addwf	INDF,F		;文字コードに変換
		return

		; RAM レジスタファイルデータを LCD へ指定文字数のデータを出力

lcd_disp_m
		movlw	3
		movwf	gen_cnt		;指定文字数

		movlw	buff - 1	;変換文字列の格納エリア
		movwf	FSR		;間接アドレスの設定

lcddp01		incf	FSR,F		;間接アドレスの更新
		movf	INDF,W		;読み出し
		call	Lcd_Data	;LCD へデータを１文字出力
		decfsz	gen_cnt,F
		goto	lcddp01

		return

		; EEPROM へ Beat 値の書き込み
eep_beat
		bsf	STATUS,RP0	;バンク1
		movlw	eep_size
		movwf	EEADR		;EEPROM の記録アドレスを設定
		bsf	EECON1,RD	;読み出し制御 = ON
		movf	EEDATA,W	;データの読み出し (e_beat)
		bcf	STATUS,RP0	;バンク0
		xorwf	beat,W
		btfsc	STATUS,Z	;e_beat = beat か？
		return			;Yes, 等しい場合は書き込みをしない

		bsf	STATUS,RP0	;バンク1
		movlw	2
		addwf	EEADR,F		;e_beat + 2
		bsf	EECON1,RD	;読み出し制御 = ON
		incf	EEDATA,F	;beat 書き込み回数の更新と
		call	eepwr_01	;記録

		bsf	STATUS,RP0	;バンク1
		movlw	2
		subwf	EEADR,F		;e_beat - 2
		bcf	STATUS,RP0	;バンク0

		movf	beat,W		;
		goto	eep_write

		; EEPROM へ Tempo 値の書き込み
eep_tempo
		bsf	STATUS,RP0	;バンク1
		movlw	eep_size + 1
		movwf	EEADR		;EEPROM の記録アドレスを設定
		bsf	EECON1,RD	;読み出し制御 = ON
		movf	EEDATA,W	;データの読み出し (e_tempo)
		bcf	STATUS,RP0	;バンク0
		xorwf	tempo,W
		btfsc	STATUS,Z	;e_tempo = tempo か？
		return			;Yes, 等しい場合は書き込みをしない

		bsf	STATUS,RP0	;バンク1
		movlw	2
		addwf	EEADR,F		;e_tempo + 2
		bsf	EECON1,RD	;読み出し制御 = ON
		incf	EEDATA,F	;tempo 書き込み回数の更新と
		call	eepwr_01	;記録

		bsf	STATUS,RP0	;バンク1
		movlw	2
		subwf	EEADR,F		;e_tempo - 2
		bcf	STATUS,RP0	;バンク0

		movf	tempo,W		;
eep_write
		bsf	STATUS,RP0	;バンク1
		movwf	EEDATA		;書き込みデータを設定

eepwr_01	bsf	EECON1,WREN	;書き込みイネーブル = ON
		movlw	h'55'
		movwf	EECON2		;h'55' を EECON2 に書き込む
		movlw	h'aa'
		movwf	EECON2		;h'aa' を EECON2 に書き込む
		bsf	EECON1,WR	;書き込み開始
		btfsc	EECON1,WR	;書き込み 終了か?
		goto	$-1		;No

		bcf	STATUS,RP0	;バンク0
		return

;==========================================================================
;		サウンド・出力ルーチン
;==========================================================================

		; "ドミソド" ブザー音出力
dl_mi_so_dh
		Sound_Play_M	f_doh, dsm_tim
		call	wait_50ms

		Sound_Play_M	f_mih, dsm_tim
		call	wait_50ms

		Sound_Play_M	f_soh, dsm_tim
		call	wait_50ms

		Sound_Play_M	f_do2, (dsm_tim * 2)
		return

		; "ドソミド" ブザー音出力
dh_so_mi_dl
		Sound_Play_M	f_do2, dsm_tim
		call	wait_50ms

		Sound_Play_M	f_soh, dsm_tim
		call	wait_50ms

		Sound_Play_M	f_mih, dsm_tim
		call	wait_50ms

		Sound_Play_M	f_doh, (dsm_tim * 2)
		return

		; "ピッ" ブザー音出力
buzzer_pi
		Sound_Play_M	f_soh, 70
		return

		; "ポッ" ブザー音出力
buzzer_po
		Sound_Play_M	f_do2, 70
		return

		; "ピポッ" ブザー音出力
buzzer_pipo
		call	buzzer_pi	;"ピッ" ブザー音出力
		movlw	30
		call	wait_xxms
		call	buzzer_po	;"ポッ" ブザー音出力
		return

;==========================================================================
;		ウェイト・ルーチン (8 MHz)
;==========================================================================

		; 100μ秒 ウェイトルーチン
wait_100us
		movlw	39		;1
		movwf	wt1cnt		;1
wait01		goto	$+1		;2
		decfsz	wt1cnt,F	;1,2
		goto	wait01		;2,0	1+1+(2+1+2)*38+(2+2+0)=196
;		goto	$+1		;2
		return			;2	196+2=198
					;       198/8MHz*4 = 99 μS = 100 μS = 0.1 mS

		; 1m 秒 ウェイトルーチン
wait_1ms
		movlw	10		;1
		movwf	wt2cnt		;1
wait02		call	wait_100us	;2	2+198=200
		decfsz	wt2cnt,F	;1,2
		goto	wait02		;2,0	1+1+(200+1+2)*9+(200+2+0)=2031
		return			;2	2031+2=2033
					;       2033/8MHz*4 = 1.0165 mS = 1 mS

		; 50m 秒 ウェイトルーチン
wait_50ms
		movlw	50
		goto	wait_xxms

		; 100m 秒 ウェイトルーチン
wait_100ms
		movlw	100

		; xx m 秒 ウェイトルーチン
wait_xxms
		movwf	wt3cnt		;1
wait03		call	wait_1ms	;2	2+2033=2035
		decfsz	wt3cnt,F	;1,2
		goto	wait03		;2,0	1+(2035+1+2)*(xx-1)+(2035+2+0)=2038*(xx)
		return			;2	2038*(xx)+2
					;

		; 500m 秒 ウェイトルーチン
wait_500ms
		movlw	5
		goto	wait04

		; 1 秒 ウェイトルーチン
wait_1sec
		movlw	10
wait04		movwf	wt4cnt

wait05		call	wait_100ms
		decfsz	wt4cnt,F
		goto	wait05
		return

;==========================================================================
;		割り込み処理
;==========================================================================

interrupt
		; レジスタの保存

		movwf	w_save		;W レジスタの保存
		swapf	STATUS,W
		clrf	STATUS
		movwf	s_save		;STATUS レジスタの保存
		movf	PCLATH,W
		movwf	p_save		;PCLATH レジスタの保存
		clrf	PCLATH
		movf	FSR,W
		movwf	f_save		;FSR レジスタの保存

		bcf	STATUS,RP0	;バンク 0
		bcf	STATUS,RP1

		btfss	PIR1,CCP1IF	;コンペア（比較）モード: TMR1 レジスタの比較が一致 か？
;		goto	init		;No. Reset(Illegal interrupt)
		goto	int02

		; CCP1 (コンペア) 割り込み処理 ... (10mS 毎に割り込み)

		bcf	PIR1,CCP1IF	;CCP1 (コンペア) 割り込み解除

		movf	int_cnt,F	;CCP1割り込みカウンタ
		btfss	STATUS,Z	;int_cnt = 0 か？
		decf	int_cnt,F	;No, 更新

		movf	up_beat,F	;EEPROM(beat) 更新カウンタ
		btfss	STATUS,Z	;up_beat = 0 か？
		decf	up_beat,F	;No

		movf	up_tempo,F	;EEPROM(tempo) 更新カウンタ
		btfss	STATUS,Z	;up_tempo = 0 か？
		decf	up_tempo,F	;No

		movf	sw_cntn,F	;連続誤動作防止カウンタ
		btfsc	STATUS,Z	;sw_cntn = 0 か？
		goto	int01		;Yes

		decf	sw_cntn,F	;No
		goto	int02

int01		movf	SW_LED,W	;一度に各スイッチ入力を読み込み
		andlw	b'00111100'	;スイッチ入力だけの
		xorlw	b'00111100'	;反転値を取り出して
		btfsc	STATUS,Z	;すべてのスイッチ = OFF か？
		goto	int02		;Yes

		movwf	sw_flag		;保存する

		movlw	25		;連続誤動作防止時間 = 10 x 25 = 250 mS
		movwf	sw_cntn		;を設定

		; 割り込み処理の終了

int02		movf	f_save,W
		movwf	FSR		;FSR レジスタの復帰
		movf	p_save,W
		movwf	PCLATH		;PCLATH レジスタの復帰
		swapf	s_save,W
		movwf	STATUS		;STATUS レジスタの復帰
		swapf	w_save,F
		swapf	w_save,W	;W レジスタの復帰

		retfie			;割り込みからの復帰

;==========================================================================
;		EEPROM エリア
;==========================================================================

		org	h'2100'

eep_st		de	"Copyright (C) 2011 by M.Yamamoto"

		de	h'ff'

eep_size	equ	$ - eep_st	;EEPROM データサイズ

e_beat		de	4		;Beat
e_tempo		de	150		;Tempo

;==========================================================================

		end			;プログラムの終了
