[求助] 调了2天的51单片机程序,发现了一个问题,这算不算是Keil C51的BUG?

bobde163   2016-3-18 11:37 楼主
用Keil C51调了2天的51单片机程序,主要功能是使用定时器0实现一路模拟串口,但是在使用中模拟串口发送的数据全都是0,串口发送的字节帧格式都是对的,一个起位,8个数据位,一个停止位,说明是要发送的数据全都变为了0。使用软件仿真调试,查看汇编代码,反复分析才确定了问题的根本原因,跟51单片机内部的4个寄存器组有莫大的关系。 1、 进入中断前寄存器组从0切换到2_副本.png 1、在进入定时器0的中断函数前,可以看到系统还是使用的是默认的寄存器组0,由于设置中断函数使用寄存器组2,所以在进入中断函数前,除了对原来的运行数据进行入栈操作外,最后还切换到了寄存器组2,这里没有问题。 中断中进入带参数的函数前的参数传递.PNG 2、在中断函数中调用了带参数的函数VUartTransmitByte(*Ptr)(注:Ptr是一个指向字节数据的指针,指针变量地址为0x3D,位于data段)。在图中可以看到在汇编代码中,在实际调用函数前,先对参数进行了传递,使用R7进行参数的传递,之后正式进入函数内部,此处也没有问题。 进入函数时 参数传递给R5.PNG 3、在正式进入函数中时,从汇编代码中可以看到,第一条被执行的指令是MOV R5,0x07;这里就是第一个出问题的地方,结合后面的代码分析,这条指令的作用是将传递进来的参数(存在寄存器组2中的R7中)又转存到R5中,R7又作其他用途。但是这条指令却用的是0x07地址,0x07是寄存器组0中的R7寄存器,此时系统选用的还是寄存器2,应该使用的R7或者0x17,这是导致数据错误的第一个地方。 进行移位和与操作的汇编代码.PNG 4、之后便执行到移位操作和与操作部分if(dat & (0x01 << (TxBitCnt - 1))),从汇编出来的代码可以看到TxBitCnt - 1这一步是没有问题的,结果存放在R7中,接下来就是要执行移位操作,代码先是将需要移位的位数(就是TxBitCnt - 1的值,存放在R7中)放到R0中MOV R0,0x07;才会进入循环移位的代码部分,但是这条指令系统却是使用寄存器组0中的R7,而不是存储真正位数和寄存器组2中的R7,导致移位结果全为0。这个错误是和上一条错误是一样的。 5、我去掉定时器0中断使用寄存器组2的设置(using 2),系统就会使用默认的寄存器组0,只是进入中断函数前,会先把R0到R7的值压入堆栈,会多一些开销,但是程序运行就对了,发送的数据也都是正确的。 6、这个问题曾经遇到过,调了一周,最后只知道是只要在中断中调用个别函数,就会导致发送函数异常,没有找到原因,现在看来可能也是这个原因导致的。 6、疑问:这个问题的产生是和我代码有关系吗?这个不知道大家有没有遇到过呢?这算不算是一个Keil C51的BUG呢? 本帖最后由 bobde163 于 2016-3-18 11:39 编辑

回复评论 (10)

这个结果是对的,using指定这个函数使用某寄存器组,你的uarttransimtbyte函数没指定这所以不操作那个寄存器啊。没太看明白你期待的结果是什么。
using的使用不适合函数间的数据,那样的话就和全局量没区别了。
点赞  2016-3-18 13:19
引用: huo_hu 发表于 2016-3-18 13:19
这个结果是对的,using指定这个函数使用某寄存器组,你的uarttransimtbyte函数没指定这所以不操作那个寄存 ...

我设置了定时器中断服务函数要使用寄存器组2,并且状态寄存器PSW中也显示系统已经切换到寄存器组2了,那在整个中断函数的执行过程中,应该一直是要使用寄存器组2才对啊;
另外,像你说的,我的VUartTransmitByte()函数没有指定使用哪个寄存器组,那它是不是应该会确定使用某一个寄存器组?但是从汇编代码来看,它是横跨了寄存器组0和寄存器组2,有使用寄存器组2中的R7,R5,R0,R6等,也有通过直接寻址访问0x07(寄存器组0中的R7),函数里既使用了组0,也使用了组2,这又要怎么解释呢?
点赞  2016-3-18 14:51
引用: bobde163 发表于 2016-3-18 14:51
我设置了定时器中断服务函数要使用寄存器组2,并且状态寄存器PSW中也显示系统已经切换到寄存器组2了,那 ...

