sexta-feira, outubro 30, 2009

Abóbora Assassina - Parte 3

Neste post vamos ver o firmware da "Abóbora Assassina". Como o hardware, ele é baseado no meu artigo sobre controle de servomotor e foi desenvolvido em C.
O comportamento gerado pelo firmware é o seguinte:
  • Durante um período "longo" (2 minutos no código abaixo) a abóbora está "dormindo" (LEDs apagados e servomotor na posição de boca fechada).
  • Ao "acordar" os LEDs dos olhos são acesos e o servomotor muda de posição para "abrir a boca da abóbora".
  • Completada a abertura, é aceso o LED dentro da boca. A boca permanece aberta por alguns segundos.
  • O servo motor retorna rapidamente para a posição de boca fechada, ao final do fechamento os LEDs são apagados.
O firmware utiliza uma variável para guardar o "estado da abóbora":
enum { DORMINDO, ABRINDO, ABERTA, FECHANDO } estAbobora;
A interrupção de timer do PIC, que é programada para ocorrer a cada 64 microsegundos, é utilizada para gerar o sinal PWM de controle do servomotor e para decrementar um contador que indica a passagem de um décimo de segundo.
unsigned int16 cntDSeg;
unsigned int16 cnt_periodo;
unsigned int8  cnt_duty, duty;

//////////////////////////////////////////////
// Interrupcao de tempo real
// Ocorre a cada 64 uSeg
//////////////////////////////////////////////
#int_RTCC
void RTCC_isr() 
{
   SET_TIMER0 (192);    // conta de 192 a 255
   
   // Trata Servo
   if ((estAbobora != DORMINDO) && (estAbobora != ABERTA)
   {
      if (--cnt_periodo == 0)
      {
         // fim de um ciclo
         cnt_periodo = TMPUS_PERIODO/TMPUS_OVF;
         cnt_duty = duty;
         output_high (MOTOR);
      }
      else if (cnt_duty != 0)
      {
         cnt_duty--;
         if (cnt_duty == 0)
            output_low (MOTOR);
      }
   }
   
   if (cntDSeg != 0)
      cntDSeg--;
}
O programa principal contém a iniciação do hardware e a lógica de mudança de estado a cada décimo de segundo:
#define MOTOR     PIN_A2
#define LED_OLHO  PIN_A4
#define LED_BOCA  PIN_A5

#define  TMPUS_OVF     64l      // tempo uSeg entre interrupções do timer
#define  TMPUS_PERIODO 20000l   // periodo do PWM em uSeg
#define  TMPUS_DSEG    100000l

#define  TMPDSEG_DORME   1000  
#define  TMPDSEG_ABRINDO    3  
#define  TMPDSEG_FECHANDO   1
#define  TMPDSEG_ABERTA    30

#define  DUTY_0      400      // tempo para 0 graus
#define  DUTY_90     1000     // tempo para 90 graus
#define  DUTY_180    1600     // tempo para 180 graus

#define  DUTY_ABERTA    768
#define  DUTY_FECHADA   576

unsigned int16 cntEstado;

void main()
{
   // configura os periféricos
   #use fast_io(A)
   set_tris_A(0xCB);
   setup_timer_0(RTCC_INTERNAL|RTCC_DIV_1);
   SET_TIMER0 (192);    // conta de 192 a 255
   setup_timer_1(T1_DISABLED);
   setup_comparator(NC_NC);
   setup_vref(FALSE);

   output_high (LED_BOCA);
   output_high (LED_OLHO);
   output_low (MOTOR);

   // inicia as variaveis
   estAbobora = FECHANDO;
   cntDSeg = TMPUS_DSEG/TMPUS_OVF;
   cnt_periodo = TMPUS_PERIODO/TMPUS_OVF;
   cnt_duty = duty = DUTY_FECHADA / TMPUS_OVF;
   cntEstado = 3;
   
   enable_interrupts(INT_RTCC);
   enable_interrupts(GLOBAL);

   for (;;)
   {
      // Trata atualização do estado a cada décimo de segundo
      if (cntDSeg == 0)
      {
         disable_interrupts(GLOBAL);
         cntDSeg = TMPUS_DSEG/TMPUS_OVF;
         if (--cntEstado == 0)
         {
            switch (estAbobora)
            {
               case DORMINDO:
                  estAbobora = ABRINDO;
                  duty = DUTY_FECHADA / TMPUS_OVF;
                  output_high (LED_OLHO);
                  cntEstado = TMPDSEG_ABRINDO;
                  break;
               case ABRINDO:
                  if (duty == (DUTY_ABERTA / TMPUS_OVF))
                  {
                     estAbobora = ABERTA;
                     output_high (LED_BOCA);
                     cntEstado = TMPDSEG_ABERTA;
                  }
                  else
                  {
                     duty++;
                     cntEstado = TMPDSEG_ABRINDO;
                  }
                  break;
               case ABERTA:
                  estAbobora = FECHANDO;
                  cntEstado = TMPDSEG_FECHANDO;
                  break;
               case FECHANDO:
                  if (duty == (DUTY_FECHADA / TMPUS_OVF))
                  {
                     estAbobora = DORMINDO;
                     output_low (LED_BOCA);
                     output_low (LED_OLHO);
                     output_low (MOTOR);
                     cntEstado = TMPDSEG_DORME;
                  }
                  else
                  {
                     duty--;
                     cntEstado = TMPDSEG_FECHANDO;
                  }
                  break;
            }
         }
         enable_interrupts(GLOBAL);
      }
   }
}
No próximo post da série vamos ver a montagem da minha "abóbora" (que acabou sendo uma esfera de isopor).

Nenhum comentário: