标题: 请教各位大神一个汇编语言堆栈大小计算的问题 [打印本页]

作者: newlined    时间: 2022-7-12 06:34
标题: 请教各位大神一个汇编语言堆栈大小计算的问题
我有一个主程序A(是51单片机的汇编),在STC 8H上跑,这个主程序A调用了子程序A1,A1又调用了A2,A2又调用了A3,即A->A1->A2->A3。中断用了5个,可以定义成中断B,C,D,E,F。其中中断B,C是高级中断,D,E,F是低级中断。所有中断中都是PUSH ACC,PUSH PSW,没有再PUSH其他寄存器,出中断时POP PSW ,POP ACC。中断B中,调用了子程序B1,B1又调用了B2即B-B1->B2。C中断中没有调用其他子程序。D中断中调用了子程序D1,D1又调用了D2,即D->D1-D2。E中断中调用了子程序E1,E1又调用了E2,E2又调用了E3,即E->E1->E2-E3。所有子程序都是简单的对单片机的端口进行操作,即置高或低,子程序没有用堆栈传递参数。按照我的理解,考虑一个最复杂的情况,主程序在调用A3时,堆栈用了6个字节,这时发生E中断,E中断在调用E3时,堆栈用了8个字节,这是发生B中断,在B中断调用B2时,堆栈用了6个字节,这样堆栈最多用20个字节就可以了,但在实际情况中,单片机不时会死机.这个程序原先不是我写的,我不过做了一点修改,即在中断E中加了一个子程序E3。以前堆栈留的比较小,只有20个字节,后来我又找了几个加上了,但还是不大。现在我怀疑是堆栈溢出了,请问我堆栈大小的计算对吗?衷心感谢

作者: datouyuan    时间: 2022-7-12 12:03
1.51的任何子程序都需要通过堆栈传递PC值,最少2字节。
2.51的中断子程序通过堆栈传递PC、ACC、PSW ,最少4字节。
3.中断内部调用子程序算情形1,最少2字节。

你的代码有2级中断调用,情形1调用算6级,所以最少20字节(2*4+6*2)。
但上述是最理想的情况。我们一般按10级情形1算,但情形1算5字节,预留50字节堆栈。
作者: Y_G_G    时间: 2022-7-12 12:26
8051是软件堆栈,如果不是刻意去填满它,堆栈基本上是不会有问题的
卡死的问题不一定是堆栈的问题,可能是中断太多,而且中断触发间隔时间太短
比如说:
1,ADC中你调用了某个子程序,ADC执行的总时长是5mS,ADC中断你又设定成最高优先等级,而ADC中断间隔是1mS
2,定时器中断是2mS的
那么,这个程序就一直在ADC和定时器之间运行了,主程序基本是不会执行的
作者: newlined    时间: 2022-7-12 14:21
datouyuan 发表于 2022-7-12 12:03
1.51的任何子程序都需要通过堆栈传递PC值,最少2字节。
2.51的中断子程序通过堆栈传递PC、ACC、PSW ,最少 ...

谢谢您的回复,我算的是最多需要20字节,您算的最少需要20字节
我是这样理解的同为高级的2个中断B,C,同一时刻只能有一个中断响应,C中断响应,堆栈占用2个字节,B中断响应,最多占用6个字节,所以按B中断记,D,E,F三个低级中断,它们三个同一时刻只有1个被CPU响应,主贴中忘记说了,F中断内没有子程序,按子程序调用最深的E中断算,低级中断最多需要8个字节,主程序中子程序调用最多需要6个字节,这样需要20个字节就可以了。按照您的计算,我肯定是哪里理解错了,错在那里,麻烦您再说一下,我在这方面是半路出家,有些方面理解不透,谢谢。
作者: newlined    时间: 2022-7-12 14:28
Y_G_G 发表于 2022-7-12 12:26
8051是软件堆栈,如果不是刻意去填满它,堆栈基本上是不会有问题的
卡死的问题不一定是堆栈的问题,可能是中 ...

谢谢您的回复,我明白了您的意思,您是说,中断触发间隔的时间短,而中断本身执行的时间长,结果就是CPU在中断之间运行,一个中断执行完,马上又去响应另一个中断,然后又去响应前一个中断,没有时间去执行主程序,我按您的思路拿示波器跟踪下,看看每一个中断需要多少时间。
作者: hb_lhw    时间: 2022-7-12 15:00
每次中断,自动入栈一个PC寄存器,具体几个字节我忘了,然后中断返回的时候再自动出栈,楼主没考虑这个,所以可能会乱。
作者: newlined    时间: 2022-7-12 15:24
hb_lhw 发表于 2022-7-12 15:00
每次中断,自动入栈一个PC寄存器,具体几个字节我忘了,然后中断返回的时候再自动出栈,楼主没考虑这个,所 ...

您说的应该是程序寄存器,也就是PC寄存器,它是16位的,占用2个字节,按说我已经考虑到了。
作者: newlined    时间: 2022-7-12 15:27
datouyuan 发表于 2022-7-12 12:03
1.51的任何子程序都需要通过堆栈传递PC值,最少2字节。
2.51的中断子程序通过堆栈传递PC、ACC、PSW ,最少 ...

我刚才又想到,是不是中断嵌套时,堆栈需要消耗更多的字节?中断嵌套时堆栈消耗的字节是怎样计算的?
作者: newlined    时间: 2022-7-12 15:44
本帖最后由 newlined 于 2022-7-12 15:47 编辑
Y_G_G 发表于 2022-7-12 12:26
8051是软件堆栈,如果不是刻意去填满它,堆栈基本上是不会有问题的
卡死的问题不一定是堆栈的问题,可能是中 ...

您好,我刚才拿示波器跟踪了一下,一共5个中断,2个中断是1毫秒一次,一个外部中断是20毫秒到100毫秒一次,这3个1个请问-中断的时间在5微妙到10微妙之间,应该没有问题。还有一个是30毫秒1次,执行时间是10微秒,也没有问题,执行时间比较长的是一个掉电中断,是高级中断,执行时间10毫秒,这个中断执行完就不再执行其他语句了,单片机就等着掉电了。按说也不影响主程序的运行
作者: 188610329    时间: 2022-7-12 16:25
是不是堆栈大小的问题, 用KEIL 编译后,仿真一下,看一下 SP_max 就知道了。有什么好算得?

我现在好奇的是,你确定是堆栈问题? 你那么多中断 你居然   都是只 PUSH ACC 和 PSW 的? 你不用 R0~R7 的? 不用 B 的?  不用 DPTR 的? 这些只要有一个,你该PUSH 的没有PUSH 都会有可能造成发生过 中断后,程序出现错误,最终跑飞。
作者: Y_G_G    时间: 2022-7-12 16:56
newlined 发表于 2022-7-12 14:28
谢谢您的回复,我明白了您的意思,您是说,中断触发间隔的时间短,而中断本身执行的时间长,结果就是CPU ...

大概就是这么个意思了
也就是说程序是在中断之间来回跑的
主程序几乎没有执行的时间
为什么要在中断中执行那么多功能呢?能不能放在主程序执行呢?
作者: newlined    时间: 2022-7-12 17:03
Y_G_G 发表于 2022-7-12 16:56
大概就是这么个意思了
也就是说程序是在中断之间来回跑的
主程序几乎没有执行的时间

