Quadrature Encoder.h Issue

Hello,

I am having an issue with PJRC's Encoder.h library, found linked below.

https://www.pjrc.com/teensy/td_libs_Encoder.html

I am attempting to read 7 quadrature encoders. Using this library, 5 out of 7 encoders are working properly.

I have scoped all these lines/pins to ensure the encoders are working properly and are consistent with the object declaration. I have checked all the pins to make sure they are properly digitalReading concluding that the Arduino Due is working properly. Does it have to something with the Encoder.h library whereby it does not accept RX1 (19), TX1 (18), RX2 (17), TX2 (16)? or am I completely blind?

The encoders that are not working, I have marked in my code with a "//*".

#define ENCODER_OPTIMIZE_INTERRUPTS

#include <Encoder.h>

// Encoder Signals

long m1p = 0;
long m2p = 0;
long m3p = 0;

long s1p = 0;
long s2p = 0;
long s3p = 0;
long s4p = 0;

Encoder m1l(21, 20);
Encoder m2(19, 18); //*
Encoder m3(17, 16); //*

Encoder s1(52,50);
Encoder s2(24, 22);
Encoder s3(53, 51);
Encoder s4(25, 23);

void setup()
{

}

void loop()
{ 
  m1p = m1.read();
  m2p = m2.read();
  m3p = m3.read();
  
  s1p = s1.read();
  s2p = s2.read();
  s3p = s3.read();
  s4p = s4.read();
}

The interrupt_pins file in utility folder of the library it says those pins are valid for the Due, but it also says that the Arduino Due is untested.

I think that #define ENCODER_OPTIMIZE_INTERRUPTS uses a reduced set of available interrupts.

Do the two encoders work if us use #define ENCODER_USE_INTERRUPTS or #define ENCODER_DO_NOT_USE_INTERRUPTS and let the pins be polled?

Hello Cattledog,

I tried a couple of methods.

  1. Commenting out ENCODER_OPTIMIZE_INTERRUPTS. This did not change the results - the 2 encoders that weren't working were still not working.

  2. Trying out ENCODER_USE_INTERRUPTS. Same results as above.

  3. Trying out ENCODER_DO_NOT_USE_INTERRUPTS. The encoders worked but performance was absolutely horrid. Lots of missed counts were obviously shown.

Do you have any documentation on this library? From the website I linked previously, these #defines you provided me were never mentioned. Can you diagnose the problem from what I shared with you?

Looking at the Encoder.h file, it seems there is a interrupt_config.h file it includes if you #define ENCODER_OPTIMIZE_INTERRUPTS. Can a solution be to use add these pins to the config file? I have never attempted to change libraries before and this library is written in "ARM language", so it would be difficult for me to modify it - but I can give it a shot.

Thank you again,

I ended up making my functions for the quadrature counting. Works fine now.

Good news.

While you posted this information I was typing up a response suggesting that you work without the library, because in my experience the quadrature encoders are simple enough to code for directly.

Can you please post the code you wound up using? Are you using interrupts or polling within the loop?

Are you are using digitalRead in your functions? The code can be made significantly faster by using direct port reading and bit masks (or bitRead if it works on the DUE). The port assignments and the bit masks for the DUE are different than for the AT 328 Arduino's so if your using that approach it would be nice to see what you have done? If you are using digitalRead, I'd be willing to work with you to get the faster approaches working on the DUE.

Hey Cattledog,

Yes, I can definitely share with you what I did. Before I continue, I just want to let you know that this is using Arduino Due. As I mentioned before I have 7 encoders I would like to interpret.

First off declaring pins of channels A and B from the encoders.

int m1_CHA = 21;    
int m1_CHB = 20;
int s1_CHA = 52;    
int s1_CHB = 50;

int m2_CHA = 19;   
int m2_CHB = 18;   
int s2_CHA = 24;   
int s2_CHB = 22;   

int m3_CHA = 17;    
int m3_CHB = 16;   
int s3_CHA = 53;    
int s3_CHB = 51;   

int s4_CHA = 25;
int s4_CHB = 23;

Secondly, declaring volatile variables that represent the current position of the encoder.

volatile float m1p = 0; 
volatile float s1p = 0;
volatile float m2p = 0; 
volatile float s2p = 0;
volatile float m3p = 0; 
volatile float s3p = 0;
volatile float s4p = 0;

Third, attaching interrupts on one of the channels of each encoder (in my case CHA). It is important to do both RISING and FALLINg in order to get quadrature counts (4x), with respecting ISR. As well as declaring the pinMode of CHB as inputs.

  attachInterrupt(m1_CHA, m1_ISR_RISING, RISING);
  attachInterrupt(s1_CHA, s1_ISR_RISING, RISING);
  attachInterrupt(m2_CHA, m2_ISR_RISING, RISING);
  attachInterrupt(s2_CHA, s2_ISR_RISING, RISING);
  attachInterrupt(m3_CHA, m3_ISR_RISING, RISING);
  attachInterrupt(s3_CHA, s3_ISR_RISING, RISING);
  attachInterrupt(s4_CHA, s4_ISR_RISING, RISING);
  
  attachInterrupt(m1_CHA, m1_ISR_FALLING, FALLING);
  attachInterrupt(s1_CHA, s1_ISR_FALLING, FALLING);
  attachInterrupt(m2_CHA, m2_ISR_FALLING, FALLING);
  attachInterrupt(s2_CHA, s2_ISR_FALLING, FALLING);
  attachInterrupt(m3_CHA, m3_ISR_FALLING, FALLING);
  attachInterrupt(s3_CHA, s3_ISR_FALLING, FALLING);
  attachInterrupt(s4_CHA, s4_ISR_FALLING, FALLING);

  pinMode(m1_CHB, INPUT);
  pinMode(s1_CHB, INPUT);
  pinMode(m2_CHB, INPUT);
  pinMode(s2_CHB, INPUT);
  pinMode(m3_CHB, INPUT);
  pinMode(s3_CHB, INPUT);
  pinMode(s4_CHB, INPUT);

