安全地检测函数是否从中断服务程序(ISR)中调用?

23
我正在为ARM Cortex M3(NXP LPC1769)微控制器开发软件。目前,我正在寻找一种机制来检测我的函数是否在中断服务程序(ISR)中被调用。我假设我需要检查一个寄存器。根据这些信息,我想调用一些复杂的函数。
我已经查阅了参考手册,看是否有包含所需信息的寄存器。
例如,我尝试通过“中断活动位寄存器”(IABR)来检测是否从ISR中调用(我使用了SysTick-ISR)。如果ISR处于活动状态,该寄存器的值应该不等于0。但是该值为0x00000000,这意味着没有中断处于活动状态。除了这个测试之外,我还在参考手册中检查了NVIC和SC寄存器,寻找包含所需标志的寄存器,但是没有找到。
有人知道适合我问题的合适寄存器/机制吗?

2
为什么你不能让ISR调用一个函数,而让程序的其余部分调用另一个函数呢? - Lundin
1
是的... 在做这件事情时必须非常小心,特别是涉及任务处理器时。不过,它已经嵌入其中了,祝你好运! - Martin James
5
也许是为了简化实时操作系统(RTOS)的接口。例如Keil的RL-RTX为中断和线程上下文提供单独的API,例如os_evt_setisr_evt_set。在编写可重用代码时,未必总能预测未来可能在哪些上下文中使用它,因此可能创建一个通用的evt_set包装器,可在任何地方使用。此外,如果意外调用了错误的API,则操作系统会表现不正确,因此运行时选择更安全,但会带来一点小的开销。 - Clifford
4个回答

32

你需要测试Interrupt Control State RegisterVECTACTIVE字段。

我使用以下内容:

//! Test if in interrupt mode
inline bool isInterrupt()
{
    return (SCB->ICSR & SCB_ICSR_VECTACTIVE_Msk) != 0 ;
}

SCM和SCB_ICSR_VECTACTIVE_Msk在CMSIS(core_cm3.h)中定义,我想这将间接包含在您的特定头文件(例如lpc17xx.h)中。我正在使用C++,在C++中包含stdbool.h将会使您获得bool类型,或者更改为int或自己的typedef。

例如,可以这样使用:

void somefunction( char ch )
{
    if( isInterrupt() )
    {
        // Do not block if ISR
        send( ch, NO_WAIT ) ;
    }
    else
    {
        send( ch, TIMEOUT ) ;
    }
}

如果需要一种不需要对架构有任何了解的解决方案,请考虑以下内容:

volatile int interrupt_nest_count = 0 ;
#define ENTER_ISR() interrupt_nest_count++
#define EXIT_ISR()  interrupt_nest_count--
#define IN_ISR() (interrupt_nest_count != 0)

void isrA()
{
     ENTER_ISR() ;
     somefunction( 'a' ) ;
     EXIT_ISR() ;
}

void isrB()
{
     ENTER_ISR() ;
     somefunction( 'b' ) ;
     EXIT_ISR() ;
}

void somefunction( char ch )
{
    if( IN_ISR() )
    {
        // Do not block if ISR
        send( ch, NO_WAIT ) ;
    }
    else
    {
        send( ch, TIMEOUT ) ;
    }
}

然而,这个问题涉及到如何安全地检测中断上下文,并且这依赖于在所有中断服务程序(ISRs)中添加enter/exit宏。

然而,这个问题涉及到如何安全地检测中断上下文,并且这依赖于在所有中断服务程序(ISRs)中添加enter/exit宏。


