/*
 * File: 
 * Beginning of DMA weird architecture machine
 * ---------
 * This machine makes
 * bit outputs on RA0 and RB0
 * from one of three of blocks 
 * ---------
 * The machine increments a variable
 * and uses the variable value to conditionally branch
 * between three different pulse generator DMA blocks
 * The branch mechanism is to load a new DMA0 block to effectively
 * change the DMA block 'program counter' to a new value
 * The new execution path runs until the original DMA0 exectuion path
 * is restored
 * ---------
 * Bruce Land
 * Target PIC:  PIC32MX250F128B
 */
// all peripheral library includes
#include <plib.h>
////////////////////////////////////
// graphics libraries for dbugging
// SPI channel 1 connections to TFT
//#include "tft_master.h"
//#include "tft_gfx.h"

// 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){
    // 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);
}
*/
/////////////////////////////////////////////////////////
#pragma config FNOSC = FRCPLL, POSCMOD = OFF
//#pragma config FNOSC = PRIPLL, POSCMOD = EC, OSCIOFNC = OFF
#pragma config FPLLIDIV = DIV_2, FPLLMUL = MUL_20, FPLLODIV = DIV_2  //40 MHz
#pragma config FPBDIV = DIV_1 // PB 40 MHz
// turn off alternative functions for port pins B.4 and B.5
#pragma config JTAGEN = OFF, DEBUG = OFF
#pragma config FSOSCEN = OFF

// ==============
// Block list macros to make defining a list of blocks easier
//
// base address of DMA block list being defined
unsigned int block_list_addr ;
// counter for the current block to create
int N = 0;
// total blocks defined, set by end list macro
int number_of_blocks ; // 10 for full pgm

// offsets into the DMA control block
#define DCH0SSA_OFFSET 0x30 // DMA source address field

// the source address required for the next block to be defined
#define next_blk_src_addr (void*)(DMA_blocks+length_of_block*(N+1)+DCH0SSA_OFFSET)

// the address of the next block to be defined
#define next_blk_addr (void*)(DMA_blocks+length_of_block*(N+1))

// the address of DMA0
#define DMA0_addr (void*)&DCH0CON

// the address of DMA2
#define DMA2_addr (void*)&DCH2CON

// ==============
// macros to begin and end execution block list
#define begin_execution_block_list(list_name){\
    N=0;\
    block_list_addr = list_name;\
}

#define end_execution_block_list{\
    number_of_blocks = N;\
}

// ==============
// macros to begin and end non-execution block list
#define begin_block_list(list_name){\
    N=0;\
    block_list_addr = list_name;\
}

#define end_block_list{\
}

// ==============
// A macro to make defining DMA2 blocks more terse
#define make_DMA2_block(src_addr, dest_addr, src_size, dest_size, cell_size){\
    DmaChnOpen(2, 0, DMA_OPEN_AUTO);\
    DmaChnSetTxfer(2, src_addr, dest_addr, src_size, dest_size, cell_size);\
    DmaChnSetEventControl(2, DMA_EV_START_IRQ(_DMA0_IRQ));\
    DmaChnSetEvEnableFlags(2, DMA_EV_CELL_DONE);\
    DmaChnEnable(2);\
    memcpy(block_list_addr+192*N, &DCH2CON, 192);\
    N++;\
}

// ==============
// A macro to make defining DMA0 blocks more terse
#define make_DMA0_block(src_addr, dest_addr, src_size, dest_size, cell_size){\
    DmaChnOpen(0, 0, DMA_OPEN_AUTO);\
    DmaChnSetTxfer(0, src_addr, dest_addr, src_size, dest_size, cell_size);\
    DmaChnSetEventControl(0, DMA_EV_START_IRQ(_DMA2_IRQ));\
    DmaChnSetEvEnableFlags(0, DMA_EV_CELL_DONE);\
    DmaChnEnable(0);\
    memcpy(block_list_addr+192*N, &DCH0CON, 192);\
    N++;\
}

// ==============
// DMA block image list
// The number of blocks in our execute list
// The machine will cycle through these blocks, then start again

// From http://people.ece.cornell.edu/land/courses/ece4760/PIC32/Microchip_stuff/2xx_datasheet.pdf
// page 52, TABLE 4-12:DMA CHANNELS 0-3 REGISTER MAP
// Total DMA control block size in bytes:
#define length_of_block 192
// the main DMA block program list
// max of 15 blocks!
#define max_blocks 15
unsigned char DMA_blocks[max_blocks * length_of_block];