这个程序是20年前别人编写的,我现在接手维护,整个程序还没有吃透。
作者: newlined    时间: 2022-7-12 17:13
Y_G_G 发表于 2022-7-12 16:56
大概就是这么个意思了
也就是说程序是在中断之间来回跑的
主程序几乎没有执行的时间

大约1小时前回复过,不知道回帖为什么没有了。今天下午我拿示波器跟踪了各个中断的执行时间,有2个是1毫秒1次的中断,执行时间在10微妙左右,1个是30毫秒的中断,执行时间是10微妙多一点,一个外部中断,20毫秒到100毫秒一次,执行时间在8微妙左右,应该都可以,不会引起主程序执行时间的不足,一个掉电中断,是高级中断,执行时间10毫秒,这个按说也不影响主程序的运行,因为这个中断执行完,就等着掉电了,不再执行其他语句了。
作者: newlined    时间: 2022-7-12 17:18
188610329 发表于 2022-7-12 16:25
是不是堆栈大小的问题, 用KEIL 编译后,仿真一下,看一下 SP_max 就知道了。有什么好算得?

我现在好奇 ...

这个程序是20年前别人写的,我接手维护,小改下,不好动架构。
作者: datouyuan    时间: 2022-7-12 17:23
newlined 发表于 2022-7-12 14:21
谢谢您的回复,我算的是最多需要20字节,您算的最少需要20字节
我是这样理解的同为高级的2个中断B ...
考虑一个最复杂的情况,主程序在调用A3时,堆栈用了6个字节,这时发生E中断,E中断在调用E3时,堆栈用了8个字节,这是发生B中断,在B中断调用B2时,堆栈用了6个字节,这样堆栈最多用20个字节就可以了

A到A3,是函数调用3级,最少6字节。
E中断,是中断函数调用,最少4字节。
E到E3,是函数调用3级,最少6字节。
B中断,是中断函数调用,最少4字节。
B到B2,是函数调用2级,最少4字节。
最少24字节。

不需要按上述计算,堆栈就是按函数调用深度和中断函数调用深度估算。
函数调用深度8级,最少占用16(2*8)字节。
中断函数调用深度2级,最少占用8(4*2)字节。
我提到的3点原则只考虑了PCH PCL ACC PSW的保存、恢复,是最低要求的情形。有些应用还要考虑DPTR、B、R0~R7的保存、恢复,所以堆栈需求会明显大于24个字节。
作者: datouyuan    时间: 2022-7-12 17:35
C51中断中使用函数是很不好的习惯。
你把中断中的函数改成不是函数,以代码空间换时间和RAM。
应该能解决问题。
作者: Y_G_G    时间: 2022-7-12 18:20
newlined 发表于 2022-7-12 17:13
大约1小时前回复过,不知道回帖为什么没有了。今天下午我拿示波器跟踪了各个中断的执行时间,有2个是1毫 ...

所谓"死机"和中断无法跳出,这不过是推测而已,说不定可能就是其它的小问题而已
汇编是很容易出问题的
如果说是以前的代码是可以量产的,就说明以前的代码是没有问题的
重点看你的代码会影响到哪些
如果不是什么商业机密,就把完整代码上传,大家一看就知道了
作者: newlined    时间: 2022-7-13 13:32
188610329 发表于 2022-7-12 16:25
是不是堆栈大小的问题, 用KEIL 编译后,仿真一下,看一下 SP_max 就知道了。有什么好算得?

我现在好奇 ...

您好,以前的程序就是没有对DPTR保护。看了您的发言后,我看了下程序,里边的确用了DPTR,不知道为什么程序还可以运行。工作寄存器的4个区,有1个区是2个低级中断公用的,这2个中断不会同时响应,只用了R0和R1,每次用之前,都要先赋值,访问间接寻址的空间,所以不会冲突。B寄存器也用了,不知道为什么没有保护。这个程序大几千行,没有文字的介绍资料,注释也很少,估计不止一人维护过.我还远没有吃透.
作者: 188610329    时间: 2022-7-13 13:44
newlined 发表于 2022-7-13 13:32
您好,以前的程序就是没有对DPTR保护。看了您的发言后,我看了下程序,里边的确用了DPTR,不知道为什么程 ...

程序没吃透的前提下, 最不变应万变的做法,就是中断里用了了什么, 进入中断前就 PUSH什么, 出中断前 POP什么, 这是最笨,但也是最稳妥的办法。 等到吃透之后,可以选择性的 PUSH 和 POP
作者: newlined    时间: 2022-7-13 13:47
datouyuan 发表于 2022-7-12 17:35
C51中断中使用函数是很不好的习惯。
你把中断中的函数改成不是函数,以代码空间换时间和RAM。
应该能解决 ...

您好,您这一说,我意识到一个问题,函数重入,这个程序编译时曾经出过这个警告,后来我把一个函数复制了一份,另起了一个名字,供不同的子程序还是中断调用,现在是不是还有这个问题?在keil c中也不能这么用吗?
作者: datouyuan    时间: 2022-7-13 15:12
newlined 发表于 2022-7-13 13:47
您好,您这一说,我意识到一个问题,函数重入,这个程序编译时曾经出过这个警告,后来我把一个函数复制了 ...

一般的C51函数是不支持再入的。
一个函数复制了一份,另起了一个名字

可以这样解决(这个函数内部不能有函数),但你必须确保业务逻辑不会有问题。

中断调用的函数一般只被中断调用,所以没有必要写成函数。
作者: newlined    时间: 2022-7-13 15:42
datouyuan 发表于 2022-7-12 17:35
C51中断中使用函数是很不好的习惯。
你把中断中的函数改成不是函数,以代码空间换时间和RAM。
应该能解决 ...

您说的对,开始我并没有理解您的意思,刚才看中断中的函数,我想到,假设在中断中,我使用了R0,然后调用函数,在函数中我又使用了R0,从函数返回后,R0的值肯定变了.好在我用R0都是用来访问间接寻址的地址,随用随赋值,要是用R0保存某个数据的话,调用函数前,可不可以PUSH到堆栈,调用函数后再POP?
作者: datouyuan    时间: 2022-7-13 15:52
newlined 发表于 2022-7-13 15:42
您说的对,开始我并没有理解您的意思,刚才看中断中的函数,我想到,假设在中断中,我使用了R0,然后调用 ...

当然可以。但这又会增加堆栈需求。
汇编代码要自己考虑如何保护现场,恢复现场。

C51编译器能根据上下文,自动做好保护现场,恢复现场。
好在我用R0都是用来访问间接寻址的地址,随用随赋值

你这种做法没有用。这个操作不是原子操作,中断会发生在操作中间,发生中断后,不能恢复现场。
作者: newlined    时间: 2022-7-13 16:11
datouyuan 发表于 2022-7-13 15:52
当然可以。但这又会增加堆栈需求。
汇编代码要自己考虑如何保护现场,恢复现场。