1
我会将“interrupt_nest_count”声明为易失性变量。 - Ibrahim
@Ibrahim:我也是这样想的;竟然花了5年时间才有人注意到!谢谢。希望任何有用的人都使用了第一种方法。 - Clifford
在C语言中,默认情况下即使变量i是volatile类型,对于i++操作并不能保证原子性。 - vgru
@Groo:好观点 - 给读者的一个练习。可能有依赖于实现/架构行为而不是语言定义行为的好理由。例如,为了最小化中断延迟。这是一个想法的内核。 - Clifford
我认为你的第一个例子是最好的(检查某个平台相关标志),即使它不是“可移植的”。嵌入式代码无论如何都需要硬件抽象层,没有其他方法可以使它在不同的平台上可移植。特别是因为这个答案恰好是OP针对那个特定架构所问的。 - vgru
2
@Groo:我认为我的回答中已经表达了这一点。那是很久以前的事了 - 如果今天回答,也许我会省略第二个if。 - Clifford

16

经过一些讨论和更多搜索,我找到了正确的寄存器:


中断程序状态寄存器: IPSR包含当前中断服务例程(ISR)的异常类型编号。请参见表626中的寄存器摘要以获取其属性。


如果函数不是从中断服务例程(isr)调用,则寄存器的值为IPSR == 0


是的,IPSR是我过去用于此目的的寄存器。 - Dan Moulding

-1
最简单的方法是将上下文作为参数传递给函数。这也是平台无关的。
typedef enum _context {
    normal_context = 0,
    isr_context = 1
} context;

从ISR调用函数:

func(param1, param2, isr_context);

从普通代码中调用函数:

func(param1, param2, normal_context);

如果ISR代码不在您的控制范围内,而您只是传递一个函数指针,则可以使用两个不同的包装函数。其中一个将isr_context作为参数传递,另一个将normal_context作为参数传递给函数。

3
我认为“最简单的方法”实际上是询问处理器状态。 - Clifford

-1

最好的方法可能是创建两个不同的函数:一个从ISR中调用,另一个从程序的其余部分调用。

如果这不是一个选项,那么你可以使用纯标准C来确定调用者,不需要寄存器:

inline void my_func (const char* caller);

static void isr (void)
{
  my_func(__func__);
}


inline void my_func (const char* caller)
{
  if(strcmp(caller, "isr")==0)
  {
    // was called from isr
  }
  else
  {
    // called from elsewhere
  }
}

如果您为中断服务程序(ISR)命名得当,上述代码就足够快速地从ISR中运行。

8
处理器“知道”何时处于中断上下文。这种方法是不必要的、低效的并且容易出错的。 - Clifford
1
@Clifford 不,处理器不知道这个。ARM Cortex M3 知道这个。看起来 OP 正在编写某种独立于硬件的库。这段代码是可移植的。它并不低效,可以进一步优化为编译器可以在编译时评估然后丢弃的内容。通过一些微小的修改,你可以将预处理器输出设置为 if('i' == 'i' && 's' == 's' && 'r' == 'r' && '\0' == '\0')。当然,任何半靠谱的编译器都会在编译时进行优化。我不明白这会有什么错误倾向,请解释一下? - Lundin
2
@Lundin,我不同意 - 这个问题非常具体,涉及到Cortex M3特定的解决方案。硬件抽象包装器更能提高可移植性。我想知道你可能遇到过哪些没有确定中断上下文手段的硬件。这种方法会出错,因为它依赖于调用者传递正确的信息。 - Clifford
2
@Lundin,我就说到这里,你开始有些疲劳了。可以说,在我看来,这是一个相当不优雅的解决方案,用于解决一个简单的问题。你添加了一个 strcmp() 到某个只需要进行位测试的东西中 - 而且在可能很关键的 ISR 中。 - Clifford
当我发现这个帖子时,读到了一个有趣的论点,因为我和楼主有同样的问题。在这一点上,我必须全心全意地支持@Clifford。在我的情况下,我有一些函数不应该在中断上下文中执行。我告诉自己永远不要在中断上下文中调用它们,我可以在注释中加入这样的警告等。然而,我仍然不相信自己能在两个月后记得这件事,我绝对不相信在我之后可能更改代码的任何人会忽略注释中的警告。因此,我将测试IPSR并完成它。 - Dmitri
显示剩余9条评论

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接