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
2
3
4
5
6
7
8
9
10
11
st=>start: 开始
op1=>operation: 设置堆栈SP
op2=>operation: 关中断,初始化
op3=>operation: 初始化定时器
op4=>operation: 置中断次数初值
op5=>operation: 初始化时间序列存储
op6=>operation: 开中断
op7=>operation: 拆分时间序列BCD
op8=>operation: 调用显示子程序

st->op1->op2->op3->op4->op5->op6->op7->op8->op7

定时器T0中断程序框图
1
2
3
4
5
6
7
8
9
10
11
st=>start: 中断子程序
op1=>operation: 关中断
cond=>condition: 中断次数到吗?
op2=>operation: 恢复中断次数初值
op3=>operation: 时间序列加一秒
op4=>operation: 开中断
e=>end: 返回

st->op1->cond
cond(no)->op4
cond(yes)->op2->op3->op4->e

时间序列加一秒子程序
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
op1=>operation: 取秒钟字节
op2=>operation: 加一,转BCD码,存储
con1=>condition: 是否到60秒?
op3=>operation: 秒数清零
op4=>operation: 取分钟字节
op5=>operation: 加一,转BCD码,存储
con2=>condition: 是否到60分?
op6=>operation: 分数清零
op7=>operation: 取时钟字节
op8=>operation: 加一,转BCD码,存储
con3=>condition: 是否到24时?
op9=>operation: 时数清零
e=>end: 返回

op1->op2->con1
con1(yes)->op3->op4->op5->con2
con2(yes)->op6->op7->op8->con3
con3(yes)->op9->e
con1(no)->e
con2(no)->e
con3(no)->e

BCD拆分子程序
1
2
3
4
5
6
7
8
9
10
11
op1=>operation: 取第一个字节
op2=>operation: 取高位
op3=>operation: 高低位转换,存入显示缓冲区
op4=>operation: 取低位,存显示缓冲区
op5=>operation: 指向下一个字节
con=>condition: 是否转换完?
e=>end: 返回

op1->op2->op3->op4->op5->con
con(yes)->e
con(no)->op2

显示子程序框图
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
st=>start: 显示子程序入口
op1=>operation: 置显示缓冲区首地址
op2=>operation: 置位码初值为20H
op3=>operation: 关显示
op4=>operation: 取缓冲区内容
op5=>operation: 取段码表首址
op6=>operation: 查表取段码
op7=>operation: 送段码、位码
op8=>operation: 短暂延时
op9=>operation: 缓冲区地址加一
op10=>operation: 位码右移一位
cond=>condition: 六位显示完?
e=>end: 返回

st->op1->op2->op3->op4->op5->op6->op7->op8->op9->op10->cond
cond(yes)->e
cond(no)->op4

3、还凑合的源代码

一些说明
  1. 本代码对应的单片机时钟晶振为12MHz
  2. 仿真机的6位8段数码管,段码口地址为8004H,位码口地址为8002H
  3. 20H 21H 22H 单元分别存储【时 分 秒】的BCD数值
  4. 30H~36H共6个单元作为显示缓冲区
  5. 由于指令的执行本身占据一定的机器周期,所以时钟每秒可能有微秒级的误差,后续可以使用示波器进行调试校正,修改代码
  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
    109
    	ORG 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 灭

    END

    4、关于数码管动态显示

    共阴数码管字符段码
    八段数码管外形

段码
段码

数码管动态显示原理

动态显示的原理是每一个数码管的公共端分别由一条 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(米斯达狂喜)