;***** VoiceClockII.asm ***************************************************
;
;	音声（お喋り）時計II（音声告知クロック）
;
;	origin:  http://www.zea.jp/audio/vck/vck_01.htm (vck_16f877a.asm)
;		電子マスカット:Vクロック ver.1.0 いしづかやすゆき:2008.03.29
;
;     ( coding:  Ver. 0.00   2008/05/12  by M.Yamamoto )
;	coding:  Ver. 1.00   2008/05/22  初期バージョン
;	update:  Ver. 1.01   2019/12/28  見直し
;	update:  Ver. 1.10   2020/01/02  PIC16F873A に変更（bug で苦戦）
;	update:  Ver. 1.11   2020/01/02  リストの整理
;	update:  Ver. 1.12   2020/01/03  UP/DOWN スイッチ機能の追加
;	update:  Ver. 1.13   2020/01/03  コメント<機能概要>の追加
;	update:  Ver. 1.14   2020/01/06  サブルーチン数の大幅な削減→使用プログラムメモリの削減
;	bugfix:  Ver. x.xx
;
;***************************************************** PicAsm 0217-2 ******

;	Device: PIC16F873A	Clock: 20MHz (セラロック)

#ifdef comment

 <機能概要>

・まず初めに、本機に電池をセットすると、12 時 00 分 00 秒から時刻のカウントが始まる。しかし、本機にはこれらの数字を表示するための７
　セグＬＥＤを代表とするような視覚表示器がないため、その時刻を目で確認することはできない。

・本機がカウント中の現在時刻を知る唯一の方法は、音声告知（VOICE）スイッチを押すことで、音声でその時刻を知らせる。電池をセットした直
　後にこの VOICE スイッチを押すと、「PiPi ... 12 時です」とスピーカーが喋る。このように、「秒」の音声表示は一切なく、また、毎時 00 
　分のときには「分」の音声表示も省略がされる。

・このスピーカーで喋るための音声データの各フレーズは、外付けの I2C-EEPROM に格納されており、ＰＩＣと I2C 通信を行いながらそのときに
　必要となるフレーズが、ＰＩＣ内に読み出される。これらのフレーズは、下のリスト中にある「フレーズサブルーチン」に見られるように、全部
　で 32 個が用意されている。

・これらのフレーズは、初めに電池をセットするときに 音声告知（VOICE）スイッチを押しながらセットをすると、テストモードとなってすべての
　フレーズを発声した後、通常動作となる。

・上述のように、電池をセットすると 12 時 00 分 00 秒から時刻のカウントが始まるが、当然ながらリアルな現在時刻とは異なっているため、こ
　の「時」「分」「秒」の時刻を変更することが必要となる。

・そのために、本機では「時」調整（H ADJ）スイッチ、「分」調整（M ADJ）スイッチと、他に変更操作が効率よく行えるように UP/DOWN スイッ
　チの計３つが用意されている。

・「時」調整（H ADJ）スイッチは、押すごとに「時」が ＋１され、1 → 2 → 3 → 4 → 5 → 6 → 7 → 8 → 9 → 10 → 11 → 12 → 1 → 
　・・・ の順に変更がされて、スピーカーが喋りながらその変更の様子を知らせる。

・このとき、UP/DOWN スイッチを押しながら「時」調整（H ADJ）スイッチを押すと、押すごとに「時」が－１され、12 → 11 → 10 → 9 → 8 → 
　7 → 6 → 5 → 4 → 3 → 2 → 1 → 12 ・・・ の順に変更がされ。同様にスピーカーが喋りながらその変更の様子を知らせる。

・これらのときに、「時」調整（H ADJ）スイッチを離すことなく連続で押していると、押している間中は「時」が ＋１、または－１を繰り返すが、
　後述するような早送り機能は持たせていない。

・一方、「分」調整（M ADJ）スイッチは、押すごとに「分」が ＋１され、1 → 2 → 3 → 4 → 5 → ・・・ → 55 → 56 → 57 → 58 → 59 →
　（無音）→ 1 → 2 → 3 → ・・・ の順に変更がされて、スピーカーが喋りながらその変更の様子を知らせる。

・このとき、UP/DOWN スイッチを押しながら「分」調整（M ADJ）スイッチを押すと、押すごとに「分」が －１され、59 → 58 → 57 → 56 → 55 
　→ ・・・→ 5 → 4 → 3 → 2 → 1 →（無音）→ 59 → 58 → 57 → ・・・ の順に変更がされ、同様にスピーカーが喋りながらその変更の
　様子を知らせる。

・これらのときに、「分」調整（M ADJ）スイッチを離すことなく連続で押していると、初めの５回までは ＋１、または －１を繰り返すが、なおも
　連続で押していると、その後は ＋5、または －5 を繰り返す早送り動作をするようになり、押している間中はそれらが続けられる。

・なお、この「分」調整（M ADJ）スイッチによって「分」が変更されるときには、「分」が変更がされると同時に、本機内部の「秒」カウンタは
　ゼロクリアされる。

・このように、音声告知（VOICE）スイッチ を押したとき、「時」調整（H ADJ）スイッチ、「分」調整（M ADJ）スイッチの操作で時刻を変更した
　ときには、スピーカーでその内容を喋ってユーザに知らせるが、そのほかにも毎時と毎時 30 分になると自動でその時刻を告げる。

・毎時のときには「PiPi ... ○○時です」と喋り、毎時 30 分では「Pi ... ○○時 30 分です」と喋べってその時刻を知らせる。

・本機（PIC16F873A 版）では、元々デバッグ用に使用をしたＬＥＤ（赤）を、デバッグ終了後に、４秒間隔で瞬時点灯（約 10 ミリ秒間）をさせて、
　時間の流れが実感できるようにした。

#endif

;==========================================================================

;	PIC16F873A のピンと用途

