/* Truth Wristband!!!
/
/ A wearable device that dynamically reflects the your 
/ psycho-emotional response to the world, promoting internal 
/ states to be externalized and made into interactive forms 
/ of expression. Measuring the galvanic skin response 
/ (a marker of emotional arousal commonly used in lie detector 
/ tests), this device’s lights turn from blue to red as the 
/ wearer becomes aroused. Ask the wearer an evocative question 
/ and reveal his or her inner Truth.
/
/ Skin resistance changes -> B->R waves over 5 LEDs
/ Fs = 50 Hz
/ 5 sec calibration delay on startup
/ Smoothed to 3.33Hz
/ Average skin resistance calculated using 1 second decay constant
/ VDD = 2.4-3V
/
/ Written by Sean M. Montgomery, 2009/02
/ http://www.produceconsumerobot.com/truth/
/
/ Truth Wristband by Sean M. Montgomery is licensed under a 
/ Creative Commons Attribution-Noncommercial-Share Alike 3.0 
/ United States License
/ 
*/

#include <p18f25k20.h>
#include <adc.h>
#include <timers.h>
#include <math.h>
#include "SetOutputs.h"
void high_isr(void);
void init(void);
void GetData(void);

// counter values to convert A/D sampling to 20ms
unsigned char t1_20msConversion = 5; 
unsigned char t1_20msCounter = 0;

static unsigned char onBit = 1; // set ON output value

// counter to set startup calibration time
static int startupCounter = 0;
static int maxStartupCounter = 250; //50Hz * 5sec

static float dataBuffer = 0.0; // current skin resistance value
static float meanData = 0.0; // average skin resistance value

// period over which data is smoothed
static float smoothPeriod = 1.0;
static const float maxSmoothPeriod = 15.0; //50Hz/15=3.33Hz 

// period over which average skin resistance is calculated 
static float normPeriod = 1.0;
static const float maxNormPeriod = 50.0; //50Hz * 1sec

// initializes output arrays to use the SetOutputs function
#define NUMLEDS 5
#define NUMLEDPINS 10
volatile near unsigned char * outPorts[NUMLEDPINS] = {
					&LATC,
					&LATC,

					&LATC, // for RGB LEDs
//					&LATA, // for RBG LEDs
					&LATA,

					&LATC,
					&LATC,

					&LATB,
					&LATB,

					&LATB,
					&LATA,
					};
unsigned char  outBits[NUMLEDPINS] = {
					0b00001000, // for RGB LEDs
//					0b00000100, // for RBG LEDs
					0b00000010,

					0b00000001, // for RGB LEDs
//					0b01000000, // for RBG LEDs
					0b10000000,

					0b00100000, // for RGB LEDs
//					0b01000000, // for RBG LEDs
					0b10000000,

					0b00000001, // for RGB LEDs
//					0b00000010, // for RBG LEDs
					0b00000100,

					0b00001000, // for RGB LEDs
//					0b00010000, // for RBG LEDs
					0b00000100
					};
unsigned char outVals[NUMLEDPINS] = {
					0,
					0,

					0,
					0,

					0,
					0,

					0,
					0,

					0,
					0
					};

// RGB pointers for keeping track of LED outputs
typedef struct {
	unsigned char * r;
	unsigned char * g;
	unsigned char * b;
} RGBpointers;
RGBpointers outLEDs[NUMLEDS];
	

void main(void) {
	unsigned short j;
	float diffData;
	float threshold;
	float threshFactor = 0.24; 
	float threshOffset = 0.01;
	unsigned char hue = 0;

	init();
	
	while(1) {
		// turn all lights red for startup calibration
		if (startupCounter < maxStartupCounter) {
			for (j=0; j<NUMLEDS; j++) {
				*outLEDs[j].r = onBit;
				*outLEDs[j].b = !onBit;		
			}
		} else {	
			// subtract baseline average
			diffData = meanData - dataBuffer;
			
			// determine if skin resistance deviation crosses the 
			// threshold for each LED
			for (j=0; j<NUMLEDS; j++) {
				// uses cubic function for thresholds across 5 LEDs
				threshold = (float) (j*j*j); 
				threshold = ((threshold*threshFactor)+threshOffset) ;
				if (diffData > threshold) {
					*outLEDs[j].r = onBit;
					*outLEDs[j].b = !onBit;		
				} else {
					*outLEDs[j].r = !onBit;
					*outLEDs[j].b = onBit;		
				}
			}
		}
	}
}

