学校教学用的是汇编,正好之前也没有怎么学过,一起学一学

C51

基本操作

demo1

计算1到5的和,并把结果存储在SUM这个地址中。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
    ;定义两个地址常量
INDEX EQU 20H ;索引寄存器,用于循环计数
SUM EQU 21H ;和寄存器,用于存储结果

;定义程序起始地址
ORG 0000H
START: ;程序开始标签
MOV INDEX, #5 ;把5赋值给索引寄存器
MOV A, #0 ;把0赋值给累加器A
LOOP: ;循环开始标签
ADD A, INDEX ;把索引寄存器的值加到累加器A上
DJNZ INDEX, LOOP ;索引寄存器减1,如果不为0则跳转到循环开始标签
MOV SUM, A ;把累加器A的值赋值给和寄存器
SJMP $ ;无限循环,程序结束
END ;程序结束标签
  • EQU是英文单词equal的缩写,用于定义一个符号常量,不占用存储空间12。比如,COUNT EQU 100就是把100赋值给符号名COUNT,以后在程序中使用COUNT就相当于使用100。
  • INDEX是一个符号名,可以用EQU或其他方式定义,用于表示一个地址或一个数值。比如,INDEX EQU 20H就是把20H赋值给符号名INDEX,以后在程序中使用INDEX就相当于使用20H。
  • DJNZ是一个指令助记符,表示递减并跳转(Decrement and Jump if Not Zero)。它的格式是DJNZ Rn, label或DJNZ direct, label,表示把寄存器Rn或直接寻址单元的内容减1,如果结果不为0,则跳转到标签label处执行3。比如,DJNZ INDEX, LOOP就是把索引寄存器INDEX的内容减1,如果不为0则跳转到循环开始标签LOOP处执行。
  • ORG是英文单词Origin的缩写,表示起始地址或源。它是一个伪指令,用于指定程序的起始偏移地址12。比如,ORG 2000H就是把2000H作为程序的起始偏移地址。

demo2

赋值

1
2
3
4
5
6
ORG 	0000H
CLR A
MOV DPTR, #200AH
MOVX @DPTR, A
SJMP $
END

demo3

批量填充数据

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
    ; 声明一个常量,表示要填充的数据
DATA EQU 9CH
; 声明一个常量,表示要填充的起始地址
START EQU 2010H
; 声明一个常量,表示要填充的单元个数
COUNT EQU 256

; 设置程序入口点
ORG 0000H
; 跳转到主程序
AJMP MAIN

; 主程序
MAIN:
; 将要填充的数据送入累加器
MOV A, #DATA
; 将要填充的起始地址送入DPTR寄存器
MOV DPTR, #START
; 将要填充的单元个数送入R0寄存器
MOV R0, #COUNT

; 循环开始
LOOP:
; 将累加器中的数据写入DPTR指向的单元
MOVX @DPTR, A
; DPTR加1,指向下一个单元
INC DPTR
; R0减1,表示剩余的单元个数
DJNZ R0, LOOP

; 循环结束,停止运行
STOP:
SJMP STOP
END ; 程序结束

中断

硬件连接

一个数码管是由八个发光二进管组成,用跳线将数码管位选 S1~S8 连接到 P1.0~P1.7,段选 A~H 接到单片机 IO 口 P2.0~P2.7。拨动开关 1~8 接到单片机 IO 口 P0.0~P0.7。将 EA 用跳线上拉到 VCC,程序从片内启动。

demo4

中断

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
	ORG 	0
AJMP STAR

ORG 0003H ;中断入口地址:INT0:0003H; TIM0:000BH
;INT1:0013H; TIM1:001BH; UART:0023H
RL A
MOV P2,A
RETI ;中断返回函数
STAR:
MOV P1,#0FBH ;第三个数码管亮
MOV A,#0FEH
MOV P2,A
SETB EA ;置 EA=1
SETB EX0 ;允许 INT0 中断
SETB IT0 ;边缘触发中断
SJMP $

demo5

记录并显示 INT0 中断次数(中断次数<16 次)

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
	ORG 	0
