Rotary encoder pulses missing > 60kHz

Hi,

For a DIY project at home (making an inverted pendulum) I'm getting some trouble with the readout of a quadrature encoder. I've got 2 encoders one with 1000PPR and another with 5000PPR. The first one will be used as DC servo feedback and position while the second one is used for the rotation of the pendulum.

At first, I'm doing some initial tests, writing a code to read the encoder values. After some browsing, I came up with using the ISR(interrupt service routine) to be sure that all pulses are counted.

At first, it seemed to work but at a higher revolution per minute, I'm losing pulses. At full speed, the motor turns 3000 RPM which is 50 revs/sec and thus 50kHz or 100kHz measured at both edges.

Ill will add some graphs tomorrow, I can use a function generator with a 20kHz signal for testing.

I've looked in the datasheet of the encoder, voltage output and according to the datasheet with build in 2 kOhm pull-up resistor.
Datasheet-. https://www.nemicon.co.jp/store-en/wp-content/uploads/pdf/noc-s.pdf

I tried working with a FastDigitalRead with the use of timers but that did not help. Other solutions that I've seen so far is using an HCTL-2022 which is made for decoding.

I don't see the drawback why it isn't working, since it works at 16 MHz, even if I need 100 clock cycles in the ISR I still have 160 kHz for sampling. What is a crude rule of thumb for the max read speed?

Thanks in advance, the code is included below

int pulse = 0;        //int since it can reach 5000 - 20000
int encoderPos = 0;     // max encoder pulses 2^15 (16 bit) 32768dec
int RotationCount = 0;


void setup()
{
  DDRD = DDRD | B00000000;
  DDRD = DDRD & B00000011; // Set DDr register to input & leave D0,D1 unharmed
  PORTD = PORTD & B00000011;
  PORTD = PORTD | B00010100;
  attachInterrupt(0, doEncoder, FALLING);  // encoder pin on interrupt 0 (pin 2)
  Serial.begin(9600);
}

void loop()
{
 uint8_t oldSREG = SREG; //Interrupts need to be disabled while performing a read-modify-write
 cli(); 
 SREG = oldSREG;
    Serial.print("   encoderPos : ");
    Serial.println(encoderPos);
 }
 
void doEncoder() 
{
bool encoderPinA  = PIND & B00000100;
bool encoderPinB  = PIND & B00010000;

 if (encoderPinA == encoderPinB)   
   encoderPos++; // count up if both encoder pins are the same on pin change interupt
 else                                       
   encoderPos--; //count down if pins are different
 
 if (encoderPos > 4999) {
    encoderPos = encoderPos - 5000;
    RotationCount++;
  } else if (encoderPos < 0) {
    encoderPos = encoderPos + 5000;
    RotationCount--;  
}
}

Interrupts have over 5 usec overhead, 84 cycles.

Polling won't get twice that and is not in synch with encoder signals.

The serial prints are the slowest thing in your code, converting binary to decimal text takes lots of cycles.

Also, 9600 baud? That clears the 64 byte print buffer at 960 cps and you fill it a bit faster at times? When the buffer fills, execution locks on getting whatever you last printed sent which with your code, I wonder how.

void loop()
{
 uint8_t oldSREG = SREG; //Interrupts need to be disabled while performing a read-modify-write
 cli();
 SREG = oldSREG;
    Serial.print("   encoderPos : ");
    Serial.println(encoderPos);
 }

You read the status register, disable interrupts then set the status register to what you read just why? To enable interrupts?

Serial uses interrupts.

You need to make the variables you use in your IRQ volatile.

volatile int encoderPos = 0; // max encoder pulses 2^15 (16 bit) 32768dec
volatile int RotationCount = 0;

In loop() you could compare encoderPos to last known and print the new values. Perhaps use a shorter label like "Pos: ".

You might get into the 90K range.

For really high pulses number per second, there is a reliable solution with an hardware quadrature decoder (reply #84):

https://forum.arduino.cc/index.php?topic=140205.75

GoForSmoke:
Also, 9600 baud? That clears the 64-byte print buffer at 960 cps and you fill it a bit faster at times? When the buffer fills, execution locks on getting whatever you last printed sent which with your code, I wonder how.

What do you mean by this? I can not find useful links about buffers in combination with serial communication. What clears at 960 cps(Abbrivation?) and how fast do I fill it?

Second, If the ISR is at least 5 usec(200 kHz) I'm worried about attaching a second encoder ISR2. And if I reach let's say 50 kHz I'm freezing the program for 0.25 seconds per second? This looks like a lot of burden on the MCU.

If you think that such a huge resolution is required, you can split your project and use one dedicated controller for each encoder. Else it might help to use e.g. 28 PPR encoders instead, or to place the encoders after the gearbox.

Koenzo:
What do you mean by this? I can not find useful links about buffers in combination with serial communication. What clears at 960 cps(Abbrivation?) and how fast do I fill it?

Second, If the ISR is at least 5 usec(200 kHz) I'm worried about attaching a second encoder ISR2. And if I reach let's say 50 kHz I'm freezing the program for 0.25 seconds per second? This looks like a lot of burden on the MCU.

Serial data takes time for each character to send 1 start bit, 8 data bits, 1 stop bit. The baud rate you set in Serial.begin() is how many bits per second get sent and there's 10 bits per char and you chose 9600 baud making 960 characters sent per second at a bit over 1 millisecond each.

In a few microseconds a sketch can fill the 64 character serial output buffer with a print statement waiting to get what the sketch wants printed into the buffer and will execute nothing else but interrupts until it can finish getting the characters in code copied into the buffer. Buffer is full? Every so often the character in the serial port finishes sending and the next to send is taken from the output buffer and 1 more character gets into the buffer from code, after a while the block may clear and the next print statement gets a chance. Lesson is, don't print too much as serial does have a cost.

As far as interrupts, the overhead is known from the cycles that interrupt code takes. Nick counts them in his tutorial.

Check Nick's site if you want more details about any of this.

The Arduino Zero runs faster as does the Teensy Zero, IIRC 48MHz. The Teensy 3.6 can run up to 240MHz.