Могут ли прерывания arduino возникать в середине оператора if?
Я получил проблемы за несколько дней, получил некоторый код, который сбрасывает время в миллисекундах при успешной связи i2c, если нет, он перезапускает реле через указанный период времени. Вот часть кода:
if ((unsigned long)(i2c1Millis1 - i2cpreviousMillis1) >= i2cset1 ) // проверить, не будет ли перезапущен в ближайшее время
{
i2c1Millis1
обновляются при каждом запуске цикла и при успешной связи i2c.
i2cpreviousMillis1
обновляется при успешном запросе i2c (поступающем из прерывания)
Мне удается выполнить отладку, когда происходит ложный перезапуск и выглядит так, что i2c1Millis1
ниже, чем i2cpreviousMillis1
Итак, вопрос: может ли произойти прерывание при сравнении этих двух миллисекунд, когда значение i2c1Millis1
берется для сравнения, а затем происходит прерывание, где i2cpreviousMillis1
обновляется до текущего миллисекунды и оно становится больше и вызывает условие?
(Я уже модифицировал свой код, i2cpreviousMillis1
обновляется значением i2c1Millis1
при успешной связи, чтобы предотвратить это, но он не работает, время от времени он делает ложные перезапуски ).
@user3503519, 👍0
Обсуждение3 ответа
Лучший ответ:
Да. И это еще хуже. Поскольку ваши переменные имеют длину long
, они имеют длину 4 байта, а большинство Arduino имеют только 8-битные процессоры, код за раз проверяет только один байт. Так, например, после проверки первого байта могло произойти прерывание, изменяющее 2-й, 3-й и 4-й байт. Таким образом, вы сравниваете первый байт старого значения и другие байты нового значения.
Чтобы предотвратить это, вы можете отключить прерывание на очень короткое время и сделать копию переменной.
cli();//отключаем прерывания
unsigned long temp = i2c1Millis1;
sei();//включаем прерывания
Или используйте макросы util/atomic.h
#include <util/atomic.h>
...
ATOMIC_BLOCK(ATOMIC_FORCEON)
{
unsigned long temp = i2c1Millis1;
}
У меня слишком много отключенных прерываний в моем коде, если их слишком сильно увеличить, я начну возиться с коммуникацией i2c, я изменил код, подняв флаг (переменная байта), чтобы инициировать обновление миллис в основном цикле, а не внутри маршрута прерывания, пока работает стабильный, @user3503519
В зависимости от того, насколько большой i2cset1
(<250), вы можете использовать одну переменную byte
вместо 4-байтовых long
. Таким образом, нет необходимости отключать прерывания., @Gerben
@user3503519 user3503519, так что вы использовали рекомендацию флага из ответа Крисла и приняли ответ Гербенса, @Juraj
Во-первых, я прочитал этот ответ и придумал использовать флаг и сделать это. после этого я прочитал другие ответы, @user3503519
Вы можете изменить принятый ответ на Крисл, если я не ошибаюсь., @Gerben
Да, прерывание может произойти в любое время и может изменить значения во время вычисления. И так как вы работаете с переменными unsigned long
(которые, насколько я помню, 4 байта), также возможно, что при вычислении используется старое значение для первых байтов и новое значение для других байтов, это означает, что используемое значение для расчета — полная ерунда.
Во избежание этого ваши проверки должны быть либо атомарными (то есть состоять только из 1 базовой инструкции, которую нельзя прервать, например, обрабатывать только 1 байт) (это невозможно здесь), либо вы должны предотвратить прерывание вычислений. Для этого вы можете отключить все или только соответствующие прерывания. Чтобы отключить все прерывания, вы можете использовать noInterrupts()
и interrupts()
, чтобы снова включить их.
Но сомнительно, зачем нужно обновлять в этот раз прямо в ISR. Вместо этого вы можете установить атомарный флаг (1 байт) и проверить этот флаг в цикле. Если флаг был установлен, сбросьте его и сохраните значение в миллисекундах. Если ваш цикл работает достаточно быстро (что кажется так, поскольку вы уже используете его для установки отметки времени в миллисекундах), этого должно быть достаточно
Можно использовать оба способа, и это зависит от вас, можно ли отключать прерывания на короткое время, поскольку таким образом вы можете пропустить прерывание I2C.
Да. Прерывание может произойти где угодно.
Прерывания обрабатываются между каждой инструкцией языка ассемблера. Этот if
будет состоять из множества ASM-инструкций, поэтому существует множество мест, где может произойти прерывание.
Чтобы избежать этого, вы используете так называемые критические секции, где вы отключаете прерывания перед выполнением некоторых операций, а затем снова включаете их после этого.
Рекомендуется делать критические разделы как можно короче, чтобы не нарушать прерывания. По этой причине большинство критических разделов обычно включают копирование значений в локальные переменные или из них перед выполнением над ними операций.
noInterrupts();
uint32_t localPreviousMillis = i2cpreviousMillis1;
interrupts();
if ((unsigned long)(i2c1Millis1 - localPreviousMillis) >= i2cset1 ) // проверяем, не будет ли перезапущен в ближайшее время
В 8-битной системе даже присвоение такого простого значения может быть прервано. 32-битное значение состоит из четырех 8-битных значений, и между копированием любого из этих 4 байтов может возникнуть прерывание.
спасибо за ваш ответ, упомянув ассемблер, я напоминаю старую Apple ][ дни, когда я писал на ассемблере и базовом :), @user3503519
- Использование millis() и micros() внутри процедуры прерывания
- Подсчет импульсов с прерыванием
- Устранение дребезга кнопки с помощью прерывания
- Программа arduino выдаёт ошибку expected //primary-expression before ')' token error: //expected ';' before '}' token E
- Почему необходимо использовать ключевое слово volatile для глобальных переменных при обработке прерываний в ардуино?
- Как сгенерировать аппаратное прерывание в mpu6050 для пробуждения Arduino из режима SLEEP_MODE_PWR_DOWN?
- Использование поворотных энкодеров с прерываниями смены контактов
- Arduino непрерывно считывает значение АЦП с помощью прерывания
Вы объявили
i2c1Millis1
какизменчивый
?, @Gerbenнет, никогда не использовал это, когда вы упоминаете, что я читал об этом, похоже, это не поможет, так как миллис длинный., @user3503519
Вы все равно должны использовать его!!! Я думаю, что это, скорее всего, вызовет проблемы, которые у вас есть. Оба являются проблемами, но один из них возникает чаще, чем другой., @Gerben
После того, как я переместил обновление Millis из функции прерывания, теперь все работает стабильно., @user3503519
Затем вы можете добавить
volatile
к флагу, установленному в прерывании., @Gerbenэто нормально, когда я использую флаг, я предотвращаю обновление миллис при сравнении, это исправляет это, больше ничего не требуется, этот вопрос не о том, как решить мою проблему, а о том, чтобы понять, как могут возникать прерывания. Когда я это понимаю, есть много способов исправить это., @user3503519