AJMP NT
ORG 0003H
AJMP INTOR
NT:
MOV IE,#81H ;允许 INT0 中断,置 EA=1
SETB IT0 ;边沿触发中断
MOV R0,#0 ;计数初值为 0
BIOU:
MOV P1,#0FEH ;第一个数码管显示中断次数
MOV DPTR,#TAB0 ;字形码表送 DPTR
MOV A,R0
MOVC A,@A+DPTR ;查表
MOV P2,A ;显示
SJMP $ ;结束
INTOR:
INC R0 ;中断次数加 1
CJNE R0,#010H,RET0 ;中断是否满 15 次
MOV R0,#0 ;循环
RET0:
POP DPH ;从堆栈弹出断点地址
POP DPL
MOV DPTR,#BIOU
PUSH DPL ;修改中断返回点
PUSH DPH
RETI
TAB0:
DB 0C0H,0F9H,0A4H,0B0H,99H,92H,82H,0F8H,80H,90H,88H,83H,0C6H,0A1H,86H,8EH,0FFH
END

demo6

选中第一个数码管并使其8个led轮流点亮

1
2
3
4
5
6
7
8
9
10
11
12
13
14
    ORG 0000H
MOV P1,#0xFE
MOV P2,#00H
MOV A,#0xFE
NEXT:
MOV P2,A
MOV R3,#0
LOOP:
MOV R4,#0
DJNZ R4,$
DJNZ R3,LOOP
RL A
SJMP NEXT
END

demo7

使六个数码管,每个的a、b、c、d段,共24段LED各段轮流亮。

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
	NUM 	EQU 6 ; 6个数码管
SEG EQU 4 ; 4个发光二极管
DELAY EQU 0 ; 延时为0
ORG 0000H
AJMP MAIN ; 跳转

MAIN:
MOV P1, #0xFE ;初始化和重置
MOV A, #0xFE
MOV P2, #00H
MOV R0, #NUM ; 每次重置R0

;大循环遍历数码管
LOOP:
MOV R1, #SEG ;每次大循环重置小循环R1
MOV B, A ;转存
MOV A, #0xFE ;重置A用于P2
;小循环遍历二极管
SUB_LOOP:
MOV P2 A
LCALL DELAY_SUB ; 调用延时子程序
RL A
DJNZ R1, SUB_LOOP ; 循环判断
;移动P1
MOV A, B
RL A
MOV P1 A ; 选择下一个数码管
DJNZ R0, LOOP ;R0自减1,如果不为0,跳转到循环开始,否则继续执行
SJMP MAIN ; 跳转

; 延时子程序
DELAY_SUB:
MOV R3,#0
DELAY_LOOP:
MOV R4,#0
DJNZ R4,$
DJNZ R3,DELAY_LOOP
RET ; 返回

END ; 程序结束

demo8

拨动几号开关置“0N”第一个数码管显示几。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
	ORG 0
MOV DPTR,#TAB0
MOV P1,#0FEH
STA1:
SETB C
MOV R0,#01
ASP:
MOV P0,#0FFH
MOV A,P0
ASP1:
RRC A
JNC LED ;检测是那个开关置“ON”
INC R0
CJNE R0,#9,ASP1
SJMP STA1
LED:
MOV A,R0 ;R0 为开关号
MOVC A,@A+DPTR
MOV P2,A
SJMP STA1
TAB0:
DB 0C0H,0F9H,0A4H,0B0H,99H,92H,82H,0F8H,80H,90H,88H,83H,0C6H,0A1H,86H,8EH,0FFH
END

这里需要注意的是ASP函数,是从P0口读取八个开关的情况。

MOV P0,#0FFH这句是将P0口的八个引脚都写为1,表示高阻输入。这样可以从P0口读取八个开关的状态,对应位为 0 就意味着ON状态。

MOV A,P0这句是将P0口的状态值送入A寄存器,然后用RRC A指令来带上C位循环右移A寄存器,便可以通过检测C标志位来判断哪个开关置ON。

至于用#0FF赋值和用#FF赋值效果是一样的,不过加个零可能是从形式上做出某种区分?

demo9

数码管跑马程序

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
	ORG 0
DLED:
MOV R0,#0H ;R0 存字形表偏移量
WE:
MOV A,#0FEH ;A 置数码管位选码
NEXT:
MOV B,A ;保存位选码
MOV P1,A
MOV DPTR,#TAB0 ;DPTR 置字形表头地址
MOV A,R0
MOVC A,@A+DPTR ;查字形码表
MOV P2,A ;送 P2 口输出
MOV R3,#0 ;延时
LOP:
MOV R4,#0
LOP1:
NOP
NOP
DJNZ R4,LOP1
DJNZ R3,LOP
MOV A,B
RL A ;指向下一位
CJNE A,#0FEH,NEXT ;8 个数码管显示完否
INC R0 ;指向下一位字形
CJNE R0,#10H,WE ;从 0 到 F 显示完否
SJMP DLED
TAB0:
DB 0C0H,0F9H,0A4H,0B0H,99H,92H,82H,0F8H,80H,90H,88H,83H,0C6H,0A1H,86H,8EH,0FFH
END

