C51填坑记:中断处理导致主程序函数参数改变

1.现象

平台:keil c51,中颖SH79F7019A

现象:在增加了一个中断处理逻辑后,发现主程序异常,断点调试发现某个函数的参数被改变了,程序使用了错误的数据导致逻辑出错。

2.排查

初步分析,可能原因如下:

1.参数寄存器(R0-R7)的值,被中断函数改变。

2.堆栈溢出。

2.1参数寄存器

首先排查参数寄存器(中断里面调用了函数,有参数传递)。通过仿真器观察中断函数汇编代码,发现在进入中断之前是对R0-R7进行了压栈操作的。进一步将中断有关的函数全部用“using”关键字知道不同的寄存器组,发现问题没有得到改善。到此可以排除参数寄存器原因。

2.2堆栈溢出

排除寄存器之后,进一步想到可能是堆栈溢出,导致数据错乱。由于51单片机栈空间是有限的(只能使用片内RAM,编译器将片内变量分配好之后,将剩余空间作为栈空间使用,参考:https://wenku.baidu.com/view/7659066ffbd6195f312b3169a45177232e60e472.html)。如果函数调用层数过多,加之中断本身需要保护现场,可能导致栈溢出。

于是将断点设置在最底层的函数,观察堆栈寄存器“SP”的值。结果发现被没有发生溢出。因此可以排除。

2.3回到原点

排除了上面的原因之后,只能回到最开始出异常的地方继续跟踪调试。发现该函数参数并不是直接通过寄存器传递,而是保存内存里面(图1),后续使用的时候,去里面读取。

图1

于是查看map文件(根据所选编译器不同可能是m51文件,这个文件有详细内存分配信息)发现在很多变量都放在了这个地址(这是C51编译器的特性之一,C51的局部变量在编译的时候就分配好了,编译器将他认为可以复用的变量放在同一地址,已到达节省空间的目的),其中就有在中断中调用的函数的形参(图2)。

图2

当中断运行的时候,这个内存地址的数据被修改了,而这个地址内容是不会被压栈的(只会对关键的寄存器值进行压栈)。这就导致回到主程序后,继续使用这个地址的函数取到错误的数据。从而出现错误。

3.C51参数传递

3.1参数

顺带提一下C51的参数传递规则。在C51中函数传递参数的总体思想是:尽量使用寄存器传递参数,万不得已时采样编译器分配的固定位置传递,对于可重入函数(使用“reentrant”关键字修饰的函数)将会使用模拟堆栈传递(这种方式会消耗更多的RAM)。

寄存器参数传递:寄存器参数传递指参数通过寄存器R1~R7来传递的,这种形式可产生高效的代码,利用51单片机的工作寄存器最多传递3个参数

 

固定位置传递:如果不适用寄存器来传递参数、或者参数过多、或者无法用寄存器传递的数据类型(bit类型),则需要通过编译器分配的固定位置进行传递。此外,如果第一个参数时bit类型,那么所有参数将使用固定位置传递。

3.2返回值

C51返回值最多只有一个。传递方式如下:

4.深入挖掘 

要解决这个问题,需要了解编译器分配变量的机制。前面提到的不同变量使用相同的地址,主要时由于编译器使用了数据覆盖(Overlay)技术(参考:http://www.keil.com/support/man/docs/lx51/lx51_overlaying.htm)。编译器将函数参数和局部比变量保存在固定的内存区域。通过分析程序结构,编译器生成函数调用树。然后根据调用树决定那些变量可被覆盖。不同调用树使用的内存区域时分开的,不会重叠。程序编译完成后,完整的调用树可以在“Objects”目录下的“map”文件查看(如果时BL51编译器,则是“m51”)。该文件是否生成需要在编译选项“Listing”里面设置。

一般情况下,编译器可以自动处理调用关系。但是以下情况则需要特殊处理:

1.使用了间接调用(函数指针)。

2.使用了函数指针数组。

3.递归调用

4.重入函数(一个函数会被多个任务调用,即数据两个或两个以上的调用树)

对于这些情况,需要手动配置调整调用关系。

本文所遇到的问题,就是属于第1、2中情况。在中断函数中使用了函数指针进行间接调用,导致编译器无法正确解析调用关系,从而导致两个函数参数地址重叠。

4.1 调整调用树

编译器提供了“OVERLAY”指令来手动调整函数调用关系(参考:http://www.keil.com/support/man/docs/lx51/lx51_overlay.htm)。语法如下:

1.增加新的跟节点(“sfname”表示函数名称,可在map文件查看)

OVERLAY (* ! sfname)

2.移除根节点

OVERLAY (sfname ! *)

3.移除调用关系(“caller”表示调用者,“collee”表示被调用者)

OVERLAY (sfname-caller ~ sfname-callee)
OVERLAY (sfname-caller ~ (sfname-callee, sfname-callee))

4.添加调用关系(“caller”表示调用者,“collee”表示被调用者)

OVERLAY (sfname-caller ! sfname-callee)
OVERLAY (sfname-caller ! (sfname-callee, sfname-callee))

在keil中调整调用关系方法如下:

打开设置选项,选择“LX51 Misc”,然后在“Overlay”里面输入规则即可,也可以勾选“use linker control file”。这个编译器控制文件其实就是个后缀为“.lin”的文本文件。可以点击“create”创建一个,然后添加规则即可。 

5 总结

这个问题原因是在使用了函数指针导致编译器不能正确的解析调用关系,从而出现参数地址重叠。

使用中断的时候,需要注意的问题:

1.中断和主程序属于不同的调用树,尽量不要调用同一个函数,如果不可避免,需要声明为重入函数。

2.如果使用了函数指针,需要手动维护函数调用关系。

6.参考链接:

C51中函数参数传递个数:http://www.51hei.com/bbs/dpj-31890-1.html

C51单片机堆栈深入剖析:https://wenku.baidu.com/view/7659066ffbd6195f312b3169a45177232e60e472.html

Keil C 堆栈溢出【转至:半岛渔翁的博客】:https://blog.csdn.net/zhang2005/article/details/46341121

C51 Product Manuals: http://www.keil.com/support/man_c51.htm

LX51 Data Overlaying: http://www.keil.com/support/man/docs/lx51/lx51_overlaying.htm

OVERLAY Linker Directive: http://www.keil.com/support/man/docs/lx51/lx51_overlay.htm

The Call Tree: http://www.keil.com/support/man/docs/lx51/lx51_ol_calltree.htm

 

  • 6
    点赞
  • 20
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值