//interrupt code:
#pragma code high_interrupt=0x08
void high_interrupt(void) {
	_asm goto high_isr _endasm
}
#pragma code
//handle interrupts
#pragma interrupt high_isr
void high_isr(void) {
	int j;
	if ( INTCONbits.TMR0IF ) { // timer 0
		INTCONbits.TMR0IF = 0;
		WriteTimer0(131); 

		// converts timer triggers to 20ms
		t1_20msCounter++;
		if (t1_20msCounter == t1_20msConversion) {
			t1_20msCounter = 0;
			
			// Set the LEDs
			SetOutputs(NUMLEDPINS,outVals,outPorts,outBits);

			GetData(); // reads data from A/D
			if (startupCounter < maxStartupCounter) {
				startupCounter++;
			}
		}
	}
}

// Load data into dataBuffer
void GetData(void) {
	unsigned int tempData;
	
	while( BusyADC() ); // Wait for completion
	tempData = ReadADC(); // Read result
	ConvertADC(); // Start conversion
	
	// smooth the data
	dataBuffer = dataBuffer*(smoothPeriod-1.0);
	dataBuffer = dataBuffer + ((float) tempData);
	dataBuffer = dataBuffer/smoothPeriod;
	if (smoothPeriod < maxSmoothPeriod) {
		smoothPeriod = smoothPeriod + 1.0;
	} else {
		// average the data to calculate baseline skin resistance
		meanData = meanData*(normPeriod-1.0);
		meanData = meanData + dataBuffer;
		meanData = meanData/normPeriod;
		if (normPeriod < maxNormPeriod) { 
			normPeriod = normPeriod + 1.0;
		}
	} 
}

	


void init(void) {
	unsigned char j;

	// map output vector onto red and blue arrays
	for (j=0;j<NUMLEDS;j++) {
		outLEDs[j].r = &outVals[j*2];
		outLEDs[j].b = &outVals[j*2+1];
		*outLEDs[j].r = onBit;
		*outLEDs[j].b = !onBit;		
	}


	// Set INTOSC bits; 110=8MHz
	OSCCONbits.IRCF2 = 1;
	OSCCONbits.IRCF1 = 1;
	OSCCONbits.IRCF0 = 0;

	// select system clock; 1x=internal osc, 00=primary oscillator
	OSCCONbits.SCS1 = 0;
	OSCCONbits.SCS0 = 0;

	OSCTUNEbits.PLLEN = 1; // turn on 4x PLL clock multiplier

	//set outputs
	TRISA = 0; 
	TRISB = 0;
	TRISC = 0;	
	PORTA = 0b11111111;
	PORTB = 0b11111111;
	PORTC = 0b11111111;

	// set inputs
	TRISAbits.TRISA5 = 1;
	TRISAbits.TRISA3 = 1;

	// set analog channels
	ANSELbits.ANS4 = 1;
	ANSELbits.ANS3 = 1;


	// set up timer
	OpenTimer0(TIMER_INT_ON & // interrupt on
				T0_8BIT & // 8 bit
				T0_SOURCE_INT & // internal clock
				T0_PS_1_256); // 1/256 clock speed
				

	// Set A/D Channel; 0000=AN0, 0100=AN4
 	ADCON0bits.CHS3 = 0;
 	ADCON0bits.CHS2 = 1;
 	ADCON0bits.CHS1 = 0;
 	ADCON0bits.CHS0 = 0;

	// Set VREF; 00=VSS,VDD
	ADCON1bits.VCFG1 = 0; //Negative Voltage Reference
	ADCON1bits.VCFG0 = 1; //Positive Voltage Reference

	// Set A/D Result Format; 1=right just
	ADCON2bits.ADFM = 1;

	// Set A/D Conv clock; 010=32TOSC; 110=64TOSC
	ADCON2bits.ADCS2 = 0;
	ADCON2bits.ADCS1 = 1;
	ADCON2bits.ADCS0 = 0;

	// Set A/D TAD; 001=2TAD
	ADCON2bits.ACQT2 = 0;
	ADCON2bits.ACQT1 = 0;
	ADCON2bits.ACQT0 = 1;

	//Turn on AD
	ADCON0bits.ADON = 1;


	INTCONbits.GIE=1; // needed to make timer interrupt catch
	ConvertADC(); // Start conversion (preps for first read)
	WriteTimer0(0); // sets PreLoad

}

