打印

新手容易错--51单片机寄存器组的设置

[复制链接]
9504|21
手机看帖
扫描二维码
随时随地手机跟帖
跳转到指定楼层
楼主
救火车|  楼主 | 2009-6-27 21:41 | 只看该作者 回帖奖励 |倒序浏览 |阅读模式
 
    大家都知道51单片机有的寄存器R0-R7共有四组。最近1年来,我在与新手朋友交流时发现,很多朋友对寄存器组的使用时经常出现问题。虽然这并不是多难的问题,但如果出现错误,也会造成很严重的后果。
    首先介绍一下51的寄存器组:
    通过设置PSW寄存器的第3位和第4位可以任意切换寄存器组。在进入中断前,切换寄存器组,可以方便的保护原寄存器组的数据不被中断里的语句破坏,很方便。
RS1 RS0             字节地址
 0   0   0组寄存器  00H~07H
 0   1   1组寄存器  08H~0FH
 1   0   2组寄存器  10H~17H
 1   1   3组寄存器  18H~1FH
 RS1=PSW.4 RS0=PSW.3

常见错误有三种:
1、为中断函数指定了第0组寄存器 
    C程序: void int0() interrupt 0 using 0
    编译后的汇编如下:
    PUSH  ACC 
    PUSH  B
    PUSH  DPH
    PUSH  DPL
    PUSH  PSW
    MOV   PSW,#0x00  
    。。。。。。
    因为main()函数使用的就是第0组寄存器,中断程序会改变寄存器组的数据。主程序运行时,随时都有可能产生中断,等中断返回主程序时,寄存器R0-R7的值已经被改变了。这是非常严重的错误。而且故障时有时无,错误也是莫明其妙。
2、中断优先级不同,寄存器组号相同
    C程序: void int0()  interrupt  0  using  1  //低优先级中断
             void T0()  interrupt  1  using  1  //高优先级中断 
    因为高优先级的中断可以打断正在执行的低级中断,转向持行高级中断。这就是所谓的中断的中断。与第1种错语一样,高级中断的程序,会改变低级中断正在使用的寄存器。
3、不写using 。严格的说,这样写不能算是错误。但这是相当不好的习惯。
    C程序: void int0()  interrupt  0
    编译后的汇编如下:
    PUSH  ACC 
    PUSH  B
    PUSH  DPH
    PUSH  DPL
    PUSH  PSW
    MOV   PSW,#0x00  
    PUSH  0x00
    PUSH  0x01
    PUSH  0x02
    PUSH  0x03
    PUSH  0x04
    PUSH  0x05
    PUSH  0x06
    PUSH  0x07
    。。。。。。
    没用using指定寄存器组,编译器就默认分配了第0组寄存器,然后又用8条语句把第0组的R0-R7保存到栈中,退出中断时还需要8个弹栈。这样“笨拙”的写法,占用了程序空间32个字节、占有堆栈8个字节。也许高级版本的编译器会改进吧,搞单片机的还是规矩些好。
    
经验总结:
    1、写中断程序一定要用using语句指定寄存器组。第1、2、3组都可以,不能是0.
    2、51单片机的中断有两个优先级。一个中断不会打断另一个相同优先级的中断。这样相同级别中断可以使用同一个组。比如:低优先级的中断函数都用 using 1,高优先级的中断都用 using 2 。这样不会冲突。
    下面是一个正常的例子:
    C程序: void int0() interrupt 0 using 1
    编译后的汇编如下:
    PUSH  ACC 
    PUSH  B
    PUSH  DPH
    PUSH  DPL
    PUSH  PSW
    MOV   PSW,#0x08  
        。。。。。。
    
    就是这么简单。虽然这点事儿对老鸟不算什么问题,但新手朋友犯此错误的可不少。我遇到的就不下15个了。今天我写这个贴子,也是为所有新手提个醒。到处救火不如防患于未燃。

相关帖子

沙发
Batistuta_| | 2009-6-27 23:47 | 只看该作者

用上未必好.尤其是用KEIL ..

使用特权

评论回复
板凳
arm_fan168| | 2009-6-28 15:07 | 只看该作者

用上的好处多

用上using可以精简代码,节省堆栈,不过有时会出现一个问题:
用上using ,在中断服务程序里调用函数要小心一点,因为keil C有时会产生依赖绝对地址的代码,例如如下函数,功能是从片外的存储设备中读取一个字节:
uchar ReadByte(uchar address)
{
  retrun PBYTE[address];
}
会被编译成如下代码:
  MOV      R0,0x07
  MOVX     A,@R0
  MOV      R7,A
这时,如果在中断服务程序里调用 ReadByte(0xAA); 就会发现读出的数据根本不对,因为using 1使得中断服务程序在调用函数时使用第一组寄存器传递参数,编译器生成的代码如下:
  MOV      R7,#0xAA
  LCALL    ReadByte