您是说中断自己不能恢复现场,需要人为的PUSH,POP来恢复现场?而函数调用,C51的编译器可以调用函数时,用堆栈保存PC的值,调用完,函数执行到RET,PC的值自动恢复,其他寄存器的值也自动恢复?
作者: datouyuan    时间: 2022-7-13 17:12
newlined 发表于 2022-7-13 16:11
您是说中断自己不能恢复现场,需要人为的PUSH,POP来恢复现场?而函数调用,C51的编译器可以调用函数时, ...

只有PC值能自动恢复,其它值得恢复要靠软件操作。
作者: newlined    时间: 2022-7-13 17:22
是这样,明天我仿真下,再仔细领会您的意思。
作者: 188610329    时间: 2022-7-13 18:22
newlined 发表于 2022-7-13 16:11
您是说中断自己不能恢复现场,需要人为的PUSH,POP来恢复现场?而函数调用,C51的编译器可以调用函数时, ...

你是不是理解 混了?
你如果写汇编的话, 是不用C51 编译器, 用A51 编译器的。
C51 的话, 函数是可以做成重入函数的, (即便他编译报了有重入风险)。
而 A51 的话,你只要用好了 PUSH POP 函数也可以做成  重入的。比如: 被调用函数,用到 R0, 你只要函数入口 PUSH AR0, 出口 POP AR0  那么,这个函数,你中断调用了,一样现场会被保护。
作者: newlined    时间: 2022-7-14 08:26
Y_G_G 发表于 2022-7-12 18:20
所谓"死机"和中断无法跳出,这不过是推测而已,说不定可能就是其它的小问题而已
汇编是很容易出问题的
如 ...

您好,这个可能不方便贴出,随说是20年的程序,但老板交代过。
经 188610329大神提醒,我DPTR没有保护造成的,但这个寄存器以前就没有保护,可能是我修改了程序,不保护不行了。汇编是很麻烦,要面面俱到。、z*cx您好,这个可能不方便贴出,随说是20年的程序,但老板交代过。

作者: newlined    时间: 2022-7-14 08:39
您好,是这样,程序是汇编的,扩展名是ASM,但是在KEIL C下编译的,我注意到一个问题,在KEIL C下新建一个汇编工程,它的扩展名是A51,我怀疑这个程序最开始不是在KEIL C下编译的,后来转到KEIL C下,KEIL C内部是不是有A51的编译器?
作者: newlined    时间: 2022-7-14 08:45
Y_G_G 发表于 2022-7-12 18:20
所谓"死机"和中断无法跳出,这不过是推测而已,说不定可能就是其它的小问题而已
汇编是很容易出问题的
如 ...

您好,刚才回复过,提交时不小心按错了键,出现了乱码,可能审核不通过。
这个程序,虽说是20年前的了,但老板有交代,不方便贴出。
经188610329 大神提醒,我怀疑是DPTR没有保护造成的,我补充了一些代码,可能是不保护不行了。
作者: newlined    时间: 2022-7-14 09:57
datouyuan 发表于 2022-7-13 17:12
只有PC值能自动恢复,其它值得恢复要靠软件操作。

刚才仿真了下,只看的R0,在中断下,给它赋一个值,比如说5,然后调用一个函数,在函数中对R0修改为8,函数执行完后,再返回调用它的那个中断,发现R0的值还是8,看来在中断中使用函数真不是一个好的习惯。
作者: Hephaestus    时间: 2022-7-14 19:44
你的软件用到了idata了吗?
作者: 188610329    时间: 2022-7-14 19:55
newlined 发表于 2022-7-14 08:39
您好,是这样,程序是汇编的,扩展名是ASM,但是在KEIL C下编译的,我注意到一个问题,在KEIL C下新建一个 ...

KEIL 就是编译器,
KEIL C51 是在KEIL 下的 51单片机 用的C语言
KEIL A51 是在KEIL 下的 51单片机 用的A语言
你既然是 用的汇编,后缀还是 ASM 就肯定是用的 A51 编译,怎么可能用 C51编译?
两者编译方式完全不一样, KEIL中的提示也不一样,一个是: compiling xxxxxxx.C...  一个是:assembling xxxxxxx.ASM...
作者: 188610329    时间: 2022-7-14 20:06
newlined 发表于 2022-7-14 09:57
刚才仿真了下,只看的R0,在中断下,给它赋一个值,比如说5,然后调用一个函数,在函数中对R0修改为8,函 ...

如果,你进入中断,用的是不同的寄存器组, 那么,你对 R0 的操作,是不会影响 主程序的 R0的,因为,此 R0 不是 彼R0,  相对于这个问题,我反而觉得,你应该是 对某些寄存器的 现场保护没有做好。你新加的内容才是关键。
而且,你说的部分内容,我觉得非常困惑,“以前堆栈留的比较小,只有20个字节,后来我又找了几个加上了,但还是不大。” 你这个  只有20字节是什么来的? 又找了几个加上 是怎么加的? 正常情况下, 一般分配完内存地址后, 我们就在  内存末尾 打上 Stack,作为堆栈的起点,赋值给SP, 假定SP 为 80H, 那么 从80H 往后 到 0FFH 相当于都是 堆栈用的。 所以不存在原来 堆栈只有多少,然后你还“找了几个”的情况存在。 所以,你描述的这个情况,到底是什么情况?
作者: Y_G_G    时间: 2022-7-14 20:43
newlined 发表于 2022-7-14 08:26
您好,这个可能不方便贴出,随说是20年的程序,但老板交代过。
经 188610329大神提醒,我DPTR没有保护造 ...

DPTR,R0,R1什么的,你在多个地方用到了,就进行保护,如果用不到就不用管
汇编子程序的原则是你在调用的时候,如果在其它地方你也用到某个地址的RAM.那就进行保护,每个子程序都要保护
常用的就是R0R1之类,比如DELAY:               
        PUSH R0        PUSH R1
        MOV R0,#100
        MOV R1,#100
NEXT:
        DJNZ R1,NEXT
        DJNZ R0,NEXT
        POP R1
        POP R0
        RET

这就是一个延时程序,这样的话,你在其它的程序中包括中断,再使用R0R1,也同样的PUSH,POP,那么这个程序就不会出问题
假设你整个完整的程序中,只有这一个地方用到R0R1,那么,这个PUSH,POP就是多余的
还有R0-R7这几个地址,默认的情况下,在整个程序地址是固定的,你在任何一个地方修改了R0-R7其中一个的值,它在其它地方的也是会改变的
假设你延时中用到了R0,中断中也用到R0,那么当延时程序被中断打断之后,如果不用PUSH,POP保护R0的话,等到中断中修改了R0之后
RETI返回之后,R0的值就是中断中最后操作的值,那么,你这个延时程序就出错了
你不要都是想着堆栈满不满的,基本不会的,完全可以先不管堆栈的大小问题
专注找你自己代码的问题

作者: 188610329    时间: 2022-7-14 21:07
Y_G_G 发表于 2022-7-14 20:43
DPTR,R0,R1什么的,你在多个地方用到了,就进行保护,如果用不到就不用管
汇编子程序的原则是你在调用的时 ...