demo10

置1键ON六个数码管轮流亮“1”,拨2键六个数码管轮流亮“2”,拨3键六个数码管轮流亮“3”。

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
	NUM EQU 6 ; 6个数码管
DELAY EQU 0 ; 延时寄存器初值0
TAB0 EQU 0020H ; 字形码表地址

ORG 0000H
MOV DPTR,#TAB0
AJMP MAIN ; 跳转

MAIN:
MOV P1, #0FEH ; 选择第一个数码管
MOV P2, #0FFH ; 关闭所有发光二极管
MOV R0, #NUM
MOV A, #0FEH ; 11111110
LOOP:
;段选
MOV B, A
MOV P1,A

MOV A, P0 ; A = P0读取
ANL A, #07H ; 屏蔽高五位,只保留低三位
CASE1:
CJNE A, #01H, CASE2 ; 如果是置1键不等于ON,转到下一个情况,否则就直接展示
SJMP DISPLAY ;展示

CASE2:
CJNE A, #02H, CASE3
SJMP DISPLAY

CASE3:
CJNE A, #04H, CASE4
SJMP DISPLAY

CASE4:
MOV A, #00H ; 显示0
SJMP DISPLAY

DISPLAY:
;位选
MOVC A,@A+DPTR
MOV P2, A ; 将字形码送入P2口,表示显示对应的数
LCALL DELAY_SUB ; 调用延时子程序

MOV A,B
RL A
; R0自减1,如果不为0,跳转到循环开始,否则继续执行
DJNZ R0, LOOP ; 完成一个小循环:单个数码管根据开关显示数字
SJMP MAIN ; 完成大循环:六个数码管均完成小循环。跳转MAIN, reset NUM 和段选的A

; 延时子程序
DELAY_SUB:
MOV R3,#0
DELAY_LOOP:
MOV R4,#0
DJNZ R4,$
DJNZ R3,DELAY_LOOP
RET ; 返回

TAB0:
DB 0C0H,0F9H,0A4H,0B0H,99H,92H,82H,0F8H,80H,90H,88H,83H,0C6H,0A1H,86H,8EH,0FFH

END ; 程序结束

TAB0 EQU 0020H 在最开始将字形码表地址设置为了0020H ,但是这个地址可能会和内部RAM的地址冲突,因为内部RAM的地址范围是0000H-007FH。可以把TAB0地址改成一个较大的值,比如0100H,以免造成数据混乱。

JC: 如果进位位 ©为1,就跳转到指定的地址,否则顺序执行。

JNC: 如果进位位 ©为0,就跳转到指定的地址,否则顺序执行。

CJNE: 比较两个操作数,如果不相等,就跳转到指定的地址,并根据比较结果设置进位位 ©;否则顺序执行。如果前面的数大,则©被赋 0后面的数大就赋 1

DJNE:DJNZ R3,DELAY_LOOP 即为将R3值减一,等于零则执行下一条语句,否则跳转到DELAY_LOOP

demo11

将开关1~8的置位情況显示在数码管上,开关置“0N”的对应数码管显“0”,开关置“0FF” (拨向下的对应数码管显“1”)。

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
	NUM EQU 8 ; 8个数码管
DELAY EQU 0

ORG 0000H
AJMP MAIN ; 跳转

MAIN:
MOV P1, #0FEH ; 选择第一个数码管
MOV P2, #0FFH ; 关闭所有发光二极管
MOV R0, #NUM ;小循环次数:已点亮数码管数
;获取P0口开关情况
MOV P0,#0FFH
MOV A, P0
MOV R1, A ;记录P0口开关情况
MOV A, #0FEH ; 11111110

;展示八个数码管
LOOP:
;段选
MOV B, A
MOV P1,A
;位选
MOV A, R1
RLC A ; 循环左移A寄存器,并将最高位送入C标志位
JC ZERO ; 如果C标志位为1,表示开关置“ON”,跳转到ZERO
ONE:
MOV P2, #F9H ; 如果C标志位为0,表示开关置“OFF”,显示数字“1”
SJMP NEXT
ZERO:
MOV P2, #C0H ;显示数字“0”
NEXT:
LCALL DELAY_SUB ; 调用延时子程序

;段选,换到下一个数码管
MOV A, B
RL A
; R0自减1,如果不为0,跳转到循环开始,否则继续执行
DJNZ R0, LOOP ; 循环判断
; 循环结束,跳转到主程序开始处,重复执行
SJMP MAIN ; 跳转

; 延时子程序
DELAY_SUB:
MOV R3,#0
DELAY_LOOP:
MOV R4,#0
DJNZ R4,$
DJNZ R3,DELAY_LOOP
RET ; 返回

END ; 程序结束

外部中断接线

用跳线将轻触按键 KEY1 连接到单片机的 IO 口 P3.2,即连向 INT0,脉冲源向单片机的外部中断INT0 引脚提供中断所需的脉冲,每按一次开关 KEY1,电平变换一次,产生一个跳变源,作为外部中断INT0 的中断请求信号

demo12

记录并显示 INT0 中断次数(中断次数<16 次)

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
	ORG 0
AJMP NT
ORG 0003H
AJMP INTOR

NT:
MOV IE,#81H ;允许 INT0 中断,置 EA=1
SETB IT0 ;边沿触发中断
MOV R0,#0 ;计数初值为 0
BIOU:
MOV P1,#0FEH ;第一个数码管显示中断次数
MOV DPTR,#TAB0 ;字形码表送 DPTR
MOV A,R0
MOVC A,@A+DPTR ;查表
MOV P2,A ;显示
SJMP $ ;结束
INTOR:
INC R0 ;中断次数加 1
CJNE R0,#010H,RET0 ;中断是否满 15 次
MOV R0,#0 ;循环
RET0:
POP DPH ;弹出断点
POP DPL
MOV DPTR,#BIOU
PUSH DPL ;修改中断返回点
PUSH DPH
RETI
TAB0:
DB 0C0H,0F9H,0A4H,0B0H,99H,92H,82H,0F8H,80H,90H,88H,83H,0C6H,0A1H,86H,8EH,0FFH
END

这里面有一个修改中断函数返回点的操作RET0

需要知道的是,DPHDPL就是DPTR的两个八位寄存器,分别存放DPTR的高八位低八位。这里将堆栈中保存的原来的程序计数器PC的高低字节弹出到DPH和DPL寄存器中(不过原来是多少倒是不关心)。就是把BIOU标号的地址送入DPTR寄存器,直接覆盖之前的地址,然后把DPTR寄存器的高低字节压入堆栈中。这样就相当于把堆栈中保存的原来的PC值改成了BIOU标号的地址。

一般来说,当我们按下按键触发中断的时候程序在BIOU 里的SJMP $语句,不修改返回点的话中断完了就返回到这里。

demo13

用8XX51并行口接的三个数码管,显示INT0中断次数, 显示次数超过255后清零,重新计数

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
	ORG 0
AJMP NT
ORG 0003H
AJMP INTOR

INIT:
MOV IE,#81H ;允许 INT0 中断,置 EA=1
SETB IT0 ;边沿触发中断
MOV R0,#0 ;计数初值为 0,高八位
MOV R1,#0 ;低八位
BIOU:
MOV P1,#0FEH ;第一个数码管
MOV DPTR,#TAB0 ;字形码表送 DPTR
MOV A,R0
MOVC A,@A+DPTR ;查表
MOV P2,A ;显示
LCALL DELAY_SUB ; 调用延时子程序

MOV P1,#0FDH ;第二个数码管
MOV DPTR,#TAB0 ;字形码表送 DPTR
MOV A,R1
MOVC A,@A+DPTR ;查表
MOV P2,A ;显示
LCALL DELAY_SUB
SJMP BIOU

INTOR:
INC R0 ;中断次数加 1
CJNE R0,#010H,RET0 ;中断是否满 15 次
MOV R0,#0
INC R1 ;进一位
CJNE R1,#010H,RET0
MOV R1,#0
RET0:
POP DPH ;弹出断点
POP DPL
MOV DPTR,#BIOU
PUSH DPL ;修改中断返回点
PUSH DPH
RETI

; 延时子程序
DELAY_SUB:
MOV R3,#0
DELAY_LOOP:
MOV R4,#0
DJNZ R4,$
DJNZ R3,DELAY_LOOP
RET ; 返回

TAB0:
DB 0C0H,0F9H,0A4H,0B0H,99H,92H,82H,0F8H,80H,90H,88H,83H,0C6H,0A1H,86H,8EH,0FFH
END

定时器

接线

用跳线将轻触按键 KEY1 连接到单片机的 IO 口 P3.4(即连向 T0 方向), P3.4 和脉冲源相连,脉冲源向单片机的定时计数器 0 提供外部计数脉冲,每按一次按键 KEY1,产生一个跳变源

demo14

査询方式计数外部脉冲,计两个脉冲, LED 显示段加 1

1
2
3
4
5
6
7
8
9
10
11
12
13
14
	ORG 0000H
MOV TMOD,#06 ;计数器方式 2
MOV TH0,#0FEH
MOV TL0,#0FEH ;计两个脉冲
SETB TR0 ;启动定时器
MOV P1,#0F0H
MOV A,#0FFH
COUN:
JNB TF0,$ ;等待计满两个脉冲
CLR TF0 ;中断标志
DEC A
MOV P2,A
SJMP COUN
END

定时器0被设置为计数器方式2,即自动重装载方式。这种方式下,当TL0溢出时,会自动从TH0中重新装入初始值,并产生一个中断请求,TF0会置一,跳出JNB TF0,$循环。

demo15

中断方式计数外部脉冲,计两个脉冲中断一次 LED 显示段加一。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
	ORG 0000H
AJMP COUN

;中断函数
ORG 00BH
DEC A
MOV P2,A
RETI
COUN:
MOV TMOD,#06H
MOV TH0,#0FEH
MOV TL0,#0FEH
SETB EA ;总中断允许
SETB ET0;T0中断允许
SETB TR0
MOV P1,#0F0H
MOV A,#0FFH
MOV P2,A
SJMP $
END

demo16

利用T0定时,使数码管的“8”字每隔100MS顺次亮下一个。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
	ORG 0000H
AJMP MAIN
MOV TMOD,#01H ;计数方式1
MOV TH0,#04CH
MOV TL0,#000H

SETB TR0 ;启动定时器
MOV P1,#0FEH
MOV A,#0FEH
MOV P2,#80H ;数字8
LOOP:
MOV R0 ,#2
T0_LOOP:
MOV TH0,#04CH
MOV TL0,#000H ;重新装载初值
JNB TF0,$ ;等待中断
CLR TF0 ;中断标志
DJNZ R0,T0_LOOP
RL A
MOV P1 A
MOV P2,#80H
SJMP LOOP
END

使用计数方式1(每次需要手动重新赋初值),计数器位数为 16,晶振频率为 11.0592MHz,那么一个机器周期为1.08507us, 即使初值全设为0也只有71.11ms的时长,无法实现100ms的定时。所以需要再加一个R0实现循环定时。这里让他计数 46080 次,即50ms,那么R0=2。设初值为65536-46080=19456,即MOV TH0,#04CH , MOV TL0,#000H

demo17

利用8XX51做一个秒表,基础计时百分之一秒,满100,记1秒,key1键可实现秒表开始计时,暂停,及继续,key2键可实现秒表任意时刻计数值清零。秒表满量程99秒,溢出后自动清零,重新开始。

这是newbing写的,懒得看了

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
;定义数码管显示的字形码表
LED_CODE: DB 3FH, 06H, 5BH, 4FH, 66H, 6DH, 7DH, 07H
DB 7FH, 6FH, 77H, 7CH, 39H, 5EH, 79H, 71H
;定义数码管显示的位选码表
LED_POS: DB 0FEH, 0FDH, 0FBH, 0F7H
;定义按键扫描的位掩码表
KEY_MASK: DB 0EFH, 0DFH

ORG 0000H ;设置程序起始地址为0000H
MOV TMOD,#01H ;设置定时器T0为工作方式1,16位定时器
MOV TH0,#0FFH ;给定时器T0装初值,定时10ms
MOV TL0,#0D9H
SETB EA ;开总中断
SETB ET0 ;开T0中断

MAIN: MOV DPTR,#LED_CODE ;设置DPTR指向字形码表
MOV R2,#00H ;设置R2为数码管显示的百分之一秒位的索引,初始为00H
MOV R3,#00H ;设置R3为数码管显示的秒位的索引,初始为00H
MOV R4,#00H ;设置R4为数码管显示的十秒位的索引,初始为00H
MOV R5,#01H ;设置R5为按键扫描的状态标志,初始为01H(开始计时)
MOV R6,#00H ;设置R6为按键扫描的延时计数器,初始为00H

LOOP: CALL SCAN_KEY ;调用按键扫描子程序
CALL SHOW_LED ;调用数码管显示子程序
SJMP LOOP ;无限循环

SCAN_KEY: PUSH ACC ;保存累加器内容
PUSH PSW ;保存程序状态字内容
MOV DPTR,#KEY_MASK ;设置DPTR指向位掩码表
MOV A,R5 ;将按键状态标志送入累加器A
ANL A,#01H ;与01H进行逻辑与运算,取出最低位(开始/暂停标志)
JZ SCAN_KEY1 ;如果最低位为0,则跳转到SCAN_KEY1(暂停状态)
CLR P3.2 ;如果最低位为1,则清除P3.2(开始状态)
SETB P3.3 ;置位P3.3(开始状态)
ACALL DELAY_KEY ;调用按键延时子程序
JB P1.4,NEXT1 ;如果P1.4为高电平,则跳转到NEXT1(未按下key1)
CPL R5.0 ;如果P1.4为低电平,则反转R5.0(按下key1)
NEXT1: JB P1.5,NEXT2 ;如果P1.5为高电平,则跳转到NEXT2(未按下key2)
MOV R2,#00H ;如果P1.5为低电平,则清零R2(按下key2)
MOV R3,#00H ;清零R3(按下key2)
MOV R4,#00H ;清零R4(按下key2)
NEXT2: POP PSW ;恢复程序状态字内容
POP ACC ;恢复累加器内容
RET ;返回主程序

SCAN_KEY1: SETB P3.2 ;如果最低位为0,则置位P3.2(暂停状态)
CLR P3.3 ;清除P3.3(暂停状态)
ACALL DELAY_KEY ;调用按键延时子程序
JB P1.4,NEXT3 ;如果P1.4为高电平,则跳转到NEXT3(未按下key1)
CPL R5.0 ;如果P1.4为低电平,则反转R5.0(按下key1)
NEXT3: JB P1.5,NEXT4 ;如果P1.5为高电平,则跳转到NEXT4(未按下key2)
MOV R2,#00H ;如果P1.5为低电平,则清零R2(按下key2)
MOV R3,#00H ;清零R3(按下key2)
MOV R4,#00H ;清零R4(按下key2)
NEXT4: POP PSW ;恢复程序状态字内容
POP ACC ;恢复累加器内容
RET ;返回主程序

DELAY_KEY: PUSH ACC ;保存累加器内容
PUSH PSW ;保存程序状态字内容
INC R6 ;将R6自增一
CJNE R6,#20,NEXT5; 如果R6不等于20,则跳转到NEXT5(未达到延时要求)
CLR R6; 如果R6等于20,则清零R6(达到延时要求)
NEXT5: POP PSW; 恢复程序状态字内容
POP ACC; 恢复累加器内容
RET; 返回主程序

SHOW_LED: PUSH ACC; 保存累加器内容
PUSH PSW; 保存程序状态字内容

MOV A,R2; 将百分之一秒位的索引送入累加器A
MOVC A,@A+DPTR; 根据索引从字形码表中取出对应的字形码,并送入累加器A
MOV P0,A; 将字形码送入P0口输出

MOV A,R6; 将延时计数器的值送入累加器A
ANL A,#03H; 与03H进行逻辑与运算,取出最低两位作为位选索引,并送入累加器A
MOVC A,@A+DPTR; 根据索引从位选码表中取出对应的位选码,并送入累加器A
MOV P2,A; 将位选码送入P2口输出

POP PSW; 恢复程序状态字内容
POP ACC; 恢复累加器内容
RET; 返回主程序

T0_ISR: PUSH ACC; 保存累加器内容
PUSH PSW; 保存程序状态字内容
MOV TH0,#0FFH; 给定时器T0重新装初值,保证每次进入中断函数都是10ms
MOV TL0,#0D9H
INC R2; 将百分之一秒位的索引自增一
CJNE R2,#64,NEXT6; 如果百分之一秒位的索引不等于64,则跳转到NEXT6(未满100)
CLR R2; 如果百分之一秒位的索引等于64,则清零R2(满100)
INC R3; 将秒位的索引自增一
CJNE R3,#10,NEXT7; 如果秒位的索引不等于10,则跳转到NEXT7(未满10)
CLR R3; 如果秒位的索引等于10,则清零R3(满10)
INC R4; 将十秒位的索引自增一
CJNE R4,#10,NEXT8; 如果十秒位的索引不等于10,则跳转到NEXT8(未满10)
CLR R4; 如果十秒位的索引等于10,则清零R4(满10)
NEXT8: NOP
NEXT7: NOP

