// Tab Spacing = 4 /********************************************************************** * * * FILE NAME: profiler.c * * * * DESCRIPTION: This file contains all the functions needed to * * profile bottlenecks in existing programs on the MC16C * * * * DETAILS: * * The *addon* program can simply be included with an * * existing program to relay program execution points back * * to the user via serial and LCD. * * * * NOTES: * * The use of this profiler package in your program could * * not be simpler. Simply include the function * * profileInitialize in your main initialization function. * * This will initialize the profiler timer and start the * * data accumulation process. * * * * After an initial delay, everytime the profile interrupt * * fires, the contents of the PC prior to the interrupt is * * retrieved from the stack and checked against values already * * stored in an array for spacial locality. If found, that * * address' counter is incremented. If not found, the new * * address is added to the array. * * * * This continues until the pushbutton below the RED led is * * pressed. Once pressed, ALL timers and interrupts are * * stopped and the accumulated data is displayed on the LCD * * panel. The data is then sent out to external machines via * * serial communication on one of the system pins. * * * * Once the data is transmitted, the data is cleared and it * * starts to accumulate again. * * * * profileInitialize calls a splashscreen function and * * initializes the timer. Everytime the timer goes off, * * profileIRQ is called. After an initial delay (in which the * * profileIRQ function simply returns), data accumulation * * occurs when profileIRQ calls _profileUpdate which stores * * the data in the global array. profileIRQ then checks to * * see if the RED led button has been pressed. If so, it * * calls _profileSort, which sorts the data in descending * * frequency order, followed by _profileReport, which displays * * the data on the LCD, then _profileTransmit is called, which * * sends the data out serially. Finally, the data structure * * is cleared and interrupts are re-enabled, and the program * * continues on it's merry way. * ***********************************************************************/ /********************************************************************* Revision History 1.0 [no date] Initial Version 1.1 2006-11-16 Cleaned up and added extensive comments. *********************************************************************/ /* Include the required header files */ #include #include #include #include "skp_bsp.h" // include SKP board support package #include "profiler.h" // include the profiler library definitions #pragma INTERRUPT profileIRQ // Declare the local global variables. static PROFILER Profile; unsigned int profAddr; /***************************************************************************** Name: _profileTxChar Parameters: None Returns: None Desc: Your one stop 9600 baud, one char pin toggling wonder! This function takes a char as an input and toggles the appropriate pin for the start bit, 8 data bits, NO parity bit, and 1 stop bit to serially send the specified byte. NOTE: This is typically a BAD way to do any sort of timed communcation on a system with multiple interrupts firing all the time. However, the profiler disables all interrupts until all the information has been displayed and transmitted. *****************************************************************************/ void _profileTxChar ( char ch ) { int state = 0; int idx, bd; char chk, buff; for ( idx = -1; idx < 9; idx++ ) { switch ( idx ) { case -1: // START BIT PROFILER_PORT_PIN_OUT = 0; chk = 0x01; break; default: // 8 DATA BITS if ( ch & chk ) PROFILER_PORT_PIN_OUT = 0; else PROFILER_PORT_PIN_OUT = 1; chk <<= 1; break; case 8: // STOP BIT PROFILER_PORT_PIN_OUT = 1; break; } // HOLD THE BIT FOR A SET AMOUNT. 9600 baud ~= 104 uSec // This application set the clock to 20MHz. 1/20,000,000 = 0.00000005 // 1/9600/0.00000005 = 2083 cycles. bd = 75; while ( bd-- > 0 ); } PROFILER_PORT_PIN_OUT = 1; // MAKE SURE THE LINE IS HIGH, READY FOR THE NEXT TRANSMISSION. } /* Function: _profileTxChar */ /***************************************************************************** Name: _profileSendBuffer Parameters: buffer to transmit Returns: None Desc: This function is merely a wrapper function that will call profileTxChar and send every character in the incoming buffer. *****************************************************************************/ static void _profileSendBuffer ( char *buffer ) { char *cp; int sLen, idx; if ( buffer != NULL ) { cp = buffer; sLen = strlen ( buffer ); for ( idx = 0; idx < sLen; idx++, cp++ ) { _profileTxChar ( *cp ); } } } /* Function: _profileSendBuffer */ /***************************************************************************** Name: _profileTransmit Parameters: None Returns: None Desc: Once the RED button is pressed, all interrupts stop and the data must be sent to the user. This function formats the strings to send out via an i/o port. *****************************************************************************/ static void _profileTransmit ( void ) { char buffer[80]; int idx, state = 0; int done = 0; unsigned long delay; idx = 0; while ( !done ) { if ( state == 0 ) { sprintf ( buffer, "Profiler ran for %6.2fs\n", ( (float)Profile.Samples / (float)1000.0 ) ); _profileSendBuffer ( buffer ); sprintf ( buffer, " Collected %4d Address Points\n", Profile.Points ); _profileSendBuffer ( buffer ); sprintf ( buffer, " Pnt Address Percent\n" ); _profileSendBuffer ( buffer ); state++; } else { sprintf ( buffer, " %2d 0x%04X %5.2f", Profile.Point[idx].addr, ( (float)Profile.Point[idx].cnt / (float)Profile.Samples ) ); _profileSendBuffer ( buffer ); if ( ++idx > Profile.Points ) done = 1; sprintf ( buffer, " %2d 0x%04X %5.2f", Profile.Point[idx].addr, ( (float)Profile.Point[idx].cnt / (float)Profile.Samples ) ); _profileSendBuffer ( buffer ); if ( ++idx > Profile.Points ) done = 1; } } strcpy ( buffer, "\nClearing Profile Data.\n" ); _profileSendBuffer ( buffer ); } /* Function: _profileTransmit */ /***************************************************************************** Name: _profileReport Parameters: None Returns: None Desc: This function will display the accumulated profiler statistics onto the LCD screen. It will show the data point and percent sampled on the first line, then the actual address (lower 2 bytes of the actual address stored in the PC). *****************************************************************************/ void _profileReport ( void ) { char buffer[16]; int idx, state = 0; int done = 0; unsigned long delay; int t1, t2; // CLEAR THE LCD SCREEN DisplayString(LCD_LINE1," "); DisplayString(LCD_LINE2," "); // DISPLAY ALL THE INFORMATION TILL DONE. idx = 0; while ( !done ) { if ( state == 0 ) // DISPLAY HOW LONG THE SAMPLING RAN AND HOW MANY // DISTINCT ADDRESS POINTS CAPTURED. { sprintf ( buffer, "Ran %3ds", ( Profile.Samples / 1000 ) ); DisplayString(LCD_LINE1,buffer); sprintf ( buffer, "%4d Pts", Profile.Points ); DisplayString(LCD_LINE2,buffer); state++; } else { // DISPLAY THE FREQUENCY AND ADDRESS OF EACH DATA POINT. delay = ( ( Profile.Point[idx].cnt * 1000 ) / Profile.Samples ); sprintf ( buffer, "%2d %2d.%d%%", (idx + 1 ), ( delay / 10 ), ( ( delay % 100 ) / 100 ) ); DisplayString(LCD_LINE1,buffer); sprintf ( buffer, " 0x%04X", Profile.Point[idx].addr ); DisplayString(LCD_LINE2,buffer); if ( ++idx >= Profile.Points ) done = 1; } // DELAY A BIT FOR THE USER TO READ (OR WRITE DOWN). delay = 1000000; while ( --delay > 0 ); delay = 1000000; while ( --delay > 0 ); // CLEAR THE LCD SCREEN DisplayString(LCD_LINE1," "); DisplayString(LCD_LINE2," "); } } /* Function: _profileReport */ /***************************************************************************** Name: profileSort Parameters: None Returns: None Desc: This function uses bubblesort algorithms to sort the address entries by decreasing frequency, placing the most freqently found addresses at the beginning of the profile. *****************************************************************************/ static void _profileSort ( void ) { int i, j, k; unsigned long swap; // SORT THE ADDRESS MARKDERS BY DESCENDING OCCURANCE for ( i = 0; i < ( Profile.Points - 1 ); i++ ) { for ( j = ( i + 1 ); j < Profile.Points; j++ ) { if ( Profile.Point[i].cnt < Profile.Point[j].cnt ) { swap = Profile.Point[i].cnt; Profile.Point[i].cnt = Profile.Point[j].cnt; Profile.Point[j].cnt = swap; swap = Profile.Point[i].addr; Profile.Point[i].addr = Profile.Point[j].addr; Profile.Point[j].addr = swap; } } } /* With any sampling package, random points will be observed that have no statistical bearing on the relevent information. Those points with low frequencies are removed to make room for addresses that begin to occur more frequently. */ if ( Profile.Points == PROF_MAX_PTS ) { // REMOVE THE ENTRIES THAT HAVE LOW OCCURANCE swap = (int)( Profile.Samples * 0.005 ); for ( i = ( (int)Profile.Points - 1 ); i >= 0; i-- ) { if ( Profile.Point[i].cnt < swap ) Profile.Points--; else break; } } } /* Function: profileSort */ /***************************************************************************** Name: profileUpdate Parameters: None Returns: None Desc: This function does the accounting on the recorded IRQ return address. It will count the total number of samples, then compare the new address to *****************************************************************************/ static void _profileUpdate ( void ) { unsigned int idx; long tmp; ++Profile.Samples; // Got a new sample. for ( idx = 0; idx < Profile.Points; idx++ ) // search existing samples to { // see if the new address is tmp = profAddr - Profile.Point[idx].addr; // within tolerance of any previous samples. if ( abs ( tmp ) < PROF_MAX_TOL ) // If the new address is within tolerance of break; // an existing entry, the loop is stopped and } // that entrey's information is updated. if ( idx < PROF_MAX_PTS ) { if ( idx == Profile.Points ) // The new address did not fall into the range { // of any existing addresses. A new entry is needed for Profile.Point[idx].addr = profAddr; // the new adress. Profile.Points++; // This also assumes that there is room in the address } // structure for new address points. Profile.Point[idx].cnt++; // Either way, increment the frequency counter of the address. } // EVERY SO OFTEN, RESORT THE ADDRESSES TO MAKE ADDING NEW DATA MORE EFFICIENT. if ( ++Profile.Sort > 10000 ) { Profile.Sort = 0; _profileSort (); } } /* Function: profileUpdate */ /***************************************************************************** Name: profileIRQ Parameters: None Returns: None Desc: Main interrupt service routine. This is where the meat of the profiler does it's job. Upon entering the function, the contents of the stack pointer +14 is stored into a global variable. This is because the contents of the PC (program counter) and registers are pushed onto the stack at the beginning of a interrupt service routine. The value stored in profAddr is the lower order bytes of the PC prior to the interrupt. This is the value we want to record. *****************************************************************************/ void profileIRQ ( void ) { // BEFORE AN INTERRUPT CALL, THE PC IS PUSHED ON TO THE STACK // ONCE INSIDE THE IRQ, THE REGISTERS ARE ALSO PUSHED ONTO THE STACK. // SIMPLY RETRIEVING THE VALUE STORED AT sp+14 GIVES THE LOWER ORDER // BYTES OF THE PC. THIS IS THE VALUE THAT WE WILL BE KEEPING TRACK OF. #pragma ASM mov.w 14[sp],_profAddr #pragma ENDASM // SET A PIN TO HIGH, SO THAT WE CAN MEASURE HOW MUCH TIME IN THE IRQ WE ARE SPENDING. // MAKE SURE TO CLEAR AT THE END OF THE IRQ. PROFILER_PORT_PIN_IRQ = 1; // SINCE THE FIRST FEW SECONDS OF ANY PROGRAM IS USUALLY JUST INITIALIZATION // CODE AND IS NEVER USED AGAIN IN THE PROGRAM, DELAY THE DATA ACCUMULATION. if ( Profile.Delay < PROF_DELAY ) { if ( ++Profile.Delay < PROF_DELAY ) return; } // ACCUMULATE PROFILE DATA. _profileUpdate (); /* Report???? */ if (!S1) // S1 pressed? IF IT IS, GO INTO REPORT MODE // DISABLE IRQ's AND DISPLAY/SEND DATA. { DISABLE_IRQ /* Sort the results in descending order */ _profileSort(); /* Display the results on the LCD */ _profileReport (); /* Transmit the Results via serial communication */ _profileTransmit (); /* Clear the results for the next batch */ memset ( &Profile, 0x00, sizeof ( Profile ) ); ENABLE_IRQ } // CLEARING THE IRQ PIN. PROFILER_PORT_PIN_IRQ = 0; }; /* Function: profileIRQ */ /***************************************************************************** Name: _profileTimerStart Parameters: None Returns: None Desc: This function will initialize any registers/timers/ports that the profiler may need to use. NOTE: It is best to use a non-standard timer/port for this application, to minimize interference with the 'real' program. *****************************************************************************/ static void _profileTimerStart ( void ) { /* Timer initialization */ /* Configure Timer B0 - 1ms (millisecond) counter */ PROFILER_TIMER_REG = 0x80; // Timer mode, f32, no pulse output PROFILER_TIMER = (int)( (f1_CLK_SPEED/32) * 0.001 - 1) ; // (f1/32) x 1ms - 1 /* Change interrupt priority levels to enable maskable interrupts */ DISABLE_IRQ // disable irqs before setting irq registers - macro defined in skp_bsp.h PROFILER_TIMER_IC = 2; // Set the timer's interrupt priority to level 2 ENABLE_IRQ // enable interrupts macro defined in skp_bsp.h /* Start timers */ PROFILER_TIMER_START = 1; // Start timer B0. // NOTE: the entry for the specified vector (B0) needs to be changed in // the Variable Vector section of the *.inc or the // C Start up file for M16C/Tiny SKPs to indicate which function // will be called upon expiration of the timer. In this case, it // is profileIRQ. (see function above this one). /* Serial Communication initialization */ PROFILER_PORT_DIR |= 0x0C; // Set the Direction of the Output pins. Outputs (obviously). PROFILER_PORT_PIN_OUT = 1; // Need to set the serial pin high for RS232 communications. PROFILER_PORT_PIN_IRQ = 0; // The IRQ pin is just for analysis purposes for finding out // how much time the program spends in the profiling part. }; /* Function: _profileTimerStart */ /***************************************************************************** Name: _profileSplashScreen Parameters: None Returns: None Desc: This function will display an 'about' information display on the LCD and rapidly blink the LED's. Simply here as a way to tell if the profile is compiled into the main program and initialized. *****************************************************************************/ static void _profileSplashScreen ( void ) { unsigned long delay, d2, st = 0; /* Display Profiler Splash Screen for 2 seconds */ DisplayString(LCD_LINE1," *KLH* "); DisplayString(LCD_LINE2,"Profiler"); /* Blink the LED's to gather some attention */ for ( delay = 0; delay < 15; delay++ ) { if ( st == 0 ) // Toggle the LEDS { RED_LED = LED_ON; YLW_LED = LED_ON; GRN_LED = LED_ON; } else { RED_LED = LED_OFF; YLW_LED = LED_OFF; GRN_LED = LED_OFF; } st ^= 1; for ( d2 = 0; d2 < 60000; d2++ ) // Delay for just a bit. d2++; } /* Turn them back off */ RED_LED = LED_OFF; YLW_LED = LED_OFF; GRN_LED = LED_OFF; /* Clear the screen */ DisplayString(LCD_LINE1," "); DisplayString(LCD_LINE2," "); }; /* Function: _profileSplashScreen */ /***************************************************************************** Name: profileInitialize Parameters: None Returns: None Desc: This is the function that you must call in your main program to initialize the Profiler timers. A handy splash screen on the LCD lets you know it is installed (and hopefully working correctly) *****************************************************************************/ void profileInitialize ( void ) { // Clear the global data structure. memset ( &Profile, 0x00, sizeof ( Profile ) ); // Let the User know, via the LCD, that the profiler is included in the program // and is being initialized. _profileSplashScreen (); // Start yer engines! _profileTimerStart (); }; /* Function: profileInitialize */