/**
* @file esp32_alarme_interrupcao_timer.ino
* @brief Exemplo de alarme armado/desarmado com ESP32 usando:
* - Timer de hardware (Timer Group) para piscar um LED em ISR
* - Leitura de botão com debounce por software (sem delay bloqueante prolongado)
* - Pisca de 3 LEDs com timers baseados em millis() (sem usar delay)
*
* @hardware
* Placa: ESP32 (Qualquer variante suportada pela Arduino-ESP32)
* Pinos:
* - Led1 -> GPIO 14 (saída) | pisca a cada 1 s
* - Led2 -> GPIO 13 (saída) | pisca a cada 5 s
* - Led3 -> GPIO 12 (saída) | pisca a cada 250 ms quando o alarme (bt1_pressionado) estiver ARMADO
* - LED -> GPIO 27 (saída) | pisca por interrupção de timer (100 ms)
* - Botao1-> GPIO 26 (entrada com pull-up) | arma/desarma o alarme (toggle por borda de descida)
* - Botao2-> GPIO 22 (entrada com pull-up) | reservado para expansão
*
* @dependencias
* - Core Arduino para ESP32
* - <ESP32Time.h> (incluído aqui, mas **não** utilizado — pode remover se desejar)
*
* @conceitos
* - Debounce simples por amostragem repetida
* - Temporização não-bloqueante com millis()
* - ISR em IRAM (IRAM_ATTR) para rotina de timer
*
* @compilacao
* - Arduino IDE: selecione uma placa ESP32 e porta correta
*
* @autor Rinaldo
* @data 2025-09-03
* @licenca MIT
*
* @notes
* - Aqui, o exemplo está adaptado ao ESP32, usando timer de hardware; não há attachInterrupt() neste sketch.
* - Se for usar interrupção externa no ESP32, prefira attachInterrupt(digitalPinToInterrupt(pin), isr, mode).
*
* @warning
* - Evite fazer prints dentro de ISR. Aqui, a ISR apenas alterna um pino.
* - Variáveis compartilhadas entre ISR e loop devem ser 'volatile'
*
* @changelog
* - Alinhado 'debounceDelay' para uso único.
* - Comentários extensivos e cabeçalhos de função adicionados.
*/
#include <ESP32Time.h> // (Opcional neste exemplo; pode remover se não usar)
// ===================== Mapeamento de Pinos =====================
#define Led1 14
#define Led2 13
#define Led3 12
#define LED 27
#define Botao1 26 // Botão para armar/desarmar (entrada com pull-up)
#define Botao2 22 // Botão extra (reservado)
// ===================== Parâmetros de Tempo =====================
#define debounceDelay 10 // [ms] tempo mínimo para considerar estável o estado do botão
// ===================== Estado da Aplicação =====================
bool bt1_pressionado = 0; // "Estado lógico" do alarme (armado = 1 / desarmado = 0)
bool bt2_pressionado = 0; // reservado
bool bt1_estadoAtual = 0;
bool bt2_estadoAtual = 0;
bool bt1_ultimoEstado = 0;
bool bt2_ultimoEstado = 0;
int ledAlarmeStatus = LOW; // reservado (se quiser refletir estado do alarme)
int ledArmadoStatus = LOW; // reservado
unsigned long tempoAtual = 0;
unsigned long tempoAnterior1 = 0; // referência para pisca de 1 s
unsigned long tempoAnterior2 = 0; // referência para pisca de 5 s
unsigned long tempoAnterior3 = 0; // referência para pisca de 250 ms
// ===================== Timer de Hardware (ESP32) =====================
hw_timer_t *My_timer = NULL;
/**
* @brief Debounce simples por software para uma entrada digital.
*
* @param pin GPIO do botão (entrada digital com pull-up)
* @param ultimoEstado Último estado considerado estável (true/false)
* @return boolean Estado estável atual após aplicar debounce
*
* @details
* Lê o pino; se houver mudança em relação a `ultimoEstado`, aguarda `debounceDelay`
* milissegundos e confirma a leitura. Retorna o estado final.
*
* @note
* - Como o ESP32 é rápido, usar um pequeno atraso aqui é aceitável. Se preferir 100% não-bloqueante,
* reimplemente via máquina de estados com timestamps.
*/
boolean debounce(int pin, boolean ultimoEstado) {
boolean atual = digitalRead(pin); // amostra atual
if (ultimoEstado != atual) { // detectou transição?
delay(debounceDelay); // aguarda estabilizar
atual = digitalRead(pin); // reconfirma leitura
}
return atual;
}
/**
* @brief Rotina de Interrupção do Timer (ISR) — pisca o LED associado.
*
* @interrupt
* @note IRAM_ATTR garante que a ISR rode a partir da RAM interna (recomendado no ESP32).
* @warning Não use Serial.print aqui. Evite operações lentas/bloqueantes.
*/
void IRAM_ATTR onTimer() {
digitalWrite(LED, !digitalRead(LED)); // alterna o estado do pino (pisca a cada 100 ms)
}
/**
* @brief Inicialização da aplicação.
*
* @steps
* 1) Configura Serial e pinos.
* 2) Inicializa Timer de hardware do ESP32:
* - timerBegin(timer=0, prescaler=80, countUp=true) => 80 MHz/80 = 1 MHz (1 tick = 1 us)
* - timerAlarmWrite(..., 100000 us, auto-reload=true) => interrupção a cada 100 ms
* 3) Anexa ISR e habilita alarme do timer.
*/
void setup() {
Serial.begin(115200);
pinMode(Led1, OUTPUT);
pinMode(Led2, OUTPUT);
pinMode(Led3, OUTPUT);
pinMode(LED, OUTPUT);
pinMode(Botao1, INPUT_PULLUP);
pinMode(Botao2, INPUT_PULLUP);
// --- Configuração do Timer de hardware (grupo do ESP32) ---
My_timer = timerBegin(/*timer*/0, /*prescaler*/80, /*countUp*/true);
timerAttachInterrupt(My_timer, &onTimer, /*edge*/true);
timerAlarmWrite(My_timer, /*intervalo_us*/100000, /*auto-reload*/true);
timerAlarmEnable(My_timer);
// Estados iniciais
bt1_ultimoEstado = digitalRead(Botao1); // registra estado inicial do botão (com pull-up)
digitalWrite(Led1, LOW);
digitalWrite(Led2, LOW);
digitalWrite(Led3, LOW);
digitalWrite(LED, LOW);
Serial.println(F("Sistema iniciado. Pressione o Botao1 para armar/desarmar o alarme."));
}
/**
* @brief Laço principal (não-bloqueante).
*
* @behavior
* - Lê Botao1 com debounce; ao detectar borda de descida (pressionado), alterna estado do alarme.
* - Pisca Led3 (250 ms) somente quando alarme estiver ARMADO (bt1_pressionado == true).
* - Pisca Led2 a cada 5 s e Led1 a cada 1 s, sempre (exemplos de temporizacao com millis()).
*
* @complexity
* O(1) por iteração; sem delays prolongados.
*
* @TODO
* - Usar attachInterrupt no Botao1 para capturar borda e apenas agendar a leitura debounced no loop.
* - Mapear Botao2 para uma segunda função (ex.: sirene, mudar duty, etc.).
*/
void loop() {
// --- Leitura com debounce e detecção de borda (pressionamento) ---
bt1_estadoAtual = debounce(Botao1, bt1_ultimoEstado);
// Pressionamento detectado quando sai de HIGH (pull-up) para LOW (apertado)
if (bt1_ultimoEstado == true && bt1_estadoAtual == false) {
bt1_pressionado = !bt1_pressionado; // alterna armado/desarmado
if (bt1_pressionado) {
Serial.println(F("Alarme ARMADO (botao pressionado)"));
} else {
Serial.println(F("Alarme DESARMADO (botao liberado)"));
}
}
bt1_ultimoEstado = bt1_estadoAtual;
// --- Temporizadores baseados em millis() (não bloqueantes) ---
tempoAtual = millis();
// Pisca Led3 a cada 250 ms SOMENTE quando o alarme estiver armado
if (bt1_pressionado) {
if (tempoAtual - tempoAnterior3 >= 250) {
tempoAnterior3 = tempoAtual;
digitalWrite(Led3, !digitalRead(Led3));
Serial.print(F("tempoAnterior3: "));
Serial.println(tempoAnterior3);
}
} else {
digitalWrite(Led3, LOW); // alarme desarmado => LED3 apagado
}
// Pisca Led2 a cada 5 s (independente do estado do alarme)
if (tempoAtual - tempoAnterior2 >= 5000) {
tempoAnterior2 = tempoAtual;
digitalWrite(Led2, !digitalRead(Led2));
Serial.print(F("tempoAnterior2: "));
Serial.println(tempoAnterior2);
}
// Pisca Led1 a cada 1 s (independente do estado do alarme)
if (tempoAtual - tempoAnterior1 >= 1000) {
tempoAnterior1 = tempoAtual;
digitalWrite(Led1, !digitalRead(Led1));
Serial.print(F("tempoAnterior1: "));
Serial.println(tempoAnterior1);
}
}