Отключить прерывания при выполнении важных действий?

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

/**
   Write weather data to csv
   commit: datetime,minHumidity,maxHumidity,minTemperature,maxTemperature,rainfall,irrigationRan
*/
void writeWeatherData() {
  noInterrupts();
  DBG_OUTPUT.println(F("[WEATHER DATA] Attempting to save weather data..."));
  bool writeHeader = SPIFFS.exists("/weather_data.csv") ? false : true;
  File file = SPIFFS.open("/weather_data.csv", "a");
  if (!file) {
    addPrintError("[WEATHER DATA] There was an error opening weather_data.csv for writing.");
    interrupts();
    return;
  }
  String data = "";
  if (writeHeader) {
    data += "datetime,minHumidity,maxHumidity,minTemperature,maxTemperature,rainfall,irrigationRan\r\n";
  }
  DateTime now = rtc.now();
  char buf[] = "MM/DD/YY hh:mm:ss";
  String t = now.toString(buf);
  float rainfall = getRainfall();
  data += t + "," + minHumidity + "," + maxHumidity + "," + minTemperature + "," + maxTemperature + "," + rainfall + "," + irrigationRan + "\r\n";
  if (file.print(data)) {
    DBG_OUTPUT.println(F("[WEATHER DATA] File was written."));
  } else {
    addPrintError("[WEATHER DATA] File write failed.");
  }
  file.close();
  DBG_OUTPUT.println(F("[WEATHER DATA] Task completed."));
  interrupts();
}

Я окружаю код noInterrupts и interrupts, однако я не уверен, что это необходимо. Предположим, что прерывание срабатывает именно в тот момент, когда вышеуказанная функция что-то сохраняет, что именно произойдет, если у меня не было noInterrupts .etc.?

, 👍1

Обсуждение

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

если нужно, то библиотека позаботится, @Juraj

в основе реализации SPIFFS лежит вызов ESP SDK с закрытым исходным кодом spi_flash_write, и, согласно ссылке, он отключает прерывания при записи во флэш-память., @Juraj


2 ответа


Лучший ответ:

3

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

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

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

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

Возьмите это в качестве примера: предположим, вы хотите использовать SPIFFS как в обработчике прерываний, так и вне обработчика прерываний. Итак, вы думаете, что будете умнее и отключите прерывания, прежде чем вызывать SPIFFS вне обработчика прерываний. Вы точно знаете, как работает SPIFFS? Знаете ли вы, использует ли SPIFFS какие-либо другие функции, для которых вам может понадобиться отключить прерывания в других частях вашего кода, чтобы обработчик прерываний не нарушил их работу? Вы точно знаете, что SPIFFS не выделяет память временно? Представьте, что это так — теперь вам также нужно найти каждое место в вашем коде, которое может выделять память и блокировать прерывания вокруг него. И, возможно, SPIFFS не выделяет память — но знаете ли вы, что в будущем SPIFFS никогда не будет выделять память?

Вы не можете делать предположения о том, как именно реализованы модули, которые вы вызываете, или как они будут реализованы в будущем.

Если вы серьезно не знаете, что делаете, на таких микроконтроллерах, как ардуино, ESP8266 и ESP32, лучшее, что может сделать обработчик прерывания, — это просто установить флаг, указывающий, что прерывание произошло, и позволить коду в loop() проверить наличие флага. Флаг, скорее всего, должен быть простой булевой переменной, объявленной как volatile, чтобы компилятор C знал, что ее значение может внезапно измениться:

volatile boolean interrupt_flag = false;

Безопасно установить для этого interrupt_flag значение true внутри обработчика прерывания, также безопасно проверить значение в loop(), сбросить его на false и выполняйте всю необходимую работу. Не делайте больше, если вы не знаете, что делаете.

,

чтобы было ясно: я не использую spiffs в обработчике прерываний. в моем случае обработчик прерывания просто увеличивает счетчик (при дожде). я просто беспокоился, не будут ли данные повреждены или вызовут сбой, если прерывание сработает во время некоторых хозяйственных операций, которые я делаю с spiffs. вы говорите, что я должен быть в порядке, не используя noInterrupts .etc.?, @Alex

