<html><head><meta name="color-scheme" content="light dark"></head><body><pre style="word-wrap: break-word; white-space: pre-wrap;">;**********************************************************************
; 七段式 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
;**********************************************************************


;****************************	定義宣告區	********************************************
LF	EQU	0AH		;Line Feed
CRtn	EQU	7EH		;Carrage Return "~"
BPS1200	EQU	F8H
BPS9600 EQU     FDH
COUNT	EQU	184		;每 184 次=199.64 us, 大約等於 200 us  

OFT	EQU	30H		;配置位址空間 30H 給OFT(offset) 儲存數值用
BPS	EQU	31H

US_200	EQU	32H		;每次計時中斷(US_200)累加一次
MS_50	EQU	33H		;每 50 毫秒累加
Hr	EQU	34H		;時
Min	EQU	35H		;分
Sec	EQU	36H		;秒
InCnt	EQU	37H
OutCnt	EQU	38H		;記錄目前送出多少字
ComID	EQU	39H		;記錄目前的 Command ID是什麼？
State	EQU	3AH

;收發字串各配置 5 Byte 儲存------------------------------------------
SendStr	EQU	40H		;送出的Buffer 5 Byte,位址位於 50H ~ 57H ，程式區 （DB ..）才可以用 DPTR 來定址取得值。 資料區只能用 R0 定址取值
RecvStr	EQU	48H		;接收的位址 58H ~ 5FH,
;*** 60H 開始為 stack, 不可以再宣告變數了***************




	ORG	0000H		;設定起使位址
	LJMP	BEGIN		;跳到主程式 Main Loop	
	
	ORG	0003H		;外部中斷 INT0
	LCALL	RESET
	RETI	
	
	ORG	000BH		;Timer0中斷
	LJMP	IRS_T0
	
	ORG	0013H		;外部中斷 2
	RETI
	
	ORG	001BH		;Timer 1 中斷
	RETI
	
	ORG	0023H		;串列傳輸中斷
	LJMP	IRS_Serial

	
;****************************	程式區	********************************************	
BEGIN:	
	MOV	R1,#00H		;先作一小段延遲，讓系統Ready
