1

I am working to read two quadrature encoders using a single Arduino DUE, as part of my summer vacations project. According to the following link http://atmel.force.com/support/servlet/fileField?id=0BEG0000000HRvP, the SAM3X8E has 2 quadrature decoders in it. Now I am not being able to understand how can I initialize this second quadrature decoder. According to Quadrature Decoder QDEC for SAM3/4 Devices_ApplicationNote_AT11483 Pg16 we have to first configure the IOPINS for QDEC1. I have to do that same task in Arduino IDE instead of Atmel Studio.

This is the code that I have found and works well to read encoder using QDEC0. However, I now need to read the encoder using the second quadrature decoder, whose input pins are TIOA6 , TIOB6 which correspond to Pin 5 and 4 of Arduino DUE. After reading the Atmel SAM3X datasheet, I think I should change "TC0" with "TC2" to make the second decoder work. However, this doesn't work. I think it does not work because channel 2 is busy in time base calculations. In Application Note 5.52 QDEC Speed Measurement Mode point 2 says to Initialize TC0, TC1, TC2. If I wanted to used other encoder which timer instance will be used.

/*
** Quadrature Encoder on Arduino Due in "Speed Mode"
** Will not work on other Arduino types because of a specific hardware requirement.
**
** @bungle99 / 2014-02-28
**
** Many thanks to @Designservicecorp (notably post #32 and post #42) that gave me the base to work from.
**
** Uses *hardware* to do the heavy lifting of interpreting the Encoder output, which gives it significant
** advantage over other Arduino's for this use case. Eg many people have reported their Unos maxing out
** or skipping counts when interpretting high RPMs, whereas people using the Due in hardware mode have 
** reported better success with high RPMs.
**
** This particular example puts the hardware into "Speed" mode, whereas other examples in this thread use 
** "Position" mode (see posts 32, 42). The one example in the thread that attempts Speed calcs uses Position 
** Mode and uses an interrupt on the Z Index axis to manually apply a time period (needed to caculate speed) 
** on each rotation.
**
** This code below has been used in tests (using Lego NXT and a gear train for driving / comparison) to 
** ~3,500 RPM with a 1,024 PPR Encoder (4 x 1,024 Edges). The Arduino didn't seem to struggle and the 
** numbers (eg RPM) matched fairly closely with the Lego (eg as a sanity check, the calcs in the code look
** correct).
**
** I bought one of these encoders, which seems ok;
**
**   * http://proto-pic.co.uk/rotary-encoder-1024-p-r-quadrature/
**
** Great service from proto-pic. The encoder specs are officially
**
**   * http://dlnmh9ip6v2uc.cloudfront.net/datasheets/Robotics/E6B2Encoders.pdf
**
** But look an awful lot like this one too (which is easier to read)
**
**   * http://www.ia.omron.com/product/item/e6b27032r/index.html
**
** Note that this Encoder is rated at 5V min. I've successully powered it off the 5V Arduino pin header, 
** and it doesn't work off the 3.3V one. At 5V, the outputs need to be run through some resistors to bring
** them into tolerance of the Arduino inputs.
**
** The inputs are 2 and 13 (and optionally A6 for the index).
**
** The test hardware (some Lego NXT servos and gear train) was unable to turn any faster (more gears 
** stalled the servos) - eg the Arduino Due was not the limiting factor. At this speed, the Due Hardware 
** is dealing with approx 238,000 edges per second.
**
** This example offloads the speed calculation onto the hardware, meaning no interrupt is required. 
** In addition, the index is not required either, so it allows speeds to be seen even for rotations that 
** don't pass the index marker (for example, slow rotations needing a speed before a complete rotation). 
** That said, index is available if you want it and the docs suggest you can still use it (and others) as 
** interrupt if you wish. 
**
** NB There appears to be a myriad of features on the Chip that have not been touched. Eg you can still add
** interrupts to this code to do other things; eg passing the Z index, direction changes. There are also 
** filters available if your encoder suffers from noise (eg vibrations). All this is available to play with
** another day :)
**
** Code appears to work, but no guarantees. Understand it. Test it. Feedback to the group. YMMV. Don't blame 
** me if it doesn't work or expolodes!
*/

const int ENCODER_EDGES_PER_ROTATION = 3200;  // this depends on your encoder
const int ENCODER_SAMPLES_PER_SECOND = 10;        // this will need to be tuned depending on your use case...
const int LOOP_DELAY_MS = 1 * 1000;               // ... as will this (see comments in main code)

