quinta-feira, novembro 17, 2016

Novo Timer para Apresentações - Software

Vamos dar uma olhada rápida no software que eu fiz para a palestra/oficina de montagem de timers.


O princípio básico deste software foi fazer algo simples e aproveitar ideias que já usei antes.

Do ponto de vista global o timer possui um estado (andando ou parado) e um valor a apresentar no display. Para facilitar, vamos manter também um flag para indicar que a contagem chegou a zero (alarme). O valor será armazenado em quatro bytes (um por dígito, com valor de 0 a 9).
// Estado do timer
static bool bParado = true;
static bool bAlarme = false;
static char valor[4];
O coração de tudo é uma temporização para contagem de tempo e atualização do display. A precisão do cristal do Arduino é mais que suficiente para esta aplicação. Para ficar mais simples, vamos usar uma interrupção de timer a cada 5 milisegundos. A biblioteca Timer1 é perfeita para isto. Além de apresentar o valor atual no display, a rotina de interrupção irá decrementar o valor, piscar os pontos e pulsar o buzzer. Começando pelos pontos, vamos piscá-los a cada 50 interrupções quando o timer estiver andando e mantê-los acesos quando o timer estiver parado:
  static uint8_t cntPonto = 50;
  static uint8_t mskPonto = 0;

  if (bParado) {
    mskPonto = 0x80;
  } else if (--cntPonto == 0) {
      cntPonto = 50;
      mskPonto ^= 0x80;
  }
Quando o contador chega a zero (alarme), o buzzer é pulsado junto com os pontoes:
  if (bAlarme) {
    // Pulsar o buzzer junto com o ponto
    digitalWrite (pinBuzzer, (mskPonto == 0) ? LOW : HIGH);      
  }
Se o timer está andando e não atingiu zero, a contagem deve ser decrementada a cada 200 interrupções. O código abaixo faz na raça o tratamento dos quatro dígitos e a detecção de zero.
  static uint8_t cntSeg = 200;

    // Trata contagem do tempo
    if (--cntSeg == 0) {
      cntSeg = 200;
      if (valor[3] > 0) {
        valor[3]--;
      } else {
        valor[3] = 9;
        if (valor[2] > 0) {
          valor[2]--;
        } else {
          valor[2] = 5;
          if (valor[1] > 0) {
            valor[1]--;
          } else {
            valor[1] = 9;
            valor[0]--;
          }
        }
      }
      bAlarme = (valor[0] | valor[1] | valor[2] | valor[3]) == 0;
Completando a rotina de interrupção, temos a apresentação do valor. A cada interrupção um dígito é apresentado. Uma tabela é usada para determinar quais segmentos devem ser ativados para cada número.
// Desenho dos digitos
static const uint8_t digito[10] = {
  0x3F, 0x06, 0x5B, 0x4F, 0x66,
  0x6D, 0x7D, 0x07, 0x7F, 0x6F
};

  static uint8_t iDig = 0;

  // Mostra o valor atual
  digitalWrite (pinDigito[iDig], LOW);
  iDig = (iDig+1) & 3;
  uint8_t segtos = digito[valor[iDig]] | mskPonto;
  for (uint8_t iSegto = 0; iSegto < 8; iSegto++) {
    digitalWrite (pinSegto[iSegto], (segtos & 1) ? LOW : HIGH);
    segtos = segtos >> 1;
  }
  digitalWrite (pinDigito[iDig], HIGH);
O programa principal se resume a tratar os botões (que vou chamar de A e B). Com o timer parado, o botão A seleciona a contagem inicial e o botão B inicia (ou retoma) a contagem. Com o timer andando, o botão A interrompe o alarme (se a contagem chegou a zero) e o botão B para a contagem (se ainda não chegou a zero).
// Opções de contagem
static char opcoes[][4] = {
  { 0, 0, 3, 0 },
  { 0, 0, 4, 5 },
  { 0, 1, 0, 0 },
  { 0, 2, 0, 0 },
  { 0, 5, 0, 0 },
  { 1, 0, 0, 0 },
  { 1, 5, 0, 0 },
  { 3, 0, 0, 0 }
};
#define N_OPC (sizeof(opcoes)/sizeof(opcoes[0]))
static uint8_t iOpc = 0;

void loop() {
  if (bParado) {
    if (digitalRead (pinBotaoA) == 0) {
      // Passar para o próximo valor
      iOpc++;
      if (iOpc == N_OPC) {
        iOpc = 0;
      }
      valor[0] = opcoes[iOpc][0];
      valor[1] = opcoes[iOpc][1];
      valor[2] = opcoes[iOpc][2];
      valor[3] = opcoes[iOpc][3];

      // Espera soltar
      while (digitalRead (pinBotaoA) == 0) {
      }
      delay (200);  // debounce
    }
    if (digitalRead (pinBotaoB) == 0) {
      // Espera soltar
      while (digitalRead (pinBotaoA) == 0) {
      }
      delay (200);  // debounce
      // Inicar contagem
      bParado = false;
    }
  } else {
    if (bAlarme) {
      if (digitalRead (pinBotaoA) == 0) {
        // Desligar o alarme
        bAlarme = false;
        bParado = true;
        digitalWrite (pinBuzzer, LOW);
        
        // Voltar ao valor inicial
        valor[0] = opcoes[iOpc][0];
        valor[1] = opcoes[iOpc][1];
        valor[2] = opcoes[iOpc][2];
        valor[3] = opcoes[iOpc][3];
  
        // Espera soltar
        while (digitalRead (pinBotaoA) == 0) {
        }
        delay (200);  // debounce
      }
    } else {
      if (digitalRead (pinBotaoB) == 0) {
        // Parar contagem
        bParado = true;
        // Espera soltar
        while (digitalRead (pinBotaoA) == 0) {
        }
        delay (200);  // debounce
      }
    }
  }
}
O programa completo está nos arquivos do blog (veja no alto à direita), no arquivo TimerBSides.zip.

Nenhum comentário: