/*
 * Digital AC Dimmer
 *
 * Copyright (c) 2005-2008 Chris Fallin <chris@cfallin.org>
 * Placed under the modified BSD license
 *
 * dim.c: interrupt-driven AC dimmer module
 */

#include "dim.h"
#include <avr/io.h>
#include <avr/interrupt.h>
#include <avr/signal.h>

// PD2 (INT0) is AC negative detect, PD3 is triac trigger

#define ACDET 4
#define TRIAC 8

#define nop asm volatile("nop")

// triac needs pulse width of 48 nops (clk = 16 MHz) to reliably trigger
// (empirically determined for an NTE 56003 triac)
#define triac_nop \
  nop; nop; nop; nop; nop; nop; nop; nop; nop; nop; nop; nop; \
  nop; nop; nop; nop; nop; nop; nop; nop; nop; nop; nop; nop; \
  nop; nop; nop; nop; nop; nop; nop; nop; nop; nop; nop; nop; \
  nop; nop; nop; nop; nop; nop; nop; nop; nop; nop; nop; nop;

// minimum phase delay, in timer ticks of 500 ns -- we need some buffer
// on either end of the intensity scale, near the zero crossings, to
// ensure that we don't try to trigger during the crossing or on
// the other side of it.
#define MIN_DELAY 1000

unsigned char lvl = 0;
unsigned int lvl_counts[256];

void dim_initialize()
{
  DDRD = (DDRD & 0xf3) | 0x08; // PD3 out, PD2 in
  TCCR1A = 0x00;
  TCCR1B = 0x02; // timer: Clock / 8 == 2 MHz (mains halfcycle ~ 16667 counts)

  // initialize by timing one half-cycle
  unsigned int halfcount;

  while(!(PIND & ACDET)) nop; // wait until we're in a negative half-cycle
  while((PIND & ACDET)) nop; // now wait for the beginning of pos half-cycle
  TCNT1 = 0;
  while(!(PIND & ACDET)) nop; // wait for the negative half-cycle again
  halfcount = TCNT1;

  // now calculate the timing table
  int i;
  int step = halfcount >> 8;
  int value = 0;
  for (i = 255; i >= 0; i--, value += step)
  {
    lvl_counts[i] = value;

    // enforce minimum and maximum phase delay limits
    if (lvl_counts[i] < MIN_DELAY)
      lvl_counts[i] = MIN_DELAY;
    if (lvl_counts[i] > (halfcount - MIN_DELAY))
      lvl_counts[i] = (halfcount - MIN_DELAY);
  }

  // now set up the AC detector interrupt (INT0)
  MCUCR = (MCUCR & 0xfc) | 0x01; // ISC01:ISC00 = 01b: int on rise or fall
  GICR = (GICR & 0x3f) | 0x40; // enable INT0

  // enable the timer compare interrupt
  TIMSK |= 0x10;
}

// AC zero-crossing interrupt
SIGNAL(SIG_INTERRUPT0)
{
  OCR1A = lvl_counts[lvl]; // start timer for precalculated phase delay
  TCNT1 = 0;
}

// timer compare trigger interrupt
SIGNAL(SIG_OUTPUT_COMPARE1A)
{
  // trigger the triac
  if (lvl != 0)
  {
    PORTD |= TRIAC;
    triac_nop;
    PORTD &= ~TRIAC;
  }
}

void dim_set_level(char l)
{
  lvl = l;
}
