Mbed frequency measurement

28 Jul 2010

Hi,

I was curious as to how accurately the Mbed could read and store analog frequency data. I ran a few tests, and found that the data was gibberish. In general I get a file full of values you might expect from a square wave, a list of 0s then a list of 0.7s. However analyzing the data reveals some strange results. My design is fairly simple, the program is included at the end. I have a function generator hooked up to an analog input. The Mbed is grounded to the same ground as the input ground. I have the generator set to a steady 10 or 20 Hz square wave (verified by oscilloscope) and properly offset so that the bottom of the square wave is truly 0, ground. The output data file looks as it should but once I try to find the input frequency from this data, I get insane results. My method is this:

count the number of data points taken in a full oscillation of the wave (from the beginning of a trough to the beginning of the next trough) and multiply is by the sampling rate set in the program. That yields the period of the wave, and the reciprocal of that is the frequency. So here is a table of values I got from this method.

@10Hz, square wave

sampling rate(us) #samples/period period(ms) frequency(hz)
100 112 11.2 89
200 116 23.2 43
300 115 34.5 29
400 117 46.8 21.4
500 85 42.5 23.5
600 88 52.8 18.9
700 85 59.5 16.8
800 82 65.6 15.2
900 74 66.6 15.0
1000 64 64 15.6

@20Hz, square wave

sampling rate(us) #samples/period period(ms) frequency(hz)
100 55 5.5 181
200 68 13.6 73
300 66 19.8 50.5
400 50 20 50
500 44 22 45
600 32 19.2 52
700 31 21.7 46
800 36 28.4 34
900 39 35.1 28

So clearly something is wrong. Does anyone have experience using the Mbed for frequency measurements and encountered this problem or done something differently?

 

Here is the program I used, written by D. Wendelboe, thanks

#include "mbed.h"

#define ON   1
#define OFF  0

LocalFileSystem local("local");
AnalogIn myinput(p18);
DigitalOut myled(p5);
DigitalOut led1(LED1);
DigitalOut led2(LED2);

float analog_value;
int count = 0;

int main() {

    FILE *fp = fopen("/local/analog.txt","w");  // Open a file to save samples
    printf("File analog.txt is open.\r\n");

    while (count < 500) {
        analog_value = myinput.read();  // Read analog (range 0.0 to 1.0)
        if (analog_value > 0.6)         // If value is above 0.3, then store
        {                               // it as an "above" value.
            fprintf (fp, "%3i: %f [above]\r\n", count+1, analog_value);
            led1 = ON;     // Turn LED ON if sample was stored.

        } else {           // Below 0.3, store it as a "below" value.
            fprintf (fp, "%3i: %f [below]\r\n", count+1, analog_value);
            led1 = OFF;    // Turn OFF if sample was not stored.
        }
        count++;           // Increment sample counter.
        wait_us(900);
    }
    fclose(fp);            // Close the sampe file after 100 samples read.
}

28 Jul 2010

Hmm.. I am not sure that you're doing it the most direct way.. I've had this peice of code to measure RPM, I've modified it to give frequency. It uses a timer and an interrupt to get the period, and from that, the frequency. I've tested it at around 1 kHz, Im not sure if it can do 10 kHz..

 

#include "mbed.h"

Ticker tick;
Serial pc(USBTX, USBRX);
InterruptIn in(p8);
Timer t1;

int t_period = 0;                   // This is the period between interrupts in microseconds
float t_freq = 0;

void flip();


int main() {
    in.mode(PullDown);              // Set the pin to Pull Down mode.
    in.rise(&flip);                 // Set up the interrupt for rising edge
    t1.start();                     // start the timer

    while (1) {
        wait_ms(100);
        pc.printf("%f Hz\r", t_freq);
    }

}

void flip() {
    t_period = t1.read_us();                // Get time since last interrupt
    t_freq = (1/(float)t_period)*1000000;   // Convert period (in us) to frequency (Hz)
    t1.reset();                             // Reset timer and wait for next interrupt
}

I suppose it can be optimized by taking the period it takes for a 100 interrupts and using that to get the frequency, that way you dont have to do that ugly float calculation very often. Or you can do the inverse calculation outside the ISR for better performance. I hope that helps!

28 Jul 2010

Jim:

Don't know for sure, but it sounds like a problem with your sampling rate. According to Nyquist, your samling rate must be at least 2X the maximum frequency expected in your signal. In your sample set, depending upon your sampling rate, you are either missing pulses altogether, or sampling a pulse more than once. See:

