51单片机 interrupt和 using使用详解

注:下文的“BANK”和“工作寄存器组”,这两个名词是一个概念。


首先推荐一篇文章,点击打开链接

这篇文章大部分是翻译软件直接翻译的,有些翻译的不通顺的地方、有歧义的地方,直接读读英文原版就清晰了。

如果链接挂了,自行搜索:关于如何利用Keil C实现51单片机中断功能(interrupt、using关键字的用法


官方文档里关于using的说明可参阅2个地方,(1)keil软件菜单栏->Help->uVision Heip,打开帮助文件,然后依次展开Ax51 Assembler User Guide -> Control Statement -> Reference -> USING,(2)帮助文件依次展开CX51 Compiler User‘s Guide-> Language Extensions -> Function Declarations -> Register Banks



下面是我对interrupt和 using使用理解,

首先看interrupt,这个比较简单,直接看一个外部中断0服务函数的例子

void ext_int0_src() interrupt 0 using 2//
{
    /*外部中断0的服务函数*/
}

interrupt 0,这里的数字0是外部中断的中断向量号,每一个中断向量都有对应的一个入口地址。如果对中断向量表的概念比较熟悉的话,这里很好理解,不再赘述。


下面主要看using的使用方法,这个比较复杂,我们从头说起。

    因为51的RAM空间极其有限,keil在编译51的C程序时,C语言函数中的局部变量、形参、返回值、返回地址会优先使用工作寄存器R0~R7(因为工作寄存器读写速度最快),如果这8个字节不够用,keil会为其余的局部变量分配RAM空间,这个空间在编译完成后就固定下来了,假如某个函数中的局部变量a编译后位于RAM的0x5d位置,但是keil也有可能为另一个函数的中的局部变量b分配空间时,把b也分配到RAM的0x5d位置,相当于a和b分时复用同一个RAM空间,这种编译手段也就导致了函数的不可重入特性。51的可重入函数由仿真栈来实现,可参阅本博客的另一篇文章。

      对于STM32等RAM较为充足的单片机来说,keil分配局部变量就不采用这种分时复用RAM的方法了,而是通过栈,具体地说就是,在调用函数前,先把实参添加到工作寄存器(工作寄存器并不是全部用来传参,还有一些用来传递函数的返回值、函数的返回地址等),若工作寄存器满足不了传参需求,那么其余实参将被压栈,这样,在进入函数之后,通过读取工作寄存器和出栈,即可令形参获得实参的值;另外函数中的局部变量一般也先从工作寄存器开始分配空间,若空间不足,会从栈中申请空间。这种编译方法是最常见的,而上一段keil针对51的编译手段比较另类。

    51的工作寄存器R0~R7共有4组(分别是BANK 0、1、2、3),在任何时刻,都只有1个工作组生效!这4个组(BANK)在RAM中的位置分别是[00H, 07H]、[08H, 0FH]、[10H, 17H]、[18H, 1FH],换句话说,RAM中的00H地址、08H地址、10H地址、18H地址,这四个地址的名字都叫R0,那么在汇编程序中我们经常看到类似MOV R0,#07这样的语句,这个#07到底被放到了RAM的哪个地址中去了呢?00H?08H?10H?还是18H?到底是这4个地址中的哪一个,取决于51的PSW寄存器的RS1和RS0两个位,若PSW.RS=2,就意味着第2组工作寄存器生效,R0的地址就是10H。

    上面提到了51不可重入函数的局部变量分时共享同一RAM空间的情况,需要注意的是,未使用的工作寄存器组所在的RAM地址(从00H~1FH共32字节)不会被复用,换句话说,如果我们在程序中没有手动切换工作寄存器组,那么有些寄存器组就在程序的自始至终从不被使用,白白浪费了很多RAM。举个例子,普通函数只使用了BANK 0,且中断函数通过声明using 1只使用了bank 1,那么bank 2和bank3不会被程序的任何地方给用到(唯一的例外:仿真栈指针,下面有讲到);再举个例子,假设整个51程序中都没有使用中断,也即只使用了BANK 0,那么BANK 1、2、3所在的空间在整个程序运行过程中都是闲着的(唯一的例外:仿真栈指针)。

    51在上电后,PSW的RS两个位默认为0,也即51默认使用工作寄存器组BANK 0,在默认状态下,对于普通的C语言函数,其传参、申请局部变量、导出函数的返回值等功能,keil将其翻译成汇编以后,肯定要使用R0~R7;对于51的中断服务函数,它没有形参,也不用返回值,但是一般肯定有局部变量,这时就需要用到R0~R7了;试想,在执行普通函数时,R0~R7已经被使用了,在执行普通函数时,一旦发生中断,而中断函数也需要使用R0~R7,那怎么办?我们最先想到的是,在执行中断服务函数前先把R0~R7入栈(像累加器A、状态PSW等也要入栈这个不用说大家也知道),在中断服务完成后把R0~R7出栈,然后就能恢复现场,回到普通函数中去了,但是这8个Rn不能直接入栈,PUSH R0这样的语句是不允许的,要想R0入栈只能用两句:MOV A R0; PUSH A;这样的后果是,每次工作寄存器入栈都需要2*8=16条汇编语句才能完成,再加上A、B、PSW等寄存器入栈等,相当于每次中断都要消耗大量的时间来出栈入栈,影响程序速度。如何解决这一问题呢?51提供了这样一种机制,切换工作寄存器组,过程如下:

    普通函数的执行过程中正在使用BANK0的R0~R7,执行过程中突然发生了中断,而中断函数也想使用R0~R7,在执行中断服务函数前,我们切换工作寄存器组,切换的具体方法就是直接修改PSW的RS两个比特位,而不必把BANK 0入栈,本文开头的例子中using 2,就是说,在进入外部中断0的服务函数前,先入栈CPU寄存器,再把工作寄存器组由0切换成2,在退出中断服务后,先由BANK2切换回BANK0,并弹出CPU寄存器,由于BANK0和BANK2处在不同的RAM空间,互相不干扰,切换回BANK0之后就把那个普通函数的现场给恢复了。

    还有两个问题点需要说明:

    问题1、对于同一优先级的中断,可以using同一个寄存器组,因为同一优先级的中断不会互相打断,也就是说,例如:我把同一优先级的中断函数都using 2,这些函数也不会冲突地使用R0~R7,他们只会分时复用BANK 2。但是对于不同优先级的中断函数,必须手动设定为让他们使用不同的BANK,因为高优先级的中断会打断低优先级的中断,如果使用相同的BANK,一旦发生中断嵌套,低优先级服务函数正在使用的R0~R7将会被覆盖。这些东西都是说的在用C编程时的情况,如果用的是汇编编程,那我们可以自由的任意压栈出栈CPU寄存器,任意的压栈出栈BANKn,只要程序员自己心里清楚地知道哪些Rn正在被使用,那他就能写出安全的程序。用C编程的话,程序员比较省心,只要使用了using指令,就能轻易地保护R0~R7。当然,在汇编中使用USING也是可以的,也不用压栈出栈BANK,也很省心。

问题2、如果我们使用了keil为51构造的仿真栈,以大编译模式为例,keil会给仿真栈指针?C_XBP分配一个RAM空间(2个字节),这个空间在哪呢?通过查看map文件(后缀名为.m51)我们发现,若我们的中断函数只使用了BANK 1(普通函数默认已把BANK 0给用了,除非程序员为所有的普通函数都用using 1或2或3来修饰,如果不是闲的蛋疼没人会这么做),这时仿真栈指针?C_XBP将会被分配到BANK 2的R0和R1所在的位置上,也即RAM的10H、11H地址。如果普通函数使用了BANK0,中断函数只使用了using 2,那么?C_XBP将会被分配到BANK 1的R0和R1所在的位置上;如果普通函数使用了BANK0,中断函数使用了BANK1和BANK2,那么?C_XBP将会被分配到BANK 3的R0和R1所在的位置上。


   综上所述,在进入中断服务程序后,要么通过修改PSW切换BANK 号码来保护R0~R7,要么把R0~R7入栈来保护R0~R7。使用using n编写的C语言中断服务函数,keil就给翻译成了PSW切换PSW切换的方式。

  • 38
    点赞
  • 116
    收藏
    觉得还不错? 一键收藏
  • 9
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 9
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值