/* Program: decimate.c Revision: 3-Jan-2022 The Tektronics 3054 oscilloscope is used to write an ascii raw data file. This file has 10,000 lines of time, amplitude values separated by a comma. As written by the scope these raw data files have a .isf file name suffix. Typical lines in these files look like the following: ... -3.732e-03,-0.298 # -3.732 msec wrt trigger -298 mVolts -3.730e-03,-0.294 # -3.730 msec wrt trigger -294 mVolts ... 0.0e+00,0.006 # 0 sec wrt trigger 6 mVolts 2e-06,0.006 # 2 usec wrt trigger 6 mVolts ... 3.848e-03,0.316 # 3.848 msec wrt trigger 316 mVolts 3.850e-03,0.314 # 3.850 msec wrt trigger 314 mVolts ... There is nothing else in these files - they contain only the time offset from the trigger, amplitude pairs. There is a CRLF at the end of each line and an extra final CRLF at the end of the file. Notes: - The time offset from the trigger point is always written in an exponent format even for the value zero. - The amplitude information is always written in a floating point format except that I have also see single digits, e.g. 0, as the amplitude information. This program reads a .isf input file and writes output files for: the Parsed data, the Filtered (Acceleration)data, the Volocity data, and the Position data. - The program starts by asking the filename of the .isf input file from the scope and then either: prompting the user for the names of the 4 output files or generating the names of the 4 output files based on the name of the .isf input file. - The program then raeds the 10,000 line .isf input file and parses its data into two arrays of type double named: time_array[] and ampt_array[] - The program then decimates these 10,000 entry arrays to make two new arrays of type double each containing 1000 entries. These two new arrays are named: filtered_time[] and filtered_ampt[] It averages 10 raw amplitude values from the parsed .isf amplitude data to make one new amplitude value in the filtered_ampt[] array. It uses the time stamp from the middle of these 10 raw .isf file amplitude values as the time stamp for the new average value. This selected time stamp is written to the array filtered_time[] - Next the amplitude data in the filtered_ampt[] array is scaled to give it units of meters/sec-sqrd and written back into this array. - Next the DC Baseline of about the first 1/3 of the filtered_ampt[] array is calculated and then this DC Baseline value is subtracted from all 1000 entries in the filtered_ampt[] array. - At this point the filtered_time[] and filtered_ampt[] are written out to the Filtered data .top file along with the required header/trailer for Top-Drawer. - Next the first integration is performed to generate the Velocity data. The new Velocity data is written back into the array filtered_ampt[]. Then the average value of the Velocity data is calculated and this DC Baseline of the Velocity data is subtracted from all entries in the filtered_ampt[] array. Then the filtered_time[] and filtered_ampt[] arrays are written out as the Velocity data .top file. This file includes the required header/trailer for Top-Drawer. - Finally the second integration is performed to generate the Position data. The new Position data is written back into the array filtered_ampt[]. The filtered_time[] and filtered_ampt[] arrays are then written out as the Position data .top file. This file includes the required header/trailer for Top-Drawer. Note that the header of the .top files contains the date as obtained from the operating system. So the date on the plots is the date when this program is run - not the date when the scope was used to collect the .isf file. */ #include #include #include #include #define MAX_BUFF 100 #define MAX_LINE 80 #define MAX_ARRAY 10020 #define MAX_FILTERED_ARRAY 1020 /** The first about 1/3 of the **/ /** array is just Baseline Data. **/ #define START_INDEX_DC_BASELINE 10 /** Specify section with Baseline **/ #define LAST_INDEX_DC_BASELINE 149 /** Array indexes start at zero **/ #define CALIB_CONST 4.9035 /** 2 Volts is 1 g acceleration **/ /** Declare the functions aka provide the function prototypes **/ /** with dummy arguments **/ void clearbuf ( char working_line_buffer[] ) ; void parse_t_a ( char input__buffer[], char time_buffer[], char ampt_buffer[] ) ; main( int argc, char *argv[] ) { char filename[MAX_LINE] ; char work_buf[MAX_BUFF] ; char name_buf[MAX_BUFF] ; char name_parsed[MAX_BUFF] = "_parsed.txt" ; char name_filtered[MAX_BUFF] = "_filtered.top" ; char name_velocity[MAX_BUFF] = "_velocity.top" ; char name_position[MAX_BUFF] = "_position.top" ; char time_buf[MAX_BUFF] ; char ampt_buf[MAX_BUFF] ; FILE *fp_input_file ; FILE *fp_parsed_file ; FILE *fp_filtered_file ; FILE *fp_velocity_file ; FILE *fp_position_file ; time_t current_time; // arithmetic time type - time since the epoch char *time_string; // time_string is a pointer to a character string int parse_count = 0 ; int filter_loop_cnt = 0 ; int filter_internal_cnt = 0 ; int filter_ampt_index = 0 ; int filename_length = 0 ; double this_time ; double this_ampt ; double avg_ampt ; double time_inc ; double time_array[MAX_ARRAY] ; double ampt_array[MAX_ARRAY] ; double filtered_time[MAX_FILTERED_ARRAY] ; double filtered_ampt[MAX_FILTERED_ARRAY] ; /** Get the filenames of the: **/ /** **/ /** the input .isf file from the scope, **/ /** the Parser Check output file, **/ /** the Filtered output file, **/ /** the Velocity output file, and **/ /** the Position output file. **/ /** **/ /** This program allows two different ways to specify these **/ /** 5 different filenames: **/ /** **/ /** If no filename is specified on the command line that **/ /** starts this program then this program will prompt the **/ /** user for these 5 filenames one at a time. **/ /** **/ /** If the filename of the input .isf from the scope is **/ /** specified on the commane line that starts this program **/ /** then that filename will be used for the input .isf file **/ /** and the core of that filename will be used to generate **/ /** the filenames for the 4 output files in the following **/ /** way: **/ /** **/ /** xyz.isf input filename from the command line **/ /** xyz_parse.txt generated parse check filename **/ /** xyz_filtered.top generated filtered data filename **/ /** xyz_velocity.top generated velocity data filename **/ /** xyz_position.top generated position data filename **/ /** **/ if ( argc == 1) /** Prompt the user for 5 filenames **/ { printf("Enter the filename of the .isf file to READ: "); gets(filename); if ((fp_input_file = fopen(filename, "r")) == NULL) { printf("\nERROR: Can't open input .isf file %s\n", filename); exit (1); } printf("Enter the filename to receive the PARSE CHECK: "); gets(filename); if ((fp_parsed_file = fopen(filename, "w")) == NULL) { printf("\nERROR: Can't open PARSED output file %s\n", filename); exit (2); } printf("Enter the filename to receive the FILTERED data: "); gets(filename); if ((fp_filtered_file = fopen(filename, "w")) == NULL) { printf("\nERROR: Can't open FILTERED output file %s\n", filename); exit (3); } printf("Enter the filename to receive the VELOCITY data: "); gets(filename); if ((fp_velocity_file = fopen(filename, "w")) == NULL) { printf("\nERROR: Can't open VELOCITY output file %s\n", filename); exit (4); } printf("Enter the filename to receive the POSITION data: "); gets(filename); if ((fp_position_file = fopen(filename, "w")) == NULL) { printf("\nERROR: Can't open POSITION output file %s\n", filename); exit (4); } } else if (argc == 2) /** Given 1 filename - Generate 4 filenames **/ { if( (fp_input_file = fopen( argv[1], "r")) == NULL ) { printf( "\nERROR: Can't open input .isf file %s\n", argv[1] ); exit (1); } /** Now Generate the 4 other filenames and open them **/ strcpy( name_buf, argv[1] ); filename_length = strlen( name_buf ); name_buf[filename_length -4] = '\0' ; /** the terminating null '\0' in name_buf **/ /** has been moved so that name_buf now **/ /** contains just the name of the input **/ /** scope file minus its .isf sufix **/ strcpy( filename, name_buf ); strcat( filename, name_parsed ); if(( fp_parsed_file = fopen( filename, "w" )) == NULL ) { printf("\nERROR: Can't open output PARSED file %s\n", filename); exit (2); } strcpy( filename, name_buf ); strcat( filename, name_filtered ); if(( fp_filtered_file = fopen( filename, "w" )) == NULL ) { printf("\nERROR: Can't open output FILTERED file %s\n", filename); exit (2); } strcpy( filename, name_buf ); strcat( filename, name_velocity ); if(( fp_velocity_file = fopen( filename, "w" )) == NULL ) { printf("\nERROR: Can't open output VELOCITY file %s\n", filename); exit (2); } strcpy( filename, name_buf ); strcat( filename, name_position ); if(( fp_position_file = fopen( filename, "w" )) == NULL ) { printf("\nERROR: Can't open output POSITION file %s\n", filename); exit (2); } } else { printf( "\n\n\n " ); printf( "ERROR an illegal number of arguments was present \n "); printf( "on the command line. The user should either provide \n "); printf( "no arguments on the command line in which case this \n "); printf( "program will prompted the user for all required \n "); printf( "arguments or the user should supply just one \n "); printf( "argument on the command line, the name of the input \n "); printf( "file, in which case this program will generate the \n "); printf( "other 4 required filename arguments. \n\n\n "); exit (1); } /** Now get the system time and store it in a string **/ /** We need the system time to make the header for **/ /** the .top output files that go to Top-Drawer. **/ /* The time(NULL) function returns current system time as a time_t value */ /* The c_time() function converts a time_t value to a character string */ current_time = time(NULL); time_string = ctime(¤t_time); /* ctime() adds a terminating \n newline character and */ /* I need to pull it off of this string for use later on */ /* Cover up the \n with a \0 */ filename_length = strlen( time_string ); time_string[filename_length -1] = '\0' ; printf("\n\n Current time is: %s \n\n", time_string); /** We now have all the information that we need to start the actual work **/ /** One at a time, read a whole line into the workbuf and then call **/ /** the parse_time_amplitude function. This function puts the TIME **/ /** and AMPLITUDE information into separate character string arrays so **/ /** that the sscanf funtion can be used to extract this information **/ /** into normal double floating point variables. **/ parse_count = 0 ; clearbuf( work_buf ) ; fgets( work_buf, MAX_LINE, fp_input_file ); while( work_buf[0] != '\0' && work_buf[0] != '\r' ) /** note \r **/ { parse_t_a( work_buf, time_buf, ampt_buf ) ; sscanf( time_buf, "%le", &this_time ); fprintf( fp_parsed_file, "%12.7f ", this_time ); sscanf( ampt_buf, "%lf", &this_ampt ); fprintf( fp_parsed_file, "%12.7f\n", this_ampt ); time_array[parse_count] = this_time ; ampt_array[parse_count] = this_ampt ; ++parse_count ; clearbuf( work_buf ); fgets( work_buf, MAX_LINE, fp_input_file ); } fclose( fp_input_file ) ; fclose( fp_parsed_file ) ; /** The TIME and AMPLITUDE data is now in separate arrays of **/ /** type double so we can work with it. These two arrays **/ /** are named: time_array[] and ampt_array[] **/ /** The first filter is just a decimation by a factor of ten. **/ /** For each group of ten input values: pick the middle TIME **/ /** stamp and average the ten AMPLITUDE values. **/ /** **/ /** For the 10,000 input pairs in the .isf file from the Tek **/ /** TDS 3054 scope this should give us 1,000 TIME, AMPLITUDE **/ /** "filtered" pairs. **/ /** **/ /** Write these 1000 TIME & AMPLIDUTE values into a pair of **/ /** arrays of type double named: filtered_time[] and **/ /** filtered_ampt[] **/ filter_loop_cnt = 0 ; filter_internal_cnt = 0 ; while( filter_loop_cnt < 1000 ) { filter_internal_cnt = 0 ; avg_ampt = 0.0 ; while( filter_internal_cnt < 10 ) { filter_ampt_index = (10 * filter_loop_cnt) + filter_internal_cnt ; avg_ampt = avg_ampt + ampt_array[filter_ampt_index] ; ++filter_internal_cnt ; } this_time = time_array[ (10 * filter_loop_cnt) + 5] ; avg_ampt = avg_ampt / 10.0 ; filtered_time[filter_loop_cnt] = this_time ; filtered_ampt[filter_loop_cnt] = avg_ampt ; ++filter_loop_cnt ; } /** Now Scale the Acceleration data so that it is in units of **/ /** meters / sec squared **/ /** **/ /** Recall that: **/ /** **/ /** the output of the 3 Axis Acellerometer Box is 2 Volts/g **/ /** **/ /** one g is about 9.807 meters/second-squared **/ /** **/ /** So 1 Volt is about 4.9035 meters/second-squared **/ /** **/ /** The 1 Volt = 4.9035 meters/sec-squared calibration constant **/ /** is defined in the header of this file. **/ filter_loop_cnt = 0 ; while( filter_loop_cnt < 1000 ) { filtered_ampt[filter_loop_cnt] = filtered_ampt[filter_loop_cnt] * CALIB_CONST ; ++filter_loop_cnt ; } /** Now remove any DC offset in the Acceleration data. Do this **/ /** by calculating the average value of about the first third **/ /** of the 1000 entry filtered_ampt array. Here we assume **/ /** that the trigger point on the scope will be set for about **/ /** 40% so that all of the Amplitude data in about the first **/ /** one third of this array will just be baseline values. **/ /** **/ /** For now I will skip the first 10 entries (0:9) and **/ /** calculate the average DC baseline based on the filtered_ampt **/ /** array entries 11:300 (10:299) i.e. 290 entries. **/ /** **/ avg_ampt = 0.0 ; filter_loop_cnt = START_INDEX_DC_BASELINE ; while( filter_loop_cnt <= LAST_INDEX_DC_BASELINE ) { avg_ampt = avg_ampt + filtered_ampt[filter_loop_cnt] ; ++filter_loop_cnt ; } avg_ampt = avg_ampt / ( LAST_INDEX_DC_BASELINE - START_INDEX_DC_BASELINE + 1) ; /** The variable avg_ampt now holds the DC Baseline of **/ /** the array filtered_ampt[] This is based on entries **/ /** START_INDEX_DC_BASELINE through LAST_INDEX_DC_BASELINE **/ /** in that array. **/ /** **/ /** Now subtract this DC Baseline from all entries in **/ /** the array filtered_ampt[] **/ filter_loop_cnt = 0 ; while( filter_loop_cnt < 1000 ) { filtered_ampt[filter_loop_cnt] = filtered_ampt[filter_loop_cnt] - avg_ampt ; ++filter_loop_cnt ; } /** For now the: Decimated by 10, Scaled for Acceleration **/ /** in units of meters/sec-sqrd, and DC Baseline removed data **/ /** is considered the "filtered" Acceleration data. **/ /** **/ /** It is in the arrays: filtered_time[] & filtered_ampt[] **/ /** **/ /** Now write out the FILTERED Data file with a Top Drawer header/tailer **/ fprintf( fp_filtered_file, " SET DEVICE POSTSCR \n" ); fprintf( fp_filtered_file, " TITLE TOP '1000 Points Filtered Acceleration' \n" ); fprintf( fp_filtered_file, " MORE ' %s'\n", time_string ); fprintf( fp_filtered_file, " TITLE LEFT 'Acceleration - meters/sec**2' \n" ); fprintf( fp_filtered_file, " TITLE Bottom 'Time wrt Trigger - Seconds' \n" ); fprintf( fp_filtered_file, " SET ORDER X Y \n" ); filter_loop_cnt = 0 ; while( filter_loop_cnt < 1000 ) { fprintf( fp_filtered_file, "%12.7f ", filtered_time[filter_loop_cnt] ); fprintf( fp_filtered_file, "%12.7f\n", filtered_ampt[filter_loop_cnt] ); ++filter_loop_cnt ; } fprintf( fp_filtered_file, " JOIN 1 \n" ); fprintf( fp_filtered_file, " PLOT \n" ); fprintf( fp_filtered_file, " EXIT \n" ); fclose( fp_filtered_file ) ; /** **/ /** Now do the integration to calculate the VELOCITY **/ /** as a function of time. **/ /** **/ /** We will just re-use the array filtered_ampt[] **/ /** to hold the calculated Velocity values. **/ /** Note that we have to process index 0 of this **/ /** array differently. **/ /** There is no change to the data in the **/ /** filtered_time[] array. **/ time_inc = filtered_time[503] - filtered_time[502] ; filtered_ampt[0] = filtered_ampt[0] * time_inc ; filter_loop_cnt = 1 ; while( filter_loop_cnt < 1000 ) { filtered_ampt[filter_loop_cnt] = ( filtered_ampt[filter_loop_cnt] * time_inc ) + filtered_ampt[filter_loop_cnt - 1] ; ++filter_loop_cnt ; } /** Now remove any DC Baseline offset from the Velocity **/ /** data that is stored in the array filtered_ampt[] **/ avg_ampt = 0.0 ; filter_loop_cnt = START_INDEX_DC_BASELINE ; while( filter_loop_cnt <= LAST_INDEX_DC_BASELINE ) { avg_ampt = avg_ampt + filtered_ampt[filter_loop_cnt] ; ++filter_loop_cnt ; } avg_ampt = avg_ampt / ( LAST_INDEX_DC_BASELINE - START_INDEX_DC_BASELINE + 1) ; /** The variable avg_ampt now holds the DC Baseline of **/ /** the array filtered_ampt[] This is based on entries **/ /** START_INDEX_DC_BASELINE through LAST_INDEX_DC_BASELINE **/ /** in that array. **/ /** **/ /** Now subtract this DC Baseline from all entries in **/ /** the array filtered_ampt[] **/ filter_loop_cnt = 0 ; while( filter_loop_cnt < 1000 ) { filtered_ampt[filter_loop_cnt] = filtered_ampt[filter_loop_cnt] - avg_ampt ; ++filter_loop_cnt ; } /** Now write out the VELOCITY Data file with a Top Drawer header/tailer **/ fprintf( fp_velocity_file, " SET DEVICE POSTSCR \n" ); fprintf( fp_velocity_file, " TITLE TOP '1000 Points Velocity' \n" ); fprintf( fp_velocity_file, " MORE ' %s'\n", time_string ); fprintf( fp_velocity_file, " TITLE LEFT 'Velocity in meter/sec' \n" ); fprintf( fp_velocity_file, " TITLE Bottom 'Time wrt Trigger in Seconds' \n" ); fprintf( fp_velocity_file, " SET ORDER X Y \n" ); filter_loop_cnt = 0 ; while( filter_loop_cnt < 1000 ) { fprintf( fp_velocity_file, "%12.7f ", filtered_time[filter_loop_cnt] ); fprintf( fp_velocity_file, "%15.10f\n", filtered_ampt[filter_loop_cnt] ); ++filter_loop_cnt ; } fprintf( fp_velocity_file, " JOIN 1 \n" ); fprintf( fp_velocity_file, " PLOT \n" ); fprintf( fp_velocity_file, " EXIT \n" ); fclose( fp_velocity_file ) ; /** **/ /** Now do the integration to calculate the POSITION **/ /** as a function of time. **/ /** **/ /** We will just re-use the array filtered_ampt[] **/ /** to hold the calculated Position values. **/ /** Note that we have to process index 0 of this **/ /** array differently. **/ /** There is no change to the data in the **/ /** filtered_time[] array. **/ filtered_ampt[0] = filtered_ampt[0] * time_inc ; filter_loop_cnt = 1 ; while( filter_loop_cnt < 1000 ) { filtered_ampt[filter_loop_cnt] = ( filtered_ampt[filter_loop_cnt] * time_inc ) + filtered_ampt[filter_loop_cnt - 1] ; ++filter_loop_cnt ; } /** Now write out the POSITION Data file with a Top Drawer header/tailer **/ fprintf( fp_position_file, " SET DEVICE POSTSCR \n" ); fprintf( fp_position_file, " TITLE TOP '1000 Points POSITION' \n" ); fprintf( fp_position_file, " MORE ' %s'\n", time_string ); fprintf( fp_position_file, " TITLE LEFT 'Position in meters' \n" ); fprintf( fp_position_file, " TITLE Bottom 'Time wrt Trigger in Seconds' \n" ); fprintf( fp_position_file, " SET ORDER X Y \n" ); filter_loop_cnt = 0 ; while( filter_loop_cnt < 1000 ) { fprintf( fp_position_file, "%12.7f ", filtered_time[filter_loop_cnt] ); fprintf( fp_position_file, "%17.12f\n", filtered_ampt[filter_loop_cnt] ); ++filter_loop_cnt ; } fprintf( fp_position_file, " JOIN 1 \n" ); fprintf( fp_position_file, " PLOT \n" ); fprintf( fp_position_file, " EXIT \n" ); fclose( fp_position_file ) ; return (1); } /** **/ /** End of the main program - now for the Function Definitions **/ /** **/ /* This is the CLEARBUF function. It is used to set all elements of the workbuf to '\0'. Note that because the arguments passed to this function are the names of two arrays, this function works on the actual arrays, not on temporary local copies of the arrays */ void clearbuf( char linebuf[] ) /* function definition */ { int local_index = 0; while( local_index < MAX_LINE ) { linebuf[local_index] = '\0'; ++local_index; } return; } /* This is the PARSE_TIME_AMPLITUDE function. For the TIME information this routine just pulls characters one at a time from the in_line[] buffer until it finds the "," comma character OR reaches the the end of line \0 null. It will only reach the end of line null if there is an error in the .isf file. It puts all of the characters up to but not including the "," comma character into the time_out[] buffer. It then appends a \0 null to the end of the time_out[] buffer so that this buffer is ready to be processed as a character string array. Next it skips over the "," comma and then puts out all of the remaining characters from the in_line[] buff up to but not including the terminating \0 null and puts all of these characters into the ampt_out[] buffer. Finally it appends a \0 null to the end of the ampt_out[] buffer so that this buffer is ready to be processed as a character string array. Function Definition of parse_t_a */ void parse_t_a ( char in_line[], char time_out[], char ampt_out[] ) { int read_index = 0 ; int write_index = 0 ; /** Extract the TIME information and make it a string **/ while( in_line[read_index] != '\0' && in_line[read_index] != ',' ) { time_out[write_index] = in_line[read_index] ; ++read_index ; ++write_index ; } time_out[write_index] = '\0'; /** could also be \n **/ time_out[write_index + 1] = '\0'; /** Extract the AMPLITUDE information and make it a string **/ ++read_index ; /** skip the comma **/ write_index = 0 ; /** reset the write_index **/ while( in_line[read_index] != '\0' ) { ampt_out[write_index] = in_line[read_index] ; ++read_index ; ++write_index ; } ampt_out[read_index] = '\0'; /** could also be \n **/ ampt_out[read_index + 1] = '\0'; return; }