/*
 * File:        Breathe-Easy EEG System (eeg.c)
 * Author:      Vasudharini Mannar (vkm22), Namita Kumar (nk437)
 * Target PIC:  PIC32MX250F128B
 */

////////////////////////////////////
// clock AND protoThreads configure!
// You MUST check this file!
#include "config.h"
// threading library
#include "pt_cornell_1_2_1.h"

////////////////////////////////////
// graphics libraries
#include "tft_master.h"
#include "tft_gfx.h"
// need for rand function
#include <stdlib.h>
#include <math.h>
////////////////////////////////////


/* Main code that runs the Breathe-Easy EEG
 */

// string buffer
char buffer[60];

//FFT Defines 
#define N_WAVE         512    /* size of FFT 512 */
#define LOG2_N_WAVE    9     /* log2(N_WAVE) 0 */
#define begin {
#define end }

//Define alpha (calm) and beta (excited) wave bands
#define CALM_BEGIN     5*512/(333.3)
#define CALM_END       16*512/(333.3)
#define EXCITED_BEGIN  17*512/(333.3)
#define EXCITED_END    40*512/(333.3)

//Debouncing Defines
#define NoPush 0
#define MaybePush 1
#define Pushed 2
#define MaybeRelease 3

//Pull Down/Up Resistors for PORT B 
#define EnablePullDownB(bits) CNPUBCLR=bits; CNPDBSET=bits;
#define DisablePullDownB(bits) CNPDBCLR=bits;
#define EnablePullUpB(bits) CNPDBCLR=bits; CNPUBSET=bits;
#define DisablePullUpB(bits) CNPUBCLR=bits;


void FFTfix(int fr[], int fi[], int m);
float arr_avg (float arr[], int size);

//=== fixed conversion macros =========================================
// for 16.16 fixed point
typedef signed int fix ;
#define int2fix(a)   (((fix)(a))<<16)            //Convert char to fix. a is a char
#define fix2int(a)   ((signed int)((a)>>16))    //Convert fix to int. a is an int
#define float2fix(a) ((fix)((a)*65536.0))         //Convert float to fix. a is a float
#define fix2float(a) (((float)(a))/65536.0)       //Convert fix to float. a is an int
//#define multfix(a,b) ((fix)(((( signed long long)(a))*(( signed long long)(b)))>>16)) //multiply two fixed 16.16
#define multfix(a,b) ((fix)(((( signed long)(a))*(( signed long)(b)))>>16)) //multiply two fixed 16.16

// === thread structures ============================================
// thread control structs
static struct pt pt_adc, pt_FFT, pt_control;

//Mode control 
int main_control = 0; 

//Control Variables/Iterators
int draw_iter = 0;
int x_val=0;
int adc_ready=0;
int ratio_iter = 0;
int switch_ = 0;
int PushState = 0;
int push_button, prev_push = 0;

//ADC variables
static int adc_9, adc_fft;
short wav_buf[N_WAVE];
float ratio_filter[3];
int timer_adc = 60000;
static short shifted;

//Ratio Variables
float ratio, avg_ratio=0;
int num_calm_samples, num_excited_samples = 0;
int max_freq = 0;
fix calm, excited = 0;
float calm_avg, excited_avg = 0;

//FFT variables 
int Sinewave[N_WAVE]; // a table of sines for the FFT
int window[N_WAVE]; // a table of window values for the FFT
int fr[N_WAVE], fi[N_WAVE];
int max_fr, max_fr_index, abs_fr, abs_fi;



// == Timer 3 ISR =====================================================
void __ISR(_TIMER_3_VECTOR, ipl2) Timer3Handler(void)
{
    mT3ClearIntFlag();
    // clear the timer interrupt flag   
    //Take FFT measurement
    adc_fft = ReadADC10(0);   // read the result of channel 9 conversion from the idle buffer
    AcquireADC10(); // not needed if ADC_AUTO_SAMPLING_ON below
    
    //Store ADC samples in temp buffer until we get 512 samples for FFT
    wav_buf[x_val] = adc_fft;
    x_val++;
    if (x_val > N_WAVE-1) {
        x_val = 0;
        adc_ready = 1;
    }

}

/**The following helper function takes the average of an array
*/
float arr_avg (float arr[], int size) {
    float sum; int i;
    for (i=0; i < size; i++) {
        sum += arr[i];
    }
    return sum/size; 
}

