#include <avr/io.h>
#include <avr/interrupt.h>
#include <avr/signal.h>
#include <inttypes.h>
#include <stdlib.h>
#include <avr/pgmspace.h>

/**************************************\
 * Laser projector.
 * 32x16 display. 16x16 active pixels.
 * Use 8MHz clock.
 * 
 * Ports:
 * B0: out: Laser
 * B1: out: Motor
 * B3:  in: Fork reader (active low) (B3=INT2)
 * D7: out: Servo control (D7=OC2)
 \*************************************/

//#include "video/x.h"
//#include "test_pattern.h"
#include "video_alf.h"

/* Calibration */
const char offsetx[16] = {4,10,13,13, 12,12,14,11, 8,8,9,9, 13,9,9,3};
const uint8_t suboffsetx[16] = {8,9,8,15, 6,3,5,2, 1,9,7,5, 13,6,8,8};

/* Variables */
const uint8_t frame_div = 4; /* Number of times to show each frame */
volatile uint8_t frame_div_cnt; /* Counter for frame slowdown */
volatile uint16_t pixel_pos; /* Rotor position in pixels (0 to 511) */
volatile uint16_t rot_ticks; /* Number of counter1 ticks per rotation */
volatile uint16_t frame; /* Current video frame */
volatile uint16_t frame_inc; /* Frames forward per rotation */
volatile uint8_t the_end; /* Has the movie reached the end? */
volatile uint8_t pixelclock; /* Pixel clock (counter0 ticks per pixel) */
volatile uint8_t pixelclock_frac; /* 64*(pixelclock fraction) */
volatile uint8_t pixelclock_frac_running; /* Incremented by pixelclock_frac once per pixel */

void ioinit(void)
{
  /* Set port direction (0=in, 1=out) */
  DDRA = 0x00;
  DDRB = 0x03;
  DDRC = 0x00;
  DDRD = 0x80;
  /* Set internal pull-up (0=off, 1=on) */
  PORTA = 0xff;
  PORTB = 0xf8;
  PORTC = 0xff;
  PORTD = 0x7f;

  /* 8-bit counter (counter0) - Pixel clock */
#define TCCR0_DIV_OFF  (                         0)
#define TCCR0_DIV_1    (                  BV(CS00))
#define TCCR0_DIV_8    (         BV(CS01)         )
#define TCCR0_DIV_64   (         BV(CS01)|BV(CS00))
#define TCCR0_DIV_256  (BV(CS02)                  )
#define TCCR0_DIV_1024 (BV(CS02)         |BV(CS00))
  outp(BV(WGM01)|TCCR0_DIV_8, TCCR0); /* Reset counter on compare, set clock div */

  /* 8-bit counter (counter2) - PWM generator */
  outp(BV(WGM21)|BV(WGM20)|BV(COM21)|7, TCCR2);
  OCR2=10; /* Range: 3-18 */

  /* 16-bit counter (counter1) - Used to measure rotation time */
#define TCCR1B_DIV_OFF  (                         0)
#define TCCR1B_DIV_1    (                  BV(CS10))
#define TCCR1B_DIV_8    (         BV(CS11)         )
#define TCCR1B_DIV_64   (         BV(CS11)|BV(CS10))
#define TCCR1B_DIV_256  (BV(CS12)                  )
#define TCCR1B_DIV_1024 (BV(CS12)         |BV(CS10))
  outp(0, TCCR1A);
  outp(0, TCCR1B);

  /* External interrupt INT2 - "Reading fork", triggers once per revolution */
  cbi(MCUCSR, ISC2); /* Trigger on falling edge */
  sbi(GICR, INT2); /* Enable */

  /* Enable interrupts */
  sei();
}

/* 8-bit counter (counter0) - Pixel clock */
SIGNAL(SIG_OUTPUT_COMPARE0)
{
  int8_t x,y,i;

  pixel_pos++;
  x = pixel_pos & 0x1f;
  y = pixel_pos >> 5;

  /* Sub-x-offset */
  i = 0;
  if (x == 0)
    i = (pixelclock * (uint16_t)suboffsetx[y]) >> 4;
  if (x == 30)
  {
    pixel_pos++;
    i = pixelclock - ((pixelclock * (uint16_t)suboffsetx[y]) >> 4);
  }

  /* Pixel clock fractions / Sub-x-offset */
  pixelclock_frac_running += pixelclock_frac;
  if (pixelclock_frac_running >= 0x40)
  {
    pixelclock_frac_running -= 0x40;
    OCR0 = pixelclock + i + 1;
  }
  else
    OCR0 = pixelclock + i;

  /* Output pixel */
  x = x - offsetx[(int)y];
  if ((x >= 0) && (x < 16))
  {
    if (pgm_read_byte_near(video + (frame<<5) + (y<<1) + (x>>3)) & BV(7-(x & 0x07)))
      sbi(PORTB,0);
    else
      cbi(PORTB,0);
  }
  else
    cbi(PORTB,0);

  /* Turn off counter0 (this one), so it won't disturb INT2 interrupt */
  if (pixel_pos > 498)
  {
    timer_enable_int(0);
    cbi(PORTB,0);
  }
}

/* External interrupt INT2 */
SIGNAL(SIG_INTERRUPT2)
{
  const uint16_t hysteresis = 4;
  static uint16_t i;

  pixel_pos = 0;

  /* Read rotation time from 16-bit counter */
  outp(TCCR1B_DIV_OFF, TCCR1B); /* Stop counting */

  if (TCNT1 > rot_ticks + hysteresis) rot_ticks = TCNT1 - hysteresis;
  if (TCNT1 < rot_ticks - hysteresis) rot_ticks = TCNT1 + hysteresis;
  i = rot_ticks >> 6; /* rot_ticks * 64 / 512 / 8 */
  if (i > 126) i = 126;
  pixelclock = i; /* Typical: 61 */

  OCR0 = pixelclock;
  pixelclock_frac = rot_ticks & 0x3f;
  pixelclock_frac_running = 0;

  TCNT1 = 0;
  outp(TCCR1B_DIV_64, TCCR1B); /* Start counting again */

  /* Re-enable 8-bit counter */
  timer_enable_int(BV(OCIE0)); /* Output Compare Interrupt Enable */
  TCNT0 = 0; /* Reset counter */
  TIFR = 0xff; /* Clear pending counter interrupts. FIXME: Clears all interrupts! */

  /* Video */
  if (++frame_div_cnt >= frame_div)
  {
    frame_div_cnt = 0;
    frame += frame_inc;
    if (frame >= video_frames)
    {
      frame = video_frames-1;
      the_end = 1;
    }
  }
}

int main(void)
{
  uint8_t i;
  uint32_t j;

  frame = 0;
  frame_inc = 0;
  ioinit();

  /* Startup */
  OCR2 = 3; /* Raise screen */
  sbi(PORTB, 1); /* Turn on motor */

  /* Play video */
  for (j=0; j < 32000000; j++);
  for (i=0; i<2; i++)
  {
    frame = 0;
    frame_div_cnt = 0;
    the_end = 0;
    for (j=0; j < 32000000; j++);
    frame_inc = 1;
    while (!the_end);
    frame_inc = 0;
    for (j=0; j < 32000000; j++);
  }

  /* Shutdown */
  cli(); /* Disable interrupts */
  cbi(PORTB, 0); /* Turn off laser */
  cbi(PORTB, 1); /* Turn off motor */
  for (j=0; j < 128000000; j++);
  OCR2 = 10; /* Lower screen */

  for(;;);

  return 0;
}