原创 C51 using 用法(转)

2010-4-21 11:19 2552 6 6 分类: MCU/ 嵌入式
   刚才看到一位很牛的师兄写的一篇日志中提到了Keil C51中using这个关键字的用法,粗心的我本来一直都没有留意它是用来干嘛的(因为我一般看见它都是在中断服务函数的定义开头处,好像没有了它也可以中断呀,所以才没怎么管),然而在日志中有看到这个关键字,所以也考究了一下,突然发现,原来这个东东和我最近在帮一个同学调的一个程序的时候突然遇到一个很怪的问题是有关系的,而且就是因为它才搞得程序莫明奇妙的出错(因为编译通过了,看起来也没什么错误,按C语言的逻辑分析也分析不出个什么所以然来,所以才怪)。

        后来调试了好久,甚至到http://www.51hei.com/keil%CF%C2%D4%D8.html 这里下载了好几个版本的keil,反汇编代码看了N次,基本各个版本出来的结果都差不多,不知情的情况下,推断出是Keil C51的编译器问题,以为Keil C51的编译器的变量空间分配出问题了,R7本来是用来作参数传递的,却也被编译器分配用来在主函数中被用来作循环变量,结果就是循环变量的汇编判断指令CJNE指令执行前,R7的值(也就是循环变量)被改变了,导致判断R7总是不能达到循环设置的值,就不断地在循环,也就致使按键扫描去抖动这个延时循环无法完成,就导致延时后的第二次判断无法进行,自然按键下来也就没反应了。后来尝试着关了定时器中断,又可以了,那就可以肯定是在执行CJNE前,R7的值被改变了,就是因为跳到了中断,然后中断函数执行的过程中,R7值被改变了(因为中断函数中还调用了其它的函数,而R7后来被分析反汇编代码时发现是被用作参数传递的),中断执行完后R7的值当然不正确啦!


得出推断后我的解决办法就是:让C51编译好之后手动修改汇编的指令,不要直接使用寄存器来作循环变量,而是使用能实现间接寻地址的R1进行间接寻址(指向0x77)的方式,终于把问题解决了,因为0x77在执行中断函数的过程中没被修改,CJNE语句能正常判断了,循环能完成了,按键也能扫下来了,问题也可以解决了。后来我又想到用_at_关键字直接指定循环变量的分配地址,这样的话就不用手动改汇编指令了,编译器直接就是编译成使用间接寻址的,这也算解决了。但还是有个疑问,为什么编译器会不够聪明地把R7的空间分配错了呢?一直在疑问。。。。。。奇怪,怎么编译器会出这种问题?


        直到我刚才考究过了using的用法之后,才发现原来自己的推断是错的,结论的总方向还是对的,只是问题并不在编译器身上,真正的问题根源如下:


1.我的同学写的那个程序,用到了定时器0,它的中断服务函数定义是这样写的:


void t0(void) interrupt 1 using 0


{


    ........//一大串大代码,其中包括调用了其他函数,用到了R7作参数传递


}


这里就是加了个using 0,而问题就出在加了这个using 0,为什么?且听我的理解:


using关键字的作用就是指定某个函数在执行时切换寄存器组的:


  51中有四个寄存器组,每个组有R0-R7这8个寄存器,用于CPU的数据处理,一般主函数main()默认使用第0组,因为PSW寄存器的初始值的第3、4位是00嘛,即默认指定了第0组。using 0就是指定在执行函数时切换为使用第0组,不加using关键字的话,一般都默认使用第0组,但这样的话在调用其他函数(包括中断服务函数)的时候,就会加入一些压栈指令以保护原来的R0-R7寄存器的(这样的话,程序执行会效率会低一点,因为会产生很多个指令是用来压栈出栈的),照这样说,只是加了using 0,使用的也是第0组又不是其它的组,程序就不应该有问题了吧,但是就是因为加了using 0,编译了一次,才发现在定时器中断函数t0()的入口中并没有发现把R0-R7的代码压入栈呀,就是说没有保护好R7呀,那当然就是在执行完之后回来R7不能回复原来的值啦,接下来的事情。。。。我就不说啦,这就是问题的根源,去掉using 0就可以了,编译器就自动帮你将R0-R7压栈,手动加了using 0,就是让编译器以为之前用的并不是第0组,而现在执行这个中断函数时就切换到第0组,而省去了将R0-R7这8个寄存器压入栈的指令了,这样虽然看起来是快了,然而对于这个程序来说却是致命的问题!!!因为根本没有保护好R0-R7,而没有保护R7并不是我预期发生的!!!这不是编译器的问题,是自己没有了解好、用好using的问题啊!还有,其实也可以在编译器选项里有选项来硬性规定编译器统一不直接使用寄存器而是使用间接寻址的办法来改变循环变量分配的地址的,或者使用


#pragma NOAREGS


定义函数
#pragma AREGS


来规定某个函数的是这样子。


  当然,这样的规定和使用using本身并不冲突,只是使用了这个关键字后就可能会间接地产生一系列的问题,让人郁闷了这么久,其实本来如果有详细地看整个程序的反汇编代码,或许当时就会发现发现少了那段压栈指令了,而不是说推断为编译器分配空间的问题了。。。。。。。。希望我的这次教训对各位有所帮助!!!


  另外,using的用法,其实就是手动指定函数使用的寄存器组,用得不好,如果在中断里还有调用其它函数,用得不好会出现函数传递出错的,不信可以反汇编看看,建议如果对这个关键字用法和C51的结构及汇编不熟的话,请还是让C51编译器帮你好了,不要胡乱使用,因为会比较容易出错的,要切记哦!!!其实用using关键到底对在编译后会造成什么影响,建议自己亲自去查看汇编程序。。。


 对不起,编译器,我错怪你了!!!


举个例子来说:
定义一个函数
void func(unsigned char i) {
...
if(++i==0x12) {
...
}
...
}
有如下一个中断函数
void int_0(void) interrupt 0 using 1 {
....
}
    在默认状态下,func使用寄存器组0(BANK0),那么当int_0调用func时是否存在当传递参数时会造成参数传递错误?




    如果在中断服务函数 ISR 中使用寄存器,那么必须处理好 using 的使用问题:
    1
、中断服务函数使用 using 指定与主函数不同的寄存器组(主函数一般使用 Register bank 0)。
    2
、中断优先级相同的ISR 可用 using 指定相同的寄存器组,但优先级不同的 ISR 必须使用不同的寄存器组, ISR 中被调用的函数也要使用 using 指定与中断函数相同的寄存器组。
    3
、如果不用 using 指定, ISR 的入口,C51 默认选择寄存器组0,这相当于中断服务程序的入口首先执行指令:
MOV PSW #0
    这点保证了,没使用 using 指定的高优先级中断。可以中断使用不同的寄存器组的低优先级中断。
    4