http://en.wikipedia.org/wiki/Nyquist-Shannon_sampling_theorem

Since your source is a square wave, I would try inputting your signal into a timer/counter channel instead of an analog (ADC) channel.

Just some thoughts.

Doug

 

28 Jul 2010

Thanks for the response guys,

@Igor, thanks for the program, I didn't think to do it that way. How can I read the data? Does it output to a terminal or window on the computer?

 

@Doug, which channel would be timer/counter? Igor suggested in his code that he uses pin 8.

28 Jul 2010

Hey,

You can set the InterruptIn pin to most of the DigitalIO pins (they are  blue ones on the picture.) I chose pin8 arbitrarily. The InterruptIn object is used to run a specific function when a pin (of your choosing) goes high or low. 

The output is printed to the usb serial port: pc.printf("%f Hz\r", t_freq). The measured frequency is stored as a float in t_freq. You are free to use it however you wish in your program.

Hope that helps!

29 Jul 2010

Hmm, turns out it has something to do with the code. fprintf seems to be a lengthy opperation. I tried using an array, and almost all discrepancy is gone, but could be better. I think it might be more solid if I use long int instead of float, and perform the conversion after the data is collected. However, I don't know how to put long int values into a form which can be printed in a txt file...suggestions?

 

Thanks for the hints guys

29 Jul 2010

The fprintf placeholder for int is %d or %i. For unsigned int, its %u I believe. And you are right, using ints should make it faster. You can change the t_freq to an unsigned int and change t_freq = (1/(float)t_period)*1000000;   to  t_freq = (1000000/t_period);. That should perform integer division insead of float.

Also, I am not sure how fast the file system is, but as long as you do the printing in the main (where the program flow can be interrupted by the InterruptIn), it should be fine.

30 Jul 2010

so here is the last hurdle I think:

   long V[1000];
   ...
   for(i=0;i<1000;++i) {
   float F = V[i];
   fprintf (fp, "%3i: %f \r\n", i, F);
   } 
I'm trying to get this long integer V[i] to print to a file as a floating point number. I need it to be long until just before it is printed, hence it must be converted just before it is printed. I can't find much reputable help or a guide to doing this on-line, this is the only thing I could glean from various forum posts. However, all I get is a long list of 0.00000s. This code does work if I take V[i] to be a float to begin with, but something messes up in the conversion. Help?

 

thanks again

30 Jul 2010

so here is the last hurdle I think:

   long V[1000];
   ...
   for(i=0;i<1000;++i) {
   float F = V[i];
   fprintf (fp, "%3i: %f \r\n", i, F);
   } 
I'm trying to get this long integer V[i] to print to a file as a floating point number. I need it to be long until just before it is printed, hence it must be converted just before it is printed. I can't find much reputable help or a guide to doing this on-line, this is the only thing I could glean from various forum posts. However, all I get is a long list of 0.00000s. This code does work if I take V[i] to be a float to begin with, but something messes up in the conversion. Help?

 

thanks again

30 Jul 2010

 

The code below produces the following. I didn't try to open a file and use fprintf, but the formatting should be the same.

12345678  12345678.000000   12345678.000000
#include "mbed.h"

long   V;
float  F;
double D;
      
int main() 
{
    V = 12345678;
    F = V;
    D = V;    
    printf ("%i %f %f\r\n", V, F, D);   
}
30 Jul 2010

The code below writes the same to the file sample.txt

#include "mbed.h"

long   V;
float  F;
double D;
      
LocalFileSystem local("local");
      
int main() 
{
    V = 12345678;
    F = V;
    D = V;  
    
    FILE *fp = fopen("/local/sample.txt","w"); 
    printf("File opened\r\n");
      
    printf ("Sending this to file: %i, %f, %f, %e\r\n", V, F, D, D);
    fprintf (fp, "%i, %f, %f, %e\r\n", V, F, D, D);  
    
    printf("File closed\r\n");      
    fclose(fp); 
}
      
12345678, 12345678.000000, 12345678.000000, 1.234568e+07
30 Jul 2010

no dice, only 0s again. I'm assigning values to V[i] through an array 0-999, if that makes any difference...

30 Jul 2010

This works OK for me.

#include "mbed.h"

float  F;
double D;
long   V[1000];
      
LocalFileSystem local("local");      
      
