Measuring RPM using a 3144 hall effect sensor gives odd +/- error

KrisKasprzak

Well-known member
All,

I have a fairly reliable pulse counter for measuring RPM. I'm using a Teensy 4.0 and a 3144 hall effect sensor. The sensor reads 4 magnets connected to a PC cooling fan that are evenly placed around the fan (this is just a simple test apparatus). My system counts pulses and measures time between first pulse and last, then dividing the two gives RPM--results are very accurate when compared to a store bought tach. I'm using clock cycles as a basis for time measurement and even if I measure time using micros() to measure pulse times, the results are the same.

I can get a very accurate RPM but is error +/- 4 RPM regardless of the RPM, see the images. However notice the the odd spikes and that they are very predictable making me think it's a timing measure measurement issue--or it simply is what it is... These readings are taken every 200 ms.

If I slow reads to 1000 ms RPM error drops to about +/- 2 RPM. I suspect it's a matter of not enough samples (pulse counts in the measure time period that are causing higher +/- errors. But I would think reading the start and end times would make the error much less.

First image is schematic, second is serial monitor at around 850 RPM, third is serial monitor around 1580 RPM the last image is readings every 1000ms.

Thoughts anyone?


weirdPulseschematic.png


Maybe this is the best I can expect. Any thoughts?

weirdPulseRead.png
highRPM.png

1hz_meaurecycle.png

Code:
#include <avr/io.h>

#define RPM_PIN A0  // pin for the RPM
#define PICKUPS 4.0F

volatile uint32_t PulseStartTime = 0, PulseEndTime = 0, ClockTime = 0;
volatile uint8_t PulseCount = 0;
double PulseTime = 0.0;
double Revs = 0.0;
double RPM = 0.0f;

elapsedMillis Update;

void setup() {

  Serial.begin(115200);

  pinMode(RPM_PIN, INPUT);

  if (ARM_DWT_CYCCNT == ARM_DWT_CYCCNT) {
    Serial.println("here");
    ARM_DEMCR |= ARM_DEMCR_TRCENA;
    ARM_DWT_CTRL |= ARM_DWT_CTRL_CYCCNTENA;
  }

  attachInterrupt(digitalPinToInterrupt(RPM_PIN), ISR_READSPEED, RISING);

  ARM_DWT_CYCCNT = 0;
  PulseCount = 0;
}

void loop() {

  if (Update > 200) {
    cli();
    Update = 0;
    RPM = 0.0;
    if (PulseCount > 3) {
      // getting RPM will be found by getting the pulses in a time period and dividing by the time between the first and last pulse
      // rpm = 1 REC/PICKUPS x Pulses / second  x 60 sec / min

      Revs = (double) (PulseCount - 1) / PICKUPS; // in revolutions

      PulseTime = (double)(PulseEndTime - PulseStartTime)  / F_CPU; // in minutes
     
      RPM = (Revs * 60.0)/ PulseTime;

      Serial.println(RPM);
    }

    PulseCount = 0;
    ARM_DWT_CYCCNT = 0;
    sei();
  }
}


void ISR_READSPEED() {
  ClockTime = ARM_DWT_CYCCNT;
  if (PulseCount == 0) {
    PulseStartTime = ClockTime;
  } else {
    PulseEndTime = ClockTime;
  }

  PulseCount++;
  asm volatile("DSB");
}
 
Have you ever tried the FreqMeasureMulti library? It does exactly what you want, which is to measure the period between rising edges. The measurement is in timer clocks instead of CPU cycles, but it's more accurate because the timer capture is done in hardware. The library captures each period and writes them to a queue that you read from loop(). I think you'll find it simplifies your program a great deal and gives a better result. You won't need to use ARM_DWT_CYCCNT, write your own ISR, or disable interrupts.
 
You disable interrupts across the Serial output, I don't think that's a good idea - keep the critical section(s) as short as possible:

Code:
cli();
uint8_t count = PulseCount;
uint32_t ticks = PulseEndTime - PulseStartTime;
sei();

if (count > 3)
{
   /// do the calculation and printing here while interrupts are running.
  PulseCount = 0;  // single write to memory doesn't need guarding its already atomic.
}
 
One caveat, you have to use a pin that connects to a FlexPWM timer. On T4.0, the easily accessible pins would be 2, 4-9, 22-23. The code below has modifications to your program to use FreqMeasureMulti. I haven't tested it, and it still uses your pin A0, which won't work, but I think if you connect to a suitable pin, this program will work. Also, note that FreqMeasureMulti can use falling edges, or every edge, etc., via an optional parameter to begin(), but the default is rising edges.

Code:
#include <avr/io.h>
#include <FreqMeasureMulti.h>

#define RPM_PIN A0  // pin for the RPM
#define PICKUPS 4.0F

elapsedMillis Update;
FreqMeasureMulti measure;

void setup() {

  Serial.begin(115200);
  measure.begin(RPM_PIN);
}

void loop() {

  if (Update > 200) {
   
    Update = 0;
    double sum = 0;
    uint32_t count = 0;
   
    while (measure.available()) {
      sum = sum + measure.read();
      count++;
     
      // getting RPM will be found by getting the pulses in a time period and dividing by the time between the first and last pulse
      // rpm = 1 REC/PICKUPS x Pulses / second  x 60 sec / min

      double Revs = (double)count / PICKUPS;  // in revolutions
      double Seconds = sum / (F_CPU/4);       // in seconds   
      double RPM = 60 * Revs / Seconds;

      Serial.println(RPM);
    }
  }
}
 
Last edited:
Thanks to all.

I'm now using the FreqMeasureMulti library--I had to cut some traces on my PCB had hard wire the sensor connection. I see little difference in the +/- tolerance when compared to the clock measuring technique--this could be due to the hall effect sensor, magnets, etc. I see very accurate data on the PJRC site, so I'll consider any anomalies on my side.

Image1.jpg


The good news is that FreqMeasureMulti always reports a very consistent and correct number (my clock cycle measuring would occasionally hiccup and report a ~10% error).

I have a reliable working solution.

Thanks again.
 
Kris, glad you got the FMM library working for you. If the value on your Y axis is RPM, and X is seconds, I'm trying to understand why you have 0.5 RPM resolution. 900 rpm is 15 rev/sec, so you should have 15*4=60 (+/-1) periods per 1-second measurement, and very high resolution. Can you please post your code again?
 
Joe, Thanks for caring!


Note
1. Teensy 3.2, however 4.0 will not change the results
2. hall effect sensor is a 3144 on a small module board https://www.amazon.com/dp/B09723WH5V?psc=1&ref=ppx_yo2ov_dt_b_product_details
3. Trying a 3144 w/o the board where the out is connected directly to Teensy produces the same results
4. sensor is measuring a PC fan with 4 magnets glued to the blades (this is just a test setup so please don't laugh)
5. I don't think it's the possibility of the magnets not being exactly 90 degrees apart. I've run the test with 1 magnet, I get ~900 RPM but same irregular wave
6. scope shows very pretty square wave from the sensor, (no bounce issues).


Code:
#include <avr/io.h>
#include <FreqMeasureMulti.h>

#define RPM_PIN 22  // pin for the RPM
#define PICKUPS 4.0f

uint32_t RPMSum = 0.0;
uint32_t RPMCount = 0;
bool RPMStatus = false;
float WRPM = 0.0f;

elapsedMillis Update;

FreqMeasureMulti RPM;

void setup() {

  Serial.begin(115200);

  // Setup speed sensor
  RPMStatus = RPM.begin(RPM_PIN);
}

void loop() {

  if (RPM.available()) {
    RPMSum = RPMSum + RPM.read();
    RPMCount++;
  }

  if (Update > 500) {

    if (RPMCount > 1) {
      WRPM = 0.0;

      if (RPMCount >= 2) {
        WRPM = (60.0f / PICKUPS) * RPM.countToFrequency(RPMSum / RPMCount);
      }


      RPMSum = 0;
      RPMCount = 0;
    }
    Update = 0;

    Serial.println(WRPM);
  }
}

Untitled.jpg






IMG_8433.jpg





Results with 1 magnet
1-magnet.jpg
 
I think the problem is you are using uint32 for both sum and count, and the integer division is throwing away all of the resolution. Make sum double and it should help.
 
I used floats or doubles initially for that reason, but the lib returns these as uint32_t. There's no difference.

If I put a signal generator (square wave from my scope), the frequency is perfect w/o a glitch.

If I carefully watch the duty cycle of the 3144 sensor, it fluctuates a percent or so. I suspect the shape of the magnet when the sensor hits detects the magnetic field, etc. all contribute to slight variations.

I think +/- 1 RPM is close enough.

Thanks again.
 
The 3144 Hall Effect sensor itself requires at least 4.5V power. Perhaps try powering the module from 5V and pull the output up to 3.3V as you have it for compatibility with the T4.0 to see if it helps to stabilize the readings.
 
I used floats or doubles initially for that reason, but the lib returns these as uint32_t. There's no difference.

If I put a signal generator (square wave from my scope), the frequency is perfect w/o a glitch.

If I carefully watch the duty cycle of the 3144 sensor, it fluctuates a percent or so. I suspect the shape of the magnet when the sensor hits detects the magnetic field, etc. all contribute to slight variations.

I think +/- 1 RPM is close enough.

Thanks again.
If you're willing to try one more thing, try the line below, which avoids the integer division and truncation of (RPMSum / RPMCount).

Code:
        WRPM = (60.0f / PICKUPS) * RPMCount * RPM.countToFrequency(RPMSum);
 
Back
Top