/*
 * File:        TFT, keypad, DAC, LED, PORT EXPANDER test
 *              1.3.2 spawn example
 *              1.3.2 Modified scheduler
 * 
 * Author:      Bruce Land
 * For use with Sean Carroll's Big Board
 * http://people.ece.cornell.edu/land/courses/ece4760/PIC32/target_board.html
 * Target PIC:  PIC32MX250F128B
 */

////////////////////////////////////
// clock AND protoThreads configure!
// You MUST check this file!
#include "config_1_3_2.h"
// threading library
#include "pt_cornell_1_3_2.h"
// yup, the expander
#include "port_expander_brl4.h"

////////////////////////////////////
// graphics libraries
// SPI channel 1 connections to TFT
#include "tft_master.h"
#include "tft_gfx.h"
// need for rand function
#include <stdlib.h>
// need for sin function
#include <math.h>
////////////////////////////////////

// lock out timer 2 interrupt during spi communication to port expander
// This is necessary if you use the SPI2 channel in an ISR.
// The ISR below runs the DAC using SPI2
#define start_spi2_critical_section INTEnable(INT_T2, 0)
#define end_spi2_critical_section INTEnable(INT_T2, 1)

////////////////////////////////////
// DAC ISR
// A-channel, 1x, active
#define DAC_config_chan_A 0b0011000000000000
// B-channel, 1x, active
#define DAC_config_chan_B 0b1011000000000000
// DDS constant
#define two32 4294967296.0 // 2^32 
#define Fs 100000

// === thread comm signals ===============================================
int blink_control ;
int tone_control ;
int tft_control ;
// thread spawn control structure
static struct pt tft_spawn ;

//== Timer 2 interrupt handler ===========================================
// generates a DDS sinewave at a frequency set by the serial thread
// default frequency is 400 Hz.
//
volatile unsigned int DAC_data ;// output value
volatile SpiChannel spiChn = SPI_CHANNEL2 ;	// the SPI channel to use
volatile int spiClkDiv = 4 ; // 10 MHz max speed for port expander!!
// the DDS units:
volatile unsigned int phase_accum_main, phase_incr_main=400.0*two32/Fs ;//
// DDS sine table
#define sine_table_size 256
volatile int sin_table[sine_table_size];

void __ISR(_TIMER_2_VECTOR, ipl2) Timer2Handler(void)
{
    int junk;
    
    mT2ClearIntFlag();
    
    // main DDS phase and sine table lookup
    if(tone_control) phase_accum_main += phase_incr_main  ;
    DAC_data = sin_table[phase_accum_main>>24]  ;
 
    // === Channel A =============
    // wait for possible port expander transactions to complete
    while (TxBufFullSPI2());
    // reset spi mode to avoid conflict with port expander
    SPI_Mode16();
    while (SPI2STATbits.SPIBUSY); // wait for end of transaction
    // CS low to start transaction
     mPORTBClearBits(BIT_4); // start transaction
    // write to spi2 
    WriteSPI2( DAC_config_chan_A | ((DAC_data + 2048) & 0xfff));
    while (SPI2STATbits.SPIBUSY); // wait for end of transaction
     // CS high
    mPORTBSetBits(BIT_4); // end transaction
    // need to read SPI channel to avoid confusing port expander
    junk = ReadSPI2(); 
   //    
}

// === print a line on TFT =====================================================
// Utilities to print a line on the TFT
// Predefined colors definitions (from tft_master.h)
//#define	ILI9340_BLACK   0x0000
//#define	ILI9340_BLUE    0x001F
//#define	ILI9340_RED     0xF800
//#define	ILI9340_GREEN   0x07E0
//#define ILI9340_CYAN    0x07FF
//#define ILI9340_MAGENTA 0xF81F
//#define ILI9340_YELLOW  0xFFE0
//#define ILI9340_WHITE   0xFFFF

// print a line on the TFT
// string buffer
char print_buffer[80];
void tft_printLine(int line_number, int indent, char* print_buffer, short text_color, short back_color, short char_size){
    // This routine prints one line of text with controlled screen position, color and size.
    // This rouitine is TEXT oriented. Therefore, all positions are in
    // units of LINE NUMBER and CHARACTER INDENT. 
    // Both are related to the SIZE of the text you choose.
    // SIZEs are small positive integers. Useful range is 1-6 or so
    // I find SIZE=2 to be the most useful
    // For size=1 
    // -- tft_setRotation(0) give 39 lines, each line 36 characters
    // -- tft_setRotation(1) give 29 lines, each line 49 characters
    // For size=2  
    // -- tft_setRotation(0) give 19 lines, each line 18 characters
    // -- tft_setRotation(1) give 14 lines, each line 25 characters
    // For size=3  
    // -- tft_setRotation(0) give 12 lines, each line 12 characters
    // -- tft_setRotation(1) give 9 lines, each line 18 characters
    // For size=4  
    // -- tft_setRotation(0) give 9 lines, each line 10 characters
    // -- tft_setRotation(1) give 6 lines, each line 12 characters
    
    // print_buffer is the string to print
    int v_pos, h_pos;
    char_size = (char_size>0)? char_size : 1 ;
    //
    v_pos = line_number * 8 * char_size ;
    h_pos = indent * 6 * char_size ;
    // erase the pixels
    //tft_fillRoundRect(0, v_pos, 239, 8, 1, back_color);// x,y,w,h,radius,color
    tft_setTextColor2(text_color, back_color); 
    tft_setCursor(h_pos, v_pos);
    tft_setTextSize(char_size);
    tft_writeString(print_buffer);
}

//=== spawnable draw thread =================================================
// Do NOT schedule this thread !!!
int protothread_tft_draw_disc(struct pt *pt)
{
    static disc_state;
    PT_BEGIN(pt);
    // draw a disc
    // void tft_fillCircle(short x0, short y0, short r, unsigned short color);
    if (disc_state) {
        tft_fillCircle(100, 100, 10, ILI9340_GREEN);
        disc_state = 0 ;
    }
    else {
        tft_fillCircle(100, 100, 10, ILI9340_BLACK);
        disc_state = 1 ;
    }
    // kill this draw thread, to allow spawning thread to execute
    PT_EXIT(pt);
    // and indicate the end of the thread
    PT_END(pt);
}

// === Timer Thread =========================================================
// system 1 second interval tick
// prints on TFT and blinks LED on RA0
int sys_time_seconds ;
// thread identifier to set thread parameters
int thread_num_timer;
// update a 1 second tick counter
static PT_THREAD (protothread_timer(struct pt *pt))
{
    PT_BEGIN(pt);     
      while(1) {        
        // draw sys_time
        PT_YIELD_TIME_msec(1000) ;
        sys_time_seconds++;
        sprintf(print_buffer,"Time=%d", sys_time_seconds);
        //tft_printLine(int line_number, int indent, char* print_buffer, short text_color, short back_color, short char_size)
        tft_printLine(1,0, print_buffer, ILI9340_GREEN, ILI9340_BLACK, 2);
        // draw a disc
        if (tft_control){
            PT_SPAWN(pt, &tft_spawn, protothread_tft_draw_disc(&tft_spawn)) ;
        }
        // NEVER exit while
      } // END WHILE(1)
  PT_END(pt);
} // timer thread

// === blink thread ==========================================================
static PT_THREAD (protothread_blink(struct pt *pt))
{
    PT_BEGIN(pt);
    // set up LED port A0 to blink
    mPORTASetBits(BIT_0 );	//Clear bits to ensure light is off.
    mPORTASetPinsDigitalOut(BIT_0 );    //Set port as output
    blink_control = 0; 
    while(1){
        // yield time 1 second
        PT_YIELD_TIME_msec(100) ;
        PT_YIELD_UNTIL(pt, blink_control) ;
        // toggle the LED on the big board
        mPORTAToggleBits(BIT_0);         
    } // END WHILE(1)   
    PT_END(pt);  
} // thread blink

