terça-feira, junho 25, 2013

Construindo um Intervalômetro - Parte 2

Na parte anterior apresentei o projeto de hardware do intervalômetro que estou construindo. Nesta segunda parte apresento o software.

Como visto na parte 1, optei pelo microcontrolador MSP430G2231. O desenvolvimento foi feito com o IAR Kickstart. Para gravar o software na Flash do MSP430 usei a Launchpad.

O programa principal consiste em parar o watchdog (que é ativado automaticamente após um reset), programar as entradas e saídas digitais (pinos não usados são programados como saída para reduzir o consumo), ativar o clock auxiliar (ACLK) de 32KHz  dividido por 8 (o que inclui selecionar os capacitores apropriados), programar o Timer A (mais sobre isto adiante) e colocar o microcontrolador no modo LPM3
#define POT         BIT0 
#define LED         BIT2
#define DISPARO     BIT3
#define NAO_USADO   (BIT4 | BIT5 | BIT6 | BIT7)
#define POT_ADC     INCH_1;

int main( void )
{
  // Stop watchdog timer to prevent time out reset
  WDTCTL = WDTPW + WDTHOLD;

  // inicia led
  P1DIR = LED | POT | DISPARO | NAO_USADO;
  P1OUT = 0;

  // Ativa o clock de 32KHz
  BCSCTL1 |= DIVA_3;   // ACLK /8
  BCSCTL3 |= XCAP_3;   // 12.5pF
  
  // init timer A
  CCTL0 = CCIE;                             // CCR0 interrupt enabled
  CCR0 = 512;
  TACTL = TASSEL_1 + MC_1;                  // ACLK upmode

  _BIS_SR(LPM3_bits + GIE);                 // Enter LPM3 w/ interrupt
}
Para economizar energia, o microcontrolador ficará a maior parte do tempo "dormindo" no modo LPM3, apenas o clock auxiliar e o timer estarão ativos. É isto que faz a instrução _BIS_SR(LPM3_bits + GIE) no final do programa principal. Ao ocorrer uma interrupção o processador entrará no modo ativo, com todos os clocks ativos. Ao final da interrupção, salvo procedimentos especiais, o microcontrolador restaurará o modo anterior (LPM3).

O timer A tem por entrada um clock de 32768/8 Hz. Programado para o upmode com uma contagem máxima de 512, ele gera uma interrupção a cada 1/8 de segundo. A rotina de interrupção é uma máquina de estados:
#define T_LED     2   // tempo de LED aceso em 1/8 seg
#define T_PAUSA   8   // tempo entre apagar o LED e disparar a foto em 1/8 seg
#define T_DISPARO 2   // tempo que mantem ativo o disparo em 1/8 seg

static volatile unsigned int pot;

// Timer A interrupt service routine
// Ocorre a cada 32768/8/512 = 1/8 segundo
#pragma vector=TIMERA0_VECTOR
__interrupt void Timer( void )
{
  static byte estado = 0;
  static unsigned int cnt = 1;
  static unsigned int tempo = 10;
  static unsigned int pot_ant = 0;
  
  if (estado == 0)
  {
    if (--cnt == 0)
    {
      // Inicio de um novo ciclo
      // solta o disparo
      P1OUT &= ~DISPARO;
      // ler o potenciômetro
      P1OUT |= POT;   // alimenta o potenciômetro
      ADC10CTL0 &= ~ENC;
      ADC10CTL0 = ADC10SHT_3 + ADC10ON + ADC10IE;
      ADC10CTL1 = ADC10SSEL_3 + POT_ADC;
      ADC10CTL0 |= ENC + ADC10SC;      
      estado = 1;
    }
  }
  else if (estado == 1)
  {
    // Leu o potenciômetro, determina o novo tempo
    int dif = (pot > pot_ant) ? (pot - pot_ant) : (pot_ant - pot);
    if (dif > 16)
    {
      // mudou significativamente a posição
      unsigned int aux = pot >> 4;
      if (aux < 10)
        tempo = 10 + 2*aux;         // 10 a 28seg, passo = 2 seg
      else if (aux < 20)
        tempo = 30 + 10*(aux-10);   // 30 a 120 seg, passo = 10 seg
      else if (aux < 35)
        tempo = 150 + 30*(aux-20);  // 150 a 570 seg, passo = 30 seg
      else if (aux < 55)
        tempo = 600 + 60*(aux-35);  // 10 a 29 min, passo = 1 min
      else
        tempo = 1800 + 10*(aux-55); // 30 a 75 min, passo = 5 min
      pot_ant = pot;
    }
    cnt = tempo << 3;           // converte p/ oitavos de segundo
    cnt -= T_LED + T_PAUSA + T_DISPARO;
    estado = 2;
  }
  else if (estado == 2)
  {
    // Aguardando tempo para acender o LED
    if (--cnt == 0)
    {
      // acende o led
      P1OUT |= LED;
      cnt = T_LED;
      estado = 3;
    }
  }
  else if (estado == 3)
  {
    // Aguardando tempo para apagar o LED
    if (--cnt == 0)
    {
      // apaga o LED
      P1OUT &= ~LED;
      cnt = T_PAUSA;
      estado = 4;
    }
  }
  else if (estado == 4)
  {
    // Aguardando tempo para disparar foto
    if (--cnt == 0)
    {
      // dispara a foto
      P1OUT |= DISPARO;
      cnt = T_DISPARO;
      estado = 0;
    }
  }
}
A operação é dividida em cinco estados:
  • 0: aguardando o inicio de um novo ciclo
  • 1: aguardando a leitura da posição do potenciômetro
  • 2: aguardando para acender o LED
  • 3: aguardando para apagar o LED
  • 4: aguardando para disparar a foto

Após alguns testes, decidi que o LED ficaria aceso por 2/8 de segundo, o disparo seria comandado 1 segundo após apagar o LED e que o sinal de disparo ficaria ativo por 2/8 de segundo. Este último parâmetro é importante, já que o acoplador ótico é quem mais consome energia. O valor adotado pressupõe que não será usado o foco automático.

O aguardo da leitura do potenciômetro é feito com o microcontrolador dormindo. A interrupção do ADC  (conversor analógico digital) salva o valor lido e retira a alimentação do potenciômetro.
// Interrupção de fim da conversão
#pragma vector=ADC10_VECTOR
__interrupt void Adc_isr( void )
{
  pot = ADC10MEM;
  P1OUT &= ~POT;
}
A interpretação da leitura do potenciômetro toma alguns cuidados. O ADC é programado para usar o seu clock interno e trabalhar com a escala de 0 a Vcc. O valor lido é entre 0 a 1023 (10 bits), o código ignora pequenas variações na leitura. O valor lido é dividido por 16, ficando entre 0 e  63. Organizei os tempos em faixas contínuas mas com inclinação crescente, para permitir escolher facilmente tempos entre 10 segundos e 75 minutos:
  • 10 a 28 segundos, passo de 2 segundos
  • 30 a 120 segundos, passo de 10 segundos
  •  150 a 570 segundos, passo de 30 segundos
  • 10 a 29 minutos, passo de 1 minuto
  • 30 a 75 minutos, passo de 5 minutos

Nenhum comentário: