学校教学用的是汇编,正好之前也没有怎么学过,一起学一学
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
需要知道的是,DPH
和DPL
就是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),并且四个数码管显示均匀。