Using the Hardware Quadrature Encoder Channel on the DUE

just solved the problem.. the EDGPHA bit in the TC_BMR register controls whether the edges are detected on both PHA and PHB or just on PHA. I had this bit set to 0 before which according to the datasheet was the good choice.

("0: edges are detected on both PHA and PHB. 1: edges are detected on PHA only.")

BUT!! the description in the datasheet is WRONG!! its inverted!

so the code that works fine is the following:

const int quad_A = 2;
const int quad_B = 13;
const unsigned int mask_quad_A = digitalPinToBitMask(quad_A);
const unsigned int mask_quad_B = digitalPinToBitMask(quad_B);  

void setup() {
    Serial.begin(115200);  
    delay(100);
    
    // activate peripheral functions for quad pins
    REG_PIOB_PDR = mask_quad_A;     // activate peripheral function (disables all PIO functionality)
    REG_PIOB_ABSR |= mask_quad_A;   // choose peripheral option B    
    REG_PIOB_PDR = mask_quad_B;     // activate peripheral function (disables all PIO functionality)
    REG_PIOB_ABSR |= mask_quad_B;   // choose peripheral option B 
    
    // activate clock for TC0
    REG_PMC_PCER0 = (1<<27);
    // select XC0 as clock source and set capture mode
    REG_TC0_CMR0 = 5; 
    // activate quadrature encoder and position measure mode, no filters
    REG_TC0_BMR = (1<<9)|(1<<8)|(1<<12);
    // enable the clock (CLKEN=1) and reset the counter (SWTRG=1) 
    // SWTRG = 1 necessary to start the clock!!
    REG_TC0_CCR0 = 5;    
    
void loop() {
  Serial.println(REG_TC0_CV0,DEC);
  delay(500);
}

so for us dummies, does this code print the accumulated count from quadrature input on digital pins 2 and 13? not too good at complicated datasheets. also, are there only two pins that can be used as quadrature input and are they D2 and D13? i have a TFT shield that is wired to pin 2 among many others and can probably cut and jump to make pin 2 available but would prefer to not. my not very good interpretation of the data sheets suggests that three timers are available for quad processing but only two are brought out on the DUE board. is this correct?
The Timer Counter (TC) embeds a quadrature decoder logic connected in front of the 3 timers

and driven by TIOA0, TIOB0 and TIOA1 inputs. When enabled, the quadrature decoder per-
forms the input lines filtering, decoding of quadrature signals and connects to the
3 timers/counters in order to read the position and speed of the motor through user interface.

@schwingkopf:

great work, works really nice

@spencoid:

unfortunately you have to use Pin2, Pin13 and PinA7 for the quadrature encoder (PinA7 is the third pin/index Pin for the necoder).
I dont know if there is something like virtual pins on the SAM3X like on the XMega line. If there is something like that, the TIOA0, TIOB0 and TIOA1 lines could be routed to other pins I think

Ok i dont have a DUE yet, but im coding ahead of time. how about handling 2 or 3 encoders? and how can i level shift encoders? i have multiple 5v encoders. extremely fast 250lines.

i don't know if you can do it fast enough for high speed operation but the last encoders i bought (made by Bourns) have a chip select line so you could conceivably stick them all on a bus and sweep through them looking for signal changes. the programming might get a little confusing or might even be impossible.

A hex buffer, CD4050, will work nicely. Power the Vss pin with 3V3 to establish the output level. The inputs will take 5V signals.

As far as I understand the due has only one hardware encoder, but you can still use interrupt versions of software encoders to handle almost as many encoders as you wish.

in terms of speed you will then be limited by your interrupt handlers. If you programm cleverly you can get the time to handle the interrupts well below 1us (I can provide code if necessary), meaning that you can detect slopes at rates above 1MHz. If you use more than one encoder you just divide the 1MHz by the number of encoders you'd like to use.

I don't know exactly how fast the hardware encoder can go but since it uses the master clock (84MHz) as a reference I guess it can take at least more than 10MHz slope rate, probably even much faster.

For your application this would mean that you connect the encoder with the highest speed demands on the hardware encoder and you programm two interrupt based software encoders for the other 2. Those would then be able to detect slope rates of more than 500kHz.

Would that fit your "extremely fast" application?

for level shifting you can use chips like 74LVCC4245 and others. they're convenient to handle and super fast (10ns delay)

the buffer is a better way but i am using my 5 volt encoders with a voltage divider on the output. just a 10k and 20k resistor in series. easier to stick in as an afterthought than wiring in the buffer.

I am using a 500 pulse/rev encoder from maxon motors (I think they are rebranded AvagoTech Encoders), rated for 5V and 3,3kOhm Pullups on the signal lines. I use it with 3.3V and 10k Pullups and it works just fine. Maybe just give it a try.

Thank you all. Im going to buy the next Arduino Due ASAP. and replace my MEGA 1280. and get 3 buffers. its going to be interesting. next question how can i get a 5v pwm signal out?

Use one of the same buffer chips but connect Vss to 5V. As long as your input signal crosses around the 50% point, the output will switch states. Use the B version of the chip as it's switching level is better defined. Check the data sheet on AllDataSheet.com.

you might actually run into troubles using the hardware quadrature encoder simultaneously with the standard PWM outputs.
The hardware encoder uses the timer counter TC0 and the standard PWM uses it partially too, as far as I know..

anyone with more insight on how the standard PWM would interfere with the settings on TC0 channel 0 and 1?

I circumvent this problem by not using the arduino defined PWM outputs (that use the timer counter channels) but by using the SAM3x8e internal PWM channels (labeled PWMHx and PWMLx in the due pinout diagramm Arduino Forum). They actually exceed the functionality of the standard ones in terms of resolution and available PWM frequencies. The drawback is that you have to do a bit of reading in the datasheet and to programm some registers.
I will probably post some code examples when I have a bit more time..

schwingkopf:
just solved the problem.. the EDGPHA bit in the TC_BMR register controls whether the edges are detected on both PHA and PHB or just on PHA. I had this bit set to 0 before which according to the datasheet was the good choice.

("0: edges are detected on both PHA and PHB. 1: edges are detected on PHA only.")

BUT!! the description in the datasheet is WRONG!! its inverted!

so the code that works fine is the following:

const int quad_A = 2;

const int quad_B = 13;
const unsigned int mask_quad_A = digitalPinToBitMask(quad_A);
const unsigned int mask_quad_B = digitalPinToBitMask(quad_B);

void setup() {
    Serial.begin(115200); 
    delay(100);
   
    // activate peripheral functions for quad pins
    REG_PIOB_PDR = mask_quad_A;     // activate peripheral function (disables all PIO functionality)
    REG_PIOB_ABSR |= mask_quad_A;   // choose peripheral option B   
    REG_PIOB_PDR = mask_quad_B;     // activate peripheral function (disables all PIO functionality)
    REG_PIOB_ABSR |= mask_quad_B;   // choose peripheral option B
   
    // activate clock for TC0
    REG_PMC_PCER0 = (1<<27);
    // select XC0 as clock source and set capture mode
    REG_TC0_CMR0 = 5;
    // activate quadrature encoder and position measure mode, no filters
    REG_TC0_BMR = (1<<9)|(1<<8)|(1<<12);
    // enable the clock (CLKEN=1) and reset the counter (SWTRG=1)
    // SWTRG = 1 necessary to start the clock!!
    REG_TC0_CCR0 = 5;   
   
void loop() {
  Serial.println(REG_TC0_CV0,DEC);
  delay(500);
}

I tested the proposed code at up to 60 kHz, works great! But the index is not work at A7. Does anyone understand? My knowledge there is no longer enough.

To enable the Index Signal you have to configure the input pin to the right peripheral function

After an hour studying the SAM3X manual, I am getting a vague idea of how the decoding works - but can an example of how to also count the index be added to the working code example?

I am trying with an interrupt for the index, but the counts are low and drop more when the encoder is spun faster (TS5303 - can handle 5000RPM, pulses dropped at perhaps 100RPM).

I'm using 4N35s - perhaps they aren't switching fast enough?

Last question : the count keeps increasing, does it eventually overflow? can it be reset (per rev)? how is this normally handled?

I would be very appreciative of help here

When using interup try to keep the statments as quick and short as you can. my interupt code ive only been able to run at 0.1-150RPM with 250Pulses/rev. (1000counts) and that is with an arduino mega 1280, with encoder A+B+Z channels on interupts@change.

my interrupts are simple:

X_EncoderA(){
 if (digitalRead(encoderXPinA)==HIGH) {
   if (digitalRead(encoderXPinB) == LOW)  encoderXPos++;         // CW
   if (digitalRead(encoderXPinB) == HIGH)  encoderXPos--;         // CCW
 } else {
   if (digitalRead(encoderXPinB) == HIGH)  encoderXPos++;        // CW
   if (digitalRead(encoderXPinB) == LOW)  encoderXPos--;         // CCW
 }
}
X_EncoderB(){
 if (digitalRead(encoderXPinB)==HIGH) {
   if (digitalRead(encoderXPinA) == LOW)  encoderXPos++;         // CW
   if (digitalRead(encoderXPinA) == HIGH)  encoderXPos--;         // CCW
 } else {
   if (digitalRead(encoderXPinA) == HIGH)  encoderXPos++;         // CW
   if (digitalRead(encoderXPinA) == LOW)  encoderXPos--;          // CCW
 }
}

if you keep spinning the encoder in one direction it will overflow. depending on what data type you are using to store them. i use "unsigned int" that gives me 0 to 4,294,967,295 (2^32 - 1). i use unsigned because when i zero it, it is at it's left most extreme location. (i used to use "int" because i zero'ed it in the middle and needed the negative numbers.)

Sorry i cant help with any SAM3X information, or buffers. i dont use them YET.

Ok, that would give me more than 24 hours before it overflowed (at 1800RPM) so not a problem

My interrupt is even simpler, just detects RISING and sets a flag

I'm using the flag in loop to display the number of pulses. So the serial comms should not be interfering, unless the previous print has not completed before the flag is being set again

And if I just count high flag state and only display the pulses after 100 revs, the count is still low and differening. I only get the expected count when rotating very slowly

schwingkopf da boss....

Now running up to 4000RPM with consistent pulsing (1440 state changes per rev, 96000 per second). Problem - bad breadboard connection somewhere :blush:

Ready to solder :slight_smile:

I'd like to get the Z (index) pin working instead of using an interrupt - as I sometimes get a count +1 or -1, I guess a race situation because both index and a pulse pin are changing more or less at the same time. Funny that an encoder does that in a way - if the Due hardware logic sorts it out, it really can't be much better

you guys have got to be jokeing!!!

4000RPM with consistent pulsing (1440 state changes per rev, 96000 per second)

I cant wait to get my DUE. how are you getting feedback, serial?

const int quad_A = 2;
const int quad_B = 13;
const unsigned int mask_quad_A = digitalPinToBitMask(quad_A);
const unsigned int mask_quad_B = digitalPinToBitMask(quad_B);
const int z = 10;
volatile int reg_at_z=0;
volatile int zcount=0;
volatile int lag;
unsigned int prevmillis;
unsigned int thismillis;
int t;
int prevt=0;
float rpm=0.0;

volatile int firsttime=1;

void setup() {
Serial.begin(115200);
delay(100);

// activate peripheral functions for quad pins
REG_PIOB_PDR = mask_quad_A; // activate peripheral function (disables all PIO functionality)
REG_PIOB_ABSR |= mask_quad_A; // choose peripheral option B
REG_PIOB_PDR = mask_quad_B; // activate peripheral function (disables all PIO functionality)
REG_PIOB_ABSR |= mask_quad_B; // choose peripheral option B

// activate clock for TC0
REG_PMC_PCER0 = (1<<27);
// select XC0 as clock source and set capture mode
REG_TC0_CMR0 = 5;
// activate quadrature encoder and position measure mode, no filters
REG_TC0_BMR = (1<<9)|(1<<8)|(1<<12);
// enable the clock (CLKEN=1) and reset the counter (SWTRG=1)
// SWTRG = 1 necessary to start the clock!!
REG_TC0_CCR0 = 5;

pinMode(z, INPUT);
attachInterrupt(z, zrising, RISING);

}
void loop() {
if (reg_at_z==1) {
t=REG_TC0_CV0;
Serial.println((t-prevt),DEC);
thismillis=millis();
rpm = 1.0/((thismillis-prevmillis)/60000.0);
Serial.println(rpm,DEC);
reg_at_z=0;
prevt=t;
prevmillis=thismillis;
}

}

void zrising() {
reg_at_z=1;
}