Using a defined external pin to start and stop a timer

Hi all!

I am using an Arduino Due (Atmel SAM3X8E) and I am trying to implement the following:
I would like to use an external pin interrupt (preferrably digital pin 5 which is TIOA6) to start a timer/counter with rising edge and stop the same timer/counter with a falling edge on the same pin.

I already saw some forum posts using TIOA0 and TIOB0 for starting and stopping timers but never with the same pin not to speak of using TIOA6 for both.

My timer/counter setup approach is the following so far:

	// initialize timer/counter
	PMC->PMC_PCER1 |= PMC_PCER1_PID34;                  // TC7 power ON : Timer Counter 2 channel 1 IS TC7 (datasheet page 39)

	TC2->TC_CHANNEL[1].TC_CMR = TC_CMR_TCCLKS_TIMER_CLOCK1 // TIMER_CLOCK1 = MCK/2 = 42MHz
							  | TC_CMR_LDBSTOP			// stop counter when register B is loaded
							  | TC_CMR_ABETRG			// use TIOA as 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
                              							// reset all other options to 0 ("="" instead of "|=" used!)

	TC2->TC_CHANNEL[1].TC_CCR = TC_CCR_CLKEN;  			// and enable counter clock (should already be enabled by the TC8 used in the main program)

How can I implement the timer/counter described above?

Thanks for any help in advance!

Best,
Florian

What is the circuit for? I guess you are trying to time a pulse? What range is pulse lengths and what accuracy is required?

I am trying to measure the response time of an ultrasonic distance sensor for an accurate position measurement.
So the more precise the better and thats why I want to use a pin interrupted timer/counter.

So you are not happy with the available Arduino function pulsIn() ?

see https://www.arduino.cc/reference/en/language/functions/advanced-io/pulsein/

If not It might be helpful to study the implementation

https://github.com/arduino/ArduinoCore-avr/blob/master/cores/arduino/wiring_pulse.c

Good luck!

I would read the CCR two times from interrupt.

Hi @fl020

I took @ard_newbie 's excellent TC capture code example from here:

...and tweaked it to generate pulse width and period output on the console from an input signal on digital pin D5. A 1000Hz, 25% duty-cycle test signal is provided on D7. Note that the console is set to a baud rate of 250000:

/**************************************************************************************/
/*                        Capture PWM frequency and duty cycle                        */
/*     Hook a jumper between pin 7 (PWML_6) and pin D5 (TIOA6)                        */
/**************************************************************************************/

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


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

/**************          Generate simply a PWM signal                   ***************/
  analogWriteResolution(12); // From 0 to 2exp12  - 1 = 4095
  analogWrite(7, 1024);                                   // Duty cycle is 25% with 12 bits resolution
  
/*************         Capture a PWM frequency and duty cycle          ****************/
  PMC->PMC_PCER1 |= PMC_PCER1_PID33;                      // Timer Counter 2 channel 0 IS TC6, TC6 power ON

  TC2->TC_CHANNEL[0].TC_CMR = TC_CMR_TCCLKS_TIMER_CLOCK1 // capture mode, MCK/2 = 42 MHz, 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
                              
  // If you want to capture PWM data from TC6_Handler()
  TC2->TC_CHANNEL[0].TC_IER |= TC_IER_LDRAS | TC_IER_LDRBS; // Trigger interruption on Load RA and load RB
  
  TC2->TC_CHANNEL[0].TC_CCR = TC_CCR_SWTRG | TC_CCR_CLKEN; // Reset TC counter and enable

  NVIC_EnableIRQ(TC6_IRQn);                                // Enable TC6 interrupts

  Serial.println("Timer Capture");

}

void loop() {
  if (CaptureFlag) {
    CaptureFlag = 0;
    printf("\r %d , %d \n", CaptureCountB - CaptureCountA, CaptureCountB);
  }
  
}

// Note that you could either test status register by polling in loop()
void TC6_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 TIOA6.
  //TimerCount = TC2->TC_CHANNEL[0].TC_CV;            // save the timer counter register, for testing

  uint32_t status = TC2->TC_CHANNEL[0].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) abort();

  // TODO: calculate frequency and duty cycle from data below *****************
  if (status & TC_SR_LDRAS) {  // If ISR is fired by LDRAS then ....
    CaptureCountA = TC2->TC_CHANNEL[0].TC_RA;        // get data from capture register A for TC2 channel 0     
  }
  else { /* if (status && TC_SR_LDRBS)*/  // If ISR is fired by LDRBS then ....
    CaptureCountB = TC2->TC_CHANNEL[0].TC_RB;         // get data from capture register B for TC2 channel 0
    TC2->TC_CHANNEL[0].TC_CCR = TC_CCR_SWTRG;         // reset TC2 channel 0
    CaptureFlag = 1;                      // set flag indicating a new capture value is present
  }

}

