Using TC to count from GPIO

Hello,

I am currently looking for a way to redirect some GPIO inputs to TC counters without CPU intervention on a MKRGSM 1400, to calculate the frequency every second.

I am quite at a loss about how to do it, and I do not manage to make analogies between the SAMD21 datasheet and the code to use.

I found that this thread :

spoke about a code that seems to do similar things, but I cannot find the mentioned code.

Could somebody help me please?

Hi @diamondemon

It's possible to acquire the pulse width and period of incoming pulses with the SAMD21's TC timer on pin D12 of the MKR1400 GSM, using the following code:

// Setup TC4 to capture pulse-width and period on digital pin D12
volatile boolean periodComplete;
volatile uint32_t isrPeriod;
volatile uint32_t isrPulsewidth;
uint32_t period;
uint32_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(4);            // Set division on Generic Clock Generator (GCLK) 4

  GCLK->GENCTRL.reg = GCLK_GENCTRL_IDC |           // Set the duty cycle to 50/50 HIGH/LOW
                      GCLK_GENCTRL_GENEN |         // Enable GCLK 4
                      GCLK_GENCTRL_SRC_DFLL48M |   // Set the clock source to 48MHz
                      GCLK_GENCTRL_ID(4);          // Set clock source on GCLK 4
  while (GCLK->STATUS.bit.SYNCBUSY);               // Wait for synchronization
  
  GCLK->CLKCTRL.reg = GCLK_CLKCTRL_CLKEN |         // Route GCLK4 to TC4 and TC5
                      GCLK_CLKCTRL_GEN_GCLK4 |     
                      GCLK_CLKCTRL_ID_TC4_TC5;     

  // Enable the port multiplexer on port pin PA09
  PORT->Group[PORTA].PINCFG[9].bit.PMUXEN = 1;
  // Set-up the pin as an EIC (interrupt) on port pin PA09
  PORT->Group[PORTA].PMUX[9 >> 1].reg |= PORT_PMUX_PMUXO_A;

  EIC->EVCTRL.reg |= EIC_EVCTRL_EXTINTEO9;                                 // Enable event output on external interrupt 9
  EIC->CONFIG[1].reg |= EIC_CONFIG_SENSE1_HIGH;                            // Set event detecting a HIGH level
  EIC->INTENCLR.reg = EIC_INTENCLR_EXTINT9;                                // Disable interrupts on external interrupt 9
  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_TC4_EVU);                // Set the event user (receiver) as timer TC4

  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_9) |    // Set event generator (sender) as external interrupt 9
                       EVSYS_CHANNEL_CHANNEL(0);                           // Attach the generator (sender) to channel 0

  TC4->COUNT32.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
                  
  TC4->COUNT32.CTRLC.reg = TC_CTRLC_CPTEN1 |               // Enable capture on CC1
                           TC_CTRLC_CPTEN0;                // Enable capture on CC0
  while (TC4->COUNT32.STATUS.bit.SYNCBUSY);                // Wait for synchronization

  NVIC_SetPriority(TC4_IRQn, 0);      // Set the Nested Vector Interrupt Controller (NVIC) priority for TC4 to 0 (highest)
  NVIC_EnableIRQ(TC4_IRQn);           // Connect the TC4 timer to the Nested Vector Interrupt Controller (NVIC)
 
  TC4->COUNT32.INTENSET.reg = TC_INTENSET_MC1 |            // Enable compare channel 1 (CC1) interrupts
                              TC_INTENSET_MC0;             // Enable compare channel 0 (CC0) interrupts
  
  TC4->COUNT32.CTRLA.reg = TC_CTRLA_PRESCSYNC_PRESC |      // Overflow on precaler clock, (rather than the GCLK)
                           TC_CTRLA_PRESCALER_DIV16 |      // Set prescaler to 16, 16MHz/16 = 1MHz
                           TC_CTRLA_MODE_COUNT32;          // Set TC4/TC5 to 32-bit timer mode
                          
  TC4->COUNT32.CTRLA.bit.ENABLE = 1;                       // Enable TC4
  while (TC4->COUNT32.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("PW: ");
    SerialUSB.print(pulsewidth);
    SerialUSB.print(F("   "));
    SerialUSB.print("P : ");
    SerialUSB.println(period);
    periodComplete = false;                       // Start a new period
  }
}

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

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

Thank you, what a well-explained piece of code!
I was looking to use the GCLKIN source but it seems to not be a good solution since it prevents any synchronisation when there's nothing coming on the GCLKIN port.

  • However, I seem to run into an odd problem: I made a simple circuit with a pulldown resistor to create the impulsions with a button. When I put the cable on the pin D12, my PC doesn't find the Arduino board anymore... (even before compiling and loading the code on it)
    Can I change the pin freely?

  • Moreover, the Serial port does not show anything even after minutes of the code running, is that related to the aforementioned problem?

Hi @diamondemon

The MKR1400 GSM's D12 pin doubles as the I2C SCL line and is already pulled up with an on-board 4k7 resistor. An external pull-down resistor, depending on its value, may or may not pull the input down to the correct voltage.

To test that the above code is working OK, I added a test PWM output, 1500us pulses at 50Hz (20000us period) on D7. After compilation I connected D7 to D12.

Here's the additional test code (that I inserted after the TC4 and TC5 GCLK set-up):

  GCLK->CLKCTRL.reg = GCLK_CLKCTRL_CLKEN |         // Route GCLK4 to TCC0 and TCC1
                      GCLK_CLKCTRL_GEN_GCLK4 |     
                      GCLK_CLKCTRL_ID_TCC0_TCC1;  
  while (GCLK->STATUS.bit.SYNCBUSY);              

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

  TCC0->WAVE.reg =  TCC_WAVE_WAVEGEN_NPWM;          // Enable Normal PWM (NPWM) mode
  while (TCC0->SYNCBUSY.bit.WAVE);                  // Wait for synchronization
  
  TCC0->PER.reg = 19999;                            // Set the timer to output to 50Hz
  while (TCC0->SYNCBUSY.bit.PER);                   // Wait for synchronization

  TCC0->CC[3].reg = 1500;                            // Set CC3 duty-cycle to 1500us pulse width
  while (TCC0->SYNCBUSY.bit.CC3);                   // Wait for synchronization

  TCC0->CTRLA.reg = TCC_CTRLA_PRESCSYNC_PRESC |     // Reload timer on the next prescaler clock
                    TCC_CTRLA_PRESCALER_DIV16;      // Set prescaler to 16, 16MHz/16 = 1MHz
                    
  TCC0->CTRLA.bit.ENABLE = 1;                       // Enable timer TCC0
  while (TCC0->SYNCBUSY.bit.ENABLE);                // Wait for synchronization

The output on the console is as follows:

PW: 1499   P : 19999
PW: 1499   P : 19999
PW: 1499   P : 19999
PW: 1499   P : 19999
PW: 1499   P : 19999
PW: 1499   P : 19999

The above example measures microseconds, however it's possible to tweak both the GCLK4 divisor and/or the TC timer prescaler, to generate the required timer resolution for your application.

Could you please enlighten me about how you link D7 to D12? I used a simple wire, but this does not do anything. I still remain with a saddening blank Serial Monitor no matter how much I wait for something to display (and I am pretty sure I should not have to wait). Since your code works for you, I am absolutely at a loss right now.

Anyway, shouldn't it display something even if there was no input (like the equivalent of 0 frequency) ?

If I put a LED on the output of the D7 I can see that there is indeed an output (my LED is lit) but I get a quite similar result on the D12 pin, and tell me if I'm wrong but it seems like this is not normal if I understand the code well.

Hi @diamondemon

Strange that it's not working, it should just mean connecting D7 to D12 with a wire.

Do you have access to a digital multimeter (DMM), or even better an oscilloscope? If you change the TCC0 duty-cycle to 50%, or in other words change the CC3 register to 10000 (instead of 1500), your DMM should read 1.65V (or thereabouts) on the D7 output. This will at least prove that the PWM output is working.

The reason why you're not seeing any output, is because the TC4 timer interrupt is only triggered when it sees an incoming pulse. Otherwise it will just sit there waiting.

It's also possible to route the input signal to almost any other pin other than D12. If you have anonother pin free on your board, I can provide you with the code for that.

MartinL

@diamondemon Here's the complete code that I'm using:

