发一下去年在Proteus上随手做的一个秒表


使用说明

  • 待机状态下按 继续 按钮开始正计时,计到60秒自动停止

  • 待机状态下按 倒计时 按钮从60秒开始倒计时

  • 正(倒)计时中按 暂停 按钮暂停计时,按 继续 按钮之后继续正(倒)计时

  • 正计时中按 暂停 按钮暂停计时,此时按 倒计时 按钮会从当前值开始倒计时

  • 正计时运行状态下按 清零,计数值清零,秒表继续从零计时;正计时暂停状态下按 清零 ,计数值清零,秒表保持暂停

  • 退出 按钮,计数值清零,秒表回归待机状态

电路说明

按键部分

P1口作按钮输入口,按钮不按时由于接了上拉电阻和VCC,P1.0~P1.4都处于高电平,当有按钮按下时,对应的位由于接地会变成低电平,所以读取P1口的值就可以判断是哪一个按钮按下。

显示部分

P0口作为段码输出口,这里用的是共阴极数码管,即要显示某一段管子要将对应的位置高电平,因此每一个数字会有对应的段码。

共阴极对应的段码如下:

其他部分

用于产生12MHz的晶振,使得定时器计数值加1的时候刚好对应的时间是加1μs

实际效果

这里放一个十几秒的正计时与暂停演示视频

主程序框图

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
st=>start: 开始
sp=>operation: 初始化设置,定时器初始设置
neicun=>operation: 秒表计数值内存初始值置0,标志位置0
loose=>subroutine: 将内存中秒与毫秒两个字节的压缩BCD码拆分成4个字节存到显示缓冲区中
disp=>subroutine: 调用显示,将显示缓冲区中的数字在数码管上显示出来
isclear=>condition: 清除按钮按了?
ispause=>condition: 暂停按钮按了?
iscontinue=>condition: 继续按钮按了?
isexit=>condition: 退出按钮按了?
isdaojishi=>condition: 倒计时按钮按了?
clear=>subroutine: 调用清除按钮子程序
pause=>subroutine: 调用暂停按钮子程序
continue=>subroutine: 调用继续按钮子程序
exit=>subroutine: 调用退出按钮子程序
daojishi=>subroutine: 调用倒计时按钮子程序
st->sp->neicun->loose->disp->isclear
isclear(no)->ispause
ispause(no)->iscontinue
iscontinue(no)->isexit
isexit(no)->isdaojishi
isdaojishi(no)->loose
isclear(yes)->clear->loose
ispause(yes)->pause->loose
iscontinue(yes)->continue->loose
isexit(yes)->exit->loose
isdaojishi(yes)->daojishi->loose

源代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
$NOMOD51
$INCLUDE (8051.MCU)

;================================================================
;内存使用备注
;30H 存储秒表秒位
;31H 存储秒表毫秒位
;32H ~35H 显示缓冲区,放置带显示的数字
;36H~37H 存储定时器暂停时的计数值
;==================================================================

; Reset Vector
org 0000H
jmp Start

;====================================================================
; CODE SEGMENT
ORG 000BH ;定时器0中断入口地址
LJMP TIME0

RUNNING BIT 20H.0 ;运行标志位,置1表示秒表正在运行,置0表示处于暂停或退出状态
DAOJISHI BIT 20H.1 ;倒计时标志位,置1表示秒表运行在倒计时模式

LEDTAB: ;段码表,用于数码管显示
DB 3FH,06H,5BH,4FH,66H,6DH,7DH,07H,7FH,6FH ;0 1 2 3 4 5 6 7 8 9
DB 77H,7CH,39H,5EH,79H,71H ;A B C D E F
;====================================================================

org 0030h
Start:
MOV SP,#6FH ;设置堆栈指针起始地址
CLR EA ;关中断
MOV TMOD,#01H ;设置定时器0工作在方式1(16位定时器)
MOV TH0,#03CH ;设置定时初值,65536-3CB0H=50000μs 即每50ms产生一次中断
MOV TL0,#0B0H
MOV 36H,TH0 ;保存定时器初值
MOV 37H,TL0
SETB ET0 ;定时器中断允许
MOV 30H,#00H ;内存中的秒位
MOV 31H,#00H ;内存中的毫秒位
CLR RUNNING ;初始时刻标志位置0
CLR DAOJISHI
MAIN:
LCALL LOOSE ;调用子程序,压缩BCD码拆分成两个0XH,保存到显示缓冲区
LCALL DISP ;调用显示子程序,将显示缓冲区的数字显示到数码管上
JNB P1.0,TOCLEAR ;判断清除按钮是否按下
JNB P1.1,TOPAUSE ;判断暂停按钮是否按下
JNB P1.2,TOCONTINUE ;判断继续按钮是否按下
JNB P1.3,TOEXIT ;判断退出按钮是否按下
JNB P1.4,TOCOUNTDOWN ;判断倒计时按钮是否按下
SJMP MAIN ;循环主程序段

TOCLEAR: LCALL CLEAR ;调用清除子程序
SJMP MAIN
TOPAUSE: LCALL PAUSE ;调用暂停子程序
SJMP MAIN
TOCONTINUE: LCALL CONTINUE ;调用继续子程序
SJMP MAIN
TOEXIT: LCALL EXIT ;调用退出子程序
SJMP MAIN
TOCOUNTDOWN: LCALL COUNTDOWN ;调用倒计时子程序
SJMP MAIN






;【0 TIME0】
;===================================
;中断子程序,负责内存区数值的加或减
;===================================
TIME0:
PUSH ACC ;压栈,保护寄存器A
JB DAOJISHI,SUBNUM ;若倒计时标志位为1,说明处于倒计时状态,跳转倒计时程序段
ADDNUM:
;正计时程序段
MOV TH0,#03CH ;重装定时器初值
MOV TL0,#0B0H
MOV A,31H ;取毫秒位到A
CLR C ;清除进位标志C
ADD A,#05H ;毫秒位数值加5
DA A ;转BCD格式
MOV 31H,A ;保存结果
MOV A,30H ;取秒位
ADDC A,#00H ;A=A+00H+C,当上一步的加法产生进位时C会置1,在这里把这个进位加上
DA A
MOV 30H,A
CJNE A,#60H,TORETI ;若未计到60秒,可返回;否则执行下面的程序
CLR EA ;若计到60秒,则要关中断
CLR TR0 ;停止定时器
MOV 31H,#00H ;毫秒位清零
SJMP TORETI ;出程序
SUBNUM:
;倒计时程序段
MOV TH0,#03CH
MOV TL0,#0B0H
MOV A,31H
CLR C
ADD A,#95H ;这里有点难解释,总之就是把减5的操作转换成为加5的十进制补码,运算结果等价于减5
DA A ;若有借位,C置0,无借位C置1
MOV 31H,A
CPL C ;将C取反好办事
MOV A,#9AH
SUBB A,#00H ;A=A-00H-C,意思是,如果上一步有借位,就取1的十进制补码99H,否则取0的十进制补码9AH
MOV B,A
MOV A,30H
ADD A,B ;如果有借位,这里秒数会减1
DA A
MOV 30H,A
CJNE A,#00H,TORETI ;若秒数为0,则继续向下执行,否则TORETI
MOV A,31H
CJNE A,#00H,TORETI ;若毫秒数也为0,则继续向下执行,否则TORETI
CLR EA ;如果到了这一步,说明倒计时到0了,需要关中断和关定时器
CLR TR0
TORETI:
POP ACC ;A 出栈
RETI






;【1 LOOSE】
;============================================================
;字节拆分子程序,实现的功能是把30H和31H两个单元的字节高四位和低四位拆开
;存到32H~35H这4个单元当中,保存格式为0XH
;============================================================
LOOSE:
MOV R0,#30H ;压缩BCD码的首地址
MOV R1,#32H ;显示缓冲区的首地址
MOV R2,#2 ;要处理的字节数
LLOOP:
MOV A,@R0
ANL A,#0F0H ;取高4位
SWAP A ;高低4位互换
MOV @R1,A ;存高4位
INC R1 ;指向下一个显示缓冲区单元
MOV A,@R0
ANL A,#0FH ;取低4位
MOV @R1,A ;存低4位
INC R0 ;指向下一个压缩BCD码存储单元
INC R1 ;指向下一个显示缓冲区单元
DJNZ R2,LLOOP





;【2 DISP】
;============================================================
;显示子程序,作用是把显示缓冲区的数字显示到数码管上
;采用动态显示的原理,P0口作为段码口,输送要显示的数字的段码
;P2作为位码口,将想要显示的那一位置低电平,其余都置高电平
;四个管子外加中间一个小数点轮流显示一遍,每个显示设置250微秒的延时
;由于视觉残像的原理,看起来就跟同时在亮一样,但其实它们是轮流亮的
;============================================================
DISP:
MOV R0,#32H ;显示缓冲区首地址
MOV R2,#4 ;总共要显示4位
MOV R3,#0FEH ;初始位码
MOV R4,#250 ;延时时间250微秒
SINGLE:
MOV P2,#0FFH ;先清显示
MOV DPTR,#LEDTAB ;取段码表基地址
MOV A,@R0 ;取偏移地址
MOVC A,@A+DPTR ;将表中对应的段码取出存到A中
MOV P0,A ;送段码
MOV P2,R3 ;送位码,数码管显示该位
DJNZ R5,$ ;延时
INC R0 ;下一位
MOV A,R3
RL A ;位码向左移动一位,下次显示右一个数码管
MOV R3,A
MOV R4,#250
DJNZ R2,SINGLE ;循环4次
MOV P0,#80H ;送中间小数点的段码
MOV P2,#0FDH ;送第二个数码管的位码
DJNZ R5,$ ;延时
MOV P2,#0FFH ;关显示
RET





;【3 CLEAR】
;================================================================
;清除按钮功能子程序
;将当前的计数值清零,如果是倒计时则会让计时停止
;如果是正计时,则只清零,不影响秒表的状态,即计时中按下清零,会继续从零计时
;================================================================
CLEAR:
MOV 30H,#00H ;内存区数值清零
MOV 31H,#00H
JNB DAOJISHI,WAIT0 ; 如果不是倒计时,直接转WAIT0
CLR DAOJISHI ;倒计时标志位置0(关倒计时)
CLR RUNNING ;运行标志位置0(停止秒表计数)
CLR TR0 ;关中断
CLR EA ;关定时器
WAIT0:
JNB P1.0,WAIT0 ;这一段作用是等待按键释放,当按钮释放之后才会继续往下
RET




;【4 PAUSE】
;==================================================================
;暂停按钮子程序
;作用是使计时暂停
;==================================================================
PAUSE:
MOV 36H,TH0 ;将现在定时器的计数值保存下来,以备继续时可以使用
MOV 37H,TL0
CLR TR0
CLR EA
CLR RUNNING
WAIT1:
JNB P1.1,WAIT1
RET




;【5 CONTINUE】
;======================================================================
;继续按钮,可以让待机状态的秒表正计时,或者让暂停的秒表继续正计时(倒计时)
;======================================================================
CONTINUE:
JB RUNNING,WAIT2 ;如果秒表已经在运行了,直接转WAIT2
MOV TH0,36H ;装载上次暂停时记录下来的定时器计数值,确保一定从从上暂停的地方开始计时
MOV TL0,37H
SETB TR0 ;开中断
SETB EA ;开定时器
SETB RUNNING ;运行标志位置1
WAIT2:
JNB P1.2,WAIT2
RET



;【6 EXIT】
;==========================================================================
;退出按钮子程序,计数值清零,退出秒表运行,回归初始状态
;===========================================================================
EXIT:
;关中断,关定时器,清标志位,内存清零
CLR TR0
CLR EA
CLR RUNNING
CLR DAOJISHI
MOV 30H,#00H
MOV 31H,#00H
WAIT3:
JNB P1.3,WAIT3
RET



;【7 COUNTDOWM】
;=========================================================================
;倒计时按钮子程序,待机时按下会从60秒开始倒计时,暂停时按下会从当前值开始倒计时
;=========================================================================
COUNTDOWN:
JB RUNNING,WAIT4 ;如果秒表已经在运行状态,直接转WAIT4
MOV A,30H
ADD A,31H ;将秒的数值与毫秒的数值加起来
JNZ GOAHEAD ;秒加毫秒为0,说明当前处于待机状态,继续往下,否则转GOAHEAD
MOV 30H,#60H ;装载倒计时初值60秒
MOV 31H,#00H
GOAHEAD:
SETB TR0 ;开定时器
SETB EA ;开中断
SETB DAOJISHI ;倒计时标志位置1
SETB RUNNING ;运行标志位置1
WAIT4:
JNB P1.4,WAIT4
RET





;====================================================================
END