而ReadByte这个函数的代码是使用绝对地址为0x07的第0组寄存器的R7来传递参数的,所以会出问题。
解决方法是在定义ReadByte这个函数的前面加上"#pragma noaregs",这样编译器就会生成不依赖于绝对地址的代码了,函数ReadByte被编译生成的代码如下:
 XCH      A,R0
 MOV      A,R7
 XCH      A,R0
 MOVX     A,@R0
 MOV      R7,A
这样就可以大胆的使用using了,使用using才是充分利用51架构的使用方法。

使用特权

评论回复
地板
ayb_ice| | 2009-6-28 18:06 | 只看该作者

LS

使用using可不节省堆栈.
因为指定的寄存器组空间全部被保留,即使你没有用完,表面上节省了堆栈,编译后的RAM用量却增加了8个字节,代码的执行效率一般是高了,因为寄存器不用入栈...
强烈建议新手不要使用这些,好的中断程序本身一般很简单,根本就没有必要用,用了也可能被KEIL忽略...

使用特权

评论回复
5
ayb_ice| | 2009-6-28 18:11 | 只看该作者

LS

使用using可不节省堆栈.
因为指定的寄存器组空间全部被保留,即使你没有用完,表面上节省了堆栈,编译后的RAM用量却增加了8个字节,代码的执行效率一般是高了,因为寄存器不用入栈...
强烈建议新手不要使用这些,好的中断程序本身一般很简单,根本就没有必要用,用了也可能被KEIL忽略...

使用特权

评论回复
6
batiafu| | 2009-6-28 18:15 | 只看该作者

避免3楼所述情况还有一种方法:

如图,选中复选框

使用特权

评论回复
7
hkap| | 2009-6-28 19:06 | 只看该作者

...

1. 用using时特别需要注意的是中断中被调函数不能允许寄存器绝对地址访问!!!
2. 不同优先级的中断不能使用同一寄存器组
3. 如果没把握干脆不要用还好.

使用特权

评论回复
8
arm_fan168| | 2009-6-28 19:11 | 只看该作者

四楼五楼

我说的是节省堆栈,没说节省ram,你不用using不一定就节省ram,谁都想把中断服务写的简单,但有些情况下中断服务不可避免的需要一些运算。如果中断服务只用了一两个寄存器,也大可不用using,但如果用到得寄存器多了,不用using既不节省ram,又增加了中断服务程序的时间和代码量。

使用特权

评论回复
9
xwj| | 2009-6-28 19:17 | 只看该作者

使用using确实不节省堆栈.,但可以显著减小保护现场所花的

当然,也与你中断程序影响的寄存器多少有关

使用特权

评论回复
10
zyboy| | 2009-6-28 22:23 | 只看该作者

楼上几位说的太好!!怎么不早说呀

我在开发中就遇到这个问题,死找才发现是用using引起的寄存器切换问题。

