;**********************************************************************
; 七段式 LED 測試，包含數字和英文字母,  
; 利用計時中斷作延遲 Use Timer0, Mode 2, 8 bit, 256 maximun，自動載入
; 每個 Machine Cycle=1.08506944 us, use 11.0592 MHz oscilator
; 利用外部中斷 Int0 作 重新開始
; 送出 時分秒等數值到 PC
; 接收 PC 傳來指令並動作
; 每小時校正時間一次
; Daniel Lu  2003'11'04
;**********************************************************************


;****************************	定義宣告區	********************************************
BPS9600 EQU     FDH
COUNT	EQU	184		;每 184 次=199.64 us, 大約等於 200 us  

OFT	EQU	30H		;配置位址空間 30H 給OFT(offset) 儲存數值用
cnt	EQU	31H

US_200	EQU	32H		;每次計時中斷(US_200)累加一次
MS_50	EQU	33H		;每 50 毫秒累加
Hr	EQU	34H		;時
Min	EQU	35H		;分
Sec	EQU	36H		;秒
StrCnt	EQU	37H
State	EQU	38H		;記錄目前狀態   Pending=0,  Recving=1, Sending=2

;收發字串各配置 5 Byte 儲存------------------------------------------
SendStr	EQU	39H		;送出的Buffer 5 Byte,位址位於 39H ~ 3DH
RecvStr	EQU	3EH		;接收的位址 3EH ~ 42H



	ORG	0000H		;設定起使位址
	LJMP	BEGIN		;跳到主程式 Main Loop	
	
	ORG	0003H		;外部中斷 INT0
	LJMP	RESET	
	
	ORG	000BH		;Timer0中斷
	LJMP	IRS_T0
	
	ORG	0013H		;外部中斷 2
	RETI
	
	ORG	001BH		;Timer 1 中斷
	RETI
	
	ORG	0023H		;串列傳輸中斷
	RETI

	
;****************************	程式區	********************************************	
BEGIN:	
	MOV	R1,#00H		;先作一小段延遲，讓系統Ready
$:	DJNZ	R1,$		
	MOV	SP,#60H		;指定堆疊起始位址
	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	StartTimer1	;設定 Timer0 模式	
	CALL	RESET		;呼叫 RESET 副函式，將每個 LED (a,b,c,d,e,f,g) 各跑一次
	
	SETB	EA		;啟動中斷服務
	
LOOP:	
	;Send String-------------------------	
	MOV	R7,cnt
	CJNE	R7,#0,$ok
	CALL	SendErrStr
	SJMP	$inccnt
$ok:	CJNE	R7,#1,$dbg
	CALL	SendOK	
	SJMP	$inccnt
$dbg:	CJNE	R7,#2,$other	
	CALL	SendDbgStr
	SJMP	$inccnt	
$other:	MOV	DPTR,#Hr
	CALL	SendString
	MOV	cnt,#FFH
	SJMP	$inccnt

$inccnt:INC	cnt
	;Send String-------------------------
	
	JMP	LOOP		;進入回圈
	
		
;RESET 副函式---從 a 段到 g 段 跑一遍 -----------------------------------
RESET:			
	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	
	
	MOV	Hr,#0		;重置時間
	MOV	Min,#0
	MOV	Sec,#0
	MOV	OFT,#0
	MOV	State,#0
	MOV	cnt,#0
	
	CALL	SendOK
			
	RETI	




;接收字串的函式---------------------------------------------------------------------
ReceiveString:		
	RET
	

	
	
;處理輸入的指令------------------------------------------------------

		
		

;傳送字串的函式----------------------------------------------------------------
SendString:	
	SETB	TI		;設定TI=1，表示上一次已經傳送完畢，可以開始傳送
	
$1:	CLR	A		
	MOVC	A,@A+DPTR	;不能用 MOVX A,@DPTR 和 INC DPTR 來逐一取得所要的字元
	CJNE	A,#0,$SndCh
	JMP	$RetSnd
$SndCh:	
	CALL	SendChar			
	INC	DPTR
	JMP	$1
$RetSnd:
	RET
	