// === Keypad Thread ========================================================
// Decodes a keypad attached to the port expander, including a shift KEY.
//
// Port Expander connections:
// y0 -- row 1 -- thru 300 ohm resistor -- avoid short when two buttons pushed
// y1 -- row 2 -- thru 300 ohm resistor
// y2 -- row 3 -- thru 300 ohm resistor
// y3 -- row 4 -- thru 300 ohm resistor
// y4 -- col 1 -- internal pullup resistor -- avoid open circuit input when no button pushed
// y5 -- col 2 -- internal pullup resistor
// y6 -- col 3 -- internal pullup resistor
// y7 -- shift key connection -- internal pullup resistor

static PT_THREAD (protothread_key(struct pt *pt))
{
    PT_BEGIN(pt);
    static int keypad, i ;
    // order is 0 thru 9 then * ==10 and # ==11
    // no press = -1
    // table is decoded to natural digit order (except for * and #)
    // with shift key codes for each key
    // keys 0-9 return the digit number
    // keys 10 and 11 are * adn # respectively
    // Keys 12 to 21 are the shifted digits
    // keys 22 and 23 are shifted * and # respectively
    static int keytable[24]=
    //        0     1      2    3     4     5     6      7    8     9    10-*  11-#
            {0xd7, 0xbe, 0xde, 0xee, 0xbd, 0xdd, 0xed, 0xbb, 0xdb, 0xeb, 0xb7, 0xe7,
    //        s0     s1    s2  s3    s4    s5    s6     s7   s8    s9    s10-* s11-#
             0x57, 0x3e, 0x5e, 0x6e, 0x3d, 0x5d, 0x6d, 0x3b, 0x5b, 0x6b, 0x37, 0x67};
    // bit pattern for each row of the keypad scan -- active LOW
    // bit zero low is first entry
    static char out_table[4] = {0b1110, 0b1101, 0b1011, 0b0111};
    // init the port expander
    
    // Turn off the T2 ISR for a few microseconds
    start_spi2_critical_section;
    initPE();
    // PortY on Expander ports as digital outputs
    mPortYSetPinsOut(BIT_0 | BIT_1 | BIT_2 | BIT_3);    //Set port as output
    // PortY as inputs
    // note that bit 7 will be shift key input, 
    // separate from keypad
    mPortYSetPinsIn(BIT_4 | BIT_5 | BIT_6 | BIT_7);    //Set port as input
    mPortYEnablePullUp(BIT_4 | BIT_5 | BIT_6 | BIT_7); 
    end_spi2_critical_section ;
    
    // the read-pattern if no button is pulled down by an output
    #define no_button (0x70)

      while(1) {
        // yield time for a keypad should be around 30 mSec
        PT_YIELD_TIME_msec(30);
        
        // stepping thru the 4 rows of the keypad
        for (i=0; i<4; i++) {
            
            start_spi2_critical_section ;
            // scan each row active-low
            writePE(GPIOY, out_table[i]);
            //reading the port also reads the outputs
            keypad  = readPE(GPIOY);
            end_spi2_critical_section ;
            
            // was there a keypress?
            if((keypad & no_button) != no_button) { break;}
        }
        
        // search for keycode
        if (keypad > 0){ // then button is pushed
            for (i=0; i<24; i++){
                if (keytable[i]==keypad) break;
            }
            // if invalid, two button push, set to -1
            if (i==24) i=-1;
        }
        else i = -1; // no button pushed

        // decode tone key command
        if (i==1){ // stop tone
            tone_control = 1;
        }
        if (i==2){ // start tone
            tone_control = 0;
        }
        
        // decode blink key command
        if (i==4){ // stop blinking
            blink_control = 1;
        }
        if (i==5){ // start blinking
            blink_control = 0;
        }
        
        // decode draw key command
        if (i==7){ // stop blinking
            tft_control = 1;
        }
        if (i==8){ // start blinking
            tft_control = 0;
        }
        
        // NEVER exit while
      } // END WHILE(1)
  PT_END(pt);
} // keypad thread