;	 (1) MCLR マスター・クリア入力		(28) RB7  入力: 音声告知（VOICE）スイッチ
;	 (2) RA0  出力: 電力増幅IC 電源制御	(27) RB6  入力:「時」調整（H ADJ）スイッチ
;	 (3) RA1  出力: I2C-CLOCK 端子　	(26) RB5  入力:「分」調整（M ADJ）スイッチ
;	 (4) RA2  出力: I2C-DATA 端子		(25) RB4  出力: R-2R ラダー 4
;	 (5) RA3  入力: UP/DOWN スイッチ	(24) RB3  出力: R-2R ラダー 3
;	 (6) RA4  出力: 予備			(23) RB2  出力: R-2R ラダー 2
;	 (7) RA5  出力: 予備			(22) RB1  出力: R-2R ラダー 1
;　	 (8) VSS  電源 GND			(21) RB0  出力: R-2R ラダー 0
;	 (9) OSC1 オシレータ水晶入力		(20) VDD  電源 +5V DC
;	(10) OSC2 オシレータ水晶入力		(19) VSS  電源 GND
;	(11) RC0  タイマー１用オシレータ入力	(18) RC7  出力: R-2R ラダー 7
;	(12) RC1  タイマー１用オシレータ入力	(17) RC6  出力: R-2R ラダー 6
;	(13) RC2  出力: 予備			(16) RC5  出力: R-2R ラダー 5
;	(14) RC3  出力: 予備			(15) RC4  出力: 予備

;==========================================================================

	list		p=pic16f873a	;デバイス情報
	include		p16f873a.inc	;インクルードファイル

	errorlevel	-302		;suppress message 302 from list file
	errorlevel	-305		;suppress message 305 from list file

	__config _CP_OFF & _DEBUG_OFF & _WRT_OFF & _CPD_OFF & _LVP_OFF & _BODEN_OFF & _PWRTE_ON & _WDT_OFF & _HS_OSC

;==========================================================================
;		定数の定義とレジスタ割付け
;==========================================================================

		radix	dec

sp_p0_0		equ	h'00'		;「音声データ開始最上位P0ビット = 0
sp_p0_1		equ	h'02'		;「音声データ開始最上位P0ビット = 1

sp_12j_hl	equ	h'0000'		;「12時」音声データ開始上位下位アドレス
sp_01j_hl	equ	h'11D0'		;「1時」 音声データ開始上位下位アドレス
sp_02j_hl	equ	h'1FF0'		;「2時」 音声データ開始上位下位アドレス
sp_03j_hl	equ	h'2970'		;「3時」 音声データ開始上位下位アドレス
sp_04j_hl	equ	h'37F0'		;「4時」 音声データ開始上位下位アドレス
sp_05j_hl	equ	h'42D0'		;「5時」 音声データ開始上位下位アドレス
sp_06j_hl	equ	h'4D10'		;「6時」 音声データ開始上位下位アドレス
sp_07j_hl	equ	h'5BA0'		;「7時」 音声データ開始上位下位アドレス
sp_08j_hl	equ	h'6AD0'		;「8時」 音声データ開始上位下位アドレス
sp_09j_hl	equ	h'7A60'		;「9時」 音声データ開始上位下位アドレス
sp_10j_hl	equ	h'8340'		;「10時」音声データ開始上位下位アドレス
sp_11j_hl	equ	h'8F00'		;「11時」音声データ開始上位下位アドレス
sp_10f_hl	equ	h'A510'		;「10分」音声データ開始上位下位アドレス
sp_20f_hl	equ	h'B5A0'		;「20分」音声データ開始上位下位アドレス
sp_30f_hl	equ	h'C940'		;「30分」音声データ開始上位下位アドレス
sp_40f_hl	equ	h'E2D0'		;「40分」音声データ開始上位下位アドレス

sp_50f_hl	equ	h'0000'		;「50分」音声データ開始上位下位アドレス
sp_10t_hl	equ	h'11B0'		;「10…」音声データ開始上位下位アドレス
sp_20t_hl	equ	h'18F0'		;「20…」音声データ開始上位下位アドレス
sp_30t_hl	equ	h'2570'		;「30…」音声データ開始上位下位アドレス
sp_40t_hl	equ	h'3490'		;「40…」音声データ開始上位下位アドレス
sp_50t_hl	equ	h'4420'		;「50…」音声データ開始上位下位アドレス
sp_01f_hl	equ	h'4F90'		;「01分」音声データ開始上位下位アドレス
sp_02f_hl	equ	h'60D0'		;「02分」音声データ開始上位下位アドレス
sp_03f_hl	equ	h'6D20'		;「03分」音声データ開始上位下位アドレス
sp_04f_hl	equ	h'8190'		;「04分」音声データ開始上位下位アドレス
sp_05f_hl	equ	h'9080'		;「05分」音声データ開始上位下位アドレス
sp_06f_hl	equ	h'A050'		;「06分」音声データ開始上位下位アドレス
sp_07f_hl	equ	h'B350'		;「07分」音声データ開始上位下位アドレス
sp_08f_hl	equ	h'C130'		;「08分」音声データ開始上位下位アドレス
sp_09f_hl	equ	h'D100'		;「09分」音声データ開始上位下位アドレス
sp_des_hl	equ	h'DF70'		;「です」音声データ開始上位下位アドレス

auto00		equ	0		;stat_byte 00分オートスピーチフラグ
auto30		equ	1		;stat_byte 30分オートスピーチフラグ
timset		equ	2		;stat_byte 時刻調整中フラグ
swint		equ	3		;stat_byte RB ポート変化割り込みからの証拠フラグ

amp_cntl	equ	0		;電力増幅IC 制御
voice_sw	equ	7		;音声告知スイッチ
h_adj_sw	equ	6		;時刻調整「時」スイッチ
m_adj_sw	equ	5		;時刻調整「分」スイッチ
up_dn_sw	equ	3		;UP/DOWN スイッチ

scl		equ	1		;I2C-CLOCK 端子
sda		equ	2		;I2C-DATA 端子
do		equ	0		;I2C-BUFFER 内出力ビット
di		equ	1		;I2C-BUFFER 内入力ビット
ack_bit		equ	2		;I2C-ACK ビット

		cblock  h'20'
tim_cnt1				;遅延タイマー用レジスタ
tim_cnt2				;遅延タイマー用レジスタ
beep_cnt				;BEEP 振動回数カウンタ
stat_byte				;状態バイト
s4_cnt					;4秒カウンタ
ml_cnt					;分カウンタ
mh_cnt					;十分カウンタ
ho_cnt					;時カウンタ
fast_cnt				;分早送りカウンタ
addr_high				;フレーズアドレステーブルの該当フレーズのテーブル high アドレス
addr_low				;フレーズアドレステーブルの該当フレーズのテーブル low アドレス
wk_cnt					;ワークレジスタ
lpcnt					;ループカウンタ