volatile boolean periodComplete;
volatile uint32_t isrPeriod;
volatile uint32_t isrPulsewidth;
uint32_t period;
uint32_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(4);            // Set division on Generic Clock Generator (GCLK) 4

  GCLK->GENCTRL.reg = GCLK_GENCTRL_IDC |           // Set the duty cycle to 50/50 HIGH/LOW
                      GCLK_GENCTRL_GENEN |         // Enable GCLK 4
                      GCLK_GENCTRL_SRC_DFLL48M |   // Set the clock source to 48MHz
                      GCLK_GENCTRL_ID(4);          // Set clock source on GCLK 4
  while (GCLK->STATUS.bit.SYNCBUSY);               // Wait for synchronization
  
  GCLK->CLKCTRL.reg = GCLK_CLKCTRL_CLKEN |         // Route GCLK4 to TC4 and TC5
                      GCLK_CLKCTRL_GEN_GCLK4 |     
                      GCLK_CLKCTRL_ID_TC4_TC5;     

  GCLK->CLKCTRL.reg = GCLK_CLKCTRL_CLKEN |         // Route GCLK4 to TCC0 and TCC1
                      GCLK_CLKCTRL_GEN_GCLK4 |     
                      GCLK_CLKCTRL_ID_TCC0_TCC1;  
  while (GCLK->STATUS.bit.SYNCBUSY);              

  // Enable the port multiplexer on digital pin D7
  //PORT->Group[g_APinDescription[7].ulPort].PINCFG[g_APinDescription[7].ulPin].bit.PMUXEN = 1;
  PORT->Group[PORTA].PINCFG[21].bit.PMUXEN = 1;
  // Set-up the pin as an TCC0 PWM output on D7
  //PORT->Group[g_APinDescription[7].ulPort].PMUX[g_APinDescription[7].ulPin >> 1].reg |= PORT_PMUX_PMUXO_F;
  PORT->Group[PORTA].PMUX[21 >> 1].reg |= PORT_PMUX_PMUXO_F;

  TCC0->WAVE.reg =  TCC_WAVE_WAVEGEN_NPWM;          // Enable Normal PWM (NPWM) mode
  while (TCC0->SYNCBUSY.bit.WAVE);                  // Wait for synchronization
  
  TCC0->PER.reg = 19999;                            // Set the timer to output a 50Hz
  while (TCC0->SYNCBUSY.bit.PER);                   // Wait for synchronization

  TCC0->CC[3].reg = 1500;                            // Set CC3 duty-cycle to 1500us pusle width
  while (TCC0->SYNCBUSY.bit.CC3);                   // Wait for synchronization

  TCC0->CTRLA.reg = TCC_CTRLA_PRESCSYNC_PRESC |     // Reload timer on the next prescaler clock
                    TCC_CTRLA_PRESCALER_DIV16;      // Set prescaler to 16, 16MHz/16 = 1MHz
                    
  TCC0->CTRLA.bit.ENABLE = 1;                       // Enable timer TCC0
  while (TCC0->SYNCBUSY.bit.ENABLE);                // Wait for synchronization
  
  // Enable the port multiplexer on port pin PA09
  PORT->Group[PORTA].PINCFG[9].bit.PMUXEN = 1;
  // Set-up the pin as an EIC (interrupt) on port pin PA09
  PORT->Group[PORTA].PMUX[9 >> 1].reg |= PORT_PMUX_PMUXO_A;

  EIC->EVCTRL.reg |= EIC_EVCTRL_EXTINTEO9;                                 // Enable event output on external interrupt 9
  EIC->CONFIG[1].reg |= EIC_CONFIG_SENSE1_HIGH;                            // Set event detecting a HIGH level
  EIC->INTENCLR.reg = EIC_INTENCLR_EXTINT9;                                // Disable interrupts on external interrupt 9
  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_TC4_EVU);                // Set the event user (receiver) as timer TC4

  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_9) |    // Set event generator (sender) as external interrupt 9
                       EVSYS_CHANNEL_CHANNEL(0);                           // Attach the generator (sender) to channel 0

  TC4->COUNT32.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
                  
  TC4->COUNT32.CTRLC.reg = TC_CTRLC_CPTEN1 |               // Enable capture on CC1
                           TC_CTRLC_CPTEN0;                // Enable capture on CC0
  while (TC4->COUNT32.STATUS.bit.SYNCBUSY);                // Wait for synchronization

  NVIC_SetPriority(TC4_IRQn, 0);      // Set the Nested Vector Interrupt Controller (NVIC) priority for TC4 to 0 (highest)
  NVIC_EnableIRQ(TC4_IRQn);           // Connect the TC4 timer to the Nested Vector Interrupt Controller (NVIC)
 
  TC4->COUNT32.INTENSET.reg = TC_INTENSET_MC1 |            // Enable compare channel 1 (CC1) interrupts
                              TC_INTENSET_MC0;             // Enable compare channel 0 (CC0) interrupts
  
  TC4->COUNT32.CTRLA.reg = TC_CTRLA_PRESCSYNC_PRESC |      // Overflow on precaler clock, (rather than the GCLK)
                           TC_CTRLA_PRESCALER_DIV16 |      // Set prescaler to 16, 16MHz/16 = 1MHz
                           TC_CTRLA_MODE_COUNT32;          // Set TC4/TC5 to 32-bit timer mode
                          
  TC4->COUNT32.CTRLA.bit.ENABLE = 1;                       // Enable TC4
  while (TC4->COUNT32.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("PW: ");
    SerialUSB.print(pulsewidth);
    SerialUSB.print(F("   "));
    SerialUSB.print("P : ");
    SerialUSB.println(period);
    periodComplete = false;                       // Start a new period
  }
}

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

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