网上看到梦游网友总结的。感觉就是大家前面的总结似的,呵呵

 
      äºŒã€using的用法,using可以修饰任何函数,不过个人建议只用来修饰中断函数;简单的说,“using”会指定工作寄存器组,由于中断函数一般都是比较紧急的事情,有时一条语句都会斤斤计较,所以使用using切换寄存器组可以省去一些压栈的动作,由于51只有两级中断,同级中断不能被打断,因此,我们可以同级中断设成同样的寄存器组,从某种意义上来说,有一组寄存器是多余的。同时个人建议中断函数应该使用using这个关键字。
      ä¸‰ã€ä¸­æ–­ä¸­è°ƒç”¨å‡½æ•°ï¼Œé¦–先要讨论中断函数中调用函数的必要性,前天在论坛上我和别人争论过这个问题,现在我还是这个观点:有些情况中断中调用函数还是必要的,这个时候是不是该调用函数,其实和普通函数差不多,首先是这个函数如果调用多次,或者要带一些参数什么的就更加必要的;前天有人跟我叫劲,说假如只调用一次且无参数无返回的函数要直接写,因为如果用函数,至少会增加CALLå’ŒRET两条语句,我不敢苟同,我是实际调试发现的,当你程序比较复杂时,你将那部单独拉出来做成函数,可能代码和时间都会更好。
      å››ã€ä¸­æ–­ä¸­è°ƒç”¨çš„函数最好不要被中断外的其它函数调用,因为会出现“重复调用”的警告,有时这种调用是很致命的,有人说这个函数可以用reentrant来修饰,是的,的确可以这样解决,不过个人不建议这么做,也许这样会跟你减少很多堆栈空间,并且整个程序的优化要差很多,个人建议出现这种情况就把这个函数写两遍,分成两个函数分别调用。
        äº”、中断调用了函数,会出现一些莫名其妙的问题,一些数据不对(我现在遇到这个问题)其实一般是因为汇编中使用了绝对寄存器引起的,有人说中断函数使用那个寄存器组,被中断调用的函数就使用哪个寄存器组(我认为好参考C51.PDF:Functions called from an interrupt procedure must function with the same register bank as the interrupt procedure. When the NOAREGS directive is not explicitly specified, the compiler may generate absolute register accesses using the register bank selected (by the using attribute or by the REGISTERBANK control) for that function. Unpredictable results may occur when a function assumes a register bank other than the one currently selected. Refer to â€œRegister Bank Access” on page 124 for more information.),我认为这样不好:
      è¿™æ ·ä¼šå¢žåŠ é¢å¤–的消耗,使用using会增加一下语句:
       PUSH PSW
       MOV PSW, #XX
        ....
        POP PSW
      æ›´é‡è¦çš„是,使用using的函数不能有返回值(这个地方有问题,应该可以有返回值,下文说是不能不能返回bit类型的值),这是致命伤(所以这不是致命伤,可以使用using解决这个问题)
      ä¸ªäººæŽ¨èçš„方法有两种:
       1、使用“#pragma NOAREGS”禁止使用绝对寄存器
       2、使用“#pragme RB(x)”来指定本文件的工作寄存器组
      å…­ã€ä¸€èˆ¬è¯´æ¥ï¼Œè¦æ±‚中断函数尽可能的短,但也有特殊情况,有些前/后台的系统中,就会把很多相对重要的事情放到定时中断(这个定时中断类似实时操作系统中的时钟节拍)去做,而且程序很长。我单独提出来这点是想告诉大家,中断函数也是一个函数而已,只要系统有必要,可以做一些看似不合理的事情,该出手时就出手,就像goto语句一样。
转自http://www.**/blog/hotchip/,请大家去他的博客中支持他,里面有不错的文章。括号中是我的理解
关于using:
举个例子来说:
定义一个函数
void func(unsigned char i) {
...
}
有如下一个中断函数
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中,寄存器非常有用。
寄存器组使用的原则:
1、8051的最低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关键字的使用,如果没把握,宁可不用,交给编译系统自己去处理好了。

使用特权

评论回复
11
Batistuta_| | 2009-6-28 23:46 | 只看该作者

看来大家都受过伤啊.

当初俺看一本书上这么说.然后在一个程序中遇到了该类问题,好一通找啊,最后在反汇编找到了问题.

使用特权

评论回复
12
chshfeng84| | 2009-7-1 16:31 | 只看该作者

长见识了

使用特权

评论回复
13
weizhen555| | 2009-7-1 17:16 | 只看该作者

呵呵,受教!曾经这样做过确实要省很多事。。。

使用特权

评论回复
14
lelee007| | 2009-7-2 02:51 | 只看该作者

规范一些好

效率问题倒不是大问题

就怕一些莫名其妙的问题出来了,反汇编再去慢慢查找,折腾死人

使用特权

评论回复
15
dandywang| | 2009-7-2 09:32 | 只看该作者

建议忘掉using

建议忘掉using

使用特权

评论回复
16
ch2003_23| | 2009-7-2 15:34 | 只看该作者

以前没注意到过

学习了,谢谢

使用特权

评论回复
17
xlsbz| | 2009-11-30 16:53 | 只看该作者
楼主说:也许高级版本的编译器会改进吧!
呵呵 确实是改进了! 自动切换到寄存器组1啦!!

3楼和6楼说的方法我在非中断函数上进行寄存器组切换了!!但是即使用了两位的方法还是不行! 不知道在中断中用行不行? 应该行吧! 因为文档上就这么说的!但是我没有实验过!

对于4、5楼讨论的问题,没有被用作寄存器组的,就会被编译器当做普通的地址用了!

结论:在现在的keil编译器下编程,用了using不好!

使用特权

评论回复
18
xlsbz| | 2009-11-30 16:59 | 只看该作者
十分感谢救火车的这个帖子,让我学到很多东西!!

我看了这个帖子后 对单片机又有了深的认识!

救火车另一个帖子 就是选举斑竹的那个帖子也很好!!!

全是精品!!

使用特权

评论回复
19
血之魔王| | 2009-11-30 17:59 | 只看该作者
学习

使用特权

评论回复
20
不亦心| | 2009-11-30 20:38 | 只看该作者
学习了,刚从汇编转过来,对C还不是很了解,不知道哪些该注意。

使用特权

评论回复
发新帖 我要提问
您需要登录后才可以回帖 登录 | 注册

本版积分规则

113

主题

1249

帖子

2

粉丝