Alexandru Cimpanu Alexandru Cimpanu - 17 days ago 4x
C Question

Compute oscillator frequency without using float or uint64 types in C

I have a 32.768 kHz oscillator which produces a 1-Hz pulse. I'm measuring this pulse with a 40MHz clock. I am trying to measure the actual frequency of the oscillator by comparing the expected results with the obtained one.

  • I cannot change the period that I'm measuring, it has to be 1-Hz (1s).

  • I cannot use float, double or types larger than uint32.

  • I also need the first digit after the integer part (like 32768.1 Hz).

Without the constraints the solution would be like:

computedFreq = (uint32) ( (float32)32768u / ( ( (float32)measuredPeriod / (float32)40,000,000 ) / (float32)10u ) );


computedFreq = ( 32768*10*40,000,000 ) / measuredPeriod;

So for a measured period of 40,008,312 the result will be 327611.

How can this be achieved while satisfying the constraints?


Your problem is that measured range goes up to 40010000 Hz, while you are only interested in 20000 Hz range. So the solution is to limit the range.

You can do this with linear interpolation using 2 points.

  1. Select 2 input values. Min/max points might do:

    inMin = 40,000,000 - 10,000
    inMax = 40,000,000 + 10,000
  2. Precalculate respective output values using your formula (values here are rough values, calculate your own): You can store these as constants in your code.

    outMin = ( 32768*10*40,000,000 ) / inMin = 327761 
    outMax = ( 32768*10*40,000,000 ) / inMax = 327598 

    Notice how max is smaller than min, due the nature of your formula. This is important when selecting types below.

  3. Use linear interpolation to calculate result using both previous points:

    out = (in - inMin) * (outMax - outMin) / (inMax - inMin) + outMin

    You have to use signed integers for this, because (outMax - outMin) is negative.

    Upper limit of the multiplication is (inMax - inMin) * (outMax - outMin), which should fit into int32.

You can pick any 2 points, but you should pick ones that will not produce too big values to overflow on multiplication. Also, if points are too near each other, answer may be less accurate.

If you you have extra precision to spare, you could use bigger multiplier than 10 on outMin/outMax and round afterwards to keep more precision.