功能指针分配在Arduino中是原子的吗?


11

以下代码片段来自TimerOne库源代码

// TimerOne.h:
void (*isrCallback)();

// TimerOne.cpp:
ISR(TIMER1_OVF_vect) // interrupt service routine that wraps a user defined function supplied by attachInterrupt
{
  Timer1.isrCallback();
}

// TimerOne.cpp:
void TimerOne::attachInterrupt(void (*isr)(), long microseconds)
{
  if(microseconds > 0) setPeriod(microseconds);
  isrCallback = isr; // register the user's callback with the real ISR
  TIMSK1 = _BV(TOIE1); // sets the timer overflow interrupt enable bit
  resume();                                                                                            
}

问题:如果定时器已经在运行,并且主程序调用了attachInterrupt()函数指针分配期间定时器中断会那发生isrCallback = isr;吗?然后,在幸运的时机下,Timer1.isrCallback();函数指针将部分由旧地址组成,而部分由新地址组成,从而导致ISR跳至伪造位置?

我想可能是这种情况,因为函数指针肯定比1字节宽,并且访问> 1字节数据不是原子的。可能的解决方法可能是:

  • 在调用detachInterrupt()之前attachInterrupt(),请务必先进行调用以确保计时器未运行,即澄清Timer1文档。
  • 或者,修改Timer1,在即将到来之前临时禁用定时器溢出中断 isrCallback = isr;

这是否有意义,或者Timer1我错过了源代码或函数指针分配中的某些内容?

Answers:


7

看一下中的attachInterrupt()和detachInterrupt()的代码 /Applications/Arduino.app/Contents/Resources/Java/hardware/arduino/cores/arduino/WInterrupts.c(好吧,无论如何,这就是它们在Mac上的位置。其他OS上的Arduino文件结构在路径的较低层可能看起来相似)。

似乎attachInterrupt()假定所讨论的中断尚未启用,因为它在未采取任何预防措施的情况下写入了函数指针。注意,在将NULL指针写入其向量之前,detachInterrupts()会禁用目标中断。所以我至少使用detachInterrupt()/ attachInterrupt()

我想在关键部分运行任何这样的代码。看来您的第一种方法(分离然后附加)可以工作,尽管我不能确定它不会错过不幸的定时中断。MCU的数据表可能还有更多话要说。但是我现在也不确定,一个全球性的cli()/ sei()也不会错过它的。ATMega2560数据手册第6.8节确实说“当使用SEI指令使能中断时,SEI之后的指令将在任何未决的中断之前执行,如本例所示”,似乎暗示它可以在中断时缓冲中断关闭。


深入研究源代码确实是有用的:) TimerOne的附加/分离中断机制似乎与标准(WInterrupt的)相似,因此具有相同的“功能”。
Joonas Pulakka 2014年

0

看来您有意思。合理的做法是禁用中断,这样一来,如果您一开始就禁用了中断,则无需重新启用它们。例如:

  uint8_t oldSREG = SREG;  // remember if interrupts are on
  cli();                   // make the next line interruptible
  isrCallback = isr;       // register the user's callback with the real ISR
  SREG = oldSREG;          // turn interrupts back on, if they were on before

“使用SEI指令启用中断时,将在任何未决中断之前执行SEI之后的指令,如本示例所示”

这样做的目的是让您编写如下代码:

  sei ();         // enable interrupts
  sleep_cpu ();   // sleep

没有该规定,您可能会在这两条线之间遇到中断,从而无限期地睡眠(因为要唤醒您的中断是您睡觉之前发生的)。处理器中规定,如果之前未启用中断,则在中断被启用后,下一条指令将始终执行,以防此情况。


这样会不会更有效TIMSK1=0; TIFR1=_BV(TOV1); isrCallback=isr; TIMSK1=_BV(TOIE1);?它节省了一个CPU寄存器,并且不会造成中断延迟。
Edgar Bonet

那么其他位(例如ICIE1,OCIE1B,OCIE1A)又如何呢?我了解延迟,但是中断应该能够应付几个时钟周期,这些时钟周期不可用。可能存在比赛条件。该中断可能已经被触发(即,CPU中的标志已设置),并且关闭TIMSK1可能不会停止对该中断的处理。您可能还需要重置TOV1(通过在TIFR1中写入1)以确保不会发生这种情况。那里的不确定性使我认为全局关闭中断是更安全的做法。
尼克·加蒙
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.