;**********************************************************************
; 七段式 LED 測試，包含數字和英文字母,  
; 利用計時中斷作延遲 Use Timer0, Mode 2, 8 bit, 256 maximun，自動載入
; 每個 Machine Cycle=1.08506944 us, use 11.0592 MHz oscilator
; 利用外部中斷 Int0 作 重新開始
; 送出 時分秒等數值到 PC
; 接收 PC 傳來字元並顯示在 七段式 LED
; 每小時校正時間一次
; Daniel Lu  2003'11'04
;**********************************************************************

OFT	EQU	30H		;配置位址空間 30H 給OFT(offset) 儲存數值用
COUNT	EQU	184		;每 184 次=199.64 us, 大約等於 200 us  
US_200	EQU	31H		;每次計時中斷(US_200)累加一次
MS_50	EQU	32H		;每 50 毫秒累加
Hr	EQU	33H		;時
Min	EQU	34H		;分
Sec	EQU	35H		;秒
StrCnt	EQU	37H

	ORG	0000H		;設定起使位址
	LJMP	START		;跳到主程式 Main Loop
	
	ORG	0003H		;外部中斷 INT0
	LJMP	INILED	
	
	ORG	000BH		;Timer0中斷
	LJMP	IRS_Display
	
	ORG	0013H
	RETI
	
	ORG	001BH
	RETI
	
	ORG	0023H
	RETI

	
;-------------------------------------------------------	
START:	MOV	R1,#00H		;先作一小段延遲，讓系統Ready
$:	DJNZ	R1,$		
	MOV	SP,#50H		;指定堆疊起始位址
	MOV	DPTR,#TABLE	;將Table 所在的位址存到 DPTR	
	
	SETB	PT0		;IP.1 中斷優先權設為高
	SETB	ET0		;IE.1 啟動 Timer0 中斷服務
	
	SETB	IT0		;Interrupt Type: 1 = 負緣觸發
	SETB	PX0		;IP.0 中斷優先權設為高
	SETB	EX0		;IE.0 啟動 Timer0 中斷服務	
	
	CALL	SetTimer0	;設定 Timer0 模式	
	CALL	INILED		;呼叫 INILED 副函式，將每個 LED (a,b,c,d,e,f,g) 各跑一次
	
	SETB	EA		;啟動中斷服務
	
LOOP:	JNB	RI,$END		;看是否有接收到訊號，如果不是 1，代表沒接受到訊號，則顯示原來的 A
	CLR	RI		;如果是，必須重置為0
	MOV	A,SBUF		;重 SBUF 內取來收到的訊號。
	SUBB	A,#47		;將收到的 ASCII 值減去 47 轉成 TABLE 上的 Offset
	MOVC	A,@A+DPTR	;將 DPTR內的位址加上 ACC 內的 Offset 值所得的新位址，取新位址(Table 內)所在的值放到 ACC
	CALL	OUTPUT		;顯示
	CALL	DELAY
	CALL	DELAY		

$END:	SJMP	LOOP		;進入回圈
	
		


	
;顯示字型的 Table----------.gfedcba -> 00000000B-------------------------------
TABLE:	DB	3FH	;0
	DB	06H	;1
	DB	5BH	;2
	DB	4FH	;3
	DB	66H	;4
	DB	6DH	;5
	DB	7DH	;6
	DB	27H	;7
	DB	7FH	;8
	DB	6FH	;9
	
	DB	41H	;:
	DB	45H	;;
	DB	38H	;<
	DB	48H	;=
	DB	0EH	;>
	DB	4BH	;?
	DB	5FH	;@
	
	DB	77H	;A
	DB	7CH	;B
	DB	58H	;C
	DB	5EH	;D
	DB	79H	;E
	DB	71H	;F
	DB	6FH	;G
	DB	74H	;H
	DB	04H	;I
	DB	0EH	;J
	DB	40H	;K  //未定義
	DB	38H	;L
	DB	40H	;M  //未定義
	DB	54H	;N
	DB	5CH	;O
	DB	73H	;P
	DB	67H	;Q
	DB	70H	;R
	DB	6DH	;S
	DB	78H	;T
	DB	1CH	;U
	DB	3EH	;V
	DB	40H	;W  //未定義
	DB	40H	;X  //未定義
	DB	6EH	;Y
	DB	5BH	;Z
	

