项目要求:
智能时钟设计(LCD 显示):利用 DS1302 芯片,设计一个智能电子钟,要求:
- 可显示年月日,星期及时分秒
- 双行字符型 Lcd 显示(一行显示年月日星期,一行显示时分秒)。
- 设置四个独立按键 K1~K4,其功能分别为:
K1:进入/退出闹钟设置;
K2:闹钟路数选择;
K3:闹钟小时设置;
K4:闹钟分钟设置。
只有在进入闹钟设置界面后,按键 2~4 才有效。此时 LCD 显示:
第一行: BELL TIME
第二行: 路数(显示 1 或 2) 小时:分钟 - 允许设置两路闹钟。默认无闹钟
- 闹钟所设时间到时,从蜂鸣器输出 5s 的闹铃。
总体设计:
- 实现DS1302的初始化:能够写入RAM值来初始化计时时间,能够从RAM中读取时分秒年月日星期。
- 实现LCD1602初始化:能够在某一位置显示相应字符。
- 实现计时模式和闹钟模式切换:用单刀双置开关实现,0-表示计时模式,1-表示闹钟模式,在while循环中进行判断即可。
- 实现闹钟路数选择:依旧采用单刀双置双置开关,0-表示闹钟路数1,1-表示闹钟路数2。闹钟初值设置成0XFF,表示无闹钟(时最多24,分和秒最多60)。
- 实现闹钟设定:利用两个下降沿按键中断,每按下一次小时和分钟就加1,到60就清零。
- 闹钟蜂鸣器:每刷新一次时间就与设定好的闹钟时间进行对比即可。
电路设计:
程序设计:
;******************* 宏定义 ************************
LCDIO EQU P0 ;LCD数据总线连接P0口
RS EQU P2.0 ;Register select 连接P2.0
RW EQU P2.1 ;Read/Write 连接P2.1
E EQU P2.2 ;Enable 连接P2.2
BF EQU P0.7 ;LCD的忙标志位
CLOCKIO EQU P1.0 ;DS1302数据IO
SCLK EQU P1.1 ;DS1302时钟端口
RST EQU P1.2 ;DS1302复位端口
SECOND EQU 60H ;秒地址
MINUTE EQU 61H ;地址
HOUR EQU 62H ;时地址
YEAR EQU 63H ;日地址
MOUTH EQU 64H ;月地址
WEEK EQU 65H ;周地址
DAY EQU 66H ;年地址
BUZZER EQU P2.7 ;蜂鸣器
CLOCK_STATUS EQU P2.3 ;闹钟状态,1--退出闹钟状态,0--进入闹钟状态
CLOCK_LINE_STATUS EQU P2.6 ;闹钟路数,1--路数1, 0--路数2
;***************************************************
ORG 0000H
AJMP MAIN
ORG 0003H
LJMP INT0_INTERRUPT
ORG 0013H
LJMP INT1_INTERRUPT
ORG 0100H
;******************** 主函数 ************************
MAIN:
NOP
LCALL CLOCK_INIT ;调用DS1602初始化函数
LCALL LCD_INIT ;调用LCD1602初始化函数
LCALL EXTI_INTER_INIT ;调用中断初始化函数
SETB RS1
SETB RS0
MOV R0, #0FFH
MOV R1, #0FFH
MOV R2, #0FFH
MOV R3, #0FFH
CLR RS1
CLR RS0
CLR BUZZER
LOOP:
LCALL GET_TIME ;调用获取时间函数,存储起始地址60H
LCALL BUZ_1
LCALL BUZ_2
LCALL SHOW_TIME ;调用LCD显示时间函数
MOV DPTR, #385 ;调用延时函数
MOV A, #1
LCALL T0_DELAY
JB CLOCK_STATUS,LOOP
LCALL CLOCK_MODE
AJMP LOOP
;***************************************************
;**************** LCD1602初始化 *********************
LCD_INIT:
MOV A, #01H ;写入清屏命令
ACALL W_CMD
MOV A, #38H ;设置显示模式16*2显示
ACALL W_CMD
MOV A, #0CH ;设置屏幕显示,无光标
ACALL W_CMD
MOV A, #14H ;设置右移
ACALL W_CMD
RET
;***************************************************
;**************** LCD1602忙标志 *********************
WAIT_BUSY:
MOV LCDIO,#0FFH
CLR RS
SETB RW
CLR E
NOP
SETB E
JB BF, WAIT_BUSY
RET
;***************************************************
;*************** LCD1602写入命令函数 ****************
W_CMD:
LCALL WAIT_BUSY
MOV LCDIO, A ;把命令写到IO口处
CLR RS ;清0,选择命令寄存器
CLR RW
SETB E ;模拟下降沿,命令写入LCD中
NOP
CLR E
RET
;***************************************************
;*************** LCD1602写入数据函数 ****************
W_DAT:
LCALL WAIT_BUSY
MOV LCDIO, A
SETB RS ;置1,选择数据寄存器
CLR RW ;清0,为写入,置1,为读取
SETB E ;模拟下降沿
NOP
CLR E
RET
;***************************************************
;************** DS1302写入字节函数 ******************
;将B写入DS1302 --传入参数B --无传出参数
INPUT:
MOV R4, #8
INPUT_Rotate:
MOV A, B
RRC A
MOV B, A
MOV CLOCKIO, C
SETB SCLK ;SCLK下降沿写入字节
NOP
CLR SCLK
DJNZ R4, INPUT_Rotate
RET
;***************************************************
;************** DS1302读出字节函数 ******************
;将DS1302数据读出到A
OUTPUT:
MOV R4, #8
OUTPUT_Rotate:
MOV C, CLOCKIO
RRC A
SETB SCLK ;SCLK下降沿读出字节
NOP
CLR SCLK
DJNZ R4, OUTPUT_Rotate
RET
;***************************************************
;************* 定义DS1302启示计时时间 ***************
;写入 20年 12月 19日 周6 13时 13分 13秒
TIME_SET:
MOV SECOND, #13H ;秒
MOV MINUTE, #13H ;分
MOV HOUR, #13H ;时
MOV DAY, #19H ;日
MOV MOUTH, #12H ;月
MOV WEEK, #06H ;周
MOV YEAR, #20H ;年
RET
;***************************************************
;****************** 初始化DS1302 ********************
CLOCK_INIT:
;留32字节存储从DS1302读取的数据
MOV R0, #60H ;从60H开始,依次增加
MOV R1, #07H ;作为索引,索引32个连续字节
MOV A, #00H ;A起到中间变量,暂时存储00H
CLR_BUF:
MOV @R0, A ;间接寻址,把00H依次写入60H开始的32个字节中
INC R0 ;地址增1
DJNZ R1, CLR_BUF ;跳转,依次赋值
;写入命令字进行初始化
CLR RST ;模拟RST上升沿,启动数据传输(说明启动传输不是代表传输,还需要SCLK的上升沿
CLR SCLK
NOP ; 还需要SCLK的上升沿才能把数据按位依次传输过去)
SETB RST
MOV B, #8EH ;命令字,写保护寄存器,此时不能对寄存器进行写操作了
LCALL INPUT ;把命令字写如DS1302
MOV B, #00H ;WP清零,现在允许外部写入DS1302中数据
LCALL INPUT ;把命令字写如DS1302
LCALL TIME_SET ;设定初始化时间
MOV R0, #SECOND ;把在RAM中地址为60H(存储数据为秒)数据给R0
MOV R7, #7 ;索引,秒、分......送七次
MOV R1, #80H ;命令字。表示写入秒数据,当R2依次增二,表明开始进行写如分,时等数据.(这是规定,写如数据前先给命令字)
WRITE:
CLR RST ;模拟RST上升沿,启动数据传输
CLR SCLK
NOP
SETB RST
MOV B, R1 ;将命令字80H写入DS1302
LCALL INPUT
MOV A,@R0 ;R0存储的秒地址,间接寻址将数据给A
MOV B, A ;必须把数据传给B,因为写入函数(INPUT)传入参数是B
LCALL INPUT
INC R0 ;R0增1表示由秒向分过渡,或者由分向时过渡,依次类推
INC R1 ;R1增2,表示由秒向分的命令字过渡,或者由分向时的命令字过渡,依次类推
INC R1
SETB SCLK
CLR RST
DJNZ R7, WRITE ;循环7次,将秒,分,时,星期,天,月,年都写入
MOV B, #8EH ;写保护寄存器
LCALL INPUT
MOV B, #80H ;WP置1,写保护
LCALL INPUT
SETB SCLK
CLR RST
;***************************************************
;***************** 获取时间函数 *********************
GET_TIME:
MOV R0, #SECOND ;把秒的地址给R0,以便依次读出数据
MOV R7, #7 ;用来循环
MOV R1, #81H ;命令字。表示读出秒数据,当R2依次增二,表明开始进行写如分,时等数据.
GET_TIME_Rotato:
CLR RST ;模拟RST上升沿,表示启动数据传输
CLR SCLK
NOP
SETB RST
MOV B, R1 ;把命令字给B,由B写如DS1302中
LCALL INPUT ;写入命令字
LCALL OUTPUT ;读出数据,读出的数据是保存到A中的
MOV @R0, A ;现在将读出来的值放到秒或者分等的地址中
INC R0 ;地址加1,从而过渡到分,时等
INC R1 ;命令字需要加2才能进行读取
INC R1
SETB SCLK
CLR RST
DJNZ R7, GET_TIME_Rotato ;循环读取
RET
;***************************************************
;***************** 显示时间函数 *********************
SHOW_TIME:
;显示小时
MOV A, #84H
LCALL W_CMD
MOV A, HOUR
PUSH Acc
SWAP A
ANL A, #0FH
ORL A, #30H
LCALL W_DAT ;显示小时高位
MOV A, #85H
LCALL W_CMD
POP Acc
ANL A, #0FH
ORL A, #30H
LCALL W_DAT ;显示小时低位
;显示符号-
MOV A, #86H
LCALL W_CMD
MOV A, #2DH
LCALL W_DAT
;显示分钟
MOV A, #87H
LCALL W_CMD
MOV A, MINUTE
PUSH Acc
SWAP A
ANL A, #0FH
ORL A, #30H
LCALL W_DAT ;显示分钟高位
MOV A, #88H
LCALL W_CMD
POP Acc
ANL A, #0FH
ORL A, #30H
LCALL W_DAT ;显示分钟低位
;显示符号-
MOV A, #89H
LCALL W_CMD
MOV A, #2DH
LCALL W_DAT
;显示秒
MOV A, #8AH
LCALL W_CMD
MOV A, SECOND
PUSH Acc
SWAP A
ANL A, #0FH
ORL A, #30H
LCALL W_DAT ;显示秒高位
MOV A, #8BH
LCALL W_CMD
POP Acc
ANL A, #0FH
ORL A, #30H
LCALL W_DAT ;显示秒低位
;显示年
MOV A, #0C3H
LCALL W_CMD
MOV A, YEAR
PUSH Acc
SWAP A
ANL A, #0FH
ORL A, #30H
LCALL W_DAT ;显示年高位
MOV A, #0C4H
LCALL W_CMD
POP Acc
ANL A, #0FH
ORL A, #30H
LCALL W_DAT ;显示年低位
;显示符号-
MOV A, #0C5H
LCALL W_CMD
MOV A, #2DH
LCALL W_DAT
;显示月
MOV A, #0C6H
LCALL W_CMD
MOV A, MOUTH
PUSH Acc
SWAP A
ANL A, #0FH
ORL A, #30H
LCALL W_DAT ;显示月高位
MOV A, #0C7H
LCALL W_CMD
POP Acc
ANL A, #0FH
ORL A, #30H
LCALL W_DAT ;显示月低位
;显示符号-
MOV A, #0C8H
LCALL W_CMD
MOV A, #2DH
LCALL W_DAT
;显示日
MOV A, #0C9H
LCALL W_CMD
MOV A, DAY
PUSH Acc
SWAP A
ANL A, #0FH
ORL A, #30H
LCALL W_DAT ;显示日高位
MOV A, #0CAH
LCALL W_CMD
POP Acc
ANL A, #0FH
ORL A, #30H
LCALL W_DAT ;显示日低位
;显示符号-
MOV A, #0CBH
LCALL W_CMD
MOV A, #2DH
LCALL W_DAT
;显示周
MOV A, #0CCH
LCALL W_CMD
MOV A, WEEK
ANL A, #0FH
ORL A, #30H
LCALL W_DAT ;显示周
RET
;****************** T0延时函数 *******************
T0_DELAY:
PUSH Acc
LCALL T0_WAIT
POP Acc
DEC A
JNZ T0_DELAY
RET
T0_WAIT:
XRL DPL, #0FFH
XRL DPH, #0FFH
INC DPTR
T0_1:
MOV TL0, #09CH
MOV TH0, #0FFH
MOV TMOD, #1
SETB TCON.4
T0_2:
JNB TCON.5, T0_2
CLR TCON.4
CLR TCON.5
INC DPTR
MOV A, DPL
MOV A, DPH
JNZ T0_1
RET
;***************************************************
;********************** 闹钟模式 *********************
;1--退出闹钟模式, 0--进入闹钟模式
CLOCK_MODE:
LCALL LCD_INIT
CLOCK_MODE_ROTATE:
JB CLOCK_STATUS, EXIT
LJMP CLOCK_SHOW
AA:
MOV DPTR, #390 ;调用延时函数
MOV A, #1
LCALL T0_DELAY
NOP
LJMP CLOCK_MODE_ROTATE
EXIT:
MOV A, #01H ;写入清屏命令
ACALL W_CMD
RET
;***************************************************
;********************** 闹钟显示 *********************
CLOCK_SHOW:
;第一行显示
MOV R0, #83H ;判断写入位置
MOV DPTR, #TAB1 ;初始地址
MOV R1, #00H ;这个地方又错误
CLOK_SHOW_ROTATE:
MOV A, R0
LCALL W_CMD
MOV A, R1
MOVC A, @A+DPTR
LCALL W_DAT
INC R0
INC R1
CJNE R1, #10, CLOK_SHOW_ROTATE
NOP
;第二行显示
;闹钟路数,1--路数1, 0--路数2
JB CLOCK_LINE_STATUS,CLOCK_LINE_1
AJMP CLOCK_LINE_2
CLOCK_LINE_1:
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
NOP
LJMP CLOCK_LINE_1_SHOW
BB:
NOP
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
AJMP CLOCK_LINE_LOOP
CLOCK_LINE_2:
LJMP CLOCK_LINE_2_SHOW
CC:
CLOCK_LINE_LOOP:
LJMP AA
;***************************************************
;***************闹钟路数1显示函数********************
CLOCK_LINE_1_SHOW:
NOP
MOV A, #0C4H ;判断写入位置
NOP
LCALL W_CMD
NOP
MOV A, #1
ANL A, #0FH
ORL A, #30H
LCALL W_DAT ;显示闹钟路数
;显示小时
MOV A, #0C7H
LCALL W_CMD
SETB RS1
SETB RS0
MOV A, R0
CLR RS1
CLR RS0
PUSH Acc
SWAP A
ANL A, #0FH
ORL A, #30H
LCALL W_DAT ;显示小时高位
MOV A, #0C8H
LCALL W_CMD
POP Acc
ANL A, #0FH
ORL A, #30H
LCALL W_DAT ;显示小时低位
;显示符号-
MOV A, #0C9H
LCALL W_CMD
MOV A, #3AH
LCALL W_DAT
;显示分钟
MOV A, #0CAH
LCALL W_CMD
SETB RS1
SETB RS0
MOV A, R1
CLR RS1
CLR RS0
PUSH Acc
SWAP A
ANL A, #0FH
ORL A, #30H
LCALL W_DAT ;显示分钟高位
MOV A, #0CBH
LCALL W_CMD
POP Acc
ANL A, #0FH
ORL A, #30H
LCALL W_DAT ;显示分钟低位
NOP
LJMP BB
;***************************************************
;***************闹钟路数2显示函数********************
CLOCK_LINE_2_SHOW:
MOV A, #0C4H ;写入位置
LCALL W_CMD
MOV A, #2
ANL A, #0FH
ORL A, #30H
LCALL W_DAT
;显示小时
MOV A, #0C7H
LCALL W_CMD
SETB RS1
SETB RS0
MOV A, R2
CLR RS1
CLR RS0
PUSH Acc
SWAP A
ANL A, #0FH
ORL A, #30H
LCALL W_DAT ;显示小时高位
MOV A, #0C8H
LCALL W_CMD
POP Acc
ANL A, #0FH
ORL A, #30H
LCALL W_DAT ;显示小时低位
;显示符号-
MOV A, #0C9H
LCALL W_CMD
MOV A, #3AH
LCALL W_DAT
;显示分钟
MOV A, #0CAH
LCALL W_CMD
SETB RS1
SETB RS0
MOV A, R3
CLR RS1
CLR RS0
PUSH Acc
SWAP A
ANL A, #0FH
ORL A, #30H
LCALL W_DAT ;显示小时高位
MOV A, #0CBH
LCALL W_CMD
POP Acc
ANL A, #0FH
ORL A, #30H
LCALL W_DAT ;显示小时低位
LJMP CC
;***************************************************
;************** 外部中断初始化函数 ******************
;外部中断0做小时设定
;外部中断1做分钟设定
EXTI_INTER_INIT:
SETB IT0 ;外部中断0下降沿触发
SETB EX0 ;打开外部中断0开关
SETB IT1 ;外部中断1下降沿触发
SETB EX1 ;打开外部中断1开关
SETB EA ;打开中断总开关
RET
;***************************************************
;************** 外部中断0服务函数 ******************
;外部中断0做分钟设定
INT0_INTERRUPT:
JB CLOCK_LINE_STATUS,CLOCK_SELECT_LINE_1_MIN
AJMP CLOCK_SELECT_LINE_2_MIN
CLOCK_SELECT_LINE_1_MIN:
SETB RS1
SETB RS0
MOV A, R1
CJNE A,#59H,UNEQUAL_1_MIN
MOV R1,#00H
AJMP STOP_MIN
UNEQUAL_1_MIN:
ANL A, #0FH
CJNE A, #09H, PLUS_ONE_1_MIN
MOV A, R1
ANL A, #0F0H
ADD A, #10H
MOV R1,A
AJMP STOP_MIN
PLUS_ONE_1_MIN:
INC R1
AJMP STOP_MIN
CLOCK_SELECT_LINE_2_MIN:
SETB RS1
SETB RS0
MOV A, R3
CJNE A,#59H,UNEQUAL_2_MIN
MOV R3,#00H
AJMP STOP_MIN
UNEQUAL_2_MIN:
ANL A, #0FH
CJNE A, #09H, PLUS_ONE_2_MIN
MOV A, R3
ANL A, #0F0H
ADD A, #10H
MOV R3,A
AJMP STOP_MIN
PLUS_ONE_2_MIN:
INC R3
STOP_MIN:
CLR RS1
CLR RS0
RETI
;***************************************************
;************** 外部中断1服务函数 ******************
;外部中断1做小时设定
INT1_INTERRUPT:
JB CLOCK_LINE_STATUS,CLOCK_SELECT_LINE_1
AJMP CLOCK_SELECT_LINE_2
CLOCK_SELECT_LINE_1:
SETB RS1
SETB RS0
MOV A, R0
CJNE A,#23H,UNEQUAL_1
MOV R0,#00H
AJMP STOP
UNEQUAL_1:
ANL A, #0FH
CJNE A, #09H, PLUS_ONE_1
MOV A, R0
ANL A, #0F0H
ADD A, #10H
MOV R0,A
AJMP STOP
PLUS_ONE_1:
INC R0
AJMP STOP
CLOCK_SELECT_LINE_2:
SETB RS1
SETB RS0
MOV A, R2
CJNE A,#23H,UNEQUAL_2
MOV R2,#00H
AJMP STOP
UNEQUAL_2:
ANL A, #0FH
CJNE A, #09H, PLUS_ONE_2
MOV A, R2
ANL A, #0F0H
ADD A, #10H
MOV R2,A
AJMP STOP
PLUS_ONE_2:
INC R2
STOP:
CLR RS1
CLR RS0
RETI
;***************************************************
;****************** 闹钟1蜂鸣器判断 *********************
BUZ_1:
SETB RS1
SETB RS0
NOP
MOV A, R0
CJNE A, HOUR , UNEQUAL_LINE_BUZ_1
MOV A, R1
CJNE A, MINUTE, UNEQUAL_LINE_BUZ_1
SETB BUZZER
MOV A, #00H
MOV B, SECOND
CJNE A, B, UNEQUAL_LINE_BUZ_1
// MOV DPTR, #5000 ;调用延时函数
// MOV A, #1
// LCALL T0_DELAY
CLR BUZZER
UNEQUAL_LINE_BUZ_1:
CLR RS0
CLR RS1
RET
;***************************************************
;****************** 闹钟2蜂鸣器判断 *********************
BUZ_2:
SETB RS1
SETB RS0
NOP
MOV A, R2
MOV B, HOUR
CJNE A, B , UNEQUAL_LINE_BUZ_2
MOV A, R3
MOV B, MINUTE
CJNE A, B, UNEQUAL_LINE_BUZ_2
MOV A, #00H
MOV B, SECOND
CJNE A, B, UNEQUAL_LINE_BUZ_2
SETB BUZZER
UNEQUAL_LINE_BUZ_2:
MOV A, #05H
MOV B, SECOND
CJNE A, B, ABC
CLR BUZZER
ABC:CLR RS0
CLR RS1
RET
;***************************************************
TAB1: DB 'BELL TIME'
//CLOCK_HOUR_1: DB 11H
//CLOCK_HOUR_2: DB 12H
//CLOCK_MINUTE_1: DB 11H
//CLOCK_MINUTE_2: DB 12H
END