Go Down

Topic: Arduino Zero TCC Capture (Read 54734 times) previous topic - next topic

trampas

I was not sending the resync command before reading counter.

Now that this fixed I am counting but the direction is always counting positive.

MartinL

#166
Feb 05, 2019, 10:25 am Last Edit: Feb 05, 2019, 10:47 am by MartinL
Hi trampas,

The easiest way to get this working, is to set-up the event channels for asynchronous operation and the interrupt inputs to work on level rather than edge detection. This allows the input signals to pass through the event channels to the TCC2 counter unhindered and for the timer to clock off the event edges instead.

Setting up the event channels for asynchronous operation also means that you don't have clock them with a generic clock.

Here's an example that sets up TCC2 to count input events on D12, with D10 determining timer count direction. The TCC2 counter value is output to the console:

Code: [Select]
// Setup TCC2 to count input events on D12, with D10 determining timer count direction
void setup()
{
  SerialUSB.begin(115200);                   // Send data back on the Zero's native port
  while(!SerialUSB);                         // Wait for the SerialUSB port to be ready
 
  PM->APBCMASK.reg |= PM_APBCMASK_EVSYS;     // Switch on the event system peripheral
 
  GCLK->CLKCTRL.reg = GCLK_CLKCTRL_CLKEN |         // Enable the generic clock...
                      GCLK_CLKCTRL_GEN_GCLK0 |     // ....on GCLK0
                      GCLK_CLKCTRL_ID_TCC2_TC3;    // Feed the GCLK0 to TCC2 and TC3
  while (GCLK->STATUS.bit.SYNCBUSY);               // Wait for synchronization

  // Enable the port multiplexer on digital pin D10 count and D12 input
  PORT->Group[g_APinDescription[10].ulPort].PINCFG[g_APinDescription[10].ulPin].reg |= PORT_PINCFG_PULLEN | PORT_PINCFG_PMUXEN;
  PORT->Group[g_APinDescription[12].ulPort].PINCFG[g_APinDescription[12].ulPin].reg |= PORT_PINCFG_PULLEN | PORT_PINCFG_PMUXEN;
 
  // Set-up the pin as an EIC (interrupt) peripheral on D10 and D12
  PORT->Group[g_APinDescription[12].ulPort].PMUX[g_APinDescription[12].ulPin >> 1].reg = PORT_PMUX_PMUXO_A | PORT_PMUX_PMUXE_A ;

  //attachInterrupt(10, NULL, HIGH);                                       // Attach interrupts to digital pin 10 (external interrupt 2)
  EIC->EVCTRL.reg |= EIC_EVCTRL_EXTINTEO2;                                 // Enable event output on external interrupt 2
  EIC->CONFIG[0].reg |= EIC_CONFIG_SENSE2_HIGH;                            // Set event on detecting a HIGH level
 
  //attachInterrupt(12, NULL, HIGH);                                       // Attach interrupts to digital pin 12 (external interrupt 3)
  EIC->EVCTRL.reg |= EIC_EVCTRL_EXTINTEO3;                                 // Enable event output on external interrupt 3
  EIC->CONFIG[0].reg |= EIC_CONFIG_SENSE3_HIGH;                            // Set event on detecting a HIGH level
  EIC->CTRL.reg |= EIC_CTRL_ENABLE;                                        // Enable EIC peripheral
  while (EIC->STATUS.bit.SYNCBUSY);                                        // Wait for synchronization

  EVSYS->USER.reg = EVSYS_USER_CHANNEL(1) |                                // Attach the event user (receiver) to channel 0 (n + 1)
                    EVSYS_USER_USER(EVSYS_ID_USER_TCC2_EV_0);              // Set the event user (receiver) as timer TCC2, event 0
 
  EVSYS->USER.reg = EVSYS_USER_CHANNEL(2) |                                // Attach the event user (receiver) to channel 1 (n + 1)
                    EVSYS_USER_USER(EVSYS_ID_USER_TCC2_EV_1);              // Set the event user (receiver) as timer TCC2, event 1

  EVSYS->CHANNEL.reg = EVSYS_CHANNEL_EDGSEL_NO_EVT_OUTPUT |                // No event edge detection
                       EVSYS_CHANNEL_PATH_ASYNCHRONOUS |                   // Set event path as asynchronous
                       EVSYS_CHANNEL_EVGEN(EVSYS_ID_GEN_EIC_EXTINT_3) |    // Set event generator (sender) as external interrupt 3
                       EVSYS_CHANNEL_CHANNEL(0);                           // Attach the generator (sender) to channel 0
 
  EVSYS->CHANNEL.reg = EVSYS_CHANNEL_EDGSEL_NO_EVT_OUTPUT |                // No event edge detection
                       EVSYS_CHANNEL_PATH_ASYNCHRONOUS |                   // Set event path as asynchronous
                       EVSYS_CHANNEL_EVGEN(EVSYS_ID_GEN_EIC_EXTINT_2) |    // Set event generator (sender) as external interrupt 2
                       EVSYS_CHANNEL_CHANNEL(1);                           // Attach the generator (sender) to channel 1
 
  TCC2->EVCTRL.reg |= TCC_EVCTRL_TCEI1 |                                   // Enable the TCC event 1 input
                      TCC_EVCTRL_TCEI0 |                                   // Enable the TCC event 0 input
                      //TCC_EVCTRL_TCINV1 |                                  // Invert the event 1 input
                      //TCC_EVCTRL_TCINV0 |                                  // Invert the event 0 input
                      TCC_EVCTRL_EVACT1_DIR |                              // Set event 1 to change the counter direction
                      TCC_EVCTRL_EVACT0_COUNTEV;                           // Set event 0 to count the incoming events

  TCC2->WAVE.reg = TCC_WAVE_WAVEGEN_NFRQ;                                  // Set the TCC2 timer counter to normal frequency mode
  while (TCC2->SYNCBUSY.bit.WAVE);                                         // Wait for synchronization
                                                                                     
  TCC2->CTRLA.bit.ENABLE = 1;                                             // Enable TCC2
  while (TCC2->SYNCBUSY.bit.ENABLE);                                      // Wait for synchronization
}