cl_da_cnt				;AD出力を00Hにするまでの割り込み回数カウンタ（ポップノイズ防止）

da_data_a				;
da_data_b				;

rom_chip				;I2C-EEPROM デバイスアドレス			;┐
rom_add_h				;I2C-EEPROM データアクセスアドレス上位レジスタ	;│順序を変更しないこと
rom_add_l				;I2C-EEPROM データアクセスアドレス下位レジスタ	;┘
buffer					;I2C-バッファ
data_in					;I2C-読み出しデータ
data_out				;I2C-送り出しデータ
bit_cnt					;I2C-1バイト8ビットのカウンタレジスタ
		endc

		cblock	h'70'
w_save					;W レジスタの退避用
s_save					;STATUS レジスタの退避用
		endc

;==========================================================================
;		プログラム開始
;==========================================================================

		org	0		;リセットベクタ
		goto	init
		org	4		;割り込みベクタ
		goto	interrupt

;==========================================================================
;		初期化処理
;==========================================================================

init
		bcf	STATUS,RP0	;バンク0
		clrf	ADCON0		;ADCON0 の設定, A/D 未使用
		bsf	STATUS,RP0	;バンク1
		movlw	h'06'		;ポートA, E を ディジタルモード
		movwf	ADCON1		;ADCON1 の設定
		movlw	b'00001000'	;RA3: 入力, RA5-4,2-0: 出力
		movwf	TRISA		;ポートA　設定
		movlw	b'11100000'	;RB7-5: 入力, RB4-0: 出力
		movwf	TRISB		;ポートB　設定
		clrf	TRISC		;ポートC　全て出力設定
		clrf	OPTION_REG	;プルアップ抵抗有効, INT割込み立下りエッジ
		bsf	PIE1,TMR1IE	;タイマー1 割込み許可
		bcf	STATUS,RP0	;バンク0

		movlw	b'00011111'	;bit5-4=01: プリスケーラ1/2
					;bit3=1:    発振器を使用
					;bit2=1:    外部入力クロックに同期させない
					;bit1=1:    外部入力クロックを使う
					;bit0=1:    タイマー1 動作有効 
		movwf	T1CON		;T1CON (タイマー１制御用レジスタ）の設定

		movlw	b'01000000'	;bit7=0: GIE  全割込み禁止
					;bit6=1: PEIE 周辺割込み許可
					;bit4=0: INTE 外部割り込み(RB0/INT)割込み禁止
		movwf	INTCON		;INTCON の設定

		clrf	s4_cnt		;4秒カウンタの初期設定
		clrf	ml_cnt		;分カウンタの初期設定
		clrf	mh_cnt		;十分カウンタの初期設定
		clrf	ho_cnt		;時カウンタの初期設定
		clrf	stat_byte	;状態バイトの初期設定
		clrf	TMR1L
		clrf	TMR1H
		bsf	PORTA,sda	;SDA="H"
		bsf	PORTA,scl	;SCL="H"
		bsf	PORTA,amp_cntl	;電力増幅IC 電源OFF

		clrf	PORTB		;ラダー抵抗ポート下5ビットをクリア
		clrf	PORTC		;ラダー抵抗ポート上3ビットをクリア

		call	rom_test	;全てのフレーズを喋るかテスト

		bsf	INTCON,GIE	;GIE 全割り込み許可

;==========================================================================
;（注意）本プログラムでは、ポート RC2,RC3,RC4 を空きポートとしたときの条件
;　　　　で、ラダー抵抗ポートの上位3ビットをプログラミングをしている。
;　　　　将来的に、これらの空きポートを出力ポートとして何かに使用する場合に
;　　　　は、現状ではラダー抵抗ポートの操作が影響を及ぼしてしまう。
;　　　　したがって、そのような場合には影響が及ばないように、プログラミング
;　　　　の変更が必要となる。
;==========================================================================
;		メイン・ルーチン
;==========================================================================

main
		btfss	stat_byte,swint	;RB ポート変化割り込みか?
		goto	main01		;No

		btfss	PORTB,voice_sw	;音声告知スイッチが押されているか?
		goto	main02		;Yes

		btfss	PORTB,h_adj_sw	;時刻調整「時」スイッチが押されているか?
		goto	main03		;Yes

		clrf	fast_cnt	;分早送りカウンタの初期設定
		btfss	PORTB,m_adj_sw	;時刻調整「分」スイッチが押されているか?
		goto	main04		;Yes
		; ???

main01		movlw	(1 << auto00) + (1 << auto30)
		andwf	stat_byte,W
		btfss	STATUS,Z	;毎時 or 毎30分スピーチか?
		goto	main06		;Yes
		goto	main09		;No

main02		call	tim_sw		;チャタリング監視時間
		btfss	PORTB,voice_sw	;音声告知スイッチが確実に押されているか?
		goto	main06		;Yes
		goto	main09		;No

;--------------
main03		call	tim_sw		;チャタリング監視時間
		btfsc	PORTB,h_adj_sw	;時刻調整「時」スイッチが確実に押されているか?
		goto	main09		;No

		; 時刻調整「時」スイッチが押された
		call	audio_set	;オーディオ回路の設定
		call	beep

		btfss	PORTA,up_dn_sw	;UP/DOWN スイッチ = ON か?
		goto	main03a		;Yes

		; 時カウンタのインクリメント
		incf	ho_cnt,F	;時カウンタ + 1
		movlw	12
		subwf	ho_cnt,W
		btfsc	STATUS,Z	;時カウンタ = 12 か?
		clrf	ho_cnt		;Yes. 12回のカウントでレジスタをクリア
		goto	main03b

		; 時カウンタのデクリメント
main03a		decf	ho_cnt,F	;時カウンタ - 1
		comf	ho_cnt,W
		btfss	STATUS,Z	;時カウンタ < 0 か?
		goto	main03b		;No

		movlw	11
		movwf	ho_cnt		;時カウンタ = 11 をセット

main03b		bsf	stat_byte,timset ;時刻調整中フラグのセット
		bcf	INTCON,GIE	;全割込み禁止
		call	sp_hour		;スピーチ順序ルーチン「時」
		bsf	INTCON,GIE	;全割込み許可
		bcf	stat_byte,timset ;時刻調整中フラグのリセット

		call	tim_sw		;チャタリング監視時間
		btfsc	PORTB,h_adj_sw	;時刻調整「時」スイッチがまだ押されているか?
		goto	main09		;No

		call	tim_200ms
		call	tim_200ms
		goto	main03

;--------------
main04		call	tim_sw		;チャタリング監視時間
		btfsc	PORTB,m_adj_sw	;時刻調整「分」スイッチが確実に押されているか?
		goto	main09		;No

		; 時刻調整「分」スイッチが押された
		call	audio_set	;オーディオ回路の設定
		clrf	TMR1L
		clrf	TMR1H
		clrf	s4_cnt		;「分」調整時のみ秒レベルまでクリア
		call	beep

		btfss	PORTA,up_dn_sw	;UP/DOWN スイッチ = ON か?
		goto	main04c		;Yes

		movlw	5
		subwf	fast_cnt,W
		btfsc	STATUS,C	;分早送りカウンタ >= 5 か?
		goto	main04b		;Yes

		; 分,十分カウンタのインクリメント
		incf	ml_cnt,F	;分カウンタ + 1
		movlw	10
		subwf	ml_cnt,W
		btfss	STATUS,Z	;分カウンタ = 10 か?
		goto	main05		;No

		clrf	ml_cnt		;Yes. 10回のカウントでレジスタクリア

main04a		incf	mh_cnt,F	;十分カウンタ + 1
		movlw	6
		subwf	mh_cnt,W
		btfsc	STATUS,Z	;十分カウンタ = 6 か?
		clrf	mh_cnt		;Yes. 6回のカウントでレジスタクリア
		goto	main05

		; 分,十分カウンタの早送り（増加）
main04b		movlw	5
		addwf	ml_cnt,F	;分カウンタ + 5
		movlw	10
		subwf	ml_cnt,W
		btfss	STATUS,C	;分カウンタ >= 10 か?
		goto	main05		;No

		movwf	ml_cnt		;分カウンタ - 10
		goto	main04a

;-------
main04c		movlw	5
		subwf	fast_cnt,W
		btfsc	STATUS,C	;分早送りカウンタ >= 5 か?
		goto	main04e		;Yes

		; 分,十分カウンタのデクリメント
		decf	ml_cnt,F	;分カウンタ - 1
		comf	ml_cnt,W
		btfss	STATUS,Z	;分カウンタ < 0 か?
		goto	main05		;No

		movlw	9
		movwf	ml_cnt		;分カウンタ = 9 をセット

main04d		decf	mh_cnt,F	;十分カウンタ - 1
		comf	mh_cnt,W
		btfss	STATUS,Z	;十分カウンタ < 0 か?
		goto	main05		;No

		movlw	5
		movwf	mh_cnt		;十分カウンタ = 5 をセット
		goto	main05

		; 分,十分カウンタの早送り（減少）
main04e		movlw	5
		subwf	ml_cnt,F	;分カウンタ - 5
		btfsc	STATUS,C	;分カウンタ >= 0 か?
		goto	main05		;Yes

		movlw	10
		addwf	ml_cnt,F	;分カウンタ + 10
		goto	main04d

main05		bsf	stat_byte,timset ;時刻調整中フラグのセット
		bcf	INTCON,GIE	;全割込み禁止
		call	sp_minute	;スピーチ順序ルーチン「分」
		bsf	INTCON,GIE	;全割込み許可
		bcf	stat_byte,timset ;時刻調整中フラグのリセット

		call	tim_sw		;チャタリング監視時間
		btfsc	PORTB,m_adj_sw	;時刻調整「分」スイッチがまだ押されているか?
		goto	main09		;No

		incf	fast_cnt,F	;分早送りカウンタ + 1
		call	tim_200ms
		goto	main04

;--------------	; 毎時スピーチ ／ 毎30分スピーチ ／ 音声告知スイッチ
main06		call	audio_set	;オーディオ回路の設定
		call	beep		;ビープ音１回目

		btfsc	stat_byte,auto30 ;毎30分スピーチか?
		goto	main08		;Yes

		call	tim_100ms
		call	beep		;ビープ音２回目

main08		call	tim_200ms
		call	tim_200ms
		call	tim_200ms
		bcf	INTCON,GIE	;全割込み禁止
		call	sp_hour		;スピーチ順序ルーチン「時」
		call	sp_minute	;スピーチ順序ルーチン「分」
		call	sp_des		;フレーズ「です」読み出し
		bsf	INTCON,GIE	;全割込み許可

;--------------	; オーディオ電源 OFF
main09		bsf	PORTA,amp_cntl	;電力増幅IC 電源OFF
		clrf	stat_byte	;状態バイトのクリア
		movf	cl_da_cnt,F
		btfsc	STATUS,Z	;割り込み回数カウンタ = 0 か?
		goto	main10		;Yes

		decf	cl_da_cnt,F	;割り込み回数カウンタ - 1
		btfss	STATUS,Z	;割り込み回数カウンタ = 0 か?
		goto	main10		;No

		clrf	PORTB		;ラダー抵抗ポート下5ビットをクリア
		clrf	PORTC		;ラダー抵抗ポート上3ビットをクリア

main10		bsf	INTCON,RBIE	;RBポート変化割り込みを許可
		sleep			;普段はここ
		;
		;
		btfss	stat_byte,swint	;RB ポート変化割り込みか?
		goto	main11		;No

		movf	PORTB,W
		andlw	h'e0'		;スイッチ入力を
		xorlw	h'e0'		;反転
		btfss	STATUS,Z	;スイッチ操作 = あり か？
		goto	main11		;Yes

		bcf	stat_byte,swint	;間違いのため RBポート変化割り込みの記憶をクリア
		goto	main10		;再度 sleep

main11		bsf	PORTC,2		;
		call	tim_10ms	;デバッグ用
		bcf	PORTC,2		;
		goto	main

;--------------------------------------------------------------------------

;		オーディオ回路の設定

audio_set
		movlw	h'80'		;ノイズ防止のため中立の h'80'をラダー抵抗ポートにスタンバイ
		movwf	PORTC		;ラダー抵抗ポート上3ビット
		clrf	PORTB		;ラダー抵抗ポート下5ビット

		bcf	PORTA,amp_cntl	;電力増幅IC 電源ON
		movlw	4
		movwf	cl_da_cnt	;音声告知終了後に3回目の割込み突入でラダー抵抗ポートをクリア
		return

;--------------------------------------------------------------------------

;		BEEP 音生成サブルーチン

beep
		movlw	h'60'		;振動回数
		movwf	beep_cnt	;BEEP 振動回数カウンタを設定

beep01		bsf	PORTC,5		;RC5 = 1
;		call	tim_100us
;		call	tim_100us
		call	tim_100us
		call	tim_100us
		call	tim_100us
		bcf	PORTC,5		;RC5 = 0
;		call	tim_100us
;		call	tim_100us
		call	tim_100us
		call	tim_100us
		call	tim_100us
		decfsz	beep_cnt	;BEEP 振動回数カウンタ - 1 = 0 か？
		goto	beep01		;No

		return

;==========================================================================
;		スピーチ順序ルーチン「時」「分」
;==========================================================================

;		スピーチ順序ルーチン「時」

sp_hour
		movlw	high hour_addr_tbl
		movwf	addr_high	;時フレーズアドレステーブルの先頭 high アドレス
		movlw	low hour_addr_tbl
		movwf	addr_low	;時フレーズアドレステーブルの先頭 low アドレス
		movf	ho_cnt,W
		call	phrase_read	;フレーズ読み出し
		return

;		スピーチ順序ルーチン「分」

sp_minute
		movf	ml_cnt,W
		btfss	STATUS,Z	;分カウンタ = 0 か?
		goto	spm02		;No

		; 下位が「0分」のとき
		movf	mh_cnt,W
		btfss	STATUS,Z	;十分カウンタ = 0 か?
		goto	spm01		;No

		call	sp_00f		;フレーズ「無音」
		goto	spm04

spm01		movlw	high minute_x0_addr_tbl
		movwf	addr_high	;分_x0_フレーズアドレステーブルの先頭 high アドレス
		movlw	low minute_x0_addr_tbl
		movwf	addr_low	;分_x0_フレーズアドレステーブルの先頭 low アドレス
		decf	mh_cnt,W
		call	phrase_read	;フレーズ読み出し
		goto	spm04

		; 下位が「0分」以外の上位分
spm02		movf	mh_cnt,W
		btfsc	STATUS,Z	;十分カウンタ = 0 か?
		goto	spm03		;Yes

		movlw	high minute_xx_addr_tbl
		movwf	addr_high	;分_xx_フレーズアドレステーブルの先頭 high アドレス
		movlw	low minute_xx_addr_tbl
		movwf	addr_low	;分_xx_フレーズアドレステーブルの先頭 low アドレス
		decf	mh_cnt,W
		call	phrase_read	;フレーズ読み出し

		; 下位が「0分」以外の下位分
spm03		movlw	high minute_0x_addr_tbl
		movwf	addr_high	;分_0x_フレーズアドレステーブルの先頭 high アドレス
		movlw	low minute_0x_addr_tbl
		movwf	addr_low	;分_0x_フレーズアドレステーブルの先頭 low アドレス
		decf	ml_cnt,W
		call	phrase_read	;フレーズ読み出し

spm04		return

;==========================================================================
;		フレーズ読み出しサブルーチン
;==========================================================================
; 呼び出されると指定のアドレスから ROMを読み出しラダー抵抗ポートに出力する

phrase_read
		movwf	wk_cnt		;ワークレジスタにコピー
		addwf	wk_cnt,F	;x 2
		addwf	wk_cnt,W	;x 3
		addwf	addr_low,F	;フレーズアドレステーブルの該当フレーズのテーブル low アドレス
		btfsc	STATUS,C	;オーバーフローした場合は
		incf	addr_high,F	;フレーズアドレステーブルの該当フレーズのテーブル high アドレスの更新

		movlw	rom_chip	;rom_chip レジスタのアドレス
		movwf	FSR
		movlw	3		;
		movwf	lpcnt		;ループカウンタを設定

phrd01		call	addr_read	;フレーズアドレスデータの読み出し
		movwf	INDF
		incf	FSR,F		;間接アドレス +1 更新
		incf	addr_low,F	;該当フレーズのテーブル low アドレスの更新
		btfsc	STATUS,C	;オーバーフローした場合は
		incf	addr_high,F	;該当フレーズのテーブル high アドレスの更新
		decfsz	lpcnt,F		;ループカウンタ - 1 = 0 か？
		goto	phrd01		;No

		call	rom_read	;EEPROM 読み出し
		return

		; フレーズアドレスデータの読み出し
		; 入力:	addr_high: 該当フレーズアドレスの先頭 high アドレス
		;	addr_low:  該当フレーズアドレスの先頭 low アドレス
		; 出力:	W: 該当フレーズの先頭アドレスデータ

addr_read
		movf	addr_high,W	;該当フレーズアドレスの先頭の high アドレス
		movwf	PCLATH
		movf	addr_low,W	;該当フレーズアドレスの先頭の low アドレス
		movwf	PCL

;--------------------------------------------------------------------------

		; フレーズ「です」読み出し
sp_des
		movlw	sp_p0_1	
		movwf	rom_chip
		movlw	high sp_des_hl
		movwf	rom_add_h
		movlw	low sp_des_hl
		movwf	rom_add_l
		call	rom_read	;ROM 読み出し
		return

		; 時刻調整時のみ 0.8秒間無音とする
sp_00f
		btfss	stat_byte,timset ;時刻調整中か?
		goto	sp00		;No

		call	tim_200ms
		call	tim_200ms
		call	tim_200ms
		call	tim_200ms

sp00		return

;==========================================================================
;		I2C-EEPROM 音声データ開始アドレステーブル
;==========================================================================

;		時フレーズアドレステーブル

hour_addr_tbl

		retlw	sp_p0_0			; 0:「12時」音声データ開始アドレス
		retlw	high sp_12j_hl
		retlw	low  sp_12j_hl

		retlw	sp_p0_0			; 1:「1時」音声データ開始アドレス
		retlw	high sp_01j_hl
		retlw	low  sp_01j_hl

		retlw	sp_p0_0			; 2:「2時」音声データ開始アドレス
		retlw	high sp_02j_hl
		retlw	low  sp_02j_hl

		retlw	sp_p0_0			; 3:「3時」音声データ開始アドレス
		retlw	high sp_03j_hl
		retlw	low  sp_03j_hl

		retlw	sp_p0_0			; 4:「4時」音声データ開始アドレス
		retlw	high sp_04j_hl
		retlw	low  sp_04j_hl

		retlw	sp_p0_0			; 5:「5時」音声データ開始アドレス
		retlw	high sp_05j_hl
		retlw	low  sp_05j_hl

		retlw	sp_p0_0			; 6:「6時」音声データ開始アドレス
		retlw	high sp_06j_hl
		retlw	low  sp_06j_hl

		retlw	sp_p0_0			; 7:「7時」音声データ開始アドレス
		retlw	high sp_07j_hl
		retlw	low  sp_07j_hl

		retlw	sp_p0_0			; 8:「8時」音声データ開始アドレス
		retlw	high sp_08j_hl
		retlw	low  sp_08j_hl

		retlw	sp_p0_0			; 9:「9時」音声データ開始アドレス
		retlw	high sp_09j_hl
		retlw	low  sp_09j_hl

		retlw	sp_p0_0			;10:「10時」音声データ開始アドレス
		retlw	high sp_10j_hl
		retlw	low  sp_10j_hl

		retlw	sp_p0_0			;11:「11時」音声データ開始アドレス
		retlw	high sp_11j_hl
		retlw	low  sp_11j_hl

;		分_x0_フレーズアドレステーブル

minute_x0_addr_tbl

		retlw	sp_p0_0			;12:「10分」音声データ開始アドレス
		retlw	high sp_10f_hl
		retlw	low  sp_10f_hl

		retlw	sp_p0_0			;13:「20分」音声データ開始アドレス
		retlw	high sp_20f_hl
		retlw	low  sp_20f_hl

		retlw	sp_p0_0			;14:「30分」音声データ開始アドレス
		retlw	high sp_30f_hl
		retlw	low  sp_30f_hl

		retlw	sp_p0_0			;15:「40分」音声データ開始アドレス
		retlw	high sp_40f_hl
		retlw	low  sp_40f_hl

		retlw	sp_p0_1			;16:「50分」音声データ開始アドレス
		retlw	high sp_50f_hl
		retlw	low  sp_50f_hl

;		分_xx_フレーズアドレステーブル

minute_xx_addr_tbl

		retlw	sp_p0_1			;17:「10…」音声データ開始アドレス
		retlw	high sp_10t_hl
		retlw	low  sp_10t_hl

		retlw	sp_p0_1			;18:「20…」音声データ開始アドレス
		retlw	high sp_20t_hl
		retlw	low  sp_20t_hl

		retlw	sp_p0_1			;19:「30…」音声データ開始アドレス
		retlw	high sp_30t_hl
		retlw	low  sp_30t_hl

		retlw	sp_p0_1			;20:「40…」音声データ開始アドレス
		retlw	high sp_40t_hl
		retlw	low  sp_40t_hl

		retlw	sp_p0_1			;21:「50…」音声データ開始アドレス
		retlw	high sp_50t_hl
		retlw	low  sp_50t_hl

;		分_0x_フレーズアドレステーブル

minute_0x_addr_tbl

		retlw	sp_p0_1			;22:「01分」音声データ開始アドレス
		retlw	high sp_01f_hl
		retlw	low  sp_01f_hl

		retlw	sp_p0_1			;23:「02分」音声データ開始アドレス
		retlw	high sp_02f_hl
		retlw	low  sp_02f_hl

		retlw	sp_p0_1			;24:「03分」音声データ開始アドレス
		retlw	high sp_03f_hl
		retlw	low  sp_03f_hl

		retlw	sp_p0_1			;25:「04分」音声データ開始アドレス
		retlw	high sp_04f_hl
		retlw	low  sp_04f_hl

		retlw	sp_p0_1			;26:「05分」音声データ開始アドレス
		retlw	high sp_05f_hl
		retlw	low  sp_05f_hl

		retlw	sp_p0_1			;27:「06分」音声データ開始アドレス
		retlw	high sp_06f_hl
		retlw	low  sp_06f_hl

		retlw	sp_p0_1			;28:「07分」音声データ開始アドレス
		retlw	high sp_07f_hl
		retlw	low  sp_07f_hl

		retlw	sp_p0_1			;29:「08分」音声データ開始アドレス
		retlw	high sp_08f_hl
		retlw	low  sp_08f_hl

		retlw	sp_p0_1			;30:「09分」音声データ開始アドレス
		retlw	high sp_09f_hl
		retlw	low  sp_09f_hl

;		その他フレーズアドレステーブル

		retlw	sp_p0_1			;31:「です」音声データ開始アドレス
		retlw	high sp_des_hl
		retlw	low  sp_des_hl

tbl_end						;アドレステーブルの終了

;==========================================================================
;		スピーチテスト
;==========================================================================

rom_test
		btfsc	PORTB,voice_sw	;音声告知スイッチ = ON か？
		goto	r_test03	;No

r_test01	btfss	PORTB,voice_sw	;音声告知スイッチ = OFF か？
		goto	r_test01	;No

		call	audio_set	;オーディオ回路の設定
		call	tim_200ms
		call	beep
		call	tim_100ms
		call	beep
		call	tim_200ms

		movlw	high hour_addr_tbl
		movwf	addr_high	;時フレーズアドレステーブルの先頭 high アドレス
		movlw	low hour_addr_tbl
		movwf	addr_low	;時フレーズアドレステーブルの先頭 low アドレス

r_test02	clrw
		call	phrase_read	;各フレーズ読み出し

		movlw	low tbl_end
		subwf	addr_low,W
		btfss	STATUS,Z	;low アドレスは等しいか？
		goto	r_test02	;No

		movlw	high tbl_end
		subwf	addr_high,W
		btfss	STATUS,Z	;high アドレスは等しいか？
		goto	r_test02	;No

		call	tim_200ms
		call	tim_200ms
		call	beep
		call	tim_100ms
		call	beep

r_test03	return

;==========================================================================
;		I2C EEPROM 読み出し
;==========================================================================

rom_read
		call	sda_in		;初期設定でSDAを入力モードにしたならば必要ない
		call	start_con	;スタートコンディション
		call	rom_tim		;タイミング調整

		movlw	h'a0'		;コントロールバイト + 書き込みビット
		iorwf	rom_chip,W	;デバイスアドレスを加える(24C1024では最上位アドレスP0<BIT:1>を含む)
		movwf	data_out
		call	byte_out	;1バイト送信
		call	rom_tim		;タイミング調整

		movf	rom_add_h,W	;アドレス上位送信
		movwf	data_out
		call	byte_out	;1バイト送信
		call	rom_tim		;タイミング調整

		movf	rom_add_l,W	;アドレス下位送信
		movwf	data_out
		call	byte_out	;1バイト送信
		call	rom_tim		;タイミング調整

		call	start_con	;スタートコンディション
		call	rom_tim		;タイミング調整

		movlw	h'a1'		;コントロールバイト + 読み出しビット
		iorwf	rom_chip,W	;デバイスアドレスを加える(24C1024では最上位アドレスP0<BIT:1>を含む)
		movwf	data_out
		call	byte_out	;1バイト送信
		call	rom_tim		;タイミング調整

seq_read
		bsf	buffer,ack_bit	;ACKビットを送出して連続読み出し
		call	byte_in		;1バイト受信

		;データ処理
		comf	data_in,W	;h'ff' であるか確認
		btfsc	STATUS,Z
		goto	last_read	;h'ff' ならば読み出し終了

		movlw	h'e0'
		andwf	da_data_a,W
		movwf	PORTC		;ラダー抵抗ポート上3ビット
		movlw	h'1f'
		andwf	da_data_a,W
		movwf	PORTB		;ラダー抵抗ポート下5ビット

		clrf	da_data_b
		movf	data_in,W
		bcf	STATUS,C
		addwf	da_data_a,F
		btfsc	STATUS,C
		bsf	da_data_b,0
		rrf	da_data_a,F
		btfsc	da_data_b,0
		bsf	da_data_a,7
		call	tim_ad		;音声スピード
		movlw	h'e0'
		andwf	da_data_a,W
		movwf	PORTC		;ラダー抵抗ポート上3ビット
		movlw	h'1f'
		andwf	da_data_a,W
		movwf	PORTB		;ラダー抵抗ポート下5ビット

		movf	data_in,W
		movwf	da_data_a	;今回のデータを保存
		goto	seq_read	;シーケンス読み出し継続
		;
last_read
		bcf	buffer,ack_bit	;ACKビットを送出しないで最終読み出し
		call	byte_in		;1バイト受信
		call	rom_tim		;タイミング調整

		call	stop_con	;ストップコンディション
		return

;--------------------------------------------------------------------------

;		I2C EEPROM スタートシーケンス

start_con
		bsf	PORTA,scl	;SCL="H"
		call	rom_tim		;タイミング調整
		call	sda_out		;SDA端子を出力モードにする："H"レベル出力
		bcf	PORTA,sda	;SDA="L"
		call	rom_tim		;タイミング調整
		bcf	PORTA,scl	;SCL="L"
		call	sda_in		;SDA端子を入力モードにする（SDAはプルアップ抵抗により"H"レベル）
		return

;		I2C EEPROM ストップシーケンス

stop_con
		call	sda_out		;SDA端子を出力モードにする："H"レベル出力
		bcf	PORTA,sda	;SDA="L"
		bsf	PORTA,scl	;SCL="H"
		call	rom_tim		;タイミング調整
		call	sda_in		;SDA端子を入力モードにする（SDAはプルアップ抵抗により"H"レベル）
		return
	
;--------------------------------------------------------------------------

;		I2C EEPROM 1バイト送信

byte_out
		movlw	8
		movwf	bit_cnt		;ループカウンタにセット

byteo01		bsf	buffer,do	;    出力ビット do = 1
		btfss	data_out,7	;送り出しデータの 7ビット目 = 1 か？
		bcf	buffer,do	;No. 出力ビット do = 0
		call	bit_out		;1ビット送信
		rlf	data_out,F	;送り出しデータを 1ビット左にシフト
		decfsz	bit_cnt,F	;8ビット送信した か？
		goto	byteo01		;No

		call	bit_in		;1ビット受信
		return

;		I2C EEPROM 1バイト受信

byte_in
		clrf	data_in		;読み出しデータ（の入れ物）を初期設定
		movlw	8
		movwf	bit_cnt		;ループカウンタにセット
		bcf	STATUS,C

bytei01		rlf	data_in,F	;読み出しデータを 1ビット左にシフト
		call	bit_in		;1ビット受信
		btfsc	buffer,di	;入力ビット di = 0 か？
		bsf	data_in,0	;No. 読み出しデータの 0ビット目 = 1
		decfsz	bit_cnt,F	;8ビット受信した か？
		goto	bytei01		;No

		bsf	buffer,do	;    出力ビット do = 1
		btfsc	buffer,ack_bit	;ACKビット = 0 か？
		bcf	buffer,do	;No. 出力ビット do = 0
		call	bit_out		;1ビット送信
		return

;--------------------------------------------------------------------------

;		I2C EEPROM 1ビット送信

bit_out
		bcf	PORTA,scl	;SCL="L"
		btfsc	buffer,do	;出力ビット do = 0 か？
		goto	bito01		;No

		call	sda_out		;SDA 端子を出力設定
		bcf	PORTA,sda	;SDA="L"

bito01		bsf	PORTA,scl	;SCL="H"
		call	rom_tim		;タイミング調整
		bcf	PORTA,scl	;SCL="L"
		call	sda_in		;SDA 端子を入力設定
		return

;		I2C EEPROM 1ビット受信

bit_in
		bcf	PORTA,scl	;SCL="L"
		call	sda_in		;SDA 端子を入力設定
		bsf	buffer,di	;    入力ビット di = 1
		bsf	PORTA,scl 	;SCL="H"
		call	rom_tim		;タイミング調整
		btfss	PORTA,sda	;SDA="H" か？
		bcf	buffer,di	;No. 入力ビット di = 0
		bcf	PORTA,scl	;SCL="L"
		return

;--------------------------------------------------------------------------

;		I2C EEPROM SDA 入力端子設定

sda_in
		bsf	STATUS,RP0	;バンク1
		bsf	TRISA,sda	;SDA 端子を入力設定
		bcf	STATUS,RP0	;バンク0
		return

;		I2C EEPROM SDA 出力端子設定

sda_out
		bsf	STATUS,RP0	;バンク1
		bcf	TRISA,sda	;SDA 端子を出力設定
		bcf	STATUS,RP0	;バンク0
		bsf	PORTA,sda	;SDA="H"
		return

;--------------------------------------------------------------------------

;		I2C EEPROM タイミング調整用ディレイ
;		（配線により読み出し不良となる場合があるのであまり欲張らない）

rom_tim
		goto	$+1		;2サイクル
		goto	$+1		;2サイクル
		goto	$+1		;2サイクル
		goto	$+1		;2サイクル
		goto	$+1		;2サイクル
		goto	$+1		;2サイクル
		goto	$+1		;2サイクル
		goto	$+1		;2サイクル
		goto	$+1		;2サイクル
		goto	$+1		;2サイクル
		goto	$+1		;2サイクル
		goto	$+1		;2サイクル
		goto	$+1		;2サイクル
		goto	$+1		;2サイクル
		goto	$+1		;2サイクル
;		goto	$+1		;2サイクル
;		goto	$+1		;2サイクル
;		goto	$+1		;2サイクル
;		goto	$+1		;2サイクル
;		goto	$+1		;2サイクル
;		goto	$+1		;2サイクル
		return			;2サイクル

;==========================================================================
;		遅延タイマ・ルーチン (20 MHz)
;==========================================================================

		; 100μ秒 ウェイトルーチン

tim_100us
		movlw	166		;1
		movwf	tim_cnt1	;1

t100us01	decfsz	tim_cnt1,F	;1,2
		goto	t100us01	;2,0	1+1+(1+2)*165+(2+0)=499

		return			;2	499+2=501
					;Total	501/20MHz*4 = 0.0001002 S = 100 μS

		; 200m秒, 150m秒, 100m秒, 50m秒 ウェイトルーチン

tim_200ms				;50 mS + 50 mS + 50 mS + 50 mS
		movlw	248
		movwf	tim_cnt2

t200ms01	call	tim_100us
		call	tim_100us
		decfsz	tim_cnt2,F
		goto	t200ms01

tim_150ms				;50 mS + 50 mS + 50 mS
		movlw	248
		movwf	tim_cnt2

t150ms01	call	tim_100us
		call	tim_100us
		decfsz	tim_cnt2,F
		goto	t150ms01

tim_100ms				;50 mS + 50 mS
		movlw	248
		movwf	tim_cnt2

t100ms01	call	tim_100us
		call	tim_100us
		decfsz	tim_cnt2,F
		goto	t100ms01

tim_50ms				;50 mS
		movlw	248		;1
		movwf	tim_cnt2	;1

t50ms01		call	tim_100us	;503	2+501
		call	tim_100us	;503	2+501
		decfsz	tim_cnt2,F	;1,2
		goto	t50ms01		;2,0	1+1+(503+503+1+2)*247+(503+503+2+0)=250233

		return			;2	2+250233=250235
					;Total	250235/20MHz*4 = 0.050047 S = 50 mS

tim_10ms				;10 mS	;デバッグ用
		movlw	48		;1
		movwf	tim_cnt2	;1
		goto	t50ms01

;--------------------------------------------------------------------------

		; 音声スピード

tim_ad
		movlw	h'17'		;h'01'～h'30'程度
		movwf	tim_cnt1

timad01		decfsz	tim_cnt1,F
		goto	timad01

		return

;--------------------------------------------------------------------------

		; チャタリング監視時間 (約20mS)

tim_sw
		movlw	99		;1
		movwf	tim_cnt2	;1

timsw01		call	tim_100us	;503	2+501
		call	tim_100us	;503	2+501
		decfsz	tim_cnt2,F	;1,2
		goto	timsw01		;2,0	1+1+(503+503+1+2)*98+(503+503+2+0)=99892

		return			;2	99892+2=99894
					;Total	99894/20MHz*4 = 0.0199788 S = 20 mS

;==========================================================================
;		割り込み処理
;==========================================================================
; 割込みに入るのは タイマー１オーバーフローと RBポート変化の2通り
; タイマー１割込みは4秒ごとに発生する

interrupt
		movwf	w_save		;W レジスタの保存
		movf	STATUS,W
		movwf	s_save		;STATUS レジスタの保存

		btfsc	PIR1,TMR1IF	;タイマ１オーバフロー か？
		goto	int_tmr1	;Yes

		; RB ポート変化割り込み処理

int_rb		bcf	INTCON,RBIF	;RBポート変化割り込みフラグをクリア
		bcf	INTCON,RBIE	;RBポート変化割り込みを禁止
		bsf	stat_byte,swint	;RBポート変化割り込みであることを記憶
		goto	int_out

		; タイマー１割り込み

int_tmr1	bcf	PIR1,TMR1IF	;タイマ１オーバフローを解除

		incf	s4_cnt,F	;4秒カウンタ + 1
		movlw	15
		subwf	s4_cnt,W
		btfss	STATUS,Z	;4秒カウンタ = 15 か？
		goto	int_out		;No

		clrf	s4_cnt		;Yes. 15回のカウントでレジスタクリア

		incf	ml_cnt,F	;分カウンタ + 1
		movlw	10
		subwf	ml_cnt,W
		btfss	STATUS,Z	;分カウンタ = 10 か?
		goto	int_out		;No

		clrf	ml_cnt		;Yes. 10回のカウントでレジスタクリア

		incf	mh_cnt,F	;十分カウンタ + 1
		movlw	3
		subwf	mh_cnt,W
		btfsc	STATUS,Z	;十分カウンタ = 3 か?
		bsf	stat_byte,auto30 ;Yes. 30分オートスピーチフラグをセット

		movlw	6
		subwf	mh_cnt,W
		btfss	STATUS,Z	;十分カウンタ = 6 か?
		goto	int_out		;No

		clrf	mh_cnt		;Yes. 6回のカウントでレジスタクリア

		incf	ho_cnt,F	;時カウンタ + 1
		bsf	stat_byte,auto00 ;00分オートスピーチフラグをセット
		movlw	12
		subwf	ho_cnt,W
		btfss	STATUS,Z	;時カウンタ = 12 か?
		goto	int_out		;No

		clrf	ho_cnt		;Yes. 12回のカウントでレジスタクリア

int_out		movf	s_save,W
		movwf	STATUS		;STATUS レジスタの復帰
		swapf	w_save,F
		swapf	w_save,W	;W レジスタの復帰

		retfie

;==========================================================================

		end			;プログラムの終了