«просто установите флаг, указывающий, что произошло прерывание, и позвольте коду в цикле () проверить наличие флага». - Хотя тогда вам вообще не нужен ISR, так как MCU уже имеет встроенный в свой регистр флаг прерывания. Вы также можете опросить этот флаг., @chrisl

Отличный ответ. Просто комментарий на тему «_правильно написанный обработчик прерывания сохранит любое необходимое состояние_»: если код не написан на ассемблере, код для сохранения и восстановления состояния пишется компилятором, и вы вполне можете доверять ему, чтобы сделать это правильно., @Edgar Bonet

@Alex Да, вам не следует отключать прерывания во время выполнения хозяйственных операций. Важно *не* отключать прерывания. Если бы это было проблемой, почти ни один код Arduino не работал бы правильно., @romkey

@chrisl правда! Хотя я думаю, что большинство людей, которые знают это, могут сделать больше в обработчике прерывания, чем установить флаг :), @romkey

@EdgarBonet хороший момент, аппаратное обеспечение тоже выполняет часть работы,, @romkey


2

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

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

Иногда у вас есть фрагмент кода, который нельзя прерывать. Может быть, это чрезвычайно критично ко времени, как генерация в программном обеспечении импульс шириной ровно 0,5 мкс. Чаще это будет чтение данных из памяти, которая изменяется обработчиком прерывания, и вы не хотите, чтобы эти данные были изменены в середине чтения операция. Такие фрагменты кода называются критическими разделами, и вы держите их в безопасности между noInterrupts() и interrupts(). Если запрос на прерывание срабатывает, когда программа выполняет критическую секцию, запрос приостанавливается и обслуживается только тогда, когда критическая секция готово. Это добавляет некоторую задержку к прерыванию, которое, если чрезмерное, может привести к тому, что прерывание пропустит свой крайний срок. Это причина, по которой критические разделы должны быть как можно короче.

Вам сказали, что обработчики прерываний должны быть как можно короче. возможно, но я не уверен, что вы знаете, почему это так. Фундаментальный Причина в том, что обработчики прерываний сами по себе являются критическими секциями. Пока работает обработчик, другие прерывания блокируются. На некоторых архитектуры (особенно не AVR, не знаю насчет ESP), только прерывания с более низким приоритетом блокируются, но это все еще проблема. обоснование того, что эти обработчики должны быть короткими, в равной степени применимо к любому критический раздел.

Теперь приведем пример того, какие плохие вещи могут случиться, если вы блокируете прерывания без необходимости, рассмотрите эту часть код:

noInterrupts();
Serial.println("Inside critical section.");
interrupts();

Это помещает строку "Внутри критической секции." в программный буфер. Вы можете сказать, что это не огромная задача. На самом деле нажатие байтов вывод на UART — это работа обработчика прерывания, запускаемого UART, когда он готов принять новый байт. Но что тогда произойдет, если в буфере недостаточно места для строки? В этом случае Serial.println() ожидает цикл занятости. Он ждет прерывание, чтобы переместить байты из буфера и освободить место для нового нить. Но подождите, мы только что отключили прерывания... Видите проблему? По этой причине обычно рекомендуется никогда не использовать Serial.print(). внутри обработчика прерывания. То же самое относится к любому критическому разделу.

Правка: как отмечает Юрай в комментарии, код HardwareSerial теперь реализует обход этой проблемы: если обнаруживает, что он должен ждать с отключенными прерываниями, он заботится о вызов самого обработчика прерывания. Это (относительно) поздно дополнение к коду HardwareSerial. Их авторы предположительно было свидетелем того, как слишком много новичков выполняли отладочные распечатки из обработчиков прерываний. и быть укушенным этой проблемой. Обратите внимание, что это обходной путь, а не решение: часто критическая секция будет длиться неоправданно долгое время.

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

,

write() аппаратного Serial проверяет, разрешены ли прерывания, и если нет, то напрямую вызывает функцию отправки, @Juraj

@Юрай: Ты прав. Я отредактировал ответ., @Edgar Bonet

Это все замечательные моменты, спасибо, что опубликовали их ... Жаль, что я не включил их в свой пост :), @romkey