Переривання роботи в мікроконтролерах та прикладі FSM


9

Початкове запитання

У мене загальне питання щодо обробки перебоїв у мікроконтролерах. Я використовую MSP430, але я думаю, що питання може поширюватися і на інші UC. Мені хотілося б знати, чи є хорошою практикою включення / відключення переривань часто уздовж коду. Я маю на увазі, якщо у мене є частина коду, яка не буде чутливою до перерв (або ще гірше, не повинна слухати переривання з будь-якої причини), чи краще:

  • Вимкніть переривання раніше, а потім повторно ввімкніть їх після критичного розділу.
  • Поставте прапор всередину відповідного ISR і (замість того, щоб вимкнути переривання), встановіть прапор значення false перед критичним розділом і відновіть його до істинного, відразу після. Щоб запобігти виконанню коду ISR.
  • Жодне з двох, тому пропозиції вітаються!

Оновлення: переривання та діаграми стану

Я надам конкретну ситуацію. Припустимо, ми хочемо реалізувати діаграму стану, яка складається з 4 блоків:

  1. Переходи / Ефект.
  2. Умови виходу.
  3. Вхідна діяльність.
  4. Займайтеся активністю.

Цьому нас навчав професор в університеті. Напевно, не найкращий спосіб зробити це, дотримуючись цієї схеми:

while(true) {

  /* Transitions/Effects */
  //----------------------------------------------------------------------
  next_state = current_state;

  switch (current_state)
  {
    case STATE_A:
      if(EVENT1) {next_state = STATE_C}
      if(d == THRESHOLD) {next_state = STATE_D; a++}
      break;
    case STATE_B:
      // transitions and effects
      break;
    (...)
  }

  /* Exit activity -> only performed if I leave the state for a new one */
  //----------------------------------------------------------------------
  if (next_state != current_state)
  {
    switch(current_state)
    {
      case STATE_A:
        // Exit activity of STATE_A
        break;
      case STATE_B:
        // Exit activity of STATE_B
        break;
        (...)
    }
  }

  /* Entry activity -> only performed the 1st time I enter a state */
  //----------------------------------------------------------------------
  if (next_state != current_state)
  {
    switch(next_state)
    {
      case STATE_A:
        // Entry activity of STATE_A
        break;
      case STATE_B:
        // Entry activity of STATE_B
        break;
      (...)
    }
  }

  current_state = next_state;

  /* Do activity */
  //----------------------------------------------------------------------
  switch (current_state)
  {
    case STATE_A:
      // Do activity of STATE_A
      break;
    case STATE_B:
      // Do activity of STATE_B
      break;
    (...)
  }
}

Припустимо також, що, скажімо STATE_A, я хочу бути чутливим до переривання, що виникає з набору кнопок (із системою дебютування тощо) тощо. Коли хтось натискає одну з цих кнопок, створюється переривання і прапор, пов'язаний з вхідним портом, копіюється в змінну buttonPressed. Якщо для дебютації якимось чином встановлено 200 мс (таймер сторожового часу, таймер, лічильник, ...), ми впевнені, що buttonPressedне можна оновити нове значення до 200 мс. Це я прошу вас (і я :) звичайно)

Чи потрібно ввімкнути перерву в діяльності DO STATE_Aта відключити його перед відходом?

/* Do activity */
//-------------------------------------
switch (current_state)
{
  case STATE_A:
    // Do activity of STATE_A
    Enable_ButtonsInterrupt(); // and clear flags before it
    // Do fancy stuff and ...
    // ... wait until a button is pressed (e.g. LPM3 of the MSP430)
    // Here I have my buttonPressed flag ready!
    Disable_ButtonsInterrupt();
    break;
  case STATE_B:
    // Do activity of STATE_B
    break;
  (...)
}

Таким чином, я впевнений, що наступного разу, коли я виконую блок 1 (перехід / ефекти) при наступній ітерації, я впевнений, що умови, перевірені під час переходів, не виходять із наступного переривання, яке перезаписало попереднє значення того, buttonPressedщо я потреба (хоча неможливо, що це трапляється, оскільки 250 мс має минути).


3
Важко дати рекомендацію, не знаючи більше про свою ситуацію. Але іноді доводиться відключати перебої у вбудованих системах. Переважно, щоб перерви були відключені лише на короткий проміжок часу, щоб перерви не були пропущені. Можливо, можливо відключити лише окремі переривання, а не всі переривання. Я не пригадую, щоб ніколи використовував техніку прапор-всередині ISR, як ви описали, тому я скептично ставлюсь до того, чи найкраще це рішення.
kkrambo

Відповіді:


