在AVR中断期间,调用堆栈如何操作?


8

(特定于Arduino Uno ...)

当AVR微控制器上发生中断并调用函数时,堆栈会发生什么?编译器是否内联代码?它是否将堆栈缓存在某个地方,然后重置堆栈指针?它是否仅具有用于中断的辅助堆栈?

据我了解,中断的向量是汇编中的直接GOTO命令。另外,我不认为微控制器会自动弄乱堆栈,因此它可能一个人呆着。但是,这仍然不能解释功能在ISR期间如何工作。


下面的答案很好,请注意,编译器无法内联中断调用,因为...它们是中断调用:D
Vladimir Cravero

1
@VladimirCravero我的意思是内联在中断中调用的函数(如果我在ISR中调用foo()是否将其内联,所以它不是真正的函数调用?)
Anonymous Penguin

所以下面的答案不能回答您的问题,或者是?Caveman解释了发生中断时会发生什么,但不会解释在中断的上下文中调用函数时会发生什么。后者的答案将是:没什么特别的,该函数被调用而已。魔术发生在触发中断时,之后(在iret之前)只是正常的代码,通常是不可中断的代码。
弗拉基米尔·克拉韦罗

@VladimirCravero是的(间接)。我在谈论如何为ISR修改堆栈,以为首先必须对其进行修改才能使用功能。我将猜测,ISR设置后,功能将以完全相同的方式工作。
匿名企鹅

好吧,那你就知道了。跳转到中断向量后,一切都很好,您可以随意移动。
弗拉基米尔·克拉韦罗

Answers:


16

AVR是RISC架构,因此具有相当基本的中断硬件处理能力。大多数处理器在中断期间都会使堆栈混乱,尽管有两种使用不同方法的处理器,最著名的是ARM和PowerPC。

无论如何,这是AVR对中断的作用:

发生中断时,处理器硬件将执行以下步骤,而不仅仅是简单的GOTO:

  1. 完成当前指令。
  2. 禁用全局中断标志。
  3. 将下一条指令的地址压入堆栈。
  4. 将正确中断向量中的地址(根据发生的中断)复制到程序计数器中。

现在,硬件已经完成了所有要做的工作。该软件必须正确编写以免损坏。通常,接下来的步骤将遵循这些原则。

  1. 将状态寄存器压入堆栈。(必须先进行更改,然后再进行更改)。

  2. 将将(或可能被更改)的所有CPU寄存器压入堆栈。编程模型定义了需要以这种方式保存的寄存器。编程模型由编译器定义。

现在可以运行工作中断代码。为了回答调用函数的问题,它只是做它总是做的事情,将返回值压入堆栈,然后在完成后将其弹出。到目前为止,这不会影响我们保存在堆栈中的所有先前值。

  1. 运行ISR工作代码。

现在我们完成了,想从中断中返回。首先,我们必须进行软件清理。

  1. 弹出我们在步骤6中推送的CPU寄存器。
  2. 将保存的状态值弹出回状态寄存器。此后,我们必须注意不要执行任何可能更改状态寄存器的指令。
  3. 执行RTI指令。硬件为此指令执行以下步骤:

    一个。启用全局中断标志。(请注意,在接受下一个中断之前,必须至少运行一条指令。这可以防止大量中断完全阻塞后台工作。)

    b。将保存的返回地址弹出到PC中。

现在我们回到普通代码。

注意,有些地方我们必须非常小心,特别是在状态寄存器和可能更改的保存寄存器周围。幸运的是,如果您使用的是C编译器,那么所有这些操作都是在后台进行的。

另外,您还必须注意堆栈深度。在启用中断的任何时候,ISR都可以使用比查看本地代码明显更多的堆栈。当然,除非您将内存推到极限,否则确实不会有太大的变化。

如果需要参考,这里是描述此过程的链接。


5/6步骤的目的是什么?对我来说,不直接修改寄存器似乎很愚蠢,尽管我认为在中断期间弄乱某些寄存器可能会产生一些令人讨厌的结果。
匿名企鹅

1
步骤5和6保存了系统的当前(中断之前)状态,因此可以在中断完成后将其恢复(步骤8和9),以使主程序能够像未中断一样继续运行。
彼得·贝内特

1
这是一个很好的总结。
康纳·沃尔夫

3
还值得注意的是,如果您确实非常了解自己在做什么,则可以使用GCC标志(ISR_NAKED)禁用状态和其他寄存器的自动保存。在您确实需要这些循环的情况下,这可以让您跳过第5、6、8、9步。缺点是您必须绝对确定自己将保留任何相关的寄存器,或者可以无害地覆盖它们。
康纳·沃尔夫

2
知道这很有趣,但是我会在使用该标志之前进行汇编编程。太危险了……
穴居人2015年
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.