Обработка прерываний в микроконтроллерах и пример FSM


9

Начальный вопрос

У меня есть общий вопрос об обработке прерываний в микроконтроллерах. Я использую MSP430, но я думаю, что вопрос может быть распространен на другие СК. Я хотел бы знать, является ли хорошей практикой частое включение / отключение прерываний по всему коду. Я имею в виду, если у меня есть часть кода, которая не будет чувствительна к прерываниям (или, что еще хуже, не должна слушать прерывания по любой причине), лучше:

  • Отключите прерывания до, а затем снова включите их после критического раздела.
  • Поместите флаг внутри соответствующего ISR и (вместо отключения прерывания) установите флаг в значение false перед критическим разделом и сбросьте его в значение true, сразу после. Для предотвращения выполнения кода 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. Если debounce каким-либо образом установлен на 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этого I нужно (хотя это невозможно, потому что должно пройти 250 мс).


3
Трудно дать рекомендацию, не зная больше о вашей ситуации. Но иногда необходимо отключить прерывания во встроенных системах. Желательно, чтобы прерывания были отключены только на короткие промежутки времени, чтобы прерывания не пропускались. Может быть возможно отключить только определенные прерывания, а не все прерывания. Я не помню, чтобы когда-либо использовался метод flag-inside-ISR, как вы описали, поэтому я скептически отношусь к тому, является ли это вашим лучшим решением.
kkrambo

Ответы:


17

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

Тем не менее, машина там, чтобы служить вам, а не наоборот. Общие практические правила запрещают плохим программистам писать действительно плохой код. Гораздо лучше понять , как именно работает машина, а затем разработать хороший способ использовать эти возможности для выполнения желаемой задачи.

Имейте в виду, что, если вы действительно не ограничены циклами или областями памяти (это, безусловно, может произойти), в противном случае вы захотите оптимизировать для ясности и удобства сопровождения кода. Например, если у вас есть 16-битный компьютер, который обновляет 32-битный счетчик в прерывании тактового тактового сигнала, вам необходимо убедиться, что когда код переднего плана считывает счетчик, его две половины согласованы. Один из способов - отключить прерывания, прочитать два слова, а затем снова включить прерывания. Если задержка прерывания не критична, то это вполне приемлемо.

В случае, когда у вас должна быть низкая латентность прерывания, вы можете, например, прочитать старшее слово, прочитать младшее слово, снова прочитать старшее слово и повторить, если оно изменилось. Это немного замедляет код переднего плана, но вообще не добавляет задержки прерывания. Есть разные маленькие хитрости. Другой может быть установить флаг в подпрограмме прерывания, который указывает, что счетчик должен быть увеличен, а затем сделать это в основном цикле событий в коде переднего плана. Это прекрасно работает, если частота прерываний счетчика достаточно медленная, так что цикл обработки будет делать приращение до того, как флаг будет установлен снова.

Или вместо флага используйте счетчик из одного слова. Код переднего плана содержит отдельную переменную, которая содержит последний счетчик, до которого она обновила систему. Он вычитает беззнаковый текущий счетчик минус сохраненное значение, чтобы определить, сколько тиков за раз он должен обработать. Это позволяет коду переднего плана пропускать до 2 N -1 событий за раз, где N - количество битов в собственном слове, которое ALU может обработать атомарно.

Каждый метод имеет свой набор преимуществ и недостатков. Единого правильного ответа не существует. Опять же, поймите, как работает машина, тогда вам не понадобятся практические правила.


7

Если вам нужен критический раздел, вы должны убедиться, что операция, защищающая ваш критический раздел, является атомарной и не может быть прервана.

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

В зависимости от вашей системы могут возникнуть некоторые проблемы, например, может быть пропущено прерывание. Некоторые микроконтроллеры устанавливают флаги независимо от состояния разрешения глобальных прерываний, и после выхода из критической секции прерывания выполняются и просто задерживаются. Но если у вас есть прерывание, которое происходит с высокой скоростью, вы можете пропустить второй раз, когда прерывание произошло, если вы блокируете прерывания на слишком долгое время.

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

Я обнаружил, что программирую процедуры обработки прерываний как можно короче. Поэтому они просто устанавливают флаг, который затем проверяется во время обычной программы. Но если вы сделаете это, остерегайтесь условий гонки, ожидая, пока этот флаг будет установлен.