17

Перша тактика полягає в тому, щоб сконструювати загальну прошивку так, щоб в будь-який час траплялися перебої. Необхідність вимкнення переривань, щоб код переднього плану міг виконати атомну послідовність, слід робити щадно. Часто існує архітектурний шлях навколо нього.

Однак машина там, щоб послужити вам, а не навпаки. Загальні правила є лише для того, щоб погані програмісти не писали дійсно поганий код. Набагато краще зрозуміти , як саме працює машина, а потім створити архітектор, як вдало використати ці можливості для виконання потрібного завдання.

Майте на увазі, що якщо ви дійсно не обмежуєтесь циклами або пам'яттю (це, безумовно, може статися), то в іншому випадку ви хочете оптимізувати чіткість та ремонтопридатність коду. Наприклад, якщо у вас 16-розрядна машина, яка оновлює 32-бітний лічильник під час переривання галочки годинника, вам потрібно переконатися, що коли код переднього плану зчитує лічильник, що дві його половини відповідають. Один із способів - вимкнути переривання, прочитати два слова, а потім увімкнути перерви. Якщо затримка переривання не є критичною, то це цілком прийнятно.

У випадку, коли у вас повинна бути низька латентність переривання, ви можете, наприклад, прочитати високе слово, прочитати низьке слово, знову прочитати високе слово і повторити, якщо воно змінилося. Це трохи уповільнює код переднього плану, але зовсім не додає затримки переривання. Існують різні маленькі хитрощі. Іншим може бути встановлення прапора в процедурі переривання, який вказує, що лічильник повинен бути посилений, а потім зробити це в циклі основного події в коді переднього плану. Це добре працює, якщо швидкість переривання лічильника досить повільна, щоб цикл подій зробив приріст до того, як прапор буде встановлений знову.

Або замість прапора використовуйте однослівний лічильник. Код переднього плану зберігає окрему змінну, яка містить останній лічильник, до якого оновлена ​​система. Це робить непідписане віднімання лічильника на реальному рівні за мінусом збереженого значення, щоб визначити, скільки кліщів за один раз воно має обробити. Це дозволяє коду переднього плану пропускати до 2 подій N -1 одночасно, де N - кількість бітів у рідному слові, яке ALU може обробляти атомно.

У кожного методу є свій набір переваг і недоліків. Єдиної правильної відповіді немає. Знову ж таки зрозумійте, як працює машина, тоді вам не знадобляться великі правила.


7

Якщо вам потрібен критичний розділ, ви повинні переконатися, що операція, що захищає ваш критичний ділянку, є атомною і не може бути перервана.

Таким чином, вимкнення переривань, які найчастіше обробляються однією інструкцією процесора (і викликається за допомогою внутрішньої функції компілятора), є однією з найбезпечніших ставок, яку ви можете взяти.

Залежно від вашої системи, з цим можуть виникнути деякі проблеми, як-от перерив може бути пропущений. Деякі мікроконтролери встановлюють прапори незалежно від стану глобального включення переривання, і після виходу з критичного розділу переривання виконуються і просто затримуються. Але якщо у вас перерва, яка відбувається з високою швидкістю, ви можете пропустити другий раз, коли перерва сталася, якщо ви занадто довгий час блокуєте переривання.

Якщо ваш критичний розділ вимагає не лише одного переривання, а інший повинен бути виконаний, інший підхід здається життєздатним.

Я вважаю, що я програмую процедуру обслуговування переривань якомога коротше. Тому вони просто встановлюють прапор, який потім перевіряється під час звичайних програмних процедур. Але якщо ви це зробите, остерігайтеся умов перегонів, дочекавшись встановлення цього прапора.

Варіантів дуже багато, і, звичайно, немає єдиної правильної відповіді на це, це тема, яка вимагає ретельного проектування і заслуговує трохи більше думки, ніж інші речі.


5

Якщо ви визначили, що розділ коду повинен працювати безперебійно, за винятком незвичних обставин, слід вимкнути переривання на мінімально можливу тривалість виконання завдання та повторно включити їх після.

Поставте прапор всередину відповідного ISR і (замість того, щоб вимкнути переривання), встановіть прапор значення false перед критичним розділом і відновіть його до істинного, відразу після. Щоб запобігти виконанню коду ISR.

Це все ще дозволить відбутися переривання, стрибок коду, перевірка, а потім повернення. Якщо ваш код може впоратися з такою сильною перервою, вам, мабуть, слід просто спроектувати ISR для встановлення прапора, а не для перевірки - це було б коротше - і обробити прапор у звичайному режимі коду. Це здається, що хтось вводить занадто багато коду в переривання, і вони використовують переривання для виконання довших дій, які повинні відбуватися в звичайному коді.