不一定,全局量是系统安排的,只有局部变量才组2.
不指定和using 0 是一样的。

using的使用和编译器分配的空间有关系,如果没用到就当没有。
点赞  2016-3-18 18:19
引用: huo_hu 发表于 2016-3-18 18:19
不一定,全局量是系统安排的,只有局部变量才组2.
不指定和using 0 是一样的。

using的使用和编译器 ...

但是从汇编出来的代码来看,在中断里被调用的函数VUartTransmitByte()里面同时使用了组2和组0,这个就是一个大问题。我指出的第二个错误的地方,完全是已经在函数里面了,理论上是函数里只使用一个寄存器组,但是在存储中间结果时还是同时操作了2个寄存器组,这才是我想不明白的地方,是我的程序在哪个地方忽略了什么东西吗?
点赞  2016-3-19 13:13
引用: bobde163 发表于 2016-3-19 13:13
但是从汇编出来的代码来看,在中断里被调用的函数VUartTransmitByte()里面同时使用了组2和组0,这个就 ...

你把源程序贴上来哪天有空了看看
点赞  2016-3-19 17:00
引用: huo_hu 发表于 2016-3-19 17:00 你把源程序贴上来哪天有空了看看
那就劳烦版主帮忙看看,模拟串口的代码如下:
V_Uart.c (9.09 KB)
(下载次数: 5, 2016-3-21 10:06 上传)
本帖最后由 bobde163 于 2016-3-21 10:07 编辑
点赞  2016-3-21 10:01
引用: bobde163 发表于 2016-3-21 10:01
那就劳烦版主帮忙看看,模拟串口的代码如下:

楼主的钻研精神可嘉,
你这样,把生成lst和map文件给我,我的思路是先看看每个变量被分配的实际地址,然后再分析一下。
点赞  2016-3-21 11:46
引用: huo_hu 发表于 2016-3-21 11:46
楼主的钻研精神可嘉,
你这样,把生成lst和map文件给我,我的思路是先看看每个变量被分配的实际地址,然 ...

好的,我这就发上来,这个问题不解决,怕是以后写程序要有阴影,不自信了
V_Uart.lst (16.35 KB)
(下载次数: 3, 2016-3-21 13:11 上传)

ID_Reader_ReWrite_5916AD.map (136.78 KB)
(下载次数: 3, 2016-3-21 13:11 上传)


点赞  2016-3-21 13:11
个人感觉,既然用C语言了,就不要涉及到编译器编译层面上的控制,比如指定寄存器组(Using)。提高执行效率的想法很好,但目前编译器的优化和单片机的执行速度已经大大提高了,强行指定一方面不利于程序移植,一方面容易带来异常,得不偿失。如果真的要提高效率,可以直接用汇编和C混编。最后对于你这个问题,我觉得你只是指定了在中断函数中强制寄存器组,但在中断中调用的函数是在中断函数外被编译的,并不属于中断函数域,所以不受中断函数的寄存器组指定影响。
点赞  2016-4-4 15:21
引用: ntmusic 发表于 2016-4-4 15:21
个人感觉,既然用C语言了,就不要涉及到编译器编译层面上的控制,比如指定寄存器组(Using)。提高执行效率 ...

谢谢你的解答,我在Keil的帮助文档中查找到的结果与你说的大致相近,Keil建议在没有禁用“寄存器绝对寻址方式”时,在中断服务程序中调用的函数一定要保证与中断服务函数使用相同的寄存器组,否则会发生不生预知的异常,从我上面贴出来的汇编代码中可以看到,寄存器的使用确实是发生了混乱,一会是寄存器组2中的寄存器,一会是寄存器组0(中断外使用的)中寄存器。由于我要使用模拟串口,139us要进一次定时器中断,而我所使用的单片机pop和push指令需要费5个时钟周期,同时中断函数里在接收和发送都在进行的时候会执行比较多的代码,因此才会指定寄存器组2以弥补一点时间损失。现在我的解决办法就是在中断服务函数中要调用的函数,我会给函数指定一样的寄存器组,这样就可以了;还有就是禁用“寄存器绝对寻址”功能,虽然兼容性会好一些,但是程序效率会软低,我就选择合适自己的就好。再次谢谢你。
点赞  2016-4-6 10:28
电子工程世界版权所有 京B2-20211791 京ICP备10001474号-1 京公网安备 11010802033920号
    写回复