Help with FreqMeasure for Turbo Shaft Speed

mikey.antonakakis

Active member
I've got a Teensy 4.0 integrated into my project car, and its main job is sending and receiving CAN messages, reading sensors, actuating things, and sending data to a Nextion display. Overall the system is working great, but lately I've been noticing some suspicious data from one of its smaller jobs, using FreqMeasure to monitor the shaft speed of a turbo.

The turbo has a variable relucatance sensor generating a signal with one pulse per revolution, run through a VR signal conditioner and logic level shifter to generate a 3.3v square wave. Shaft speed with engine running could range from 0rpm to 120,000rpm, or 0Hz to 2kHz (although mostly it shouldn't exceed 90,000rpm with my current engine setup). I know this is higher than the ideal range for FreqMeasure, but when it works I see up to 90,000rpm readings coming out nice and clean.

However, I've noticed at times that it will start reporting smaller values, almost like it's hitting a wall (for example, reading a very flat 30,000rpm when it should be increasing from about 50,000 to 80,000rpm). Conversely, at idle the shaft speed should be something like 3,000rpm, but I see frequent spikes to very high values unless I use a more aggressive moving average filter. I am fairly certain this is an issue with my implementation - I first wrote the turbo speed portion of my code about 2 years ago so I don't clearly remember my reasons for writing it the way I did.

Overall, my code runs on an event-based timing, using receipt of a CAN message to run the bulk of the code, using FlexCAN_T4. Messages from different devices run different sections of code, all contained within a CAN handler function. The loop() function contains just 2 lines of code:
Code:
void loop(void) {
  Can.events();
  updateTurboSpeed();
}

The updateTurboSpeed() function:
Code:
void updateTurboSpeed() {
  // Update turbo speed only if engine is running:
  if (rpm > 0) {
    if (FreqMeasure.available() > 3) {
      if (n < freqSize) { //first few readings only
        freqNew = FreqMeasure.read();
        n++; //current number of samples in freq[]
        freqAvgNew = freqAvgOld + (freqNew - freqAvgOld) / n; //update the average
        turboRPM = FreqMeasure.countToFrequency(freqAvgNew) * 60;
        freq[k] = freqNew;
        freqAvgOld = freqAvgNew;
        k = (k + 1) % freqSize;
      }
      else { //once we have at least freqSize readings
        freqNew = FreqMeasure.read();
        freqAvgNew = freqAvgOld + (freqNew - freq[k]) / n; //update the average
        turboRPM = FreqMeasure.countToFrequency(freqAvgNew) * 60;
        freq[k] = freqNew;
        freqAvgOld = freqAvgNew;
        k = (k + 1) % freqSize;
      }
    }
  }
  turboSpeed16 = (uint16_t)(turboRPM / 10); //for MSCAN message, 10-bit(?) data
}

All the variables in the function are global uint32_t except for turboRPM (float). Looking at this code now. I'm drawn to this as a start:
Code:
if (FreqMeasure.available() > 3)
Honestly not sure why I added the >3 condition, but think the effect would be that I only read every 3rd measurement (or maybe 4th or more depending on how long the CAN event code blocks?). I'm guessing this will fill the 12-element FreqMeasure buffer pretty quickly, and may be part of my issue. The next if/else statement is just for my moving average of size freqSize. Upon startup it fills the array, after that casts out the oldest reading and updates with newest. Shouldn't be an issue there, I've used versions of it in plenty of projects.

I will make a few changes (e.g. get rid of the ">3" condition or add a for loop to ensure I read every measurement until the buffer is empty). But overall, are there any glaring issues with calling this function from the loop when Can.events() is going to have a highly variable run time? Or, anything I should be doing differently?
 
On each pulse, FreqMeasure computes the period (time) since the previous pulse and stores the value in a FIFO, but if the FIFO is full, it throws the reading away. If the time between calls to updateTurboSpeed() is too long, the one value you read from the FIFO could be too "old" to accurately represent the current speed. Eventually you will work through the old values and get a "good" value, but I think that could explain the odd values. I think you should try modifying updateTurboSpeed() to read all of the values in the FIFO, and use only the LAST value read from the FIFO (the most recent data) or compute the average of all of the values in the FIFO. Or, if you process all of the data in the FIFO the same way you do now, your turboRPM will always represent the N most-recent measurements. If you find that the time between reads is so long that the FIFO can be full, you should either increase the size of the FIFO or somehow read it more often.
 
Last edited:
Thanks Joe! I think that helps a ton, knowing that the FIFO ignores new entries if it’s full, rather than tossing out the oldest entries and keeping itself updated.

Quick back of the envelope calculation - each of my 3 main chunks of code that get triggered by the 3 types of CAN events run on the order of 20Hz (50ms), driven by CAN broadcast rate of other devices on the network, and I am pretty confident that they are nowhere close to running out of time to execute on Teensy 4.0. So let’s say they each take about 15ms to run (I suppose I could time this). I’ll assume that reading the full FreqMeasure buffer as you suggest (and as I plan to implement) would be much faster than that. At worst case turbo shaft speed sensor would give FreqMeasure a signal at 2000Hz, or 0.5ms. So if my CAN code blocks me from reading out the buffer for about 15ms, I could potentially go about 30 pulses between reads. But even in that case I would only be missing about 10ms of data which would probably be acceptable. And it’s likely that would only happen some of the time - since during the period between processing and acting on one CAN message and waiting for the next, I could constantly be updating the turbo speed readings.

I’m pretty sure now that my current code probably grabbing some mix of old and new FIFO data, and is unpredictable/unstable in what that mix ratio is.

I will implement your suggestions and take some timing measurements.
 
freqAvgNew = freqAvgOld + (freqNew - freq[k]) / n; //update the average
All the variables in the function are global uint32_t

Unsigned arithmetic is tricky, subtraction may give a modulo largest number effect, and I am not completely sure, but when the rpm is slowing you could see very large spikes. I would suggest changing all the variables to int32_t and see if that fixes anything.
 
Thanks for that! In my case I think it should be okay, looks like Teensy 4.0 uses a 150MHz clock, and minimum pulse rate is around 30Hz, so something like 5,000,000 clock cycles which fits no problem into uint32_t. Guessing that’s why the readme page lists 0.1Hz as the lower range for FreqMeasure (1,500,000,000 counts is getting closer to overflow).

In my implementation I don’t read the turbo speed unless the engine is running, so worst case only my very first readings might be incorrect due to overflow as I read out the old FIFO entries, which is no problem for me.
 
Unsigned arithmetic is tricky, subtraction may give a modulo largest number effect, and I am not completely sure, but when the rpm is slowing you could see very large spikes. I would suggest changing all the variables to int32_t and see if that fixes anything.

Oh, I think I totally missed what you were getting at re: unsigned integer subtraction! Specifically in my moving average calculation. I will change to int32_t.
 
Oh, I think I totally missed what you were getting at re: unsigned integer subtraction! Specifically in my moving average calculation. I will change to int32_t.

You could also call countToFrequency() after each read, store the results in freq[] (type float), and do your running average on the (float) frequency values rather than the (unsigned) count values.
 
Made some of the changes above (mainly data types and using a while loop to empty buffer each time), got some improvement, but still seeing some funny stuff. Going to remove the averaging altogether temporarily to rule it out.

Just realized I may have a pin conflict. Using 22 for FreqMeasure and 23 for a SPI CS pin… not sure if it matters but will be quick to fix.
 
Removed the averaging filter, just converted each reading to frequency and used it as-is. Still getting spikes at low actual turbo speed when engine is idling (500,000rpm or more). Again, expected max at full throttle and redline is about 100,000rpm, the short periods when idle is stable is showing 1500rpm, which seems reasonable. As soon as I get off idle, even with no averaging filter, the data is extremely smooth and reasonable, see photo below to see the stark difference as soon as I touch the throttle pedal (top plot shows engine speed and throttle position, bottom plot is turbo speed).

Still not sure what the issue is at idle. The turbo is running approximately 1500rpm there, or 25Hz, or 6,000,000 counts, which shouldn't be a problem. I doubt it is coming to a full stop (although I could check visually, which I will do ASAP).

Here's the latest function, called from loop() after Can.events() function.
freqNew is uint32_t, turboRPM is float, turboSpeed16 is uint16_t and the value that gets sent to datalogger:

Code:
void updateTurboSpeed() {
  // Update turbo speed only if engine is running:
  if (rpm > 0) {
    while (FreqMeasure.available()) {
      freqNew = FreqMeasure.read();
      turboRPM = FreqMeasure.countToFrequency(freqNew) * 60.0;
    }
  }
  turboSpeed16 = (uint16_t)(turboRPM / 10); //for MSCAN message, 10-bit(?) data
}

Capture2.PNG
 
Extra edges getting in there somehow? If you want to try a different pin, you can use FreqMeasureMulti, which does the same thing as FreqMeasure, but allows use of almost any timer pin.
 
Thanks, may give that a try, especially since I might tap into the ABS tone ring in the future to get a vehicle speed signal into the Teensy... There is also a chance that I'm getting some weird stuff at the lowest speeds due to the nature of the VR sensor (may not generate a strong enough wave form for the MAX9924 signal conditioner, maybe causing some funny stuff). Would probably need oscilloscope to check that.

Honestly if it only has issues at idle, and it's a hardware issue (too weak of VR sensor signal, for instance) I wouldn't mind that. The data in that condition doesn't have any importance to me, so I may just add an if statement to ignore the data below certain engine speed or throttle. Mostly just want to make sure I'm not doing anything blatantly wrong with my implementation.

For a little additional info, I timed the various blocks of code that run after each type of CAN event. Two of them execute their code in 1us or less, one of them in 90us, and the last one takes about 15ms (does a fair amount of CAN and Serial communication, I'm guessing that's where the time is going). Shouldn't really be an issue, I can live with the possible level of inaccuracy that might be caused by that. Regardless, I'm going to dig into that section of code a little more to see what's taking that time to see if there's room to optimize.

EDIT: quick napkin calculation gives maybe 200 bytes transmitted to my Nextion display each cycle, at 115200 baud that should take about 14ms, so that checks out.
 
When using period measurement like this, it's usually necessary to handle issues that occur at low speed, the main one being overflow of the period measurement that occurs at zero (or near zero) speed. That's not your issue here, but it's a useful example. One thing you can do deal with the problems at low speed is to simply count the number of readings your get over a second or so and use that to compute a low-resolution speed. When you know the speed at which the period values are reliable, you can transition from "low-res" to "high-res" mode above that value, and revert to low-res mode when below, with some hysteresis. I'll have to look at FreqMeasure and FreqMeasureMulti to see how they handle overflow.
 
Hi there, when measuring low speeds on turbo-chargers please be aware that the turbo speed will 'pulse' due to the exhaust pulses from slow exhaust valve openings. Small turbo's have low inertia and will respond to these exhaust pulses. As you have seen the faster the engine runs the smoother the results. My guess would be some form of averaging will be required or slower update at lower speeds.

What I am trying to say is that it may not be a measuring problem but a mechanical one.
 
Hi there, when measuring low speeds on turbo-chargers please be aware that the turbo speed will 'pulse' due to the exhaust pulses from slow exhaust valve openings. Small turbo's have low inertia and will respond to these exhaust pulses. As you have seen the faster the engine runs the smoother the results. My guess would be some form of averaging will be required or slower update at lower speeds.

What I am trying to say is that it may not be a measuring problem but a mechanical one.

Thanks @Ceetee, I was wondering if it could be something like that. If that's the case, then he could just do longer averages at low speed and shorter ones at higher speed if necessary.
 
So with the updates (getting rid of averaging, mostly). Found a few small spikes at full throttle/high engine speed. Turbo speed seems reasonable, but occasionally jumping 10-15% for a single sample in the datalog when it's otherwise reading about 90,000rpm. Definitely not due to long time between samples or unstable VR signal in that condition... So I will move that SPI CS pin away from 23 and see if that helps.
 
So with the updates (getting rid of averaging, mostly). Found a few small spikes at full throttle/high engine speed. Turbo speed seems reasonable, but occasionally jumping 10-15% for a single sample in the datalog when it's otherwise reading about 90,000rpm. Definitely not due to long time between samples or unstable VR signal in that condition... So I will move that SPI CS pin away from 23 and see if that helps.

Okay. Just note that when you get a spike in speed, it implies higher frequency and less time between edges, not longer time.
 
https://www.pjrc.com/teensy/td_libs_FreqMeasure.html


The docs for FreqMeasure indicate that FreqCount would be a better fit for your application, and interestingly you have issues at low speed where the FreqMeasure library is supposed to be better. Perhaps that does indicate some issue with the sensor at low speeds.

A median filter may be what you could use to eliminate the single sample issue at high speeds. With a regular average ( mean ) spikes will average in and still effect the results, whereas a median filter will throw away values that are not in line with the others.

I wrote a 3 element median filter when having issues with some hobby servos. The code is simple in that it doesn't need to sort values. You could adapt it to your data types if you wish to try it. It does 4 radio control channels, you could simplify or process more than one sensor.

Code:
// return the median value of the last 3 to ignore outliers in the data stream
 // store data for 4 channels
 int median( int8_t ch, int val ){
 static int vals[4][3];
 static int8_t in[4];
 int8_t j,i,k;                                  // low, median, high

   vals[ch][in[ch]] = val;                     // save the new value
   ++in[ch];
   if( in[ch] > 2 ) in[ch] = 0;

   j = 0, i = 1, k = 2;                             // pretend they are in the correct order
   if( vals[ch][j] > vals[ch][k] ) k = 0, j = 2;    // swap guess high and low
   if( vals[ch][i] < vals[ch][j] ) i = j;           // is lower than the low guess, pick that one instead
   if( vals[ch][i] > vals[ch][k] ) i = k;           // is higher than the high guess

   return vals[ch][i];
 }

Edit: calling code looks like this:

Code:
     us -= 1500;                 // sub out the zero point, get -500 to 500 in values
     us = median(i,us);          // discard glitches ( from interrupt latency I think )
 
The docs for FreqMeasure indicate that FreqCount would be a better fit for your application, and interestingly you have issues at low speed where the FreqMeasure library is supposed to be better. Perhaps that does indicate some issue with the sensor at low speeds.

A median filter may be what you could use to eliminate the single sample issue at high speeds. With a regular average ( mean ) spikes will average in and still effect the results, whereas a median filter will throw away values that are not in line with the others.

[/CODE]
Ah cool, I will give that a try after changing the CS pin.
Re: FreqMeasure vs FreqCount, I figured FreqMeasure would work better the majority of the time. Unless I'm full throttle and high engine speed, turbo shaft speed is 10,000 to 30,000 while driving around (170-500Hz). With current tuning, seeing about 95,000rpm (~1600Hz) max, a little past the recommended range.

I will also try a few things to identify/isolate possible sensor noise issues. I don't think it will matter for my use case, but it would also be easy to change VR sensor polarity. Don't have access to an oscilloscope at the moment, which would really help find any signal issues, but I can try some other things (e.g. plug in a spare VR sensor, but leave it away from the trigger wheel and see if I get any turbo speed readings).

In the FreqMeasure documentation, there is also this note: "Libraries which disable interrupts for long times (eg, NewSoftSerial) can be problematic." Could my use of FlexCAN_T4, a series of Serial2.print() calls that take ~15ms total, or analogWrite on pins other than pin 23 affect FreqMeasure?
 
Why not connect another Teensy to your input and simulate the sensor at various speeds.
This would allow you to determine whether it is a hardware or a software problem.
 
If you have interrupts disabled for too long, you could miss data, but this would tend to give you readings that look like longer periods (slower speeds), not the spikes you’re getting. Doing pulse counting with FreqCount with your 1-PPR sensor will require a relatively long counting period, perhaps 1 second. Pulse counting is also affected by interrupts being disabled or lower priority. Your application is better suited to FreqMeasure if you can sort out what seems to be extra edges.
 
If you have interrupts disabled for too long, you could miss data, but this would tend to give you readings that look like longer periods (slower speeds), not the spikes you’re getting. Doing pulse counting with FreqCount with your 1-PPR sensor will require a relatively long counting period, perhaps 1 second. Pulse counting is also affected by interrupts being disabled or lower priority. Your application is better suited to FreqMeasure if you can sort out what seems to be extra edges.
Good idea, thank you, I will add the to the list of things to check. In the same direction, I can use the hardware as-is and just load the FreqMeasure sample sketch (car will still run without the Teensy).
 
turbo shaft speed is 10,000 to 30,000 while driving around (170-500Hz)

Yes, I see I was missing the factor of 60 for RPM. Another thought I had was you could call my median routine twice or more, like:

Code:
  val = median( 0, val );
  val = median( 1, val );

Whether that would be the same as the median of 6 values or if it would be some other convolution of the data I will leave as an exercise for the reader as it sort of muddles my mind.
 
If you have interrupts disabled for too long, you could miss data, but this would tend to give you readings that look like longer periods (slower speeds), not the spikes you’re getting.
Yeah, that's what I was thinking. If it really goes too long, maybe I run into overflow issues causing false high readings, but I don't think that would happen until I'm on the order of 20 seconds between pulses.
 
Quick test with very simple code, printing out the turbo speed as it's available. The Serial Plotter screenshot shows me slowly revving the engine up to 3000rpm (to give the turbo some speed) then gradually slowing engine speed down (turbo speed slows down). As the speed drops, the false readings appear. They are much worse as the turbo speed drops further. Going to guess this is noise getting into the signal, will try a few other tests to confirm - first one will be plugging in an uninstalled VR sensor to the existing wiring harness with this same super simple code, and monitor while engine is running. I should see no speed readings in this case, and if I do, I can assume I have noise issues.

turbospeedtest1.PNG
 
Back
Top