/* MUSIC VISUALISER Slave Board Microcontroller Code
   for PIC24EP with min. 4KBytes of RAM (note 2KB used for inputstates buffer).
   By: Kevin Koster / OmberTech, 2019
   Language: ANSI C with XC16 extensions
   Compiler: Microchip XC16
   Version: 1.0
*/

#include <xc.h>

/* Increasing this decreases the delay time
   according to the delay input value */
#define ANALOGUESCALE	2.6

// Configuration Register Initialisation
#pragma config FNOSC 	= FRC // Use Internal Fast RC oscillator
#pragma config IESO		= OFF //Start up with user-selected oscillator source
#pragma config FWDTEN	= OFF //Watchdog timer enabled/disabled by user software
#pragma config GCP		= OFF //General Segment Code protect is Disabled
#pragma config JTAGEN	= OFF //JTAG is disabled

void initadc(void)
{
 /* Initialize and enable ADC module
    -From PIC 16bit ref. man. auto-manual ADC example */
 AD1CON1 = 0x0004;
 AD1CON2 = 0x0000;
 AD1CON3 = 0x000F;
 AD1CON4 = 0x0000;
 AD1CHS0 = 0x0005;
 AD1CHS123 = 0x0000;
 AD1CSSH = 0x0000;
 AD1CSSL = 0x0000;
 //Delay_us(20); - Won't matter too much if the first cycle is wrong.
 /* Make sure of these settings: */
 _VCFG	  = 0; //Reference voltages are supply voltages
 _ADRC    = 1; //use ADC internal RC oscillator
 _FORM    = 0; //Unsigned integer format output data - 10bit counts 0-1023, 12bit counts 0-4095
 AD1CON1bits.ADON = 1;
}

unsigned int delayvoltage(void) // Read delay voltage from AN5 and convert to value between 0 and 31,713 (2sec max. delay)
{
  int inputvoltage;

  AD1CON1bits.SAMP = 0; // Start the ADC conversion
  while (!AD1CON1bits.DONE); // Wait for the conversion to complete
  AD1CON1bits.DONE = 0;      // Clear conversion done status bit
  inputvoltage = 2046 - (ADC1BUF0 * ANALOGUESCALE); // for 10bit ADC max. count = 1023 - change if using 12bit ADC
  if (inputvoltage > 0)
   return inputvoltage;
  else
   return 0;
}

/* Could use Interrupts+sleep-mode, but power
   saving isn't really critical here...     */
void sleepon(void)
{
 int startstate = PORTB & 0B10000000;
 char i;
 while ((PORTB & 0B10000000) == startstate); //Wait for first state change
 for (i=100; i>0; i--);
 while ((PORTB & 0B10000000) != startstate); //Wait for second state change (clock cycle complete)
}

int main (void)
{
 /* Initialise Data Ports */
 ANSELA = 0x0000; // All analogue functions enabled on reset, disable them
 ANSELB = 0B1000; // Set AN5/RB3 to analogue, rest to digital
 TRISA   = 0x0000; // Port A Output
 TRISB   = 0xFFFF; // Port B Input
 CNPDB = 0x0000; //Port B pull-downs disabled (probably default, but haven't seen that stated anywhere)
 CNPUB = 0B1111111101110111; //RB0-RB2 Internal Pull-ups Enabled except for RB3 (AN5) and RB7 (INT0)
 initadc();

 unsigned char inputstates[2047]; //Where all the LED states are stored.
 unsigned char *stateptr;
 unsigned int maxmem;
 unsigned char datain;
 unsigned char dataout;
 char bank = 0;

 /* Pulse all LEDs in sequence */
 int loop; //Used later as well
 LATA = 0B00000100; //Low on
 for (loop=300; loop > 0; loop--)
  sleepon();
 LATA = 0B00000001; //Mid on
 for (loop=300; loop > 0; loop--)
  sleepon();
 LATA = 0B00000010; //High On
 for (loop=300; loop > 0; loop--)
  sleepon();
 LATA = 0B00000000; //None on

 stateptr = inputstates;
 maxmem = delayvoltage(); //Sets the time delay by limiting the length of data storage

 while (1) //Inescaped loop
 {
  sleepon (); //Wait until Clock
  datain = PORTB & 0B00000111; //Sample input data from RB0-RB2

  if (maxmem > 0) //Delay is set to greater than zero
  {
   //Second Bank
   if (bank)
   {
    bank = 0;
    datain = datain << 4; //Shift data to upper bank of byte
    *stateptr = datain | *stateptr; //OR data with location in RAM
    stateptr++;
    if ((stateptr - inputstates) > maxmem) // Reset pointer if end of range reached
    {
     stateptr = inputstates;
     maxmem = delayvoltage();
    }
    LATA = (~dataout >> 4) & 0B00000111; //Write second bank to outputs
   }
   //First bank
   else
   {
    bank = 1;
    dataout = *stateptr; // Store old data aside
    *stateptr = datain; //Replace both banks of old data at RAM location.
    LATA = ~dataout & 0B00000111; //Write first bank to outputs
   }
  }
  else //Delay is set to zero
  {
   loop++;
   LATA = ~datain; //Write input data straight to outputs
   if (loop > 20000)
   {
    maxmem = delayvoltage();
    loop = 0;
   }
  }
 }
}
