Чи є атрибутні функції вказівника в 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джерелах чи присвоєннях функцій, які я пропустив?

Відповіді:


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()не пропустив би його також. Розділ 6.8 на макеті даних ATMega2560 говорить: "Використовуючи інструкцію SEI для вмикання перерв, інструкція, наступна за SEI, буде виконуватися перед будь-якими очікуваними перериваннями, як показано в цьому прикладі", мабуть, означає, що вона може буферувати переривання під час переривання. вимкнено.


Дійсно корисно зануритися до джерел :) Механізм переривання приєднання / відключення TimerOne, здається, робиться аналогічно стандартному (WInterrupt's) і тому має ті самі "особливості".
Joonas Pulakaka

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), щоб упевнитися, що цього не відбудеться. Невпевненість у собі змушує мене думати, що безпечніший шлях - це переривання глобально.
Нік Гаммон
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.