int main() 
{
    FILE *fp = fopen("/local/samples.txt","w"); 
    printf("File opened\r\n");
    
    for (int i=0; i<1000; i++)
    {
        V[i] = i * 1234;   // Make up some fake data.
        F = V[i];
        D = V[i];   
        printf ("%4.4i: %i, %f, %f, %e, %g\r\n", i, V[i], F, D, D, D);  
        fprintf (fp, "%4.4i: %i, %f, %f, %e, %g\r\n", V[i], F, D, D, D);
    }
      
    fclose(fp); 
    printf("File closed\r\n");      
}
30 Jul 2010 . Edited: 30 Jul 2010

Hmm, I am not sure you can assign an int to a float like that. You have to cast (convert) the long to a float before assignment.

float F = V[i];  // not sure this will work
float F = (float)V[i];  // try this (C style type cast)
float F = float(V[i]);  // or this.. (C++ style type cast)


Also, may I ask why you need the result as a float? It does not increase the precision of the value..

Edit: added C++ style cast

03 Aug 2010 . Edited: 03 Aug 2010

So this is kind of complex, I'll do my best to be concise.

Originally, in my program I have something reading input voltage, assigning it as a float variable to an array. Then the array is printed. However, I need the read process to be as fast as possible, and I noticed in testing that there is a discrepancy between the assigned sampling rate and the actual S.R.. This is, I assume due to the time required to make the float conversion. So I had the idea to switch to long int. However, when I tried to print the long int straight to output, I got lots of 0s. So I thought maybe making a float conversion then printing (all after the data has been taken) would work. And thats where I'm at. My problem is that after making what seems to be the correct conversion syntax (at least from what Doug and most of the internet says) I get only a long list of very precise 0s. Here is the original (fully functional) code, and below are the changes I have tried to make, but have failed. I'll list some example outputs below each as well.

 

//------------------------------
//Simple read and store program
//James K. Ehrman, July 29 2010
//------------------------------

#include "mbed.h"

#define ON   1
#define OFF  0

LocalFileSystem local("local");
AnalogIn myinput(p18);
float V[1000];
int count = 0;
int i = 0;

int main() {

    FILE *fp = fopen("/local/analog.txt","w");  // Open a file to save samples

    for (i=0;i<1000;++i) {
        V[i] = myinput.read();             // Read analog (range 0.0 to 1.0)
        wait_us(500);
        }
        i = 0;
    for (i=0;i<1000;++i) {
        fprintf (fp, "%3i: %f \r\n", i, V[i]);
    }
    fclose(fp);           
}

which yields such values:

 

 

29: 0.000000

30: 0.000000

31: 0.000000

32: 0.000000

33: 0.000000

34: 0.000000

35: 0.000000

36: 0.000000

37: 0.339194

38: 0.339194

39: 0.338950

40: 0.339194

41: 0.338706

42: 0.338950

43: 0.338950

44: 0.339194

45: 0.339194

Here are the changes I have tried:

 

#include "mbed.h"

#define ON   1
#define OFF  0

LocalFileSystem local("local");
AnalogIn myinput(p18);
long V[1000];
int count = 0;
int i = 0;

int main() {

    FILE *fp = fopen("/local/analog.txt","w");  // Open a file to save samples

    for (i=0;i<1000;++i) {
        V[i] = myinput.read();             // Read analog (range 0.0 to 1.0)
        wait_us(500);
        }
        i = 0;
    for (i=0;i<1000;++i) {
        float F = float(V[i]);
        fprintf (fp, "%3i: %f \r\n", i, F);
    }
    fclose(fp);           
}

 

which yields:

 

0: 0.000000

1: 0.000000

2: 0.000000

3: 0.000000

4: 0.000000

5: 0.000000

6: 0.000000

7: 0.000000

8: 0.000000

9: 0.000000

10: 0.000000

And it is definitely not the case, as may be possible in the latter set that I may be only observing a stage where the signal is 0 (a trough), all 1000 values are 0.000...
*EDIT*
I mentioned earlier that printing direct long int did not work, here is the code and output:
#include "mbed.h"

#define ON   1
#define OFF  0

LocalFileSystem local("local");
AnalogIn myinput(p18);
long V[1000];
int count = 0;
int i = 0;

int main() {

    FILE *fp = fopen("/local/analog.txt","w");  // Open a file to save samples

    for (i=0;i<1000;++i) {
        V[i] = myinput.read();             // Read analog (range 0.0 to 1.0)
        wait_us(500);
        }
        i = 0;
    for (i=0;i<1000;++i) {
        fprintf (fp, "%3i: %i \r\n", i, V[i]);
    }
    fclose(fp);           
}
output:
0: 0
1: 0
2: 0
3: 0
4: 0
5: 0
6: 0
7: 0
8: 0
9: 0
10: 0
I heard that there is special significance to the fact that these are now 0 instead of 000000000000.
03 Aug 2010

 

