/* 
   USB led driver firmware
   Copyright 2008 Jacob Joseph <jacob@jjoseph.org>

   This program is free software: you can redistribute it and/or modify
   it under the terms of the GNU General Public License as published by
   the Free Software Foundation, either version 3 of the License, or
   (at your option) any later version.

   This program is distributed in the hope that it will be useful,
   but WITHOUT ANY WARRANTY; without even the implied warranty of
   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
   GNU General Public License for more details.

   You should have received a copy of the GNU General Public License
   along with this program.  If not, see <http://www.gnu.org/licenses/>.
*/

/*
  Communications code between an FTDI 245R USB fifo chip, and three
  TI5940 pwm led drivers.  For use on a PIC16887, on the USB LED
  driver board for driving five 350mA Lamina RGB lights.  Note that
  each TI5940 port can attain a maximum of 120mA, so three are grouped
  for each color channel.  This is easier than proper constant-current
  amplification.

  Functional Overview:
  -----------------------------------

   A tight loop in main monitors the 245R RD line, indicating new
   input data.  The first byte defined a command, followed by a
   variable-length set of parameters, which are clocked in
   immediately.  Global state bits are then (atomically) set if, for
   example, a color update should be performed.

   The TI5940 clocking is performed first by the clkout pin, at OSO/4.
   Blanking, and all communications with the TI5940 chips must be
   coordinated such that commands complete before a blank cycle.
   These are handled by an interrupt driven by Timer 1.  Note that
   commands are processed only at the next interrupt cycle,
   introducing some latency.

   To Do:
    Ignore incomplete commands.
    CRC on received data? Just echo commands or checksum back to pc?
    Could an interrupt be used for watching RD?)
    Use UART for debug output, in ASCII

  USB command set:
  -----------------------------------

    0x00 : grayscale data. 5ports x 3colors x 2bytes(12 bit
           resolution) to follow, in order of
           port,{r,g,b},{high,low}(12bit x 5port x 3color) to follow

    0x01 : dot correction data. 5ports x 3colors x 1byte(6 bit
           resolution) to follow.

    0x02 : blank immediately, disable blank interrupt

    0x03 : resume blank interrupt, unblanking at next timer event

    0x80 : reset command input. Next byte should be a command
    0x81 : disable GIE
    0x82 : enable GIE

    0xF0 : debug: echo received bytes, perform nothing (0x80 breaks)
    0xF1 : debug: set color change bit, to refresh grayscale
    0xF2 : debug: set dot change bit, to refresh dot correction
    0xF3 : debug: echo color table
    0xF4 : debug: echo dot correction table
    0xF5 : debug: echo state bits

  Port Assignments:
  ------------------------------------
   16F887   FTDI245RL:
    RD0     TxE
    RD1     RxF
    RD2     RD
    RD3     WR
    RB0..7  D0..7

   TI communication
    RA6 (clkout)  TI gsclk
    RC0 (T1OSO)   TI xlat        (should T1OSO be used on blank?)
    RC1           TI blank
    RC3 (sclk)    TI sclk
    RC4 (sin)     TI#2 sout
    RC5 (sout)    TI#0 sin
    RE1           TI dcprg (keep high)
    RE2           TI vprg

   ICSP
    RB3 (PGM)
    RB6 (ICSPCLK)
    RB7 (ICSPDAT)
    RE3 (MCLR/Vpp)

   UART
    RC6 (tx)
    RC7 (rx)

  LED TI5940 ports
  ------------------------------------
  led0_r   TI0 OUT0   TI0 OUT1   TI0 OUT2
  led0_g   TI0 OUT3   TI0 OUT4   TI0 OUT5
  led0_b   TI0 OUT6   TI0 OUT7   TI0 OUT8
  led1_r   TI0 OUT9   TI0 OUT10  TI0 OUT11
  led1_g   TI0 OUT12  TI0 OUT13  TI0 OUT14
  led1_b   TI0 OUT15  TI1 OUT0   TI1 OUT1
  led2_r   TI1 OUT2   TI1 OUT3   TI1 OUT4
  led2_g   TI1 OUT5   TI1 OUT6   TI1 OUT7
  led2_b   TI1 OUT8   TI1 OUT9   TI1 OUT10
  led3_r   TI1 OUT11  TI1 OUT12  TI1 OUT13
  led3_g   TI1 OUT14  TI1 OUT15  TI2 OUT0
  led3_b   TI2 OUT1   TI2 OUT1   TI2 OUT2
  led4_r   TI2 OUT3   TI2 OUT4   TI2 OUT5
  led4_g   TI2 OUT6   TI2 OUT7   TI2 OUT8
  led4_b   TI2 OUT9   TI2 OUT10  TI2 OUT11

sdcc --debug -mpic14 -p16f887 main.c
 */

