/* * 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 //////////////////////////////////// // 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 ======================================================