I want to reconstruct oscilloscope data imported into a spreadsheet directly from my Velleman PCSU200 oscilloscope.

The data of primary interest are the frequency and amplitude, I'm not interested in any other data.

The information in the spreadsheet looks as per the image (snippet) below: (the resulting data is 4000+ rows so too large to post here - I can make it available for download if required.

My question is, how can I calculate the frequency and amplitude from the given data? The amplitude setting on the scope will always be fixed for the purpose of these measurements if that helps, however the timebase will vary.

Imported oscilloscope data

Screenshot of 'scope

EDIT: I have added a revised image of the Spreadsheet capture. Additionally, I have added a screenshot of the 'scope display. As can be seen, a 1kHz waveform is being displayed.

EDIT2 I will only be using pure sine waves.

The spreadsheet data can be downloaded here. (it's plain text file with a .txt extension, so will simply render in your web browser)

  • \$\begingroup\$ you have the sample rate at the top of the list ... how far apart are the minimum values? .... what is the difference between the minimum and maximum? \$\endgroup\$
    – jsotola
    Jan 27 '18 at 10:46
  • \$\begingroup\$ Does your data set have one amplitude and frequency, or does it vary as the dataset goes on? Are you very keen on Excel, or would you consider a scripting language (e.g. Python, Matlab) or data analysis software (e.g. Origin, QTIplot) instead? \$\endgroup\$
    – Jack B
    Jan 27 '18 at 11:08
  • \$\begingroup\$ Actually, I see that graph is of your whole data set. So ignore the question about it varying. New question: How accurate do you need to be? 1mV? 5mV? 10mV? The latter is much easier. Will the signal always be a low distortion sine wave? \$\endgroup\$
    – Jack B
    Jan 27 '18 at 11:18
  • \$\begingroup\$ @jsotola I count about 626 between the lowest values. \$\endgroup\$
    – Tony
    Jan 27 '18 at 11:24
  • \$\begingroup\$ @Jack B - 1mV would be preferred. \$\endgroup\$
    – Tony
    Jan 27 '18 at 11:24

The information you need is almost all right there at the top of the data dump:

  • Sample rate
  • Full scale voltage
  • Ground level

With one piece missing : it's presumably an 8-bit ADC, i.e. it counts from 0 to 255, which you can infer from GND being approximately half way (127). But confirm this from the Velleman datasheet.

So, count the number of samples N between peaks (e.g. positive peaks) or better, positive-going "zero" crossings (where "zero" = 127, the GND level).

Frequency = Sample Rate/N.

Voltage of any given sample S is (S - 127) * Full Scale/256.

Amplitude is simply the peak voltage, best calculated as
( Voltage (positive peak) - Voltage (negative peak) ) / 2
which accounts for any DC offsets. You have a spreadsheet right there - it's ideal for the job.

  • \$\begingroup\$ He is asking (in the comments) for 1mV or so resolution. With a digitisation noise of 8000/256=30mV and 4096 points that is borderline possible, but he's not going to get it by looking at the max and min. \$\endgroup\$
    – Jack B
    Jan 27 '18 at 11:55
  • \$\begingroup\$ No, he says that would be "preferred" in the comments, not the question. But he's up against the reality of an 8-bit ADC. If he's serious about that, all the above still applies but he'll have to do curve fitting (a simple Hermite interpolation might be good enough) around each peak, to derive a fractional sample value. \$\endgroup\$ Jan 27 '18 at 11:59


and it is not about ...
Programming software for a PC

This question is somewhat... ish off-topic. But I thought that since I know of a C++ solution, then I might as well share it.

#include <iostream>
#include <fstream>

using namespace std;

int main(int argc, char* argv[]){

      cout << "Drop a file on me or"<<
              "call me from the command line with one argument.\n\n";
      //cin.get();//pause, useful for when you drop a file onto the exe.
      return 1;
    cout << "You rang?\n\n";

  string suffix(argv[1]);//convert to string

    //bad names are: "t", "xt", "txt", ".txt"
    //good names are: "a.txt" or "abc.txt"
    cout << "Doesn't look good Sonny," <<
            " give me a file on the format of \"*.txt\".\n\n";
    //cin.get();//pause, useful for when you drop a file onto the exe.
    return 1;
    cout << "The length checks out.\n\n";


    cout << "Accepted type is \"txt\", yours is \"" << suffix << "\"\n\n";
    //cin.get();//pause, useful for when you drop a file onto the exe.
    return 1;
    cout << "Got a file of the correct type. Let's proceed.\n\n";
    cout << "===========================================\n\n";

  ifstream file(argv[1]);//let's finally open the file
  string row; //contains row of document, with a ',' as a delimiter
  unsigned int samples, 

  //First get the number of samples and sample rate.

  getline(file,row,'-');//throw away first part
  file >> samples; //acquired samples.
  cout << "Number of samples = " << samples << endl;    

  file >> sample_rate; //acquired the sample rate
  cout << "Sample rate = " << sample_rate << endl;


  file >> ground;
  cout << "Ground level = " << ground << endl << endl;

  //Okay, now we just need to read the rest of the document     
  for(unsigned int i=0;i<samples;++i){
    getline(file,row,',');//skip the "Data0" text
    file >> data;

    if(data<ground && previous_data>=ground){
    //we've just passed ground and we're going upwards.
        //is this the first time we've came across this zero crossing?
        //this is a potential stop

  double mean_period = (end-start)/((double)zero_crossing);
  double frequency = sample_rate / mean_period;

  cout << "First occurance of a negative edge = " << start << endl;
  cout << "Last occurance of a negative edge = " << end << endl;
  cout << "Length between those two = " << end-start << endl;
  cout << "Number of crossings detected = " << zero_crossing << endl;
  cout << "Mean length per period = " << mean_period << endl;
  cout << "Frequency = " << frequency << endl;

  cout << endl << endl;
  //cin.get();//pause, useful for when you drop a file onto the exe.
  return 0;

Compiled with the following command: "g++ -std=c++11 main.cc", assuming you save the above code in a file called "main.cc".

I've tried to comment things that "look weird" or need some comments so it's easier to follow.

The algorithm is essentially: Detect zero crossings with a negative derivative. Record the first time and the last time it occurred and count how many times it occurred.

Subtract the first time from the last time to get the length of N periods. N periods being equal to the number of occurred zero crossings. Now you have the length of a period that has been averaged across all the detected periods.

Calculate the frequency by dividing the sample rate with the length of a period and present this data.

This is how it looks in action, on a Linux machine.

enter image description here

I'm not sure if I'm just bad but I couldn't drop a file onto the "a.out" file, so I couldn't test what I normally could do on Windows. But I left some "pause" commands in the code which you can uncomment in case you want to see the information.

  • 1
    \$\begingroup\$ Wow, thank you - that's brilliant. As for being on topic - I was trying to draw the attention of electronics minded folk as against PC minded folk - it was tough deciding where to post.. (especially given that the majority of my posts have been in computing!) \$\endgroup\$
    – Tony
    Jan 27 '18 at 15:11
  • 1
    \$\begingroup\$ Loving the amusing comments throughout the code by the way.. \$\endgroup\$
    – Tony
    Jan 27 '18 at 15:25

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.