void setup() {

  Serial.begin(115200);  
  delay(10);


  // Setup Quadrature Encoder
  // http://www.atmel.com/Images/doc11057.pdf
  // Section 37 p869 
  // Section 37.6.14 p885
  // Section 37.6.14.2 p890 Position and Rotation Measurement
  // Section 37.6.14.5 p891 Speed Measurement (what this is about)

  REG_PMC_PCER0 = PMC_PCER0_PID27
                | PMC_PCER0_PID28
                | PMC_PCER0_PID29;

  // Setup a channel in waveform mode (eg an input into the encoder to trigger the time based sampling)
  // Note some of the choices here impact calculations below and if you change them, you need to change
  // the next section to suit.
  // Section 37.7.11 p906 (also refer to Section 37.6.14.5 p891 for detail of what to set)
  REG_TC0_CMR2 = TC_CMR_TCCLKS_TIMER_CLOCK4 
               | TC_CMR_WAVE
               | TC_CMR_ACPC_TOGGLE 
               | TC_CMR_WAVSEL_UP_RC;

  // Now define the sample period, using the clock chosen above as the basis
  // Note that REG_TC0_CMR2 above is using CLOCK4; this is an 128 divisor. You need to change the
  // divisor below if you change the clock above. You could change the input clock and the RC mode to 
  // suit your app (eg how many pulses are you  expecting - depends on encoder type 
  // and slowest/normal/fastest rotation speed and what you want to do with the result).
  // Section 37.6.14.5 p891 notes you need to set this up, otherwise 0 comes out all the time :-)
  REG_TC0_RC2 = F_CPU / 128 / ENCODER_SAMPLES_PER_SECOND;

  // Setup a channel in capture mode
  // Section 37.7.10 p904 (also refer to Section 37.6.14.5 p891 for detail of what to set)
  REG_TC0_CMR0 = TC_CMR_ABETRG
               | TC_CMR_LDRA_EDGE
               | TC_CMR_LDRB_EDGE 
               | TC_CMR_ETRGEDG_EDGE 
               | TC_CMR_CPCTRG;

  // Enable features, noting Speed not Position is chosen
  // Section 37.7.2 p895 (also refer to Section 37.6.14.5 p891 for detail of what to set)

// 37.7.2 TC Block Mode Register
  REG_TC0_BMR = TC_BMR_QDEN
              | TC_BMR_SPEEDEN
              | TC_BMR_EDGPHA; //Pg 896

/*EDGPHA: EDGe on PHA count mode
0: edges are detected on both PHA and PHB.
1: edges are detected on PHA only.*/


  // Set everything going
  REG_TC0_CCR0 = TC_CCR_CLKEN | TC_CCR_SWTRG;  
  REG_TC0_CCR1 = TC_CCR_CLKEN | TC_CCR_SWTRG;  
  REG_TC0_CCR2 = TC_CCR_CLKEN | TC_CCR_SWTRG;  
}

void loop() {

  int iIndexCount = REG_TC0_CV1;  // Don't need this, but manual notes its available  
  int iSpeedPPP = REG_TC0_RA0;    // This is what we're really after (speed in Pulses Per sample Period)

  // which we can convert to rps or rpm easily
  double dSpeedRPS = ((iSpeedPPP / (ENCODER_EDGES_PER_ROTATION * 1.0)) * ENCODER_SAMPLES_PER_SECOND);
  double dSpeedRPM =  dSpeedRPS * 60;

  Serial.print("Speed ppp: ");
  Serial.print(iSpeedPPP);
  Serial.print(", ");
  Serial.print("Speed rps: ");
  Serial.print(dSpeedRPS);
  Serial.print(", ");
  Serial.print("Speed rpm: ");
  Serial.print(dSpeedRPM);
  Serial.print(", ");
  Serial.print("Indexes: ");
  Serial.print(iIndexCount);
  Serial.print(". ");
  Serial.println();

  // Slow down the main loop noting the Encoder can update independently in the background (we're
  // effectively polling it to find out the latest and greatest info on each loop around).
  delay(LOOP_DELAY_MS);
}

Additionally I wanted to ask what is the difference between REG_TC0_CMR2 REG_TC0_CMR0 and REG_TC0_CMR1 ? Two of them are used in the above code.

Motor with Encoder I am using is: enter image description here

1 Answer 1

1

