I am using an Arduino UNO and an optical encoder that is rated at 2000 pulses per revolution (PPR). The encoder’s phase A is on pin 4 (PCINT20) and phase B is on pin 5 (PCINT21). The PCINT’s are set up as fast as I know how to do that but the system is only accurate at about 2-3 RPM. Faster than than and it is missing pulses.
When I make a slow revolution in either direction, the result is 360 for forward and -360 for reverse, just as it should be. I can slowly go several turns forward then the same number of turn in reverse and it remains accurate coming back to 0 degrees. Any faster and it returns home at something significantly different than 0 degrees.
If my math is correct, the interrupts from the encoder are actually happening at 4 x the PPR rate even though I am only counting a pulse after 4 interrupts. That means 8000 interrupts/rev x 3 RPM = 24000 interrupts/second. That is around 40usec each.
I don’t know how long the loop and ISR routine takes to execute, but it seems like 40usec might be about right for everything that has to happen.
If everything I have said above is accurate, this reaches the max capability of the Arduino to process the pulses. If I want to go faster, I need more HP that an UNO has.
Two questions:
1: Is what I have calculated correct?
2: Is this code about as efficient as it can be?
#include <Arduino.h>
#define PENDULUM_ENCODER_A 4 // PCINT20
#define PENDULUM_ENCODER_B 5 // PCINT21
double pulses;
volatile int32_t angle;
volatile static byte old_port_d; // saved port d configuration
static byte PCMask; // interrupt mask
void setup()
{
Serial.begin(115200);
pinMode(PENDULUM_ENCODER_A, INPUT_PULLUP); // PCINT20
pinMode(PENDULUM_ENCODER_B, INPUT_PULLUP); // PCINT21
// setup PCINT20 and PCINT21 on pins 4 & 5
old_port_d = PIND; // save the old port D configuration
PCMSK2 |= (1 << PCINT20); // enable PCINT20 pin 4
PCMSK2 |= (1 << PCINT21); // enable PCINT21 pin 5
PCMask = PCMSK2; // set the mask
PCICR |= (1 << PCIE2); // enable PCINT on portd.
}
void loop()
{
Serial.println(angle);
}
volatile int MSB, LSB, encoded, lastEncoded, sum;
ISR(PCINT2_vect)
{
MSB = PIND & B00010000; // digitalRead(Pin 4); MSB
LSB = PIND & B00100000; // digitalRead(Pin 5); LSB
encoded = (MSB >> 3) | (LSB >> 5); // convert the 2 pin value to single number
sum = (lastEncoded << 2) | encoded; // adding it to the previous encoded value
if (sum == 0b1101) // sum = 0b1101 2000/rev forward
pulses ++;
if (sum == 0b1110) // sum = 0b1110 2000/rev reverse
pulses --;
lastEncoded = encoded; // store this value for next time
angle = pulses / 5.5555555556; // 2000 PPR / 360 = 5.5555555556
}
Fixed point math is somewhat beyond me at present but if it would substantially improve this up to say 5 or 6 rpm, it would probably be worth the learning curve.
PickyBiker:
I see your point but this version always counts backwards and -720/rev not 360/-360.
What version?
If you have a revised version of your program please post it in your next Reply so we can keep up with you.
It appears to me that (encoded == 0b01) will be true in both directions.
Maybe I don't understand the purpose of your sum = (lastEncoded << 2) | encoded; calculation.
Please post a link to the datasheet for the encoder.
It is my understanding (perhaps faulty) that in one direction the values are 01 and in the other direction 10 - but maybe that is only true if you have interrupts on both pins and that may be to much work for the Arduino with a high resolution encoder.
PickyBiker:
If my math is correct, the interrupts from the encoder are actually happening at 4 x the PPR rate even though I am only counting a pulse after 4 interrupts. That means 8000 interrupts/rev x 3 RPM = 24000 interrupts/second. That is around 40usec each.
1: Is what I have calculated correct?
Actually your Maths is out by a factor of 60, RPM = Revolutions Per Minute.
3 RPM = 1/20th of a revolution per second or roughly 400 interrupts per second i.e. once every 2.5ms (assuming the encoder generates a square wave offset by 90o between the two phases).
IANCrowe, you are correct. My mistake was in claiming RPM when it is actually Revs Per Second. I have a little plastic arm on the encoder and I am spinning with my finger it at about 3 times per second. Thanks for catching that typo.
I have this code working now and it is accurate as fast as I can spin it with my finger, probably about 5 RPS. The comments re: doing math in the ISR were right on.
The math is now in the loop and I have changed all the register variables to static volatile bytes. I also returned
to the original method of determining direction.
All is well now. Thank you very much for the great help.
Here is the result:
#include <Arduino.h>
#define PENDULUM_ENCODER_A 4 // PCINT20
#define PENDULUM_ENCODER_B 5 // PCINT21
boolean checkAngle = false;
volatile int32_t angle, pulses;
static volatile byte old_port_d, PCMask; // saved port d configuration // interrupt mask
static volatile byte MSB, LSB, sum;
static volatile byte encoded, lastEncoded;
void setup()
{
Serial.begin(115200);
pinMode(PENDULUM_ENCODER_A, INPUT_PULLUP); // PCINT20
pinMode(PENDULUM_ENCODER_B, INPUT_PULLUP); // PCINT21
// setup PCINT20 and PCINT21 on pins 4 & 5
old_port_d = PIND; // save the old port D configuration
PCMSK2 |= (1 << PCINT20); // enable PCINT20 pin 4
PCMSK2 |= (1 << PCINT21); // enable PCINT21 pin 5
PCMask = PCMSK2; // set the mask
PCICR |= (1 << PCIE2); // enable PCINT on portd.
}
void loop()
{
double copyOfPulses;
if (checkAngle == true)
{
noInterrupts();
copyOfPulses = pulses;
checkAngle = false;
interrupts();
angle = pulses / 5.55555555556;
Serial.println(angle);
}
}
ISR(PCINT2_vect)
{
MSB = PIND & B00010000; // digitalRead(Pin 4); MSB
LSB = PIND & B00100000; // digitalRead(Pin 5); LSB
encoded = (MSB >> 3) | (LSB >> 5); // convert the 2 pin value to single number
sum = (lastEncoded << 2) | encoded; // adding it to the previous encoded value
if (sum == 0b1101) // sum = 0b1101 2000/rev forward
pulses ++;
if (sum == 0b1110) // sum = 0b1110 2000/rev reverse
pulses --;
lastEncoded = encoded; // store this value for next time
checkAngle = true;
}