Может ли функция вызываться автоматически при изменении входа?

21

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

int pinValue = LOW;

void pinChanged()
{
    //...
}

void setup()
{
    pinMode(2, INPUT);
}

void loop()
{
    // Read current input
    int newValue = digitalRead(2);

    // Has the input changed?
    if (newValue != pinValue) {
        pinValue = newValue;
        pinChanged();
    }
}

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

Есть ли способ заставить Arduino обнаруживать изменение входа и автоматически вызывать мою функцию?

Питер Блумфилд
источник
1
То, что вы ищете, это внешнее прерывание
Butzke

Ответы:

26

Вы можете сделать это с помощью внешних прерываний. Большинство Arduinos поддерживают это только на ограниченном числе контактов. Для получения полной информации см. Документацию по attachInterrupt().

Предполагая, что вы используете Uno, вы можете сделать это так:

void pinChanged()
{
    //...
}

void setup()
{
    pinMode(2, INPUT);
    attachInterrupt(0, pinChanged, CHANGE);
}

void loop()
{
}

Он будет вызываться pinChanged()всякий раз, когда обнаруживается изменение на внешнем прерывании 0. На Uno, который соответствует выводу GPIO 2. На других платах нумерация внешних прерываний отличается на других платах, поэтому важно проверить соответствующую документацию.

Однако у этого подхода есть ограничения. Пользовательская pinChanged()функция используется в качестве подпрограммы обработки прерывания (ISR). Это означает, что остальная часть кода (все внутри loop()) временно останавливается во время выполнения вызова. Чтобы предотвратить нарушение важного времени, вы должны стремиться сделать ISR максимально быстрыми.

Также важно отметить, что никакие другие прерывания не будут выполняться во время вашего ISR. Это означает, что все, что зависит от прерываний (например, ядро delay()и millis()функции), может не работать должным образом внутри него.

Наконец, если вашему ISR необходимо изменить какие-либо глобальные переменные в эскизе, они обычно должны быть объявлены как volatile, например:

volatile int someNumber;

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

Питер Блумфилд
источник
Что касается «коротких импульсов», упомянутых в вопросе, существует ли минимальное время, в течение которого штырь должен находиться в состоянии, чтобы он мог вызвать прерывание? (очевидно, это будет намного меньше, чем опрос, который зависит от того, что еще происходит в цикле)
sachleen
1
@sachleen Это будет работать до тех пор, пока это не произойдет во время выполнения функции ISR (как объяснено в ответе); вот почему pinChanged()должно быть как можно короче. Следовательно, обычно минимальное время должно быть временем выполнения самой pinChanged()функции.
jfpoilpret
2
+1 за этот очень подробный ответ, который включает в себя все важные вещи, которые нужно учитывать при использовании прерываний!
jfpoilpret
3
В дополнение к объявлению общих глобальных значений volatile, если глобальная переменная шире, чем 1 байт, как, например, someNumber, необходимо защитить от прерывания смены штырьков, возникающего между доступами байтов программой. Такое утверждение someNumber +=5;включает в себя добавление младших байтов и добавление старших байтов с включенным переносом. Эти два (больше, для более широких переменных) не должны быть разделены прерыванием. Достаточно отключить прерывания и восстановить их до и после операции (соответственно).
JRobert
@sachleen - относительно минимального размера импульса. Трудно найти определенный ответ в таблице данных, но, судя по времени прерываний смены контактов, они защелкиваются в течение половины такта. Как только прерывание «запоминается», оно остается в памяти до тех пор, пока не сработает ISR и не обработает его.
Ник Гэммон
5

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

К счастью, библиотека PinChangeInt делает это легко.

PCintPort::attachInterrupt(PIN, burpcount,RISING); // attach a PinChange Interrupt to our pin on the rising edge
// (RISING, FALLING and CHANGE all work with this library)
// and execute the function burpcount when that pin changes
mpflaga
источник
0

Если вы хотите обнаружить напряжение , превышающее пороговое значение , а не просто ВЫСОКОЕ или НИЗКОЕ, вы можете использовать аналоговый компаратор. Пример скетча:

volatile boolean triggered;

ISR (ANALOG_COMP_vect)
  {
  triggered = true;
  }

void setup ()
  {
  Serial.begin (115200);
  Serial.println ("Started.");
  ADCSRB = 0;           // (Disable) ACME: Analog Comparator Multiplexer Enable
  ACSR =  bit (ACI)     // (Clear) Analog Comparator Interrupt Flag
        | bit (ACIE)    // Analog Comparator Interrupt Enable
        | bit (ACIS1);  // ACIS1, ACIS0: Analog Comparator Interrupt Mode Select (trigger on falling edge)
   }  // end of setup

void loop ()
  {
  if (triggered)
    {
    Serial.println ("Triggered!"); 
    triggered = false;
    }

  }  // end of loop

Это может быть полезно для таких вещей, как детекторы света, где вам может потребоваться обнаружить изменение (скажем) от 1 В до 2 В на входе.

Пример схемы:

введите описание изображения здесь

Вы также можете использовать Input Capture Unit на процессоре, который запоминает точное время определенных входов, сохраняя текущий счетчик Timer / Counter 1. Это позволяет вам сохранить точный (ну, почти точный) момент, когда событие возник интерес, вместо того, чтобы вводить задержку (вероятно, несколько микросекунд), прежде чем ISR можно будет использовать для захвата текущего времени.

Для приложений, критичных по времени, это может повысить точность.

Пример применения: превратите Arduino в тестер конденсаторов

Ник Гаммон
источник