// === Main  ======================================================

void main(void) {
  
  // === set up timer for DAC ISR =====================
  // timer interrupt //////////////////////////
    // Set up timer2 on,  interrupts, internal clock, prescalar 1, toggle rate
    // at 30 MHz PB clock 60 counts is two microsec
    // 400 is 100 ksamples/sec
    // 2000 is 20 ksamp/sec
    OpenTimer2(T2_ON | T2_SOURCE_INT | T2_PS_1_1, 400);

    // set up the timer interrupt with a priority of 2
    ConfigIntTimer2(T2_INT_ON | T2_INT_PRIOR_2);
    mT2ClearIntFlag(); // and clear the interrupt flag
    
    // === SPI channel for DAC and Expander ============
    // control CS for DAC
    mPORTBSetPinsDigitalOut(BIT_4);
    mPORTBSetBits(BIT_4);
    // SCK2 is on pin RB15 
    // SDO2 (MOSI) is in PPS output group 2, and could be connected to RB5 which is pin 14
    PPSOutput(2, RPB5, SDO2);
    // 16 bit transfer CKP=1 CKE=1
    // possibles SPI_OPEN_CKP_HIGH;   SPI_OPEN_SMP_END;  SPI_OPEN_CKE_REV
    // For any given peripherial, you will need to match these
    // NOTE!! IF you are using the port expander THEN
    // >>> clk divider must be set to 4 for 10 MHz <<<
    SpiChnOpen(SPI_CHANNEL2, SPI_OPEN_ON | SPI_OPEN_MODE16 | SPI_OPEN_MSTEN | SPI_OPEN_CKE_REV , 4);
  // end SPI setup
    
  // === build the DDS sine lookup table =======
   // scaled to produce values between 0 and 4096
   int ii;
   for (ii = 0; ii < sine_table_size; ii++){
         sin_table[ii] = (int)(2047*sin((float)ii*6.283/(float)sine_table_size));
    }
    
  // === setup system wide interrupts  ========
  INTEnableSystemMultiVectoredInt();
  
  // === TFT setup ============================
  // init the display in main since more than one thread uses it.
  // NOTE that this init assumes SPI channel 1 connections
  tft_init_hw();
  tft_begin();
  tft_fillScreen(ILI9340_BLACK);
  //240x320 vertical display
  tft_setRotation(0); // Use tft_setRotation(1) for 320x240
  
  // === config threads ========================
  PT_setup();
  
  // === identify the threads to the scheduler =====
  // add the thread function pointers to be scheduled
  // --- Two parameters: function_name and rate. ---
  // rate=0 fastest, rate=1 half, rate=2 quarter, rate=3 eighth, rate=4 sixteenth,
  // rate=5 or greater DISABLE thread!
  // If you need to access specific thread descriptors (to change rate), 
  // then return the list index
  thread_num_timer = pt_add(protothread_timer, 0);
  pt_add(protothread_key, 1);
  pt_add(protothread_blink, 1);

  // === initalize the scheduler ====================
  PT_INIT(&pt_sched) ;
  // >>> CHOOSE the scheduler method: <<<
  // (1)
  // SCHED_ROUND_ROBIN just cycles thru all defined threads
  //pt_sched_method = SCHED_ROUND_ROBIN ;
  
  // (2)
  // SCHED_RATE executes some threads more often then others
  // -- rate=0 fastest, rate=1 half, rate=2 quarter, rate=3 eighth, rate=4 sixteenth,
  // -- rate=5 or greater DISABLE thread!
  // pt_sched_method = SCHED_RATE ;
  
  pt_sched_method = SCHED_ROUND_ROBIN ;
  
  // === scheduler thread =======================
  // scheduler never exits
  PT_SCHEDULE(protothread_sched(&pt_sched));
  // ============================================
  
} // main

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

