Therefore, each change in i could be considered as a ‘new reading’. Our real-time capabilities can be now used to incorporate an accurate time-base with the graphics plot. Listing 13-6 shows a modified version of the original program (changes shown in bold typeface) that uses a proper time-base for its horizontal axis.
In this program the variable i is still used for positioning the trace along the x axis. However, its meaning is different, now becoming a time unit of 10 ms. For this new arrangement, the main loop in the program plots continuously but only increments the value of i every 10 ms. Therefore, plotting along the horizontal axis moves by one pixel every 10 ms. This also means that the resolution of the plot is 10 ms. That is, if the signal changes in less than a 10 ms period, its change cannot be properly represented and may show as a series of vertical lines. This can be rectified by re-coding the program to use a smaller value for the delay between timer reads to suit the frequency of the incoming signal.
Listing 13-6 Graphical display of pulse-train with a real time-base - timebase.cpp.
/***************************************************** The frequency of the pulse-train being output by the voltage-controlled oscillator will change as we change the analog input voltage to the VCO circuit. The Potentiometer (POT1) on the interface board generates the input voltage to the VCO and the program reads the pulse-train being output by the VCO. This pulse-train is graphically displayed on-screen.
*****************************************************/
#include <graphics.h> #include <stdlib.h> #include <iostream.h> #include <conio.h> #include <dos.h>
#include "vco.h" #include "pctimer.h"
void main()
{
VCO Vco;
PCTimer T;
int i=0; // controls plotting in the x range int SignalLevel;
int Driver = DETECT, GraphicsMode, ErrorCode; int X, Y;
// set to graphics mode
initgraph(&Driver, &GraphicsMode, "");
//check for error codes ErrorCode = graphresult(); if (ErrorCode != grOk)
{
cout << "Graphics error: "
<<grapherrormsg(ErrorCode) << endl; cout << "Press any key to halt:" << endl; getch();
exit(1);
}
X = getmaxx(); Y = getmaxy();
rectangle(X/4-1, Y/2-76,X*3/4+1,Y/2+76); // border setviewport(X/4, Y/2-75,X/4*3,Y/2+75,1);
T.ResetTimer();
while(!kbhit())
{
SignalLevel = Vco.SignalLevel();
if(SignalLevel == 0) // low level lineto(i,100);
else // high level lineto(i,50);
if(T.ReadTimer() > 10)
{
T.ResetTimer();
i++;
}
if(i > X/2) // half screen = Viewport width
{
i = 0;
while(Vco.SignalLevel()); // wait for low level while(!Vco.SignalLevel());// wait for high level clearviewport();
moveto(0,50);
T.ResetTimer();
}
}
}
Executable File Generation
|
Required Files |
|
Listing No. |
|
Project File Contents |
|
|
pport.cpp |
|
Listing 10-8 |
|
pport.cpp |
|
|
|
|
|
|
pport.h |
|
Listing 10-7 |
|
|
|
|
vco.cpp |
Listing 10-4 |
vco.cpp |
|
vco.h |
Listing 10-1 |
|
|
|
pctimer.cpp |
Listing 13-3 |
pctimer.cpp |
|
|
pctimer.h |
Listing 13-1 |
|
|
|
|
timebase.cpp |
Listing 13-6 |
timebase.cpp |
|
The ‘zero time’ reference is set before starting to plot by calling the function ResetTimer(). This function is also used in the following if statement to periodically reset the timer every 10 ms:
if(T.ReadTimer() > 10)
{
T.ResetTimer();
i++;
}
If a 10 ms period has elapsed, the timer is reset to allow the next 10 ms period to be measured, and the index i is incremented to allow the next pixel to be plotted. Otherwise, i will remain as is and plotting will repeat at the same time position.
When the trace has reached the edge of the Viewport’s plot region (X/2; half the screen width) the program enters an if statement used to setup the screen ready for a new trace. Inside this if statement the program resets the value of i to zero, and then waits for the incoming VCO signal to switch to a high level by waiting for the VCO output to change state from logic-low to logic-high using the following combination of statements:
while(Vco.SignalLevel()); // wait for low level while(!Vco.SignalLevel());// wait for high level
This ensures the plot always starts with the same edge transition on-screen. Once the VCO signal has made the required low-to-high level transition, the screen is cleared, the cursor repositioned to the left edge, and the timer is reset to a fresh ‘zero time’ reference. Note: if interrupts are enabled, some of the pulses displayed may have wider widths due to time consumed by interrupt service routines.
Just as we used a real-time program to plot a waveform on the screen, we can timestamp data in real-time as it is acquired. We will generate a waveform using the VCO and Charge/Discharge circuitry on the interface board, digitise its analogue output using the ADC, timestamp these values, and store this data in a file.
13.8 Data Acquisition with Timestamp
The Charge/Discharge circuit on the interface board can be driven by a digital logic signal to generate an analog waveform that can be sampled to demonstrate the data acquisition process. Each data sample can be accurately time-stamped as it is acquired by using the PCTimer object in the data acquisition program.
In this section, one program will perform data acquisition, time-stamp the data as it is acquired, and store the data into a disk file for later analysis. A second program will retrieve the stored data from the disk file and process the data to determine the period of the waveform generated by the Charge/Discharge circuit.
13.8.1 The Charge/Discharge Circuit
The Charge/Discharge circuit can be driven from any digital logic signal, including one that may be generated by software using an output bit of one of the ports. However, this application uses the simple arrangement whereby the VCO drives the input of the Charge/Discharge circuit with a periodic signal as shown in Figure 13-5. The Charge/Discharge circuit has a capacitor that is charged when the VCO output becomes low and discharged when the VCO output becomes high. The analog signal output from the Charge/Discharge circuit (shown in Figure 13-6) is digitised by connecting it to the analog-to-digital converter. The program will acquire the digitised signal for more than one period and time-stamp each digitised sample. This data is then stored by writing it to a file.
+5V |
|
|
|
|
Input |
Output |
Input |
|
Output |
voltage |
pulse-train |
Charge/Discharge |
waveform |
POT |
VCO |
|
|
|
Circuit |
(To ADC) |
|
|
|
|
|
|
|
|
Interconnect Lead |
|
|
|
Figure 13-5 Connections for the VCO to drive the Charge/Discharge circuit.
|
Voltage |
|
|
|
|
(V) |
|
|
Period (T) |
|
|
|
|
|
5V |
A |
B |
C |
|
Threshold |
|
|
|
|
|
0 |
O |
|
|
|
|
|
Time |
|
|
0 |
|
|
|
|
|
Figure 13-6 Voltage waveform generated by the Charge/Discharge circuit.
42413 THE PC TIMER
13.8.2Programming Data Acquisition & Timestamp
In this section we will develop two programs. The first program (named TimeStmp.cpp) will sample the signal using the analog-to-digital converter, and read the time of sampling. These paired results will then be written to a disk file. This program will need to use the ADC class and the PCTimer class. Note: the VCO object is not used since the VCO circuit is only used as a signal generator to drive the input of the Charge/Discharge circuit.
The second program (named Period.cpp) will retrieve the stored data from the disk file and scan through the data to determine the period (T) of the waveform. It does not need to use any of the objects developed previously.
Program 1 – TimeStmp.cpp
The steps involved in this program that digitises the signal are:
1.Reset the timer.
2.While looping until sufficient time has elapsed (5 seconds suggested):
-read time and store in an array.
-read ADC and store in an array.
-wait for sampling period (10 ms. is suggested)
3.Write the data to a file.
The program for timed acquisition of data is given in Listing 13-7.
Listing 13-7 Program to acquire data with time-stamps – timestmp.cpp.
#include <iomanip.h> #include <iostream.h> #include <fstream.h> #include <stdlib.h> #include <conio.h>
#include "adc.h" #include "pctimer.h"
void main()
{
ADC Adc; PCTimer T;
double Time[500]; unsigned char Data[500]; double TempTime;
double Duration = 5000; // Acquisition period - 5000 ms. int i = 0;
const int SamplingInterval = 10; // Milliseconds
T.ResetTimer(); do
{
TempTime = T.ReadTimer(); if(TempTime > i*SamplingInterval)
{
Data[i] = Adc.ADConvert(); Time[i++] = TempTime;
}
}
while(T.ReadTimer() < Duration);
//Create, open then write data to disk file. ofstream os("timestmp.dat");
for(int j = 0; j < i; j++)
{
os << setprecision(3) << Time[j] << '\t';
os << setprecision(3) << (double) Data[j] << endl;
}
os.close(); // Close file.
}
Executable File Generation
|
Required Files |
Listing No. |
|
|
|
|
pport.cpp |
|
|
|
|
|
Listing 10-8 |
|
pport.cpp |
|
|
pport.h |
Listing 10-7 |
|
|
|
|
adc.cpp |
Listing 10-4 |
adc.cpp |
|
|
adc.h |
Listing 10-1 |
|
|
|
pctimer.cpp |
Listing 13-3 |
pctimer.cpp |
|
|
pctimer.h |
Listing 13-1 |
|
|
|
|
timestmp.cpp |
Listing 13-7 |
timestmp.cpp |
|
|
|
|
|
|
|
This program uses the ADC class and the PCTimer class. The ADC class is used to acquire the data by controlling and reading the analog-to-digital converter. The PCTimer class is used to accurately measure the time when the signal was sampled by the ADC. The two objects Adc of type ADC and T of type PCTimer have been instantiated from these two classes.
We have decided to perform data acquisition for a 5 second duration using a sampling interval of 10 ms, generating a total of 500 samples. Two arrays named Time and Data have been created to store the respective data, each having 500 elements, with the variable i used as the array subscript. The variable named