How to Get Data from Rotary Encoder at VERY High Speeds (>4500 RPM)

Equipment:

  1. Arduino ATmega2560

  2. Rotary Encoder: https://www.amazon.ca/gp/product/B08RS6M32J/ref=ppx_yo_dt_b_asin_title_o06_s01?ie=UTF8&psc=1

  3. Manual Curved Treadmill

Background:

I have a manual treadmill and I want to use my Arduino to capture distance and speed data. To do this, my approach was to use a rotary encoder, attach it to a skateboard wheel (older picture is attached, I'm using a bigger wheel now and there's no photo for that right now) such that when I run on the treadmill, the wheel spins proportionately.

In the code below, whenever I run it, the counter variable would accumulate to about 2000 (pulses) every time the wheel makes a full rotation. The wheel diameter = 6cm, therefore, it's circumference = 18.8495559215 cm.

This means for every centimeter I travel, there are about 106 pulses. (2000 pulses/18.8495559215 cm = ~106 pulses/cm).

Here's the code: (I got it from this website and only changed 2 lines of code because my sensor doesn't need to go in reverse; the treadmill only travels in one direction - How to connect optical rotary encoder with Arduino - Electric Diy Lab)

volatile long temp, counter = 0; //This variable will increase or decrease depending on the rotation of encoder
    
void setup() {
  Serial.begin (9600);

  pinMode(2, INPUT_PULLUP); // internal pullup input pin 2 
  
  pinMode(3, INPUT_PULLUP); // internalเป็น pullup input pin 3
   //Setting up interrupt
  //A rising pulse from encodenren activated ai0(). AttachInterrupt 0 is DigitalPin nr 2 on moust Arduino.
  attachInterrupt(0, ai0, RISING);
   
  //B rising pulse from encodenren activated ai1(). AttachInterrupt 1 is DigitalPin nr 3 on moust Arduino.
  attachInterrupt(1, ai1, RISING);
  }
   
  void loop() {
  // Send the value of counter
  if( counter != temp && counter % 106 == 0 ){ // Only print something if the wheel travels in increments of 1 cm.
  Serial.println (counter);
  temp = counter;
  }
  }
   
  void ai0() {
  // ai0 is activated if DigitalPin nr 2 is going from LOW to HIGH
  // Check pin 3 to determine the direction
  if(digitalRead(3)==LOW) {
  counter++;
  }else{
  counter++; // The original code said counter-- but my treadmill only goes one direction and the this variable starts to decrease when I reach a certain speed (around 5 kmk/h or (3.1 mph) or so). After I changed it to counter++, this issue was resolved however, the arduino serial monitor kept freezing at high speeds.


  }
  }
   
  void ai1() {
  // ai0 is activated if DigitalPin nr 3 is going from LOW to HIGH
  // Check with pin 2 to determine the direction
  if(digitalRead(2)==LOW) {
  counter++; //// The original code said counter-- but my treadmill only goes one direction and the this variable starts to decrease when I reach a certain speed (around 5 kmk/h or (3.1 mph) or so). After I changed it to counter++, this issue was resolved however, the arduino serial monitor kept freezing at high speeds.
  }else{
  counter++;
  }
  }

The Problem

*To get the speed is easy, it's just not in this code right now. Accomplished by using t1 and t2 variables and the Millis() function.

When I walk on the treadmill, I typically walk at about 4 or 5 km/h (3.1 mph). This works fine. However, the issue is I want to be able to sprint over 30 km/h (18.6 mph) and have the code be able to support up to 50 km/h (31 mph). Right now, I'm unable to run at speeds over 5 km/h without the serial monitor freezing periodically, or there being inaccurate data.

At these speeds, I would need the sensor to support the wheel moving at over 4500 RPM -> 75 RPS or (75 RPS x 2000 P/R = 150,000 Pulses/Second)

I have absolutely no idea why this issue is happening and how I should approach this to achieve my desired speeds.

Possible reasons why this is happening:

  1. I'm not an expert at Arduino at all and am just starting to understand things like interrupts. I have my pins in 2 and 3. Should I leave this alone or change it?

  2. If you click the Amazon link, you'll see that there is a 3rd Orange "Z" wire which I have absolutely no idea what it does. The few tutorials I could find only involve the two "A" and "B" wires. Maybe incorporating the "Z" wire would point me in the right direction?

  3. My Rotary encoder sensor is using the Arduino's 5V. If you click on the Amazon link, you'd see that it actually supports up to 26V. Should I find an external power source that allows up to 26V and pair it with a relay? Not sure if the extra voltage will help me.

  4. I'm not sure why by default, the sensor counted a full rotation for the wheel to be 2000 pulses. On Amazon, you'll see that it supports the following Pulses/Revolution:

Resolution (pulse/rotation) : 100, 200, 300, 360, 400, 500, 600, 1000, 1024, 1200, 2000, 2500, 3600, 5000 (optional)

How can I change my 2000 P/R to 5000 P/R for example? Would this help?

Summary

I want to use my Arduino and rotary sensor to collect speed and distance data from my manual treadmill. At slow speeds (up to 5 km/h), the code works fine, but at high speeds, the data is highly inaccurate, and the serial monitor freezes every few seconds. I want the Arduino to support speeds up to 50 km/h which is about 4500 RPM. If my solution with the rotary sensor is not feasible, I am 100% open to other ideas as I just want the speed and distance data. I'll purchase any new equipment that's necessary but ideally, I'd like to work with what I have right now.

Thank you for your help!

Unless you have a custom treadmill that can move two different directions on a random basis, you DO NOT need to use TWO encoder sensors.
Paul

Have you considered using one of the timers as a counter? That will take away the interrupt overload that is stalling your code.

I'm only using 1 encoder sensor

Can you elaborate on this please?

Yes. the Z pulse is put out only once per revolution of the encoder shaft. I would think that you should be able to work with that.

Your next option is to only use the A or B channel, not both, although I can't imagine that you need that kind of resolution.

1 Like

This makes a lot of sense. Thank you! Just a follow-up question: Is there any way to change the number of pulses per rotation in Arduino? Or is this something I can't change? I want to be as accurate as possible when running. Ideally, I'd like to record every 106 pulses (1 cm).

There are different ways to read the output of the encoder. It is called a "quadrature" encoder because there are four distinct transitions and you can read one, two or four of them.

There are many tutorials online. For example
https://howtomechatronics.com/tutorials/arduino/rotary-encoder-works-use-arduino/

My mistake, I didn't ask my question properly. On Amazon, the Rotary Encoder specifies that I can choose from the following resolutions:

Resolution (pulse/rotation) : 100, 200, 300, 360, 400, 500, 600, 1000, 1024, 1200, 2000, 2500, 3600, 5000 (optional)

Right now, by default, it seems to be set at 2000 P/R. How would I set it to 100 P/R?

I believe that is specifying different models of the encoder.

1 Like

Have you considered a different sensor such as a simple break beam device? You could use a disc with 1, 2 or 4 slots in it to do the same thing with a lot less pulses.

Do you need to know distance travelled down to ~1/100th of a cm?

1 Like

Product description
Maximum Response Frequency(*3): 100kHz

Yes, but you are using BOTH outputs. Only ONE is necessary for RPM measurements.
Paul

Jack up the baud rate - 115200 is good. Also, print less frequently, maybe every meter.

You could use a hardware counter to reduce the pulse rate from the encoder
You could use an bigger drive wheel to reduce the pulse rate or paint a marker in the belt and use and optical pickup instead to give you a slower pulse rate .

There maybe an issue with a small wheel skidding and not giving a true pulse rate , check your encoder is rated for that rpm - you don’t want it to fail in the first half hour .

You have created an unnecessary problem by using an unsuitable device to getthe data you want.
The rotary encoder (assuming it doesnt slip) will give you a position precision of 0.1mm
and pulses at a huge rate.
You could paint reflective dots on your treadmill at 10cm intervals and use an optical sensor to detect them, (or eg magnets and a reed switch) and reduce your data rate by a factor of 100 - and still get very precise speed and distance readings.

Little performance increase you can get by replacing digitalRead with reading directly, but that's a tiny benefit and doubt it can help you. The cheap solution to your problem is to use chips which divide input frequency by factor of 2, 4, 8, 16, they are very cheap, but sadly i don't know their names, in my language it's "counter". Way better than to buy another, lower precision encoder or STM32 controller.

You should use the once-per-revolution Z wire and only count pulses on this. You would get 1 pulse per 18.9 cm, and at 4500rpm only 4500 pulses per minute, or 75 pulses per second.

Although you lose the troublesome high-frequency 10.6pulse/mm or 9.4 micron resolution and precision, since the 1ppr Z signal is precisely in sync with the 2000ppr signal, you get the same accuracy.

refereing to your other thread:

I have seen quite a lot of examples where demo-code about interrupts prints from inside of the ISR.

You should never use serial.print from inside an ISR
You use the serial interface with at the very slow speed of 9600 baud.
If you turn your encoder fast the printing of one call of the isr is not yet finished before the next pulse rushes in. => you don't see a "LOW" printed

without any printing at all an isr as short as

void myISR() {
myCounter++
}

and everything else is done outside the ISR can count 10000 of pulses per second.

What kind of code fits best to your application depends on your application
at high frequencies you capture the amount of pulses for a constant time-interval
for low frequencies you measure the time between two pulses.

best regards Stefan

1 Like

This topic was automatically closed 180 days after the last reply. New replies are no longer allowed.