Using TC to count from GPIO

Oh, D0 permanently sees a 50Hz, 1/2 PW signal even if I remove the 50Hz output from D7.

Try running TCC0 at a frequency other than 50Hz. If your still seeing 50Hz on the output it could be picking up your AC mains supply, which usually operates at around 50-60Hz depending on the country.

If I refer to the datasheet correctly, I have to do this:

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

Right?

Yes, you're ahead of me.

That was a good idea, and it looked like it worked when I unplugged my AC (I'm on a laptop) but the signal keeps going, (un)plugging the button to either GND or VCC, or at least D0 do not even seem to bother it.

The behaviour I really strange. Sometimes it stops, sometimes it goes crazy, there is no way for me to explain this.

YES, this works! Thank you. (To be honest I am happy that I was able to find out by myself, I was starting to feel bad to rely so much on your knowledge)

At the end, I was struggling to keep up with the pace of the discussion. Glad to hear that you got it working.

Sorry for that, I did not manage to describe clearly what was happening on the D0, mainly because I did not understand it well enough to make it clear.

Now that this solution worked, I may have some questions:

  • Does this mean that D6 is pulled down by default?

  • Can I use D6 and D7 as both inputs for two different counters or is it impossible because they share the same MUX ?

  • How does the EIC_CONFIG_SENSEx_HIGH work ? If I look at the code in the libraries, x cannot go above 7. Does it mean I cannot use EXTINTx with x above 7 with the EIC ?

I would imagine that D6 would be floating by default, unless the Arduino core code has initialised it to the contrary. It's possible to check by outputting the PA20's PINCFG register to the console:

SerialUSB.println(PORT->Group[PORTA].PINCFG[20].reg, HEX);

or if you prefer binary:

SerialUSB.println(PORT->Group[PORTA].PINCFG[20].reg, BIN);

It's possible to use both D6 and D7 simultaneously:

  PORT->Group[PORTA].PINCFG[20].bit.PMUXEN = 1;
  PORT->Group[PORTA].PINCFG[21].bit.PMUXEN = 1;
  PORT->Group[PORTA].PMUX[20 >> 1].reg = PORT_PMUX_PMUXO_A | PORT_PMUX_PMUXE_A;

To go above interrupt channel 7, it's necessary to use the CONFIG[1] register. For example to use EIC interrupt channel 8:

EIC->CONFIG[1].reg |= EIC_CONFIG_SENSE0_HIGH;                            // Set event detecting a HIGH level

channel 9:

EIC->CONFIG[1].reg |= EIC_CONFIG_SENSE1_HIGH;                            // Set event detecting a HIGH level

...and so on...

The displayed value is 11. I suppose these are the LSBs, which means only INEN and PMUXEN are enabled (and I did enable PMUXEN myself).

That's what I tried myself, actually. And it worked.

I did not notice that EIC->CONFIG is a table, thanks for your explanation.

I am currently working on the DMAC solution in case it would be needed.

Your code with the interrupt worked well.

Then I commented out the lines as you suggested, and changed the loop like this so it displays the registered period every second or so:

int memo, memo2; // Variables for time counting in loop

// ... setup and all

void loop()
{
  memo = millis();  
  if (memo-memo2>1000){
    SerialUSB.print(F(" P: "));
    SerialUSB.println(dmacPeriod);
    // periodComplete = false;
    memo2=memo;
  }
}

and now I only get weird P: 76 results, no matter how long I wait before triggering the button again.

Do you have any idea why?

The DMAC is triggered by the TC4 timer.

If there's no waveform on the input, the TC4 timer continues counting (attempting to measure the period), but the DMAC isn't triggered. The dmacPeriod variable will simply hold the last triggered value. (Which in your code will be printed out every second).

To address this issue, simply reset dmacPeriod to zero after displaying it:

 SerialUSB.println(dmacPeriod);
 dmacPeriod = 0;

After having a chat with the person I work with, it turned out that this solution is still too bothersome because the actual trigger of the interruption causes a burst, which because of interruptions leads to all other systems' watchdogs to die.
However I learnt enough to continue to search on my own.

I searched in the datasheet and found out that I can set the EVACT0 of TCC0 to increment the counter found in TCC->COUNT.reg on every event.

After setting things up, I still cannot find anything but the value 0 if I take the value of the counter. What am I missing?

Here is my code for the setup of the counter:

void setupGCLK() {
  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 TCC0 and TCC1
                      GCLK_CLKCTRL_GEN_GCLK4 |     
                      GCLK_CLKCTRL_ID_TCC0_TCC1;  
  while (GCLK->STATUS.bit.SYNCBUSY);  
}

void setupTCC0() {
  // 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;
  PORT->Group[PORTA].PINCFG[21].bit.PULLEN = 1;
  PORT->Group[PORTA].PINCFG[21].bit.INEN = 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_A;

  EIC->EVCTRL.reg |= EIC_EVCTRL_EXTINTEO5;                                 // Enable event output on external interrupt 5
  EIC->CONFIG[0].reg |= EIC_CONFIG_SENSE5_HIGH;                            // Set event detecting a HIGH level
  EIC->INTENCLR.reg = EIC_INTENCLR_EXTINT5;                                // Disable interrupts on external interrupt 5
  EIC->CTRL.reg |= EIC_CTRL_ENABLE;                                        // Enable EIC peripheral
  while (EIC->STATUS.bit.SYNCBUSY);                                        // Wait for synchronization
  Serial.println("EIC Set up OK");
  EVSYS->USER.reg = EVSYS_USER_CHANNEL(1) |                                // Attach the event user (receiver) to channel 0 (n + 1)
                    EVSYS_USER_USER(EVSYS_ID_USER_TCC0_EV_0);                // Set the event user (receiver) as TCC0 Event 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_5) |    // Set event generator (sender) as external interrupt 5
                       EVSYS_CHANNEL_CHANNEL(0);                           // Attach the generator (sender) to channel 0

  TCC0->EVCTRL.reg = TCC_EVCTRL_TCEI0 |               // Enable the TCC event 0 input
                     //TC_EVCTRL_TCINV |            // Invert the event input
                     TCC_EVCTRL_EVACT0_INC;           // Set up the counting of Events

  Serial.println("TCC0 EV Set up OK");
  
  
  TCC0->CTRLA.reg = TCC_CTRLA_PRESCSYNC_PRESC |     // Reload timer on the next prescaler clock
                    TCC_CTRLA_PRESCALER_DIV1 ;     // Set prescaler to 1
                    
  Serial.println("TCC0 CTRLA Set up OK");
                    
  TCC0->CTRLA.bit.ENABLE = 1;                       // Enable TCC0
  Serial.println("TCC0 CTRLA Enable OK");
  while (TCC0->SYNCBUSY.bit.ENABLE);                // Wait for synchronization
}

And the main arduino file goes like this:

int memo, memo2; // Variables for time counting in loop

void setup()   {                
  SerialUSB.begin(115200);                         // Initialise the native serial port
  while(!SerialUSB);                               // Wait for the console to open
  
  setupGCLK();
  SerialUSB.println("GCLK Set up OK");
  setupTCC0();
}


void loop() {
  // put your main code here, to run repeatedly:
  
  memo=millis();
  if (memo-memo2>1000){
    memo2=memo;
    SerialUSB.print("Counters: ");
    
    SerialUSB.print(TCC0->COUNT.reg);
    SerialUSB.print(" ");
    SerialUSB.println(TCC0->CC[0].reg);
  }
}

I tried the solution @MartinL gave in that old topic, but it does not work either:

I'm not sure I understand. You have some code that's now working, measuring an incoming waveform's period, but now a trigger of an interruption cause a burst, which leads to all other systems' watchdogs to die???

This code is doing something different, it's counting the number of input pulses, not measuring their period.

Ok, but might I ask what doesn't work?

Doesn't compile? Doesn't work the way you expect? Please elaborate.

Please see below:

@diamondemon Ok, the reason why you're reading back 0, is because the TCC0's COUNT and CCx registers are read synchronized.

Reading the COUNT is quite convoluted. First it's necessary to generate a read request in the TCC0's CTRLB register, which also requires write synchronization. Then perform the read synchronization, followed by the read of the COUNT register itself:

  TCC0->CTRLBSET.REG = TCC_CTRLBCLR_CMD_READSYNC; // Trigger a read synchronization on the COUNT register
  while (TCC0->SYNCBUSY.bit.CTRLB);               // Wait for the CTRLB register write synchronization
  while (TCC0->SYNCBUSY.bit.COUNT);               // Wait for the COUNT register read sychronization
  SerialUSB.println(TCC0->COUNT.reg, HEX);        // Print the result

For the TCC0 CCx registers, should only be necessary to test the corresponding SYNCYBUSY bit:

  while (TCC0->SYNCBUSY.bit.CC0);               // Wait for the CC0 register read sychronization
  SerialUSB.println(TCC0->CC[0].reg, HEX);      // Print the result

Thank you @MartinL, I knew I was missing something (and that was the SYNCBUSY mainly, because I tried with the TCC_CTRLBSET_CMD_READSYNC too but it failed since I did not wait for sync).

Do you know why the increment of the counter is somewhere between 3 and 5 ?

This is my output:

Counters: 20 0
Counters: 24 0
Counters: 28 0
Counters: 28 0
Counters: 33 0
Counters: 33 0
Counters: 36 0
Counters: 36 0
Counters: 39 0

After trying with another button, it looks like my first button is the problem.

Could you tell me how to reset the counter, I tried this but it does not work:

if (memo-memo3>10000){
    memo3=memo;
    Serial.println("Resetting");
    TCC0->CTRLBCLR.reg |= TCC_CTRLBCLR_CMD_RETRIGGER;
    while(TCC0->SYNCBUSY.bit.CTRLB);
    while(TCC0->SYNCBUSY.bit.COUNT);
    TCC1->CTRLBSET.reg |= TCC_CTRLBSET_CMD_RETRIGGER;
    while(TCC1->SYNCBUSY.bit.CTRLB);
    while(TCC1->SYNCBUSY.bit.COUNT);
  }

I already set up my second counter on TCC1, with the same code as for TCC0.

To reset the TCC0 timer, set the retrigger command during operation:

TCC0->CTRLBSET.reg = TCC_CTRLBSET_CMD_RETRIGGER;
while (TCC0->SYNCBUSY.bit.CTRLB);

By the way, there's no need to use a read-modify-write with the bitwise OR (|=) . Registers that use SET and CLR do that for you in hardware.

There's also no need to clear the retrigger command, just use the CTRLBSET register.