TC blocks are pretty much identical (except TC1 doesn't have wired some signals to pins)

So you have to copy registers containing *_TC0_* = and replace TC0 to TC2.

Plus PC25 and PC24 should be set to its alternate function PIO_PERIPH_B. Something like this:

  PIO_Configure(PIOC, PIO_PERIPH_B, PIO_PC25B_TIOA6, PIO_DEFAULT);
  PIO_Configure(PIOC, PIO_PERIPH_B, PIO_PC26B_TIOB6, PIO_DEFAULT);

Also TC6, TC7 and TC8 clocks must be enabled by:

REG_PMC_PCER1 = PMC_PCER1_PID33 | PMC_PCER1_PID34 | PMC_PCER1_PID35;

And TC_CMR0..2 are Channel Mode Registers for one of three timers/counters in one TC block. Again, these are the same, but WAVE mode dependent.

TC_CMR WAVE=0

TC_CMR WAVE=1

Full configuration:

  REG_PMC_PCER0 = PMC_PCER0_PID27 | PMC_PCER0_PID28 | PMC_PCER0_PID29;
  REG_PMC_PCER1 = PMC_PCER1_PID33 | PMC_PCER1_PID34 | PMC_PCER1_PID35;

  PIO_Configure(PIOB, PIO_PERIPH_B, PIO_PB25B_TIOA0, PIO_DEFAULT); // arduino pin 2
  PIO_Configure(PIOB, PIO_PERIPH_B, PIO_PB27B_TIOB0, PIO_DEFAULT); // arduino pin 13

  REG_TC0_CMR2  = TC_CMR_TCCLKS_TIMER_CLOCK4 | TC_CMR_WAVE | TC_CMR_ACPC_TOGGLE | TC_CMR_WAVSEL_UP_RC;
  REG_TC0_RC2   = F_CPU / 128 / 1;
  REG_TC0_CMR0  = TC_CMR_ABETRG | TC_CMR_LDRA_EDGE | TC_CMR_LDRB_EDGE | TC_CMR_ETRGEDG_EDGE | TC_CMR_CPCTRG;
  REG_TC0_BMR   = TC_BMR_QDEN | TC_BMR_SPEEDEN /*| TC_BMR_EDGPHA*/;
  REG_TC0_CCR0  = TC_CCR_CLKEN | TC_CCR_SWTRG;
  REG_TC0_CCR1  = TC_CCR_CLKEN | TC_CCR_SWTRG;
  REG_TC0_CCR2  = TC_CCR_CLKEN | TC_CCR_SWTRG;

  PIO_Configure(PIOC, PIO_PERIPH_B, PIO_PC25B_TIOA6, PIO_DEFAULT); // arduino pin 5
  PIO_Configure(PIOC, PIO_PERIPH_B, PIO_PC26B_TIOB6, PIO_DEFAULT); // arduino pin 4

  REG_TC2_CMR2  = TC_CMR_TCCLKS_TIMER_CLOCK4 | TC_CMR_WAVE | TC_CMR_ACPC_TOGGLE | TC_CMR_WAVSEL_UP_RC;
  REG_TC2_RC2   = F_CPU / 128 / 10;
  REG_TC2_CMR0  = TC_CMR_ABETRG | TC_CMR_LDRA_EDGE | TC_CMR_LDRB_EDGE | TC_CMR_ETRGEDG_EDGE | TC_CMR_CPCTRG;
  REG_TC2_BMR   = TC_BMR_QDEN | TC_BMR_POSEN /*| TC_BMR_EDGPHA*/;
  REG_TC2_CCR0  = TC_CCR_CLKEN | TC_CCR_SWTRG;
  REG_TC2_CCR1  = TC_CCR_CLKEN | TC_CCR_SWTRG;
  REG_TC2_CCR2  = TC_CCR_CLKEN | TC_CCR_SWTRG;
3
  • I have tried this , but it doesn't work. We are not getting any measurement.... Aug 17, 2016 at 6:00
  • 1
    @MasoodSalik also clocks must be enabled for channels 6-8. I though it's enabled for whole block by REG_PMC_PCER0 = PMC_PCER_PID29 but it was just third counter in TC0.
    – KIIV
    Aug 17, 2016 at 16:55
  • 1
    I have enabled all the clocks using { REG_PMC_PCER0 = PMC_PCER0_PID27 | PMC_PCER0_PID28 | PMC_PCER0_PID29 | PMC_PCER0_PID30 | PMC_PCER0_PID31; REG_PMC_PCER1 = PMC_PCER1_PID32 | PMC_PCER1_PID33 | PMC_PCER1_PID34 | PMC_PCER1_PID35; } Now I am able to read QDEN1 from pin 4 and 5 . QDEN0 from pin 2 and 13. I am now reading clicks_per_revoluion using REG_TC2_CV0 and REG_TC0_CV0. Now i am trying to measure the speed Aug 17, 2016 at 18:25

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge that you have read and understand our privacy policy and code of conduct.

Not the answer you're looking for? Browse other questions tagged or ask your own question.