// DMA0 block jump array alternatives
// One of these blocks will be swapped into DMA0, effectively changing the execution path
// to one of three conditions, depending in the inc variable
// on-the-fly by a conditional jump sequence
// The 4th entry is ORIGINAL EXECUTION path
// This is needed as a 'return' at the end of each conditional
#define number_of_jump_blocks 4
unsigned char DMA0_jump_blocks[number_of_jump_blocks * length_of_block];

// Three sets of blocks, each to be the execution path for one of the jump blocks
// Each as two block (1) generates a pulse (2) does the 'return' to the original path
#define number_of_condition_0_blocks 2
unsigned char DMA_jump_blocks_0[number_of_condition_0_blocks * length_of_block];
//
#define number_of_condition_1_blocks 2
unsigned char DMA_jump_blocks_1[number_of_condition_1_blocks * length_of_block];
//
#define number_of_condition_2_blocks 2
unsigned char DMA_jump_blocks_2[number_of_condition_2_blocks * length_of_block];


// ==============
// output patterns to test the actual machine
// DMA output tables to LEDs
unsigned char LED_pattern0[] =
{
	0x01,	0x00,	0x01,	0x00,	0x01,	0x00,	0x01,	0x00,
	0x01,	0x00,	0x01,	0x00,	0x01,	0x00,	0x01,	0x00
} ;
// DMA output tables
unsigned char	LED_pattern1[]=
{
	0x01,	0x01,	0x01,	0x01,	0x01,	0x01,	0x01,	0x01,
	0x00,	0x00,	0x00,	0x00,	0x00,	0x00,	0x00,	0x00,
    0x01,	0x01,	0x01,	0x01,	0x01,	0x01,	0x01,	0x01,
	0x00,	0x00,	0x00,	0x00,	0x00,	0x00,	0x00,	0x00
};

// DMA output tables 64 bytes
unsigned char	LED_pattern2[]=
{
	0x01,	0x01,	0x01,	0x01,	0x01,	0x01,	0x01,	0x01,
    0x01,	0x01,	0x01,	0x01,	0x01,	0x01,	0x01,	0x01,
	0x00,	0x00,	0x00,	0x00,	0x00,	0x00,	0x00,	0x00,
    0x00,	0x00,	0x00,	0x00,	0x00,	0x00,	0x00,	0x00,
    0x01,	0x01,	0x01,	0x01,	0x01,	0x01,	0x01,	0x01,
    0x01,	0x01,	0x01,	0x01,	0x01,	0x01,	0x01,	0x01,
	0x00,	0x00,	0x00,	0x00,	0x00,	0x00,	0x00,	0x00,
    0x00,	0x00,	0x00,	0x00,	0x00,	0x00,	0x00,	0x00,
};

// DMA table for incrementing a variable (just a few entries for now) 
// [index0]=1 [index1]=2 [index2] wraps back to zero
// it is aligned so that the value in the array can be used as the low-byte
// of the pointer to the next element
unsigned char inc_array[] __attribute__ ((aligned(256))) = {1, 2, 0} ;
unsigned char inc_value=1 ;
// write-only junk to make DMA NOP
unsigned char bit_bucket ;

// array to convert 1 byte inc to four byte jump table pointer
// This is just inc_value*4, but all arithmetic is a table-lookup
// The array is aligned on a byte boundary so that the one-byte index
// can just be copied into the source field of the next DMA block
unsigned char offset_array[] __attribute__ ((aligned(256))) = {4, 8, 0} ;

// DMA table for computing a branch 
// [index0]=address of target block if variable=0
// [index1]=address of target block if variable=1
// it is aligned so that the value in the offset_array can be used as the low-byte
// of the pointer to the next element
unsigned int jmp_array[3] __attribute__ ((aligned(256))) ;

// to clear bits in DMA 0 flag register
// using DMA 1-3 transfer
unsigned short clear_flag = 0x0000 ;