R0~R7 属于 通用寄存器, 是不能PUSH的。
如果是完全自己写的代码,配合USING 可以用 PUSH AR0~AR7 方式来PUSH 但是,如果不是自己的代码,而4组寄存器 一直轮换在用的话,非常不好处理。
所以,还是比较建议楼主多注重一下,改了部分的代码,到底涉及到哪些东西,针对性的处理一下,而不是去动那些既存的,由来已久的代码。
简易楼主,全程序  查找一下, "MOV  PSW,#"   看看具体用了几组 通用寄存器,为了影响最小化,假定,之前只用了 3组寄存器,建议楼主加的部分代码全都用 第四组 通用寄存器,这样,可以把影响降到最低。
作者: Hephaestus    时间: 2022-7-14 21:41
函数的好处是可以复用,一处定义多处调用,节约程序空间。

但是对于大多数8位单片机编译器,包括C51,没有按照标准c语言的做法——在进入函数的时候在堆栈上临时分配局部变量(具体做法可以看《数据结构》关于递归的那一部分),因为8位机间接寻址指令和空间非常有限,按标准c语言的做法,最后生成的机器码会非常庞大,占用的RAM空间也非常多,对本来就很少的资源造成巨大浪费。

C51的做法是对函数调用关系进行分析,然后静态分配变量,以楼主的问题为例A->A1->A2->A3,那么A的局部变量可能是27H~29H,A1的局部变量占用2AH~2FH,A2的局部变量占用30H~35H,A3的局部变量占用36H以后空间。这种做法导致中断调用的函数是无法复用的!比如中断B调用了函数B1,B1的局部变量占用33H这个空间,那么如果主程序调用B1,局部变量在运算过程中发生了中断,中断B1修改了局部变量,那么中断结束后,回到主程序,局部变量33H的内容被修改了,那么主程序显然就会执行错误。同样的原因,不仅主程序不能调用B1,其他中断C、D也不能调用B1!!!这个函数B1是中断B专用的。

综上所述,使用函数,可以复用的优点在中断这里不存在!在中断中调用函数只有各种各样的缺点,一点好处都没有,楼主为什么要这么做?
作者: Y_G_G    时间: 2022-7-14 22:01
188610329 发表于 2022-7-14 21:07
R0~R7 属于 通用寄存器, 是不能PUSH的。
如果是完全自己写的代码,配合USING 可以用 PUSH AR0~AR7 方式 ...

好久不用汇编了,忘记了
作者: newlined    时间: 2022-7-15 10:58
Hephaestus 发表于 2022-7-14 19:44
你的软件用到了idata了吗?

您好,用到了
作者: newlined    时间: 2022-7-15 11:08
Hephaestus 发表于 2022-7-14 21:41
函数的好处是可以复用,一处定义多处调用,节约程序空间。

但是对于大多数8位单片机编译器,包括C51,没 ...

您好,在中断中有十几行语句,用了几次,早先我不知道函数调用会产生一些列的问题,就把它们写成了函数.前边datouyuan大神也已经指出了.
作者: newlined    时间: 2022-7-15 11:15
188610329 发表于 2022-7-14 21:07
R0~R7 属于 通用寄存器, 是不能PUSH的。
如果是完全自己写的代码,配合USING 可以用 PUSH AR0~AR7 方式 ...

您好,四组寄存器都已经用到了,其中有1组还是有2个同级别的低级中断共用的.考虑到这2个低级别中断不会同时被单片机响应,也没有用它们传递参数,只是用R0和R1访问间接地址,应该是不会冲突吧。

作者: Y_G_G    时间: 2022-7-15 14:06
我感觉你还是把代码上传上来吧
既然你说是多年前的程序,那就说明以前的代码是对的,问题估计就是出现你自己的代码上
把你自己的代码上传就行,不用把整个程序都上传的
你的代码在程序中是哪里调用的,如果是中断,就把中断中调用的位置和代码上传就行
MOV R0,#20H  之类的,它总不会连这也算商业机密吧
现在都是一群人在乱猜,压根就不知道你程序一个大概的流程
很有可能这只是一个简单的问题而已,却搞得那么复杂
作者: 188610329    时间: 2022-7-15 15:34
newlined 发表于 2022-7-15 11:15
您好,四组寄存器都已经用到了,其中有1组还是有2个同级别的低级中断共用的.考虑到这2个低级别中断不会同 ...

看你另外一个帖子, 你再找单片机原理的书? 所以,问题出在,你并不理解你的单片机?
我之前34楼的问题,你也没有回复,所以,我也不知道该从哪里可以帮到你,但是,个人觉得堆栈的问题,概率不大。考虑到你可能存在的“重入”问题,简单跟你说几个关键原理。
CSEG AT 0000H
LJMP  MAIN
CSEG AT 0003H
LJMP  EX0_INT

MAIN:
; 此处省略外部中断初始化程序
MAIN_Loop:
LCALL   Delay
SJMP   MAIN_Loop

EX0_INT:
PUSH   PSW
MOV    PSW,#08H
USING   1
LCALL  Delay
POP    PSW
RETI

Delay:
MOV   R7,#100H
DJNZ  R7,$
RET

这个例子,完全是为了 举例子而举例子。

我们可以看到  Delay 这个函数, 重入了, 但是,有影响么? 没有!!!, 只要确保  主程序所在 寄存器组, 不等于 中断使用的寄存器组, R0~R7, 是不会受 重入影响的。

我们再看这个例子:
CSEG AT 0000H
LJMP  MAIN
CSEG AT 0003H
LJMP  EX0_INT

MAIN:
; 此处省略外部中断初始化程序
MAIN_Loop:
LCALL   Delay
SJMP   MAIN_Loop

EX0_INT:           ;这次,中断里我们不切换寄存器
;PUSH   PSW
;MOV    PSW,#08H
;USING   1
LCALL  Delay
;POP    PSW
RETI

Delay:
USING    0            
PUSH   AR7             ; 函数内部,保护现场, 这也是常规意义的, “可重入函数”
MOV   R7,#100H
DJNZ  R7,$
POP    AR7
RET


这个例子,我们依然 重入了,   但是,有影响么??, 没有!!!!
所以,重入问题,是完全可以  靠人解决的, 编译器,绝对不会比人“更高级”,你既然现在在折腾汇编代码,就不要考虑 C 可能的问题,毕竟C 你是依赖编译器的,而,汇编,你只能依靠你自己(因为你的代码要“保密”,所以你也靠不到我们)。


基本上,目前从你这里得到的信息,只能帮到你这里了, 当然,如果你回复了 我在34楼的问题,也许可以给多你一点支持。


作者: Hephaestus    时间: 2022-7-15 20:13
现在越来越混乱了。楼主在20楼说重入警告,这是c51独有的,汇编的重入问题完全由用户控制,根本不可能出现警告,然后楼主又说用汇编。楼主连自己用的是个啥都不知道,大家还是散了吧。
作者: newlined    时间: 2022-7-16 08:06
Y_G_G 发表于 2022-7-15 14:06
我感觉你还是把代码上传上来吧
既然你说是多年前的程序,那就说明以前的代码是对的,问题估计就是出现你自己 ...