$:	DJNZ	R1,$		
	MOV	SP,#60H		;指定堆疊起始位址	
		
	;設定 X0, X1, T0, T1, Serial, T2 的中斷斷優先權和是否啟動--------------------------------------------------------------
	CALL	IniIP		;初始化 IP 暫存器(中斷優先權）。		
	CALL	IniIE		;初始化 IE 暫存器(啟動/取消中斷）。		
	CALL	IniTCON		;初始化 TCON 暫存器。
	CALL	IniVar		;初始化所有變數	
	
	CALL	RESET		;呼叫 RESET 副函式，將每個 LED (a,b,c,d,e,f,g) 各跑一次
	
	CALL	StartT0		;設定 Timer0 模式	
	
	MOV	BPS,#BPS9600	;設定鮑率 9600BPS
	CALL	StartT1		
	
	SETB	EA		;啟動中斷服務
	
LOOP:	
	
	JMP	LOOP		;進入回圈



;啟動中斷服務-------------------------------------------
IniIE:		
	;SETB	ET0			;啟動 Timer0 中斷服務。  	IE.1		
	;SETB	EX0			;IE.0 啟動 Timer0 中斷服務
	MOV	IE,#00011111B		;EA.X.ET2.ES.ET1.EX1.ET0.EX0。啟動 Serial, T1, X1, T0, X0 的中斷服務
	RET
	
;設定中斷優先權----------------------------------	
IniIP:
	;SETB	PT0			;設定 Timer0 中斷優先。	IP.1 Timer0 中斷優先權設為高
	;SETB	PX0			;設定外部中斷0 (P3.2)	IP.0 中斷優先權設為高	
	MOV	IP,#00010011B		;X.X.PT2.PS.PT1.PX1.PT0.PX0
	
	RET
	
	
;初始化 T0, T1 溢位旗標及X0, X1的中斷旗標，  停止 Timer Run ，設定 觸發條件
IniTCON:
	;SETB	IT0			;設定 Timer0 負緣觸發。 	Interrupt Type: 1 = 負緣觸發
	MOV	TCON,#00000011B		;[TF1].TR1.[TF0].TR0.[IE1].IT1.[IE0].IT0。 []:表示可中斷 CPU
	RET	
	
	
;初始化變數 ----------------------------------------------------------------
IniVar:	
	MOV	OFT,#FFH	;初始化 Offset 為 0 
	MOV	US_200,#00H
	MOV	MS_50,#00H	
	MOV	InCnt,#00H
	MOV	OutCnt,#00H
	MOV	Hr,#00H		;重置時間
	MOV	Min,#00H
	MOV	Sec,#00H
	MOV	ComID,#00H	
	RET



;Output ----將 ACC 內的值反相輸出到 P1 ----------------------------------
OUTPUT:		
	CPL	A		;因為 Active Low，所以必須先將 A 反相
	MOV	P1,A		;將 ACC 的值輸出到 P1		
	RET			;返回	

	
	
;RESET 副函式---從 a 段到 g 段 跑一遍 -----------------------------------
RESET:	
	PUSH	A		
	CALL	IniVar
	CALL	StartT0
	MOV	A,#80H		;初值為 1000 0000B
$:	RL	A		; 0000 0001 -&gt; 0000 0010 -&gt; 0000 0100 .... 逐一顯示
	CALL	OUTPUT		;呼叫顯示函式
	CALL	DELAY		;延遲一下
	CPL	A		;因為在 OUTPUT 內會將 A 反相，所以這裡要再反相回來
	CJNE	A,#80H,$	;如果不等於 1000 000B 則繼續 RL A，然後 OUTPUT				
	POP	A
	
	CALL	SendTestOK
	RET	
	
	

;StartTimer0 ，在此作時鐘計時用--------------------------------
StartT0:
	;MOV	TH0,#(65536-COUNT)/256
	;MOV	TL0,#(65536-COUNT).MOD.256
	
	CLR	TR0			;TR0 為 TCON.4。先停止 Timer0 計時		
	MOV	TH0,#(256-COUNT) 	;設定重新載入到 TL0 的 Count
	MOV	TL0,#00	
	CLR	TF0			;清除Timer 0 的溢位 flag	
	SETB	TR0			;TR0=1, Timer Run, 開始計時	
	RET	
	
	
;Set Timer1 副函式, 串列傳輸的鮑率 固定以 Timer 1 來設定--------------------------------------------------
StartT1:
	
	;TMOD Register
	;bit7	bit6	bit5	bit4	bit3	bit2	bit1	bit0
	;GATE	C/T	M1	M0	GATE	C/T	M1	M0
	; [	   Timer 1	]	  [	  Timer 0	]
	CLR	TR1	
	MOV	TH1,BPS			;只能使用 Timer 1 來設定鮑率 #FDH ，不可使用 其他如 Timer0, Timer2
	MOV	TL1,BPS	
	MOV	TMOD,#00100010B		;TMOD。	Timer0,Timer1都是模式 2，自動載入		
	MOV	SCON,#01010000B		;設定串列傳輸模式為 模式 1, 與 PC 溝通的模式,SCON 可位元定址
					;SCON --&gt; SM0.SM1.SM2.REN.TB8.RB8.[TI].[RI]。  []:表示可中斷 CPU
	SETB	TR1			;Timer Run, 開始計時	
	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			;返回

	
	
;傳送字串的函式----------------------------------------------------------------
SendString:	
	PUSH	A		;
	PUSH	PSW		;ADD, CLR, MOVC....等等會影響 PSW 旗標狀態，所以必須先儲存。
	
	SETB	TI		;設定TI=1，表示上一次已經傳送完畢，可以開始傳送
$1:	CLR	A		
	MOVC	A,@A+DPTR	;不能用 MOVX A,@DPTR 和 INC DPTR 來逐一取得所要的字元
	CJNE	A,#CRtn,$SndCh
	JMP	$RetSnd
$SndCh:	
	CALL	SendChar			
	INC	DPTR
	JMP	$1
$RetSnd:
	POP	PSW
	POP	A	
	RET
	
;傳送字串的函式----------------------------------------------------------------
SendStringEX:	
	PUSH	R0
	PUSH	A		;
	PUSH	PSW		;ADD, CLR, MOVC....等等會影響 PSW 旗標狀態，所以必須先儲存。
	
	SETB	TI		;設定TI=1，表示上一次已經傳送完畢，可以開始傳送	
	
$1:	MOV	A,@R0		
	CJNE	A,#CRtn,$SndCh
	JMP	$RetSnd
$SndCh:	
	CALL	SendChar			
	INC	R0
	JMP	$1
$RetSnd:
	POP	PSW
	POP	A
	POP	R0	
	RET
	
;傳送一個字元的函式----------------------------------------------------------------
SendChar:
$:	JNB	TI,$	;等待傳送中斷信號產生，TI=1 表示上一次已經傳送完畢，才可以繼續傳送信號		
	MOV	SBUF,A	
	CLR	TI
	RET	
	
SendTestOK:
	MOV	DPTR,#TestOK
	CALL	SendString
	RET
	
SendRecvErr:
	MOV	DPTR,#RecvErr
	CALL	SendString
	RET	
	
SendDbgStr:
	MOV	DPTR,#DbgStr
	CALL	SendString
	RET	
	
SendRecvStr:	
	MOV	4FH,#CRtn	;RecvStr 的結尾
	MOV	R0,#RecvStr
	CALL	SendStringEX
	RET	
	
SendSendStr:	
	MOV	47H,#CRtn	;SendStr 的結尾
	MOV	R0,#SendStr
	CALL	SendStringEX
	RET	
	
	
TreateCommand:
	PUSH	R0
	PUSH	R1
	PUSH	R2
	PUSH	A
	PUSH	PSW
	
	MOV	R0,#SendStr
	MOV	@R0,#'R'	;SendStr第一個字元
	INC	R0		
	MOV	@R0,#'T'	;SendStr第二個字元
	INC	R0		
	MOV	@R0,ComID	;SendStr第三個字元
	
	MOV	R1,#RecvStr	;第一個字元
	INC	R1		;第二個字元
	INC	R1		;第三個字元
	
	
	MOV	R2,ComID
	CJNE	R2,#'0',$ID1	
	AJMP	$Send
;詢問 8051 目前時間***************************
$ID1:
	CJNE	R2,#'1',$ID2	
	INC	R0		
	MOV	@R0,Hr		;SendStr第4個字元
	INC	R0
	MOV	@R0,Min		;SendStr第5個字元
	INC	R0
	MOV	@R0,Sec		;SendStr第6個字元
	INC	R0
	MOV	@R0,#CRtn	;SendStr第7個字元
	AJMP	$Send
;設定 8051 時間***************************
$ID2:
	CJNE	R2,#'2',$ID3
	INC	R1
	MOV	Hr,@R1		;RecvStr 第4個字元
	INC	R1
	MOV	Min,@R1		;RecvStr 第5個字元
	INC	R1
	MOV	Sec,@R1		;RecvStr 第6個字元
	AJMP	$Send
;詢問 8051 P1 值***************************	
$ID3:
	CJNE	R2,#'3',$ID4
	INC	R0
	MOV	@R0,P1		;SendStr第4個字元
	INC	R0
	MOV	@R0,#CRtn	;SendStr第5個字元
	AJMP	$Send
;設定 8051 P1 值***************************	
$ID4:
	CJNE	R2,#'4',$ID5
	INC	R1
	MOV	P1,@R1	;RecvStr 第4個字元
	AJMP	$Send
;詢問 8051 目前鮑率***************************	
$ID5:
	CJNE	R2,#'5',$ID6
	INC	R0
	MOV	@R0,BPS		;SendStr第4個字元
	INC	R0
	MOV	@R0,#CRtn	;SendStr第5個字元
	AJMP	$Send
;設定 8051 鮑率***************************	
$ID6:
	CJNE	R2,#'6',$ID7
	INC	R1
	MOV	BPS,@R1	;RecvStr 第4個字元
	AJMP	$Send	
	
$ID7:
	CJNE	R2,#'7',$ErrCmd
	CALL	RESET
	;INC	R0		;SendStr第4個字元
	;;MOV	@R0,#1		;0:成功 1:失敗
	;INC	R0
	;MOV	@R0,#CRtn	;SendStr第4個字元
	AJMP	$Send	
$ErrCmd:
;	CALL	SendRecvErr
;	JMP	$retn
$Send:
	CALL	SendSendStr
	
	
	
$retn:
	; 清掉 RecvStr Buffer------------------
;	MOV	R0,#RecvStr
;	MOV	R1,#7
;$clear:	MOV	@R0,#'0'
;	INC	R0
;	DJNZ	R1,$clear

	POP	PSW
	POP	A
	POP	R2
	POP	R1
	POP	R0
	MOV	ComID,#00H		
	RET
	


;T0 中斷服務，用來計算時分秒*********************************************************************	
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		;因此需將目前秒數減掉短少的秒數=&gt; 7-6.25=750 ms=50ms*15 &gt;&gt; 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:
	MOV	A,Sec		
	CALL	OUTPUT		;顯示目前秒數	
	
$ExtIRS:
	POP	PSW
	POP	A
	RETI
	
	
	
;串列中斷服務，用來處理輸入指令 *******************************************************************	
IRS_Serial:
	PUSH	A	
	PUSH	PSW	

	;接收字元，放到 RecvStr------------------------------------------------------
	JNB	RI,$CheckTI		;如果沒有收到訊號，則檢查是否要送出訊號
	CLR	RI
	
	MOV	R5,ComID
	CJNE	R5,#0,$CheckTI		;如果正在處理指令的話，不接受 RI
	
	MOV	A,#01110000B		;七節燈顯示字元 'r' 表示接收中
	CALL	OUTPUT	
	
	MOV	A,#RecvStr		;取得Receive Buffer 的起始位置	
	ADD	A,InCnt			;取得目前要存入的位置
	MOV	R0,A			;暫存器定址
	MOV	A,SBUF			;取得 SBUF 內的字放入 ACC
	MOV	@R0,A			;將值從 ACC 存入 R0 指向的位址	
	INC	InCnt			;接收數量加一
	MOV	R5,InCnt		;數量未滿 8 個字元的話繼續加收
	CJNE	R5,#8,$chkCtrn
	JMP	$DecodeCommand
$chkCtrn:
	CJNE	A,#CRtn,$CheckTI	;如果還沒收到最後一個字元 (Carage Return)，不作命令解析，跳去檢查是否需傳送
	
$DecodeCommand:
	MOV	InCnt,#00H	
	
	;開始解析命令RecvStr 的命令------------------------
	;第一個字元--------------
	MOV	R0,#RecvStr
	CJNE	@R0,#'Q',$ErrCommand
	;第二個字元-----------
	INC	R0
	CJNE	@R0,#'S',$ErrCommand	
	;第三個字元-----------	
	INC	R0	
	MOV	ComID,@R0		;將第三個數值放入 CommandID	
	CALL	TreateCommand
	JMP	$CheckTI
	;第四個字元----------
	
	;第五個字元 ---------
	
	;第六個字元 ---------
	
	;第七個字元 ----------
	
	;第八個字元 ---------
	
$ErrCommand:
	MOV	InCnt,#00H
	CALL	SendRecvErr	
	
		
$CheckTI:
	JNB	TI,$retn		;如果沒有傳輸訊號則跳出
	CLR	TI		
	MOV	A,#01111000B		;七節燈顯示字元 't' 表示傳送中	
	CALL	OUTPUT
	
$retn:
	POP	PSW
	POP	A
	RETI





;常數定義區 ( 放在此區 ( DB ##)的值無法被更改 )----------------------------------------------------
TestOK:	DB	"8051 Initial OK.",LF,CRtn
RecvErr:DB	"Receive Unknown Command.",LF,CRtn
DbgStr:	DB	"Debug On!",LF,CRtn	
	


;***********************************************************************************
;註：
;程式區的資料才可以用 DPTR 來定址取值， 資料區只能用 R0 等 來定址取值。
;例：
;	TeskOK 的字串是位於程式區，所以在 SendString 函式內用 DPTR 來定址，逐一送出字元。
;	SendStr的字串如果用 DPTR 定址取值，會發生錯誤，所以改用 R0 來定址取值，如 SendStringEX 函式內所示。



;***********************************************************************************
</pre></body></html>