// === Main  ======================================================
void main(void) {
 
    SYSTEMConfig(40000000, SYS_CFG_WAIT_STATES | SYS_CFG_PCACHE);
  
    // Build the actual address map for moving the jump target block
    // into DMA0 block
    // NOTE that these VIRTUAL addresses MUST be converted to physical addresses
    // On this architecture, the physical address of RAM is just the two lower bytes
    // of the virtual address
    jmp_array[0] = DMA0_jump_blocks ;
    jmp_array[1] = DMA0_jump_blocks + length_of_block ;
    jmp_array[2] = DMA0_jump_blocks + length_of_block*2 ;
    
    //=============
    // In the following code, the DMA definition functions are used to build
    // DMA blocks, which are then copied to memory sequentially.
    // They will be executed later when loaded into an active DMA channel
    // using a DMA fetch/execute machine
    //=============
    
    // make the array of execution blocks in memory for later transfer to DMA2
    // The array must be big enough to hold N*192 bytes for N blocks
    begin_execution_block_list(DMA_blocks) ;
       
    // === Trigger pulse out =====
    // Trigger pulse for scope -- One approx 0.1 microSec pulse on A0
    make_DMA2_block(LED_pattern0, (void*)&LATA, 2, 1, 2);
    
    // === Increment a variable ======
    // part 1 -- read variable, write to low-byte of source in next block
    // part 2 -- in the next block, read array element, write to variable
    // note that the CONTENTs of the array is the incremented value of the INDEX
    make_DMA2_block(&inc_value, next_blk_src_addr, 1, 1, 1);  
    // -- part 2 -- read array entry into variable
    make_DMA2_block(inc_array, &inc_value, 1, 1, 1);
    
    //=== Compute a branch ======
    // Compute a branch --
    // part 1 -- read variable, write to LOW-BYTE of source in next block
    make_DMA2_block(&inc_value, next_blk_src_addr, 1, 1, 1);
    // part 2 -- read offset array element, to get offset into jump array, essentially inc*4
    // write to LOW-BYTE of source in next block
    make_DMA2_block(offset_array, next_blk_src_addr, 1, 1, 1);
    // part 3 -- read the address of the block to be executed
    // the address "jmp-array" is MODIFIED by the previous block
    // ONLY TWO BYTES are transfered to the next block because memory is only x1fff in size
    make_DMA2_block(jmp_array, (void*)next_blk_src_addr, 2, 2, 2);
    // part 4 -- read the new DMA block into DMA0 -- effectively changing the execution 'program counter'
    // the address "DMA_jump_blocks" is REPLACED by the transfer from the previous block
    make_DMA2_block(DMA0_jump_blocks, DMA0_addr, length_of_block, length_of_block, length_of_block);
    // part 5 -- A dummy block 
    // This is essentially a DMA "NOP" -- 
    make_DMA2_block(&inc_value, &bit_bucket, 1, 1, 1); 
    
    // === end the execution path =====
    end_execution_block_list;
    
    //=============
    // A SEPARATE list of DMA0 jump blocks to be move to DMA0 to make a 'jump'
    // Each block will point to a new execution path of DMA2 blocks
    // The forth entry is the original exectuion path
#define index_of_execution_path 3
    begin_block_list(DMA0_jump_blocks);
    //#define make_DMA0_block(src_addr, dest_addr, src_size, dest_size, cell_size)
    make_DMA0_block(DMA_jump_blocks_0, DMA2_addr, number_of_condition_0_blocks*length_of_block, length_of_block, length_of_block);
    make_DMA0_block(DMA_jump_blocks_1, DMA2_addr, number_of_condition_1_blocks*length_of_block, length_of_block, length_of_block);
    make_DMA0_block(DMA_jump_blocks_2, DMA2_addr, number_of_condition_2_blocks*length_of_block, length_of_block, length_of_block);
    // original execution path
    make_DMA0_block(DMA_blocks, DMA2_addr, number_of_blocks*length_of_block, length_of_block, length_of_block);
    // end list
    end_block_list;
    
    //=============
    // SEPARATE lists of jump blocks to be pointed to by the three conditional DMA0 jump blocks
    //Define DMA block  and store it in jump block table
    begin_block_list(DMA_jump_blocks_0);
    // One approx 2 microSec pulse on B0
    make_DMA2_block(LED_pattern2, (void*)&LATB, 32, 1, 32); 
    // jump back to main execution path
    make_DMA2_block(DMA0_jump_blocks+index_of_execution_path*length_of_block, DMA0_addr, length_of_block, length_of_block, length_of_block);
    // end list
    end_block_list;
    
    begin_block_list(DMA_jump_blocks_1);
    // Two approx 2 microSec pulse on B0
    make_DMA2_block(LED_pattern2, (void*)&LATB, 64, 1, 64); 
    // jump back to main execution path
    make_DMA2_block(DMA0_jump_blocks+index_of_execution_path*length_of_block, DMA0_addr, length_of_block, length_of_block, length_of_block);
    end_block_list;
    
    begin_block_list(DMA_jump_blocks_2);
    // One approx 1 microSec pulse on B0
    make_DMA2_block(LED_pattern1, (void*)&LATB, 16, 1, 16); 
    // jump back to main execution path
    make_DMA2_block(DMA0_jump_blocks+index_of_execution_path*length_of_block, DMA0_addr, length_of_block, length_of_block, length_of_block);
    // end list
    end_block_list;
    
    // === DMA fetch/execute setup  =====================
    // This is a general setup, without specific function until the execution block
    // list defined above is traversed, one block at a time, by DMA0 acting as a 
    // fetch-from-memory to DMA2. DMA2 then produces a series of programmed moves.
    // The system is clockless, but strictly sequential because DMA0 completion triggers
    // DMA2. DMA2 completion triggers DMA0 to load the next block to DMA2.
    // ==================================================
    // Open the all the  DMA channels, 
    // except DMA2 which will be loaded and triggered by DMA0 
	// Enable the AUTO option for all
	DmaChnOpen(0, 0, DMA_OPEN_AUTO);
    DmaChnOpen(1, 0, DMA_OPEN_AUTO);
    //DmaChnOpen(2, 0, DMA_OPEN_AUTO);
    DmaChnOpen(3, 0, DMA_OPEN_AUTO);
    
	// set the transfer parameters: source & destination address,
    //   source & destination size, number of bytes per event
    // DMA0 writes DMA control blocks from an array (DMA program) to DMA2
    // DMA1 writes to the DMA0 control block to clear a flag
    // DMA2 moves data as specified in the block array defined earlier
    // DMA3 writes to the DMA2 control block to clear a flag  
    // number_of_blocks is the number defined in the execution path.
    DmaChnSetTxfer(0, DMA_blocks, DMA2_addr, number_of_blocks*length_of_block, length_of_block, length_of_block);
    DmaChnSetTxfer(1, &clear_flag, (void*)&DCH0INT, 2, 2, 2);
    //DmaChnSetTxfer(2, LED_pattern1, (void*)&LATB, 16, 1, 1);
    DmaChnSetTxfer(3, &clear_flag, (void*)&DCH2INT, 2, 2, 2);

	// set the transfer event control: what event is to start the DMA transfers
        // In this case, DMA0 triggers DMA2 and vice-versa
        // and cell transfer done on DMA0 triggers channel 1 to clear the DMA0 flag
        // and cell transfer done on DMA2 triggers channel 3 to clear the DMA2 flag
    // net result is to bounce between DMA0 and DMA2 as fast as possible
    // BUT you need to force a transfer to start the system
	DmaChnSetEventControl(0, DMA_EV_START_IRQ(_DMA2_IRQ)); //_TIMER_2_IRQ
    DmaChnSetEventControl(1, DMA_EV_START_IRQ(_DMA0_IRQ)); 
    //DmaChnSetEventControl(2, DMA_EV_START_IRQ(_DMA0_IRQ)); 
    DmaChnSetEventControl(3, DMA_EV_START_IRQ(_DMA2_IRQ));
	
    // Set up the cell transfer done flags
    // needed to set up cell done enable bit
    DmaChnSetEvEnableFlags(0, DMA_EV_CELL_DONE);
    //DmaChnSetEvEnableFlags(2, DMA_EV_CELL_DONE);
    
    // start the channels
    DmaChnEnable(0);
    DmaChnEnable(1);
    //DmaChnEnable(2);
    DmaChnEnable(3);
    
    // force the first transfer for DMA0.
    // After this, the system just runs
    DmaChnForceTxfer(0);
    
    // set up i/o port pins
    ANSELA =0; // turn off analog on A
    ANSELB =0; // turn off analog on B
    mPORTAClearBits(BIT_0 );		//Clear A bits to ensure light is off.
    mPORTASetPinsDigitalOut(BIT_0 );    //Set A port as output
    mPORTBClearBits(BIT_0 );		//Clear B bits to ensure light is off.
    mPORTBSetPinsDigitalOut(BIT_0 );    //Set B port as output
    
    // init the display
  // NOTE that this init assumes SPI channel 1 connections
  //tft_init_hw();
  //tft_begin();
  //tft_fillScreen(ILI9340_BLACK);
  //240x320 vertical display
  //tft_setRotation(1); // Use tft_setRotation(1) for 320x240
  OpenTimer2(T2_ON | T2_SOURCE_INT | T2_PS_1_32, 0xffff);
  unsigned int t;
  char buffer[60];
	while(1){
        // 
        //t = ReadTimer2();
        //if(t>32000){
            //WriteTimer2(0);
            //sprintf(buffer, "%x %x %x", jmp_array[0],jmp_array[1],jmp_array[2]);
            //tft_printLine(1, 0, buffer, ILI9340_WHITE, ILI9340_BLACK, 2);
        //};
    };   
  } // main

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

