Uma função pode ser chamada automaticamente quando uma entrada é alterada?

21

Atualmente, meu esboço está verificando um pino de entrada toda vez em volta do loop principal. Se ele detectar uma alteração, chamará uma função personalizada para responder a ela. Aqui está o código (reduzido ao essencial):

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();
    }
}

Infelizmente, isso nem sempre funciona corretamente para alterações muito curtas na entrada (por exemplo, pulsos breves), especialmente se loop()estiver um pouco lento.

Existe uma maneira de fazer o Arduino detectar a alteração de entrada e chamar minha função automaticamente?

Peter Bloomfield
fonte
1
O que você está procurando é uma interrupção externa
Butzke

Respostas:

26

Você pode fazer isso usando interrupções externas. A maioria dos Arduinos apenas suporta isso em um número limitado de pinos. Para detalhes completos, consulte a documentação em attachInterrupt().

Supondo que você esteja usando um Uno, você pode fazer assim:

void pinChanged()
{
    //...
}

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

void loop()
{
}

Isso será chamado pinChanged()sempre que uma alteração for detectada na interrupção externa 0. No Uno, isso corresponde ao pino 2. do GPIO. A numeração da interrupção externa é diferente em outras placas, portanto, é importante verificar a documentação relevante.

Existem limitações para essa abordagem. A pinChanged()função personalizada está sendo usada como uma rotina de serviço de interrupção (ISR). Isso significa que o restante do código (tudo incluído loop()) é temporariamente interrompido enquanto a chamada está em execução. Para evitar interrupções em qualquer momento importante, você deve tentar tornar os ISRs o mais rápido possível.

Também é importante observar que nenhuma outra interrupção será executada durante o seu ISR. Isso significa que qualquer coisa que dependa de interrupções (como o núcleo delay()e as millis()funções) pode não funcionar corretamente dentro dele.

Por fim, se o seu ISR precisar alterar quaisquer variáveis ​​globais no esboço, elas geralmente devem ser declaradas como volatile, por exemplo:

volatile int someNumber;

Isso é importante porque informa ao compilador que o valor pode mudar inesperadamente, portanto, tenha cuidado para não usar cópias / caches desatualizados.

Peter Bloomfield
fonte
com relação aos "pulsos breves" mencionados na pergunta, há um tempo mínimo em que o pino deve estar em um estado para acionar a interrupção? (obviamente que vai ser muito menos do que emissão, o que depende do que mais está acontecendo no loop)
sachleen
1
@sachleen Isso funcionará desde que não aconteça durante a execução de uma função ISR (como explicado na resposta); é por isso que pinChanged()deve ser o mais curto possível. Portanto, normalmente o tempo mínimo deve ser o tempo para executar a pinChanged()própria função.
Jfpoilpret
2
+1 para esta resposta muito detalhada, que inclui todas as coisas importantes que você deve se preocupar ao usar interrupções!
Jfpoilpret
3
Além de declarar globais compartilhados volatile, se a variável global for maior que 1 byte, como someNumber, você deverá se proteger contra a interrupção de alteração de pin que ocorre entre acessos de byte pelo programa. Uma declaração como someNumber +=5;envolve a adição de bytes baixos e a adição de bytes altos com o carry incluído. Esses dois (mais, para variáveis ​​mais amplas) não devem ser divididos por uma interrupção. Desligar as interrupções e restaurá-las antes e depois da operação (respectivamente) é suficiente.
JRobert
@sachleen - referente ao tamanho mínimo do pulso. É difícil encontrar uma resposta definitiva na folha de dados, mas, a julgar pelo tempo das interrupções de troca de pinos, elas são travadas dentro de meio ciclo de relógio. Uma vez que a interrupção é "lembrada", ela permanece lembrada até o ISR entrar em ação e lidar com ela.
Nick Gammon
5

Qualquer estado de mudança em qualquer pino configurado como entrada digital pode criar uma interrupção. Diferentemente dos vetores exclusivos para as interrupções causadas pelo INT1 ou INT2, o recurso PinChangeInt usa um vetor comum e, em seguida, a Interrupt Service Routine (aka ISR) para esse vetor precisa determinar qual pino foi alterado.

Felizmente, a Biblioteca PinChangeInt facilita isso.

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
fonte
0

No caso de você desejar detectar uma tensão que ultrapassa um limite , em vez de ser apenas ALTA ou BAIXA, você pode usar o comparador analógico. Esboço de exemplo:

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

Isso pode ser útil para itens como detectores de luz, nos quais você pode precisar detectar uma mudança de (digamos) 1V para 2V em uma entrada.

Exemplo de circuito:

insira a descrição da imagem aqui

Você também pode usar a Unidade de captura de entrada no processador, que lembrará a hora exata de determinadas entradas, salvando a contagem atual do timer / contador 1. Isso permite armazenar o momento exato (bem, quase exato) que o evento de ocorreu um interesse, em vez de introduzir o atraso (provavelmente de alguns microssegundos) antes que um ISR possa ser usado para capturar a hora atual.

Para aplicações críticas de tempo, isso pode dar uma precisão um pouco maior.

Exemplo de aplicação: Transforme seu Arduino em um testador de capacitor

Nick Gammon
fonte