DUE Timer Capture Mode (TC0, channel 1)

The code below is intended to use the TC0, channel 1 timer in capture mode with a MCK/2 (42MHz) clock. A frequency is generated on pin 7 (PWM) of 50Hz. A jumper is connected between pin 7 and A7 (TIOA1). I've check the frequency and duty cycle of the PWM with an oscilloscope and it is correct.

On the rising edge of TIOA1 the ISR will save the TC0_RA1 register. On the falling edge the ISR will save the TC0_RB1 register. The ISR also sets a flag to indicate to loop() that a new value is available and loop() clears the flag once the captured data is read from the variable.

The ISR is being fired but the data read from TC0_RA1 and TC0_RB1 are not correct. The ISR seems to be executing continuously. If I disconnect the jumper the ISR quits firing as it should. Don't quit understand this yet.

I'm hoping someone can help me out with what I'm doing wrong or leaving out of the code.

I've done a lot of research on this and have read the Atmel datasheet on this processor. The code is very close but still no cigar.

Any help will be greatly appreciated.

Thanks in advance!

// variables used within the ISR
volatile unsigned long CaptureCountA=0, CaptureCountB, TimerCount=0;
volatile bool CaptureFlag=0;

// define bit numbers for the TC registers we are using
const byte COVFS=0, LOVRS=1, CPAS=2, CPBS=3, CPCS=4, LDRAS=5, LDRBS=6, ETRGS=7,
           CLKSTA=16, MTIOA=17, MTIOB= 18;                                      // TC_IERx and TC_SRx bits
const byte CLKEN=0, CLKDIS=1, WSTRG=2;                                          // TC_CCRx bits
const byte TCCLKS=0, CLKI=3, BURST=4, LDBSTOP=6, LDBDIS=7, ETRGEDG=8, ABETRG=10,
           CPCTRG=14, WAVE=15, LDRA=16, LDRB=18;                                // TC_CMRx bits

void setup() {
  Serial.begin(9600);                                   // initilize serial port to 9600 baud
  while (!Serial) { }                                   // wait for serial port to initialize
  
  // setup TC0 bank, channel 1 (TC1) for input capture mode w/clk of 42MHz
  pmc_enable_periph_clk (ID_TC0) ;                      // enable clock the TC0 channeL 0, to access TC0_WPMR
  REG_TC0_WPMR = 0x54494D00;                            //WPKEY, disable
  pmc_set_writeprotect(false);                          // enable write to TC registers
  pmc_enable_periph_clk (ID_TC1) ;                      // enable clock the TC0 channel 1
  REG_TC0_CCR1 = 0;                                     // clear all bits in TC0_CCR1
  REG_TC0_CCR1 = 1 << CLKEN;                            // enable clock
  REG_TC0_CMR1 = 0;                                     // capture mode, mclk/2, clk on rising edge
                                                        // burst disabled, clk not disabled when loading RA/RB
                                                        // capture mode
  REG_TC0_CMR1=((3 << ETRGEDG) |                        // trigger on both edges of trigger input
                (1 << ABETRG) |                         // TIOA is used as the external trigger
                (1 << LDRA) |                           // load RA on rising edge of trigger input
                (2 << LDRB));                           // load RB on falling edge of trigger input
  REG_TC0_IER1 = 0;                                     // disable TC1 interrupts
  REG_TC0_IER1 = ((1 << LDRAS) | (1 << LDRBS));         // enable TC1 RA and RB capture interrupts
  REG_TC0_IMR1 = ((1 << LDRAS) | (1 << LDRBS));         // enable TC1 capture interrupt mask
  REG_TC0_IDR1 = !((1 << LDRAS) | (1 << LDRBS));        // disable all other timer interrupts except capture
  NVIC_EnableIRQ(TC1_IRQn);                             // enable TC1 interrupts
  
  // print the startup header
  Serial.println("Timer Capture");
  
  // use the DUE to output a PWM frequency on pin 7, pin A7 input to the timer capture, just for testing
  pinMode(A7, INPUT);                                     // TIOA1, input to TC1 for capture, A7 on DUE
//  analogWrite(7,127);                                   // start PWM signal on pin 7, 50% duty cycle
  analogWrite(7,64);                                      // start PWM signal on pin 7, 25% duty cycle
  // a jumper needs to be installed between pin 7 (PWM output) and A7 (TC0,1 TIOA pin)
}