Thanks for the tip, I will look into the implementation.

Hi, thank you very much for the code example.

Looks very good.
But where or how do I assign the specific pin to the timer interrupt?

Hi @fl020

The TC timers' input/output pins: TIOAx and TIOBx, where 'x' denotes the channel number 0 to 8, are listed in the SAMD3X8E's datasheet, section 9 " Peripherals" in the PIO Controller Multiplexing tables for ports A, B, C and D:

image

To cross reference the port pins: PA0, PA1, etc... back to the Due's digital and analog pins, its necessary to refer to the board's schematic diagram:

https://docs.arduino.cc/static/58df6c6ca7a17631a289d04b1c68f30d/schematics.pdf

Hi @fl020

Rather confusingly the SAM3X8E datasheet uses two different naming conventions for its 9 TC timers.

The TC timer IO and interrupt channels are labelled from TC0 to TC8, while the registers themselves are named TC0 through to TC2 each with 3 channels: 0, 1 and 2.

This means, for example that TC2 channel 0 interrupt is actually TC6, (yes, it's a nightmare).

On ARM microcontrollers it's necessary to connect the peripheral (TC timer) to the CPU core with the following line of code:

NVIC_EnableIRQ(TC6_IRQn);                                // Enable TC6 interrupts

The load RA and RB trigger interrupts are set in timer TC2 channel 0's IER (Interrupt Enable Register):

// If you want to capture PWM data from TC6_Handler()
TC2->TC_CHANNEL[0].TC_IER |= TC_IER_LDRAS | TC_IER_LDRBS; // Trigger interruption on Load RA and load RB

Hi @MartinL!

Thanks for the reply and sorry for my late reply.

Yes the naming and descriptions of the ATSAM datasheets are very confusing, I am not used to it from other ATMEL tiny/mega datasheets I am familiar to...

Thanks that table helped a lot, it's TIOA7 (Arudino pin D3) in my case.
The timer interrupt gives the same value as the oscilloscope, thank you very much for your productive input!

My latest working code is (for anyone who also want to implement that):

void UltrasonicDistance_init(void)		// initialize Arduino registers and pins
{
	// initialize timer/counter
	PMC->PMC_PCER1 |= PMC_PCER1_PID34;                  // TC7 power ON : Timer Counter 2 channel 1 IS TC7 (datasheet page 39)

	TC2->TC_CHANNEL[1].TC_CMR = TC_CMR_TCCLKS_TIMER_CLOCK1 // TIMER_CLOCK1 = MCK/2 = 42MHz
							  | TC_CMR_LDBSTOP			// stop counter when register B is loaded
							  | TC_CMR_ABETRG			// use TIOA as 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
                              							// reset all other options to 0 ("=" instead of "|=" used!)
	TC2->TC_CHANNEL[1].TC_IER |= TC_IER_LDRAS | TC_IER_LDRBS;	// Trigger interruption on loading RA and RB

	TC2->TC_CHANNEL[1].TC_CCR = TC_CCR_CLKEN;  			// and enable counter clock (should already be enabled by the TC8 used in the main program)
	
}

void UltrasonicDistance_start(void)		// send pulse and start counter
{
	NVIC_DisableIRQ(TC7_IRQn);					// disable TC interrupt
	pinMode(ULTRASONIC_PIN, OUTPUT);			// set SIG pin as output
	digitalWrite(ULTRASONIC_PIN, HIGH);			// sets SIG pin high
	delayMicroseconds(3);						// wait for ~3µs
	digitalWrite(ULTRASONIC_PIN, LOW);			// sets SIG pin low
	delayMicroseconds(3);						// wait for ~3µs
	pinMode(ULTRASONIC_PIN, INPUT);				// set SIG pin as input for reading in the sensor answer

	UDCaptureFlag = 0;
	UDCaptureCountA = 0;
	UDCaptureCountB = 0;
	TC2->TC_CHANNEL[1].TC_CCR |= TC_CCR_SWTRG;	// start and reset counter by software
	NVIC_EnableIRQ(TC7_IRQn);					// enable TC interrupt
}


void TC7_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 TIOA7.

  uint32_t status = TC2->TC_CHANNEL[1].TC_SR;           // read, save and clear satus register

  if (status & TC_SR_LDRAS)   // If ISR is triggered by RA
  {
    UDCaptureCountA = TC2->TC_CHANNEL[1].TC_RA;         // get data from capture register A
  }
  else                        // If ISR is triggered by RB else
  {
    UDCaptureCountB = TC2->TC_CHANNEL[1].TC_RB;         // get data from capture register B
    TC2->TC_CHANNEL[1].TC_CCR = TC_CCR_SWTRG;           // reset counter
    UDCaptureFlag = 1;                                  // set flag indicating a new capture value is present
  }
}

(UDCaptureCountB - UDCaptureCountA is the time difference when multiplied with 23.81ns)