Являются ли указатели на функции атомными в 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источниках или назначениях указателей на функции, которые я пропустил?

Joonas Pulakka
источник

Ответы:

7

Взгляните на код для attachInterrupt () и detachInterrupt () в /Applications/Arduino.app/Contents/Resources/Java/hardware/arduino/cores/arduino/WInterrupts.c(ну, в любом случае, это то, где они находятся на Mac. Структура файлов Arduino в других ОС, вероятно, похожа на более низких уровнях пути).

Похоже, что attachInterrupt () предполагает, что рассматриваемое прерывание еще не включено, поскольку оно записывает указатель на функцию без принятия каких-либо мер предосторожности. Обратите внимание, что detachInterrupts () отключает целевое прерывание перед записью NULL-указателя на его вектор. Так что я бы по крайней мере использовал detachInterrupt()/ attachInterrupt()пара

Я бы сам хотел запустить любой такой код в критической секции. Похоже, ваш первый способ (отсоединение, затем присоединение) сработает, хотя я не могу быть уверен, что он не сможет пропустить, к сожалению, прерывание по времени. Таблица данных для вашего MCU может сказать больше об этом. Но я также не уверен в этом, что глобальный cli()/ sei()не пропустил бы это также. В таблице данных ATMega2560, раздел 6.8, говорится: «При использовании инструкции SEI для разрешения прерываний инструкция, следующая за SEI, будет выполняться перед любыми ожидающими прерываниями, как показано в этом примере», что подразумевает, что она может буферизовать прерывание во время прерываний. выключены

JRobert
источник
Это действительно полезно, чтобы погрузиться в источники :) Механизм прерывания присоединения / отсоединения TimerOne, кажется, сделан аналогично стандартному (WInterrupt's) и имеет те же «особенности».
Joonas Pulakka
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);? Он экономит один регистр процессора и не вносит задержку прерывания.
Эдгар Бонет
А как насчет других битов, таких как ICIE1, OCIE1B, OCIE1A? Я понимаю о задержке, но прерывания должны быть в состоянии справиться с парой тактов отсутствия доступа. Там может быть состояние гонки. Прерывание может уже сработать (т. Е. Установлен флаг в ЦП), и отключение TIMSK1 может не остановить его обработку. Вам также может понадобиться сбросить TOV1 (записав в него 1 в TIFR1), чтобы этого не произошло. Неопределенность заставляет меня думать, что отключение прерываний во всем мире - более безопасный путь.
Ник Гэммон