是这样,是一家小公司,硬件都在那里,大家都可以看到,软件是这家公司的支柱,老板有交代,不好贴,请大家原谅。在在大家的指导下,把DPTR保护后,跑了1天多,软件不死机了,谢谢大家。
作者: newlined    时间: 2022-7-16 08:12
本帖最后由 newlined 于 2022-7-16 08:42 编辑
188610329 发表于 2022-7-15 15:34
看你另外一个帖子, 你再找单片机原理的书? 所以,问题出在,你并不理解你的单片机?
我之前34楼的问题,你也 ...

这一段程序我看懂了,函数重入后,对这个函数用到的寄存器,函数被调用之前用到,函数调用完成后,还要用到的要保护后,就不会出问题。有了问题后,总是漫天找问题。
作者: newlined    时间: 2022-7-16 08:24
Hephaestus 发表于 2022-7-15 20:13
现在越来越混乱了。楼主在20楼说重入警告,这是c51独有的,汇编的重入问题完全由用户控制,根本不可能出现 ...

不好意思,最近记忆力严重衰退,连老板都笑话我,也许我记串了。有一次,一个问题我们两个人,商讨后解决了,过了几天,我说那个问题怎么办,老板说我们两个商量后不是已经解决了吗,如何如何解决的,我听后好久才回忆起来。
对51的汇编,我是边学边用,实际上,我一般是先用KEIL C写出来,运行通过后再按照这个思路,改成汇编。这个汇编程序中,有一个函数,主程序和中断都调用,也许是出问题,我就复制了一份,另起一个名字,函数内部用到的直接寻址的地址,用另外的地址,运行通过了。
作者: lksbbs    时间: 2022-7-16 08:38
这么复杂的程序,空间够用的情况下直接使用全局变量不好么,随便你咋中断,随便你咋调用,我都不用压堆的.
作者: newlined    时间: 2022-7-16 08:42
188610329 发表于 2022-7-14 20:06
如果,你进入中断,用的是不同的寄存器组, 那么,你对 R0 的操作,是不会影响 主程序的 R0的,因为,此  ...

程序中,四个工作区都用到了,其中两个低级中断共用一个区,因为不会同时被单片机响应,不会出问题。以前的堆栈,设在50H到64H之间,64H到80H有的地址用了,有的还空着,我就把用的改在一起,空出来的跟64H连在一起做堆栈.
作者: Hephaestus    时间: 2022-7-16 08:51
newlined 发表于 2022-7-16 08:42
程序中,四个工作区都用到了,其中两个低级中断共用一个区,因为不会同时被单片机响应,不会出问题。以前的堆 ...

越来越混乱了,我在32楼问你用到idata了吗?你在39楼回答用到了。只有用到了80h以上空间才会用到idata,而C51编译器会自动把堆栈分配到idata上面。如果你用汇编的话,也应该利用高128字节来做堆栈,然而你又说堆栈在50h和64h之间???到底是怎么写的?这种空对空的说根本解决任何问题,按42楼建议,如果原程序是商业机密你不可以贴,但是你自己写的那部分,你拿捏不准的地方不贴出来,没有讨论的价值,也不太可能有人能提出关键性建议。
作者: datouyuan    时间: 2022-7-16 10:13
本帖最后由 datouyuan 于 2022-7-16 10:22 编辑
newlined 发表于 2022-7-16 08:42
程序中,四个工作区都用到了,其中两个低级中断共用一个区,因为不会同时被单片机响应,不会出问题。以前的堆 ...

看来你这代码问题很大。
51的堆栈要安排在ram的最后。
例如你需要32字节堆栈,那么堆栈空间为0xe0~0xff。初始化SP要小于等于0xe0。
例如你需要48字节堆栈,那么堆栈空间为0xd0~0xff。初始化SP要小于等于0xd0。
小于0x80区域(data区),可以直接寻址,变量尽量安排在此区域。
>=0x80区域(idata区),只能间接寻址,变量在data区安排不下时(重要!!!),将数组变量、使用不频繁的变量安排在此区域。

编写汇编代码时,要确定所有变量使用了多少字节,例如使用了150个字节,那么SP应等于150,那么堆栈空间为150~0xff(共106字节)。
这帖子中,我提到的仅仅是些基本原则,估计楼主独立解决应该比较困难,建议楼主将代码交给有经验的帮忙。

作者: datouyuan    时间: 2022-7-16 11:58
newlined 发表于 2022-7-16 08:24
不好意思,最近记忆力严重衰退,连老板都笑话我,也许我记串了。有一次,一个问题我们两个人,商讨后解决 ...

这样只是解决了编译器报警问题。
前提你要保证你的业务逻辑是正确的,假如业务逻辑有bug,即使你添加了再入属性,或者复制一份,都不能解决问题。
作者: 188610329    时间: 2022-7-16 18:00
newlined 发表于 2022-7-16 08:42
程序中,四个工作区都用到了,其中两个低级中断共用一个区,因为不会同时被单片机响应,不会出问题。以前的堆 ...

堆栈不能这么搞, 堆栈是向上增长的,所以应该分配在内存的最末尾,而且,我不知道你用的什么单片机,最初的编写环境是怎么样的,照理你这代码 如果用的标准 A51 规范来写,你所有的变量声明不用  SEGMENT 来定的么? 不然,维护起来有多复杂?正常不应该是这么一个套路么?My_DATA SEGMENT DATA        ;预约DATA 内存
RSEG My_DATA
   ABC:           DS     1
   EFG:           DS     1
   TEMP1:       DS     1
   TEMP2:       DS     1
My_IDATA SEGMNET IDATA   ;预约IDATA 内存
RSEG My_IDATA
   REV_BUF:      DS     32
   Disp_BUF:      DS     8
   ?STACK:        DS     1

CSEG   AT   0000H
LJMP      MAIN

Main_PROG  SEGMENT CODE
RSEG    Main_PROG
MAIN:
       MOV     SP,#?STACK-1                ;定位堆栈起点

       SJMP     $

END

你那个代码,到底是怎么折腾的,能够把 堆栈地址夹在那么奇怪的一个地方的?



作者: newlined    时间: 2022-7-18 16:39
datouyuan 发表于 2022-7-16 10:13
看来你这代码问题很大。
51的堆栈要安排在ram的最后。
例如你需要32字节堆栈,那么堆栈空间为0xe0~0xff ...

堆栈以前就是那样设定的。再就是提到堆栈可以安排在80H到FFH之间,我以前不知道,我以为这些地址只可以利用R0,R1间接寻址来访问,使用。
作者: newlined    时间: 2022-7-18 16:42
datouyuan 发表于 2022-7-16 11:58
这样只是解决了编译器报警问题。
前提你要保证你的业务逻辑是正确的,假如业务逻辑有bug,即使你添加了 ...

业务逻辑保证是正确的,里边用到的地址都改了,不会冲突。
作者: newlined    时间: 2022-7-18 16:53
188610329 发表于 2022-7-16 18:00
堆栈不能这么搞, 堆栈是向上增长的,所以应该分配在内存的最末尾,而且,我不知道你用的什么单片机,最 ...

