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
  1. #define POT         BIT0   
  2. #define LED         BIT2  
  3. #define DISPARO     BIT3  
  4. #define NAO_USADO   (BIT4 | BIT5 | BIT6 | BIT7)  
  5. #define POT_ADC     INCH_1;  
  6.   
  7. int main( void )  
  8. {  
  9.   // Stop watchdog timer to prevent time out reset  
  10.   WDTCTL = WDTPW + WDTHOLD;  
  11.   
  12.   // inicia led  
  13.   P1DIR = LED | POT | DISPARO | NAO_USADO;  
  14.   P1OUT = 0;  
  15.   
  16.   // Ativa o clock de 32KHz  
  17.   BCSCTL1 |= DIVA_3;   // ACLK /8  
  18.   BCSCTL3 |= XCAP_3;   // 12.5pF  
  19.     
  20.   // init timer A  
  21.   CCTL0 = CCIE;                             // CCR0 interrupt enabled  
  22.   CCR0 = 512;  
  23.   TACTL = TASSEL_1 + MC_1;                  // ACLK upmode  
  24.   
  25.   _BIS_SR(LPM3_bits + GIE);                 // Enter LPM3 w/ interrupt  
  26. }  
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:
  1. #define T_LED     2   // tempo de LED aceso em 1/8 seg  
  2. #define T_PAUSA   8   // tempo entre apagar o LED e disparar a foto em 1/8 seg  
  3. #define T_DISPARO 2   // tempo que mantem ativo o disparo em 1/8 seg  
  4.   
  5. static volatile unsigned int pot;  
  6.   
  7. // Timer A interrupt service routine  
  8. // Ocorre a cada 32768/8/512 = 1/8 segundo  
  9. #pragma vector=TIMERA0_VECTOR  
  10. __interrupt void Timer( void )  
  11. {  
  12.   static byte estado = 0;  
  13.   static unsigned int cnt = 1;  
  14.   static unsigned int tempo = 10;  
  15.   static unsigned int pot_ant = 0;  
  16.     
  17.   if (estado == 0)  
  18.   {  
  19.     if (--cnt == 0)  
  20.     {  
  21.       // Inicio de um novo ciclo  
  22.       // solta o disparo  
  23.       P1OUT &= ~DISPARO;  
  24.       // ler o potenciômetro  
  25.       P1OUT |= POT;   // alimenta o potenciômetro  
  26.       ADC10CTL0 &= ~ENC;  
  27.       ADC10CTL0 = ADC10SHT_3 + ADC10ON + ADC10IE;  
  28.       ADC10CTL1 = ADC10SSEL_3 + POT_ADC;  
  29.       ADC10CTL0 |= ENC + ADC10SC;        
  30.       estado = 1;  
  31.     }  
  32.   }  
  33.   else if (estado == 1)  
  34.   {  
  35.     // Leu o potenciômetro, determina o novo tempo  
  36.     int dif = (pot > pot_ant) ? (pot - pot_ant) : (pot_ant - pot);  
  37.     if (dif > 16)  
  38.     {  
  39.       // mudou significativamente a posição  
  40.       unsigned int aux = pot >> 4;  
  41.       if (aux < 10)  
  42.         tempo = 10 + 2*aux;         // 10 a 28seg, passo = 2 seg  
  43.       else if (aux < 20)  
  44.         tempo = 30 + 10*(aux-10);   // 30 a 120 seg, passo = 10 seg  
  45.       else if (aux < 35)  
  46.         tempo = 150 + 30*(aux-20);  // 150 a 570 seg, passo = 30 seg  
  47.       else if (aux < 55)  
  48.         tempo = 600 + 60*(aux-35);  // 10 a 29 min, passo = 1 min  
  49.       else  
  50.         tempo = 1800 + 10*(aux-55); // 30 a 75 min, passo = 5 min  
  51.       pot_ant = pot;  
  52.     }  
  53.     cnt = tempo << 3;           // converte p/ oitavos de segundo  
  54.     cnt -= T_LED + T_PAUSA + T_DISPARO;  
  55.     estado = 2;  
  56.   }  
  57.   else if (estado == 2)  
  58.   {  
  59.     // Aguardando tempo para acender o LED  
  60.     if (--cnt == 0)  
  61.     {  
  62.       // acende o led  
  63.       P1OUT |= LED;  
  64.       cnt = T_LED;  
  65.       estado = 3;  
  66.     }  
  67.   }  
  68.   else if (estado == 3)  
  69.   {  
  70.     // Aguardando tempo para apagar o LED  
  71.     if (--cnt == 0)  
  72.     {  
  73.       // apaga o LED  
  74.       P1OUT &= ~LED;  
  75.       cnt = T_PAUSA;  
  76.       estado = 4;  
  77.     }  
  78.   }  
  79.   else if (estado == 4)  
  80.   {  
  81.     // Aguardando tempo para disparar foto  
  82.     if (--cnt == 0)  
  83.     {  
  84.       // dispara a foto  
  85.       P1OUT |= DISPARO;  
  86.       cnt = T_DISPARO;  
  87.       estado = 0;  
  88.     }  
  89.   }  
  90. }  
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.
  1. // Interrupção de fim da conversão  
  2. #pragma vector=ADC10_VECTOR  
  3. __interrupt void Adc_isr( void )  
  4. {  
  5.   pot = ADC10MEM;  
  6.   P1OUT &= ~POT;  
  7. }  
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: