一,堆栈在汇编的作用,以及PUSH ACC 和 PUSH PSW
- 通过最简单的8051单片机RAM的分配可知,单片机从烧程序到达到一定的工作过程。首先明白为什么要引如“烧”写的过程 。我们知道一般我说存储器ROM和RAM。首先ROM内存大,保存数据时间长,但这样也限制了ROM不能做到随时随写,因为为了达到内存大,存储更多逻辑,还要长期保存,其芯片设计目前不能达到随时改逻辑。
- 于是RAM孕育而生,RAM的功能就是随便写,随便读,各个部分的高低电平(就是硅晶体电子上浮下移)——逻辑结构可以根据处理器(内核)不断的修改,处理速度块。
- 为了完成固定保存逻辑,又要可以随时改变逻辑,就形成了ROM存程序,存表格数据,而RAM存数据,就是CPU(处理器)根据一行行(各个地址)要求,在RAM执行相应的操作。
- 那么问题来了,RAM是怎样保存运行ROM的程序,以及CPU怎样在ROM得到逻辑识别作用RAM?
这就是RAM有特殊功能区,如上图,在8051单片机中,RAM有516个单元存储(也就是寄存器),ROM存储的程序就是调用RAM的各个寄存器完成逻辑。
比如 :在ROM存储 MOV A, #20H
MOV P1, A
就是ROM发给CPU要给ACC寄存器(ACC其实就是标识符在RAM的位置高128位FSR(特殊功能寄存器)的地址0EH赋值,然后把RAM的ACC(地址E0H)赋值给P1,这里P1也是RAM寄存器(存储单元)存储位置的标识符在90H。
- 那么问题来了,堆栈SP有什么作用?
- 首先讲栈的作用;
堆栈就是在执行PUSH ###,把它压倒栈里,把数据保存起来,(注意这里一切都在RAM里面完成),就是在RAM开辟一段空间,当压入时,就是把压入的值赋给栈的寄存器(注意RAM所有地址都叫寄存器)
例如:MOV 30H,#22H
PUSH 30H
MOV 30H,#01H
/%%%MOV A ,30H
POP 30H
MOV A, 30H
如上程序,第一段就是把22h的16进制数存到地址为30H寄存器(寄存器上,其实像 P1,TCON,TMOD,都是RAM寄存器的标识符,他们都表示地址,这里30H是用户自定义区,在汇编编译器没有赋值标识符,当然高手就是直接可以调用头文件直接把每个位置附上标识符),然后执行PUSH 30H,就是把RAM30H单元存储的值(22H)放在栈里,,之后,在对地址30H赋值01H,在“////%%%"A的值为01H,之后再POP 30h,,这里POP只是标记弹出在最后压栈的数据,给30H,也就是30H存储值又恢复22H。
以上就是堆栈的功能(默认都学过C语言,栈是先进后出,在51压栈指针增)
- 那么问题来了,堆栈是在什么地方堆栈?
这就引出今天讲的重点,堆栈指针SP,指针就是告知对于压入的数据和调用子程序的开始地址和中断时程序运行的地址。但是在RAM中像什么通用寄存器,用户自定义区,可位寻址区,特殊寄存器区,其实没有什么界限,原理上堆栈开始位置可以设置任何区域。但但但是,不要覆盖我们可能要用的寄存器(RAM地址)。
在8051单片机中,栈的默认位置为07H,通过以上RAM地址可以看到,在07H寄存器以上又其他通用寄存器,和位寻址寄存器,用户自定义寄存器。如果当启动中断,调用子函数,或者压栈,栈会保存地址信息,保存压栈的内容,这是原来在(注意当栈确定位置时,压栈数据存储在储存位置上个,如栈起始为07H,进栈存储在08H),08H,09H,的数据就会被覆盖,当调用08H,09H的数据原来数据就没有了,会造成程序紊乱。
所以这也是汇编的弊端,在C语言写程序不管调用多个程序,多个子程序,多少中断,C语言会自动分配合适的栈地址,所以C语言,甚至高级语言基本没有调用栈的程序。(例如在我们学习C语言,老师就讲递归的害处,因为递归是从后往前算,当要得到一个值,要找前面的值,每调用一次,函数的地址就要压栈,当调用多次,把栈的范围扩大到本程序要用的地址(如特殊功能寄存器),就会造成程序崩溃)。
但是在我们单片机汇编语言中,我们是面向寄存器的语言,要时刻关注各个寄存器运行情况。下面来举几个具体的例子。
举例:堆栈SP和RAM
MOV A, #00H
SETB RS0
CLR RS1 //启用通用寄存器第1组,这时R0的地址为08H
MOV R0, A
MOV 30H, #0FFH //给用户自定义区30H,赋值为FFH
MOV 31H, #0EEH //给用户自定义区30H,赋值为EEH
PUSH 30H //把30H的值压倒栈,这里是默认07H为栈的初始位置,在07H,这样08H的值为FFH
PUSH 31H // 把31H的值压倒栈,这里是默认07H为栈的初始位置,
//因为之前已压一个在07H,这样08H的值为EEH
POP 30H //表示弹出栈口的值,给地址30H,因为栈是先进后出,30H地址存储EEH
POP 31H//同理31H地址的值为FFH
MOV A, R0 //最后由于之前进栈操作把原来R0的值覆盖,得到现在R0的值为FFH之前的C语言,定义unsigned char u;就是编译器在RAM已经生成个存储u的数据,我们不用管,在调用子函数,中断函数,也是C语言已经设置好了堆栈空间,存储函数入口地址。
现在,在汇编,我们经常会遇到修改栈指针就是修改进栈地址,防止我我们设置的寄存器的影响。
汇编模板
ORG 0000H
AJMP MAIN
ORG 0030H //在40H开始写程序,因为在0000H-0030H的ROM有中断入口
MAIN:MOV SP ,#50H //现在使用的单片机的RAM有很大空间,目前我写函数时直接修改栈地址50H
//这样在30H-50H,我可以随意赋值,且不会覆盖,如:MOV 3FH,#0FFH
//对于改栈指针最好在主函数执行第一个改MOV 30H, #00H //首先这个程序存储在ROM中,//
//然后这个程序执行的任务就是把RAM的30H寄存器(地址)赋值00H
MOV 31H, #00H
MOV 32H, #00H
CLR RS0
CLR RS1 //调用通用寄存器组0
MOV A, 30H
MOV R0 ,#31H
MOV A, @R0
ACALL DELAY500MS //把子函数调用开始位置数据压倒栈里//注意调用子函数,特别是含有通用寄存器,或者SFR,要想到会不会对主函数有影响
//保险起见,对于封闭的子函数都调用通用寄存器3,主函数都调用通用寄存器组0
DELAY500MS:
SETB RS0
SETB RS1 //调用通用寄存器3
MOV R7,#17H
DL1:
MOV R6,#98H
DL0:
MOV R5,#46H
DJNZ R5,$
DJNZ R6,DL0
DJNZ R7,DL1
CLR RS0
CLR RS1 //调用通用寄存器组0
RET
二,中断和定时器的基础知识
2.1中断
在看逻辑图和寄存器映射编写自己的程序是要注意两点:1,逻辑关系走向,2,寄存器是否可以位寻址。
CPU在正常执行程序的过程中,由于某种已经预见到的外部或CPU内部事件的发生,使CPU暂停执行当前的程序,而去处理临时发生的事件,在事件处理完毕后,再返回原先暂停的程序继续向下执行,这个过程叫做中断(Interrupt)。
编者按:下图是51系列的中断(在我们现在大部分是STC89C52,它又加了额外的中断和优先级(但兼容51),这里就不详细描述,具体可以根据数据手册的流程图配置)这里讲解中断定时器,以及配置都是根据以下的逻辑图和他们对应的寄存器(当然如果可以位寻址,可以根据逻辑图直接配置,问题是有的寄存器不能位寻址)
由图可知,80C51单片机的5个中断源分别是:2个外部中断(由ND、xn引脚输入中断请求信号)、2个片内定时器/计数器溢出中断(TO、T1)和1个片内串行口中断。TCON、SCON是用来存放各中断源的中断申请标志的寄存器;IE是用来设置是否允许中断源中断的寄存器;IP是用来设置中断源优先级别的寄存器;硬件查询是相同优先级的中断源再进行排队的硬件电路。
TCON、SCON、IE和IP是与中断相关的寄存器,下面分别对这4个寄存器的用法进行详细介绍。
1定时器控制寄存器TCON(Timer ControlRegister)。
TCON为定时器/计数器TO、T1的控制寄存器,同时也锁存了TO、T1的溢出中断和2个外部中断请求的标志位。TCON的单元地址为88H,可位寻址,这些中断请求标志位可以用软件查询。TCON的格式及与中断有关的位的功能如下。
上面的逻辑图,可以地IE1,IE2,IT1,IT0,分别是中断请求标志位,都可以在完成中断自动清零。
3串行口控制寄存器SCON(Serial ControlRegister)。
SCON的低两位锁存了串行口发送中断和接收中断请求标志位。SCON的单元地址为98H,可位寻址。SCON的格式和与中断有关的位的功能如下。(由上章串口通信,这里SCON是一部分)
TI:串行口发送中断请求标志位。当串行口发送完一个数据帧时,将TI置位(TI=1),向CPU申请中断。
RI:串行口接收中断请求标志位。当串行口接收完一个数据帧时,将RI置位(RI=1),向CPU申请中断。注意在串口中断时,中断标志位不能自动清零(也就是术语:硬件清零),在编程时跳到中断要清零(软件清零)。
4中断允许寄存器IE(Interrupt EnableRegister)
对于各个中断源的中断允许控制位,当其为1时,允许中断;当其为0时,禁止中断。中断允许寄存器IE的单元地址是0A8H,各控制位(位地址为0A8H~0AFH)可位寻址,故用户既可以用字节传送指令又可以用位操作指令来对各个中断请求加以控制。画×的位未用到,可设置为0。
EA:中断允许总控制位。若EA=0,则所有中断源的中断请求均被禁止;若EA=1,则所有中断源的中断请求均被允许,但中断请求最终能否被CPU响应,还取决于IE中各中断源的中断允许控制位的状态。EX0和EX1:EX0为外部中断0的中断允许控制位;EX1为外部中断1的中断允许控制位。
ETO和ET1:ETO为定时器/计数器TO的溢出中断允许控制位;ET1为定时器/计数器T1的溢出中断允许控制位。
ES:串行口中断允许控制位。
5中断优先级寄存器IP(Interrupt Priority Register)
CPU在某一时刻只能响应一个中断源的中断申请,如果有很多的中断源同时申请中断,到底应该响应哪一个呢?可以通过设定优先级别的方法来解决这个问题。80C51单片机的5个中断源,均可由程序设置为高优先级中断或低优先级中断,谁的优先级别高,就先响应谁。每个中断源的中断优先级都通过中断优先级寄存器IP统一设置。IP的格式和与优先级设置有关的位的功能如下。
PX0和PX1:PX0是外部中断0的中断优先级控制位;PX1是外部中断1的中断优先级控制位。
PTO和PT1:PTO为定时器/计数器TO的溢出中断优先级控制位;PT1为定时器/计数器T1的溢出中断优先级控制位。
PS:串行口中断优先级控制位。
当某个中断源的优先级控制位为1时,设置为高优先级中断;为0时,设置为低优先级中断。IP的单元地址为0B8H,各控制位(位地址为0B8H~0BCH)可位寻址,故用户既可以用字节传送指令,又可以用位操作指令来对各个中断源的优先级进行设置。单片机复位时,IP=00H,即所有中断源为低优先级中断。
6中断的处理过程
中断的处理过程大致可分为4步:中断请求、中断响应、中断处理和中断返回。
1.中断请求
当中断源发出中断请求时,将相应的中断请求标志位置“1”,向CPU请求一次中断服务。如果中断允许寄存器IE中的总控开关和相应的分控开关是闭合的,那么这个中断标志位就会传送到CPU中。
2.中断响应
CPU响应中断时,先置位相应的优先级状态触发器,指明CPU开始处理的中断源的优先级别,以屏蔽后面的同级或低级中断请求;然后自动清除相应的中断标志(TI或RI除外);最后再执行由硬件自动生成的长调用指令(LCALL),该指令将程序计数器PC的内容(主程序被中断的地址,也称断点地址)压入堆栈保护起来,再将对应的中断服务程序的首地址(也称中断入口地址或中断矢量地址)装入程序计数器PC,使CPU去执行中断服务程序。注意,断点地址被压入堆栈保护起来了,但对诸如PSW、累加器A等寄存器的数据并没有保护,需要时可在中断服务程序中完成保护任务。系统规定的各中断源相对应的中断服务程序的中断入口地址见
ORG 0003H ;外部中断0的中断入口地址
AJMP EXT0 ;转移到0200H单元
7代码示例端(注意通用寄存器的改变):
通过这个基础设置,也就是更具逻辑图,以及寄存器(可位寻址与不可位寻址)可是自行设计中断,这里就不详细描述了,我所讲的还是跟上个一样,就是调用子函数的通用寄存器的改变,以及中断的改变,如下示例,就是简单的外部中断0,下降沿有效。实验证明在PUSH PSW ;只是保存程序状态字,如保存通用寄存器组,但是当在中断仍然改变通用寄存器的值,它还会对主函数造成影响,原来不管中断还是调用子程,默认都是调用通用寄存器组0,为了避免影响,在汇编的子程序开始修改组别,调用末尾在改回来。在C语言有个interupt using 1; 就是调用通用寄存器组1.
LED EQU P2 ORG 0000H AJMP MAIN ORG 0003H //中断的处理过程:2.中断响应之外部中断0的地址,必须要这样的格式 AJMP EXT0 ORG 0040H //因为我用的是stc52系列 MAIN: SETB IT0 //1定时器控制寄存器TCON(Timer?ControlRegister)。设置下降沿有效 SETB EX0 //4中断允许寄存器IE(Interrupt?EnableRegister) 开中断0 SETB EA //4中断允许寄存器IE(Interrupt?EnableRegister) 最高级控制所有中断 SETB PX0 MOV A,#01H CLR RS0 CLR RS1 MOV R7,#0FH LOOP: ;CPL A ;MOV LED,A //连接方式灌电流,电源-电阻-二极管-Px,低电平亮 ;ACALL DELAY800MS MOV LED,R7 //用于检验 ;;ACALL DELAY800MS ;RL A AJMP LOOP //没有中断的LED EXT0: //中断处理程序 PUSH PSW PUSH ACC ;SETB RS0 ;SETB RS1 /////////修改寄存器 MOV R7,#0F0H MOV LED,R7 //用于检验 ;ACALL DELAY1S ;ACALL DELAY1S POP ACC POP PSW CLR RS0 CLR RS1 RETI DELAY1S: ;误差 0us MOV R1,#0A7H DL11: MOV R2,#0ABH DL00: MOV R3,#10H DJNZ R3,$ DJNZ R2,DL00 DJNZ R1,DL11 NOP RET DELAY800MS: ;误差 0us MOV R4,#5FH DL1: MOV R6,#8AH DL0: MOV R5,#1DH DJNZ R5,$ DJNZ R6,DL0 DJNZ R4,DL1 NOP NOP RET END