void loop() {
  if (CaptureFlag) {
    CaptureFlag=0;
    Serial.print(CaptureCountA);
    Serial.print(", ");
    Serial.print(CaptureCountB);
    Serial.print(", ");
    Serial.println(TimerCount);
  }
  else {
    Serial.println(REG_TC0_CV1);
  }
}

void TC1_Handler(){
  TimerCount=REG_TC0_CV1;               // save the timer counter register, for testing
  TC_GetStatus(TC0, 1);                 // get status, TC0 channel 1, allow the interrupt to fire again
  if (MTIOA) {
    CaptureCountA = REG_TC0_RA1;          // get data from capture register A for TC0 channel 1
  }
  else {
    CaptureCountB = REG_TC0_RB1;          // get data from caputre register B for TC0 channel 1
  }
  CaptureFlag=1;                        // set flag idicating a new capture value present
}

AFAICT, there is an issue in TC1_Handler() :

if (MTIOA) ...but MTIOA is a constant (= 17) thus the condition is always true ! CaptureCountA is incremented twice, whereas CaptureCountB is never incremented.

I did some simplifications in your sketch (not tested thought):

// variables used within the ISR
volatile uint32_t CaptureCountA, CaptureCountB, TimerCount;
volatile boolean CaptureFlag;
/*
  ISR/IRQ TC   Channel Due pins (A,B)
  TC0     TC0     0     2,  13
  TC1     TC0     1     60, 61
  TC2     TC0     2     58
  TC3     TC1     0     none 
  TC4     TC1     1     none
  TC5     TC1     2     none
  TC6     TC2     0     5,  4
  TC7     TC2     1     3,  10
  TC8     TC2     2     11, 12
*/
void setup() {
  Serial.begin(250000);                                   // initilize serial port to 250000 baud

  analogWriteResolution(12); // From 0 to 2exp12  - 1 = 4095

  PMC->PMC_PCER0 |= PMC_PCER0_PID28;                      // Timer Counter 0 channel 1 IS TC1

  TC0->TC_CHANNEL[1].TC_CCR = TC_CCR_SWTRG | TC_CCR_CLKEN; // Reset TC counter and enable

  TC0->TC_CHANNEL[1].TC_CMR = TC_CMR_TCCLKS_TIMER_CLOCK1 // capture mode, mclk/2, clk on rising edge
                              | TC_CMR_ETRGEDG_EDGE        // trigger on both edges of trigger input
                              | TC_CMR_ABETRG              // TIOA is used as the external trigger
                              | TC_CMR_LDRA_RISING         // load RA on rising edge of trigger input
                              | TC_CMR_LDRB_FALLING;       // load RB on falling edge of trigger input


  TC0->TC_CHANNEL[1].TC_IER |= TC_IER_LDRAS | TC_IER_LDRBS; // Trigger interruption on Load RA and load RB

  NVIC_EnableIRQ(TC1_IRQn);                             // enable TC1 interrupts

  
  // print the startup header
  Serial.println("Timer Capture");

  // use the DUE to output a PWM frequency on pin 7, pin A7 input to the timer capture, just for testing
  // pinMode(A7, INPUT);                                     // TIOA1, input to TC1 for capture, A7 on DUE
  //  analogWrite(7,127);                                   // start PWM signal on pin 7, 50% duty cycle
  //analogWrite(7,64);                                      // start PWM signal on pin 7, 25% duty cycle
  analogWrite(7, 1024);                                   // Duty cycle is 25% with 12 bits resolution

  // a jumper needs to be installed between pin 7 (PWM output) and pin A7 (TC0 channel 1 TIOA pin)
}

void loop() {
  if (CaptureFlag) {
    CaptureFlag = 0;
    Serial.print(CaptureCountA);
    Serial.print(", ");
    Serial.print(CaptureCountB);
    Serial.print(", ");
    Serial.println(TimerCount);
  }
  else {
    Serial.println(REG_TC0_CV1);
  }
}

void TC1_Handler() {
  //TC_GetStatus(TC0, 1); 
  uint32_t status = TC0->TC_CHANNEL[1].TC_SR;       // Read & Save status register -->Clear status register
   
  TimerCount = TC0->TC_CHANNEL[1].TC_CV;            // save the timer counter register, for testing
                
  if ((status & TC_SR_LDRAS) == TC_SR_LDRAS) {  // If ISR is fired by LDRAS then ....
    CaptureCountA = TC0->TC_CHANNEL[1].TC_RA;        // get data from capture register A for TC0 channel 1
  }
  else 
  if ((status & TC_SR_LDRBS) == TC_SR_LDRBS) {  // If ISR is fired by LDRBS then ....                                       // If ISR is fired by LDRBS then ....
    CaptureCountB = TC0->TC_CHANNEL[1].TC_RB;         // get data from caputre register B for TC0 channel 1
  }
  
  CaptureFlag = 1;                      // set flag indicating a new capture value is present
}

