Using the Hardware Quadrature Encoder Channel on the DUE

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;
}

For what it's worth - I suggest some careful checking before trying this.

Pin connections need to be adjusted according to those used in the sketch (or whatever pins are used)

I would like to start working with the quadrature encoder hardware in the DUE. It appears you can use the pins TIOA0, TIOB0 and TIOA1 to decode 2 channel quadrature plus an index. (SAM3X manual p. 855) If my reading of the pinout is correct, this would be pins 2, 13 and AD7.

As a point of clarification I would restate that it would be pins 2, 13, and A7 since technically AD7 as can be found on the wonderful pin-out diagram that Gray Nomad put together and the SAM3X8e datasheet that the SAM3X8e pin AD7 is actually on A0 which is how I originally interpreted the post that started this thread :frowning:

I've checked and the original code for using the bit masks to select peripherals and such is not working. You can verify that by looking at the status registers after executing the commands. Additionally the quad decoding still works after commenting out all the mask _PDR AND _ABSR variables and assignment statements. I'm even wondering if turning clocks on and off is working. I think the Arduino init code/IDE is probably turning on write protect registers after initializing and before calling our setup function.

For those that are still trying to get the encoder Index to work, here is what I did:

I started with the code from Schwinghopf in Reply#6 and as Exedor suggested in Reply#31, I removed the peripheral function setup and found it still worked. After reading through the SAM3x document many times I eventually discovered that the line: REG_PMC_PCER0 = (1<<27); was not turning on all of TC0 but rather only turning on channel0 of TC0. In order to start counting indexes, channel1 of TC0 needs to be turned on also.

Another issue was that I had to connect the Index wire to A6 (not A7 as the documentation shows). Not sure why this is, but since I wasn't using A6 for anything else I didn't look into it.

In the code below, I enabled interrupts for the Index (Z). Every index, REG_TC0_CV1 will reset back to zero. If you want it to continue counting, remove the interrupt (last 4 lines of setup() ).

Hope this helps.

volatile int z_Total=0;

void setup() {

    // Setup Quadrature Encoder with Marker

  REG_PMC_PCER0 = (1<<27); // activate clock for TC0

  REG_PMC_PCER0 = (1<<28); // activate clock for TC1

  // 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<<8)|(1<<9)|(1<<12);

  // enable the clock (CLKEN=1) and reset the counter (SWTRG=1) 

  REG_TC0_CCR0 = 5;  

  REG_TC0_CCR1 = 5;

  //Remark out the next 4 lines to remove index interrupt and
  // accumulate index count
  REG_TC0_CMR1 = (1<<8); // Set rising edge of Z
  REG_TC0_IER1=0b10000000; // enable interrupt on Z
  REG_TC0_IDR1=0b01111111; // disable other interrupts
  NVIC_EnableIRQ(TC1_IRQn);

}

void loop() {
//REG_TC0_CV0 Stores count from encoder
//REG_TC0_CV1 Stores count from index if interrupts are off

}

void TC1_Handler() {

  z_Total++;
  long dummy=REG_TC0_SR1; // vital - reading this clears some flag
                            // otherwise you get infinite interrupts
}

I'm not able to read the value on REG_TC0_CV1 even when interrupts are off. How did you turn interrupts off? I'm wondering if rather than actually using the index line, it is instead recognizing an "external" trigger which happens to be coming through A6 rather than the actual index line. From the data sheet it sounds like the TC0_CV0 register is supposed to reset to 0 each time an index pulse comes through.

I updated my earlier post to include remarking out one more line (REG_TC0_CMR1 =) in order to allow the accumulation of index pulses.

I'll also go back and rearrange the code to make it easier to see the interrupt lines.

with the updated version of your code I still dont see the index pulses being counted on REG_TC0_CV1.

just to clearify the functionality of the hardware quadrature decoder logic:
every index pulse should increase or decrease the value in the REG_TC0_CV1 register by one and at the same time restart or zero the position value in the REG_TC0_CV0 register. Please dont state you have solved the problem until you really see this behaviour!

As far as I understand your code, it just counts manually the index pulses using the interrupt handler, which is definitely not the same as the automatic counting using the hardware decoder logic.

Concerning the fact that the index pin might be on A6 and not on A7: In the figure 37-15 in the datasheet the index pin is drawn to be connected to TIOB1 (and not to TIOA1 as stated thoughout the rest of the datasheet) which corresponds to A6.

Did you remark out the 4 lines to stop the interrupt on the index? I copied the code to my DUE after editing my post and tried it both ways(with/without interrupts) and it functions. With interrupts enabled CV1 stays at zero but the interrupt handler occurs each index. With the interrupts remarked out CV1 counts up each index.

I'm running an encoder emulator box that only drives one direction, so I can't attest to lowering REG_TC0_CV1 only that mine adds each index. I'll have to swap my A & B wires and see what happens.

As for zeroing REG_TC0_CV0 on each index, I understood that to be a setting that needed to be enabled. My project didn't require it, so I didn't look into setting it up.

Thanks for spotting the A6 figure. This is not the worst document I have had the privilege of deciphering, but it's real close.

BTW, I never stated I solved the problem. Only sharing what I did to get the index to accumulate.

You got me curious on the direction issue.

I reloaded the code after remarking out the index interrupt and added a print statement for CV1 and started it.

I let the value increase to about 80 rotations and then pulled and swapped the A & B wires. The index count did start to decrease so direction does work. The number after passing zero became very large, however.

Thanks for that. It still isn't doing what the data sheet says it will do. According to the datasheet, when an index pulse comes along, it will supposedly reset the value in the TC0_CV0 register and start the count over at 0. Um, nope. That ain't happening! Either data sheet lies or we have peripheral usage/configuration issues. The fact that pulses are coming in A6 tells me something is amiss....nevertheless, I'm grateful to have at least something. As I understand it, the index pulses are designed to help keep your stuff in sync. I have seen for extremely fast pulse rates, pulses do get dropped and I've seen a tiny amount of drift. I'm working with an extremely high precision application and that is a problem so I was really hoping to get the hardware quadrature decoder working as documented. It's almost like the clock is actually using TIOB1 and not TIOA1 like the data sheet says or maybe for that port, the other peripheral is selected??

Also, for anyone looking to obtain the direction, you can do this:

dir = (REG_TC0_QISR >> 8) & 0x1

Assuming you have of course declared "dir"
I have it declared as a regular int and it is 0 when clockwise and 1 when counter-clockwise assuming you have ChA connected to pin 2 and ChB connected to pin 13.

For those that want the index to trigger a reset of the A/B count register this works using the code from my earlier post (Reply #32):

Replace the line: REG_TC0_CMR0 = 5;

With this:

REG_TC0_CMR0 = (1<<0)|(1<<2)|(1<<8)|(1<<10);

This works with interrupts enabled on the index or not.