;傳送一個字元的函式----------------------------------------------------------------
SendChar:
$:	JNB	TI,$	;等待傳送中斷信號產生，TI=1 表示上一次已經傳送完畢，才可以繼續傳送信號	
	CLR	TI
	MOV	SBUF,A	
	RET	

SendOK:
	MOV	DPTR,#TestOK	;將 SendStr 位址存在 DPTR	
	CALL	SendString	
	RET
	
SendErrStr:
	MOV	DPTR,#RecvErr	;將 SendStr 位址存在 DPTR	
	CALL	SendString	
	RET
	
SendDbgStr:
	MOV	DPTR,#DbgStr	;將 SendStr 位址存在 DPTR	
	CALL	SendString	
	RET	



;常數定義區 ( 放在此區 ( DB ##)的值無法被更改 )----------------------------------------------------
TestOK:	DB	"8051 Initial OK.",00AH,00DH,000H
RecvErr:DB	"Receive Unknown Command.",00AH,00DH,000H
DbgStr:	DB	"Debug On!",00AH,00DH,000H

	
;顯示字型的 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:	
	PUSH	A
	CPL	A		;因為 Active Low，所以必須先將 A 反相
	MOV	P1,A		;將 ACC 的值輸出到 P1	
	POP	A
	RET			;返回	

	

		

;極短Delay 副函式-----------------------------------------
ShortDelay:		
	MOV	R6,#00H		;外層回圈 -----
$1:	MOV	R7,#00H		;內層回圈 --  |	
$2:	DJNZ	R7,$2		;內  _____|  |	
	JB	RI,$Ret		;如果有訊號送進 SBUF ，則立刻跳出
	DJNZ	R6,$1		;外__________|
$Ret:	
	RET
	

;Delay 副函式-----------------------------------------
DELAY:		
	MOV	R5,#05H		;Delay 參數,改變此次數可控制Delay 時間
$sDely:	CALL	ShortDelay	
	DJNZ	R5,$sDely	
	RET			;返回



;Set Timer1 副函式, 串列傳輸的鮑率 固定以 Timer 1 來設定--------------------------------------------------
StartTimer1:
	;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	
	
	;TMOD Register
	;bit7	bit6	bit5	bit4	bit3	bit2	bit1	bit0
	;GATE	C/T	M1	M0	GATE	C/T	M1	M0
	; [	   Timer 1	]	  [	  Timer 0	]
		
	MOV	TH1,#BPS9600	;只能使用 Timer 1 來設定鮑率 #FDH ，不可使用 其他如 Timer0, Timer2
	MOV	SCON,#50H;#01010000B	;設定串列傳輸模式為 模式 1, 與 PC 溝通的模式,SCON 可位元定址
	MOV	TMOD,#22H;#00100010B	;Timer0,Timer1都是模式 2，自動載入
	SETB	TR1		;Timer Run, 開始計時	
	SETB	TI		;設定傳輸中斷Flag為 1
	RET
	
;StartTimer0 --設定 Timer0 (TH0, TL0) 初值--------------------------------
StartTimer0:
	;MOV	TH0,#(65536-COUNT)/256
	;MOV	TL0,#(65536-COUNT).MOD.256
	
	CLR	TR0
	
	MOV	TH0,#(256-COUNT) ;設定重新載入到 TL0 的 Count
	MOV	TL0,#00	
	CLR	TF0		;清除Timer 0 的溢位 flag
	
	MOV	OFT,#FFH	;初始化 Offset 為 0 
	MOV	US_200,#00H
	MOV	MS_50,#00H	
	MOV	StrCnt,#00H
	
	SETB	TR0		;TR0=1, Timer Run, 開始計時	
	
	RET
		



	

	
;Timer 0中斷服務---------------------------------------------------------
IRS_T0:	
	PUSH	A
	PUSH	PSW		

	;判斷離上次送出訊號後是否經過一秒-------------------------------------
	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,$ExtIRS	;如果 Offset 沒有超過 Table 定義範圍，則 Show，否則重置為 0
	MOV	OFT,#00	
	
	
	
	
$ExtIRS:	
	MOV	A,OFT
	CALL	OUTPUT
	POP	PSW
	POP	A
	RETI