#define __16f887
/*#define NO_BIT_DEFINES*/
#include "pic/pic16f887.h"
#include "main.h"

#define NPORTS 5

unsigned char raw_frame[72];
unsigned char raw_dot[12];
struct led colors[NPORTS];
unsigned char do_color_update;
unsigned char do_dot_update;

void Intr(void) __interrupt 0 {

  /* Timer 0 expired.  Pulse blank */
  if (TMR0IF) {
    TI_BLANK = 1;
	TMR0 = 0;
    TI_BLANK = 0;
    TMR0IF = 0;
  }
}

void init_pic() {
  INTCON = 0;

  TMR0 = 0;
  T0IE = 1;     /* enable Timer 0 overflow interrupt */

  CCP1CON = 0;  /* disable CCP */
  CCP2CON = 0;

  /*SSPCON = 0b00100000;*/   /* enable hardware SPI */
  /*SSPCON = 0x20;*/   /* enable hardware SPI */
  SSPCON = 0;

  PIE1 = 0;     /* disable peripheral interrupts */
  PIE2 = 0;

  CM1CON0 = 0;  /* disable comparitors */
  CM2CON0 = 0;

  /*OSCCON = 0b01111000;*/    /* 8mhz */
  /*OSCCON = 0b00001000; */     /* 31.25khz */
  OSCCON = 0x78;      /* 8mhz */
  /*OSCCON = 0x08;*/      /* 31.25khz */

  /* Timer 0 configuration for TI_BLANK
     1:16 prescaler (1 click every 16 cycles, overflow every 4096) */
  /*OPTION_REG = 0b00000011; */
  OPTION_REG = 0x03;

  /* Port configuration */
  PORTA = 0;
  PORTB = 0;
  PORTC = 0;
  PORTD = 0;
  PORTE = 0;

  /* Start with USB_RD, TI_BLANK high */
  FT_RD = 1;
  FT_WR = 1;
  TI_DCPRG = 1;
  TI_VPRG = 1;
  TI_BLANK = 1;
  TI_XLAT = 0;
  TI_SCLK = 0;

  ANSEL = 0;     /* digital I/O */
  TRISA = 0x00;  /* unused, save RA6 */

  ANSELH = 0;    /* digital I/O */
  /*WPUB          weak pull-up  */
  TRISB = 0xFF;  /* USB data input */

  TRISC = 0x10;  /* in: 4  out: 0,1,3,5 */

  TRISD = 0xC3;  /* in: 0,1,6,7  out: 2,3 */

  /* note RE3 (mclr) is input only */
  TRISE = 0x00;  /* out: 1,2 */

  do_color_update = 0;
  do_dot_update = 0;
  /*RD4 = 1; */
}

unsigned char usb_read_byte( void) {
  unsigned char cmd;

  RD4 = 1;
  /* wait for RxF to be low */
  while (FT_RXF) {};
  RD4 = 0;

  FT_RD = 0;
  cmd = FT_BYTE;
  /* hack to use RD6,7 instead of RB6,7 (which are ICD) */
  /* ICD hack
  if (RD6)
    cmd |= 0x40;
  else
    cmd &= ~0x40;
  if (RD7)
    cmd |= 0x80;
  else
    cmd &= ~0x80; 
  */

  FT_RD = 1;

  return cmd;
}

void usb_write_byte( unsigned char c) {
  TRISB = 0x00;
  TRISD &= 0x3F;

  FT_BYTE = c;

  /* ICD hack
  if (c & 0x40)
    RD6 = 1;
  else
    RD6 = 0;
  if (c & 0x80)
    RD7 = 1;
  else
    RD7 = 0;
  */

  while (1) {
    /* Wait for txe to be low before writing */
    if (!FT_TXE)
      break;
  }
  
  FT_WR = 0;
  FT_WR = 1;
  TRISB = 0xFF;
  TRISD |= 0xC0; 
}

/* call when ready to read the FTDI chip */
void usb_read( void) {
  unsigned char cmd;
  unsigned char i;

  cmd = usb_read_byte();

  switch(cmd) {
  case(0x00):
    /* grayscale data: 30 bytes, ordered port,{r,g,b},{h,l} */
    /*for (i=0; i < NPORTS; i++) {
      colors[i].r = usb_read_byte() << 8;
      colors[i].r |= usb_read_byte();
      colors[i].g = usb_read_byte() << 8;
      colors[i].g |= usb_read_byte();
      colors[i].b = usb_read_byte() << 8;
      colors[i].b |= usb_read_byte(); 
    }*/
    do_color_update = 1;
    break;
  
  case(0x01):
    /* dot correction data 15 bytes, ordered port,{r,g,b} */
    /*for (i=0; i < NPORTS; i++) {
      colors[i].rdot = usb_read_byte();
      colors[i].gdot = usb_read_byte();
      colors[i].bdot = usb_read_byte();
    }*/
    do_dot_update = 1;
    break;

  case(0x02):
    /* raw grayscale data, chip 0. 16x12bits = 24 bytes ordered msb
       first */
    for (i=0; i < 72; i++) {
      raw_frame[i] = usb_read_byte();
    }
    do_color_update = 1;
    break;

  case(0x03):
    /* raw dot correction data, chip 0. 16x6bits = 12 bytes ordered msb
       first */
    for (i=0; i < 12; i++) {
      raw_dot[i] = usb_read_byte();
    }
    do_dot_update = 1;
    break;

  case(0x80):
    /* reset command input. */
    xD5;
    break;

  case(0x81):
    /* disable GIE */
    GIE = 0;
    break;

  case(0x82):
    /* enable GIE */
    GIE = 1;
    break;

  case(0xF3):
    /* echo color table */
    for (i=0; i < NPORTS; i++) {
      usb_write_byte( (unsigned char) (colors[i].r >> 8));
      usb_write_byte( (unsigned char) colors[i].r & 0xFF);
      usb_write_byte( (unsigned char) (colors[i].g >> 8));
      usb_write_byte( (unsigned char) colors[i].g & 0xFF);
      usb_write_byte( (unsigned char) (colors[i].b >> 8));
      usb_write_byte( (unsigned char) colors[i].b & 0xFF);
    }  
    break;

  case(0xF4):
    /* echo dot table */
    for (i=0; i < NPORTS; i++) {
      usb_write_byte( colors[i].rdot);
      usb_write_byte( colors[i].gdot);
      usb_write_byte( colors[i].bdot);
    }  
    break;

  case(0xF5):
    /* echo state bits */
    usb_write_byte( do_color_update);
    usb_write_byte( do_dot_update);
    break;
  }
}

void spi_send_byte( unsigned char b) {
	unsigned char i;
	
	for( i=0; i < 8; i++) {
		if( b & 0x80)
			TI_SIN = 1;
		else
			TI_SIN = 0;

		TI_SCLK = 1;
		b <<= 1;
		TI_SCLK = 0;
	}

/*	bit_count = 8;
_asm
	Transmit_bit$:
		btfsc	_b, 7
		goto 	Transmit_hi
		bcf		_PORTC_bits, 5
		goto	Transmit_clkout
	Transmit_hi
		bsf		_PORTC_bits, 5
	Transmit_clkout
		bsf		_PORTC_bits, 3
		rlf		_b, F
		bcf		_PORTC_bits, 3
		
		decfsz	_bit_count, F
		goto	Transmit_bit
_endasm; */

}

void ti_send_frame(void) {
  unsigned char i;
  unsigned char first_cycle;

  /* wait for timer0 to reset, allowing enough time to complete
     transmission before another reset */
  while (TMR0 > 10) {};
  
  if (TI_VPRG) {
	TI_VPRG = 0;
    first_cycle = 1;
  }
  else
	first_cycle = 0;

  for( i=0; i < 72; i++) {
    /*SSPBUF = raw_frame[i];*/

    /* wait for byte to be clocked out */
    /*while (BF) {};*/

	spi_send_byte( raw_frame[i]);
  }

  /* pulse xlat */
  TI_XLAT = 1;
  TI_XLAT = 0;

  if (first_cycle) {
    TI_SCLK = 1;
    TI_SCLK = 0;
    first_cycle = 0;
  }
  do_color_update = 0;
}

void ti_send_dot(void) {
  unsigned char i;
  while (TMR0 > 10) {};

  TI_VPRG = 1;
  TI_XLAT = 0;

  for( i=0; i < 12; i++) {
    /*SSPBUF = raw_dot[i]; */

    /* wait for byte to be clocked out */
    /*while (BF) {}; */
	spi_send_byte( raw_dot[i]);
  }

  TI_XLAT = 1;
  TI_XLAT = 0;
  do_dot_update = 0;
}

void main(void) {
  unsigned char i;
  init_pic();

  GIE = 1;  /* start interrupts */
	
	for( i=0; i < 12; i++) {
		raw_dot[i] = 0xFF;
	}
	ti_send_dot();
	
	for (i = 0; i < 24; i++) {
		raw_frame[i] = 0xFF;
    }
	/*for (i = 23; i > 10; i--) {
		raw_frame[i] = 0x00;
	}*/
	ti_send_frame();

  while (1) {
    /* When low, data available */
    if (!FT_RXF) {
      usb_read();
    }

    if (do_color_update)
      ti_send_frame();

    if (do_dot_update)
      ti_send_dot();

  }
}
