1、仔细想想还不错的思路
用定时器写一个时钟程序,想想都觉得头大。撇开其他花里胡哨的功能,先从最基本的时间显示开始吧,剩下的以后再说。
我们一般希望的是时钟能不停地计时,在需要的时候调用显示来显示当前时间,同时也可以干其他事,所以当然不能用延时来写,不然这个时钟除了只能显示时间之外就是个废物了。
- 我们希望每定时到一秒的时候来个中断,在中断里使存储里的时间序列加一秒(时间序列以时分秒各一个字节的方式存储)。51单片机定时器0方式1的最大定时时间是2^16^=65536μs,也就是大约65ms出头。但我们可以每50ms来一次中断,中断到第20次的时候时间序列加一秒;或者每20ms来一次中断,到第50次的时候时间序列加一秒。一般来说,尽量使单次定时的时间大一点可以减小误差,但我还是想用20ms为例QwQ
- 关于时间序列,在加一秒时,要能满足60秒进位分,满60分进位秒,满24时归零的功能。
- 显示部分,采用动态显示的方法,即调用显示子程序时,从左到右每一位轮流显示0.5ms的时间,不停循环,对人眼的视觉效果来说就是常亮的时间显示。
- 由于显示用的是6位八段数码管,所以显示之前又必须将时间序列(3字节的压缩BCD码)拆分成6位0XH存入显示缓冲区
理一遍思路,首先定时器设置20ms产生一次中断,每中断到第50次时调用子程序使存储中的时间序列加一秒。主程序中暂时只要进行拆分BCD→显示的循环操作即可(后续添加功能时就可以直接在主程序中添加)。
2、快进到画程序框图
注:flow语法在Typora里面可以显示的流程图不知为何在网页上渲染不出来,所以我直接在下面放了几张png,如果看不清可以直接copy代码到markdown编辑器中查看
主程序框图
1 | st=>start: 开始 |
定时器T0中断程序框图
1 | st=>start: 中断子程序 |
时间序列加一秒子程序
1 | op1=>operation: 取秒钟字节 |
BCD拆分子程序
1 | op1=>operation: 取第一个字节 |
显示子程序框图
1 | st=>start: 显示子程序入口 |
3、还凑合的源代码
一些说明
- 本代码对应的单片机时钟晶振为12MHz
- 仿真机的6位8段数码管,段码口地址为8004H,位码口地址为8002H
- 20H 21H 22H 单元分别存储【时 分 秒】的BCD数值
- 30H~36H共6个单元作为显示缓冲区
- 由于指令的执行本身占据一定的机器周期,所以时钟每秒可能有微秒级的误差,后续可以使用示波器进行调试校正,修改代码
- 目前只有时间显示的功能,其余功能待后续更新
源代码
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
109ORG 0000H
SJMP MAIN
ORG 000BH
LJMP SER0
ORG 0030H
MAIN:
MOV SP,#70H ;设置堆栈SP
CLR EA ;关中断
MOV 30H,#23H ;设置初始时间值
MOV 31H,#59H
MOV 32H,#55H
MOV TMOD,#01H ;使用定时器0,软件控制,定时模式,方式1
MOV TH0,0B1H ;定时初值:2^16-20ms/1us=45536=B1E0H
MOV TL0,0E0H
MOV R6,#50 ;设定计数次数50,作为中断次数计数的全局变量
SETB EA ;开中断
SETB ET0 ;允许T0中断
SETB TR0 ;启动定时器0
SHOW:
LCALL LOOSE ;调用子程序拆分BCD码并存入显示缓冲区
LCALL DISP ;调用显示子程序进行显示
LJMP SHOW
SER0: ;中断子程序
CLR EA ;关中断
MOV TH0,#0B1H ;再次填装定时初值
MOV TL0,#0E0H
DJNZ R6,EXIT ;计到50次就继续执行下面指令,否则出中断
MOV R6,#50 ;重置计数次数
PUSH ACC ;保护工作寄存器
PUSH PSW
CLR RS1 ;选择寄存器组01
SETB RS0
LCALL NUMINC ;调用子程序使时间值加一秒
POP PSW
POP ACC
EXIT:
RETI
LOOSE: ;拆分子程序,压缩BCD码->0XH
MOV R5,#03H ;总共3个字节,需要循环3次
MOV R0,#30H ;显示缓冲区首地址
MOV R1,#20H ;时间序列存储首地址
LOOP1:
MOV A,@R0
ANL A,#0F0H ;取高位
SWAP A
MOV @R1,A
INC R1
MOV A,@R0 ;存高位
ANL A,#0FH ;取低位
MOV @R1,A ;存低位
INC R0
INC R1
DJNZ R5,LOOP1
DISP: ;显示子程序
MOV R2,#20H ;位码初值
MOV R3,#06H ;总共6个位
MOV R0,#20H ;显示缓冲区首地址
SIX:
MOV R7,#250 ;延时自循环次数
MOV DPTR,#8002H ;位码口地址
MOV A,#00H
MOVX @DPTR,A ;关显示
MOV A,@R0
MOV DPTR,#DATA0
MOVC A,@A+DPTR ;查表取段码值
MOV DPTR,#8004H ;段码口首地址
MOVX @DATR,A ;段码值送段码口
MOV DPTR,#8002H
MOV A,R2
MOVX @DPTR A ;送位码,段码显示
DJNZ R7,$ ;原地循环R7次,起到延时效果
INC R0
RR A
MOV R2,A
DJNZ R3,SIX
RET
NUMINC: ;时间序列加一秒子程序
MOV R1,#32H ;时间序列末地址(秒)
MOV A,@R1
ADD A,#01H ;加1
DA A ;转BCD码
MOV @R1,A ;保存
CJNE @R1,#60H,TORET;若未到60秒,直接返回,否则执行下面程序
MOV @R1,#00H ;秒数清零
DEC R1 ;进行分钟位的操作
MOV A,@R1
ADD A,#01H
DA A
MOV @R1,A
CJNE @R1,#60H,TORET;若未到60分,直接返回,否则执行下面程序
MOV @R1,#00H ;分钟数清零
DEC R1 ;进行时数的操作
MOV A,@R1
ADD A,#01H
DA A
MOV @R1,A
CJNE @R1,#24H,TORET;若未到24时,直接返回,否则执行下面程序
MOV @R1,#00H ;小时数清零
TORET:
RET
DATA0: 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,00H ;A B C D E F 灭
END4、关于数码管动态显示
共阴数码管字符段码
数码管动态显示原理
动态显示的原理是每一个数码管的公共端分别由一条 I/O 输出控制,实现分时选通;而各个数码管的段相并联后,与一片锁存器的输出端相连接,当CPU通过数据总线输出欲显示的字符的编码,经锁存器锁存,此时,仅当由 I/O口输出选通的那一位才显示,其它的均不显示。为了使每位显示不同的字符,必须采用扫描方式,即在单位时间内,CPU通过 I/O口输出不同的数值,选通不同的位,逐一轮流显示(某一时刻只有一位显示);只要每位显示的时间间隔足够短,由于人眼视觉存在暂留现象,使得看起来每位均在显示各自的字符。为了稳定显示字符,CPU必须不断地刷新各个数码管,所以,这种显示方式虽然降低硬件成本,但要占用 CPU的时间,亮度也较低。
仿真机数码显示线路
上图为仿真系统的显示电路,8 位段码、6 位位码是由两片 74LS374 输出。
位码经 MC1413 或 ULN2003 倒相驱动后,选择相应显示位。U1 的输出 Q1~Q8 用于段码输出口(需要显示的数据送到该口);U2 的输出 Q1~Q8 中的低 6 位为位码输出口(即选通哪一位显示的输出口);当数码管需要显示某一字符时,该字符的段码通过数据总线送入 U1,经 74LS374 锁存送到数码管的 a~g、dp;然后,需要使哪个数码管显示的位码也通过数据总线送入 U2,也是通过 74LS374锁存控制6 个数码管,从左到右的位码分别为20H、10H、08H、04H、02H、01H。
5、没啥
没啥,就凑个5(米斯达狂喜)
- 本文链接:http://kuuhaku.top/posts/CLOCKofMCS51/
- 版权声明:本博客所有文章除特别声明外,均默认采用 许可协议。