In fact, I have no pin used on the board for now, I am currently only focusing on using the TC counter for a tachometer.

Hi @MartinL

I managed to get my hands on a DMM and it seems like the D7 pin is working perfectly. However, I cannot explain the 1.08V value I find on the D12. It seems to me like an absolutely random value, which I do not understand.

Diamondemon

Have you removed the external pull-down resistor on D12?

A value of 1.08V would indicate that an external 2k2 pull-down is still connected to the input. This would create a voltage divider with the MKR1400 GSM's on-board I2C 4k7 pull-up reistor.

In fact I was testing all my pins while disconnected, thus there was nothing connected to D12.

@diamondemon Perhaps we could try a different pin as an input.

Do you have any other digital input pins available?

As I said earlier, all my pins are available.

I'm just going to modify and test some code that'll use another digital pin.

Thank you.
Moreover, I cannot explain why but it turns out that the other MKR GSM 1400 I have available has the same behaviour about not displaying anything (but Serial communication works for sure).

It's always nice to have two boards to compare. The fact that they're displaying same behaviour usually means that it's the code and not the hardware that's at fault. I'm just testing the code...

To update something, the 1.08V on pin D12 seems to have disappeared.

@diamondemon I've moved the input from D12 to D6. If you upload the code below, connect D7 to D6 then open the console, you should see the pulse width and period output.

By the way, I made a small mistake that I've now corrected in the code above. The GCLK divisor was set to 1 instead of 3.

GCLK->GENDIV.reg = GCLK_GENDIV_DIV(3) |          // Divide the 48MHz system clock by 3 = 16MHz

It doesn't make any difference to the result, other than the fact that both timers ran 3 times faster than I anticipated. Anyway, that's now been corrected.

I've tested the code, but please let me know if this works for you (or not)?

Here's the code:

