/********************************************************************* * * NTSC TV interface * ********************************************************************* * Bruce Land Cornell University * June 2014 * This code uses many cool ideas from * Programming 32-bit Microcontrollers in C: Exploring the PIC32 * by Lucio Di Jasio * * Uses two Compare units from one timer to do sync and video timing *~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ * * SCK1 is pin 25 * SDO1 is PPS group 2, map to RPA1 (pin 3) * SDI1 is PPS group 2, map to RPB8 (pin 17) * SYNC is PortB.0 (pin 4) */ #include #include // need for pps #include #include // Configuration Bit settings // SYSCLK = 60 MHz (8MHz Crystal/ FPLLIDIV * FPLLMUL / FPLLODIV) // PBCLK = 30 MHz // Primary Osc w/PLL (XT+,HS+,EC+PLL) // WDT OFF // Other options are don't care // 8MHZ 4MHz 60MHz 30 <-----<--- 60MHz #pragma config FNOSC = FRCPLL, POSCMOD = HS, FPLLIDIV = DIV_2, FPLLMUL = MUL_15, FPBDIV = DIV_2, FPLLODIV = DIV_1 #pragma config FWDTEN = OFF #pragma config FSOSCEN = OFF, JTAGEN = OFF // core frequency we're running at // peripherals at 40 MHz #define SYS_FREQ 60000000 // The SPI channel volatile SpiChannel spiChn = SPI_CHANNEL1 ; // the SPI channel to use volatile int spiClkDiv = 6 ;//5 MHz pixel rate => 256 points in 51.2 microSec // main screen buffer array 256 wide x 200 high = 51200 pixels // 51200/32 = 1600 integer words int screen_buffer[1600] ; volatile int *screen_buffer_addr = screen_buffer ; volatile int *screen_ptr ; // The DMA channels #define DMAchn1 1 #define DMApri0 0 // video timing #define line_cycles 1905 // 63.5 uSec at 30 MHz Fpb, prescaler=1 #define us_5_cycles 150 // 5 uSec at 30 MHz Fpb, prescaler=1 #define us_13_cycles 390 // beginning of the video DMA burst -- use for centering #define us_11_cycles 330 // beginning of the video DMA burst -- use for centering #define us_10_cycles 300 // beginning of the video DMA burst -- use for centering // video active lines -- 200 total #define image_start 20 #define image_end image_start+200 // Current line number which is modified // by a state machine in the timer2 ISR volatile int LineCount = 0 ; // ISR driven 1/60 second time volatile int time_tick_60_hz = 0 ; // video formatiing variables //One bit masks for each bit of an INT unsigned int pos[32] = {0x80000000,0x40000000,0x20000000,0x10000000,0x08000000,0x04000000,0x02000000,0x01000000, 0x800000,0x400000,0x200000,0x100000,0x080000,0x040000,0x020000,0x010000, 0x8000,0x4000,0x2000,0x1000,0x0800,0x0400,0x0200,0x0100, 0x80,0x40,0x20,0x10,0x08,0x04,0x02,0x01}; #include "ascii_characters.h" #define top 0 #define left 0 #define right 255 #define bottom 199 //=== fixed conversion macros ========================================= // for 16.16 fixed point typedef signed int fix ; #define int2fix(a) (((fix)(a))<<16) //Convert char to fix. a is a char #define fix2int(a) ((signed int)((a)>>16)) //Convert fix to int. a is an int #define float2fix(a) ((fix)((a)*65536.0)) //Convert float to fix. a is a float #define fix2float(a) (((float)(a))/65536.0) //Convert fix to float. a is an int //#define multfix(a,b) ((fix)(((( signed long long)(a))*(( signed long long)(b)))>>16)) //multiply two fixed 16.16 #define multfix(a,b) ((fix)(((( signed long)(a))*(( signed long)(b)))>>16)) //multiply two fixed 16.16 // == OC3 ISR ============================================ // VECTOR 14 is OC3 vector -- set up of ipl 3 in main // vector names from int_1xx_2xx.h // output compare 3 handler void __ISR(14, ipl3) OC3Handler(void) // 14 { // mPORTBSetBits(BIT_1); // Convert DMA to SPI control DmaChnSetEventControl(DMAchn1, DMA_EV_START_IRQ(_SPI1_TX_IRQ)); // // clear the timer interrupt flag -- name from // http://people.ece.cornell.edu/land/courses/ece4760/PIC32/Microchip_stuff/32-bit-Peripheral-Library-Guide.pdf // Table 8.2 mOC3ClearIntFlag(); // mPORTBClearBits(BIT_1); // for profiling the ISR execution time } // == Timer 2 ISR ========================================= void __ISR(_TIMER_2_VECTOR, ipl2) Timer2Handler(void) { //mPORTBSetBits(BIT_1); // for profiling the ISR execution time // update the current scanline number LineCount++ ; // start the DMA byte blaster to the screen if (LineCount >= image_start && LineCount < image_end){ // set the Chan1 DMA transfer parameters: source & destination address, // source & destination size, number of bytes per event // 32 bytes / line with 4 bytes per transfer (SPI in 32 bit mode) //screen_ptr = screen_buffer + ((LineCount - image_start)<<5) ; DmaChnSetTxfer(DMAchn1, (void*)screen_ptr, (void*)&SPI1BUF, 32, 4, 4); //32 // IRO 17 is the output compare 3 interrupt (See datasheet table 7.1) ConfigIntOC3(OC_INT_PRIOR_1 | OC_INT_ON); //3 // DmaChnSetEventControl(DMAchn1, DMA_EV_START_IRQ(17)); // // turn it on for 32 bytes DmaChnEnable(DMAchn1); // increment the image memory pointer for the next ISR pass screen_ptr += 8; // 8 32-bit words per line } else ConfigIntOC3(OC_INT_PRIOR_1 | OC_INT_OFF); //turn it off when we dont need to write to video // == SYNC state machine ==== // begin long (Vertical) synch after line 247 if (LineCount==248) {OC2R = line_cycles - us_5_cycles ;} // back to regular sync after line 250 // the first condition eliminates sync for one line (to avoid duplicate) if (LineCount==250) {OC2R = 0 ;} if (LineCount==251) {OC2R = us_5_cycles ;} // start new frame after line 262 and reset the image memory pointer and // update the frame time_tick if (LineCount==263) { LineCount = 1; // reset for the next frame screen_ptr = screen_buffer_addr; // a general propose time base //time_tick_60_hz++; } if (LineCount==image_end-30) { // a general propose time base time_tick_60_hz++; } // clear the timer interrupt flag mT2ClearIntFlag(); // mPORTBClearBits(BIT_1); // for profiling the ISR execution time } //== plot a point ========================================================= //plot one point //at x,y with color 1=white 0=black 2=invert #define word_per_line 8 void video_pt(int x, int y, char c) { //each line has 18 bytes //calculate i based upon this and x,y // the word with the pixel in it //int i = (x/32) + y*8 int i = (x >> 5) + y * word_per_line ; if (c==1) screen_buffer[i] = screen_buffer[i] | pos[x & 0x1f]; else if (c==0) screen_buffer[i] = screen_buffer[i] & ~pos[x & 0x1f]; else screen_buffer[i] = screen_buffer[i] ^ pos[x & 0x1f]; } // macro version #define video_pt_m(x,y,c) \ do{int i = (x >> 5) + y * word_per_line ; \ if (c==1) \ screen_buffer[i] = screen_buffer[i] | pos[x & 0x1f];\ else if (c==0)\ screen_buffer[i] = screen_buffer[i] & ~pos[x & 0x1f];\ else\ screen_buffer[i] = screen_buffer[i] ^ pos[x & 0x1f];\ } while(0); //============================================================================= //plot a line //at x1,y1 to x2,y2 with color 1=white 0=black 2=invert //NOTE: this function requires signed chars //Code is from David Rodgers, //"Procedural Elements of Computer Graphics",1985 void video_line(int x1, int y1, int x2, int y2, char c) { int e; signed int dx,dy,j, temp; signed char s1,s2, xchange; signed int x,y; x = x1; y = y1; //take absolute value if (x2 < x1) { dx = x1 - x2; s1 = -1; } else if (x2 == x1) { dx = 0; s1 = 0; } else { dx = x2 - x1; s1 = 1; } if (y2 < y1) { dy = y1 - y2; s2 = -1; } else if (y2 == y1) { dy = 0; s2 = 0; } else { dy = y2 - y1; s2 = 1; } xchange = 0; if (dy>dx) { temp = dx; dx = dy; dy = temp; xchange = 1; } e = ((int)dy<<1) - dx; for (j=0; j<=dx; j++) { video_pt_m(x,y,c); if (e>=0) { if (xchange==1) x = x + s1; else y = y + s2; e = e - ((int)dx<<1); } if (xchange==1) y = y + s2; else x = x + s1; e = e + ((int)dy<<1); } } //============================================================== //return the value of one point //at x,y with color 1=white 0=black 2=invert char video_state(int x, int y) { //The following construction //detects exactly one bit at the x,y location // int i = (x>>3) + ((int)y<<4) + ((int)y<<3); int i = (x >> 5) + y * word_per_line ; return (screen_buffer[i] & pos[x & 0x1f]) != 0 ; } //=============================================================== // put a big character on the screen // c is index into bitmap void video_putchar(int x, int y, int c) { int i; int y_pos; int j; for (i=0;i<7;i++) { y_pos = y + i; j = ascii[c][i] ; video_pt_m(x, y_pos, (j & 0x80)==0x80); video_pt_m(x+1, y_pos, (j & 0x40)==0x40); video_pt_m(x+2, y_pos, (j & 0x20)==0x20); video_pt_m(x+3, y_pos, (j & 0x10)==0x10); video_pt_m(x+4, y_pos, (j & 0x08)==0x08); } } //============================================================= // put a string of big characters on the screen void video_string(int x, int y, char *str) { char i; for (i=0; str[i]!=0; i++) { video_putchar(x,y,str[i]); x = x+6; } } //============================================================ // put a thick character on the screen // c is index into bitmap void video_big_putchar(int x, int y, int c) { int i; int y_pos; int j; for (i=0; i<7; i=i+1) { y_pos = y + i; j = ascii[c][i] ; video_pt_m(x, y_pos, (j & 0x80)==0x80); video_pt_m(x+1, y_pos, (j & 0x80)==0x80); video_pt_m(x+2, y_pos, (j & 0x40)==0x40); video_pt_m(x+3, y_pos, (j & 0x40)==0x40); video_pt_m(x+4, y_pos, (j & 0x20)==0x20); video_pt_m(x+5, y_pos, (j & 0x40)==0x40); video_pt_m(x+6, y_pos, (j & 0x10)==0x10); video_pt_m(x+7, y_pos, (j & 0x10)==0x10); video_pt_m(x+8, y_pos, (j & 0x08)==0x08); video_pt_m(x+9, y_pos, (j & 0x08)==0x08); } } //================================================================== // put a string of thick characters on the screen void video_bold_string(int x, int y, char *str) { char i; for (i=0; str[i]!=0; i++) { video_big_putchar(x,y,str[i]); x = x+10; } } // ======================================================================== int main(void) { // particle animation variables #define nSamp 256 int sample_number=0 ; // index for balls and for next launch int v_in[nSamp], v_disp[nSamp], v_disp_save ; fix v_fix[nSamp] ; // global time int time ; // some strings char cu1[]="Cornell ECE 4760 -- PIC32 scope"; char time_string[10] ; // Configure the device for maximum performance but do not change the PBDIV // Given the options, this function will change the flash wait states, RAM // wait state and enable prefetch cache but will not change the PBDIV. // The PBDIV value is already set via the pragma FPBDIV option above.. SYSTEMConfig(SYS_FREQ, SYS_CFG_WAIT_STATES | SYS_CFG_PCACHE); //make sure analog is cleared //ANSELA =0; //ANSELB =0; // timer 2 setup for one video line =========================================== // Set up timer2 on, interrupts, internal clock, prescalar 1, compare-value // This timer generates the time base for each horizontal video line OpenTimer2(T2_ON | T2_SOURCE_INT | T2_PS_1_1, line_cycles); // set up the timer interrupt with a priority of 2 ConfigIntTimer2(T2_INT_ON | T2_INT_PRIOR_2); mT2ClearIntFlag(); // and clear the interrupt flag // zero the system time tick which is updated in the ISR time_tick_60_hz = 0; // timer 3 setup for adc trigger ============================================== // Set up timer3 on, no interrupts, internal clock, prescalar 1, compare-value // This timer generates the time base for each ADC sample #define sample_rate 900000 //500000 #define timer_match 30000000/sample_rate OpenTimer3(T3_ON | T3_SOURCE_INT | T3_PS_1_1, timer_match); //60 is 500 kHz (works at 33 -- is 900 kHz) // set up the timer interrupt with a priority of 2 //ConfigIntTimer3(T3_INT_ON | T3_INT_PRIOR_2); //mT3ClearIntFlag(); // and clear the interrupt flag // ADC setup =================================================================== // trigger on timer3 match CloseADC10(); // ensure the ADC is off before setting the configuration // define setup parameters for OpenADC10 // Turn module on | ouput in integer | trigger mode auto | enable autosample // ADC_CLK_AUTO -- Internal counter ends sampling and starts conversion (Auto convert) // ADC_CLK_TMR -- triggered off timer3 match // ADC_AUTO_SAMPLING_ON -- Sampling begins immediately after last conversion completes; SAMP bit is automatically set // ADC_AUTO_SAMPLING_OFF -- Sampling begins with AcquireADC10(); #define PARAM1 ADC_FORMAT_INTG32 | ADC_CLK_TMR | ADC_AUTO_SAMPLING_ON //ADC_CLK_TMR ADC_CLK_AUTO // define setup parameters for OpenADC10 // ADC ref external | disable offset test | disable scan mode | do 1 sample | use single buf | alternate mode off #define PARAM2 ADC_VREF_AVDD_AVSS | ADC_OFFSET_CAL_DISABLE | ADC_SCAN_OFF | ADC_SAMPLES_PER_INT_1 | ADC_ALT_BUF_OFF | ADC_ALT_INPUT_OFF // // Define setup parameters for OpenADC10 // use peripherial bus clock | set sample time | set ADC clock divider // ADC_CONV_CLK_Tcy2 means divide CLK_PB by 2 (max speed) // ADC_SAMPLE_TIME_5 seems to work with a source resistance < 1kohm // At PB clock 30 MHz, divide by two for ADC_CONV_CLK gives 66 nSec #define PARAM3 ADC_CONV_CLK_PB | ADC_SAMPLE_TIME_5 | ADC_CONV_CLK_Tcy2 //ADC_SAMPLE_TIME_15| ADC_CONV_CLK_Tcy2 // define setup parameters for OpenADC10 // set AN0 and as analog inputs #define PARAM4 ENABLE_AN0_ANA // define setup parameters for OpenADC10 // do not assign channels to scan #define PARAM5 SKIP_SCAN_ALL // use ground as neg ref for A | use AN0 for input A // configure to sample AN4 SetChanADC10( ADC_CH0_NEG_SAMPLEA_NVREF | ADC_CH0_POS_SAMPLEA_AN0 ); // configure to sample AN0 OpenADC10( PARAM1, PARAM2, PARAM3, PARAM4, PARAM5 ); // configure ADC using the parameters defined above EnableADC10(); // Enable the ADC //=== DMA Channel 0 transfer ADC data to array v_in ================================ // Open DMA Chan1 for later use sending video to TV #define DMAchan0 0 DmaChnOpen(DMAchan0, DMApri0, DMA_OPEN_DEFAULT); DmaChnSetTxfer(DMAchan0, (void*)&ADC1BUF0, (void*)v_in, 4, 1024, 4); //256 32-bit integers DmaChnSetEventControl(DMAchan0, DMA_EV_START_IRQ(28)); // 28 is ADC done // Compuare match setup for SYNC =============================================== //Set up compare match unit to produce sync pulses // 5 uSec low // or 63.5-5 = 58.5 microSec (2340 ticks) low // pulse duration will be controlled in Timer2 ISR // #define OpenOC2( config, value1, value2) ( OC2RS = (value1), OC2R = (value2), OC2CON = (config) ) OpenOC2(OC_ON | OC_TIMER2_SRC | OC_CONTINUE_PULSE, line_cycles-1, us_5_cycles); // OC2 is PPS group 2, map to RPB5 (pin 14) PPSOutput(2, RPB5, OC2); // Compare match OC3 setup for video delay ===================================== // Compare unit for video timing, to delay DMA until end of backporch // using the interrupt flag to trigger the first DMA, // then use the ISR to change the DMA control to SPI // #define OpenOC2( config, value1, value2) ( OC2RS = (value1), OC2R = (value2), OC2CON = (config) ) // Pulse needs to be TWO cycles long OpenOC3(OC_ON | OC_TIMER2_SRC | OC_CONTINUE_PULSE , us_10_cycles+2, us_10_cycles); // // turn on ISR so that DMA can covert to SPI control ConfigIntOC3(OC_INT_PRIOR_1 | OC_INT_ON); //3 // mOC3ClearIntFlag(); // and clear the interrupt flag // SPI configure ============================================================== // SCK1 is pin 25 RB14 -- not used here // SDO1 is PPS group 2, map to RPA1 (pin 3) // SDI1 is PPS group 2, map to RPB8 (pin 17) -- not used here // SS1 input is PPS group 1, map to RPB7 (pin 16) for framing -- not used here // specify PPS group, signal, logical pin name // PPSInput (1, SS1, RPB7); PPSOutput(2, RPA1, SDO1); // set up i/o mPORTBSetPinsDigitalOut(BIT_0 | BIT_1 | BIT_5); mPORTBSetBits(BIT_0); // divide Fpb by spiClkDiv, configure the I/O ports. // 32 bit transfer SpiChnOpen(spiChn, SPI_OPEN_ON | SPI_OPEN_MODE32 | SPI_OPEN_MSTEN , spiClkDiv ) ; //=== DMA Channel 1 for TV signal ========================================== // Open DMA Chan1 for later use sending video to TV DmaChnOpen(DMAchn1, DMApri0, DMA_OPEN_DEFAULT); // === turn it all on ==================================================== // setup system wide interrupts /// INTEnableSystemMultiVectoredInt(); //=== now the application =============================================== // Draw the screen boundaries video_line(left,top+10, right,top+10, 1); // Title line video_line(left,top, right,top, 1); // top video_line(right,top, right,bottom, 1); // right video_line(right,bottom, left,bottom, 1); // bottom video_line(left,top, left,bottom ,1); // left // Draw a title //Print "CORNELL" message video_string(30,top+2,cu1); // init voltage display // for (sample_number=0; sample_number>10) ; //int2fix(ball%3);ADC1BUF0 // v_disp[sample_number] = 100-v_in[sample_number] ; // video_pt(sample_number, v_disp[sample_number],1); //} time = time_tick_60_hz ; // wait to start //while(time+600 > time_tick_60_hz){}; //time = time_tick_60_hz ; int trig_1, trig_2, max_ADC, min_ADC, mid_ADC, trig_time; // init the trigger state machine min_ADC = 500; max_ADC = 500; trig_1 = 50; trig_2 = 1000; // frequency calc int i, st, ed; char freq_str[16] ; #define wait10 asm("nop"); asm("nop");asm("nop"); asm("nop"); asm("nop"); asm("nop");asm("nop"); asm("nop");asm("nop"); asm("nop"); while(1) { //mPORTBToggleBits(BIT_1); // for profiling execution time mid_ADC = (min_ADC + max_ADC)/2 ; trig_2 = 0; trig_1 = 100000; // Always traverse the while trig_time = time_tick_60_hz ; while(!(trig_2>mid_ADC && trig_1<=mid_ADC)) { // wait for ADC done while(!(IFS1 & 0x10)) {}; // first conversion // Read the value trig_1 = ADC1BUF0 ; //IFS1<1> // wait for next one while(!(IFS1 & 0x10)) {}; // second conversion // Read the value trig_2 = ADC1BUF0 ; // do not wait longer than 1/2 second if (time_tick_60_hz > trig_time+30) break; } // start the ADC burst after the trigger is good DmaChnEnable(DMAchan0); // do not update the screen more than 60/sec // wait until the next 1/60 second and ADC transfer done #define Chn_busy 0x80 while((time_tick_60_hz <= time+1) | (DCH0CON & Chn_busy) ){}; // use ISR time-tick to update time stamp time = time_tick_60_hz ; // erase for (sample_number=1; sample_number<=nSamp/2; sample_number++){ video_line(sample_number, v_disp[sample_number], sample_number+1, v_disp[sample_number+1],0); // Convert ADC to screen coods v_disp[sample_number] = 170-(v_in[sample_number]>>3) ; } // do the last update v_disp_save = v_disp[nSamp/2+1]; v_disp[nSamp/2+1] = 170-(v_in[nSamp/2+1]>>3) ; // draw new for (sample_number=1; sample_number<=nSamp/2; sample_number++){ video_line(sample_number, v_disp[sample_number], sample_number+1, v_disp[sample_number+1],1); } // restore for the next 1/2 fame erase v_disp[nSamp/2+1] = v_disp_save ; // wait until the next 1/60 second while(time_tick_60_hz <= time+1 ){}; // use ISR time-tick to update time stamp time = time_tick_60_hz ; // erase for (sample_number=nSamp/2+1; sample_number>3) ; } // do the last update v_disp[nSamp-1] = 170-(v_in[nSamp-1]>>3) ; // draw new for (sample_number=nSamp/2+1; sample_numbermax_ADC) max_ADC=v_in[sample_number] ; // get wave period st=0; ed=0; for (sample_number=0; sample_number=mid_ADC && st==0 ) st = sample_number; if (v_in[sample_number]=mid_ADC && st>0 && sample_number>st+2 && ed==0) ed = sample_number; } // print the freq sprintf(freq_str, "FREQ = %d ", sample_rate/(ed-st)); video_string(10,bottom-10, freq_str); } //time = time_tick_60_hz ; //mPORTBClearBits(BIT_1); // for profiling execution time } // while(1) } // main