我知道堆栈是向上生长的,程序中以前就是那样设定的,我也不知道为什么,我设定的话起码会设在80H之前,我原以为堆栈的最大地址是7FH,80H到FFH是间接地址,不可以做堆栈。程序对DPTR保护后,运行了2天。没有再出以前的问题。在这里请教下,您前边的帖子里提到的指令 PUSH AR0  ,PUSH  AR1 是在哪里能学到,一般的书里都没有。再就是这个帖子里的伪指令,在哪里可以学到?一般的书里也没有,谢谢。
作者: newlined    时间: 2022-7-18 17:01
买了这2本书,也没有起多大作用,讲到中断,也只是说,保护现场,恢复现场,也没有具体的说要恢复那些东西,还不如在本帖中学到的东西多

IMG_20220718_165504[1].jpg (3.82 MB, 下载次数: 717)

IMG_20220718_165504[1].jpg

作者: newlined    时间: 2022-7-18 17:03
还有这一本

IMG_20220718_165516[1].jpg (3.14 MB, 下载次数: 705)

IMG_20220718_165516[1].jpg

作者: newlined    时间: 2022-7-18 17:07
@ 188610329,单片机是STC 8H
作者: newlined    时间: 2022-7-18 17:08
也许看这些书需要一定的功力,我还远远不够。
作者: 188610329    时间: 2022-7-18 19:36
newlined 发表于 2022-7-18 16:53
我知道堆栈是向上生长的,程序中以前就是那样设定的,我也不知道为什么,我设定的话起码会设在80H之前, ...

AR0~AR7 是伪地址, 属于KEIL A51 专用,在KEIL的帮助里面可以找到,一般讲汇编的书,不会写,要讲KEIL的书才会写, 主要是为了弥补 A51 指令的空白, 你如果已经打了一点汇编基础,你会知道, 你如果要把R7 的东西复制给 R6, 是不能直接复制的,这个时候,就可以用到 “伪地址”: MOV  R6,AR7  来达到目的, 此时的 AR7, 其实是一个 指向 R7 所在的直接地址。相当于: MOV R6,07H  这条指令,唯一的好处是,AR7 可以指代 4组通用寄存器的 任何一个组,编译的时候,会帮你自动 改直接地址。所以,同样道理,PUSH 只能PUSH 直接地址,我们可以用PUSH AR7来填补无法 PUSH R7的空白。 其实主要内容就这么点。你要还有兴趣 可以去KEIL的 帮助里面搜一下。

再跟你讲一下SP, SP其实就是指针,你仔细看51方面的书,会提到 3个8位地址指针,2个16位指针,分别对应的就是: R0,R1,SP,DPTR,PC 这5个指针。 其中: SP是半自动,PC 是全自动, R0,R1,DPTR是全手动。所以,基于SP是8位指针的特性,他的工作原理和 R0,R1 是一样的,就是访问的 IDATA 间接寻址内存。而PUSH 和 POP 指令你可以理解成:PUSH ACC
PUSH PSW
........
POP  PSW
POP  ACC
RETI

//以下代码不存在,是解释上面代码的动作。
MOV   @SP,ACC
INC   SP
MOV   @SP,PSW
INC   SP
..............
DEC  SP
MOV  PSW,@SP
DEC  SP
MOV  ACC,@SP
RETI

通过这个代码,我们可以看到。其实,你用 R0,R1 完全可以 软件模拟出 PUSH POP 指令。 反过来,指针不够用的时候, SP也可以临时拿来当 R0,R1 的替补用。

最后,说一下,代码是死的,就看你怎么用。说到底,还是要去体会和理解 单片机的工作原理。




作者: Y_G_G    时间: 2022-7-19 01:18
newlined 发表于 2022-7-18 17:03
还有这一本

你要看的是这一本书,网上应该可以搜索到不要钱的PDF版本,也可以买二手的,马云家30块左右,超过这个价钱就不要买了,我买的时候才二十多块
汇编没有那么复杂的,不要纠结堆栈了,8051是软件堆栈,我从来都不去管它的,等到你用到了硬件堆栈,你才会体验到汇编的毛病而且8051是复杂指令,功能多得很,你要是用到精简指令,你就会更加的体会到汇编的无聊
现在的编译器效率已经非常不错了,优先选择C语言,实在不行了再用汇编

作者: newlined    时间: 2022-8-1 08:16
前一段时间,我母亲生病住院,我去陪床,我母亲出院那一天,我父亲很高兴,去做饭,又烫伤了脚,我没有时间仔细看这个帖子,很抱歉。
作者: newlined    时间: 2022-8-1 08:21
188610329 发表于 2022-7-18 19:36
AR0~AR7 是伪地址, 属于KEIL A51 专用,在KEIL的帮助里面可以找到,一般讲汇编的书,不会写,要讲KEIL的 ...

看了这个帖子,我明白了,AR7是不是已经宏定义成了07H?我以前对地址了解不透彻,看来我要对这方面仔细揣摩。
作者: newlined    时间: 2022-8-1 08:25
Y_G_G 发表于 2022-7-19 01:18
你要看的是这一本书,网上应该可以搜索到不要钱的PDF版本,也可以买二手的,马云家30块左右,超过这个价钱就 ...

在网上我搜到了这本书的下载网站,但好像需要注册,需要电话号码什么的。在淘宝上有一家卖30几元,但需要45天发货,不知道怎么回事。
作者: Y_G_G    时间: 2022-8-1 13:17
这么些天了,还没有搞明白这个?
00H-1FH  这就是R0-R7的几个工作组
20H-2FH 这是可以位寻址的,位地址是从00H开始的,字节地址也有00H,通过指令来区分是位操作还是字节操作
SETE 00H 这是位操作
MOV 00H,#255 这是字节操作

一般是编写代码的时候,00H-1FH留给工作组使用,20H-2FH留给位寻址用
所以,你一般看到的用到内存的,都是多30H开始的


作者: 188610329    时间: 2022-8-1 17:21
newlined 发表于 2022-8-1 08:21
看了这个帖子,我明白了,AR7是不是已经宏定义成了07H?我以前对地址了解不透彻,看来我要对这方面仔细揣 ...

AR7  不一定是 07H,  也可能是 0FH, 17H 或者 1FH。 这牵涉到另一条 伪指令: USING  ,  当代码向上搜索 找到USING 0 则编译的时候会把  PUSH AR7  当作 PUSH 07H 来编译, 如果找到 USING 1 则会在编译的时候 把 PUSH AR7 当作 PUSH 0FH 来编译。 所以 AR7 不是绝对的,是可变的,好处是,如果更换换寄存器组,你可以不需要一行行代码去把 代表R7地址手动更改。缺点是,USING 必需要控制好。
作者: newlined    时间: 2022-8-4 09:59
188610329 发表于 2022-8-1 17:21
AR7  不一定是 07H,  也可能是 0FH, 17H 或者 1FH。 这牵涉到另一条 伪指令: USING  ,  当代码向上搜索  ...

这一段我还不理解,但我会尽量避免出现这个问题,以后我会慢慢领会。现在我遇到一个新的问题,就是单片机是STC的8H系列,您肯定知道,它对扇区的读写命令是类似这样的:

        ISP_CMD                        EQU                0C5H                        ;ISP命令寄存器
        ISP_CONTR                EQU                0C7H                        ;ISP控制寄存器
        ISP_TPS                        EQU                0F5H                        ;ISP等待时间寄存器

        MOV                          ISP_CONTR,#ENABLE_ISP
        MOV                               ISP_CMD,#01H                  ; 读扇区
        MOV                          ISP_TPS,#0CH  ;
这一段程序在主程序中用到了,在一个中断中也用到了,在中断中,对 ISP_CMD,ISP_CONTR进行压栈保护?就是 PUSH ISP_CMD,PUSH ISP_CONTR ,退出中断时 再 POP ISP_CONTR,POP ISP_CMD,这样保护能达到目的吗?编译是通过了,但我没有把握,您看可以吗?
作者: newlined    时间: 2022-8-4 11:12
Y_G_G 发表于 2022-8-1 13:17
这么些天了,还没有搞明白这个?
00H-1FH  这就是R0-R7的几个工作组
20H-2FH 这是可以位寻址的,位地址是从0 ...

这个地址我基本明白了,但对地址的运用上,还是不行,我可能需要一些实践才能领会。
作者: 188610329    时间: 2022-8-4 14:56
newlined 发表于 2022-8-4 09:59
这一段我还不理解,但我会尽量避免出现这个问题,以后我会慢慢领会。现在我遇到一个新的问题,就是单片机 ...

要说保护的话……
ISP_CMD, ISP_CONTR,之外,ISP_ADDRH, ISP_ADDRL 这些都要一起保护。
但是这些都没用。严格来讲,STC系列而言, ISP/IAP操作是唯一无法保护现场的操作。
因为: IAP_TRIG  这个启动指令, 需要连续发送两条 0x5A, 和 0xA5 的启动指令,这个是绝对不允许被打断的。所以,一般,会需要在程序中,先  CLR EA,禁止中断, 等所有 IAP操作完成 后 再次 SET EA 允许中断,这个才是关键。所以说,了解单片机的工作原理,还是重点中的重点。而如果在 主程序中已经禁止了中断,那么,相关操作就不需要 被PUSH和POP来现场保护了。

最后:IAP_TPS 是STC8系列用来设置 IAP操作速度的,理论上来说,只要你单片机不要不停的改变速度, MOV IAP_TPS,#0CH 之需要执行一次,里面的值是不会,也不需要再修改的。所以,通常来说,IAP_TPS 也是没有必要PUSH和POP的。
作者: newlined    时间: 2022-8-4 15:56
188610329 发表于 2022-8-4 14:56
要说保护的话……
ISP_CMD, ISP_CONTR,之外,ISP_ADDRH, ISP_ADDRL 这些都要一起保护。
但是这些都没 ...

真是的,您提醒了我,ISP_ADDRH, ISP_ADDRL我没有保护,程序运行主程序*ISP_ADDRH, ISP_ADDRL
作者: newlined    时间: 2022-8-4 16:00
笔记本的键盘不好用,可能是我前一段时间摔了一下.
作者: newlined    时间: 2022-8-4 16:11
程序运行过程中,有是会出现问题,可能是这个原因,两条 0x5A, 和 0xA5 的启动指令之前,中断是关的,之后打开了,实际上这个是以前的程序,我后来补充了一些程序中,尤其是中断中用到了操作EEPROM的语句,没有对主程序中操作EEPROM的现场保护。我如果对DPTR,ISP_CMD, ISP_CONTR,ISP_ADDRH, ISP_ADDRL保护了,就没有问题了吧。
作者: 188610329    时间: 2022-8-4 18:10
手机打字,我就长话短说了。 首先,trig指令发送前关中断,发送后开中断,是最低底线。 其次,我不太清楚你程序如何设计,stc8系列是支持movc读取eeprom的,所以,我的话,所有读指令都是movc完成的,写指令才会用iap指令,所以基本上是不需要保护iap相关寄存器,只需要保护dptr就可以的。 最后,牵涉到架构了,如果有可能,所有的写,擦eeprom的行为,集中在一个函数,或者一个中段中完成,对整个程序是最优的。
作者: newlined    时间: 2022-8-6 14:32
读指令movc可以不需要保护iap相关寄存器吗?我仔细揣摩下。
作者: 188610329    时间: 2022-8-7 17:59
newlined 发表于 2022-8-6 14:32
读指令movc可以不需要保护iap相关寄存器吗?我仔细揣摩下。

STC的 Eeprom 是把 Flash 空间模拟成 Eeprom 来操作,尤其是STC8系列,他在物理上就是 ROM的一部分。所以,只需要把它当 ROM来读就可以了。那么读取ROM的方式事就是:

MOV DPTR,#16位绝对地址
CLR A
MOVC A,@A+DPTR

就能读取了。整个过程没有 IAP相关寄存器的任何事情,因此,自然不需要 对 IAP相关寄存器做任何 保护工作。至于具体的 MOVC 读取时地址这块的可以详见 STC8系列的手册。
作者: newlined    时间: 2022-10-6 09:23
Y_G_G 发表于 2022-7-19 01:18
你要看的是这一本书,网上应该可以搜索到不要钱的PDF版本,也可以买二手的,马云家30块左右,超过这个价钱就 ...

我买回了这本书,正在学习中,谢谢。
作者: newlined    时间: 2022-10-6 09:28
188610329 发表于 2022-8-7 17:59
STC的 Eeprom 是把 Flash 空间模拟成 Eeprom 来操作,尤其是STC8系列,他在物理上就是 ROM的一部分。所以 ...

好的,您说的,我慢慢理会。这一段时间,正看程序,原程序中bug不少,比如,一个子程序,有时用LCALL调用,当然,这是对的,竟然有时用LJMP跳转过去,我都不知道最后执行了RET之后去了哪里。
作者: 188610329    时间: 2022-10-6 15:00
newlined 发表于 2022-10-6 09:28
好的,您说的,我慢慢理会。这一段时间,正看程序,原程序中bug不少,比如,一个子程序,有时用LCALL调用 ...

唉…… 这是基础中的基础啊……
给你举个例子吧:
有个函数:
SEND_HALF_BCD:
ANL   A,#0FH
ORL   A,#30H

SEND_BYTE:
JNB     TI,$
CLR     TI
MOV   SBUF,A
RET

你要发送  一个BCD出去, 可以制作这么一个函数:
_Send_BCD:         ;通过R7传入
MOV   A,R7
SWAP A
LCALL  SEND_HALF_BCD
MOV   A,R7
LCALL  SEND_HALF_BCD

RET
通常, 你觉得应该这么写对吧? 但是,这么写其实是完全没有 用到汇编的优势 。
所以,既然用了汇编,通常用如下写法:
_Send_BCD:         ;通过R7传入
MOV   A,R7
SWAP A
LCALL  SEND_HALF_BCD
MOV   A,R7
LJMP  SEND_HALF_BCD
这样写, 就避免了 无意义的 RET。 程序会依然回 到  LCALL  _Send_BCD 的地方。而效率会变高

所以, 你要搞清楚  有时LCALL 有时LJMP, 到底是 BUG  还是你没理解  程序本身的 真正目的?




作者: newlined    时间: 2022-10-25 13:50
188610329 发表于 2022-10-6 15:00
唉…… 这是基础中的基础啊……
给你举个例子吧:
有个函数:

你好,这个例子,我一直试图理解,但我没有想通。我是这样理解的,LCALL 一个函数时,堆栈会保存当前调用时的地址,RET 时,这个地址会恢复到PC程序计数器中,如果LJMP到一个函数,堆栈不会保存当前地址,执行到RET时,堆栈里的两个字节,恢复到PC程序计数器中,这个应该是错误的吧。
作者: 188610329    时间: 2022-10-25 14:38
newlined 发表于 2022-10-25 13:50
你好,这个例子,我一直试图理解,但我没有想通。我是这样理解的,LCALL 一个函数时,堆栈会保存当前调用 ...

CALL 一次,就会把当前地址放入  栈   然后跳转,读到RET  从 栈里 读出地址, 然后返回  CALL 的地方。
作者: 人中狼    时间: 2022-10-25 14:43
lcall和ljmp是不同的机制,lcall会压栈,ljmp不对栈操作,lcal必须有ret,ljmp一定不能有ret,51除了硬件自动压栈的以外,其他都是人工压栈的
作者: 188610329    时间: 2022-10-25 14:58
newlined 发表于 2022-10-25 13:50
你好,这个例子,我一直试图理解,但我没有想通。我是这样理解的,LCALL 一个函数时,堆栈会保存当前调用 ...

CALL 一次  就会把地址  存入栈  然后  跳转执行, 运行到 RET 就会从 栈中取出 地址, 返回CALL 的地方。
CALL 和 RET  应该是 成对的关系。没错吧? C 就是这么做的。

然后,你 CALL 一个函数, 这个函数,里面还要 调用 另一个函数,  
这个时候,应该  CALL  然后, CALL  然后 RET  然后 RET  返回最初的地方对不对?
那么,我简单点,不要CALL 直接JMP可以么?  CALL   JMP   RET  是不是结果一样?

再举个简单的例子吧

就是我之前举的那个例子:

SEND_HALF_BCD:
ANL   A,#0FH
ORL   A,#30H
SEND_BYTE:
JNB     TI,$
CLR     TI
MOV   SBUF,A
RET

这是一个复合函数,

CALL SEND_HALF_BCD  就能把 半个BCD 转成 ASCCII 发出去。
CALL SEND_BYTE      就能直接把 一个字节 发出去,这个能理解么?

到这里为止,是不是没有问题? 能理解么?  能的话,我们往下看。

换个写法:
SEND_HALF_BCD:
ANL   A,#0FH
ORL   A,#30H
CALL        SEND_BYTE
RET

SEND_BYTE:
JNB     TI,$
CLR     TI
MOV   SBUF,A
RET

这样,彻底拆成两个函数,你能看明白么?  
作用和刚才一样,这是纯C的写法。 你比较喜欢这样写是么?
此时如果 CALL SEND_HALF_BCD
这就是你认为应该的, 两次CALL 两次RET 对吧?


然后,我们再换个写法:

SEND_BYTE:
JNB     TI,$
CLR     TI
MOV   SBUF,A
RET

SEND_HALF_BCD:
ANL   A,#0FH
ORL   A,#30H
LJMP SEND_BYTE

这样, CALL SEND_HALF_BCD   有没有问题?  假如你说有问题……  那么,我们换个个。

SEND_HALF_BCD:
ANL   A,#0FH
ORL   A,#30H
LJMP SEND_BYTE

SEND_BYTE:
JNB     TI,$
CLR     TI
MOV   SBUF,A
RET

这样总没问题了吧?

汇编之所以比C 效率高,就是可以避免无意义的  RET。 节约非必要的 RET 是提高效率的一种必要手段。这真的是基础中的基础了……

作者: 人中狼    时间: 2022-10-25 15:17
楼上的例子有点特例了,像send_byte可以作为一个公用的子程序,如果用jmp,ret会不好控制,除非只有一个地方专门调用,即使是汇编,最好也是模块话,条理会清晰很多
作者: Hephaestus    时间: 2022-10-25 18:44
188610329 发表于 2022-10-25 14:58
CALL 一次  就会把地址  存入栈  然后  跳转执行, 运行到 RET 就会从 栈中取出 地址, 返回CALL 的地方。 ...
  1. %*DEFINE(SEND_BYTE)(
  2.     JNB     TI,$
  3.     CLR     TI
  4.     MOV   SBUF,A
  5. )
  6. SEND_HALF_BCD:
  7.     ANL   A,#0FH
  8.     ORL   A,#30H
  9.     %SEND_BYTE       
复制代码


我这个效率比你的还要高,连LJMP都省掉了,你的汇编还没学到家。

我用的是Intel MCS-51 MACRO ASSEMBLER, V2.2,如果用Keil A51编译不通过请查手册。
作者: 188610329    时间: 2022-10-25 19:45
Hephaestus 发表于 2022-10-25 18:44
我这个效率比你的还要高,连LJMP都省掉了,你的汇编还没学到家。

我用的是Intel MCS-51 MACRO ASS ...

人家问 CALL 和 RET,  你发个 没CALL 没 RET 的 代码
作者: newlined    时间: 2022-10-26 02:20
Hephaestus 发表于 2022-10-25 18:44
我这个效率比你的还要高,连LJMP都省掉了,你的汇编还没学到家。

我用的是Intel MCS-51 MACRO ASS ...

您这个看起来更简单,俗话说人外有人,天外有天,但我现在是更看不懂,感谢大家提供不同的代码让我们这些初学者揣摩。
作者: Hephaestus    时间: 2022-10-26 03:38
newlined 发表于 2022-10-26 02:20
您这个看起来更简单,俗话说人外有人,天外有天,但我现在是更看不懂,感谢大家提供不同的代码让我 ...

这不是“人外有人,天外有天”的问题,时间久了你就能明白最重要的学问是数学和英语。
作者: 188610329    时间: 2022-10-26 20:02
Hephaestus 发表于 2022-10-26 03:38
这不是“人外有人,天外有天”的问题,时间久了你就能明白最重要的学问是数学和英语。

数一数你这编译出来多少字节, 在来讲话
先不说人家问的  CALL RET , 你 CALL 和 RET 呢?  忘带了?
其次, define 到底是什么? 你应该很清楚,没必要再这里%。
你怎么不把所有代码  define 只后,你还能 只要一句代码  就能把智能蔽障小车写出来呢?
%

作者: Hephaestus    时间: 2022-10-26 20:18
188610329 发表于 2022-10-26 20:02
所以,先把数学学好, 数一数你这编译出来多少字节, 在来讲话, 然后把语文学好,学学怎么讲话。
先不 ...

教你一句成语——不屑一顾。
作者: 188610329    时间: 2022-10-27 00:20
Hephaestus 发表于 2022-10-26 20:18
教你一句成语——不屑一顾。

教你一句成语——夜郎自大。




欢迎光临 (http://www.51hei.com/bbs/) Powered by Discuz! X3.1