实验参考: 笨笨工作室-实验八、按键控制跑马灯(中断)。(查看)
实验板: FB51A。(查看)
实验目的: <1> 掌握堆栈在中断程序中的作用。
<2> 掌握让程序保护现场的方法。
实验现象: 二极管作左右跑马灯,当按下外部按键 K1 时, 8 个二极管全部闪烁 5 次后从 K1 按下之前的位置继续作跑马灯。
在应用中断时,若主程序在正常运行的过程中响应了中断就要转而去执行中断服务程序。那如何在执行完中断服务程序之后完全恢复原来的主程序呢?这就需要在执行中断服务程序之前将相关的状态保护起来,在中断完成后再将这些状态恢复,从而继续执行主程序。
51单片机中允许我们从内部 RAM 中指定一个空间专门保存这些断点处的状态,这个空间就是堆栈。并且还专门给了我们一个 8 位的堆栈指针,让我们用它来开辟堆栈空间。例如我们给堆栈指针赋值: mov sp, #70h ,就表示我们把内部数据 RAM 的地址为 70H 开始的单元设为堆栈。
MCS-51的片内存储器(RAM)共有256字节,高128字节是特殊功能寄存器,地址范围80H~FFH。这一部分可看作系统资源,不能随便利用。而剩下的低128字节区分如下:
──┬────────────┐
7FHㄧ 用户RAM区 ㄧ
ㄧ (数据缓冲区、堆栈区) ㄧ
30Hㄧ 80 byte ㄧ
──┼────────────┤
2FHㄧ 可位寻址区 ㄧ
20Hㄧ 16 byte ㄧ
──┼────────────┤
1FHㄧ 第3组工作寄存器区 ㄧ
18Hㄧ 8 byte ㄧ
──┼────────────┤
17Hㄧ 第2组工作寄存器区 ㄧ
10Hㄧ 8 byte ㄧ
──┼────────────┤
0FHㄧ 第1组工作寄存器区 ㄧ
08Hㄧ 8 byte ㄧ
──┼────────────┤
07Hㄧ 第0组工作寄存器区 ㄧ
00Hㄧ 8 byte ㄧ
──┴────────────┘
在这低128字节中,工作寄存器区和位寻址区的地址已分配好,我们可以利用的只有 30H ~ 7FH 的数据缓冲区。所以我们的堆栈指针只能设在这个区域。
在主程序中,让程序作左右跑马灯(参考【004】流水灯实验),。程序中通过把寄存器a中的数进行左环移来实现的。而a又是最常用的一个寄存器,在中断服务程序中也多数会用到,所以在响应中断时要将其保存起来(压入堆栈)。由于程序状态字寄存器PSW(位于特殊功能寄存器区)的不同位包含了程序运行状态的不同信息,所以进入中断时也要将PSW的值保护起来。在返回主程序之前,再把它们取出来,这样就可以使得程序从进入中断之前的位置开始,继续作跑马灯。
键识别部分参考【015】中断方式按键一文。
所用电路如下:
显示部分:
键盘部分:
程序如下:
org 0000h
ljmp start
org 0013h
ljmp ext1
org 0020h
start: setb ea ; CPU开中断
setb ex1 ; 允许外部中断1申请中断
setb it1 ; 设置外部中断1为跳变方式触发
mov sp, #70h ; 设置堆栈入口
loop1: lcall led_flow ; 调用左右流水灯程序
ljmp loop1
ext1: ; 中断服务程序
clr ea ; CPU关中断
push acc ; a 入栈
push psw ; psw 入栈
lcall key ; 调用键识别子程序
pass: pop psw ; 恢复现场(与入栈顺序相反)
pop acc
setb ea ; CPU开中断
reti ; 中断返回
led_flow: mov a, #0ffh ; 左右流水灯子程序
clr c ; 清Cy进位标志位
mov r7, #08h ; 左循环次数
lloop: rlc a ; a循环左移
mov p0, a ; a送P0口
lcall del100ms ; 延时100ms
djnz r7, lloop ; 左移8次
mov r6, #06h ; 右循环次数
rloop: rrc a
mov p0, a
lcall del100ms
djnz r6, rloop
ret
key: mov a, p1 ; 键识别子程序
anl a, #0fh
cjne a, #0dh, pass
lcall del10ms
mov a, p1
anl a, #0fh
cjne a, #0dh, pass
lcall key_flash ; K1按下则调用灯闪程序
ret
key_flash: mov a, #00h ; 灯闪5次子程序
mov r5, #10 ; 闪一次有一亮一灭
loop2: mov p0, a
call del100ms
cpl a ; 取反
djnz r5, loop2
ret
del10ms: ; 10ms延时子程序(12M)
mov r4, #20 ; 2机器周期
temp1: mov r3, #248 ; 2机器周期
djnz r3, $ ; 2机器周期 2+2×248=498
djnz r4, temp1 ; 2机器周期 2×20=40
ret ; 2+20×498+40=10002 即10ms
del100ms: mov r2, #0c3h ;100.036ms
temp2: mov r1, #0ffh ;511us
djnz r1, $
djnz r2, temp2
ret
end
★实验结果: 最终下载到FB51A实验板上得到预计结果。但在用Porteus仿真时却出现问题,流水灯正常运行时按下K1键后,8只LED闪烁5次后所有LED全灭,并未继续运行。在LED正常"流水"时按下K0、K2、或K3键时,则停在当前状态(一只LED亮)。经过调试后仍未解决,由于这个板上的键盘设计的有些不同,每个键都通过一个4148连到P3.3,而在Proteus中仿真时用4148行不通,所以用4001代替的。所以怀疑是这里的问题, 于是又试着用另一块板(AS系统)试了一下, 将外部中断1(P3.3)直接接一个开关后接地, 这样就可以直接触发中断了, 程序中去除了键识别的部分, 试验结果还是一样, 板上实测通过,仿真仍然会停在中断服务程序中。
后来又试着换了个中断源,原来用的外部中断1,现在改用外部中断0,程序中作相应的修改后再仿真就OK了。又翻了翻书,外部中断1和外部中断0好像没什么不同,只是外部中断0的优先级要比外部中断1高。而在板上实测二者均通过,只是在仿真的时候出现问题,难道是Porteus软件本身对这两个中断源的优先级有所区别? 还是软件本身有问题……
还是感觉这块板上的键盘结构在这个实验中很别扭,所以还是改为直接用一个开关控制,电路如下:
程序如下:
org 0000h
ljmp start
org 0003h
ljmp ext1
org 0030h
start: setb ea ; CPU开中断
setb ex0 ; 允许外部中断1申请中断
setb it0 ; 设置外部中断1为跳变方式触发
mov sp, #70h ; 设置堆栈入口
loop1: lcall led_flow ; 调用左右流水灯程序
ljmp loop1
ext1: ; 中断服务程序
clr ea ; CPU关中断
push acc ; a 入栈
push psw ; psw 入栈
lcall key_flash ; K1按下则调用灯闪程序
pass: pop psw ; 恢复现场(与入栈顺序相反)
pop acc
setb ea ; CPU开中断
reti ; 中断返回
led_flow: mov a, #0ffh ; 左右流水灯子程序
clr c ; 清Cy进位标志位
mov r7, #08h ; 左循环次数
lloop: rlc a ; a循环左移
mov p0, a ; a送P0口
lcall del100ms ; 延时100ms
djnz r7, lloop ; 左移8次
mov r6, #06h ; 右循环次数
rloop: rrc a
mov p0, a
lcall del100ms
djnz r6, rloop
ret
key_flash: mov a, #00h ; 灯闪5次子程序
mov r5, #10 ; 闪一次有一亮一灭
loop2: mov p0, a
call del100ms
cpl a ; 取反
djnz r5, loop2
ret
del10ms: ; 10ms延时子程序(12M)
mov r4, #20
temp1: mov r3, #248
djnz r3, $
djnz r4, temp1
ret
del100ms: mov r2, #0c3h ;100.036ms
temp2: mov r1, #0ffh
djnz r1, $
djnz r2, temp2
ret
end
评论