// Setup TC4 to capture pulse-width and period on digital pin D6, (test signal on D7)
volatile boolean periodComplete;
volatile uint32_t isrPeriod;
volatile uint32_t isrPulsewidth;
uint32_t period;
uint32_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(4);            // Set division on Generic Clock Generator (GCLK) 4

  GCLK->GENCTRL.reg = GCLK_GENCTRL_IDC |           // Set the duty cycle to 50/50 HIGH/LOW
                      GCLK_GENCTRL_GENEN |         // Enable GCLK 4
                      GCLK_GENCTRL_SRC_DFLL48M |   // Set the clock source to 48MHz
                      GCLK_GENCTRL_ID(4);          // Set clock source on GCLK 4
  while (GCLK->STATUS.bit.SYNCBUSY);               // Wait for synchronization
  
  GCLK->CLKCTRL.reg = GCLK_CLKCTRL_CLKEN |         // Route GCLK4 to TC4 and TC5
                      GCLK_CLKCTRL_GEN_GCLK4 |     
                      GCLK_CLKCTRL_ID_TC4_TC5;     

  GCLK->CLKCTRL.reg = GCLK_CLKCTRL_CLKEN |         // Route GCLK4 to TCC0 and TCC1
                      GCLK_CLKCTRL_GEN_GCLK4 |     
                      GCLK_CLKCTRL_ID_TCC0_TCC1;  
  while (GCLK->STATUS.bit.SYNCBUSY);              

  // Enable the port multiplexer on digital pin D7
  //PORT->Group[g_APinDescription[7].ulPort].PINCFG[g_APinDescription[7].ulPin].bit.PMUXEN = 1;
  PORT->Group[PORTA].PINCFG[21].bit.PMUXEN = 1;
  // Set-up the pin as an TCC0 PWM output on D7
  //PORT->Group[g_APinDescription[7].ulPort].PMUX[g_APinDescription[7].ulPin >> 1].reg |= PORT_PMUX_PMUXO_F;
  PORT->Group[PORTA].PMUX[21 >> 1].reg |= PORT_PMUX_PMUXO_F;

  TCC0->WAVE.reg =  TCC_WAVE_WAVEGEN_NPWM;          // Enable Normal PWM (NPWM) mode
  while (TCC0->SYNCBUSY.bit.WAVE);                  // Wait for synchronization
  
  TCC0->PER.reg = 19999;                            // Set the timer to output a 50Hz
  while (TCC0->SYNCBUSY.bit.PER);                   // Wait for synchronization

  TCC0->CC[3].reg = 1500;                            // Set CC3 duty-cycle to 1500us pusle width
  while (TCC0->SYNCBUSY.bit.CC3);                   // Wait for synchronization

  TCC0->CTRLA.reg = TCC_CTRLA_PRESCSYNC_PRESC |     // Reload timer on the next prescaler clock
                    TCC_CTRLA_PRESCALER_DIV16;      // Set prescaler to 16, 16MHz/16 = 1MHz
                    
  TCC0->CTRLA.bit.ENABLE = 1;                       // Enable timer TCC0
  while (TCC0->SYNCBUSY.bit.ENABLE);                // Wait for synchronization
  
  // Enable the port multiplexer on port pin PA20
  PORT->Group[PORTA].PINCFG[20].bit.PMUXEN = 1;
  // Set-up the pin as an EIC (interrupt) on port pin PA20
  PORT->Group[PORTA].PMUX[20 >> 1].reg |= PORT_PMUX_PMUXE_A;

  EIC->EVCTRL.reg |= EIC_EVCTRL_EXTINTEO4;                                 // Enable event output on external interrupt 4
  EIC->CONFIG[0].reg |= EIC_CONFIG_SENSE4_HIGH;                            // Set event detecting a HIGH level
  EIC->INTENCLR.reg = EIC_INTENCLR_EXTINT4;                                // Disable interrupts on external interrupt 4
  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_TC4_EVU);                // Set the event user (receiver) as timer TC4

  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_4) |    // Set event generator (sender) as external interrupt 4
                       EVSYS_CHANNEL_CHANNEL(0);                           // Attach the generator (sender) to channel 0

  TC4->COUNT32.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
                  
  TC4->COUNT32.CTRLC.reg = TC_CTRLC_CPTEN1 |               // Enable capture on CC1
                           TC_CTRLC_CPTEN0;                // Enable capture on CC0
  while (TC4->COUNT32.STATUS.bit.SYNCBUSY);                // Wait for synchronization

  NVIC_SetPriority(TC4_IRQn, 0);      // Set the Nested Vector Interrupt Controller (NVIC) priority for TC4 to 0 (highest)
  NVIC_EnableIRQ(TC4_IRQn);           // Connect the TC4 timer to the Nested Vector Interrupt Controller (NVIC)
 
  TC4->COUNT32.INTENSET.reg = TC_INTENSET_MC1 |            // Enable compare channel 1 (CC1) interrupts
                              TC_INTENSET_MC0;             // Enable compare channel 0 (CC0) interrupts
  
  TC4->COUNT32.CTRLA.reg = TC_CTRLA_PRESCSYNC_PRESC |      // Overflow on precaler clock, (rather than the GCLK)
                           TC_CTRLA_PRESCALER_DIV16 |      // Set prescaler to 16, 16MHz/16 = 1MHz
                           TC_CTRLA_MODE_COUNT32;          // Set TC4/TC5 to 32-bit timer mode
                          
  TC4->COUNT32.CTRLA.bit.ENABLE = 1;                       // Enable TC4
  while (TC4->COUNT32.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("PW: ");
    SerialUSB.print(pulsewidth);
    SerialUSB.print(F("   "));
    SerialUSB.print("P: ");
    SerialUSB.println(period);
    periodComplete = false;                       // Start a new period
  }
}

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

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

This works! Thank you for your help !

I now have some questions about your code:

β†’ What do you mean by switch on the evsys peripheral? What is the default peripheral? What happens if one doesn't do this?

β†’ What is the use of the multiplexer here? I know what a mux is, I just don't understand what it does here. (If you have the answer, of course).

β†’ What does that mean?

β†’ Same question, what does that mean to direct the overflow on the prescaler?

β†’ The channel compares what with what, exactly?

β†’ Why is there a need to make a request to read?