Могут ли прерывания arduino возникать в середине оператора if?

Я получил проблемы за несколько дней, получил некоторый код, который сбрасывает время в миллисекундах при успешной связи i2c, если нет, он перезапускает реле через указанный период времени. Вот часть кода:

 if ((unsigned long)(i2c1Millis1 - i2cpreviousMillis1) >= i2cset1 )  // проверить, не будет ли перезапущен в ближайшее время
        {

i2c1Millis1 обновляются при каждом запуске цикла и при успешной связи i2c.

i2cpreviousMillis1 обновляется при успешном запросе i2c (поступающем из прерывания)

Мне удается выполнить отладку, когда происходит ложный перезапуск и выглядит так, что i2c1Millis1 ниже, чем i2cpreviousMillis1

Итак, вопрос: может ли произойти прерывание при сравнении этих двух миллисекунд, когда значение i2c1Millis1 берется для сравнения, а затем происходит прерывание, где i2cpreviousMillis1 обновляется до текущего миллисекунды и оно становится больше и вызывает условие?

(Я уже модифицировал свой код, i2cpreviousMillis1 обновляется значением i2c1Millis1 при успешной связи, чтобы предотвратить это, но он не работает, время от времени он делает ложные перезапуски ).

, 👍0

Обсуждение

Вы объявили i2c1Millis1 как изменчивый?, @Gerben

нет, никогда не использовал это, когда вы упоминаете, что я читал об этом, похоже, это не поможет, так как миллис длинный., @user3503519

Вы все равно должны использовать его!!! Я думаю, что это, скорее всего, вызовет проблемы, которые у вас есть. Оба являются проблемами, но один из них возникает чаще, чем другой., @Gerben

После того, как я переместил обновление Millis из функции прерывания, теперь все работает стабильно., @user3503519

Затем вы можете добавить volatile к флагу, установленному в прерывании., @Gerben

это нормально, когда я использую флаг, я предотвращаю обновление миллис при сравнении, это исправляет это, больше ничего не требуется, этот вопрос не о том, как решить мою проблему, а о том, чтобы понять, как могут возникать прерывания. Когда я это понимаю, есть много способов исправить это., @user3503519


3 ответа


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

1

Да. И это еще хуже. Поскольку ваши переменные имеют длину 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


1

Да, прерывание может произойти в любое время и может изменить значения во время вычисления. И так как вы работаете с переменными unsigned long (которые, насколько я помню, 4 байта), также возможно, что при вычислении используется старое значение для первых байтов и новое значение для других байтов, это означает, что используемое значение для расчета — полная ерунда.

Во избежание этого ваши проверки должны быть либо атомарными (то есть состоять только из 1 базовой инструкции, которую нельзя прервать, например, обрабатывать только 1 байт) (это невозможно здесь), либо вы должны предотвратить прерывание вычислений. Для этого вы можете отключить все или только соответствующие прерывания. Чтобы отключить все прерывания, вы можете использовать noInterrupts() и interrupts(), чтобы снова включить их.

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

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

,

0

Да. Прерывание может произойти где угодно.

Прерывания обрабатываются между каждой инструкцией языка ассемблера. Этот if будет состоять из множества ASM-инструкций, поэтому существует множество мест, где может произойти прерывание.

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

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

noInterrupts();
uint32_t localPreviousMillis = i2cpreviousMillis1;
interrupts();

if ((unsigned long)(i2c1Millis1 - localPreviousMillis) >= i2cset1 )  // проверяем, не будет ли перезапущен в ближайшее время

В 8-битной системе даже присвоение такого простого значения может быть прервано. 32-битное значение состоит из четырех 8-битных значений, и между копированием любого из этих 4 байтов может возникнуть прерывание.

,

спасибо за ваш ответ, упомянув ассемблер, я напоминаю старую Apple ][ дни, когда я писал на ассемблере и базовом :), @user3503519