;Output ----將 ACC 內的值反相輸出到 P1 ----------------------------------
OUTPUT:	CPL	A		;因為 Active Low，所以必須先將 A 反相
	MOV	P1,A		;將 ACC 的值輸出到 P1	
	RET			;返回	

	
;INILED 副函式---從 a 段到 g 段 跑一遍 -----------------------------------
INILED:		
	;CLR	TR0		;先停止 Timer0
	CALL	StartTimer0
	MOV	A,#80H		;初值為 1000 0000B
$	RL	A		; 0000 0001 -> 0000 0010 -> 0000 0100 .... 逐一顯示
	CALL	OUTPUT		;呼叫顯示函式
	CALL	DELAY		;延遲一下
	CPL	A		;因為在 OUTPUT 內會將 A 反相，所以這裡要再反相回來
	CJNE	A,#80H,$	;如果不等於 1000 000B 則繼續 RL A，然後 OUTPUT

	;上面和下面的 Code 功能相同，但是使用上面的 Code，總程式只佔 98 Bytes，下面則佔 123 bytes

	;MOV	A,#01H	;七段顯示器的 a 段 LED
	;CALL	OUTPUT	;
	
	;MOV	A,#02H	;七段顯示器的 b 段 LED
	;CALL	OUTPUT	;
	
	;MOV	A,#04H	;七段顯示器的 c 段 LED
	;CALL	OUTPUT	;
	
	;MOV	A,#08H	;七段顯示器的 d 段 LED
	;CALL	OUTPUT	;
	
	;MOV	A,#10H	;七段顯示器的 e 段 LED
	;CALL	OUTPUT	;
	
	;MOV	A,#20H	;七段顯示器的 f 段 LED
	;CALL	OUTPUT	;
	
	;MOV	A,#40H	;七段顯示器的 g 段 LED
	;CALL	OUTPUT	;	
	RETI


;Delay 副函式-----------------------------------------
DELAY:	MOV	R0,#05H		;Delay 參數,改變此次數可控制Delay 時間
$0:	MOV	R1,#00H		;外層回圈 -----
$1:	MOV	R2,#00H		;內層回圈 --  |	
$2:	DJNZ	R2,$2		;內  _____|  |	
	JB	RI,$Ret		;如果有訊號送進 SBUF ，則立刻跳出
	DJNZ	R1,$1		;外__________|
	DJNZ	R0,$0		;
$Ret:	RET			;返回


;Timer Delay 副函式 (精確延遲)---------------------------------------
;TimerDelay	
;	MOV	MS_50,#00H	;將 MS_50 初始為 0
;$IniT0	CALL	StartTimer0	;
;$DlayMS	JNB	TF0,$DlayMS	;延遲50 msec
;	JNB	P3.7,INIOFT	;如果 P3.7 為 0，則跳到 INIOFT
;	INC	MS_50		;MS_50 加一
;	MOV	R0,MS_50		;將 MS_50 的值 copy 到 ACC 利用 ACC 作比較動作
;	CJNE	R0,#20,$IniT0	;延遲 50 ms * 20 = 1000 ms = 1 sec
;	RET

;Set Timer0 副函式 --------------------------------------------------
SetTimer0:
	;MOV	TMOD,#11110001B	;模式 1，自動載入	 
	;MOV	A,TMOD		;將 TMOD 的值 copy 到 ACC 準備作每個位元設定
	;ANL	A,#11110000B	;將 Timer0 初始化為 0000B
	;CLR	ACC.3		;GATE=0 -> 允許計時/計數
	;CLR	ACC.2		;C/!T=0 -> 計時模式
	;CLR	ACC.1		;M0=1
	;SETB	ACC.0		;M1=0  ->模式 2
	;MOV	TMOD,A		;將設定好的值 回 copy 到 TMOD，設定TMOD	
	
	MOV	TMOD,#00100010B	;Timer0,Timer1都是模式 2，自動載入
	MOV	TH1,#FDH	;設定鮑率	
	SETB	TR1
	MOV	SCON,#01010000B
	RET
	
