/*
 * File: ping-pong DMA0 and DMA2
 * bit outputs on RA0 and RB0
 * Bruce Land
 * Target PIC:  PIC32MX250F128B
 */
// all peripheral library includes
#include <plib.h>

#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

// DMA output tables
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 block image list
// The number of blocks in our execute list
#define number_of_blocks 4
// 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
#define length_of_block 192
// the DMA block program
unsigned char DMA_blocks[number_of_blocks * length_of_block];
// current block to update
int N=0;

// 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);
  
    //=============
    // 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
    //=============
    //Define DMA block 0 and store it in block table
    // One approx 1 microSec pulse on B0
    DmaChnOpen(2, 0, DMA_OPEN_AUTO);
    DmaChnSetTxfer(2, LED_pattern1, (void*)&LATB, 32, 1, 16);
    DmaChnSetEventControl(2, DMA_EV_START_IRQ(_DMA0_IRQ));
    DmaChnSetEvEnableFlags(2, DMA_EV_CELL_DONE);
    DmaChnEnable(2);
    // now move to array
    memcpy(DMA_blocks, &DCH2CON, length_of_block);
    
    //=============
    //Define DMA block 1 and store it
    // Two approx 1 microSec pulses on B0
    N++;
    DmaChnOpen(2, 0, DMA_OPEN_AUTO);
    DmaChnSetTxfer(2, LED_pattern1, (void*)&LATB, 32, 1, 32);
    DmaChnSetEventControl(2, DMA_EV_START_IRQ(_DMA0_IRQ));
    DmaChnSetEvEnableFlags(2, DMA_EV_CELL_DONE);
    DmaChnEnable(2);
    // base address + 
    memcpy(DMA_blocks+length_of_block*N, &DCH2CON, length_of_block);
    
    //=============
    //Define DMA block 2 and store it
    // Four approx 0.1 microSec pulses on B0
    N++;
    DmaChnOpen(2, 0, DMA_OPEN_AUTO);
    DmaChnSetTxfer(2, LED_pattern0, (void*)&LATB, 16, 1, 8);
    DmaChnSetEventControl(2, DMA_EV_START_IRQ(_DMA0_IRQ));
    DmaChnSetEvEnableFlags(2, DMA_EV_CELL_DONE);
    DmaChnEnable(2);
    // base address + 
    memcpy(DMA_blocks+length_of_block*N, &DCH2CON, length_of_block);
    
    //=============
    //Define DMA block 3 and store it
    // Trigger pulse for scope
    // One approx 0.1 microSec pulse on A0
    N++;
    DmaChnOpen(2, 0, DMA_OPEN_AUTO);
    DmaChnSetTxfer(2, LED_pattern0, (void*)&LATA, 2, 1, 2);
    DmaChnSetEventControl(2, DMA_EV_START_IRQ(_DMA0_IRQ));
    DmaChnSetEvEnableFlags(2, DMA_EV_CELL_DONE);
    DmaChnEnable(2);
    // base address + 
    memcpy(DMA_blocks+length_of_block*N, &DCH2CON, length_of_block);
    
    //=============
    // now kill DMA2 channel so that
    // it can be started later by DMA0 block 0
    DmaChnDisable(2);
    DmaChnClrEvEnableFlags(2, DMA_EV_ALL_EVNTS) ;
    
    // === DMA fetch/execute setup  =====================
    // 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  
    DmaChnSetTxfer(0, DMA_blocks, (void*)&DCH2CON, 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
    
	while(1){
        // keep moving -- nothing here to see
    };   
  } // main

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

