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?
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.
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
}
}
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:
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:
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:
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
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)