void loop()
{
  TCC2->CTRLBSET.reg = TCC_CTRLBSET_CMD_READSYNC;                         // Trigger a read synchronization on the COUNT register
  while (TCC2->SYNCBUSY.bit.CTRLB);                                       // Wait for the CTRLB register write synchronization
  while (TCC2->SYNCBUSY.bit.COUNT);                                       // Wait for the COUNT register read sychronization
  SerialUSB.println(TCC2->COUNT.reg);                                     // Output the TCC2 COUNT register
}


aromring

Hello Everyone,
I have implemented some of your code (dutifully acknowledged) in my project:

https://www.instructables.com/id/How-to-Measure-High-Frequency-and-Duty-Cycle-Simul/

Thank you for posting!

nappo

I would like to thank the many people contributing to the topic "Zero TCC Capture"! The information I found here regarding the configuration of the timers was very valuable for my project.

In particular the code of MartinL using the DMAC for reading the PWM values is a wonderful piece of software, since it can deliver a fast capture of pulse count rate including timestamps without any load of the main CPU (#107, TCCCapture_DMAC_TCCount.ino). Thank you very much!

By adapting the code for my needs, I found a little flaw. Though the TCC counter compare registers have 24 bit, the measured output for the period (CC0) already  overflows at 16bit, for example, by feeding the counter with a low enough frequency (e.g. 700 Hz). It turned out that this behaviour is due to the chosen beat size (HWORD, 16bit) for the DMA transfer. Changing the beat size to WORD (32bit) fixes this issue.

Simply replace the two instances of

descriptor.btctrl = DMAC_BTCTRL_BEATSIZE_HWORD | DMAC_BTCTRL_VALID;

by

descriptor.btctrl = DMAC_BTCTRL_BEATSIZE_WORD | DMAC_BTCTRL_VALID;

Thanks again for your generous help!


lorenrus

Hi electro_95,

I finally solved it.

The code automatically captures an incoming digital waveform's period and pulse-width.

The incoming waveform is detected by enabling interrupts for level detection on the input pin. The event system is sort of a 12 channel highway between peripherals that allows them to communicate without CPU intervention. By enabling an output event for the interrupt pin (generator/sender) and the input event for a timer (user/receiver) and connecting them on a given channel, we can pass infomation about changes on the input pin to the timer using the event system. With the timer set to "Period and Pulse-Width Capture Action" mode, we can then automatically capture the period and pulse-width of the waveform.

The timer starts on the rising edge of the event line. The number of counter ticks to the subsequent trailing edge gives us the pulse-width, which is stored in the counter compare register 1 (CC1). The following rising edge gives us the period, which is stored in the counter compare register 0 (CC0). At this point the counter is reset back to 0 to start the cycle over again.

The following code outputs on the Zero's native port, the period and pulse-width of a radio controlled receiver channel in microseconds, (pulse-width: 1-2ms, period: ~22ms) using capture on TC3, at 1MHz (1us tick):

Code: [Select]
// Setup TC3 to capture pulse-width and period
volatile boolean periodComplete;
volatile uint16_t isrPeriod;
volatile uint16_t isrPulsewidth;
uint16_t period;
uint16_t pulsewidth;

void setup()
{
  SerialUSB.begin(115200);                  // Send data back on the Zero's native port
  while(!SerialUSB);                        // Wait for the SerialUSB port to be ready
 
  REG_PM_APBCMASK |= PM_APBCMASK_EVSYS;     // Switch on the event system peripheral
 
  REG_GCLK_GENDIV = GCLK_GENDIV_DIV(3) |    // Divide the 48MHz system clock by 3 = 16MHz
                    GCLK_GENDIV_ID(5);      // Set division on Generic Clock Generator (GCLK) 5
  while (GCLK->STATUS.bit.SYNCBUSY);        // Wait for synchronization

  REG_GCLK_GENCTRL = GCLK_GENCTRL_IDC |           // Set the duty cycle to 50/50 HIGH/LOW
                     GCLK_GENCTRL_GENEN |         // Enable GCLK 5
                     GCLK_GENCTRL_SRC_DFLL48M |   // Set the clock source to 48MHz
                     GCLK_GENCTRL_ID(5);          // Set clock source on GCLK 5
  while (GCLK->STATUS.bit.SYNCBUSY);              // Wait for synchronization*/

  REG_GCLK_CLKCTRL = GCLK_CLKCTRL_CLKEN |         // Enable the generic clock...
                     GCLK_CLKCTRL_GEN_GCLK5 |     // ....on GCLK5
                     GCLK_CLKCTRL_ID_TCC2_TC3;    // Feed the GCLK5 to TCC2 and TC3
  while (GCLK->STATUS.bit.SYNCBUSY);              // Wait for synchronization

  REG_EIC_EVCTRL |= EIC_EVCTRL_EXTINTEO3;                                 // Enable event output on external interrupt 3
  attachInterrupt(12, NULL, HIGH);                                        // Attach interrupts to digital pin 12 (external interrupt 3)
 
  REG_EVSYS_USER = EVSYS_USER_CHANNEL(1) |                                // Attach the event user (receiver) to channel 0 (n + 1)
                   EVSYS_USER_USER(EVSYS_ID_USER_TC3_EVU);                // Set the event user (receiver) as timer TC3

  REG_EVSYS_CHANNEL = EVSYS_CHANNEL_EDGSEL_NO_EVT_OUTPUT |                // No event edge detection
                      EVSYS_CHANNEL_PATH_ASYNCHRONOUS |                   // Set event path as asynchronous
                      EVSYS_CHANNEL_EVGEN(EVSYS_ID_GEN_EIC_EXTINT_3) |    // Set event generator (sender) as external interrupt 3
                      EVSYS_CHANNEL_CHANNEL(0);                           // Attach the generator (sender) to channel 0

  REG_TC3_EVCTRL |= TC_EVCTRL_TCEI |              // Enable the TC event input
                    /*TC_EVCTRL_TCINV |*/         // Invert the event input
                    TC_EVCTRL_EVACT_PPW;          // Set up the timer for capture: CC0 period, CC1 pulsewidth
                   
  REG_TC3_READREQ = TC_READREQ_RREQ |             // Enable a read request
                    TC_READREQ_ADDR(0x06);        // Offset of the CTRLC register
  while (TC3->COUNT16.STATUS.bit.SYNCBUSY);       // Wait for (read) synchronization
  REG_TC3_CTRLC |= TC_CTRLC_CPTEN1 |              // Enable capture on CC1
                   TC_CTRLC_CPTEN0;               // Enable capture on CC0
  while (TC3->COUNT16.STATUS.bit.SYNCBUSY);       // Wait for (write) synchronization

  //NVIC_DisableIRQ(TC3_IRQn);
  //NVIC_ClearPendingIRQ(TC3_IRQn);
  NVIC_SetPriority(TC3_IRQn, 0);      // Set the Nested Vector Interrupt Controller (NVIC) priority for TC3 to 0 (highest)
  NVIC_EnableIRQ(TC3_IRQn);           // Connect the TC3 timer to the Nested Vector Interrupt Controller (NVIC)
 
  REG_TC3_INTENSET = TC_INTENSET_MC1 |            // Enable compare channel 1 (CC1) interrupts
                     TC_INTENSET_MC0;             // Enable compare channel 0 (CC0) interrupts
 
  REG_TC3_CTRLA |= TC_CTRLA_PRESCALER_DIV16 |     // Set prescaler to 16, 16MHz/16 = 1MHz
                   TC_CTRLA_ENABLE;               // Enable TC3
  while (TC3->COUNT16.STATUS.bit.SYNCBUSY);       // Wait for synchronization
}

void loop()
{
  if (periodComplete)                             // Check if the period is complete
  {
    noInterrupts();                               // Read the new period and pulse-width
    period = isrPeriod;                   
    pulsewidth = isrPulsewidth;
    interrupts();
    SerialUSB.print(period);                      // Output the results
    SerialUSB.print(F("   "));
    SerialUSB.println(pulsewidth);
    periodComplete = false;                       // Start a new period
  }
}

void TC3_Handler()                                // Interrupt Service Routine (ISR) for timer TC3
{     
  // Check for match counter 0 (MC0) interrupt
  if (TC3->COUNT16.INTFLAG.bit.MC0)             
  {
    REG_TC3_READREQ = TC_READREQ_RREQ |           // Enable a read request
                      TC_READREQ_ADDR(0x18);      // Offset address of the CC0 register
    while (TC3->COUNT16.STATUS.bit.SYNCBUSY);     // Wait for (read) synchronization
    isrPeriod = REG_TC3_COUNT16_CC0;              // Copy the period 
    periodComplete = true;                        // Indicate that the period is complete
  }

  // Check for match counter 1 (MC1) interrupt
  if (TC3->COUNT16.INTFLAG.bit.MC1)           
  {
    REG_TC3_READREQ = TC_READREQ_RREQ |           // Enable a read request
                      TC_READREQ_ADDR(0x1A);      // Offset address of the CC1 register
    while (TC3->COUNT16.STATUS.bit.SYNCBUSY);     // Wait for (read) synchronization
    isrPulsewidth = REG_TC3_COUNT16_CC1;          // Copy the pulse-width
  }
}


Hi , this code is good also for Arduino M0 Pro ?

MartinL

#170
May 27, 2020, 11:33 pm Last Edit: May 27, 2020, 11:43 pm by MartinL
Hi lorenrus,

Below is an updated version of the code you describe. I've tested it on the throttle channel of my RC receiver, plugged into digital pin D12:

PW 1055
P 22020
PW 1055
P 22026
PW 1055
P 22017
PW 1055
P 22020
PW 1055
P 22017

It outputs 1ms (1055us) pulses at approximately 50Hz (22017us) intervals.

It should work on the Arduino M0 Pro, as hardwarewise it's very similar to the Arduino Zero, only that D2 and D4 are swapped (and the Zero has an addition ATN GPIO pin).

Here's the updated code:

Code: [Select]
// Setup TC3 to capture pulse-width and period
volatile boolean periodComplete;
volatile uint16_t isrPeriod;
volatile uint16_t isrPulsewidth;
uint16_t period;
uint16_t pulsewidth;

void setup()   {               
  SerialUSB.begin(115200);                         // Initialise the native serial port
  while(!SerialUSB);                               // Wait for the console to open
 
  PM->APBCMASK.reg |= PM_APBCMASK_EVSYS;           // Switch on the event system peripheral
 
  GCLK->GENDIV.reg = GCLK_GENDIV_DIV(3) |          // Divide the 48MHz system clock by 3 = 16MHz
                     GCLK_GENDIV_ID(5);            // Set division on Generic Clock Generator (GCLK) 5
  while (GCLK->STATUS.bit.SYNCBUSY);               // Wait for synchronization

  GCLK->GENCTRL.reg = GCLK_GENCTRL_IDC |           // Set the duty cycle to 50/50 HIGH/LOW
                      GCLK_GENCTRL_GENEN |         // Enable GCLK 5
                      GCLK_GENCTRL_SRC_DFLL48M |   // Set the clock source to 48MHz
                      GCLK_GENCTRL_ID(5);          // Set clock source on GCLK 5
  while (GCLK->STATUS.bit.SYNCBUSY);               // Wait for synchronization

  GCLK->CLKCTRL.reg = GCLK_CLKCTRL_CLKEN |         // Enable the generic clock...
                      GCLK_CLKCTRL_GEN_GCLK5 |     // ....on GCLK5
                      GCLK_CLKCTRL_ID_TCC2_TC3;    // Feed the GCLK5 to TCC2 and TC3
  while (GCLK->STATUS.bit.SYNCBUSY);               // Wait for synchronization

  // Enable the port multiplexer on digital pin D12
  PORT->Group[g_APinDescription[12].ulPort].PINCFG[g_APinDescription[12].ulPin].bit.PMUXEN = 1;
  // Set-up the pin as an EIC (interrupt) peripheral on D12
  PORT->Group[g_APinDescription[12].ulPort].PMUX[g_APinDescription[12].ulPin >> 1].reg |= PORT_PMUX_PMUXO_A;

  EIC->EVCTRL.reg |= EIC_EVCTRL_EXTINTEO3;                                 // Enable event output on external interrupt 3
  EIC->CONFIG[0].reg |= EIC_CONFIG_SENSE3_HIGH;                            // Set event detecting a HIGH level
  EIC->INTENCLR.reg = EIC_INTENCLR_EXTINT3;                                // Disable interrupts on external interrupt 3
  EIC->CTRL.reg |= EIC_CTRL_ENABLE;                                        // Enable EIC peripheral
  while (EIC->STATUS.bit.SYNCBUSY);                                        // Wait for synchronization
 
  EVSYS->USER.reg = EVSYS_USER_CHANNEL(1) |                                // Attach the event user (receiver) to channel 0 (n + 1)
                    EVSYS_USER_USER(EVSYS_ID_USER_TC3_EVU);                // Set the event user (receiver) as timer TC3

  EVSYS->CHANNEL.reg = EVSYS_CHANNEL_EDGSEL_NO_EVT_OUTPUT |                // No event edge detection
                       EVSYS_CHANNEL_PATH_ASYNCHRONOUS |                   // Set event path as asynchronous
                       EVSYS_CHANNEL_EVGEN(EVSYS_ID_GEN_EIC_EXTINT_3) |    // Set event generator (sender) as external interrupt 3
                       EVSYS_CHANNEL_CHANNEL(0);                           // Attach the generator (sender) to channel 0

  TC3->COUNT16.EVCTRL.reg |= TC_EVCTRL_TCEI |              // Enable the TC event input
                             //TC_EVCTRL_TCINV |             // Invert the event input
                             TC_EVCTRL_EVACT_PPW;          // Set up the timer for capture: CC0 period, CC1 pulsewidth
                 
  TC3->COUNT16.CTRLC.reg = TC_CTRLC_CPTEN1 |               // Enable capture on CC1
                           TC_CTRLC_CPTEN0;                // Enable capture on CC0
  while (TC3->COUNT16.STATUS.bit.SYNCBUSY);                // Wait for (write) synchronization

  NVIC_SetPriority(TC3_IRQn, 0);      // Set the Nested Vector Interrupt Controller (NVIC) priority for TC3 to 0 (highest)
  NVIC_EnableIRQ(TC3_IRQn);           // Connect the TC3 timer to the Nested Vector Interrupt Controller (NVIC)
 
  TC3->COUNT16.INTENSET.reg = TC_INTENSET_MC1 |            // Enable compare channel 1 (CC1) interrupts
                              TC_INTENSET_MC0;             // Enable compare channel 0 (CC0) interrupts
 
  TC3->COUNT16.CTRLA.reg = TC_CTRLA_PRESCALER_DIV16;       // Set prescaler to 16, 16MHz/16 = 1MHz
 
  TC3->COUNT16.CTRLA.bit.ENABLE = 1;                       // Enable TC3
  while (TC3->COUNT16.STATUS.bit.SYNCBUSY);                // Wait for synchronization
}

void loop() {
  if (periodComplete)                             // Check if the period is complete
  {
    noInterrupts();                               // Read the new period and pulse-width
    period = isrPeriod;                 
    pulsewidth = isrPulsewidth;
    interrupts();
    SerialUSB.print("P ");
    SerialUSB.println(period);
    SerialUSB.print("PW ");
    SerialUSB.println(pulsewidth);
    periodComplete = false;                       // Start a new period
  }
}

void TC3_Handler()                                // Interrupt Service Routine (ISR) for timer TC3
{   
  // Check for match counter 0 (MC0) interrupt
  if (TC3->COUNT16.INTFLAG.bit.MC0)           
  {
    TC3->COUNT16.READREQ.reg = TC_READREQ_RREQ |           // Enable a read request
                               TC_READREQ_ADDR(0x18);      // Offset address of the CC0 register
    while (TC3->COUNT16.STATUS.bit.SYNCBUSY);              // Wait for (read) synchronization
    isrPeriod = TC3->COUNT16.CC[0].reg;                    // Copy the period 
    periodComplete = true;                                 // Indicate that the period is complete
  }

  // Check for match counter 1 (MC1) interrupt
  if (TC3->COUNT16.INTFLAG.bit.MC1)         
  {
    TC3->COUNT16.READREQ.reg = TC_READREQ_RREQ |           // Enable a read request
                               TC_READREQ_ADDR(0x1A);      // Offset address of the CC1 register
    while (TC3->COUNT16.STATUS.bit.SYNCBUSY);              // Wait for (read) synchronization
    isrPulsewidth = TC3->COUNT16.CC[1].reg;                // Copy the pulse-width
  }
}

lorenrus

Hi lorenrus,

Below is an updated version of the code you describe. I've tested it on the throttle channel of my RC receiver, plugged into digital pin D12:

PW 1055
P 22020
PW 1055
P 22026
PW 1055
P 22017
PW 1055
P 22020
PW 1055
P 22017

It outputs 1ms (1055us) pulses at approximately 50Hz (22017us) intervals.

It should work on the Arduino M0 Pro, as hardwarewise it's very similar to the Arduino Zero, only that D2 and D4 are swapped (and the Zero has an addition ATN GPIO pin).

Here's the updated code:

Code: [Select]
// Setup TC3 to capture pulse-width and period
volatile boolean periodComplete;
volatile uint16_t isrPeriod;
volatile uint16_t isrPulsewidth;
uint16_t period;
uint16_t pulsewidth;

void setup()   {               
  SerialUSB.begin(115200);                         // Initialise the native serial port
  while(!SerialUSB);                               // Wait for the console to open
 
  PM->APBCMASK.reg |= PM_APBCMASK_EVSYS;           // Switch on the event system peripheral
 
  GCLK->GENDIV.reg = GCLK_GENDIV_DIV(3) |          // Divide the 48MHz system clock by 3 = 16MHz
                     GCLK_GENDIV_ID(5);            // Set division on Generic Clock Generator (GCLK) 5
  while (GCLK->STATUS.bit.SYNCBUSY);               // Wait for synchronization

  GCLK->GENCTRL.reg = GCLK_GENCTRL_IDC |           // Set the duty cycle to 50/50 HIGH/LOW
                      GCLK_GENCTRL_GENEN |         // Enable GCLK 5
                      GCLK_GENCTRL_SRC_DFLL48M |   // Set the clock source to 48MHz
                      GCLK_GENCTRL_ID(5);          // Set clock source on GCLK 5
  while (GCLK->STATUS.bit.SYNCBUSY);               // Wait for synchronization

  GCLK->CLKCTRL.reg = GCLK_CLKCTRL_CLKEN |         // Enable the generic clock...
                      GCLK_CLKCTRL_GEN_GCLK5 |     // ....on GCLK5
                      GCLK_CLKCTRL_ID_TCC2_TC3;    // Feed the GCLK5 to TCC2 and TC3
  while (GCLK->STATUS.bit.SYNCBUSY);               // Wait for synchronization

  // Enable the port multiplexer on digital pin D12
  PORT->Group[g_APinDescription[12].ulPort].PINCFG[g_APinDescription[12].ulPin].bit.PMUXEN = 1;
  // Set-up the pin as an EIC (interrupt) peripheral on D12
  PORT->Group[g_APinDescription[12].ulPort].PMUX[g_APinDescription[12].ulPin >> 1].reg |= PORT_PMUX_PMUXO_A;

  EIC->EVCTRL.reg |= EIC_EVCTRL_EXTINTEO3;                                 // Enable event output on external interrupt 3
  EIC->CONFIG[0].reg |= EIC_CONFIG_SENSE3_HIGH;                            // Set event detecting a HIGH level
  EIC->INTENCLR.reg = EIC_INTENCLR_EXTINT3;                                // Disable interrupts on external interrupt 3
  EIC->CTRL.reg |= EIC_CTRL_ENABLE;                                        // Enable EIC peripheral
  while (EIC->STATUS.bit.SYNCBUSY);                                        // Wait for synchronization
 
  EVSYS->USER.reg = EVSYS_USER_CHANNEL(1) |                                // Attach the event user (receiver) to channel 0 (n + 1)
                    EVSYS_USER_USER(EVSYS_ID_USER_TC3_EVU);                // Set the event user (receiver) as timer TC3

  EVSYS->CHANNEL.reg = EVSYS_CHANNEL_EDGSEL_NO_EVT_OUTPUT |                // No event edge detection
                       EVSYS_CHANNEL_PATH_ASYNCHRONOUS |                   // Set event path as asynchronous
                       EVSYS_CHANNEL_EVGEN(EVSYS_ID_GEN_EIC_EXTINT_3) |    // Set event generator (sender) as external interrupt 3
                       EVSYS_CHANNEL_CHANNEL(0);                           // Attach the generator (sender) to channel 0

  TC3->COUNT16.EVCTRL.reg |= TC_EVCTRL_TCEI |              // Enable the TC event input
                             //TC_EVCTRL_TCINV |             // Invert the event input
                             TC_EVCTRL_EVACT_PPW;          // Set up the timer for capture: CC0 period, CC1 pulsewidth
                 
  TC3->COUNT16.CTRLC.reg = TC_CTRLC_CPTEN1 |               // Enable capture on CC1
                           TC_CTRLC_CPTEN0;                // Enable capture on CC0
  while (TC3->COUNT16.STATUS.bit.SYNCBUSY);                // Wait for (write) synchronization

  NVIC_SetPriority(TC3_IRQn, 0);      // Set the Nested Vector Interrupt Controller (NVIC) priority for TC3 to 0 (highest)
  NVIC_EnableIRQ(TC3_IRQn);           // Connect the TC3 timer to the Nested Vector Interrupt Controller (NVIC)
 
  TC3->COUNT16.INTENSET.reg = TC_INTENSET_MC1 |            // Enable compare channel 1 (CC1) interrupts
                              TC_INTENSET_MC0;             // Enable compare channel 0 (CC0) interrupts
 
  TC3->COUNT16.CTRLA.reg = TC_CTRLA_PRESCALER_DIV16;       // Set prescaler to 16, 16MHz/16 = 1MHz
 
  TC3->COUNT16.CTRLA.bit.ENABLE = 1;                       // Enable TC3
  while (TC3->COUNT16.STATUS.bit.SYNCBUSY);                // Wait for synchronization
}

void loop() {
  if (periodComplete)                             // Check if the period is complete
  {
    noInterrupts();                               // Read the new period and pulse-width
    period = isrPeriod;                 
    pulsewidth = isrPulsewidth;
    interrupts();
    SerialUSB.print("P ");
    SerialUSB.println(period);
    SerialUSB.print("PW ");
    SerialUSB.println(pulsewidth);
    periodComplete = false;                       // Start a new period
  }
}

void TC3_Handler()                                // Interrupt Service Routine (ISR) for timer TC3
{   
  // Check for match counter 0 (MC0) interrupt
  if (TC3->COUNT16.INTFLAG.bit.MC0)           
  {
    TC3->COUNT16.READREQ.reg = TC_READREQ_RREQ |           // Enable a read request
                               TC_READREQ_ADDR(0x18);      // Offset address of the CC0 register
    while (TC3->COUNT16.STATUS.bit.SYNCBUSY);              // Wait for (read) synchronization
    isrPeriod = TC3->COUNT16.CC[0].reg;                    // Copy the period 
    periodComplete = true;                                 // Indicate that the period is complete
  }

  // Check for match counter 1 (MC1) interrupt
  if (TC3->COUNT16.INTFLAG.bit.MC1)         
  {
    TC3->COUNT16.READREQ.reg = TC_READREQ_RREQ |           // Enable a read request
                               TC_READREQ_ADDR(0x1A);      // Offset address of the CC1 register
    while (TC3->COUNT16.STATUS.bit.SYNCBUSY);              // Wait for (read) synchronization
    isrPulsewidth = TC3->COUNT16.CC[1].reg;                // Copy the pulse-width
  }
}

Hi martin with your code and Arduino M0 Pro can i meausere frequency = 1 Mhz ?

Thank you

MartinL

#172
May 28, 2020, 01:16 pm Last Edit: May 28, 2020, 01:17 pm by MartinL
Hi lorenrus,

Quote
...can i meausere frequency = 1 Mhz ?
The example code provided is designed to operate at lower frequencies, in this case a RC receiver.

The TC capture can however run at much faster speeds, but how it's set-up depends on your minimum pulse-width and maximum period. It's a trade off: measuring faster, narrower pulses, reduces the maximum period before the timer overflows.

If you let me know the minimum pulse-width and maximum period of your input signal, I'll be able to suggest suitable timer settings.

lorenrus

#173
May 28, 2020, 01:21 pm Last Edit: May 28, 2020, 02:10 pm by lorenrus
Hi lorenrus,

The example code provided is designed to operate at lower frequencies, in this case a RC receiver.

The TC capture can however run at much faster speeds, but how it's set-up depends on your minimum pulse-width and maximum period. It's a trade off: measuring faster, narrower pulses, reduces the maximum period before the timer overflows.

If you let me know the minimum pulse-width and maximum period of your input signal, I'll be able to suggest suitable timer settings.
Hi
and thanks for reply

Yes infact, i modify the value of the prescaler to 1.

But if you show a code for higher frequency should be better.

i have to measure to two wave:

- First wave is F = 115 200 Hz and T = 8,68 us and T/2 = 4 us
- Second wave is F = 1 Mhz and T = 1 us and T/2 = 0.5 us

Another  information, the value that you read in P e PW is already in us ?


Thank you

MartinL

#174
May 28, 2020, 03:00 pm Last Edit: May 28, 2020, 03:02 pm by MartinL
Hi lorenrus,

If the period is just two times the pulse-width, then timer overflow isn't an issue. This issue only occurs if you've got widely spaced pulses and the timer runs out (overflows) before the next pulse.

As you mention, chaging the timer's presaler is one option to increase the timer's speed from 1MHz to 16MHz. The other is by changing the generic clock (GCLK5) divisor to 1 instead of 3, increasing the timer's speed further from 16MHz to 48MHz:

Code: [Select]
GCLK->GENDIV.reg = GCLK_GENDIV_DIV(1) |          // Divide the 48MHz system clock by 1 = 48MHz
                     GCLK_GENDIV_ID(5);          // Set division on Generic Clock Generator (GCLK) 5
  while (GCLK->STATUS.bit.SYNCBUSY);             // Wait for synchronization

The value for the pulse-width and period are the number of elapsed timer ticks, so actual time is this value multiplied by the timer tick period (or 1 / timer frequency):

pulse-width or period = (1 / timer frequency) * number of ticks

For instance, in the example code the timer frequency is 1MHz:

pulse-width = (1 / 1MHz) * 1055 = 1055us

period = (1 / 1MHz) * 22017 = 22017us

lorenrus

I add my part of code, i generate a PWM wave with f = 96 khz.

I entered the code in the attachments as I was exceeding in the font size.


I obtain the result reported in photo.

the max freq of timer 1 is 16 Mhz if i put its prescaler to one ?

P = (1/16 000 000) * 509 = 31,81 us but my wave is F = 94 khz --> T = 10,64us.

What am I doing wrong?

Thank you


MartinL

#176
May 28, 2020, 04:30 pm Last Edit: May 28, 2020, 04:31 pm by MartinL
Hi lorenrus,

It's because in your TCC_PulseW_Period_Count.ino sketch, the GCLK5 divisor has been set to 1 and the TC3 prescaler to DIV1. This means that the TC3 timer is running at 48MHz, therefore:

P = (1/48000000) * 509 = 10.6us = 94kHz approx.

lorenrus

Hi lorenrus,

It's because in your TCC_PulseW_Period_Count.ino sketch, the GCLK5 divisor has been set to 1 and the TC3 prescaler to DIV1. This means that the TC3 timer is running at 48MHz, therefore:

P = (1/48000000) * 509 = 10.6us = 94kHz approx.
Yes i found the error.

I have another strange behavior, up to 530 khz I can read correctly and I can also see the values on screen. Over the 530 khz of the incoming wave I no longer print them on the Arduino plotter. What is it due to?

Thank you

MartinL

#178
May 28, 2020, 06:56 pm Last Edit: May 28, 2020, 10:16 pm by MartinL
Quote
I have another strange behavior, up to 530 khz I can read correctly and I can also see the values on screen. Over the 530 khz of the incoming wave I no longer print them on the Arduino plotter. What is it due to?
The issue is something I should've mentioned earlier. As the frequency increases the SAMD21 isn't able to service the serial port quickly enough. Removing the SerialUSB entries the allows the TC capture to work at 530kHz, but now another problem arises, as calling the TC3 timer's interrupt service routine (TC3_Handler()) 106000000 (2 * 530000) times a second prevents the CPU from doing anything else.

The solution is to either only turn on the TC3's interrupt service routine when required, by enabling and disabling the its Match Compare 0 (MC0) and Match Compare 1 (MC1) interrupts.

The other solution is to use the Direct Memory Access Controller (DMAC) to continously read the TC3 CC[0] and CC[1] registers for the pulse-width and period and transfer them to memory. This frees up the CPU from the time consuming task of servicing the ISR, allowing it to read the results from memory at its leisure.

I have some example code for TC capture with the DMAC, but I wrote it awhile ago and just need to retest it before publishing. I'll post it tomorrow.

lorenrus

Ok perfect.

Thank you very much Martin.

See you tomorrow

Go Up