因为next7和next8是跳转标签,用来实现条件分支。如果不加nop指令,那么跳转到这两个标签后,程序会继续执行下面的指令,可能会造成逻辑错误。nop指令是空操作指令,不会影响任何寄存器或标志位,只是占用一个机器周期。加上nop指令可以保证跳转到这两个标签后,程序会直接跳出中断服务子程序,而不会执行其他多余的指令。

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
	ORG 0000H
AJMP MAIN

ORG 0BH ;外部中断0
LJMP EXTI0

ORG 13H ;外部中断1
LJMP EXTI1

MAIN:
MOV TMOD,#01H ;计数方式1
MOV TH0,#0DBH
MOV TL0,#0FFH
SETB TR0 ;启动定时器
SETB EA ;总中断允许
SETB EX0 ;外部中断0
SETB IT0 ;边沿触发
SETB EX1 ;外部中断1
SETB IT1 ;边沿触发

; 计数初始化
MOV R0,#0
MOV R1,#0
MOV R2,#0
MOV R3,#0
; 计数标志初始化
MOV R4,#1

LOOP:
MOV TH0,#0DBH
MOV TL0,#0FFH ;重新装载初值
JNB TF0,$ ;等待中断
CLR TF0 ;中断标志
;过了10ms,更新计数
INC R0
CJNE R0,#00AH,RET0 ;满10进1
MOV R0,#0
INC R1 ;进一位
CJNE R1,#00AH,RET0 ;满10进1
MOV R1,#0
INC R2
CJNE R2,#00AH,RET0 ;满10进1
MOV R2,#0
INC R3
CJNE R3,#006H,RET0 ;满6归零
RET0:
; 显示
MOV DPTR,#TAB0 ;字形码表送 DPTR

MOV P1,#0FEH ;第1个数码管
MOV A,R0
MOVC A,@A+DPTR ;查表
MOV P2,A ;显示
LCALL DELAY_SUB ; 调用延时子程序

MOV P1,#0FDH ;第2个数码管
MOV A,R1
MOVC A,@A+DPTR ;查表
MOV P2,A ;显示
LCALL DELAY_SUB ; 调用延时子程序

MOV P1,#0FBH ;第3个数码管
MOV A,R2
MOVC A,@A+DPTR ;查表
MOV P2,A ;显示
LCALL DELAY_SUB ; 调用延时子程序

MOV P1,#0F7H ;第4个数码管
MOV A,R3
MOVC A,@A+DPTR ;查表
MOV P2,A ;显示
LCALL DELAY_SUB ; 调用延时子程序

LJMP LOOP

EXTI0:
CJNE R4,#01H,SET1 ;R4==0,置1
MOV R4,#00H ;R4==1, 置0
SJMP RET1
SET1:
MOV R4,#01H
RET1:
RET

EXTI1:
; 重置
MOV R0,#0
MOV R1,#0
MOV R2,#0
MOV R3,#0
RET

; 延时子程序
DELAY_SUB:
MOV R3,#20
DELAY_LOOP:
MOV R4,#10
DJNZ R4,$
DJNZ R3,DELAY_LOOP
RET ; 返回

TAB0:
DB 0C0H,0F9H,0A4H,0B0H,99H,92H,82H,0F8H,80H,90H,88H,83H,0C6H,0A1H,86H,8EH,0FFH
END

百分之一秒,10000us,计数9216次,设置定时器初值为56319,就是#DBH,#FFH;和循环查询定时器中断标志,产生一次中断,在允许将计数的时候计数加一,各位用多个寄存器存储,依次进位,溢出清零,在不同数码管显示;key1,key2分别接线对应两个外部中断。key1中断函数将根据计数标识反转,决定是否允许计数;key2将存储计数的各个寄存器清零。

这个程序需要注意调整延时子程序的延时时长,四个延时加起来不超过,但又尽量接近10ms,确保能及时更新数据(也就是100Hz),并且四个数码管显示均匀。