Вариантов много, и, конечно же, нет ни одного правильного ответа на этот вопрос, эта тема требует тщательного проектирования и заслуживает большего внимания, чем другие вещи.


5

Если вы определили, что часть кода должна работать непрерывно, то, за исключением необычных обстоятельств, вы должны отключить прерывания на минимально возможную продолжительность, чтобы выполнить задачу, и повторно включить их после.

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

Это все равно позволило бы произойти прерыванию, переходу кода, проверке, а затем возврату. Если ваш код может справиться с таким большим количеством прерываний, то вам, вероятно, следует просто спроектировать ISR для установки флага, а не выполнять проверку - она ​​будет короче - и обрабатывать флаг в вашей обычной кодовой подпрограмме. Это звучит так, как будто кто-то вставил слишком много кода в прерывание, и они используют прерывание для выполнения более длительных действий, которые должны выполняться в обычном коде.

Если вы имеете дело с кодом, в котором прерывания длинные, предложенный вами флаг может решить проблему, но все же будет лучше просто отключить прерывания, если вы не можете повторно кодировать код, чтобы исключить излишний код в прерывании. ,

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

  • Если в критической секции не было прерываний, ни одно не выполняется после.
  • Если одно прерывание происходит во время критической секции, одно выполняется после.
  • Если в критической секции происходит несколько прерываний, только одно выполняется после.

Если ваша система сложна и должна отслеживать прерывания более полно, вам придется разработать более сложную систему для отслеживания прерываний и соответствующей работы.

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

Это должно исключить все, кроме очень, очень немногих ситуаций, когда вам может понадобиться непрерывный раздел кода.


Я обновил пост, чтобы лучше объяснить ситуацию, в которой я работаю :)
Умберто Д.

1
В вашем примере кода я рассмотрел бы отключение определенного прерывания кнопки, когда оно не нужно, и включение его, когда это необходимо. Делать это часто - не проблема его замысла. Оставьте глобальные прерывания включенными, чтобы при необходимости вы могли добавить другие прерывания в код позже. В качестве альтернативы просто сбросьте флаг, когда вы перейдете в состояние A, иначе игнорируйте его. Если кнопка нажата и флаг установлен, кого это волнует? Не обращайте на это внимания, пока не вернетесь к состоянию А.
Адам Дэвис,

Да! Это может быть решением, потому что в реальном проекте я часто захожу в LPM3 (MSP430) с включенными глобальными прерываниями, и я выхожу из LPM3, возобновляя выполнение, как только обнаруживается прерывание. Таким образом, решение, которое вы представили, о котором я думаю, сообщается во второй части кода: разрешить прерывания, как только я начну выполнять действие do состояния, которое в этом нуждается, и отключить перед переходом в блок переходов. Может ли другое возможное решение заключаться в том, чтобы отключить прерывание непосредственно перед тем, как покинуть «Блокировать активность», и повторно включить некоторое время (когда?) После?
Умберто Д.

1

Размещение флага внутри ISR, как вы описываете, вероятно, не сработает, так как вы в основном игнорируете событие, которое вызвало прерывание. Глобальное отключение прерываний обычно является лучшим выбором. Как уже говорили другие, вам не нужно делать это очень часто. Имейте в виду, что любое чтение или запись, выполняемое с помощью одной инструкции, не должно быть защищено, так как инструкция либо выполняется, либо нет.

Многое зависит от того, какими ресурсами вы пытаетесь поделиться. Если вы передаете данные из ISR в основную программу (или наоборот), вы можете реализовать что-то вроде буфера FIFO. Единственной элементарной операцией будет обновление указателей чтения и записи, что минимизирует количество времени, которое вы проводите с отключенными прерываниями.


0

Есть небольшая разница, которую нужно учитывать. Вы можете выбрать «задержать» обработку прерывания или «игнорировать» и прерывать.

Часто в коде мы говорим, что отключаем прерывание. Что, вероятно, произойдет из-за аппаратных функций, так это то, что как только мы включим прерывания, он сработает. Это каким-то образом задерживает прерывание. Чтобы система работала надежно, нам нужно знать максимальное время, которое мы можем отложить на обработку этих прерываний. И затем убедитесь, что все случаи, когда прерывания отключены, будут завершены в более короткие сроки.

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

Используя наш сайт, вы подтверждаете, что прочитали и поняли нашу Политику в отношении файлов cookie и Политику конфиденциальности.
Licensed under cc by-sa 3.0 with attribution required.