、使用 using 关键字给中断指定寄存器组,这样直接切换寄存器组而不必进行大量的 PUSH POP 操作,可以节省RAM空间,加速 MCU 执行时间。寄存器组的切换,总的来说比较容易出错,要对内存的使用情况有比较清晰的认识,其正确性要由你自己来保证。特别在程序中有直接地址访问的时候,一定要小心谨慎!至于什么时候要用到寄存器组切换”,一种情况是:当你试图让两个(或以上)作业同时运行,而且它们的现场需要一些隔离的时候,就会用上了。在 ISR 或使用实时操作系统 RTOS ,寄存器非常有用。
  18051 的最低32 个字节分成 4 8 寄存器。分别为寄存器R0 R7。寄存器组由PSW 的低两位选择。在 ISR ,MCU 可以切换到一个不同的寄存器组。对寄存器组的访问不可位寻址,C51 编译器规定使用 using 禁止中断的函数(#pragma disable )均不能返回 bit 类型的值。
    2
、主程序(main函数)使用一组, bank 0;低中断优先级的所有中断均使用第二组, bank 1;高中断优先级的所有中断均使用再另外一组, bank 2。显然,同级别的中断使用同一组寄存器不会有问题,因为不会发生中断嵌套;而高优先级的中断则要使用与低优先级中断不同的一组,因为有可能出现在低优先级中断中发生高优先级中断的情况。编译器会自动判断何时可使用绝对寄存器存取。
    3
、在 ISR 中调用其它函数,必须和中断使用相同的寄存器组。当没用 NOAREGS 命令做明确的声明,编译器将使用绝对寄存器寻址方式访问函数选定(即用 using REGISTERBANK 指定)的寄存器组,当函数假定的和实际所选的寄存器组不同时,将产生不可预知的结果,从而可能出现参数传递错误,返回值可能会在错误的寄存器组中。
    举一例子:当需要在中断内和中断外调用同一个函数,假定按照程序的流程控制,不会出现函数的递归调用现象,这样的调用会不会出现问题?若确定不会发生重入情况,则有以下两种情况:
    1
、如果 ISR 和主程序使用同一寄存器组(主程序缺省使用BANK 0, ISR 没有使用 using 为其指定寄存器区,则缺省也使用 BANK 0,则不需其他设置。
    2
、如果 ISR 和主程序使用不同的寄存器组(主程序缺省使用BANK 0,ISR 使用 using 指定了其他 BANK,则被调用函数必须放在:
#pragma NOAREGS
#pragma AREGS
控制参数对中,指定编译器不要对该函数使用绝对寄存器寻址方式;或者也可在 Options->C51,选中“Don''t use absolute register accesses”,使所有代码均不使用绝对寄存器寻址方式(这样,执行效率将稍有降低)。不论以上的哪一种情况,编译器均会给出重入警告,需手工更改 OVERLAY 参数,做重入说明。
    3
、还有一种办法:如果被调用函数的代码不是很长,还是将该函数复制一份,用不同的函数名代替,这种情况适合ROM有足够多余的空间。
因此,using关键字的使用,如果没把握,宁可不用,交给编译系统自己去处理好了。





C51中断函数



C51的中断函数的格式为:void FuncIr(void) interrupt x [using y]



以下是梦游的一些分析:
    
一、中断函数是一个特殊的函数,没有参数,也没有返回值;但是程序中允不允许使用return呢?答案是允许的,不过只能用"return;",不能用"return(z);";用在一些需要快速返回的地方,对应的汇编会有多个ret语句,相对效率会高一些。
    
二、using的用法,using可以修饰任何函数,不过个人建议只用来修饰中断函数;简单的说,“using”会指定工作寄存器组,由于中断函数一般都是比较紧急的事情,有时一条语句都会斤斤计较,所以使用using切换寄存器组可以省去一些压栈的动作,由于51只有两级中断,同级中断不能被打断,因此,我们可以同级中断设成同样的寄存器组,从某种意义上来说,有一组寄存器是多余的。同时个人建议中断函数应该使用using这个关键字。
    
三、中断中调用函数,首先要讨论中断函数中调用函数的必要性,前天在论坛上我和别人争论过这个问题,现在我还是这个观点:有些情况中断中调用函数还是必要的,这个时候是不是该调用函数,其实和普通函数差不多,首先是这个函数如果调用多次,或者要带一些参数什么的就更加必要的;前天有人跟我叫劲,说假如只调用一次且无参数无返回的函数要直接写,因为如果用函数,至少会增加CALLRET两条语句,我不敢苟同,我是实际调试发现的,当你程序比较复杂时,你将那部单独拉出来做成函数,可能代码和时间都会更好。
    
四、中断中调用的函数最好不要被中断外的其它函数调用,因为会出现重复调用的警告,有时这种调用是很致命的,有人说这个函数可以用reentrant来修饰,是的,的确可以这样解决,不过个人不建议这么做,也许这样会跟你减少很多堆栈空间,并且整个程序的优化要差很多,个人建议出现这种情况就把这个函数写两遍,分成两个函数分别调用。
   
五,中断调用了函数,会出现一些莫名其妙的问题,一些数据不对。其实一般是因为汇编中使用了绝对寄存器引起的,有人说中断函数使用那个寄存器组,被中断调用的函数就使用哪个寄存器组,我认为这样不好:
    
这样会增加额外的消耗,使用using会增加一下语句:
       PUSH PSW
       MOV PSW, #XX
     ....
     OP PSW
   
更重要的是,使用using的函数不能有返回值,这是致命伤
   
个人推荐的方法有两种:
    1
、使用“#pragma NOAREGS”禁止使用绝对寄存器
    2
、使用“#pragme RB(x)”来指定本文件的工作寄存器组
   
六、一般说来,要求中断函数尽可能的短,但也有特殊情况,有些前/后台的系统中,就会把很多相对重要的事情放到定时中断(这个定时中断类似实时操作系统中的时钟节拍)去做,而且程序很长。我单独提出来这点是想告诉大家,中断函数也是一个函数而已,只要系统有必要,可以做一些看似不合理的事情,该出手时就出手,就像goto语句一样。

PARTNER CONTENT

文章评论0条评论)

登录后参与讨论
EE直播间
更多
我要评论
0
6
关闭 站长推荐上一条 /3 下一条