// === PUSHBUTTON CONTROL THREAD =============================================
static PT_THREAD (protothread_control(struct pt *pt))
{
    PT_BEGIN(pt); 
    while(main_control == 0) {
        //PT_YIELD_TIME_msec(500);
        push_button = mPORTBReadBits(BIT_9);  
        
        //DEBOUNCING CODE
        switch (PushState) {
             case NoPush: 
                if (push_button > 0) {
                   PushState=MaybePush;
                }
                else PushState=NoPush;
                break;
             case MaybePush:
                if (push_button > 0) {
                   PushState=Pushed;
                    tft_fillScreen(ILI9340_BLACK);
                    main_control = 1;
                }
                else PushState=NoPush;
                break;

             case Pushed:  
                if (push_button > 0) PushState=Pushed; 
                else PushState=MaybeRelease;    
                break;

             case MaybeRelease:
                if (push_button > 0) PushState=Pushed; 
                else PushState=NoPush;    
                break;
        }        
       
        prev_push = push_button; 
    }
  PT_END(pt);
} // animation thread



// === FFT & DISPLAY Thread =============================================
static PT_THREAD (protothread_FFT(struct pt *pt))
{
    PT_BEGIN(pt); 
    while(main_control > 0) {
        // yield time 1 second
       
        // variables for magnitude
        int abs_fr, abs_fi, max_fr, max_fr_index ;
        
        switch_ = mPORTBReadBits(BIT_8);
        
        
        if (switch_) {
            adc_9 = ReadADC10(0);   // read the result of channel 9 conversion from the idle buffer
            AcquireADC10(); // not needed if ADC_AUTO_SAMPLING_ON below
            draw_iter++;
            if (draw_iter > 320) draw_iter = 0;
            shifted = adc_9 >> 2;
            tft_fillRoundRect(draw_iter+1,0, 1, 180, 1, ILI9340_BLACK);// x,y,w,h,radius,color
            tft_drawPixel(draw_iter,shifted-140 , ILI9340_CYAN);
            // print raw ADC, floating voltage, fixed voltage       
        }
        
        
        //Set variables to 0 for next iteration
        calm = 0;
        excited = 0;
        calm_avg = 0;
        excited_avg = 0;
        num_calm_samples = 0; 
        num_excited_samples = 0;
                
        if (adc_ready) {
        // compute and display fft
        int sample_number;
        for (sample_number=0; sample_number<512; sample_number++){
            fr[sample_number] = multfix(wav_buf[sample_number]<<4, window[sample_number]); // 10 bit to fixed pt fraction <<5 for signal gen
            fi[sample_number] = 0 ;
        }

        FFTfix(fr, fi, LOG2_N_WAVE);

                     // get magnitude
        max_fr = 0 ; // peak magnitude for freq calc
        max_fr_index = 0 ;
        fr[1] = 0 ; // dc scatter
        // compute approx magnitude, then find the peak
        for (sample_number=2; sample_number<630/2; sample_number++) {
            abs_fr = abs(fr[sample_number]) ; //>>9
            abs_fi = abs(fi[sample_number]) ;
            // reuse fr to hold magnitude
            fr[sample_number] = max(abs_fr, abs_fi) + multfix(min(abs_fr,abs_fi), 26214 ); //0.4 is 26214 in 16:16 fixed
            
            if (sample_number > CALM_BEGIN && sample_number < CALM_END) {
                calm += fr[sample_number];
                num_calm_samples += 1;
            }
            //Sum all bins between 17 Hz and 40 Hz (Alpha Waves)
            else if (sample_number > EXCITED_BEGIN && sample_number < EXCITED_END) {
                excited += fr[sample_number];
                num_excited_samples += 1;
            }    
            if (fr[sample_number]>max_fr) { //Determine largest FFT index for max freq
                     max_fr = fr[sample_number];
                     max_fr_index = sample_number;
            }            
            if (!switch_) {
                //FFT DISPLAY CODE
                tft_fillRoundRect(sample_number+1,0, 1, 200, 1, ILI9340_BLACK);// x,y,w,h,radius,color
                tft_drawPixel(sample_number, 240-(fr[sample_number]+60), ILI9340_MAGENTA);
            }
        }
        
        //Get maximum frequency from FFT index
        max_freq = max_fr_index * 333/(512);
        
        if (max_freq > 3 && max_freq < 58) { //HIGH/LOW PASS FILTER IN SOFTWARE
            calm_avg = (fix2float(calm))/num_calm_samples;
            excited_avg = (fix2float(excited))/num_excited_samples;
            ratio = calm_avg/excited_avg;
            
            ratio_filter[ratio_iter] = ratio;
            ratio_iter++; 

            //3-point Averaging Filter for Ratio 
            if (ratio_iter > 2) { 
                ratio_iter = 0;
                avg_ratio = (ratio_filter[0]+ratio_filter[1]+ratio_filter[2])/3;
            }
        

            //SET LED Based on Ratio 
            if (avg_ratio < 0.9) { //red LED 
                mPORTAClearBits (BIT_0);  //yellow
                mPORTASetBits(BIT_1); //red
                mPORTBClearBits(BIT_5);   //green
            } else if (avg_ratio > 0.9 && avg_ratio < 1.2) { //yellow LED
                mPORTASetBits (BIT_0);  //yellow
                mPORTAClearBits(BIT_1); //red
                mPORTBClearBits(BIT_5);   //green
            } else if (avg_ratio > 1.2) { //green LED 
                mPORTAClearBits (BIT_0);  //yellow
                mPORTAClearBits(BIT_1); //red
                mPORTBSetBits(BIT_5);   //green
            }
        }
        
        //Code for displaying wave stats (frequency & ratio)
        tft_setTextColor(ILI9340_YELLOW); tft_setTextSize(1);
        tft_setCursor(0, 220);
        tft_fillRoundRect(0,220, 320, 20, 1, ILI9340_BLACK);// x,y,w,h,radius,color
        sprintf(buffer, "PEAK FREQ = %d, Calm = %f, \n Exc = %f, AVG_RATIO = %1.1f", max_fr_index * 333/(512), calm_avg, excited_avg, avg_ratio);

        tft_writeString(buffer);
                
        } adc_ready = 0; 
        // NEVER exit while
      } // END WHILE(1)
  PT_END(pt);
} // animation thread


