/* 
   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 one
  TI5940 pwm led drivers.  For use on a PIC16887, on the USB LED
  Controller board in combination with 5 LED Driver boards with 350mA
  Lamina RGB lights.

  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 : Raw grayscale data

    0x03 : Raw dot correction data (not functional)

    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:
    RC6     TxE
    RC7     RxF
    RB1     RD
    RB0     WR
    RD0..7  D0..7

   TI communication
    RA6 (clkout)  TI gsclk
    RE0           TI xlat        (should T1OSO be used on blank?)
    RE1           TI blank
    RC3 (sclk)    TI sclk
    RC4 (sin)     TI#2 sout
    RC5 (sout)    TI#0 sin
    -             TI dcprg
    -             TI vprg  (gnd for grayscale mode)

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

   UART  -- NOT CURRENTLY USED
    RC6 (tx)
    RC7 (rx)

  LED TI5940 ports
  ------------------------------------
  led0_rgb   TI0 OUT0   TI0 OUT1   TI0 OUT2
  led1_rgb   TI0 OUT3   TI0 OUT4   TI0 OUT5
  led2_rgb   TI0 OUT6   TI0 OUT7   TI0 OUT8
  led3_rgb   TI0 OUT9   TI0 OUT10  TI0 OUT11
  led4_rgb   TI0 OUT12  TI0 OUT13  TI0 OUT14

sdcc --debug -mpic14 -p16f887 main.c

program, verify, release mclr
pk2cmd -P -M -Y -R -F main.hex
 */

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

#define NPORTS 5

unsigned char raw_frame[24];
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 = 0x78;      /* 8mhz */
  /*OSCCON = 0b00001000; */     /* 31.25khz */
  /*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 = 0xC8;  /* out: 0,1 in: 3,6,7(iscp)  0b11001000 */

  TRISC = 0xD0;  /* in: 4,6,7  out: 3,5 0b11010000 */

  TRISD = 0xFF;  /* USB data input/output */

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

  do_color_update = 0;
  do_dot_update = 0;
}

unsigned char usb_read_byte( void) {
  unsigned char cmd;

  /*TRISD=0xFF;*/  /* usb data input */

  /* wait for RxF to be low */
  while (FT_RXF) {};

  FT_RD = 0;
  cmd = FT_BYTE;
  FT_RD = 1;

  return cmd;
}

void usb_write_byte( unsigned char c) {

  /* wait for TXE to be low before writing */
  while (FT_TXE) {};
  
  /* Make PORTD output */
  TRISD = 0x00;

  FT_WR = 1;
  FT_BYTE = c;
  FT_WR = 0;

  /* Restore PORTD to input */
  TRISD = 0xFF;
}

/* 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 < 24; 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(0xF0):
    /* 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(0xF1):
    /* 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(0xF2):
    /* echo raw grayscale data */
    for (i=0; i < 24; i++)
      usb_write_byte( raw_frame[i]);
    break;

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

  case(0xF6):
    /* dumb echo */
    i = usb_read_byte();
    usb_write_byte(i);
    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 < 24; 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] = 0x77;
  }
  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();*/

  }
}
