处理非常小的堆栈的任何提示?

时间:2010-09-02 03:07:55

标签: c pointers embedded function-pointers microcontroller

我想知道嵌入式领域的开发人员是否知道任何有趣的技巧来帮助减轻堆栈空间非常有限的微控制器开发的痛苦。我最近为8位UC(Microchip PIC18F系列,31字节堆栈)编写了一些固件,因此我不得不压缩程序并减少传递给函数的参数数量。我也试图最小化我对较大局部变量的依赖。扁平化旨在将更少的东西放在堆栈上,减少局部变量有助于节省RAM中“自动变量”程序部分(psect)中的空间。我知道,哈佛建筑并不好玩,但这正是我正在处理的问题。我注意到从ISR深度调用多个函数的问题,这可能是我的堆栈窗口受IRQ上下文保存影响的结果。我知道我正在使用限制性架构,但我想知道是否有人有任何减少头痛的技巧。我尽可能使用指针和边界检查,但我确信有一些我没有发现自己的智慧。作为免责声明,我目前正在使用函数指针来促进状态机。我觉得我正在走90线虚空函数和实际使用函数的代码之间走钢丝。

7 个答案:

答案 0 :(得分:7)

对参数和本地使用register变量。当然,根据处理器中可用的寄存器数量和编译器生成的代码质量,这可能完全没有任何好处。尽可能将本地声明为static。这将使它们不被分配到堆栈中。

答案 1 :(得分:2)

对于非递归函数,可以使局部变量保持静态。

BTW,术语“哈佛架构”简单地指的是指令和数据存在单独的地址空间,而“冯·诺依曼架构”则从数据来自相同的地址空间加载指令。这与您的问题无关,因为它意味着您无法编写自修改代码。

答案 2 :(得分:1)

  1. 使用可以有效静态分配“堆栈”的整个程序优化编译器 - 我相信Hi-Tech PICC编译器可以做到这一点。请参阅the PICC18 manual

  2. 中的第5.9节
  3. 使用protothreads

答案 3 :(得分:1)

使用非本地goto(setjmp()longjmp(),而jmp_buf未存储在堆栈中)以避免函数调用。

答案 4 :(得分:1)

修改

PIC系列不是编译器友好的指令集架构。处理PIC的小堆栈和一般有限资源的第一个技巧是用汇编程序编程。与在C中编程相比,您可以在相同的程序空间和相同的执行时间或相同的任务中执行更多任务。

第二个是不使用堆栈。无论你怎么称呼你的变量全局变量或局部全局变量(本地变量都带有static),或者你声明它的编译器输出是相同的变量有一个静态内存位置并且不使用堆栈。

对于PIC系列以外的处理器,您可以使用优化器或寄存器变量声明来防止使用堆栈。 PIC只有两个寄存器并且做任何有用的事情你必须不断地将内容驱逐到ram,所以这对PIC的影响很小。对于非PIC处理器,除了鼓励编译器尽可能地将数据保留在寄存器中之外,还可以通过限制传递给函数的变量数以及使用的其他局部变量数来辅助它。功能。对于不明显使用多少寄存器的情况,反汇编并检查编译器输出,重新安排代码执行可以改变寄存器分配和驱逐。如果这不起作用,可以考虑将函数拆分为两个并依次调用另一个函数,这样每个函数都可以在可用的寄存器中执行而不使用堆栈。

如果这个项目是为了一个有趣的教育体验,那么PIC的编程本身就是教育性的,用C语言编写PIC也是一种教育经验。非常值得学习,非常值得陷入陷阱和战斗回来的方式。

如果你这样做是为了工作(职业和/或生计悬而未决)并且不想在汇编程序中编程,我建议尝试将项目切换到另一个平台。 msp430,avr或其中一个基于ARM的(可能是stellaris)。了解ARM对于您在嵌入式领域的职业生涯非常有益,但与PIC相比,从功耗和成本的角度来看,ARM部件将变得更加笨重。 avr和msp430的部件更相同。所有这三种替代架构都更适合C编程,并且相同的程序将比PIC上的等效程序消耗更少的内存/堆栈。这意味着你可以用一个8K替换8K PIC,你不一定需要在备用架构上有更多内存才能做更多事情。您仍然需要担心堆栈,并应避免(非全局)局部变量以防止堆栈增长。这些架构也因PIC没有单独的堆栈和通用RAM而与PIC不同。您可以自由地平衡全局分配的变量和基于堆栈的变量,而不是由架构强制实施。如果使用基于堆栈的变量,则在工作环境中,查看代码以确定嵌套函数的最坏情况路径,并计算该路径中有多少非全局变量,以及是否导致堆栈与全局分配的变量发生冲突。

反汇编是你在嵌入式中最好的朋友,你不一定要用汇编程序编程,但仍然应该能够读取它。检查程序集是减少所有形式的嵌入式疼痛的最佳技巧:堆栈问题,内存使用,性能,启动问题等。

答案 5 :(得分:0)

我曾经为8051编程,我们使用Keil C编译器来做到这一点。此编译器不使用堆栈调用函数,而是使用全局函数传递参数(您仍然可以通过标记函数“reentrant”来获得此行为)。我认为它还通过做更多的事情来减少堆栈的使用。

我想说的是有些编译器会比其他编译器使用更少的堆栈空间。如果PIC有这样的编译器,我无法分辨。

答案 6 :(得分:0)

一些一般的跨平台提示(正如其他人提到的):

  • 使用静态局部变量。如果函数不可重入(无论是递归还是从多个异步线程/中断调用),这适用于任何地方。您只需要记住不要在它们上使用初始化程序(因为它们只会在启动时设置它们的值,而不是在函数中的每个条目处设置它们)。为了使预期的静态变量与众不同,您可以为当地人使用类似“#define fn_local static”的内容。

  • 减少传递给函数的参数。在8位微传递中,char导数而不是int导数通常会在堆栈上保存一个字节(因为整数通常是16位)。如果您有许多参数,请考虑将它们分组到结构中,并使该函数接受指向此结构的指针。

  • 对中断非常小心。避免内部的函数调用。尝试设计您的软件架构,这样您就不需要嵌套中断。根据编译器以及它在中断条目中保存内容的方式/方式,这可能会或可能不会占用大量的堆栈。

MPLAB / PIC18特定提示:

  • 在MPLAB中,函数参数也可以是静态的。这种效果与静态本地化相同:它们不需要堆栈,使用它们会使函数不可重入。请注意,如果小心操作,在PIC18上使用静态也可能是一个巨大的性能优势,因为堆栈指针相对寻址相当复杂。

  • PIC18具有用于函数调用的硬件堆栈。它支持的31层通常对于大多数复杂的软件来说已经足够了。这样做的结果是调用本身对软件堆栈没有贡献,从而占用了宝贵的RAM空间:真正可以编写PIC软件,甚至可能不需要使用上述方法的任何软件堆栈。

  • 可以控制编译器在中断条目上保存的内容,这不仅会影响堆栈使用,还会影响中断延迟。有关此主题,请参阅this decent paper。最值得注意的是,来自中断代码的外部函数调用使得编译器无法确定它将需要哪些资源,因此它将生成冗长的中断进入/退出代码,从而在堆栈上保存很多东西。