Hi James,

Thanks for posting some code, it's much clearer now what the problem seems to be. The first thing that jumps at me is the following:

...
long V[1000];
...
V[i] = myinput.read();             // Read analog 
...

I don't know why the compiler didn't complain (I guess it goes ahead and casts before assignment), but this will make V[i] always zero.
AnalogIn read() returns a floating point number in the range of 0.0 to 1.0. You are then assigning it to an integer type. Integer types cannot be used to represent fractional numbers (i.e 2.1, 3.14149, 0.8..). Integers can only be used to represent whole numbers. When you try to assign (or cast) a float to any type of int, the fraction part of the float gets truncated (i.e. chopped off) and you're left with whatever is to the left of the decimal point.

For example:

float a = 3.141592654;
float b = 1.0;
float c = 0.5;
int d = 1;
int x;

x = (int)a;    // x becomes 3
x = (int)b;    // x becomes 1
x = (int)c;    // x becomes 0! (remember, anything after the decimal point is gone..)
/* Going the other way */
a = (float)d;  // a now becomes 1.0 (note that the .0 was added to signify a floating point number)


Your program is printing the results as it should. 

As for the speed issue, There are a number of ways to do it. The simplest way to do it is to read the analog voltage as an int. If you use read_u16() to read the voltage, you will get an integer value from 0 to 65535 that maps the voltage from 0 to 3.3 volts. You can save that to your array and manipulate the results after. To get a value from 0.0 to 1.0 you can do the following:

float a;
a = float(V[i])/65535;    // This casts V[i] to a float and then divides it my the maximum value to give you a value from 0.0 to 1.0. 
                          // The cast here is important as to ensure that floating point division is used (instead of integer division)

That should make the AnalogIn reads a little faster..

I hope that helps!

03 Aug 2010 . Edited: 03 Aug 2010

to avoid a floating point divide (expensive in hardware), store the value 1/(65535) in a variable and multiply by float(V[i]) when you need to, this should run faster. The memory access to load the stored constant and multiplication should be faster than the floating point divide.

04 Aug 2010

Good point..

04 Aug 2010

Great, I have the conversion working well. However at 100 us sampling rate I still have a 65% discrepancy in measured signal frequency. Looks like there is some fundamental hardware limit, as seen in the code above, the read portion of code is very minimal.

04 Aug 2010

Why can't you connect the pin to an interruptible DIO, start a timer, and just read the timer's value each time the interrupt occurs. The difference in the timer's value each interrupt directly gives you the frequency given the timer's tick rate. The ADC can only sample so fast...I'm not sure what the sampling rate is on the mbed, but usually microcontrollers are equipped with slow SAR ADC converters. Maybe the ADC is not able to sample as fast as you are requesting an analog read command or the sampling is not occurring at a steady rate.

Another method is to start two timers, one timer is incremented by the signal, and the other counts freely. The freely running timer should be set to trigger an interrupt when it overflows. In the interrupt, immediately read the value of the timer that is being incremented by the signal. Since the freely running timer overflows after a known amount of time, the count of the incremented timer directly gives you the frequency. This method allows you to not have to service interrupts at a high rate, unlike the previously mentioned method. Very high frequencies (several MHz) can be measured in this manner.

If you are trying to measure the frequency of an analog signal, particularly a single tone (sinusoid), square it off using a schmitt trigger and use either of the methods above. If you have a signal with more than one frequency component, sample the signal using the ADC at a steady interval and perform an FFT to reveal the frequency content.The ADC sampling should be triggered by a timer so that a steady rate is assured.

04 Aug 2010
user avatar elliot briggs wrote:
The ADC sampling should be triggered by a timer so that a steady rate is assured

Would not a Ticker work?

04 Aug 2010

Thats a very good idea, however for my purpose, measuring frequency is a test of accuracy and not the purpose of the instrument. What I need is a string of voltage readings (we hope to record voltage output of a photodiode). The photodiode will be observing meteor trails, and by nature the phenomena will be erratic, and thus a measure of the brightness of the event as recorded by the diode will be more useful. I suppose I might have added that to my original post, sorry for the run around!

04 Aug 2010

Did you try using read_u16() instead?

05 Aug 2010

yea, it helped, but only about 2-5%