I tested the code you provided. The interrupt doesn't seem to fire continuously, that appears to be fixed.

It appears that the RA is captured but not RB.

Output to the serial monitor:

Timer Capture
1500
2233
2916
3599
4332
5015
5751
6434
7117
7852
8535
9270
9954
10688
11450
126
790
1402
2132
2815
3551
8728
18808
30568
31324, 0, 16
24028
31324, 0, 16
17488
29248
31324, 0, 16
22708
31324, 0, 16
16168
27928
31324, 0, 16
21388

I don't have time to work on this today but I will look into it further when I have time.

Thank you for your feed back.

And after some minor changes, it works :

 // a jumper needs to be installed between pin 7 (PWM output) and pin A7 (TC0 channel 1 TIOA pin)

volatile uint32_t CaptureCountA, CaptureCountB, TimerCount;
volatile boolean CaptureFlag;

void setup() {
  Serial.begin(250000);                                   // initilize serial port to 250000 baud

  analogWriteResolution(12); // From 0 to 2exp12  - 1 = 4095
  analogWrite(7, 1024);                                   // Duty cycle is 25% with 12 bits resolution

  PMC->PMC_PCER0 |= PMC_PCER0_PID28;                      // Timer Counter 0 channel 1 IS TC1

  TC0->TC_CHANNEL[1].TC_CCR = TC_CCR_CLKDIS ;                           // disable internal clocking while setup regs

  TC0->TC_CHANNEL[1].TC_CMR = TC_CMR_TCCLKS_TIMER_CLOCK1 // capture mode, MCK/2, clk on rising edge                           
                              | TC_CMR_ABETRG              // TIOA is used as the external trigger
                              | TC_CMR_LDRA_RISING         // load RA on rising edge of trigger input
                              | TC_CMR_LDRB_FALLING;       // load RB on falling edge of trigger input

  TC0->TC_CHANNEL[1].TC_IER |= TC_IER_LDRAS | TC_IER_LDRBS; // Trigger interruption on Load RA and load RB
  TC0->TC_CHANNEL[1].TC_CCR = TC_CCR_SWTRG | TC_CCR_CLKEN; // Reset TC counter and enable

  //NVIC_DisableIRQ(TC1_IRQn); 
  //NVIC_ClearPendingIRQ(TC1_IRQn); 
  //NVIC_SetPriority(TC1_IRQn, 0);                      // Give TC1 interrupt the highest urgency
  NVIC_EnableIRQ(TC1_IRQn);                             // Enable TC1 interrupts

  // print the startup header
  Serial.println("Timer Capture");

}

void loop() {
  if (CaptureFlag) {
    CaptureFlag = 0;
    printf("\r %d , %d , %d \n", CaptureCountA,CaptureCountB,TimerCount);
  }
  else {
    Serial.println(TC0->TC_CHANNEL[1].TC_CV);
  }
}

void TC1_Handler() {

  //Registers A and B (RA and RB) are used as capture registers. They are loaded with
  //the counter value TC_CV when a programmable event occurs on the signal TIOA.
  TimerCount = TC0->TC_CHANNEL[1].TC_CV;            // save the timer counter register, for testing

  uint32_t status = TC0->TC_CHANNEL[1].TC_SR;       // Read & Save satus register -->Clear status register

  // If TC_SR_LOVRSRA is set, RA or RB have been loaded at least twice without any read
  // of the corresponding register since the last read of the Status Register,
  // We are losing some values,trigger of TC_Handler is not fast enough !!
  if ((status & TC_SR_LOVRS) == TC_SR_LOVRS) abort();

  if ((status & TC_SR_LDRAS) == TC_SR_LDRAS) {  // If ISR is fired by LDRAS then ....
    CaptureCountA = TC0->TC_CHANNEL[1].TC_RA;        // get data from capture register A for TC0 channel 1
  }
  else if ((status & TC_SR_LDRBS) == TC_SR_LDRBS) { // If ISR is fired by LDRBS then ....
    CaptureCountB = TC0->TC_CHANNEL[1].TC_RB;         // get data from caputre register B for TC0 channel 1
  }

  CaptureFlag = 1;                      // set flag indicating a new capture value is present
}