Якщо ви маєте справу з кодом, де переривання довгі, прапор, як ви пропонуєте, може вирішити проблему, але все одно буде краще просто відключити переривання, якщо ви не зможете повторно розмістити код, щоб усунути зайвий код у перериванні .

Основна проблема при виконанні цього прапора полягає в тому, що ви взагалі не виконуєте переривання - що може мати наслідки пізніше. Більшість мікроконтролерів відстежують прапори переривань, навіть коли переривання глобально відключені, а потім виконують переривання при повторному включенні переривань:

  • Якщо під час критичного перерізу не було перерв, після цього жодна не виконується.
  • Якщо під час критичного перерізу відбувається одне переривання, воно виконується після.
  • Якщо під час критичного перерізу трапляються кілька переривань, після цього виконується лише одна.

Якщо ваша система є складною і має відслідковувати перерви більш повно, вам доведеться розробити більш складну систему для відстеження перерв і роботи відповідно.

Однак якщо ви завжди проектуєте свої переривання для виконання мінімальної роботи, необхідної для досягнення їх функції, і затримуєте все інше до регулярної обробки, тоді переривання рідко впливатимуть на ваш інший код негативно. Потрібно встановити дані про переривання переривання або звільнення, якщо потрібно, або встановити / скинути виходи тощо за необхідності, тоді основний шлях коду зверне увагу на прапори, буфери та змінні, на які впливає переривання, щоб тривала обробка могла бути виконана в основному циклі, а не переривання.

Це повинно усунути всі, але дуже, дуже мало ситуацій, коли вам може знадобитися безперебійний розділ коду.


Я оновив пост, щоб краще пояснити ситуацію, над якою працюю :)
Умберто Д.

1
У вашому прикладі коду я б розглядав можливість відключення конкретного переривання кнопки, коли вона не потрібна, і вмикати її за потреби. Робити це часто не є проблемою його задумом. Залиште глобальні переривання ввімкнутими, щоб ви могли пізніше додати інші переривання до коду, якщо потрібно. Крім того, просто скиньте прапор, переходячи до стану A, інакше ігноруйте його. Якщо натиснути кнопку і встановити прапор, кого це хвилює? Ігноруйте це, поки не повернетесь до штату А.
Адам Девіс

Так! Це може бути рішенням, оскільки в реальній конструкції я часто заходжу в LPM3 (MSP430) з включеними глобальними перериваннями, і я виходжу з LPM3, поновлюючи виконання, як тільки виявляється переривання. Отже, рішення - це те, що ви подали, і, на мою думку, повідомляється у другій частині коду: увімкніть переривання, як тільки я почніть виконувати дію стану стану, який цього потребує, і відключіть перед тим, як перейти до блоку переходів. Можливо, іншим можливим рішенням буде відключити переривання безпосередньо перед виходом із "Блокувати активність" та повторно включити колись (коли?) Після?
Умберто Д.

1

Поставити прапор всередині ISR, як ви описуєте, ймовірно, не вийде, оскільки ви в основному ігноруєте подію, яка викликала переривання. Вимкнення переривань в усьому світі зазвичай є кращим вибором. Як говорили інші, вам не слід це робити дуже часто. Майте на увазі, що будь-яке читання або запис, виконане за допомогою однієї інструкції, не повинно бути захищеним, оскільки інструкція виконується або не виконується.

Багато залежить від того, якими ресурсами ви намагаєтесь поділитися. Якщо ви подаєте дані з ISR в основну програму (або навпаки), ви можете реалізувати щось на зразок буфера FIFO. Єдиною атомною операцією було б оновлення покажчиків читання і запису, що мінімізує кількість часу, який ви проводите з відключеними перервами.


0

Існує тонка різниця, яку потрібно враховувати. Ви можете обрати "затримку" обробки переривання або "ігнорування" та переривання.

Часто в коді ми кажемо, що ми відключаємо переривання. Що, мабуть, відбудеться завдяки апаратним функціям, це те, що коли ми вмикаємо переривання, воно спрацьовує. Це певним чином затримує переривання. Для того, щоб система надійно працювала, нам потрібно знати максимальний час, який ми можемо затримати обробку цих переривань. А потім переконайтесь, що всі випадки, коли відключення відключаються, будуть закінчені за коротший час.

Іноді ми хочемо ігнорувати переривання. Найкращим способом може бути блокування переривання на апаратному рівні. Часто є контролер переривань або подібний, де можна сказати, які входи повинні генерувати переривання.

Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.