FreqMeasure with Teensy4.0 - strange results

MorryStu

Member
Hi All

I am trying to get an RPM tachometer working for my lathe using FreqMeasure.h
the RPM range will be 1RPM-2000RPM

The encoder is a generic Chinese ( OMRON style ) 1000ppr with A,B & Z. The Z outputs 1 pulse per Rev with 5v output
The enc output on pin Z is high until the index mark is reach then drops low, briefly

I am feeding pin 22 via a voltage divider into the Teensy, voltage is about 3.15-3.2v. I have also feed the pin via a BC547 which also inverts the logic Thinking this might help with an inverted signal that just gives a single pulse every revolution

at 0 rpm it displays ( via serial ) a value of between 1-4...as the speed increases it will pick up the pulse and display RPM. Above 800rpm it displays the RPM accurately, but under 800 rpm it starts to display the rpm AND the 0 rpm value, as the rpm decrease the 0 rpm value is show a lot more between the RPM value

I have tried pullup/down resistors, adding a filter cap.....but nothing seems to change the results

The spindle encoding using the same enc works perfectly

The relevant code is after // RPM measure
Code:
void loop() {
  newSpindle = spindleEnc.read();
  if (newSpindle != oldPosition) {
    //Serial.print("Spindle = ");
    //Serial.print(newSpindle);
    //Serial.println();
    oldPosition = newSpindle;
  }

  {
    // RPM measure
    sum = sum + FreqMeasure.read();
    count = count + 1;
    if (count > 1) {
      float frequency = FreqMeasure.countToFrequency(sum / count);
      RPM = frequency/0.0166;
     if (RPM >0) {
      Serial.print("freq  ");
      Serial.println(RPM);
      display1.clear();
      display1.showNumberDec(RPM, false, 4, 4);
    }
      sum = 0;
      count = 0;
    }
  }

Outputs - NOTE freq =RPM....I just havent changed the code..
Code:
freq  4.19
freq  4.19
freq  588.49
freq  4.19
freq  4.19
freq  4.19
freq  4.19
freq  4.19
freq  588.75
freq  4.19
freq  4.19
freq  4.19
freq  4.19
freq  4.19
freq  4.19
freq  588.69
freq  4.19
freq  4.19
freq  4.19
freq  4.19
freq  4.19
freq  588.64
freq  4.19
freq  4.19
freq  4.19
freq  4.19
freq  4.19
freq  588.46
freq  4.19
freq  4.19
freq  4.19
freq  4.19
freq  4.19
freq  588.65
freq  4.19
freq  4.19
freq  4.19
freq  4.19
freq  4.19
freq  588.60
freq  4.19
freq  4.19
freq  4.19
freq  4.19
freq  4.19
freq  588.65
freq  4.19
freq  4.19
freq  4.19
freq  4.19
freq  4.19
freq  588.91
freq  4.19
Code:
freq  992.98
freq  992.56
freq  992.72
freq  992.59
freq  993.34
freq  992.79
freq  993.47
freq  993.20
freq  993.09
freq  993.26
freq  993.38
freq  4.20
freq  993.19
freq  993.24
freq  993.23
freq  993.21
freq  993.17
freq  993.33
freq  993.44
freq  993.42
freq  993.20
freq  993.32
freq  993.32
freq  993.47
freq  993.22
freq  993.34
freq  993.35
freq  993.07
freq  993.19
freq  993.31
freq  993.33
freq  993.52
freq  993.39
freq  993.43
freq  992.99
freq  993.61
freq  993.34
freq  993.29
freq  993.52
freq  993.46
freq  993.34
freq  993.23
freq  4.20
freq  993.38
freq  993.23
freq  993.30
freq  993.34
freq  993.04

Code:
freq  992.98
freq  992.56
freq  992.72
freq  992.59
freq  993.34
freq  992.79
freq  993.47
freq  993.20
freq  993.09
freq  993.26
freq  993.38
freq  4.20
freq  993.19
freq  993.24
freq  993.23
freq  993.21
freq  993.17
freq  993.33
freq  993.44
freq  993.42
freq  993.20
freq  993.32
freq  993.32
freq  993.47
freq  993.22
freq  993.34
freq  993.35
freq  993.07
freq  993.19
freq  993.31
freq  993.33
freq  993.52
freq  993.39
freq  993.43
freq  992.99
freq  993.61
freq  993.34
freq  993.29
freq  993.52
freq  993.46
freq  993.34
freq  993.23
freq  4.20
freq  993.38
freq  993.23
freq  993.30
freq  993.34
freq  993.04

Code:
freq  1659.76
freq  1659.56
freq  1659.33
freq  1659.93
freq  1659.31
freq  1659.36
freq  1659.55
freq  1659.46
freq  1658.83
freq  1659.54
freq  1659.41
freq  1659.77
freq  1658.81
freq  1658.86
freq  1659.27
freq  1659.12
freq  1659.18
freq  1659.19
freq  1658.67
freq  1658.65
freq  1659.37
freq  1658.81
freq  1658.06
freq  1658.97
freq  1658.68
freq  1658.97
freq  1658.47
freq  1658.13
freq  1658.87

Code:
freq  951.88
freq  951.92
freq  951.73
freq  952.52
freq  951.94
freq  951.84
freq  4.20
freq  951.95
freq  947.11
freq  932.62
freq  926.38
freq  902.29
freq  4.20
freq  862.85
freq  851.86
freq  4.20
freq  842.86
freq  842.22
freq  4.20
freq  842.00
freq  841.83
freq  4.20
freq  842.22
freq  842.54
freq  4.20
freq  842.21
freq  841.53
freq  4.20
freq  841.85
freq  842.81
freq  4.20
freq  842.51
freq  841.65

Full Code

Code:
/*
MM Pin0 = MMButt
   Pin1 = MMLed
TPI Pin2 = TPIButt
    Pin3 = TPILed
Feed Pin4 = FeedButt
     Pin5 = FeedLED
STOP Pin6 = STOPButt
     Pin7 = STOPLed
FWD Pin8 = FWDButt
    Pin9 = FWDLED
REV Pin10 = REVButt
    Pin11 = REVLED
DISP1 Pin16 = DISP1Clk
      Pin17 = DISP1Dat
DISP2 Pin18 = DISP2Clk
      Pin19 = DISP2Dat
ENC Pin20 = ENCClk
    PIN21 = ENCDat
    PIN22 = RPM - <FreqMeasure.h> derived from Encoder pin 31 as shared input from spindleEnc . Hardware set pin
SPINDLE Pin31 = SPINDLE_A <QuadEncoder.h> Uses hardware quad encoder pins
        Pin33 = SPINDLE_b
        */
#include <RotaryEncoder.h>  //Rotary_Encoder_KY-040_Fixed-main/RotaryEncoder.h https://github.com/ownprox/Rotary_Encoder_KY-040_Fixed.....NOTE. this installs as RotaryEncoder.h, so make sure to remove any existing library
#include <Bounce2.h>
#include <SPI.h>
#include <Wire.h>
#include <TM1637Display.h>
#include <QuadEncoder.h>
#include <Encoder.h>
#include <FreqMeasure.h>

#define MMButt_PIN 0  // WE WILL attach() THE BUTTON TO THE FOLLOWING PIN IN setup()
#define MMLed_PIN 1   // DEFINE THE PIN FOR THE LED :
#define TPIButt_PIN 2
#define TPILed_PIN 3
#define FEEDButt_PIN 4
#define FEEDLed_PIN 5
#define STOPButt_PIN 6
#define STOPLed_PIN 7
#define FWDButt_PIN 8
#define FWDLed_PIN 9
#define REVButt_PIN 10
#define REVLed_PIN 11
#define CLK1 16  //Display 1 Clk
#define DIO1 17  // Display 1 data
#define CLK2 18  // Display 2 Clk
#define DIO2 19  // Display 2 data
//define pin 22 for freqmeasure.h  - pin is assigned in the library and is a FIXED HARDWARE PIN

TM1637Display display1(CLK1, DIO1);
TM1637Display display2(CLK2, DIO2);
QuadEncoder spindleEnc(1, 31, 33, 1);  // Encoder on channel 1 of 4 available // Channel (1), Phase A (pin0), PhaseB(pin1), Pullups Req(1=intPullup). FIXED HARWARE PINS


Bounce MMdebouncer = Bounce();
Bounce TPIdebouncer = Bounce();
Bounce FEEDdebouncer = Bounce();
Bounce STOPdebouncer = Bounce();
Bounce FWDdebouncer = Bounce();
Bounce REVdebouncer = Bounce();

int ledState = LOW;  // SET A VARIABLE TO STORE THE LED STATE
int Counter = 100, LastCount = 0;
float Countermm = Counter;
float Countertpi = 0;
float Counterfeed = 0;
float RPM = 0;
long newSpindle = 0;
long oldPosition = -999;  // for spindle
float Pitchmm;            // used for metric leadscrew calculation and stepper config
float Pitchtpi;

double sum = 0;
int count = 0;

enum MODE  // available modes
{
  MM,
  TPI,
  FEED,
};
MODE mode = MM;

void RotaryChanged();
RotaryEncoder Rotary(&RotaryChanged, 20, 21, 28);  // Pins 20 (DT), 21 (CLK), 28 (SW) - 28 is just a null number as the (SW) is not used

void RotaryChanged() {
  const unsigned int state = Rotary.GetState();
  if (state & DIR_CW)
    Counter++;
  if (state & DIR_CCW)
    Counter--;
}


void setup() {
  Serial.begin(19200);
  pinMode(22,INPUT_PULLUP);
  FreqMeasure.begin();
  Serial.println("T.I.S.M");  // Death to ART tour, featuring Great Trucking Songs of The Renaissance

  display1.setBrightness(0x0f);
  display2.setBrightness(0x0f);
  delay(500);

  display1.clear();
  display2.clear();
  display1.showNumberDecEx(8888, 0b01000000, false, 4, 4);  //Displays 10.11
  display2.showNumberDec(1234, false, 4, 0);                // shows 10 on far left

  MMdebouncer.attach(MMButt_PIN, INPUT_PULLUP);  // Attach the debouncer to a pin with INPUT_PULLUP mode
  MMdebouncer.interval(25);                      // Use a debounce interval of 25 milliseconds
  pinMode(MMLed_PIN, OUTPUT);                    // Setup the LED
  digitalWrite(MMLed_PIN, ledState);

  TPIdebouncer.attach(TPIButt_PIN, INPUT_PULLUP);
  TPIdebouncer.interval(25);
  pinMode(TPILed_PIN, OUTPUT);
  digitalWrite(TPILed_PIN, ledState);

  FEEDdebouncer.attach(FEEDButt_PIN, INPUT_PULLUP);
  FEEDdebouncer.interval(25);
  pinMode(FEEDLed_PIN, OUTPUT);
  digitalWrite(FEEDLed_PIN, ledState);

  STOPdebouncer.attach(STOPButt_PIN, INPUT_PULLUP);
  STOPdebouncer.interval(25);
  pinMode(STOPLed_PIN, OUTPUT);
  digitalWrite(STOPLed_PIN, ledState);

  FWDdebouncer.attach(FWDButt_PIN, INPUT_PULLUP);
  FWDdebouncer.interval(25);
  pinMode(FWDLed_PIN, OUTPUT);
  digitalWrite(FWDLed_PIN, ledState);

  REVdebouncer.attach(REVButt_PIN, INPUT_PULLUP);
  REVdebouncer.interval(25);
  pinMode(REVLed_PIN, OUTPUT);
  digitalWrite(REVLed_PIN, ledState);

  spindleEnc.setInitConfig();  //Loads default configuration for the encoder channel
  spindleEnc.init();           //Initializers the encoder for the channel selected
}

void loop() {
  newSpindle = spindleEnc.read();
  if (newSpindle != oldPosition) {
    //Serial.print("Spindle = ");
    //Serial.print(newSpindle);
    //Serial.println();
    oldPosition = newSpindle;
  }

  {
    // average several readings together - gives slower output and averages out readings
    sum = sum + FreqMeasure.read();
    count = count + 1;
    if (count > 1) {
      float frequency = FreqMeasure.countToFrequency(sum / count);
      RPM = frequency/0.0166;
     if (RPM >0) {
      Serial.print("freq  ");
      Serial.println(RPM);
      display1.clear();
      display1.showNumberDec(RPM, false, 4, 4);
    }
      sum = 0;
      count = 0;
    }
  }
  readButton();  // read button and update mode
  switch (mode) {
    case MM:
      mm();
      break;
    case TPI:
      tpi();
      break;
    case FEED:
      feed();
      break;
  }
}



// read button and update mode

void readButton() {

  MMdebouncer.update();      // Update the Bounce instance
  if (MMdebouncer.fell()) {  // Call code if button transitions from HIGH to LOW
    ledState = !ledState;    // Toggle LED state
    digitalWrite(MMLed_PIN, ledState);
    switch (mode)
    case MM:
    default:
      break;
  } else

    TPIdebouncer.update();
  if (TPIdebouncer.fell()) {
    ledState = !ledState;
    digitalWrite(TPILed_PIN, ledState);
    switch (mode)
    case TPI:
    default:
      break;
  } else
    FEEDdebouncer.update();
  if (FEEDdebouncer.fell()) {
    ledState = !ledState;
    digitalWrite(FEEDLed_PIN, ledState);
    switch (mode)
    case FEED:
    default:
      break;
  }
}
void mm() {
  //Serial.println("void mm");
  if (LastCount != Counter) {
    //Serial.println(Counter);
    Pitchmm = Counter / 100.00;  //Metric threads have dec point values. i.e 1.25mm
    display2.clear();
    display2.showNumberDecEx(Counter, 0b10000000, true, 3, 1);  // Use Counter value for deciml point placement on LED disp
    //Serial.println(Pitchmm);
  }
}

void tpi() {
  Serial.println("void tpi");
  if (LastCount != Counter) {
    Serial.println(Counter);
    Pitchtpi = Counter;  // value in TPI
    display2.clear();
    display2.showNumberDec(Pitchtpi, false, 2, 1);
  }
}

void feed() {
  Serial.println("feed tpi");
  if (LastCount != Counter) {
    Serial.println(Counter);
    Pitchtpi = Counter;  // value in TPI
    display2.clear();
    display2.showNumberDec(Pitchtpi, false, 2, 1);
  }
}
 
So I tried the serial output example and it worked fine, same hardware and connection
Code:
/* FreqMeasure - Example with serial output
 * http://www.pjrc.com/teensy/td_libs_FreqMeasure.html
 *
 * This example code is in the public domain.
 */
#include <FreqMeasure.h>

void setup() {
  Serial.begin(57600);
  FreqMeasure.begin();
}

double sum=0;
int count=0;

void loop() {
  if (FreqMeasure.available()) {
    // average several reading together
    sum = sum + FreqMeasure.read();
    count = count + 1;
    if (count > 1) {
      float frequency = FreqMeasure.countToFrequency(sum / count);
      float RPM;
      RPM = frequency / 0.016;
      Serial.println(RPM);
      sum = 0;
      count = 0;
    }
  }
}


I also modified the code for a work around, which seems to work, but does not fix the problem
Code:
// RPM measure

  sum = sum + FreqMeasure.read();
  count = count + 1;
  //if (count > 0) {
    float frequency = FreqMeasure.countToFrequency(sum / count);

    //RPM = frequency/0.016;
    if (frequency >1 ){
    RPM = frequency/0.016;
    Serial.print(frequency);
    Serial.print ("----");
    Serial.println(RPM);
    display1.clear();
    display1.showNumberDec(RPM, false, 4, 4);
    }
    sum = 0;
    count = 0;
 
So I tried the serial output example and it worked fine, same hardware and connection
Yes, because
Code:
  if (FreqMeasure.available()) {
…
    }
Your code does a read() regardless of whether a new value is actually available; that won’t work.

Ate you really fitting a 1000ppr encoder, then only using the 1ppr index pulse to calculate the RPM? Seems an odd way to go about things to me…
 
if (FreqMeasure.available()) {

Of course......I knew it would be simple

Yes, I really am using a 1000ppr encoder and the 1ppr resolution for RPM.

Now for the reason.

If you use 1000ppr ( say you use 'A' or 'B' ) the individual pulses or not the same time spacing for each position, so you end up with a modulation on the output, so because FreqMeasure looks at the time between each pulse you end up with a display that changes so fast its unreadable. Yes you could average it out, but slows it down, remember you are dealing with 1000 pulses. And because the RPM value is only going to be an indicative display 1ppr works perfectly to give a nice smooth readable display

I use the A&B for the lathe spindle position encoder

Encoders , like the cheap Chinese ones, use a magnetic position sensor with a rotating diametric magnet. There is a diagram somewhere on the interwebs showing how the magnet fields influences the time spacing between each pulse. It happens every quadrant, so if you plot the output you get a sine wave equal to the time difference. Its a bit like phase modulation
 
FreqMeasure will give you the revolutions per second. For RPM you need to multiply it by 60.

I made my own Electronic Lead Screw controller using a Teensy4.1 and the ILI9341. I use the 1024 PPR quadrature encoder high speed output and simply measure how many counts I get in 50ms. I mean, the count is already there, why not use it? Then the RPM is alpha filtered. It works well enough, better than the built in RPM on the lathe.

Any phase modulation on the encoder is averaged out. Besides, if you have significant phase modulation on a lathe, you have a bad motor controller, or no chuck at all. Most any inertial load will smooth it out.

I've used my ELS for two years now, there's no perceptible issues for either fine or coarse pitch threads. I have implemented feeding, threading, feed to stop, and thread to stop, for all feeds and threads, metric and imperial. Now working on multi-start threading.

This is short video of my thread to stop. 1.25 mm thread, my lathe has 12 TPI lead screw. Automatically pauses to sync to spindle. Uses the DRO scales and spindle angle to start the thread in the right place every time. Don't have to worry about keeping the half nuts engaged, because it doesn't matter... I disengage the half nuts every pass, and engage at an arbitrary point on the thread dial. As you may know, on a normal lathe that would result in a totally botched thread.
 
This is my controller. I could have easily done a touch screen controller, but I like the tactile feel off buttons and knobs.
I went 1000ppr because the stepper motor is closed loop with a 1000ppr encoder. It will be driving a 4mm leadscrew so this actually makes the control fairly easy. As the spindle and leadscrew need to be insync the halfnut needs to stay engaged, but that is not really a problem. I could use a dro feed, but I just want to keep it nice and simple. I already have TouchDRO.

For me, 1ppr is perfect for the lathe RPM, for reasons mentioned before. I am looking for an etched glass optical encoder which wont suffer the same issue as the mag encoder.

Be interested to see your code and compare it
lath controller.jpg
 
Last edited:
Since you only have one index pulse per revolution, and a presumed measurement interval of one second, the minimum RPM you can measure is 60, since that's 1 rev/second. If you want to measure lower RPM, you need more pulses per rev, or a longer time interval.

Nice box. Much snazzier than mine.

As an observation, by polling the encoder in a loop, you need to ensure that your loop time is lower than the time between encoder pulses. Otherwise you will miss counts. An interrupt based approach doesn't have that limitation. As you add more features to your code, a polled implementation will run into trouble, especially at high RPM. Slipping counts will make a mess for you, because it's hard to debug.

I use an ISR for my encoder. EncoderTool works great for my ELS as I can customize the callback routine. The Bresenham algorithm is implemented within that callback. It executes very quickly, leaving loads of time for all the rest of the code, and it's never missed a count. It's been tested to 2200 RPM on my lathe, my maximum chuck speed, but it should run up to 5000 RPM, according to my electronic tests. (Used another Teensy to generate quadrature outputs and increased the "RPM" to see when things unravel.)
 
The reason I use FreqMeasure and QuadEnc is because they use hardware ( but only on specific pins ) Takes all the fun out of setting up ISR's etc. The hardware does all the work and lets the rest of the code do its thing. but you make a valid point. It would never work on an Arduino as they are just to slow

Will have a look at EncTool. Thanks for letting me know about it

So far with FreqMeasure.h it works down to 1 rpm. FreqMeasure reads the time between pulses. it can read down to .1Hz
 
Back
Top