Lastly, the RISING and FALLING ISRs required. Note in order for the counts to not cancel out with one another, RISING and FALLING edges have opposite signs (increment vs. decrement)

// RISING EDGE ISRs
void m1_ISR_RISING() { if (digitalRead(m1_CHB) == 0)  m1p += 1; else m1p -= 1; }
void m2_ISR_RISING() { if (digitalRead(m2_CHB) == 0)  m2p += 1; else m2p -= 1; }
void m3_ISR_RISING() { if (digitalRead(m3_CHB) == 0)  m3p += 1; else m3p -= 1; }
void s1_ISR_RISING() { if (digitalRead(s1_CHB) == 0)  s1p += 1; else s1p -= 1; }
void s2_ISR_RISING() { if (digitalRead(s2_CHB) == 0)  s2p += 1; else s2p -= 1; }
void s3_ISR_RISING() { if (digitalRead(s3_CHB) == 0)  s3p += 1; else s3p -= 1; }
void s4_ISR_RISING() { if (digitalRead(s4_CHB) == 0)  s4p += 1; else s4p -= 1; }

// FALLING EDGE ISRs
void m1_ISR_FALLING() { if (digitalRead(m1_CHB) == 0)  m1p -= 1; else m1p += 1; }
void m2_ISR_FALLING() { if (digitalRead(m2_CHB) == 0)  m2p -= 1; else m2p += 1; }
void m3_ISR_FALLING() { if (digitalRead(m3_CHB) == 0)  m3p -= 1; else m3p += 1; }
void s1_ISR_FALLING() { if (digitalRead(s1_CHB) == 0)  s1p -= 1; else s1p += 1; }
void s2_ISR_FALLING() { if (digitalRead(s2_CHB) == 0)  s2p -= 1; else s2p += 1; }
void s3_ISR_FALLING() { if (digitalRead(s3_CHB) == 0)  s3p -= 1; else s3p += 1; }
void s4_ISR_FALLING() { if (digitalRead(s4_CHB) == 0)  s4p -= 1; else s4p += 1; }

Cattledog, please expand and suggest how to implement and use bitRead, I have never used it before. I hope it would be easily portable into the my code above.

Thank you,

PS: I apologize for the late response.

Yes, I understand that you have a Due. If you had a AT328 based Arduino I'd have code for you instead of a learning experience for both of us. If I'm way off base, I hope someone with DUE experience will join this thread.

We are going to replace the digitalRead of all the CHB pins with direct Port reads of the pin state which are much faster. If you Google "Arduino Port Manipulation" you will find references for how this is done on the AT 328/168 processors.

First, you want to familiarize yourself with this Due pin out document which shows how all the numbered pins are located on Ports A,B,C, and D on the processor.

See http://arduino.cc/en/Hacking/PinMappingSAM3X and http://forum.arduino.cc/index.php/topic,132130.0.html

What I see is this for your pin assignments on the port registers (32 bit in the Due).

int m1_CHB = 20;  //PORTB 12
int s1_CHB = 50;  // PORTC 13   
int m2_CHB = 18; //PORTA 11   
int s2_CHB = 22; // PORTB 26     
int m3_CHB = 16; //PORTA 13     
int s3_CHB = 51;  //PORTC 12
int s4_CHB = 23; //PORTA 14

The syntax for reading these is different from the AT328 which is very simple. bitRead (http://arduino.cc/en/Reference/BitRead)
supports port reading with statements like bitRead(PINB , 1) which, for example returns the state of digital pin 9.(PORTB, register location 1). Register bit locations start at 0 index.

The PINX syntax will not work on the DUE. and I don't know if bitRead will support the port identification syntax which is used on the DUE.

From what I have read in forum posts, I think the port reading syntax needs to be changed to
REG_PIOx_PDSR where x is A,B,C,D. PDSR is "pin data status register".

You can try to use bitRead(REG_PIOB_PDSR, 12) to read pin 20. If this works, great.

If it doesn't, we will have to read the full register, bit shift, and mask out the bit we want. See << - Arduino Reference and & - Arduino Reference

In that case, I think you will use

int registerRead = REG_PIOB_PDSR

which will return all 32 bits of the register. I'm unclear about the type cast , but I think that with the Due, the integer is 32 bits.

Then to follow our example of reading portB register bit 12 (pin 20), I would bit shift right by 12 to place the bit we want in the first position, and read it with an & mask of 01.

int myBit= registerRead>>12 & B01 or more directly

int myBit =  REG_PIOB_PDSR >> 12 & B01

Before we go further, lets see how it goes with what I've given you so far. In terms of more general comments on your code, I don't understand the use of float for the encoder positions m1p, s1p, etc. I would think they should be volatile int. Your scheme of having an interrupt on the rising and falling edge of encoder pinA and reading pinB will only pick up two of the four quadrature changes. If you want to see all four state changes you need to look at the transitions on both pins. Interrupts can be written for CHANGING on both pins. I don't know what your project involves, but you may want to reconsider the pins you are using for the encoder. Pins 20 and 21 are I2C pins SDA/SDL which you may want to use. Also if you move towards interrupts on both pins, having them on adjacent pins on a single register (like m2_CHA/B) makes the reading and masking more simple than with pins on different registers like s3_CHA/B on pins 53 and 51 which are on different registers.