;StartTimer0 --設定 Timer0 (TH0, TL0) 初值--------------------------------
StartTimer0:
	;MOV	TH0,#(65536-COUNT)/256
	;MOV	TL0,#(65536-COUNT).MOD.256
	
	CLR	TR0		;先停止 Timer0	
	MOV	TH0,#(256-COUNT)
	MOV	TL0,#00	
	CLR	TF0		;清除溢位 flag
	
	MOV	OFT,#FFH	;初始化 Offset 為 0 
	MOV	US_200,#00H
	MOV	MS_50,#00H	
	
	SETB	TR0		;TR0=1, Timer Run, 開始計時
	
	MOV	Hr,#0		;重置時間
	MOV	Min,#0
	MOV	Sec,#0
	MOV	StrCnt,#0
	
	RET
	

	
;Timer 0中斷服務---------------------------------------------------------
IRS_Display:		
	;判斷離上次送出訊號後是否經過一秒-------------------------------------
	INC	US_200
	MOV 	R2,US_200	;因為 ACC 用來輸出到 P1，所以利用 R0 來作比較
	CJNE 	R2,#250,$ExtIRS	;如果未累積到 200 us * 250 =50 ms 則跳出中斷服務，實際只有 199.6584*250=49.9146 ms
	MOV	US_200,#00H	;如果已經達到 50 ms，則將 US_200 清為 0
	INC	MS_50
	MOV	R2,MS_50	
	CJNE	R2,#20,$ExtIRS	;如果未累積到 50 ms * 20 =1000 ms 則跳出中斷服務, 
	MOV	MS_50,#00H	;每 1 秒實際秒數只有 49.9146*20=998.26388888 ms，每一秒短少 1.7361111 ms=1736.1111 us
	
	;開始計算時分秒 ----------------------------------------------------------
	INC	Sec
	MOV	R2,Sec
	CJNE	R2,#60,$NoCorrection
	MOV	Sec,#0		;超過60秒，重置為 0，並將 Min 加上 1
	INC	Min
	MOV	R2,Min
	CJNE	R2,#60,$NoCorrection	
	MOV	Min,#0		;超過60分，重置為 0，並將 Hr 加上 1		
	;//每小時校正一次, 每小時(3600秒)短少 1.7360000 ms*3600=6249.99996 ms ~= 6.25 秒    
	MOV	A,Sec		;因此需將目前秒數減掉短少的秒數=> 7-6.25=750 ms=50ms*15 >> MS_50=15
	SUBB	A,#7
	MOV	Sec,A
	MOV	MS_50,#15
	
	INC	Hr
	MOV	R2,Hr
	CJNE	R2,#24,$NoCorrection	
	MOV	Hr,#0		;超過60分，重置為 0，並將 Hr 加上 1	

	
$NoCorrection:	
	INC	OFT
	MOV	R2,OFT
	CJNE	R2,#43,$OutHMS	;如果 Offset 沒有超過 Table 定義範圍，則 Show，否則重置為 0
	MOV	OFT,#00	
	
	;判斷要送出時、分、秒或其他字元--------------------------------------------------------
$OutHMS	INC	StrCnt
	MOV	R2,StrCnt	
	CJNE	R2,#4,$OutH	;如果未累積到 50 ms * 20 =1000 ms 則跳出中斷服務, 49.91*200=998.2 ms，每一秒誤差 1.8 ms
	MOV	StrCnt,#0
$OutH	CJNE	R2,#1,$OutM
	MOV	A,Hr
	JMP	$Show
$OutM	CJNE	R2,#2,$OutS
	MOV	A,Min
	JMP	$Show	
$OutS	CJNE	R2,#3,$OutCh
	MOV	A,Sec
	JMP	$Show
$OutCh:	CJNE	R2,#4,$Show	
	MOV	A,OFT		
	ADD	A,#48		;將 offset 值加上 48 轉成 ASCII 值，準備傳送	
	

$Show:	CLR	TI		;清除傳送中斷位元後， 送到 SBUF 的值才能繼續傳送到 PC
	MOV	SBUF,A
	MOV	A,OFT
	MOVC	A,@A+DPTR	;將 DPTR內的位址加上 ACC 內的 Offset 值所得的新位址，取新位址(Table 內)所在的值放到 ACC	
	CALL	OUTPUT		;顯示	
	
$ExtIRS:RETI