/**
 * This is a very small example that shows how to use
 * === PWM with DDS sine wave ===
 * The PWM runs at 6-bit resolution at 500 kHz
 * Spectral purity is about 32 db at low frequency
 * 28 db at 20 kHz and dropping above that
 *
 * There is a command thread to set parametners
 * There is a thread that plays a scale
 * 
 * -- Pin 4 is toggled by the timer2 interrupt
 * -- Pin 18 is PWM out
 *
 * -- Uart connections explained elsewhere
 * Modified by Bruce Land 
 * Jan 2015
 */

// i/o names 
#include <plib.h>
// set cpu freq to 64 MHz, peripeherial to 32 MHz
#pragma config FNOSC = FRCPLL, POSCMOD = HS, FPLLIDIV = DIV_2, FPLLMUL = MUL_16, FPBDIV = DIV_2, FPLLODIV = DIV_1
#pragma config FWDTEN = OFF
#pragma config FSOSCEN = OFF, JTAGEN = OFF
// frequency we're running at
#define	SYS_FREQ 64000000
// serial stuff
#include <stdio.h>
#include <math.h>

// protoThreads environment
#include "pt_cornell.h" 

// === thread structures ============================================
// thread control structs

//print lock
static struct pt_sem print_sem ;

// note that UART input and output are threads
static struct pt  pt_scale, pt_cmd, pt_time, pt_input, pt_output, pt_DMA_output ;

// system 1 second interval tick
int sys_time_seconds ;

//The actual period of the wave
int generate_period = 63 ; // PWM sample time
int pwm_on_time = 32 ;

// sine lookup table
volatile signed char sine_table[64];
unsigned int sine_index ;
// DDS variables
// set DDS increment to 1000 Hz
volatile unsigned int DDS_accumulator, DDS_increment=8590000, DDS_amp ;

// play a scale
int play_scale ;
/*
C4   262 Hz (middle C)
        D4   294
	E4   330
	F4   349
	G4   392
	A4   440
	B4   494
	C5   523
 * */
// scale freq in Hz
int notes[9]={262, 294, 330, 349, 392, 440, 494, 523, 0};

// == Timer 2 ISR =====================================================
// DDS
void __ISR(_TIMER_2_VECTOR, ipl2) Timer2Handler(void)
{
    // generate a trigger strobe for timing other events
    mPORTBSetBits(BIT_0);
    // change the PWM duty cycle
    DDS_accumulator += DDS_increment ;
    sine_index = DDS_accumulator>>26 ;
    pwm_on_time = (sine_table[sine_index]>>DDS_amp) + 32 ;
    SetDCOC3PWM(pwm_on_time);
    // clear the timer interrupt flag
    mT2ClearIntFlag();
    mPORTBClearBits(BIT_0);
}

// === Command Thread ======================================================
// The serial interface
static char cmd[16]; 
static int value;

static PT_THREAD (protothread_cmd(struct pt *pt))
{
    PT_BEGIN(pt);
      while(1) {
          
            // send the prompt via DMA to serial           
            sprintf(PT_send_buffer,"cmd>");
            // by spawning a print thread
            PT_SPAWN(pt, &pt_DMA_output, PT_DMA_PutSerialBuffer(&pt_DMA_output) );
 
          //spawn a thread to handle terminal input
            // the input thread waits for input
            // -- BUT does NOT block other threads
            // string is returned in "PT_term_buffer"
            PT_SPAWN(pt, &pt_input, PT_GetSerialBuffer(&pt_input) );
            // returns when the thead dies
            // in this case, when <enter> is pushed
            // now parse the string
             sscanf(PT_term_buffer, "%s %d", cmd, &value);
         
             if (cmd[0]=='f' ) {
                 // increment = freq* (2^32)/(PWM rate)
                 // PWM rate = 500 kHz
                 DDS_increment = (unsigned int)(value*8590);
             }

             if (cmd[0]=='s' ) {
                 // play scale
                 play_scale = 1 ;
             }
             //        
            // never exit while
      } // END WHILE(1)
  PT_END(pt);
} // thread 3

// === Scale Thread ======================================================
// update a 1 second tick counter
static PT_THREAD (protothread_scale(struct pt *pt))
{
    PT_BEGIN(pt);

      while(1) {
          static int i, save_DDS;
          PT_YIELD_UNTIL(pt, play_scale);
          // save freq that is playing
          save_DDS = DDS_increment;
          for (i=0; i<9; i++) {
              DDS_increment = (unsigned int)(notes[i]*8590);
              PT_YIELD_TIME_msec(250) ;
          }
          
          play_scale = 0;
          // retore freq
          DDS_increment = save_DDS ;
            // NEVER exit while
      } // END WHILE(1)

  PT_END(pt);
} // thread

// === One second Thread ======================================================
// update a 1 second tick counter
static PT_THREAD (protothread_time(struct pt *pt))
{
    PT_BEGIN(pt);

      while(1) {
            // yield time 1 second
            PT_YIELD_TIME_msec(1000) ;
            sys_time_seconds++ ;
            // NEVER exit while
      } // END WHILE(1)

  PT_END(pt);
} // thread 

// === Main  ======================================================

int main(void)
{
  
  // === Config timer and output compares to make pulses ========
  // set up timer2 to generate the wave period
  OpenTimer2(T2_ON | T2_SOURCE_INT | T2_PS_1_1, generate_period);
  ConfigIntTimer2(T2_INT_ON | T2_INT_PRIOR_2);
  mT2ClearIntFlag(); // and clear the interrupt flag

  // set up compare3 for PWM mode
  OpenOC3(OC_ON | OC_TIMER2_SRC | OC_PWM_FAULT_PIN_DISABLE , pwm_on_time, pwm_on_time); //
  // OC3 is PPS group 4, map to RPB9 (pin 18)
  PPSOutput(4, RPB9, OC3);
 // mPORTASetPinsDigitalOut(BIT_3);    //Set port as output -- not needed

  // === config the uart, DMA, vref, timer5 ISR ===========
  PT_setup();

  // build sine table
  int i;
        for (i = 0; i < 64; i++){
            sine_table[i] =(signed char) (32.0 * sin((float)i*6.283/(float)64)) ;  //8 bit amp, 64 samples
         }

  // === setup system wide interrupts  ====================
  INTEnableSystemMultiVectoredInt();
    
  // === set up i/o port pin ===============================
  mPORTBSetPinsDigitalOut(BIT_0 );    //Set port as output

  // === now the threads ===================================
  
  // init the threads
  PT_INIT(&pt_cmd);
  PT_INIT(&pt_time);

  // schedule the threads
  while(1) {
    PT_SCHEDULE(protothread_cmd(&pt_cmd));
    PT_SCHEDULE(protothread_time(&pt_time));
    PT_SCHEDULE(protothread_scale(&pt_scale));
  }
} // main