//====================================================================
// FFT
void FFTfix(int fr[], int fi[], int m)
//Adapted from code by:
//Tom Roberts 11/8/89 and Malcolm Slaney 12/15/94 malcolm@interval.com
//fr[n],fi[n] are real,imaginary arrays, INPUT AND RESULT.
//size of data = 2**m
// This routine does foward transform only
begin
    int mr,nn,i,j,L,k,istep, n;
    int qr,qi,tr,ti,wr,wi;

    mr = 0;
    n = 1<<m;   //number of points
    nn = n - 1;

    /* decimation in time - re-order data */
    for(m=1; m<=nn; ++m)
    begin
        L = n;
        do L >>= 1; while(mr+L > nn);
        mr = (mr & (L-1)) + L;
        if(mr <= m) continue;
        tr = fr[m];
        fr[m] = fr[mr];
        fr[mr] = tr;
        //ti = fi[m];   //for real inputs, don't need this
        //fi[m] = fi[mr];
        //fi[mr] = ti;
    end

    L = 1;
    k = LOG2_N_WAVE-1;
    while(L < n)
    begin
        istep = L << 1;
        for(m=0; m<L; ++m)
        begin
            j = m << k;
            wr =  Sinewave[j+N_WAVE/4];
            wi = -Sinewave[j];
            wr >>= 1;
            wi >>= 1;

            for(i=m; i<n; i+=istep)
            begin
                j = i + L;
                tr = multfix(wr,fr[j]) - multfix(wi,fi[j]);
                ti = multfix(wr,fi[j]) + multfix(wi,fr[j]);
                qr = fr[i] >> 1;
                qi = fi[i] >> 1;
                fr[j] = qr - tr;
                fi[j] = qi - ti;
                fr[i] = qr + tr;
                fi[i] = qi + ti;
            end
        end
        --k;
        L = istep;
    end
end


// === Main  ======================================================
void main(void) {
 //SYSTEMConfigPerformance(PBCLK);
  
  ANSELA = 0; ANSELB = 0; 

  // === config threads ==========
  // turns OFF UART support and debugger pin, unless defines are set
  PT_setup();

  // === setup system wide interrupts  ========
  INTEnableSystemMultiVectoredInt();
  
  // the ADC ///////////////////////////////////////
        // configure and enable the ADC
	CloseADC10();	// ensure the ADC is off before setting the configuration

	// define setup parameters for OpenADC10
	// Turn module on | ouput in integer | trigger mode auto | enable autosample
        // ADC_CLK_AUTO -- Internal counter ends sampling and starts conversion (Auto convert)
        // ADC_AUTO_SAMPLING_ON -- Sampling begins immediately after last conversion completes; SAMP bit is automatically set
        // ADC_AUTO_SAMPLING_OFF -- Sampling begins with AcquireADC10();
        #define PARAM1  ADC_FORMAT_INTG16 | ADC_CLK_AUTO | ADC_AUTO_SAMPLING_OFF //

	// define setup parameters for OpenADC10
	// ADC ref external  | disable offset test | disable scan mode | do 1 sample | use single buf | alternate mode off
	#define PARAM2  ADC_VREF_AVDD_AVSS | ADC_OFFSET_CAL_DISABLE | ADC_SCAN_OFF | ADC_SAMPLES_PER_INT_1 | ADC_ALT_BUF_OFF | ADC_ALT_INPUT_OFF
        //
	// Define setup parameters for OpenADC10
        // use peripherial bus clock | set sample time | set ADC clock divider
        // ADC_CONV_CLK_Tcy2 means divide CLK_PB by 2 (max speed)
        // ADC_SAMPLE_TIME_5 seems to work with a source resistance < 1kohm
        #define PARAM3 ADC_CONV_CLK_PB | ADC_SAMPLE_TIME_5 | ADC_CONV_CLK_Tcy2 //ADC_SAMPLE_TIME_15| ADC_CONV_CLK_Tcy2

	// define setup parameters for OpenADC10
	// set AN11 and  as analog inputs
	#define PARAM4	ENABLE_AN11_ANA // pin 24

	// define setup parameters for OpenADC10
	// do not assign channels to scan
	#define PARAM5	SKIP_SCAN_ALL

	// use ground as neg ref for A | use AN11 for input A     
	// configure to sample AN11 
	SetChanADC10( ADC_CH0_NEG_SAMPLEA_NVREF | ADC_CH0_POS_SAMPLEA_AN11 ); // configure to sample AN4 
	OpenADC10( PARAM1, PARAM2, PARAM3, PARAM4, PARAM5 ); // configure ADC using the parameters defined above

	EnableADC10(); // Enable the ADC
  ///////////////////////////////////////////////////////
    
  //Timer for ADC measurement
  OpenTimer3(T3_ON | T3_SOURCE_INT | T3_PS_1_2, timer_adc);
  ConfigIntTimer3(T3_INT_ON | T3_INT_PRIOR_2);
  mT3ClearIntFlag(); // and clear the interrupt flag    
  
  //Turn off LEDs initially
  mPORTASetBits(BIT_0 | BIT_1);
  mPORTBSetBits(BIT_5);
  
  //Set pins 2, 3 and 14 as output pins for LEDs
  mPORTASetPinsDigitalOut(BIT_0 | BIT_1);    //Set pins 2 & 3 as output
  mPORTBSetPinsDigitalOut(BIT_5);    //Set pin 14 as output 
  
  //Set pins 17 and 18 for switches
  mPORTBSetPinsDigitalIn(BIT_8);    //Set pin 17 as input 
  mPORTBSetPinsDigitalIn(BIT_9);    //Set pin 18 as input 

  
  
  // === init FFT data =====================================
  // one cycle sine table
  //  required for FFT
    int ii;
    for (ii=0; ii<N_WAVE; ii++){
       Sinewave[ii] = float2fix(sin(6.283*((float)ii)/N_WAVE));
       window[ii] = float2fix(0.5*(1.0-cos(6.283*((float)ii)/(N_WAVE-1)))) ;
       //window[ii] = float2fix(1.0) ;
    }
  
  // init the threads
  PT_INIT(&pt_control);
  PT_INIT(&pt_FFT);

  // init the display
  tft_init_hw();
  tft_begin();
  tft_fillScreen(ILI9340_BLACK);
  //240x320 vertical display
  tft_setRotation(3); // Use tft_setRotation(1) for 320x240

  //Initial TFT texts
  tft_setTextColor(ILI9340_YELLOW); tft_setTextSize(2);
  tft_setCursor(100, 160);
  sprintf(buffer, "EEG is OFF; \n Press button to \n begin measurement.");
  tft_writeString(buffer);
  
  // seed random color
  srand(1);

  // round-robin scheduler for threads
  while (1){
      PT_SCHEDULE(protothread_control(&pt_control));
      PT_SCHEDULE(protothread_FFT(&pt_FFT));
      }
  } // main

// === end  ======================================================

