As dicas aqui contidas resolveram problemas que eu encontrei, mas podem não ser a melhor forma de resolve-los, não exite em contribuir nos comentários.
Os exemplos de código consideram o uso da biblioteca da Adafruit para o MCP 23017.
É importante que alguns detalhes sejam observados:
1 - Coloque um capacitor de 1µF em paralelo com o botão (caso você use). Ative, no código, o pull-up interno do MCP23017 que é de 100KΩ, então teremos um resistor em série com o capacitor, quando o botão está aberto. E o tempo de suavização (anti-debounce) será de 0,1s (τ = RC => τ = 100K*1µ => τ = 0,1s).
O uso deste capacitor (um em paralelo para cada botão) além do efeito desejado que é a suavização durante o acionamento tem um efeito indesejado: quando o circuito é energizado o capacitor está descarregado e irá parecer para o MCP 23017 que ocorreu um acionamento do botão. Para eliminar este efeito indesejado é necessário incluir um delay de 60ms (testado empiricamente como um tempo adequado 20ms não deu certo 30ms sim, então escolhi 60ms para ter folga ) e uma leitura para limpar o buffer, no código da Arduino no setup depois da inicialização do MCP 23017.
#define button1 13 #define led 0 void setup() { //inicializa o MCP23017 mcp.begin(); // use default address 0 mcp.setupInterrupts(true, false, LOW); // mirror habilitado - cada interrupcao em INTA e espelhada em INTB //saídas (só para mostrar com se faz) mcp.pinMode(led, OUTPUT);//pino 0 mcp_PinA0 configurado com saída //entradas mcp.pinMode(button1, INPUT); // pino 13 mcp_PinB5 button1 configurado com entrada mcp.pullUp(button1, HIGH); // ativa pullup interno de 100K mcp.setupInterruptPin(button1, FALLING); // gera interrupcao quando de sai de nivel alto para nivel baixo //tratamento para o uso dos capacitores em paralelo com os botões delay(60);//pequena espera antes da leitura inicial mcp.getLastInterruptPinValue();//leitura inicial, para limpar o buffer
2 - Cuidados no código com a interrupção
A) Quando se trabalha com Interrupções, uma boa prática é realizar o mínimo possível dentro da função (interrupt service routine (ISR)) chamada pela interrupção, preferencialmente mude uma variável booleana e trate no loop. IMPORTANTE está variável global e volatile, por exemplo:
volatile boolean interruptHappen = false;
Normalmente no loop você terá uma chamada que trata a interrupção, algo como:
if(interruptHappen) handleInterrupt();
mas ainda não implemente desta forma, veja o item C).
B) Para evitar que ocorra uma interrupção enquanto a função handleInterrupt() deve desligar a interrupção no seu inicio e ligar no final:
void handleInterrupt(){ detachInterrupt(arduinoInterrupt); .... attachInterrupt(arduinoInterrupt,intCallBack,FALLING); }
C) É recomendável que se limite o intervalo mínimo entre uma interrupção e a próxima (depois de experimentar uma instabilidade, que travava o funcionamento do MCP 23017, eu resolvi com este intervalo mínimo, equivale a um anti debounce de software para a interrupção.
//variávei globais volatile unsigned long button_time = 0; //anti debounce de software na interrupção volatile unsigned long last_button_time = 0; //anti debounce de software na interrupção volatile int buttonInterruptInterval = 100; //intervalo mínimo entre uma interrupção e outra, evita problema de debounce volatile uint8_t mcpPin; ... dentro do loop: if (interrupted) { button_time = millis(); //Anti debounce de software para a interrupção mcpPin=mcp.getLastInterruptPin(); //lê qual pino foi acionado if (((button_time - last_button_time) > buttonInterruptInterval)&&(mcpPin==button1))//somente trata se for um botão válido e em intervalos superiores a 100ms { handleInterrupt(); last_button_time = button_time; } else mcp.getLastInterruptPinValue();//senão limpa o bus (ignora o acionamento) }
D) A função hanleInterrupt() deve ser a mais direta possível, não adicione código que poderia estar em outra função, veja um exemplo:
void handleInterrupt() { detachInterrupt(digitalPinToInterrupt(PIN_INTERRUPT));//desativa a interrupção para evitar multiplos acionamentos if(mcpPin==button1) {código de botão 1();} // false = desligado while( ! (mcp.digitalRead(button1) );//espera o botão ser solto updateLedsProfile(); cleanInterrupts(); attachInterrupt(digitalPinToInterrupt(PIN_INTERRUPT), intCallBack, FALLING);//reativa a interrupção }
E) Estará sendo usada comunicação I2C, que é uma comunicação serial, logo recomendo uma implementação como a usada acima, no passo C uma variável global volatil mcpPin é carregada usando mcp.getLastInterruptPin() esta mesma variável é usada dentro do handleInterrupt() para definir a ação, ao invés de carregar novamente. A ideia é: seja simples recebeu uma interrupção identifique o botão e trate.
Informações sobre debounce podem ser encontradas em:
http://www.gammon.com.au/forum/?id=10945
Informações sobre interrupções podem ser encontradas em: https://www.arduino.cc/en/Reference/AttachInterrupt
Atenção: Quando se trabalha com delay() no loop pode ocorrer instabilidade na chamada da função através de interrupção, mesmo com acionamento LOW (por exemplo). Esta instabilidade aumenta caso a interrupção seja acionada por transição como FALLING.