Arduino Forum

Products => Arduino Zero => Topic started by: electro_95 on Apr 27, 2016, 12:51 am

Title: Arduino Zero TCC Capture
Post by: electro_95 on Apr 27, 2016, 12:51 am
I've been reading over documentation and Atmel studio examples all day for SAMD processors trying to figure out how to setup the TCC module in capture mode.  So far the vast majority of examples that I have found involve operating the timers for generating waveform outputs.

Some of the helpful code examples taht I've found are:
https://github.com/manitou48/ZERO
and
https://github.com/maxbader/arduino_tools

Then there are docs which explain operating the module in PWP mode.  However they stop short of providing a readable example code.  Then there is this app note for D20 that doesn't compile in Atmel Studio:
http://www.atmel.com/images/atmel-42267-tc-capture-on-external-interrupt-with-the-event-system-on-sam-d20_applicationnote_at05567.pdf

So I'm just curious if there is anyone out there who has had success with the capture mode of SAMD?

What I really want to do is detect pulse width and period of a signal that is connected to one of my Arduino Zero pins.  The receive an interrupt each time that new pulses arrive.
Title: Re: Arduino Zero TCC Capture
Post by: electro_95 on Apr 28, 2016, 03:35 pm
Ok so here is an update as to where I am at.  first here is the code:

Code: [Select]

uint32_t last = 0;
uint32_t now = 0;
uint32_t width = 0;
uint32_t period = 0;
uint32_t irqs = 0;

// the setup function runs once when you press reset or power the board
void setup() {
  // initialize digital pin 13 as an output.
  pinMode(27, INPUT_PULLUP);
  pinMode(LED_BUILTIN, OUTPUT);
  Serial.begin(115200);
  while (!Serial);
  Serial.println("Hello!");
 
  // Configure the Clock
  GCLK->CLKCTRL.reg = (uint16_t) (GCLK_CLKCTRL_CLKEN | GCLK_CLKCTRL_GEN_GCLK0 | GCLK_CLKCTRL_ID( GCM_TC1_TC2)) ;
  while ( GCLK->STATUS.bit.SYNCBUSY == 1 ); // wait for sync

  // Diable the Timer
  REG_TC1_CTRLA &= ~TC_CTRLA_ENABLE;

  // Set prescaler and make timer a 32bit counter
  REG_TC1_CTRLA |= TC_CTRLA_PRESCALER_DIV1 | TC_CTRLA_MODE(TC_CTRLA_MODE_COUNT32_Val);   // Set perscaler

  // Connect the MCE0 capture channel to the Event control
  REG_TC1_EVCTRL |= TC_EVCTRL_MCEO0 | TC_EVCTRL_TCEI | TC_EVCTRL_EVACT_PWP;

  // Setup Interrupts
  REG_TC1_INTENSET = 0;              // disable all interrupts
  REG_TC1_INTENSET |= TC_INTENSET_OVF | TC_INTENSET_MC0;  // Overflow and MCE0 capture
  NVIC_EnableIRQ(TC1_IRQn);
 
  // Enable TC
  REG_TC1_CTRLA |= TC_CTRLA_ENABLE ;
  Serial.println("TC Enabled!");

  attachInterrupt(27,pin_irq,CHANGE);
  EIC->EVCTRL.bit.EXTINTEO7 = 1;
  PORT->Group[0].PMUX[27/2].bit.PMUXO = PINMUX_PA27A_EIC_EXTINT7 & 0xFFFF;

  PM->APBCMASK.reg |= PM_APBCMASK_EVSYS;
  GCLK->CLKCTRL.reg = (uint16_t) (GCLK_CLKCTRL_CLKEN | GCLK_CLKCTRL_GEN_GCLK0 | GCLK_CLKCTRL_ID_EVSYS_0);
  while (GCLK->STATUS.bit.SYNCBUSY);

  EVSYS->CTRL.reg = EVSYS_CTRL_SWRST;
  EVSYS->CHANNEL.reg = EVSYS_CHANNEL_PATH_ASYNCHRONOUS | EVSYS_CHANNEL_EVGEN(EVSYS_ID_GEN_EIC_EXTINT_7) | EVSYS_CHANNEL_CHANNEL(0); // Maybe CH+1 but why?
  EVSYS->USER.reg = (uint16_t) (EVSYS_USER_USER(EVSYS_ID_USER_TC1_EVU) | EVSYS_USER_CHANNEL(0));
  EVSYS->INTENSET.bit.EVD0 = 1;
 
}

// the loop function runs over and over again forever
void loop() {
  now = REG_TC1_COUNT32_COUNT;
  width = REG_TC1_COUNT32_CC0;
  period = REG_TC1_COUNT32_CC1;
  Serial.print("Count: ");
  Serial.println(now);
  Serial.print("Delta: ");
  Serial.println(now-last);
  Serial.print("Width: ");
  Serial.println(width);
  Serial.print("Period: ");
  Serial.println(period);
  Serial.print("IRQs: ");
  Serial.println(irqs);
  irqs = 0;
  last = now;
  digitalWrite(LED_BUILTIN, HIGH);   // turn the LED on (HIGH is the voltage level)
  delay(1000);
  //Serial.println("low");// wait for a second
  digitalWrite(LED_BUILTIN, LOW);    // turn the LED off by making the voltage LOW
  delay(1000);              // wait for a second
}

void pin_irq() {
  Serial.println("pin_irq");
}

void TC1_Handler()
{
  irqs++;
  //Serial.println(irqs);
  //REG_TC1_INTFLAG = 0x00;
  //Serial.println(REG_TC1_INTFLAG);
  TcCount32* TC = (TcCount32*) TC1;       // get timer struct
  if (TC->INTFLAG.bit.OVF == 1) {  // A overflow caused the interrupt
    //digitalWrite(pin_ovf_led, irq_ovf_count % 2); // for debug leds
    //digitalWrite(pin_mc0_led, HIGH); // for debug leds
    Serial.println("OVF!");
    TC->INTFLAG.bit.OVF = 1;    // writing a one clears the flag ovf flag
    //irq_ovf_count++;                 // for debug leds
  }
 
  if (TC->INTFLAG.bit.MC0 == 1) {  // A compare to cc0 caused the interrupt
    //digitalWrite(pin_mc0_led, LOW);  // for debug leds
    Serial.println("MC0!");
    TC->INTFLAG.bit.MC0 = 1;    // writing a one clears the flag ovf flag
  }
}

void EVSYS_Handler() {
  Serial.println("EVSYS_IRQ");
  Serial.println( EVSYS->INTENSET.reg);
  EVSYS->INTFLAG.bit.EVD0 = 1;
}


So what is working:
The Timer is running in 32bit mode
The Timer overflow interrupt does work
The Pin interrupt does work

What I'm not sure about:
Is pin PA27 properly routed to the Event system?
 - Port PUX properly configured?  It should be since this is done via "pinMode(" and "attachInterrupt("
 - Eventsys properly configured?

Title: Re: Arduino Zero TCC Capture
Post by: MartinL on Apr 28, 2016, 05:06 pm
Hi electro_95,

I'm also trying to get this to work, but there appears to be a mismatch between the "Period and Pulse-Width Capture Action" mode and how the events system works.

I understand that the "Period and Pulse-Width Capture Action" captures the waveform's pulse-width and period in the CC0 and CC1 registers (or vice-versa), but the problem is that the TC timer counters on the SAMD21 have no inputs and they're only able to capture input events from an event channel.

That leaves the question of how to get the input waveform to the TC timer counter? As the PORTs don't have any event functionality, that just leaves the EIC (External Interrupt Controller).

The EIC allows to you activate events, that as far as I can gather (as the datasheet doesn't explain) corresponds to the EIC interrupt flag. I believe the EIC will send out event pulses on one of the specified event channels, as the given interrupt flag is set and cleared.

So the EIC is generating a short positive pulses on the event channel for each transition of the input waveform, however the "Period and Pulse-Width Capture Action" mode by default is using the rising pulses to capture the period and the time to the following trailing pulse to capture the pulsewidth. As far as I can see the two just don't match up.

It almost appears as if the "Period and Pulse-Width Capture Action" mode was designed for a timer counter input pin, rather than an event channel.

Anyone got any ideas?
Title: Re: Arduino Zero TCC Capture
Post by: MartinL on Apr 29, 2016, 12:25 am
I'm beginning to think that the event channel just toggles with each event, as I consider is probably only way interrupt events could work in conjunction with the timer.

Title: Re: Arduino Zero TCC Capture
Post by: MartinL on Apr 29, 2016, 01:04 am
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
  }
}
Title: Re: Arduino Zero TCC Capture
Post by: Rucus on Jul 17, 2016, 09:54 am
Hi,

I was trying to port MartinL's (big thanks to you, otherwise it will take me a lot of time to figure out these things basing from the SAMD21 datasheet alone!) code to the Adafruit Feather M0 board. Everything compiles except that when I try to run the code, it gets stuck at this line:

Code: [Select]
attachInterrupt(12, NULL, HIGH);

I think this has something to do with clock synchronization stuff, but I have no idea what happens internally when I call the attachInterrupt function.

Any help to solve this issue is much appreciated.

Thanks a lot!

-Ruch

Title: Re: Arduino Zero TCC Capture
Post by: MartinL on Jul 17, 2016, 07:58 pm
Hi Rucus,

I've checked the pinout diagram of the Adafruit Feather M0 and it's the same as the Zero, so there shouldn't be any difference. The sketch should wait for pulses on digital pin 12 on either board.

Are you sure that it isn't getting stuck at the line?:

Code: [Select]
while(!SerialUSB);                        // Wait for the SerialUSB port to be ready
This will wait until the console is opened before proceeding with the rest of the sketch. You can just comment out this line, if you prefer to execute the sketch without waiting.
Title: Re: Arduino Zero TCC Capture
Post by: Rucus on Jul 17, 2016, 10:14 pm
Hi MartinL,

Thanks for your quick response.

I am not sure though if they have the same "pinouts", if indeed we are referring to the same thing. For example in Zero, pin 12 is referring to the MISO pin, while pin 12 of the Adafruit M0 adalogger is referring to a generic IO and the MISO is actually on pin 22.

I am not using the SerialUSB for printing texts. I have pasted the code below, which is basically your code that I changed a little bit to use the Serial object of printing texts over the terminal. :)

Code: [Select]
void setup()   {               
  Serial.begin(115200);
 
  delay(7000);  //have ample time to catch text in serial monitor
 
  Serial.println("My Project");
 
  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
  Serial.println("Done with TC init");
 
}

void loop() {
  static uint8_t lineSpacePosNow = 0, lineSpacePosIdx = 9;
  // read data from the GPS in the 'main loop'
 
  if (periodComplete)                             // Check if the period is complete
  {
    noInterrupts();                               // Read the new period and pulse-width
    period = isrPeriod;                   
    pulsewidth = isrPulsewidth;
    interrupts();
    Serial.print("P ");
    Serial.println(period, 2);
    Serial.print("PW ");
    Serial.println(pulsewidth, 2);
    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)             
  {
//    Serial.println("MO Intrpt");
    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)           
  {
//    Serial.println("M1 Intrpt");
    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
  }
}


The word "Done with TC init" is printed on my terminal if I comment the attachInterrupt(12, NULL, HIGH); line. I checked the function declaration at C:\Users\Ruchie\AppData\Local\Arduino15\packages\adafruit\hardware\samd\1.0.11\cores\arduino and it looks like it implements waiting for SYNCBUSY bit to get cleared. So I kind of stuck now as to what causing the code to stick somewhere and where it is happening (I hope I have a debugger to dig in further).

Thanks!

-Ruch
Title: Re: Arduino Zero TCC Capture
Post by: Rucus on Jul 17, 2016, 10:24 pm
Hi MartinL,

Here is by the way the exact Adafruit M0 part that I am using.

https://learn.adafruit.com/adafruit-feather-m0-adalogger/pinouts (https://learn.adafruit.com/adafruit-feather-m0-adalogger/pinouts)

Thanks,
Ruch
Title: Re: Arduino Zero TCC Capture
Post by: MartinL on Jul 17, 2016, 11:09 pm
Hi Ruch,

The pinouts for the Adafruit Feather M0 Adalogger and the Arduino Zero are the same for digital pin 12. Both use PA19, which is physical pin 28 on the device itself.

I ran your code with an RC receiver outputting CPPM (Pulse Position Modulation) and get the following result giving you the pulse width and period in binary:

P 10111010101
PW 1011111111
P 10111001111
PW 11001100111111
P 10111010000
PW 10010100100
P 11000011110
PW 10010101010
P 10000101001
PW 10010011101
P 11010001101001
PW 10010100100

So that works OK.

If however I run it without the RC receiver attached, I get only:

My Project
Done with TC init

What pulse width and period waveform you using? The reason I ask is that if you're using a different signal you might need to reconfigure the TC3 timer.

Title: Re: Arduino Zero TCC Capture
Post by: Rucus on Jul 18, 2016, 12:25 am
Hi MartinL,

You are right. They indeed use the same physical pin. I did not realize that as I was only looking at their alternate pin functions. My bad.

I am using a magnetometer sensor that outputs a signal (pulses) that varies the period as a function of magnetic field strength. The frequency is from 50kHz to 150kHz within the measuring range. So the output from sensor is continuous pulses that I need to measure the period to get the field strength.

Thanks,
Ruch
Title: Re: Arduino Zero TCC Capture
Post by: Rucus on Jul 18, 2016, 02:24 am
At maximum frequency, I think that the ISR for TC3 will be called once in every ~6.67 us. Do you think it's too fast that I may need to do some configurations to ensure the micro can pickup the pulses? I am also running other modules in my system like the OLED and GPS, which I will deal with later once I have the capture mode up and running.
Title: Re: Arduino Zero TCC Capture
Post by: MartinL on Jul 18, 2016, 10:05 am
The 50kHz to 150kHz signal is at a high frequency with resepect to the SAMD21's 48MHz clock. The speed of TC3 really depends on the resolution (frequency steps) of your magnetometer, but will most likely require the TC3 timer to run at its maximum speed of 48MHz.

To set up TC3 to run at 48MHz, just change the generic clock divider from 3, down to 1:

Code: [Select]
REG_GCLK_GENDIV = 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

...and the TC3 timer prescaler from divide by 16, to also divide by 1:

Code: [Select]
REG_TC3_CTRLA |= TC_CTRLA_PRESCALER_DIV1 |      // Set prescaler to 1, 48MHz/1 = 48MHz
                   TC_CTRLA_ENABLE;               // Enable TC3
  while (TC3->COUNT16.STATUS.bit.SYNCBUSY);       // Wait for synchronization

The period values output from the timer will be multiples of 1/48MHz = 20.83ns.
Title: Re: Arduino Zero TCC Capture
Post by: Rucus on Jul 19, 2016, 08:02 am
Hi MartinL,

Thanks! I think that the code is working all along, but the problem is that the frequency of the signal that I am feeding to the TC3 timer is too fast so by the time that I kick in the attachInterrupt() in the code, my guess is that it is already calling for the ISR that is null (non-existent, but is the compiler supposed to warn me about it?) since I have it called before I have actually set up the TC3 interrupt at NVIC.

I made a little modification of your code and managed to make it work in my setup.

Code: [Select]
void setup()   {                
  Serial.begin(115200);
  
  delay(7000);  //have ample time to catch text in serial monitor
  
  Serial.println("My Project");

  pinMode(DCDC_EN, OUTPUT);
  
  REG_PM_APBCMASK |= PM_APBCMASK_EVSYS;     // Switch on the event system peripheral
  
  REG_GCLK_GENDIV = GCLK_GENDIV_DIV(1) |    // 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
  digitalWrite(DCDC_EN, 0);
  delay(3000);
//  attachInterrupt(MAGNETO_IN1, 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_DIV1 |     // Set prescaler to 16, 16MHz/16 = 1MHz
                   TC_CTRLA_ENABLE;               // Enable TC3
  while (TC3->COUNT16.STATUS.bit.SYNCBUSY);       // Wait for synchronization

  Serial.println("Done with TC init");
  delay(1000);                        //wait for some delay before turning on power to magnetometers
    
  timer = millis();
  digitalWrite(DCDC_EN, 1);          
  delay(1000);                        //wait for some delay for power to stabilize
  attachInterrupt(MAGNETO_OUT1, NULL, HIGH);                       //enable interrupt
}

void loop() {
  static uint32_t sampleCntr=0;
  static uint8_t lineSpacePosNow = 0, lineSpacePosIdx = 9;
  // read data from the GPS in the 'main loop'

  // if millis() or timer wraps around, we'll just reset it
  if (timer > millis())  timer = millis();
  
  // approximately every 2 seconds or so, print out the current stats
  if (millis() - timer > 1000) {
    timer = millis(); // reset the timer
//    sampleCntr++;
//    Serial.print("t ");
//    Serial.println(sampleCntr, DEC);
    if (periodComplete)                             // Check if the period is complete
    {
      period = isrPeriod;                  
      pulsewidth = isrPulsewidth;
      Serial.print("P ");
      Serial.println(period, DEC);
      Serial.print("PW ");
      Serial.println(pulsewidth, DEC);
      periodComplete = false;                       // Start a new period
    }
    attachInterrupt(MAGNETO_OUT1, NULL, HIGH);     //re-arm interrupt
  }
}

void TC3_Handler()                                // Interrupt Service Routine (ISR) for timer TC3
{    
  detachInterrupt(MAGNETO_OUT1);
  // 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
//    Serial.println("MO");
  }

  // 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
//    Serial.println("M1");
  }
}


I can see now the P and PW values being generated. I have also changed the TC3 clock settings to the one you suggested above so it now runs at max clock speed. Thank you for that wonderful piece advice!

I am, however, encountering some "weirdness" in my the behavior of my code. When the input frequency from the magnetometer is less than 55kHz, everything works fine (P and PW values get dumped on the terminal). If the frequency is greater than 55kHz, the code is kind of stuck somewhere. As I try to lower down the frequency again, the P and PW gets dumped on the terminal. I tried changing the length of characters to print but seem to not have any effect at all. Could it be something related to millis() function? I am thinking of using another Timer module to interrupt every 1000ms, but I would like to understand first what could have been the cause of the program freezing when input frequency is >55kHz.

Thanks a lot!

-Ruch
Title: Re: Arduino Zero TCC Capture
Post by: AloyseTech on Jul 19, 2016, 08:38 am
If the frequency is to high, the ISR will be called over and over again without letting time to the rest of the code to be executed. For example : if the code in you ISR take 18µs (1/55k) to execute and you have a 55kHz frequency input, the ISR will be re-called right after it finish executing over and over.
Title: Re: Arduino Zero TCC Capture
Post by: MartinL on Jul 19, 2016, 11:01 am
Hi Ruch,

As AloyseTech points out, it appears as though you've reached the limit SAMD21's capabilities, as the overhead of rapidly calling the interrupt service routine (ISR) is becoming too great for the processor to manage.

You also mention that you're planning to use an OLED display and GPS in your system. The GPS will also put a high demand on the processor, especially if you're parsing NMEA strings.

Is it necessary to use this magnetometer? Could you use one that uses an I2C or SPI bus for communication? This would significantly lighten the CPU load.
Title: Re: Arduino Zero TCC Capture
Post by: Rucus on Jul 20, 2016, 04:46 am
Thanks, AloyseTech and MartinL.

I am actually trying to disable the interrupt by issuing a detachInterrupt() right after the program enters the TC3 ISR, so the program only tries to read the output from magnetometer once every second one period (cycle) at a time in my hope to not get repetitive ISR events. I think it is worth checking though the detachInterrupt() function if there is any significant delay added to it before the actual write to the register to disable interrupt. I think what I am trying to do makes things more complicated.

What if instead of measuring the period of the signal, I actually implement a counter to count the number of pulses coming from the sensor and do a timer interrupt (using a separate timer module in SAMD) to read the count from another timer module? Basically I implement a free-running counter that I read at constant intervals in time and derive a transfer function to kind of convert those counts into equivalent intensity? I may need to reset the counters (I need 3 of them because I need to read data from 3 magnetometers) at every sampling time. So I think in this scheme I will be needing at least 4 timer modules from the SAMD. This solution kind of integrate the output from the sensor and I may lose some precision and speed there, but I think that will not matter much from what I am trying to do.

MartinL's question is a very good point. I wish I could use one that can talk via serial protocol. The thing is that I am tied to using these magnetometers because I need the extra sensitivity and directionality offered by these fluxgate magnetometer. A MEMS type is something I like to look into in the near future.
Title: Re: Arduino Zero TCC Capture
Post by: MartinL on Jul 20, 2016, 10:18 am
Depending on how often you need to read the magnetometer, the simplest way might be to set up your code so that it periodically activates the TC3 handler, takes a reading and then gets the handler to automatically switch itself off. That way this function won't be consuming all of the processor's time. I'm not sure if the SAMD21 can physically measure the 150kHz signal using TC capture, but if you keep your code in the handler to a minimum it might be worth a try?

It's possible to switch off interrupts from within the TC3 handler function using the timer's interrupt enable clear register (INTENCLR):

Code: [Select]
REG_TC3_INTENCLR = TC_INTENCLR_MC0;   // Disable period interrupts
To turn the handler function back on use the timer's interrupt enable set register (INTENSET):

Code: [Select]
REG_TC3_INTENSET = TC_INTENSET_MC0;  // Enable period interrpts
Title: Re: Arduino Zero TCC Capture
Post by: Rucus on Jul 21, 2016, 01:17 am
Thanks, MartinL.

I inserted the statements above to my code and I am getting an error somehow. Looks like the TC_INTENCLR_M0 and TC_INTENSET_M0 defines do not exist. I changed them to MC0 and MC1 so the statements looks like this

Code: [Select]
REG_TC3_INTENCLR = TC_INTENCLR_MC1 | TC_INTENCLR_MC0;

and

Code: [Select]
REG_TC3_INTENSET = TC_INTENSET_MC1 | TC_INTENSET_MC0;

You are right, still not fast enough to cope up with the input pulses. I want to start experimenting with timer counters and see if I get any luck on it.
Title: Re: Arduino Zero TCC Capture
Post by: MartinL on Jul 21, 2016, 09:22 am
Hi Ruch,

Thanks for the correction, I've amended it.

I ran a test with the TC capture code pared-down, measuring period only and managed to get it to work up to a maximum of 80kHz.
Title: Re: Arduino Zero TCC Capture
Post by: Rucus on Jul 23, 2016, 09:33 pm
Thanks, MartinL!

I am wondering if you managed in the past to make the Counter work by counting the pulses from an input pin?

-Ruch
Title: Re: Arduino Zero TCC Capture
Post by: MartinL on Jul 23, 2016, 10:40 pm
Hi Ruch,

Quote
I am wondering if you managed in the past to make the Counter work by counting the pulses from an input pin?
Do you mean just counting the pulses, rather than timing them?
Title: Re: Arduino Zero TCC Capture
Post by: Rucus on Jul 24, 2016, 02:24 am
Hi MartinL,

Yes, counting the pulses via the Timer Counter module, and reading the COUNT value at fixed intervals in time (maybe every 1ms or so).

Thanks,
Ruch
Title: Re: Arduino Zero TCC Capture
Post by: MartinL on Jul 24, 2016, 05:57 pm
Hi Ruch,

The example code below uses the Zero's TC4 timer in 8-bit mode, that generates an interrupt every 1ms:

Code: [Select]
void setup() {
   // Set up the generic clock (GCLK4) used to clock timers
  REG_GCLK_GENDIV = GCLK_GENDIV_DIV(3) |          // Divide the 48MHz clock source by divisor 3: 48MHz/3=16MHz
                    GCLK_GENDIV_ID(4);            // Select Generic Clock (GCLK) 4
  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 GCLK4
                     GCLK_GENCTRL_SRC_DFLL48M |   // Set the 48MHz clock source
                     GCLK_GENCTRL_ID(4);          // Select GCLK4
  while (GCLK->STATUS.bit.SYNCBUSY);              // Wait for synchronization

  // Feed GCLK4 to TC4 and TC5
  REG_GCLK_CLKCTRL = GCLK_CLKCTRL_CLKEN |         // Enable GCLK4 to TC4 and TC5
                     GCLK_CLKCTRL_GEN_GCLK4 |     // Select GCLK4
                     GCLK_CLKCTRL_ID_TC4_TC5;     // Feed the GCLK4 to TC4 and TC5
  while (GCLK->STATUS.bit.SYNCBUSY);              // Wait for synchronization
 
  REG_TC4_CTRLA |= TC_CTRLA_MODE_COUNT8;           // Set the counter to 8-bit mode
  while (TC4->COUNT8.STATUS.bit.SYNCBUSY);        // Wait for synchronization

  REG_TC4_COUNT8_CC0 = 0x55;                      // Set the TC4 CC0 register to some arbitary value
  while (TC4->COUNT8.STATUS.bit.SYNCBUSY);        // Wait for synchronization
  REG_TC4_COUNT8_CC1 = 0xAA;                      // Set the TC4 CC1 register to some arbitary value
  while (TC4->COUNT8.STATUS.bit.SYNCBUSY);        // Wait for synchronization
  REG_TC4_COUNT8_PER = 0xFF;                      // Set the PER (period) register to its maximum value
  while (TC4->COUNT8.STATUS.bit.SYNCBUSY);        // Wait for synchronization

  //NVIC_DisableIRQ(TC4_IRQn);
  //NVIC_ClearPendingIRQ(TC4_IRQn);
  NVIC_SetPriority(TC4_IRQn, 0);    // Set the Nested Vector Interrupt Controller (NVIC) priority for TC4 to 0 (highest)
  NVIC_EnableIRQ(TC4_IRQn);         // Connect TC4 to Nested Vector Interrupt Controller (NVIC)

  REG_TC4_INTFLAG |= TC_INTFLAG_MC1 | TC_INTFLAG_MC0 | TC_INTFLAG_OVF;        // Clear the interrupt flags
  REG_TC4_INTENSET = TC_INTENSET_MC1 | TC_INTENSET_MC0 | TC_INTENSET_OVF;     // Enable TC4 interrupts
  // REG_TC4_INTENCLR = TC_INTENCLR_MC1 | TC_INTENCLR_MC0 | TC_INTENCLR_OVF;     // Disable TC4 interrupts
 
  REG_TC4_CTRLA |= TC_CTRLA_PRESCALER_DIV64 |     // Set prescaler to 64, 16MHz/64 = 256kHz
                   TC_CTRLA_ENABLE;               // Enable TC4
  while (TC4->COUNT8.STATUS.bit.SYNCBUSY);        // Wait for synchronization
}

void loop() {
  // put your main code here, to run repeatedly:

}

void TC4_Handler()                              // Interrupt Service Routine (ISR) for timer TC4
{    
  // Check for overflow (OVF) interrupt
  if (TC4->COUNT8.INTFLAG.bit.OVF && TC4->COUNT8.INTENSET.bit.OVF)            
  {
    // Put your timer overflow (OVF) code here:    
    // ...
  
    REG_TC4_INTFLAG = TC_INTFLAG_OVF;         // Clear the OVF interrupt flag
  }

  // Check for match counter 0 (MC0) interrupt
  if (TC4->COUNT8.INTFLAG.bit.MC0 && TC4->COUNT8.INTENSET.bit.MC0)            
  {
    // Put your counter compare 0 (CC0) code here:
    // ...
  
    REG_TC4_INTFLAG = TC_INTFLAG_MC0;         // Clear the MC0 interrupt flag
  }

  // Check for match counter 1 (MC1) interrupt
  if (TC4->COUNT8.INTFLAG.bit.MC1 && TC4->COUNT8.INTENSET.bit.MC1)          
  {
    // Put your counter compare 1 (CC1) code here:
    // ...
  
    REG_TC4_INTFLAG = TC_INTFLAG_MC1;        // Clear the MC1 interrupt flag
  }
}
Title: Re: Arduino Zero TCC Capture
Post by: Rucus on Jul 24, 2016, 10:08 pm
Thanks, MartinL!

I was trying to configure the GCLK module to use it as a clock source for TC3, but instead of using the DFLL48M generator, I'd like to hook it up to the GCLK_IO[6] so I can feed the output (pulses) of my sensor to that pin (PA22). The intent is to use the output pulses from my sensor as the new clock source for TC3 where it will count up for every clock cycle. The period of clock varies as a function of entity being sensed. The COUNT register is read and reset at fixed intervals of time, so I get various COUNT values depending on the intensity of entity being sensed. Basically, at higher intensities, the sensor will output more pulses (the period for each pulse cycle will be shorter), thus, clocking the TC3 module at higher speeds (I hope). Here is what the code looks like inside the setup() function:

Code: [Select]

  PORT->Group[0].WRCONFIG.reg = PORT_WRCONFIG_HWSEL |
 PORT_WRCONFIG_WRPMUX | PORT_WRCONFIG_PMUXEN | PORT_WRCONFIG_PMUX( 6 ) | //Route input to pin PA22 via PMUX. Pin 22 is Peripheral line #6 of the upper 16 of the Port Group
 PORT_WRCONFIG_INEN |                    //Enable input for the pin selected in PINMASK, WRPINCFG and HWSEL combination
 PORT_WRCONFIG_WRPINCFG |
 PORT_WRCONFIG_PINMASK( 1u << 6 ) ; //shift left by 6 to mask other pins except PA22
 
  uint32_t temp;
  temp = (PORT->Group[0].PMUX[6].reg) & PORT_PMUX_PMUXO( 0xF ); //get old values so we can append them when we re-write to this 32-bit reg
  PORT->Group[0].PMUX[6].reg = temp | PORT_PMUX_PMUXE( PORT_PMUX_PMUXE_H_Val ); //enable peripheral function for PA22, which is an even number port. Write back new value to register. PA22 is line #6 of the upper 16 port group
  PORT->Group[0].PINCFG[22].reg |= PORT_PINCFG_PMUXEN;    //enable MUX for pin PA22
 
  Serial.println("Done PORT config");
 
  /* Set GCLK */
  REG_GCLK_GENDIV = GCLK_GENDIV_DIV(1) |    // Divide the 48MHz system clock by x
                    GCLK_GENDIV_ID(5);      // Set division on Generic Clock Generator (GCLK) 5
  while (GCLK->STATUS.bit.SYNCBUSY);        // Wait for synchronization
  Serial.println("Done GCLK GENDIV config");
 
  REG_GCLK_GENCTRL = GCLK_GENCTRL_IDC |           // Set the duty cycle to 50/50 HIGH/LOW
 GCLK_GENCTRL_GENEN |         // Enable GCLK 5
                     GCLK_GENCTRL_SRC_GCLKIN |    // Set the clock source to generator input pad, set to PA22 in PORT module
                     GCLK_GENCTRL_ID(5);          // Set clock source on GCLK 5
  while (GCLK->STATUS.bit.SYNCBUSY);              // Wait for synchronization*/
  Serial.println("Done GCLK GENCTRL config");
 
  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
  Serial.println("Done GCLK CLKCTRL config");
                   
  REG_TC3_READREQ = TC_READREQ_RREQ |             // Enable a read request
                    TC_READREQ_ADDR(0x10);        // Offset of the COUNT register
  while (TC3->COUNT16.STATUS.bit.SYNCBUSY);       // Wait for (read) synchronization
  Serial.println("Done TC READREQ config");


The code above gets stuck at this statement

Code: [Select]
while (TC3->COUNT16.STATUS.bit.SYNCBUSY);

There are 2 things I can think of why the code hangs:
1. The PORT is not properly configured, causing the SYNCBUSY bit to "wait" infinitely for the clock where the APB should sync to.
2. The TC3 simply doesn't want to accept unstable clock source. As mentioned above, the clock period changes as a function of intensity of entity being sensed, so basically it changes over time.

I used a stable clock generator in place of the sensor, however, it seems that the code still gets stuck on the line above. So most likely the problem is on my port configuration, or a bit of both. Any idea on why my code doesn't work?

P.S. The code seem to work when I change the clock source to DFLL48M from GCLKIN. The COUNT register is updated and I can manipulate the clocking speed by changing GCLK_GENDIV_DIV(1).

-Ruch
Title: Re: Arduino Zero TCC Capture
Post by: MartinL on Jul 25, 2016, 11:11 am
Hi Ruch,

Regarding the port pin set-up, you don't need to use both the WRCONFIG and PINCFG registers. WRCONFIG is designed to configure a block of port pins, whereas PINCFG can be used to configure them individually.

To configure PA22, which happens to be the Arduino Zero's SDA pin, you could just use the following:

Code: [Select]
// Output the clock on the SDA pin (chosen as it happens to be one of generic clock 6's IO)
// Enable the port multiplexer (mux) on the SDA pin
PORT->Group[g_APinDescription[SDA].ulPort].PINCFG[g_APinDescription[SDA].ulPin].bit.PMUXEN = 1;

// Switch port mux to peripheral function H - generic clock I/O
// Set the port mux mask for even port pin number, SDA = PA22 = 22 is an even number, PMUXE = PMUX Even
PORT->Group[g_APinDescription[SDA].ulPort].PMUX[g_APinDescription[SDA].ulPin >> 1].reg |= PORT_PMUX_PMUXE_H;

The g_APinDescription array maps from Arduino to port pins. The PMUX "ulPin" value is shifted right by 1 to divide the SDA value (port pin 22) by 2 (in this case the result is 11). This is because there are 32 pins, but only 16 PMUX registers (containing odd and even pin pairs) per port.

The next point is that as you're using port pin PA22 (SDA), you'll need to use generic clock (GCLK) 6, as GCLK_IO[6] refers to this GCLK, rather than 5.
Title: Re: Arduino Zero TCC Capture
Post by: Rucus on Jul 26, 2016, 07:58 am
Hello MartinL,

Thanks for your response. Thanks also for pointing out the usage of WRCONFIG and PINCFG, something I did not realize at first. The code you attached works perfectly! The code is able to count the pulses fed to the input pin (PA22).

Out of curiosity, I played around WRCONFIG register just for the sake of trying to make it work. I finally got it to work using the code below:

Code: [Select]
PORT->Group[0].WRCONFIG.reg = PORT_WRCONFIG_HWSEL |
PORT_WRCONFIG_WRPMUX | PORT_WRCONFIG_PMUXEN | PORT_WRCONFIG_PMUX( 7 ) | //PMUX set to H alternate function
PORT_WRCONFIG_INEN |
PORT_WRCONFIG_WRPINCFG |
PORT_WRCONFIG_PINMASK( 0x40 ) ; //Mask other pins except PA22


Thanks for your help, MartinL! I greatly appreciate it.

-Ruch
Title: Re: Arduino Zero TCC Capture
Post by: Rucus on Jul 28, 2016, 07:45 am
Hi,

I am now trying to configure the TCC modules to count up using external clock fed through one of the pins. Basically, what I am trying to do is to count using the TCC module instead of TC module.

Below is what my initialization code looks like:

Code: [Select]
PORT->Group[0].WRCONFIG.reg = PORT_WRCONFIG_HWSEL |
PORT_WRCONFIG_WRPMUX | PORT_WRCONFIG_PMUXEN | PORT_WRCONFIG_PMUX( PORT_PMUX_PMUXE_H_Val ) | //PMUX set to H alternate function
PORT_WRCONFIG_INEN |
PORT_WRCONFIG_WRPINCFG |
PORT_WRCONFIG_PINMASK( 0x10 ) ; //Mask other pins except PA20
Serial.println("Done PORT config");

/* Set GCLK */
REG_GCLK_GENDIV = GCLK_GENDIV_DIV(10) |    // Divide the input clock by (x)
GCLK_GENDIV_ID(4);      // Set division on Generic Clock Generator (GCLK) 6
while (GCLK->STATUS.bit.SYNCBUSY);        // Wait for synchronization
Serial.println("Done GCLK GENDIV config");

REG_GCLK_GENCTRL = GCLK_GENCTRL_IDC |           // Set the duty cycle to 50/50 HIGH/LOW
GCLK_GENCTRL_GENEN |         // Enable GCLK 4
GCLK_GENCTRL_SRC_GCLKIN |    // Set the clock source to generator input pad, set to PA20 in PORT module
GCLK_GENCTRL_ID(4);          // Set clock source on GCLK 4
while (GCLK->STATUS.bit.SYNCBUSY);              // Wait for synchronization*/
Serial.println("Done GCLK GENCTRL config");

REG_GCLK_CLKCTRL = GCLK_CLKCTRL_CLKEN |         // Enable the generic clock...
GCLK_CLKCTRL_GEN_GCLK4 |     // ....on GCLK4
GCLK_CLKCTRL_ID_TCC0_TCC1;    // Feed the GCLK4 to TCC0 and TCC1
while (GCLK->STATUS.bit.SYNCBUSY);              // Wait for synchronization
Serial.println("Done GCLK CLKCTRL config");

REG_TCC0_CTRLBCLR |= TCC_CTRLBCLR_DIR;                     // Set register for counter to count up
while (TCC0->SYNCBUSY.bit.CTRLB);       // Wait for (write) synchronization
Serial.println("Done TCC CTRLB config");

REG_TCC0_COUNT = 0x0000;               // Clear timer's COUNT value
while (TCC0->SYNCBUSY.bit.COUNT);     // Wait for synchronization
Serial.println("Done TCC COUNT cleared");

REG_TCC0_CTRLA |= TCC_CTRLA_PRESCALER_DIV1 |      // Set timer prescaler
TCC_CTRLA_ENABLE;               // Enable TC6
while (TCC0->SYNCBUSY.bit.ENABLE);       // Wait for synchronization
Serial.println("Done TCC CTRLA config");
Serial.println("Done configuring counters");


My code gets stuck at this line:

Code: [Select]
while (TCC0->SYNCBUSY.bit.CTRLB);       // Wait for (write) synchronization

Comparing between TC and TCC modules, I have noticed a few things. With TC module, I have to implicitly "tell" TC through the READREQ register on what register I want to synchronize to. This register seem non-existent in the TCC module. I am wondering if is there a need to tell TCC module that I want to synchronize on one of its registers? If so, what register should I manipulate?

The second thing I noticed is that with TC module, you can just poll the SYNCBUSY bit from the STATUS register and wait until the sync is done for any writes to registers. With TCC, there is a SYNCBUSY register that contains individual sync flags for each fields that needs synchronization. I am wondering if I am polling the sync bits correctly in my code above?

Thanks,
Ruch
Title: Re: Arduino Zero TCC Capture
Post by: MartinL on Jul 28, 2016, 10:00 am
Hi Ruch,

The issue might be to do with the line:

Code: [Select]
REG_TCC0_CTRLBCLR |= TCC_CTRLBCLR_DIR;                     // Set register for counter to count up
The TCC CTRLB has two registers CTRLBSET and CTRLBCLR. The purpose of having a SET and CLR register, is that it avoids your code having to do a read-modify-write operation each time you access it. This operation is instead done by the processor in hardware, thereby saving time. It makes having to logically OR the register with the bitmask to set, or logically AND the register with the inverse bitmask to clear unnecessary.

To modify the CTRLB register to count up, (clear the DIR bit), without altering any other bits in the register, you just need to remove the logical OR:

Code: [Select]
REG_TCC0_CTRLBCLR = TCC_CTRLBCLR_DIR;                     // Clear DIR bit for counter to count up
Quote
With TC module, I have to implicitly "tell" TC through the READREQ register on what register I want to synchronize to. This register seem non-existent in the TCC module. I am wondering if is there a need to tell TCC module that I want to synchronize on one of its registers? If so, what register should I manipulate?
Luckily with the exception of reading the COUNT register, there's no need to do any read synchronization with the TCC timer registers.

Quote
I am wondering if I am polling the sync bits correctly in my code above?
Your polling of the sync bits looks correct.

Quote
Comparing between TC and TCC modules, I have noticed a few things.
I also find the differences between the TCC and TC modules a bit strange. I looks as though they were designed by two separate teams of engineers.
Title: Re: Arduino Zero TCC Capture
Post by: Rucus on Jul 29, 2016, 06:55 am
Thanks, MartinL!

Your suggestion solved a part of the problem. I found out that no clock actually reaches the TCC0 module. I don't know what happened, looks like some problem again with my PORT configuration. When I changed the clock source to something working in the past (GLCK_6), the code is now able to continue which to me is an indication that the TCC0 module is now being clocked. However, another issue came about. It seems that COUNT register only contains zero. My code calls the function below every 1 s (called from the loop()).

Code: [Select]
void ReadCountValTCC()
{
if(TCC0->INTFLAG.bit.OVF && TCC0->INTENSET.bit.OVF)
{
Serial.println("OVF on TCC0!");
}
else
{
Serial.print("TCC0: ");
Serial.println(TCC0->COUNT.reg, DEC);
TCC0->COUNT.reg = 0x0000; //clear count reg
while (TCC0->SYNCBUSY.bit.COUNT);     // Wait for synchronization
}
}


TCC0 always read as '0'.

TCC0: 0
TCC0: 0
TCC0: 0
....


I read from the specs that the counter will count up to the TOP value before resetting to ZERO value. From what I understand, the TOP value can be set from the PER, but PER default value is already 0xFFFFFF, in which case the counter should be able to count up to 2^24. Did I miss to set a register to make the TCC0 module count up?

Thanks,
Ruch
Title: Re: Arduino Zero TCC Capture
Post by: MartinL on Jul 29, 2016, 09:56 am
Hi Ruch,

The forum member "moresun" flagged up this problem with reading the TCC timer COUNT registers in the past: https://forum.arduino.cc/index.php?topic=341655.0 (https://forum.arduino.cc/index.php?topic=341655.0). My apologies for not mentioning it sooner.

Since then this issue has remained unresolved. Reading the COUNT register seems to work for the TC timers, but not for the TCC. This issue doesn't appear in the SAMD21 datasheet errata, so unless wer're both doing something wrong, it might be a hardware bug in the processor.
Title: Re: Arduino Zero TCC Capture
Post by: Rucus on Jul 30, 2016, 04:04 am
Hi MartinL,

Thanks for your reply. And no need to apologize really. :) Everything is fine.

Indeed I think we are all experiencing the same problem. I tried a few more combinations and re-read that portion of the datasheet but still I can't make it to work. Very interesting though that SAMD21G has a total of 6 Timer/Counter modules (TCC0-2, TC3-5) but technically you can only use 2 when using external clock as clock source. This is because you can only feed this clock to any pair of them (TCC0-TCC1 pair, TCC2-TC3 pair, TC4-TC5 pair). TCC0 and TCC1 seem to not work, so basically I can only use the remaining 2. :)

I will see if I can use Events and count those instead of feeding the external pulses to TC modules as clock. But I am not sure if it will be fast enough. Do you think I can count events (maybe rising edge) with max frequency of 150kHz using the TC and/or TCC modules?

Thanks,
Ruch
Title: Re: Arduino Zero TCC Capture
Post by: Rucus on Jul 30, 2016, 05:06 pm
My seem to work now. I can read the COUNT register of TCC0 module.

Here's what my initialization code looks like:

Code: [Select]
PORT->Group[0].WRCONFIG.reg = PORT_WRCONFIG_HWSEL |
 PORT_WRCONFIG_WRPMUX | PORT_WRCONFIG_PMUXEN | PORT_WRCONFIG_PMUX( PORT_PMUX_PMUXE_H_Val ) | //PMUX set to H alternate function
 PORT_WRCONFIG_INEN |
 PORT_WRCONFIG_WRPINCFG |
 PORT_WRCONFIG_PINMASK( 0xC0 ) ; //Mask other pins except PA22 and PA23
  
  Serial.println("Done PORT config");
  
  /* Set GCLK */
  REG_GCLK_GENDIV = GCLK_GENDIV_DIV(10) |    // Divide the input clock by (x)
                    GCLK_GENDIV_ID(6);      // Set division on Generic Clock Generator (GCLK) 6
  while (GCLK->STATUS.bit.SYNCBUSY);        // Wait for synchronization
  Serial.println("Done GCLK GENDIV config for first clock");
  
  REG_GCLK_GENCTRL = GCLK_GENCTRL_IDC |           // Set the duty cycle to 50/50 HIGH/LOW
 GCLK_GENCTRL_GENEN |         // Enable GCLK 6
                     GCLK_GENCTRL_SRC_GCLKIN |    // Set the clock source to generator input pad, set to PA22 in PORT module
                     GCLK_GENCTRL_ID(6);          // Set clock source on GCLK 6
  while (GCLK->STATUS.bit.SYNCBUSY);              // Wait for synchronization*/
  Serial.println("Done GCLK GENCTRL config for first clock");
  
  REG_GCLK_CLKCTRL = GCLK_CLKCTRL_CLKEN |         // Enable the generic clock...
                     GCLK_CLKCTRL_GEN_GCLK6 |     // ....on GCLK6
                     GCLK_CLKCTRL_ID_TCC0_TCC1;    // Feed the GCLK6 to TCC0/1
  while (GCLK->STATUS.bit.SYNCBUSY);              // Wait for synchronization
  Serial.println("Done GCLK CLKCTRL config for first clock");
  
  while (TCC0->SYNCBUSY.bit.CTRLB);                
  REG_TCC0_CTRLBCLR = TCC_CTRLBCLR_DIR;                     // Set register for counter to count up
  while (TCC0->SYNCBUSY.bit.CTRLB);       // Wait for (write) synchronization
  Serial.println("Done TC CTRLB config for third timer");
  
  TCC0->PER.reg = 0xFFFF;              // Set counter Top using the PER register
  while (TCC0->SYNCBUSY.bit.PER == 1); // wait for sync
  
  REG_TCC0_COUNT = 0x0000;               // Clear timer's COUNT value
  while (TCC0->SYNCBUSY.bit.COUNT);     // Wait for synchronization
  Serial.println("Done TC COUNT cleared for third timer");
  
  while (TCC0->SYNCBUSY.bit.CTRLB);
  TCC0->CTRLBSET.bit.CMD = TCC_CTRLBSET_CMD_READSYNC_Val;
  while (TCC0->SYNCBUSY.bit.CTRLB);
  
  REG_TCC0_CTRLA = TCC_CTRLA_PRESCALER_DIV1; // Set timer prescaler
  
  while (TCC0->SYNCBUSY.bit.ENABLE);       // Wait for synchronization
  REG_TCC0_CTRLA |= TCC_CTRLA_ENABLE;               // Enable TCC0
  while (TCC0->SYNCBUSY.bit.ENABLE);       // Wait for synchronization


I created a simple function that gets the COUNT value and prints it to the terminal.

Code: [Select]
void ReadCountValTCC()
{
 if(TCC0->INTFLAG.bit.OVF && TCC0->INTENSET.bit.OVF)
 {
 Serial.println("OVF on TCC0!");
 }
 else
 {
 Serial.print("TCC0: ");
 while (TCC0->SYNCBUSY.bit.CTRLB);
 TCC0->CTRLBSET.bit.CMD = TCC_CTRLBSET_CMD_READSYNC_Val;
 while (TCC0->SYNCBUSY.bit.CTRLB);
 delay(1);                                            // <<--- This line is very important. Without it, COUNT will read zero
 while (TCC0->SYNCBUSY.bit.COUNT);     // Wait for synchronization
 Serial.println(TCC0->COUNT.reg, DEC);
 TCC0->COUNT.reg = 0x0000; //clear count reg
 while (TCC0->SYNCBUSY.bit.COUNT);     // Wait for synchronization
 }
}


This function is called from the loop() every x ms. In my case, I am calling it every 1000 ms.

Basically, the CMD data field of the CTRLBSET reg should be set to READSYNC to force read of the COUNT reg. The delay is important otherwise COUNT will still read as zero.

Thanks,
Ruch
Title: Re: Arduino Zero TCC Capture
Post by: MartinL on Jul 31, 2016, 08:32 pm
Hi Ruch,

Thanks. Now you've pointed it out, I can clearly see it says "READSYNC" in the SAMD21 datasheet.

That's a really useful piece of information for anyone else who wants to read the TCC COUNT register.
Title: Re: Arduino Zero TCC Capture
Post by: MartinL on Aug 01, 2016, 09:25 am
Hi Ruch,

I just ran a little test program. I managed to get it to read the TCC COUNT register without the addition of the 1ms delay.

Here's the code:

Code: [Select]
// Setup TCC0 counter and display the COUNT register continuously in the console
void setup()
{
  SerialUSB.begin(115200);
  while(!SerialUSB);
 
  REG_GCLK_GENDIV = GCLK_GENDIV_DIV(1) |          // Divide the 48MHz system clock by 1 = 48MHz
                    GCLK_GENDIV_ID(4);            // Set division on Generic Clock Generator (GCLK) 4
  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 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

  REG_GCLK_CLKCTRL = GCLK_CLKCTRL_CLKEN |         // Enable the generic clock...
                     GCLK_CLKCTRL_GEN_GCLK4 |     // ....on GCLK4
                     GCLK_CLKCTRL_ID_TCC0_TCC1;   // Feed the GCLK4 to TCC0 and TCC1
  while (GCLK->STATUS.bit.SYNCBUSY);              // Wait for synchronization
 
  REG_TCC0_CTRLA |= TCC_CTRLA_PRESCALER_DIV1 |    // Set prescaler to 1, 48MHz/1 = 48Mhz
                    TCC_CTRLA_ENABLE;             // Enable TCC0
  while (TCC0->SYNCBUSY.bit.ENABLE);              // Wait for synchronization
}

void loop()
{
  REG_TCC0_CTRLBSET = TCC_CTRLBSET_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(REG_TCC0_COUNT, HEX);         // Print the result
  REG_TCC0_COUNT = 0x5;                           // Set the COUNT register to 5
  while (TCC0->SYNCBUSY.bit.COUNT);               // Wait for the COUNT register write sychronization
}

All this synchronization is getting a bit convoluted, as you mentioned you have to set the "READSYNC" command in the CTRLB register, which itself has to be write synchronized, to trigger a read synchronization in COUNT register and only then can you read the TCC's COUNT register itself, phew!
Title: Re: Arduino Zero TCC Capture
Post by: pguerra75 on Sep 04, 2016, 03:06 am
Hi, MartinL and Rucus

    I have been reading and trying my best to understand this thread you two have been working at. I have to admit much of it is above my head however I am learning a great deal. I am currently working on a project were I am receiving a 20-75kHz square wave signal. I was using the Atmega328P and using the FreqCount Library. I however ran out of head room for the display (low power Sharp Memory LCD) and was looking to move to the SAMD21.

    My question is, Would it be possible to assign TCC0 to count events on a particular pin? Or is TCC0 already assigned to a pin?

    Or am I even barking up the right tree?

Thanks for any advice in advance!
Paul
Title: Re: Arduino Zero TCC Capture
Post by: MartinL on Sep 04, 2016, 07:47 pm
Hi Paul,

Quote
My question is, Would it be possible to assign TCC0 to count events on a particular pin? Or is TCC0 already assigned to a pin?
In the case of TCC or TC capture on the SAMD21, any pin capable of receiving interrupts, which happens to be all of them except digital pin 4 can receive the input pulse. It's the event system that routes these pin change signals to the timer itself.

The SAMD21 will struggle to read a 75kHz signal using TCC capture. The issue is that the signal frequency is so high with respect to the processor's 48MHz clock, that the microcontroller ends up spending all its time servicing the incoming interrupts generated by pulses and nothing else.

If you're using a 20-75kHz signal, you could instead use Ruch's example (above), which uses a generic clock input and feed this to the TCC timer. In this way, it's possible to read the number of events (timer counts) over a given period of time.
Title: Re: Arduino Zero TCC Capture
Post by: Rucus on Sep 10, 2016, 05:06 am
Hi MartinL,

I am sorry it took me a very long time to revisit this thread. I am glad you made COUNT register work without the need of the delay. That is really great otherwise we would keep on guessing how much delay is needed. Hmmm.. these synchronization thing makes things more complicated indeed. Is it also true for other ARM-based MCU's? I am just curious. This is the first ARM processor that I used where I have to mess up with low-level registers for some functionalities I can't get from the ASF. For other micros, I just use the HAL drivers provided by the vendor.

Thanks,
Ruch
Title: Re: Arduino Zero TCC Capture
Post by: MartinL on Sep 10, 2016, 10:04 am
Hi Ruch,

Quote
Hmmm.. these synchronization thing makes things more complicated indeed. Is it also true for other ARM-based MCU's?
My only other experience of ARM based microcontrollers is the ARM Cortex M3, SAM3X8E used on the Due.

Although there are a few core CPU similarities, at register level the SAM3X8E is completely different from the SAMD21 and also has the benefit that it doesn't require register synchronization by the programmer.

Martin
Title: Re: Arduino Zero TCC Capture
Post by: Rucus on Sep 11, 2016, 04:44 am
Very interesting to know that SAM3X8E doesn't need the synchronization! I've never used the Due's myself. Hopefully will have the time to try it out in the future. Thanks Martin for sharing!  :)
Title: Re: Arduino Zero TCC Capture
Post by: MartinL on Sep 11, 2016, 07:33 pm
The Due's a nice bit of kit. Running at 84MHz, it's a powerful microcontroller and really does supercharge your Arduino projects.

Shame the Due's not produced by Arduino.cc anymore, although it's still manufactured by Arduino.org.
Title: Re: Arduino Zero TCC Capture
Post by: electro_95 on Sep 23, 2016, 08:05 am
Hi Everyone,

Sorry for being away from the forum so long.  I'm happy that this discussion carried and it looks like others have been making discoveries and getting help with TCC0.  For awhile I was happy with using TC instead but now I want to try again to migrate to TCC0.  Using some of the code share earlier I put together this to try to do a PPW capture on PA04.  For the life of me, I cant figure out why this wont work, any help is appreciated!



Code: [Select]
void setup() {
 Serial.begin(115200);
 while (!Serial) ;
 Serial.println("Hello World");
 pinMode(4,INPUT);
 attachInterrupt(4,NULL,HIGH);

 // Enable clock for TC
 REG_GCLK_CLKCTRL = (uint16_t) (GCLK_CLKCTRL_CLKEN | GCLK_CLKCTRL_GEN_GCLK0 | GCLK_CLKCTRL_ID_TCC0_TCC1) ;
 while ( GCLK->STATUS.bit.SYNCBUSY == 1 ); // wait for sync

 GCLK->CLKCTRL.reg = (uint16_t) (GCLK_CLKCTRL_CLKEN | GCLK_CLKCTRL_GEN_GCLK0 | GCLK_CLKCTRL_ID( GCLK_CLKCTRL_ID_EIC));
 while ( GCLK->STATUS.bit.SYNCBUSY == 1 );
 
 TCC0->CTRLA.reg &= ~TCC_CTRLA_ENABLE;   // Disable TC
 while (TCC0->SYNCBUSY.bit.ENABLE == 1); // wait for sync

 TCC0->CTRLA.reg |= TCC_CTRLA_PRESCALER_DIV1 | TCC_CTRLA_CPTEN0;   // Set perscaler
 while (TCC0->SYNCBUSY.reg & (TCC_SYNCBUSY_PER | TCC_SYNCBUSY_PERB));

 TCC0->PER.reg = 0xFFFFFFFF;              // Set counter Top using the PER register  
 while (TCC0->SYNCBUSY.bit.PER == 1); // wait for sync

 TCC0->EVCTRL.reg =  TCC_EVCTRL_TCEI1 |  // enable input event
                       TCC_EVCTRL_EVACT1_PPW; // event action = PPW
 
 // Interrupts
 TCC0->INTENSET.reg = 0;                 // disable all interrupts
 TCC0->INTENSET.bit.OVF = 1;          // enable overfollow
 TCC0->INTENSET.bit.MC0 = 1;          

 // Enable InterruptVector
 NVIC_EnableIRQ(TCC0_IRQn);

 // Enable TC
 TCC0->CTRLA.reg |= TCC_CTRLA_ENABLE ;
 while (TCC0->SYNCBUSY.bit.ENABLE == 1); // wait for sync

 
 PM->APBCMASK.reg |= PM_APBCMASK_EVSYS;
 
 EIC->EVCTRL.bit.EXTINTEO4 = 1;
 EIC->INTENCLR.bit.EXTINT4 = 1;

 EVSYS->CHANNEL.reg = EVSYS_CHANNEL_PATH_ASYNCHRONOUS | EVSYS_CHANNEL_EVGEN(EVSYS_ID_GEN_EIC_EXTINT_4) | EVSYS_CHANNEL_CHANNEL(2);
 EVSYS->USER.reg = (uint16_t) (EVSYS_USER_USER(EVSYS_ID_USER_TCC0_EV_0 | EVSYS_USER_CHANNEL(1)));
 
}

void loop() {
 delay(250);
 Serial.println(REG_TCC0_CC0);
}

void TCC0_Handler()
{
 Tcc* TC = (Tcc*) TCC0;       // get timer struct
 if (TC->INTFLAG.bit.OVF == 1) {  // A overflow caused the interrupt
   TC->INTFLAG.bit.OVF = 1;    // writing a one clears the flag ovf flag
   //irq_ovf_count++;                 // for debug leds
   Serial.println("OVF");
 }
 
 if (TC->INTFLAG.bit.MC0 == 1) {  // A compare to cc0 caused the interrupt
   TC->INTFLAG.bit.MC0 = 1;    // writing a one clears the flag ovf flag
   Serial.println("MC0");
 }
}
Title: Re: Arduino Zero TCC Capture
Post by: MartinL on Sep 23, 2016, 11:46 am
Hi electro_95,

The TCC timers' set-up is subtly different from that of the TC timers.

1. The capture channel enble bits (CC0/CC1) bits have moved from the CTRLC register on TC, to the CTRLA on the TCC.
2. The indivual counter match compare channel inputs (MCEI0/MCEI1) must also be enbled in the TCC's event control register.
3. The TCC event 1 must be used, as this is the only event input capable of pulse-width and period capture. (Event 0 doesn't have this option).

The following code sets-up capture pulse-width and period on TCC0 on digital pin D12:

Code: [Select]
// Setup TCC0 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_TCC0_TCC1;   // Feed the GCLK5 to TCC0 and TCC1
  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_TCC0_EV_1);              // Set the event user (receiver) as timer TCC0, event 1

  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_TCC0_EVCTRL |= TCC_EVCTRL_MCEI1 |           // Enable the match or capture channel 1 event input
                     TCC_EVCTRL_MCEI0 |           //.Enable the match or capture channel 0 event input
                     TCC_EVCTRL_TCEI1 |           // Enable the TCC event 1 input
                     /*TCC_EVCTRL_TCINV1 |*/      // Invert the event 1 input         
                     TCC_EVCTRL_EVACT1_PPW;       // Set up the timer for capture: CC0 period, CC1 pulsewidth
                                       
  //NVIC_DisableIRQ(TCC0_IRQn);
  //NVIC_ClearPendingIRQ(TCC0_IRQn);
  NVIC_SetPriority(TCC0_IRQn, 0);      // Set the Nested Vector Interrupt Controller (NVIC) priority for TCC0 to 0 (highest)
  NVIC_EnableIRQ(TCC0_IRQn);           // Connect the TCC0 timer to the Nested Vector Interrupt Controller (NVIC)
 
  REG_TCC0_INTENSET = TCC_INTENSET_MC1 |            // Enable compare channel 1 (CC1) interrupts
                      TCC_INTENSET_MC0;             // Enable compare channel 0 (CC0) interrupts
 
  REG_TCC0_CTRLA |= TCC_CTRLA_CPTEN1 |              // Enable capture on CC1
                    TCC_CTRLA_CPTEN0 |              // Enable capture on CC0
                    TCC_CTRLA_PRESCALER_DIV16 |     // Set prescaler to 16, 16MHz/16 = 1MHz
                    TCC_CTRLA_ENABLE;               // Enable TCC0
  while (TCC0->SYNCBUSY.bit.ENABLE);                // 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 TCC0_Handler()                              // Interrupt Service Routine (ISR) for timer TCC0
{     
  // Check for match counter 0 (MC0) interrupt
  if (TCC0->INTFLAG.bit.MC0)             
  {   
    isrPeriod = REG_TCC0_CC0;                   // Copy the period
    periodComplete = true;                       // Indicate that the period is complete
  }

  // Check for match counter 1 (MC1) interrupt
  if (TCC0->INTFLAG.bit.MC1)           
  {
    isrPulsewidth = REG_TCC0_CC1;               // Copy the pulse-width
  }
}

Luckily, the code's a bit shorter as the TCC timer requires less synchronization.
Title: Re: Arduino Zero TCC Capture
Post by: electro_95 on Sep 23, 2016, 05:01 pm
 :)  :)  :)

You're the best!  Thank you so much for the help, this code does exactly what I was trying to do.

Is there actual documentation that you used to figure this out or is it purely from trial and error?  Maybe I'm missing it but I felt like the datasheet described what PPW capture was like in concept but provided  very little info on how to actually configure it.  Especially to route the event to EV_1 and that MCEI0 and MCEI1 needed to be enabled (same with CPTEN0 and CPTEN1).

Anyway, many many thanks, this one was frustrating.

Now dare I ask (or even attempt) how to put these PPW measurements straight into memory via DMA?
Title: Re: Arduino Zero TCC Capture
Post by: sundancekis on Dec 02, 2016, 04:33 am
Hello All,

Thanks for discussion on this thread, it has helped me with my project.
I am hoping to get help understanding the code though. Your help is appreciated.
Some background on the code and use:

I am using an arduino MKR1000 which is very similar to the Zero.
The code input captures 3 pwm signals using 3 TCs
The code builds off of the code in this thread to accomplish this, but does not use an ISR
The code works (at bottom sniped and attached to post), I am just curious about how a few lines work mainly the assignments of

REG_EVSYS_USER
REG_EVSYS_CHANNEL
and why I can set the REG separately and it seems to complete 3 functions. I would have thought I would need to OR the commands together like:
Code: [Select]

  REG_EVSYS_USER = EVSYS_USER_CHANNEL(1) |                                // Attach the event user (receiver) to channel 0 (n + 1)
                   EVSYS_USER_USER(EVSYS_ID_USER_TC5_EVU);                // Set the event user (receiver) as timer TC5

  REG_EVSYS_USER |= EVSYS_USER_CHANNEL(2) |                                // Attach the event user (receiver) to channel 1 (n + 1)
                   EVSYS_USER_USER(EVSYS_ID_USER_TC4_EVU);                // Set the event user (receiver) as timer TC4
 REG_EVSYS_USER |= EVSYS_USER_CHANNEL(3) |                                // Attach the event user (receiver) to channel 2 (n + 1)
                   EVSYS_USER_USER(EVSYS_ID_USER_TC3_EVU);                // Set the event user (receiver) as timer TC3



A snip of the working code:

 
Code: [Select]
// Code will initalize timers on mkr to do 3 input captures and
// output 3 pwm waves that vary duty cycle at a fixed freq

//Input capture. Using TC3 TC4 and TC5

void setup() {
  // put your setup code here, to run once:
   SerialUSB.begin(115200);                  // Send data back on the MKR1000's native port
   while(!SerialUSB);                        // Wait for the SerialUSB port to be ready
 
  init_clocks();                             // Initalize the TC's for inputcapture
}

void loop() {


    getpulse();           // Function to collect the PWM signals

    period = isrPeriod1;
    pulsewidth = isrPulsewidth1;
    SerialUSB.print(period);                      // Output the results...



}


void init_clocks(){
  SerialUSB.println("setup clocks...");
\\ Feed clock code from before
                     
  while (GCLK->STATUS.bit.SYNCBUSY);              // Wait for synchronization*/
 SerialUSB.println("feeding clocks..");
  REG_GCLK_CLKCTRL = GCLK_CLKCTRL_CLKEN |         // Enable the generic clock...
                     GCLK_CLKCTRL_GEN_GCLK5 |     // ....on GCLK5
                     GCLK_CLKCTRL_ID_TC4_TC5;    // Feed the GCLK5 to TC4 and TC5
  while (GCLK->STATUS.bit.SYNCBUSY);              // Wait for synchronization
 

 SerialUSB.println("feeding clocks part 2..");    // Doing this a second time does not make sense to me, but it works and does not overwrite the previous registry writting
  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_EXTINTEO2;                                 // Enable event output on external interrupt 2 used iwth TC5
  attachInterrupt(A1, NULL, HIGH);                                        // Attach interrupts. Not sure why this is nesseary

  REG_EIC_EVCTRL |= EIC_EVCTRL_EXTINTEO4;                                 // Enable event output on external interrupt 4 to be used with TC4
  attachInterrupt(6, NULL, HIGH);       

  REG_EIC_EVCTRL |= EIC_EVCTRL_EXTINTEO3;                                 // Enable event output on external interrupt 3 to be used with TC3
  attachInterrupt(A2, NULL, HIGH);                                        // Attach interrupts to digital pin A2 (external interrupt 3).
 
//
 SerialUSB.println("setting event system..");
  REG_EVSYS_USER = EVSYS_USER_CHANNEL(1) |                                // Attach the event user (receiver) to channel 0 (n + 1)
                   EVSYS_USER_USER(EVSYS_ID_USER_TC5_EVU);                // Set the event user (receiver) as timer TC5
 
  // Doing this a second time does not make sense to me, but it works and does not overwrite the previous registry writting
 
  REG_EVSYS_USER = EVSYS_USER_CHANNEL(2) |                                // Attach the event user (receiver) to channel 1 (n + 1)
                   EVSYS_USER_USER(EVSYS_ID_USER_TC4_EVU);                // Set the event user (receiver) as timer TC4

  // Doing this a third time does not make sense to me, but it works and does not overwrite the previous registry writting
  REG_EVSYS_USER = EVSYS_USER_CHANNEL(3) |                                // Attach the event user (receiver) to channel 2 (n + 1)
                   EVSYS_USER_USER(EVSYS_ID_USER_TC3_EVU);                // Set the event user (receiver) as timer TC3

 SerialUSB.println("setting up channel..");
  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_2) |    // Set event generator (sender) as external interrupt 2
                      EVSYS_CHANNEL_CHANNEL(0);
                     
  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_4) |    // Set event generator (sender) as external interrupt 4
                      EVSYS_CHANNEL_CHANNEL(1);
//                     
  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(2);                           // Attach the generator (sender) to channel

//
 SerialUSB.println("setting up input capture..");
  REG_TC5_EVCTRL |= TC_EVCTRL_TCEI |              // Enable the TC event input
                    TC_EVCTRL_EVACT_PPW;          // Set up the timer for capture: CC0 period, CC1 pulsewidth
  REG_TC4_EVCTRL |= TC_EVCTRL_TCEI |              // Enable the TC event input
                    TC_EVCTRL_EVACT_PPW;          // Set up the timer for capture: CC0 period, CC1 pulsewidth                 
  REG_TC3_EVCTRL |= TC_EVCTRL_TCEI |              // Enable the TC event input
                    TC_EVCTRL_EVACT_PPW;          // Set up the timer for capture: CC0 period, CC1 pulsewidth
                             
  REG_TC5_READREQ = TC_READREQ_RREQ |             // Enable a read request
                    TC_READREQ_ADDR(0x06);        // Offset of the CTRLC register

  while (TC5->COUNT16.STATUS.bit.SYNCBUSY);       // Wait for (read) synchronization
  REG_TC5_CTRLC |= TC_CTRLC_CPTEN1 |              // Enable capture on CC1
                   TC_CTRLC_CPTEN0;               // Enable capture on CC0.
                               

  while (TC5->COUNT16.STATUS.bit.SYNCBUSY);       // Wait for (write) synchronization
 
  REG_TC5_CTRLA |= TC_CTRLA_PRESCALER_DIV16 |     // Set prescaler to 16, 16MHz/16 = 1MHz
                   TC_CTRLA_ENABLE;               // Enable TC5

  while (TC5->COUNT16.STATUS.bit.SYNCBUSY);       // Wait for synchronization                                 

// Repeat for TC4 and TC3

//
  SerialUSB.println("setup complete");
}

void getpulse()
{
    REG_TC5_READREQ = TC_READREQ_RREQ |           // Enable a read request
                      TC_READREQ_ADDR(0x18);      // Offset address of the CC0 register. need to find the addresses for all

    while (TC5->COUNT16.STATUS.bit.SYNCBUSY);     // Wait for (read) synchronization
    isrPeriod1 = REG_TC5_COUNT16_CC0;              // Copy the period

    REG_TC5_READREQ = TC_READREQ_RREQ |           // Enable a read request
                      TC_READREQ_ADDR(0x1A);      // Offset address of the CC1 register
    while (TC5->COUNT16.STATUS.bit.SYNCBUSY);     // Wait for (read) synchronization
    isrPulsewidth1 = REG_TC5_COUNT16_CC1;          // Copy the pulse-width
//
//    Repeat for TC4 and TC3
}
Title: Re: Arduino Zero TCC Capture
Post by: MartinL on Dec 02, 2016, 09:19 am
Hi sundancekis,

The event system's User Multiplexer register (REG_EVSYS_USER) is a 16-bit register, but rather than being a simple register, it is in fact an interface or gateway to the event system multiplexer.

The register allows you to connect one of the event system's 12 channels to a peripheral (such as a timer), called a USER, that is the intended recipient of the event. It's possible to write to this register multiple times to connect various event channels to different users.

The fact that it's an interface to the event system multiplexer means that you can't simply read this register. You first have to do an 8-bit write to the user portion of the register, (the least significant byte), then read the register to get back the channel to which it's attached.

The register is described on page 420 of the SAMD21 datasheet.

The channel register (REG_EVSYS_CHANNEL) words in a similar manner.
Title: Re: Arduino Zero TCC Capture
Post by: dlabun on Dec 04, 2016, 09:51 pm
Does anyone have a complete example they could post? I can't seem to combine all of snippets of code in this post into a working project.
Title: Re: Arduino Zero TCC Capture
Post by: MartinL on Dec 05, 2016, 10:42 am
Hi dlabun,

Quote
I can't seem to combine all of snippets of code in this post into a working project.
The TC/TCC period and pulse width capture can be boiled down to two examples that read the CPPM (pulse position modulation) input from a RC (radio controlled) receiver.

If you're interested in a different period and pulse width times, then it's necessary to change the generic clock divisor (GCLK_GENDIV_DIV(x)) and/or the timer prescaler (TC_CTRLA_PRESCALER_DIVx or TCC_CTRLA_PRESCALER_DIVx), so that the maximum period won't cause the timer to overflow.

The TC3 example:

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
  }
}
Title: Re: Arduino Zero TCC Capture
Post by: MartinL on Dec 05, 2016, 10:43 am
...and the TCC0 example:

Code: [Select]
// Setup TCC0 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_TCC0_TCC1;   // Feed the GCLK5 to TCC0 and TCC1
  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_TCC0_EV_1);              // Set the event user (receiver) as timer TCC0, event 1

  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_TCC0_EVCTRL |= TCC_EVCTRL_MCEI1 |           // Enable the match or capture channel 1 event input
                     TCC_EVCTRL_MCEI0 |           //.Enable the match or capture channel 0 event input
                     TCC_EVCTRL_TCEI1 |           // Enable the TCC event 1 input
                     /*TCC_EVCTRL_TCINV1 |*/      // Invert the event 1 input         
                     TCC_EVCTRL_EVACT1_PPW;       // Set up the timer for capture: CC0 period, CC1 pulsewidth
                                       
  //NVIC_DisableIRQ(TCC0_IRQn);
  //NVIC_ClearPendingIRQ(TCC0_IRQn);
  NVIC_SetPriority(TCC0_IRQn, 0);      // Set the Nested Vector Interrupt Controller (NVIC) priority for TCC0 to 0 (highest)
  NVIC_EnableIRQ(TCC0_IRQn);           // Connect the TCC0 timer to the Nested Vector Interrupt Controller (NVIC)
 
  REG_TCC0_INTENSET = TCC_INTENSET_MC1 |            // Enable compare channel 1 (CC1) interrupts
                      TCC_INTENSET_MC0;             // Enable compare channel 0 (CC0) interrupts
 
  REG_TCC0_CTRLA |= TCC_CTRLA_CPTEN1 |              // Enable capture on CC1
                    TCC_CTRLA_CPTEN0 |              // Enable capture on CC0
                    TCC_CTRLA_PRESCALER_DIV16 |     // Set prescaler to 16, 16MHz/16 = 1MHz
                    TCC_CTRLA_ENABLE;               // Enable TCC0
  while (TCC0->SYNCBUSY.bit.ENABLE);                // 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 TCC0_Handler()                              // Interrupt Service Routine (ISR) for timer TCC0
{     
  // Check for match counter 0 (MC0) interrupt
  if (TCC0->INTFLAG.bit.MC0)             
  {   
    isrPeriod = REG_TCC0_CC0;                   // Copy the period
    periodComplete = true;                       // Indicate that the period is complete
  }

  // Check for match counter 1 (MC1) interrupt
  if (TCC0->INTFLAG.bit.MC1)           
  {
    isrPulsewidth = REG_TCC0_CC1;               // Copy the pulse-width
  }
}
Title: Re: Arduino Zero TCC Capture
Post by: dlabun on Dec 05, 2016, 05:09 pm
Hi Martin,

Thank you once again for the fine examples. My particular usage is taking the information in this discussion in a slightly different direction, counting number of pulses occurring within an adjustable time period (aka, a radiation sensor).
Title: Re: Arduino Zero TCC Capture
Post by: Petirrojo31 on Dec 31, 2016, 11:31 pm
Greetings Mister Martin.

I need your guidance and help with follow subject.

I have got a project based in Arduino Leonardo which has become bigger for this little platform, so I decided to move to Arduino M0.

One of task Arduino must carry on is to count pulses; in Leonardo I had configured a free-running counter in T1 timer/counter module.

For Arduino M0, I have taken code from this post; basically I need to set up Arduino to count pulses entered in Pin 12 (Pin 28 from SAM21G = PA19), code is as follow.

Code: [Select]
#define PinLED 13
volatile boolean Pp = true;


void setup()
{

 SerialUSB.begin(19200);
 while(!SerialUSB);
 
 pinMode(PinLED, OUTPUT);

 // Configuración del pin PA19 (Pin 12 Arduino) para contar eventos
 PORT->Group[0].WRCONFIG.reg = PORT_WRCONFIG_HWSEL |
                 PORT_WRCONFIG_WRPMUX |
                 PORT_WRCONFIG_PMUXEN |
                 PORT_WRCONFIG_PMUX( PORT_PMUX_PMUXE_H_Val ) | //PMUX set to H alternate function
                 PORT_WRCONFIG_INEN |
                 PORT_WRCONFIG_WRPINCFG |
                 PORT_WRCONFIG_PINMASK( 0x08 ) ; //Mask other pins except PA19
  SerialUSB.println("PORT configurado");

  // - Enable TCC0 Bus clock (Timer counter control clock)
  PM->APBCMASK.reg |= PM_APBCMASK_TCC0;
  
  // Configuración de GCLK
  REG_GCLK_GENDIV = GCLK_GENDIV_DIV(1) |          // Divide the input clock by (x)
                    GCLK_GENDIV_ID(6);            // Set division on Generic Clock Generator (GCLK) 6
  while (GCLK->STATUS.bit.SYNCBUSY);              // Wait for synchronization
  SerialUSB.println("Configuración GCLK GENDIV realizada para el primer reloj");
  
  REG_GCLK_GENCTRL = GCLK_GENCTRL_IDC |           // Set the duty cycle to 50/50 HIGH/LOW
           GCLK_GENCTRL_GENEN |         // Enable GCLK 6
                     GCLK_GENCTRL_SRC_GCLKIN |    // Set the clock source to generator input pad, set to PA19 in PORT module
                     GCLK_GENCTRL_ID(6);          // Set clock source on GCLK 6
  while (GCLK->STATUS.bit.SYNCBUSY);              // Wait for synchronization*/
  SerialUSB.println("Configuración GCLK GENCTRL realizada para el primer reloj");
  
  REG_GCLK_CLKCTRL = GCLK_CLKCTRL_CLKEN |         // Enable the generic clock...
                     GCLK_CLKCTRL_GEN_GCLK6 |     // ....on GCLK6
                     GCLK_CLKCTRL_ID_TCC0_TCC1;   // Feed the GCLK6 to TCC0/TCC1
  while (GCLK->STATUS.bit.SYNCBUSY);              // Wait for synchronization
  SerialUSB.println("Configuración GCLK CLKCTRL realizada para el primer reloj");

  
  /* Configuración de TCC0 */

  REG_TCC0_CTRLA &=~TCC_CTRLA_ENABLE;   // DISABLE TCC0
  while (TCC0->SYNCBUSY.bit.ENABLE);         // Wait for synchronization
  SerialUSB.println("Comenzando configuración de TCC0");

  while (TCC0->SYNCBUSY.bit.CTRLB);
  REG_TCC0_CTRLBCLR = TCC_CTRLBCLR_DIR;   // Clear DIR bit for counter to count up
  while (TCC0->SYNCBUSY.bit.CTRLB);       // Wait for (write) synchronization
  SerialUSB.println("Configuración TCC0 CTRLB realizada para el tercer reloj");
  
  TCC0->PER.reg = 0xFFFF;              // Set counter Top using the PER register
  while (TCC0->SYNCBUSY.bit.PER); // wait for sync
  SerialUSB.println("Registro TCC0 PER inicializado");

  while (TCC0->SYNCBUSY.bit.CTRLB);
  TCC0->CTRLBSET.bit.CMD = TCC_CTRLBSET_CMD_READSYNC_Val;
  while (TCC0->SYNCBUSY.bit.CTRLB);
  
  REG_TCC0_COUNT = 0x0000;               // Clear timer's COUNT value
  while (TCC0->SYNCBUSY.bit.COUNT);      // Wait for synchronization
  SerialUSB.println("Registro TCC0 COUNT inicializado para el tercer reloj");
  
  REG_TCC0_CTRLA = TCC_CTRLA_PRESCALER_DIV1; // Set timer prescaler
  while (TCC0->SYNCBUSY.bit.ENABLE);         // Wait for synchronization
  
  REG_TCC0_CTRLA |= TCC_CTRLA_ENABLE;        // Enable TCC0
  while (TCC0->SYNCBUSY.bit.ENABLE);         // Wait for synchronization

  SerialUSB.println("Configuración TCC0 CTRLA realizada para el tercer reloj");
  
}



void loop()
{
  delay(1000);  // Wait one second
  
  digitalWrite(PinLED, Pp);
  Pp = !Pp;
  
  REG_TCC0_CTRLBSET = TCC_CTRLBSET_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.print("TCC0: ");
  SerialUSB.println(REG_TCC0_COUNT, DEC);         // Print the result
  //REG_TCC0_COUNT = 0x00;                           // Clear the COUNT register
  while (TCC0->SYNCBUSY.bit.COUNT);               // Wait for the COUNT register write sychronization
}


What I experience is code gets stuck in every TCC0 synchronization polling

Code: [Select]
REG_TCC0_CTRLBCLR = TCC_CTRLBCLR_DIR;   // Clear DIR bit for counter to count up
 while (TCC0->SYNCBUSY.bit.CTRLB);


If I comment every 'while' code run properly, but TCC0 count register is always '0'.

For fast checking I have tied pin 13 and pin 12, but LED on board stays off when I join them, if I detach them, LED blinks every second.

For the references in this post (http://forum.arduino.cc/index.php?topic=346731.105), I know M0 and Zero boards are differents. I am using IDE 1.8.0 from arduino.org, code compiles well but always get stuck in those whiles from TCC0.


Any help it will be appreciated.
Title: Re: Arduino Zero TCC Capture
Post by: MartinL on Jan 01, 2017, 11:46 am
Hi Petirrojo31,

I've also tried to get this working. However like you, I found that activating the GCLK input pad causes the Zero's microcontroller to exhibit some strange behaviour. The code below sets up GCLK 4 input on digital pin 6 (D6), routes this to timer TCC0 and outputs the timer's COUNT register to the console.

Here are my observations:

1. If the input is left floating the counter counts and if a pull-up resistor is introduced it stops. So far so good.

2. Pulling the signal to ground through the pull-up causes the timer to jump up a number of counts each time. Again, as the signal isn't debounced this is as expected.

3. Set-up a digital IO to switch on and off every half a second and use this as an input to the GCLK pin. The program stalls, no output from the console.

4. Set-up a digital IO to switch on and off every half a second on another Arduino Zero and use this as an input the GCLK pin. The program takes a couple of seconds to start and correctly counts, but only outputs to the console every 4 seconds or so, despite there being no delay in the loop(). Possibly a synchronization issue?

There seems to be some strange interaction going on that causes the program to stall or slow. If the clock source on the other hand is taken from an internal source such as the 48MHz clock, then the program works just fine. So far I haven't yet found a solution.

Here's the code I used:

Code: [Select]
// Setup TCC0 counter to count inputs from the D6 pin and display its COUNT register in the console
void setup()
{
  SerialUSB.begin(115200);                        // Set up USB port at 115200 baud
  while(!SerialUSB);                              // Wait for the console to be started
 
  // Output the clock on the D6 pin (chosen as it happens to be one of generic clock 4's IO)
  // Enable the port multiplexer (mux) on the D6 pin
  PORT->Group[g_APinDescription[6].ulPort].PINCFG[g_APinDescription[6].ulPin].bit.PMUXEN = 1;
 
  // Switch port mux to peripheral function H - generic clock I/O
  // Set the port mux mask for even port pin number, D6 = PA20 = 20 is an even number, PMUXE = PMUX Even
  PORT->Group[g_APinDescription[6].ulPort].PMUX[g_APinDescription[6].ulPin >> 1].reg |= PORT_PMUX_PMUXE_H;
 
  REG_GCLK_GENDIV = GCLK_GENDIV_DIV(1) |          // Divide the generic clock input by 1
                    GCLK_GENDIV_ID(4);            // Set division on Generic Clock Generator (GCLK) 4
  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 4
                     /*GCLK_GENCTRL_SRC_DFLL48M |*/GCLK_GENCTRL_SRC_GCLKIN |    // Set the clock source to GCLK input
                     GCLK_GENCTRL_ID(4);          // Set clock source on GCLK 4
  while (GCLK->STATUS.bit.SYNCBUSY);              // Wait for synchronization

  REG_GCLK_CLKCTRL = GCLK_CLKCTRL_CLKEN |         // Enable the generic clock...
                     GCLK_CLKCTRL_GEN_GCLK4 |     // ....on GCLK4
                     GCLK_CLKCTRL_ID_TCC0_TCC1;   // Feed the GCLK4 to TCC0 and TCC1
  while (GCLK->STATUS.bit.SYNCBUSY);              // Wait for synchronization
 
  REG_TCC0_CTRLA |= TCC_CTRLA_PRESCALER_DIV1 |    // Set prescaler to 1
                    TCC_CTRLA_ENABLE;             // Enable TCC0
  while (TCC0->SYNCBUSY.bit.ENABLE);              // Wait for synchronization
}

void loop()
{
  REG_TCC0_CTRLBSET = TCC_CTRLBSET_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(REG_TCC0_COUNT, HEX);         // Output the TCC0 COUNT register
}

Title: Re: Arduino Zero TCC Capture
Post by: MartinL on Jan 02, 2017, 09:58 am
Come to think of it, in observation 2 above the timer should continue outputting to the console, even when the pull-up resistor is attached.

I think the issue is that the generic clock (GCLK) source is driving the whole timer peripheral and not just the timer's counter. So when the GCLK stops the whole timer peripheral and all its registers stop responding and therefore the program appears to stall, as it just sits there waiting for register synchronization.

It might be better to use another method to count incoming pulses. Perhaps using the TCC capture code above, but instead of measuring the pulse width in the timer's interrupt handler, just increment a "count" variable.
Title: Re: Arduino Zero TCC Capture
Post by: Petirrojo31 on Jan 04, 2017, 12:04 am
Hello Mister Martin.

As you have pointed expertly in your response, I have also detected for TCC0 that if GCLK is configured with pin input external clock (GCLK_GENCTRL_SRC_GCLKIN), all peripherals register stay waiting for clock signal to continue working, so if there is not signal applied to that specific pin (as my case when pulses come later when sensor is plugged to board), program will stall.

I think the issue is that the generic clock (GCLK) source is driving the whole timer peripheral and not just the timer's counter. So when the GCLK stops the whole timer peripheral and all its registers stop responding and therefore the program appears to stall, as it just sits there waiting for register synchronization.
Yesterday (2nd january), I have tried with next code taken from this post...

Code: [Select]
#define PinLED  13

volatile boolean Pp = true;
volatile boolean periodComplete;

volatile unsigned long tcc0_cnt;

void setup() {
  // put your setup code here, to run once:
 
  SerialUSB.begin(19200);                   // Send data back on the Zero's native port
  while(!SerialUSB);                        // Wait for the SerialUSB port to be ready

  pinMode(PinLED, OUTPUT);
  digitalWrite(PinLED, LOW);
 
 
  REG_PM_APBCMASK  |= PM_APBCMASK_EVSYS;  // Switch on the event system peripheral
  //PM->APBCMASK.reg |= PM_APBCMASK_TCC0;   // Enable TCC0 Bus clock (Timer counter control clock)
 
  REG_GCLK_GENDIV = GCLK_GENDIV_DIV(4) |    // 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
  SerialUSB.println("Configuración GCLK GENDIV realizada");

  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
  SerialUSB.println("Configuración GCLK GENCTRL realizada");

  REG_GCLK_CLKCTRL = GCLK_CLKCTRL_CLKEN |         // Enable the generic clock...
                     GCLK_CLKCTRL_GEN_GCLK5 |     // ....on GCLK5
                     GCLK_CLKCTRL_ID_TCC0_TCC1;   // Feed the GCLK5 to TCC0 and TCC1
  while (GCLK->STATUS.bit.SYNCBUSY);              // Wait for synchronization
  SerialUSB.println("Configuración GCLK CLKCTRL realizada");

  REG_EIC_EVCTRL |= EIC_EVCTRL_EXTINTEO3;                                 // Enable event output on external interrupt 3
  attachInterrupt(12, NULL, RISING);                                      // Attach interrupts to digital pin 12 (external interrupt 3)
  SerialUSB.println("Configuración EXTINT_03 -Pin 12- realizada");
 
  REG_EVSYS_USER = EVSYS_USER_CHANNEL(1) |                                // Attach the event user (receiver) to channel 0 (n + 1)
                   EVSYS_USER_USER(EVSYS_ID_USER_TCC0_EV_1);              // Set the event user (receiver) as timer TCC0, event 1
  SerialUSB.println("Configuración EVE_SYS_USER realizada");

  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
  SerialUSB.println("Configuración EVE_SYS_CHANNEL realizada");
 

  REG_TCC0_EVCTRL |= TCC_EVCTRL_MCEI1 |           // Enable the match or capture channel 1 event input
                     TCC_EVCTRL_MCEI0 |           // Enable the match or capture channel 0 event input
                     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_EVACT1_PPW;       // Set up the timer for capture: CC0 period, CC1 pulsewidth
                     TCC_EVCTRL_EVACT0_INC;       // Set up the timer to increment TC on event
  SerialUSB.println("Configuración TCC0_EVCTRL realizada");

  //NVIC_DisableIRQ(TCC0_IRQn);
  //NVIC_ClearPendingIRQ(TCC0_IRQn);
  NVIC_SetPriority(TCC0_IRQn, 0);      // Set the Nested Vector Interrupt Controller (NVIC) priority for TCC0 to 0 (highest)
  NVIC_EnableIRQ(TCC0_IRQn);           // Connect the TCC0 timer to the Nested Vector Interrupt Controller (NVIC)
  SerialUSB.println("Configuración NVIC/IRQ realizada");

  REG_TCC0_INTENSET = TCC_INTENSET_MC1 |            // Enable compare channel 1 (CC1) interrupts
                      TCC_INTENSET_MC0 |            // Enable compare channel 0 (CC0) interrupts
                      TCC_INTENSET_CNT;             // Enable counter interrupt
  SerialUSB.println("Configuración TCC0_INTENSET realizada");
 
  REG_TCC0_CTRLA |= TCC_CTRLA_CPTEN1 |              // Enable capture on CC1
                    TCC_CTRLA_CPTEN0 |              // Enable capture on CC0
                    TCC_CTRLA_PRESCALER_DIV16 |     // Set prescaler to 16, 16MHz/16 = 1MHz
                    TCC_CTRLA_ENABLE;               // Enable TCC0
  while (TCC0->SYNCBUSY.bit.ENABLE);         // Wait for synchronization
  SerialUSB.println("Módulo TCC0 habilitado");
/*
  NVIC_DisableIRQ(TCC0_IRQn);       SerialUSB.println("Disable...");
  NVIC_ClearPendingIRQ(TCC0_IRQn);  SerialUSB.println("Cleared...");
  NVIC_SetPriority(TCC0_IRQn, 1);   SerialUSB.println("Setted priority...");
  NVIC_EnableIRQ(TCC0_IRQn);        SerialUSB.println("Enable IRQ");
*/
}

void loop() {
  // put your main code here, to run repeatedly:

  delay(1000);  // Wait one second
 
  digitalWrite(PinLED, Pp);
  Pp = !Pp;

  SerialUSB.print("TCC0 Counter: ");
  SerialUSB.println(tcc0_cnt, DEC);         // Print the result

}


void TCC0_Handler()                              // Interrupt Service Routine (ISR) for timer TCC0
{     
  // Check for counter interrupt
  if (TCC0->INTFLAG.bit.CNT)             
  {   
    tcc0_cnt = REG_TCC0_COUNT;                   // Copy TCC0 count value
    periodComplete = true;                       // Indicate that the period is complete
  }

  if (TCC0->INTFLAG.bit.MC0){};
  if (TCC0->INTFLAG.bit.MC1){};
 
}


But I get nothing in the count (always '0'); I am looking how to set register properly so count pulses can work fine.

This subject is driving me crazy, this is a part from bigger project and I feel I am making a tempest in a teapot.

Thanks for your advice and help.


Vladimir.
Title: Re: Arduino Zero TCC Capture
Post by: MartinL on Jan 04, 2017, 10:25 am
Hi Vladimir,

In your code you need to change the 4 to 3, in order to get 16MHz from GCLK5:

Code: [Select]
REG_GCLK_GENDIV = GCLK_GENDIV_DIV(4) |    // Divide the 48MHz system clock by 3 = 16MHz
Reading the TCC0 COUNT register is pretty convoluted:

1. It requires you to first request a read synchronization for the TCC0 COUNT, by setting the TCC_CTRLBSET_CMD_READSYNC bits in the TCC0's Control B register (REG_TCC0_CTRLBSET).

2. This Control B register itself then has to be write synchronized.

3. We then to need wait for read synchronization with the COUNT register.

4. Finally the COUNT register can be read. 

Just place this bit of code:

Code: [Select]
REG_TCC0_CTRLBSET = TCC_CTRLBSET_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

...before the line:

Code: [Select]
tcc0_cnt = REG_TCC0_COUNT;                   // Copy TCC0 count value
...in your code.

Alternatively, the following code is just the TCC0 example above, but with "isrCount" and "count" variables included, that count the number of pulses:

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

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_TCC0_TCC1;   // Feed the GCLK5 to TCC0 and TCC1
  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_TCC0_EV_1);              // Set the event user (receiver) as timer TCC0, event 1

  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_TCC0_EVCTRL |= TCC_EVCTRL_MCEI1 |           // Enable the match or capture channel 1 event input
                     TCC_EVCTRL_MCEI0 |           //.Enable the match or capture channel 0 event input
                     TCC_EVCTRL_TCEI1 |           // Enable the TCC event 1 input
                     /*TCC_EVCTRL_TCINV1 |*/      // Invert the event 1 input         
                     TCC_EVCTRL_EVACT1_PPW;       // Set up the timer for capture: CC0 period, CC1 pulsewidth
                                       
  //NVIC_DisableIRQ(TCC0_IRQn);
  //NVIC_ClearPendingIRQ(TCC0_IRQn);
  NVIC_SetPriority(TCC0_IRQn, 0);      // Set the Nested Vector Interrupt Controller (NVIC) priority for TCC0 to 0 (highest)
  NVIC_EnableIRQ(TCC0_IRQn);           // Connect the TCC0 timer to the Nested Vector Interrupt Controller (NVIC)
 
  REG_TCC0_INTENSET = TCC_INTENSET_MC1 |            // Enable compare channel 1 (CC1) interrupts
                      TCC_INTENSET_MC0;             // Enable compare channel 0 (CC0) interrupts
 
  REG_TCC0_CTRLA |= TCC_CTRLA_CPTEN1 |              // Enable capture on CC1
                    TCC_CTRLA_CPTEN0 |              // Enable capture on CC0
                    TCC_CTRLA_PRESCALER_DIV16 |     // Set prescaler to 16, 16MHz/16 = 1MHz
                    TCC_CTRLA_ENABLE;               // Enable TCC0
  while (TCC0->SYNCBUSY.bit.ENABLE);                // 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;
    count = isrCount;
    interrupts();
    SerialUSB.print(period);                      // Output the results
    SerialUSB.print(F("   "));
    SerialUSB.print(pulsewidth);
    SerialUSB.print(F("   "));
    SerialUSB.println(count);
    periodComplete = false;                       // Start a new period
  }
}

void TCC0_Handler()                              // Interrupt Service Routine (ISR) for timer TCC0
{     
  // Check for match counter 0 (MC0) interrupt
  if (TCC0->INTFLAG.bit.MC0)             
  {   
    isrPeriod = REG_TCC0_CC0;                   // Copy the period
    periodComplete = true;                       // Indicate that the period is complete
  }

  // Check for match counter 1 (MC1) interrupt
  if (TCC0->INTFLAG.bit.MC1)           
  {
    isrPulsewidth = REG_TCC0_CC1;               // Copy the pulse-width
    isrCount++;
  }
}

Kind regards,
Martin
Title: Re: Arduino Zero TCC Capture
Post by: Petirrojo31 on Jan 10, 2017, 07:20 pm
Greetings Martin,

After a long week, after to read many forums and application notes from different Atmel devices, finally I could get a code for Arduino M0 board to count pulses.

I implemented it as simple as I could.

Code: [Select]
#define PinLED  13

volatile boolean Pp = true;

volatile unsigned long PulseCnt, tcc0_cnt, Period, PWidth;


void setup() {
  // put your setup code here, to run once:
 
  SerialUSB.begin(19200);                   // Send data back on the Zero's native port
  while(!SerialUSB);                        // Wait for the SerialUSB port to be ready

  pinMode(PinLED, OUTPUT);
  digitalWrite(PinLED, LOW);
 
 
  REG_PM_APBCMASK |=  PM_APBCMASK_EVSYS;    // Switch on the event system peripheral
  PM->APBCMASK.reg |= PM_APBCMASK_TCC0;     // Enable TCC0 Bus clock (Timer counter control clock)

 /*
  REG_GCLK_GENDIV = GCLK_GENDIV_DIV(1) |          // Divide the 48MHz system clock by 3 = 16MHz
                    GCLK_GENDIV_ID(2);            // Set division on Generic Clock Generator (GCLK) 2
  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 2
                     GCLK_GENCTRL_SRC_DFLL48M |   // Set the clock source to 48MHz
                     GCLK_GENCTRL_ID(2);          // Set clock source on GCLK 2
  while (GCLK->STATUS.bit.SYNCBUSY);              // Wait for synchronization
*/

  REG_GCLK_CLKCTRL = GCLK_CLKCTRL_CLKEN |         // Enable the generic clock...
                     GCLK_CLKCTRL_GEN_GCLK0 |     // .... on GCLK0...
                     GCLK_CLKCTRL_ID_EIC;         // ... to feed the GCLK0 to EIC peripheral
  while (GCLK->STATUS.bit.SYNCBUSY);              // Wait for synchronization
  SerialUSB.println("Configuración GCLK_CLKCTRL realizada para EIC");
 
  REG_GCLK_CLKCTRL = GCLK_CLKCTRL_CLKEN |         // Enable the generic clock...
                     GCLK_CLKCTRL_GEN_GCLK0 |     // ....on GCLK0...
                     GCLK_CLKCTRL_ID_TCC0_TCC1;   // ... to feed the GCLK5 to TCC0 and TCC1 peripheral
  while (GCLK->STATUS.bit.SYNCBUSY);              // Wait for synchronization
  SerialUSB.println("Configuración GCLK_CLKCTRL realizada para TCC0/TCC1");


  //PORT->Group[PORTA].DIRCLR.reg = PORT_DIRCLR_DIRCLR(1<<19);   // Set pin PA19 pin as input
  PORT->Group[PORTA].PMUX[19 >> 1].reg |= PORT_PMUX_PMUXO_A;     // Connect PA19 pin to peripheral A (EXTINT[3])
  PORT->Group[PORTA].PINCFG[19].reg |= PORT_PINCFG_PMUXEN;       // Enable pin peripheral multiplexation
  //PORT->Group[PORTA].PINCFG[19].bit.INEN = PORT_PINCFG_INEM;   // Enable input pin buffer
  //attachInterrupt(12, NULL, RISING);                           // Attach external interrupt to digital pin 12 (external interrupt 3)
  SerialUSB.println("Configuración PA19/EXTINT_03 -Pin 12- realizada");


  /* - Configuración del EIC - */
 
  REG_EIC_EVCTRL |= EIC_EVCTRL_EXTINTEO3;         // Enable event from pin on external interrupt 3 (EXTINT03)
  REG_EIC_CONFIG0 |= EIC_CONFIG_SENSE3_RISE;      // Set event on rising edge of signal
  REG_EIC_CTRL |= EIC_CTRL_ENABLE;                // Enable EIC peripheral
  while (EIC->STATUS.bit.SYNCBUSY);               // Wait for synchronization

  //REG_EIC_INTENSET = EIC_INTENSET_EXTINT3;      // External interrupt is enable
 

  /* - Configuración del EVSYS - */
 
  REG_EVSYS_USER = EVSYS_USER_CHANNEL(1) |                                // Attach the event user (receiver) to channel n=0 (n + 1)
                   EVSYS_USER_USER(EVSYS_ID_USER_TCC0_EV_0);              // Set the event user (receiver) as timer TCC0, event 1
  SerialUSB.println("Configuración EVE_SYS_USER realizada");

  REG_EVSYS_CHANNEL = EVSYS_CHANNEL_EDGSEL_NO_EVT_OUTPUT |                // No event output 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
  SerialUSB.println("Configuración EVE_SYS_CHANNEL realizada");


  /* - Configuración de TCC0 - */

  REG_TCC0_CTRLA &=~TCC_CTRLA_ENABLE;             // Disable TCC0 peripheral
  //while (TCC0->SYNCBUSY.bit.ENABLE);            // Wait for synchronization
  SerialUSB.println("Comenzando configuración de TCC0");

  REG_TCC0_CTRLBCLR |= TCC_CTRLBCLR_DIR;          // Clear DIR bit to count up
  while (TCC0->SYNCBUSY.bit.CTRLB);               // Wait for (write) synchronization
  SerialUSB.println("Configuración TCC0 CTRLB realizada");

  REG_TCC0_EVCTRL |= TCC_EVCTRL_TCEI0 |           // Enable the TCC event 0 input
                     //TCC_EVCTRL_TCEI1 |         // Enable the TCC event 1 input
                     //TCC_EVCTRL_TCINV1 |        // Invert the event 1 input
                     TCC_EVCTRL_EVACT0_COUNT;     // Set up TCC timer/counter to count on event
  SerialUSB.println("Configuración TCC0_EVCTRL realizada");
 
  REG_TCC0_CTRLA |= //TCC_CTRLA_CPTEN0 |          // Enable capture on CC0
                    //TCC_CTRLA_PRESCALER_DIV16 | // Set prescaler to 16, 16MHz/16 = 1MHz
                    TCC_CTRLA_ENABLE;             // Enable TCC0
  while (TCC0->SYNCBUSY.bit.ENABLE);              // Wait for synchronization
  SerialUSB.println("Módulo TCC0 habilitado");

}

void loop() {
  // put your main code here, to run repeatedly:

  delay(1000);  // Wait one second
 
  digitalWrite(PinLED, Pp);
  Pp = !Pp;

  REG_TCC0_CTRLBSET = TCC_CTRLBSET_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.print("TCC0 count: ");
  //SerialUSB.println(tcc0_cnt, DEC);             // Print the result
  SerialUSB.println(REG_TCC0_COUNT, DEC);         // Print the result

}


/*
void EIC_Handler()                            // Interrupt Service Routine (ISR) for External Interrupt Controller (EIC)
{
  if(EIC->INTFLAG.bit.EXTINT3)
    EIC->INTFLAG.reg = EIC_INTFLAG_EXTINT3;   // Clear interrupt flag by writing '1' on it
}
*/
Title: Re: Arduino Zero TCC Capture
Post by: Petirrojo31 on Jan 10, 2017, 07:46 pm
I would share with you and others some tips I found when I have tried to get the code works.

1.   All peripheral need a main clock to work (bus clock). In SAMD21 some peripheral have enabled this clock, but for some others is disable by default. It is mandatory to check over the Power Manager register (PM) to see which peripheral need to enable main clock. For example in this code, External Interrupt Controller (EIC) has its bus clock enable by default (PM->APAMASK.bit.EIC = 1), but Event System (EVSYS) and Timer/Counter for Control (TCCn) peripherals need to enable their main clocks (EVSYS bus clock, TCCn bus clock)

Code: [Select]
REG_PM_APBCMASK |=  PM_APBCMASK_EVSYS;    // Switch on the event system peripheral
PM->APBCMASK.reg |= PM_APBCMASK_TCC0;     // Enable TCC0 Bus clock (Timer counter control clock)


2.   Some peripherals need a generic clock to work properly (clock the peripheral). You must read the clock section in peripheral chapter from datasheet carefully to check over if peripheral needs a clock generator; but here there is a hint, as Mister Lars points in this thread (http://community.atmel.com/comment/2003776#comment-2003776), you cannot do multiple configurations "at once" in the CLKCTRL register, CLKCTRL ins not a normal register, all peripherals that need a generator clock must be configured in this register but one at time. So in this code, I need to configure a generic clock for EIC and TCC0 to work as expected like datasheet says.

Code: [Select]
REG_GCLK_CLKCTRL = GCLK_CLKCTRL_CLKEN |         // Enable the generic clock...
                   GCLK_CLKCTRL_GEN_GCLK0 |     // .... on GCLK0...
                   GCLK_CLKCTRL_ID_EIC;         // ... to feed the GCLK0 to EIC peripheral
while (GCLK->STATUS.bit.SYNCBUSY);              // Wait for synchronization
SerialUSB.println("Configuración GCLK_CLKCTRL realizada para EIC");
 
REG_GCLK_CLKCTRL = GCLK_CLKCTRL_CLKEN |         // Enable the generic clock...
                   GCLK_CLKCTRL_GEN_GCLK0 |     // ....on GCLK0...
                   GCLK_CLKCTRL_ID_TCC0_TCC1;   // ... to feed the GCLK5 to TCC0 and TCC1 peripheral
while (GCLK->STATUS.bit.SYNCBUSY);              // Wait for synchronization
SerialUSB.println("Configuración GCLK_CLKCTRL realizada para TCC0/TCC1");


I have taken Generic Clock 0 (GCLK0) because it is enabled by default. You can choose any other Generic clock generator from the nine available [0…8] as you need them whit a special or specific configuration (source, frequency, prescaler, etc.); for additional configuration you could use GENDIV, GENCTRL and other registers, "one at time" for every peripheral that need a generic clock with special cettings, or at once if a generic clock is shared among peripherals, but for assignment to each peripheral CLKCTRL register ought to be written one at time.


3.   I chose pin 12 (PA19 = PIN28 from SAMD21G) because I have already an add-on board which connect a sensor output to pin 12 in Arduino board; but pin can be any of I/O no matter which.
For pin configuration in External Interrupt Controller (EIC) peripheral, there are two ways.

I chose the long one...

Code: [Select]
//PORT->Group[PORTA].DIRCLR.reg = PORT_DIRCLR_DIRCLR(1<<19);   // Set pin PA19 pin as input
PORT->Group[PORTA].PMUX[19 >> 1].reg |= PORT_PMUX_PMUXO_A;     // Connect PA19 pin to peripheral A (EXTINT[3])
PORT->Group[PORTA].PINCFG[19].reg |= PORT_PINCFG_PMUXEN;       // Enable pin peripheral multiplexation
//PORT->Group[PORTA].PINCFG[19].bit.INEN = PORT_PINCFG_INEM;   // Enable input pin buffer
//attachInterrupt(12, NULL, RISING);                           // Attach external interrupt to digital pin 12 (external interrupt 3)
SerialUSB.println("Configuración PA19/EXTINT_03 -Pin 12- realizada");


  /* - Configuración del EIC - */
 
REG_EIC_EVCTRL |= EIC_EVCTRL_EXTINTEO3;         // Enable event from pin on external interrupt 3 (EXTINT03)
REG_EIC_CONFIG0 |= EIC_CONFIG_SENSE3_RISE;      // Set event on rising edge of signal
REG_EIC_CTRL |= EIC_CTRL_ENABLE;                // Enable EIC peripheral
while (EIC->STATUS.bit.SYNCBUSY);               // Wait for synchronization


The other way is much shorter

Code: [Select]
REG_EIC_EVCTRL |= EIC_EVCTRL_EXTINTEO3;       // Enable event from pin on external interrupt 3 (EXTINT03)
attachInterrupt(12, NULL, RISING);            // Attach external interrupt to digital pin 12 (external interrupt 3)


If you seek attachInterrupt() function code in in WInterrupts.c and wiring_private.c files in your PC (mine is in C:\Users\MyName\AppData\Local\Arduino15\packages\arduino\hardware\samd\1.6.11\cores\arduino\WInterrupts.c), you will see that function configures exactly same parameters for EIC peripherals, in the end attachInterrut is only for external interrupts.

I chose the long way because I did not want to configure any interruption to avoid implementation of any interrupt service rutine (ISR), in this way code (i think) runs faster; in fact EVSYS peripheral routes the event from pin to TCCn whitout CPU intervention (24. EVSYS - Event System - 24.1. Overview - Page 433), so this is a fast way to get an external signal into TCCn peripheral.


4.   Event System peripheral (EVSYS) is the peripheral than routes event from the source (a pin, a condition, an action or whatever be possible) to a destination (a pin, a peripheral); something I struggle to understand is event edge (EDGSEL) and path synchronity (PATH_ASYNCHRONOUS) configuration parameters in the code. I actually do not understand clearly these parameters. Something I understood, same as CLKCTRL register, if you need to configure diverse EVSYS channel you must do it "one at time".


The rest is TCC0 peripheral configuration that I found in this post and other forums, and I could implement thanks to guidance and help from Mister Martin, so I have a code for M0 to count pulses. I do not know what is the highest frequency this code can count pulses, I do not have a signal generator neither oscilloscope and I am too lazy to build a 555 oscillator to test code; I have used LED pin (pin 13 from Arduino board) to toggle every second and feed into external interrupt 3 pin (pin 12 from Arduino board).


I hope this code can help others in their projects.


Vladimir Zúñiga.
Title: Re: Arduino Zero TCC Capture
Post by: dlabun on Jan 20, 2017, 10:44 pm
I've been testing Petirrojo31 code and so far up to 1MHz it's pretty damn accurate. By 1MHz the error is within about +/-150 Hz. Nice job man!
Title: Re: Arduino Zero TCC Capture
Post by: quichedood on May 26, 2017, 01:55 am
Hi,

I'm working on a motorbike datalogger and need a 100ms interrupt.
I'm actually using this code which is working fine (thank you all !!) :

Code: [Select]
  REG_GCLK_CLKCTRL = (uint16_t) (GCLK_CLKCTRL_CLKEN | GCLK_CLKCTRL_GEN_GCLK0 | 0x1B) ;
  while (GCLK->STATUS.bit.SYNCBUSY == 1); // wait for sync

  // The type cast must fit with the selected timer mode
  TcCount16* TC = (TcCount16*) TC3; // get timer struct

  TC->CTRLA.reg &= ~TC_CTRLA_ENABLE;   // Disable TC (to enable configuration mode)
  while (TC->STATUS.bit.SYNCBUSY == 1); // wait for sync
  TC->CTRLA.reg |= TC_CTRLA_MODE_COUNT16;  // Set Timer counter Mode to 16 bits
  while (TC->STATUS.bit.SYNCBUSY == 1); // wait for sync
  TC->CTRLA.reg |= TC_CTRLA_WAVEGEN_MFRQ; // Set TC as  Match Frq
  while (TC->STATUS.bit.SYNCBUSY == 1); // wait for sync
  TC->CTRLA.reg |= TC_CTRLA_PRESCALER_DIV256;   // Set prescaler
  while (TC->STATUS.bit.SYNCBUSY == 1); // wait for sync
  TC->CC[0].reg = 18743; // 18743 OK
  while (TC->STATUS.bit.SYNCBUSY == 1); // wait for sync

  TC->INTENSET.reg = 0;              // disable all interrupts
  TC->INTENSET.bit.MC0 = 1;          // enable compare match to CC0

  // Enable InterruptVector
  NVIC_EnableIRQ(TC3_IRQn); // interrupt > TC3_Handler(){}

  TC->CTRLA.reg |= TC_CTRLA_ENABLE; // Enable TC (configuration mode finished)
  while (TC->STATUS.bit.SYNCBUSY == 1); // wait for sync


But I just saw that my compare value (18743) is not good after a 10 minutes test (I get a few ms more than the real time, if I use 18742 I get a few ms less).

So I would like to use a 32 bits counter.
If I understand well, in 32 bits mode, CC0 is a 32 bits value, so I can put more than 65535 in CC0. But I can put even 1 000 000, it seems to stop at 65535.

I only change :
Code: [Select]

TC->CTRLA.reg |= TC_CTRLA_MODE_COUNT32;
TC->CTRLA.reg |= TC_CTRLA_PRESCALER_DIV64;
TC->CC[0].reg = 256000;



Code: [Select]
  REG_GCLK_CLKCTRL = (uint16_t) (GCLK_CLKCTRL_CLKEN | GCLK_CLKCTRL_GEN_GCLK0 | 0x1B) ; // Register CLKCTRL (Page 106) / 0x1B = TCC2,TC3
  while (GCLK->STATUS.bit.SYNCBUSY == 1); // wait for sync

  // The type cast must fit with the selected timer mode
  TcCount16* TC = (TcCount16*) TC3; // get timer struct

  TC->CTRLA.reg &= ~TC_CTRLA_ENABLE;   // Disable TC (to enable configuration mode)
  while (TC->STATUS.bit.SYNCBUSY == 1); // wait for sync
  TC->CTRLA.reg |= TC_CTRLA_MODE_COUNT32;  // Set Timer counter Mode to 32 bits (Page 630)
  while (TC->STATUS.bit.SYNCBUSY == 1); // wait for sync
  TC->CTRLA.reg |= TC_CTRLA_WAVEGEN_MFRQ; // Set TC as  Match Frq
  while (TC->STATUS.bit.SYNCBUSY == 1); // wait for sync
  TC->CTRLA.reg |= TC_CTRLA_PRESCALER_DIV64;   // Set prescaler
  while (TC->STATUS.bit.SYNCBUSY == 1); // wait for sync

  // CC0 & CC1
  TC->CC[0].reg = 256000;
  while (TC->STATUS.bit.SYNCBUSY == 1); // wait for sync

  TC->INTENSET.reg = 0;              // disable all interrupts
  TC->INTENSET.bit.MC0 = 1;          // enable compare match to CC0

  // Enable InterruptVector
  NVIC_EnableIRQ(TC3_IRQn); // interrupt > TC3_Handler(){}

  TC->CTRLA.reg |= TC_CTRLA_ENABLE; // Enable TC (configuration mode finished)
  while (TC->STATUS.bit.SYNCBUSY == 1); // wait for sync


Am I missing something ?
Title: Re: Arduino Zero TCC Capture
Post by: MartinL on May 26, 2017, 10:08 am
Hi quichedood,

The TC timers are 16-bits, however it's possible to pair them in 32-bit mode.

On the SAMD21G you've only got the option to pair TC3 and TC4. To configure the paired 32-bit counter you have to access it through the registers of the even numbered timer counter, in this case TC4.

Here's the information from the SAMD21 datasheet:

Quote
COUNT32: This mode is achieved by pairing two 16-bit TC peripherals. TC3 is paired with TC4,
and TC5 is paired with TC6. TC7 does not support 32-bit resolution.
Quote
When paired, the TC peripherals are configured using the registers of the even-numbered TC (TC4 or
TC6 respectively). The odd-numbered partner (TC3 or TC5 respectively) will act as slave, and the Slave
bit in the Status register (STATUS.SLAVE) will be set. The register values of a slave will not reflect the
registers of the 32-bit counter. Writing to any of the slave registers will not affect the 32-bit counter.
Normal access to the slave COUNT and CCx registers is not allowed.
Title: Re: Arduino Zero TCC Capture
Post by: quichedood on May 26, 2017, 12:52 pm
Damn you're right, thanks for your help !
I download last datasheet version (Microchip) and found the information.

I double checked every line in my code but can't figure it out.
I made some modifications of course :

Set clock on the TC4 timer
Code: [Select]
 // 32 bits counter (TC3 + TC4 16 bits counters / TC4 is "master")
  REG_GCLK_CLKCTRL = (uint16_t) (GCLK_CLKCTRL_CLKEN | GCLK_CLKCTRL_GEN_GCLK0 | GCLK_CLKCTRL_ID_TC4_TC5) ;
  while (GCLK->STATUS.bit.SYNCBUSY == 1); // wait for sync


Use TC4 (TC3 will be used as a slave)
Code: [Select]
TcCount16* TC = (TcCount16*) TC4; // get timer struct

Change interrupt vector
Code: [Select]
NVIC_EnableIRQ(TC4_IRQn);

Code: [Select]
void TC4_Handler() {
  TcCount16* TC = (TcCount16*) TC4; // get timer struct
  TC->INTFLAG.bit.MC0 = 1; // writing a one clears the ovf flag of MC0 (P.624)
[...]


The value I set in CC register doesn't seem to be saved as a 32 bits integer.
Interrupts go faster when I set CC to 65535 compare to 1000000

Anyway, i keeping reading this datasheet, if you have any idea, you're welcome :)


The whole code :
Code: [Select]
// 32 bits counter (TC3 + TC4 16 bits counters / TC4 is "master")
  REG_GCLK_CLKCTRL = (uint16_t) (GCLK_CLKCTRL_CLKEN | GCLK_CLKCTRL_GEN_GCLK0 | GCLK_CLKCTRL_ID_TC4_TC5) ; // Register CLKCTRL (Page 119) / 0x1C = TC4+TC5
  while (GCLK->STATUS.bit.SYNCBUSY == 1); // wait for sync
  //REG_GCLK_CLKCTRL = (uint16_t) (GCLK_CLKCTRL_CLKEN | GCLK_CLKCTRL_GEN_GCLK1 | GCLK_CLKCTRL_ID_TCC2_TC3) ; // Register CLKCTRL (Page 119) / 0x1B = TCC2+TC3
  //while (GCLK->STATUS.bit.SYNCBUSY == 1); // wait for sync

  // The type cast must fit with the selected timer mode
  TcCount16* TC = (TcCount16*) TC4; // get timer struct

  TC->CTRLA.reg &= ~TC_CTRLA_ENABLE;   // Disable TC (to enable configuration mode)
  while (TC->STATUS.bit.SYNCBUSY == 1); // wait for sync
  TC->CTRLA.reg |= TC_CTRLA_MODE_COUNT32;  // Set Timer counter Mode to 32 bits (Page 552)
  while (TC->STATUS.bit.SYNCBUSY == 1); // wait for sync
  TC->CTRLA.reg |= TC_CTRLA_WAVEGEN_MFRQ; // Set TC as  Match Frq
  while (TC->STATUS.bit.SYNCBUSY == 1); // wait for sync
  TC->CTRLA.reg |= TC_CTRLA_PRESCALER_DIV256;   // Set prescaler
  while (TC->STATUS.bit.SYNCBUSY == 1); // wait for sync

  // CC0
  TC->CC[0].reg = 655350; // 18743 OK
  while (TC->STATUS.bit.SYNCBUSY == 1); // wait for sync

  TC->INTENSET.reg = 0;              // disable all interrupts
  TC->INTENSET.bit.MC0 = 1;          // enable compare match to CC0 (P. 622)

  // Enable InterruptVector
  NVIC_EnableIRQ(TC4_IRQn); // interrupt > TC4_Handler(){}

  TC->CTRLA.reg |= TC_CTRLA_ENABLE; // Enable TC (configuration mode finished)
  while (TC->STATUS.bit.SYNCBUSY == 1); // wait for sync
Title: Re: Arduino Zero TCC Capture
Post by: quichedood on May 26, 2017, 02:19 pm
Think I just found, timer struct is 32 bits not 16 !
Code: [Select]
TcCount32* TC = (TcCount32*) TC4;

I confirm this in a few minutes/hours ;)
Title: Re: Arduino Zero TCC Capture
Post by: quichedood on May 26, 2017, 03:46 pm
Everything look fine, here is code :

Code: [Select]
  /*
      First timer (TC4)
      a 100ms interrupt
      Prescaler = 1, Preload = 4798000, Source clock = 48000000
      Counter on 32 bits > 2x 16 bits counters : TC3 (slave) and TC4 (master)
  */
  REG_GCLK_CLKCTRL = (uint16_t) (GCLK_CLKCTRL_CLKEN | GCLK_CLKCTRL_GEN_GCLK0 | GCLK_CLKCTRL_ID_TC4_TC5) ; // Register CLKCTRL (Page 119)
  while (GCLK->STATUS.bit.SYNCBUSY == 1); // wait for sync

  // The type cast must fit with the selected timer mode
  TcCount32* TC = (TcCount32*) TC4; // get timer struct

  TC->CTRLA.reg &= ~TC_CTRLA_ENABLE;   // Disable TC (to enable configuration mode)
  while (TC->STATUS.bit.SYNCBUSY == 1); // wait for sync
  TC->CTRLA.reg |= TC_CTRLA_MODE_COUNT32;  // Set Timer counter Mode to 32 bits (Page 552)
  while (TC->STATUS.bit.SYNCBUSY == 1); // wait for sync
  TC->CTRLA.reg |= TC_CTRLA_WAVEGEN_MFRQ; // Set TC as  Match Frq
  while (TC->STATUS.bit.SYNCBUSY == 1); // wait for sync
  TC->CTRLA.reg |= TC_CTRLA_PRESCALER_DIV1;   // Set prescaler (Page 551)
  while (TC->STATUS.bit.SYNCBUSY == 1); // wait for sync

  // CC0 Register (preload)
  TC->CC[0].reg = 4798000;
  while (TC->STATUS.bit.SYNCBUSY == 1); // wait for sync

  TC->INTENSET.reg = 0;              // disable all interrupts
  TC->INTENSET.bit.MC0 = 1;          // enable compare match to CC0 (P. 622)

  // Enable InterruptVector
  NVIC_EnableIRQ(TC4_IRQn); // interrupt > TC4_Handler(){}

  TC->CTRLA.reg |= TC_CTRLA_ENABLE; // Enable TC (configuration mode finished)
  while (TC->STATUS.bit.SYNCBUSY == 1); // wait for sync


Code: [Select]
void TC4_Handler() {
  TcCount32* TC = (TcCount32*) TC4; // get timer struct
  TC->INTFLAG.bit.MC0 = 1; // writing a one clears the ovf flag of MC0 (P.624)
[...]


The last thing, I want a 100msec interrupt, but setting the preload to 4 800 000 makes the counter a little bit slower than real time.
Is there a way to caculate the exact preload to use in my case ?
Title: Re: Arduino Zero TCC Capture
Post by: MartinL on May 26, 2017, 05:47 pm
Hi quichedood,

The issue here is that the Zero's 48MHz clock is an integer multiple of its external 32.768kHz crystal.

Forum member Manitou48 calculated the crystal and resonator accuracy for quite a number of development boards: https://github.com/manitou48/crystals/blob/master/crystals.txt (https://github.com/manitou48/crystals/blob/master/crystals.txt)

So the frequency you're actually getting is 32768Hz * 1464 = 47972352Hz.

This means the 48MHz clock is 576µs off every second.

Title: Re: Arduino Zero TCC Capture
Post by: quichedood on May 26, 2017, 06:08 pm
Very interesting, but even with 4797235 (1464*32768/10) in CC I see a drift after 10 minutes.
I'll create a new dedicated topic to keep this thread clean ;)

EDIT : here it is http://forum.arduino.cc/index.php?topic=479806.0 (http://forum.arduino.cc/index.php?topic=479806.0)
Title: Re: Arduino Zero TCC Capture
Post by: borispavlov on May 27, 2017, 04:04 am
Hi All
Thanks a lot to all contributors to this thread and mostly MartinL.

I am trying to use "STOP" command with TCC0 on Arduino Zero.
My understanding from the data sheet is that the counter should only stop counting but not cleared after the "STOP" command is issued.

I copied MartinL (thanks) code from post 34 that reads the counter and displays it.
My modification is that i use Serial instead of SerialUSB and do add the "STOP" command.

The code works and reads 3 TCC0 counter values. One is before the STOP command and 2 after the STOP command.

I am expecting to see the same value in the counter after the STOP but for some reason it is always "0" after the STOP as if the STOP command also clears the counter.

How can i read the counter after i stop it before it is cleared?

Code is attached

Title: Re: Arduino Zero TCC Capture
Post by: MartinL on May 27, 2017, 10:37 am
Hi borispavlov,

Once you've stopped the TCC0 timer, you don't need to use READSYNC anymore.

I've just deleted the READSYNC lines in the code:

Code: [Select]
// Setup TCC0 counter and display the COUNT register continuously in the console
void setup()
{
  Serial.begin(115200);
  while(!Serial);
  
  REG_GCLK_GENDIV = GCLK_GENDIV_DIV(1) |          // Divide the 48MHz system clock by 1 = 48MHz
                    GCLK_GENDIV_ID(4);            // Set division on Generic Clock Generator (GCLK) 4
  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 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

  REG_GCLK_CLKCTRL = GCLK_CLKCTRL_CLKEN |         // Enable the generic clock...
                     GCLK_CLKCTRL_GEN_GCLK4 |     // ....on GCLK4
                     GCLK_CLKCTRL_ID_TCC0_TCC1;   // Feed the GCLK4 to TCC0 and TCC1
  while (GCLK->STATUS.bit.SYNCBUSY);              // Wait for synchronization
  
  REG_TCC0_CTRLA |= TCC_CTRLA_PRESCALER_DIV1 |    // Set prescaler to 1, 48MHz/1 = 48Mhz
                    TCC_CTRLA_ENABLE;             // Enable TCC0
  while (TCC0->SYNCBUSY.bit.ENABLE);              // Wait for synchronization
}

void loop()
{
delay(5000);  //long delay to allow to open Serial port if not open already
  
  REG_TCC0_CTRLBSET = TCC_CTRLBSET_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
  Serial.print("TCC0_1: ");
  Serial.println(REG_TCC0_COUNT, HEX);         // Print the result
  //REG_TCC0_COUNT = 0x5;                           // Set the COUNT register to 5
  //while (TCC0->SYNCBUSY.bit.COUNT);               // Wait for the COUNT register write sychronization

delay(10);

  REG_TCC0_CTRLBSET = TCC_CTRLBSET_CMD_STOP;  // Trigger a STOP 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

  //REG_TCC0_CTRLBSET = TCC_CTRLBSET_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
  Serial.print("TCC0_2: ");
  Serial.println(REG_TCC0_COUNT, HEX);         // Print the result

delay(10);

  //REG_TCC0_CTRLBSET = TCC_CTRLBSET_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
  Serial.print("TCC0_3: ");
  Serial.println(REG_TCC0_COUNT, HEX);         // Print the result

  delay(6000000);       //very long delay
}

The program now outputs:

TCC0_1: 4E0DD0
TCC0_2: 4E0DD0
TCC0_3: 4E0DD0

Title: Re: Arduino Zero TCC Capture
Post by: borispavlov on May 27, 2017, 12:03 pm
Thanks MartinL for the prompt response. The code is working now but i do not understand completely.

Can you explain why all 3 TCC0 values are identical?
I would expect second and third to be identical but different from the first.

After the first value TCC0_1 is obtained, then there is "delay(10)" during which the counter should be still counting up before the STOP command. Values TCC0_2 and TCC0_3 are obtained after the STOP command.
and should be identical, but different than value TCC0_1.

 
Title: Re: Arduino Zero TCC Capture
Post by: MartinL on May 27, 2017, 06:32 pm
Hi borispavlov,

Quote
Can you explain why all 3 TCC0 values are identical?
I've checked the SAMD21 datasheet, but unfortunately it doesn't explain the TCC0's behaviour.

As you've discovered, it appears as though it's not possible to use READSYNC to read the TCC0's COUNT register once the timer's been stopped.

The only alternative I found was to read the TCC0's COUNT register using a READSYNC request, before subsequently issuing a STOP command.

Title: Re: Arduino Zero TCC Capture
Post by: borispavlov on May 27, 2017, 10:53 pm
Hi MartinL,

I appears that READSYNC does stop the timer, but not the STOP command.

I removed the STOP command from the code and i still get 3 identical values.

What is even more confusing is that if the loop is executed again these 3 values do change.
Looks like the counter is stopped by the READSYNC command while inside the loop and starts counting after it exits the loop and then it stopped again by the "READSYNC" after re-entering the loop.

Is this possible?

Code with removed "STOP" command included.

 
Title: Re: Arduino Zero TCC Capture
Post by: MartinL on May 27, 2017, 11:59 pm
Hi borispavlov,

Quote
I appears that READSYNC does stop the timer, but not the STOP command.
Going back to post #34 of this thread. The code there uses READSYNC to sucessfully read the timer's COUNT register. So using READSYNC works when the timer is running.

The strange behaviour is occuring when the timer is stopped. I think what I said about commenting out READSYNC, in retrospect is wrong. The weird thing is if you include the READSYNC code and the timer's stopped, the COUNT register returns zero.

Nomally if you slow down the GCLK to the timer peripheral, it slows down the whole timer module, including the speed of register access and synchronization. Maybe stopping the timer switches off the GCLK to the COUNT register, but this also prevents it from being read?

Or perhaps there is a way? We just haven't found it yet.

Title: Re: Arduino Zero TCC Capture
Post by: borispavlov on May 28, 2017, 03:45 am
Hi MartinL,

Thanks a lot for spending time on this issue.

Here is what i found regarding the STOP command:

""
static void tcc_stop_counter   (   const struct tcc_module *const    module_inst   )   
inline  static
Stops the counter.
This function will stop the counter. When the counter is stopped the value in the count register is set to 0 if the counter was counting up, or maximum or the top value if the counter was counting down.

""

This is from this website regarding SAMD21 and is at the very end of the website page:

" " "
http://asf.atmel.com/docs/latest/samr21/html/group__asfdoc__sam0__tcc__group.html#gab600907da31e721cef4eb32d2b8afccf

" " "

So the "0" values after the "STOP" command is expected and normal.





And here is what i found regarding the "READSYNC" command from the same website:


" " "
uint32_t tcc_get_count_value   (   const struct tcc_module *const    module_inst   )   
Get count value of the given TCC module.
Retrieves the current count value of a TCC module. The specified TCC module can remain running or stopped.

" " "

So a very smart Atmel engineer wrote that when using "READSYNC" the counter  can remain running or stopped without providing more information.

So the identical values after the "READSYNC" are expected but only in some so far unknown register pre-settings. 

Well "can" does not clarify this and i would like to know under what setting of what register  "READSYNC" bit will cause the counter to remain in "stopped" and under what condition it will be running.

May be you will have better better luck and figure this out.


The above info can be found on the website by searching (Ctrl-F) for "tcc_get_count" and " tcc_stop_counter".




Title: Re: Arduino Zero TCC Capture
Post by: MartinL on May 29, 2017, 12:32 pm
Hi borispavlov,

Thanks for the link to the ASF (Atmel Software Framework) functions.

Looking at the source code for the "tcc_stop_counter" function:

Code: [Select]
/**
 * \brief Stops the counter
 *
 * This function will stop the counter. When the counter is stopped
 * the value in the count register is set to 0 if the counter was
 * counting up, or maximum or the top value if the counter was counting
 * down.
 *
 * \param[in]  module_inst   Pointer to the software module instance struct
 */
static inline void tcc_stop_counter(
    const struct tcc_module *const module_inst)
{
    /* Sanity check arguments */
    Assert(module_inst);
    Assert(module_inst->hw);

    /* Get a pointer to the module's hardware instance */
    Tcc *const tcc_module = module_inst->hw;
    uint32_t last_cmd;

    /* Wait until last command is done */
    do {
        while (tcc_module->SYNCBUSY.bit.CTRLB) {
            /* Wait for sync */
        }
        last_cmd = tcc_module->CTRLBSET.reg & TCC_CTRLBSET_CMD_Msk;
        if (last_cmd == TCC_CTRLBSET_CMD_NONE) {
            break;
        } else if (last_cmd == TCC_CTRLBSET_CMD_STOP) {
            /* Command have been issued */
            return;
        } else if (last_cmd == TCC_CTRLBSET_CMD_RETRIGGER) {
            /* Cancel RETRIGGER command and issue STOP */
            tcc_module->CTRLBCLR.reg = TCC_CTRLBCLR_CMD_Msk;
        }
    } while (1);

    /* Write command to execute */
    tcc_module->CTRLBSET.reg = TCC_CTRLBSET_CMD_STOP;
}

There's nothing special going on here, other than waiting for the CMD (command) bitfield to clear to 0 before proceeding with the STOP command.

I tested this code and sure enough the TCC0's COUNT register returns 0 when the timer's stopped.

However, this behaviour contradicts the SAMD21 datasheet that states (in section TCC Timer Stop Command and Event Action):

Quote
When a stop is detected while the counter is running, the counter will maintain its current value.
The datasheet also goes on to say that (in section TCC Timer Retrigger Command and Event Action):

Quote
If the re-trigger command is detected when the counter is stopped, the counter will
resume counting operation from the value in COUNT.
I've tested retriggering after stopping the timer, but contrary to the datasheet the output shows that the timer's actually been reset.

The discrepancy doesn't appear in the datasheet's errata either.

If you need to read the stopped timer's counter, then all I can suggest is using one of the TC peripherals instead.

Title: Re: Arduino Zero TCC Capture
Post by: dlabun on May 29, 2017, 11:50 pm
Martin,

Looping back to accuracy for a moment, would using an external 24Mhz crystal oscillator improve the accuracy of the internal clocks?
Title: Re: Arduino Zero TCC Capture
Post by: MartinL on May 30, 2017, 12:43 pm
Hi dlabun,

Quote
Looping back to accuracy for a moment, would using an external 24Mhz crystal oscillator improve the accuracy of the internal clocks?
I don't know enough about the SAMD21's SYSCTRL - System Controller module that configures the crystal and the digital frequency locked loop.

I imagine it should be possible to use a crystal frequency that's factor of 48MHz, such as an 8 or 12MHz to get a more precise 48MHz. However, you'd have to change the start-up sequence code and most likely the bootloader to make it compatible with Arduino.

Also, it's not possible to simply swap out the current crystal on the Arduino Zero, as any external multipurpose crystal (not 32.768kHz) has to connect to XIN and XOUT on port pins PA14 and PA15, rather than XIN32 and XOUT32 on PA00 and PA01.
Title: Re: Arduino Zero TCC Capture
Post by: dlabun on May 30, 2017, 06:24 pm
Hi Martin,

I was thinking more for people who design custom boards based on the SAMD21... I have a custom board in the design process right now and I am contemplating using an I2C adjustable XO from SiLabs. Could prove useful to the people that really want to customize the clocks on the SAMD21.
Title: Re: Arduino Zero TCC Capture
Post by: athtest on Oct 03, 2017, 05:36 pm
Thanks for the code.
I am trying to count pulses with timer while sleeping (RTCZero -> standby). Is that possible?
Right now it is working fine while M0 is ON. The problem is that it does not count while sleeping.
Is there solution?
Title: Re: Arduino Zero TCC Capture
Post by: DR49 on Feb 14, 2018, 10:51 pm
Hi MartinL

Several months ago you gave me very good inputs to manage my frequency generation based on my M0Pro and TCC timers. It's working very well and the "transmissioin" part of my project is now completed !

For the reception part I need a frequency-detector (-meter !) working around 40 kHz sine wave input signal. This signal is an FSK2, made with 2 different frequencies aroun 40 kHz, then I need to know which frequency is present at a time, not more !

Each burst is lasting 30 ms.

I wondered if your sketch above (pulse period / pulse count) could be use to get an accurate frequency meter ?

Could you help to provide me the relevant code modifications to get such function ?

Many thanks

DR49
Title: Re: Arduino Zero TCC Capture
Post by: MartinL on Feb 17, 2018, 10:24 am
Hi DR49,

Here's an example that uses the TCC0 timer on digital pin D12 that should work at 40kHz:

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

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(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

  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_TCC0_TCC1;   // Feed the GCLK5 to TCC0 and TCC1
  while (GCLK->STATUS.bit.SYNCBUSY);              // Wait for synchronization

  REG_EIC_EVCTRL |= EIC_EVCTRL_EXTINTEO3;                                     // Enable event output on external interrupt 3
  attachInterrupt(12, callback, 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_TCC0_EV_1);              // Set the event user (receiver) as timer TCC0, event 1

  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_TCC0_EVCTRL |= TCC_EVCTRL_MCEI1 |           // Enable the match or capture channel 1 event input
                     TCC_EVCTRL_MCEI0 |           //.Enable the match or capture channel 0 event input
                     TCC_EVCTRL_TCEI1 |           // Enable the TCC event 1 input
                     /*TCC_EVCTRL_TCINV1 |*/      // Invert the event 1 input         
                     TCC_EVCTRL_EVACT1_PPW;       // Set up the timer for capture: CC0 period, CC1 pulsewidth
                                       
  //NVIC_DisableIRQ(TCC0_IRQn);
  //NVIC_ClearPendingIRQ(TCC0_IRQn);
  NVIC_SetPriority(TCC0_IRQn, 0);      // Set the Nested Vector Interrupt Controller (NVIC) priority for TCC0 to 0 (highest)
  NVIC_EnableIRQ(TCC0_IRQn);           // Connect the TCC0 timer to the Nested Vector Interrupt Controller (NVIC)
 
  REG_TCC0_INTENSET = TCC_INTENSET_MC1 |            // Enable compare channel 1 (CC1) interrupts
                      TCC_INTENSET_MC0;             // Enable compare channel 0 (CC0) interrupts
 
  REG_TCC0_CTRLA |= TCC_CTRLA_CPTEN1 |              // Enable capture on CC1
                    TCC_CTRLA_CPTEN0 |              // Enable capture on CC0
                    TCC_CTRLA_PRESCALER_DIV1 |      // Set prescaler to 1, 48MHz/1 = 48MHz
                    TCC_CTRLA_ENABLE;               // Enable TCC0
  while (TCC0->SYNCBUSY.bit.ENABLE);                // 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;
    count = isrCount;
    interrupts();
    SerialUSB.print(period);                      // Output the results
    SerialUSB.print(F("   "));
    SerialUSB.print(pulsewidth);
    SerialUSB.print(F("   "));
    SerialUSB.println(count);
    periodComplete = false;                       // Start a new period
  }
}

void TCC0_Handler()                              // Interrupt Service Routine (ISR) for timer TCC0
{     
  // Check for match counter 0 (MC0) interrupt
  if (TCC0->INTFLAG.bit.MC0)             
  {   
    isrPeriod = REG_TCC0_CC0;                   // Copy the period
    periodComplete = true;                       // Indicate that the period is complete
  }

  // Check for match counter 1 (MC1) interrupt
  if (TCC0->INTFLAG.bit.MC1)           
  {
    isrPulsewidth = REG_TCC0_CC1;               // Copy the pulse-width
    isrCount++;
  }
}

void callback(){}                               // Dummy callback function

I've set the generic clock divisor and the timer prescaler to 1, so that the timer runs at 48MHz.
Title: Re: Arduino Zero TCC Capture
Post by: MartinL on Feb 17, 2018, 10:28 am
Here's another example that uses the synchronous event path, that triggers on the interrupt rising edge rather than the HIGH level:

Code: [Select]
// Setup TCC0 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(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

  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_TCC0_TCC1;   // Feed the GCLK5 to TCC0 and TCC1
  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_EVSYS_0;     // Feed the Event Sys channel 0
  while (GCLK->STATUS.bit.SYNCBUSY);              // Wait for synchronization

  REG_EIC_EVCTRL |= EIC_EVCTRL_EXTINTEO3;                                 // Enable event output on external interrupt 3
  attachInterrupt(12, callback, RISING);                                      // 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_TCC0_EV_1);              // Set the event user (receiver) as timer TCC0, event 1

  /*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_EVSYS_CHANNEL = EVSYS_CHANNEL_EDGSEL_RISING_EDGE |                  // Rising event edge detection
                      EVSYS_CHANNEL_PATH_SYNCHRONOUS |                    // Set event path as synchronous
                      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_TCC0_EVCTRL |= TCC_EVCTRL_MCEI1 |           // Enable the match or capture channel 1 event input
                     TCC_EVCTRL_MCEI0 |           //.Enable the match or capture channel 0 event input
                     TCC_EVCTRL_TCEI1 |           // Enable the TCC event 1 input
                     /*TCC_EVCTRL_TCINV1 |*/      // Invert the event 1 input         
                     TCC_EVCTRL_EVACT1_PPW;       // Set up the timer for capture: CC0 period, CC1 pulsewidth
                                       
  NVIC_SetPriority(TCC0_IRQn, 0);      // Set the Nested Vector Interrupt Controller (NVIC) priority for TCC0 to 0 (highest)
  NVIC_EnableIRQ(TCC0_IRQn);           // Connect the TCC0 timer to the Nested Vector Interrupt Controller (NVIC)
 
  REG_TCC0_INTENSET = TCC_INTENSET_MC1 |            // Enable compare channel 1 (CC1) interrupts
                      TCC_INTENSET_MC0;             // Enable compare channel 0 (CC0) interrupts
 
  REG_TCC0_CTRLA |= TCC_CTRLA_CPTEN1 |              // Enable capture on CC1
                    TCC_CTRLA_CPTEN0 |              // Enable capture on CC0
                    TCC_CTRLA_PRESCALER_DIV1 |      // Set prescaler to 1, 48MHz/1 = 48MHz
                    TCC_CTRLA_ENABLE;               // Enable TCC0
  while (TCC0->SYNCBUSY.bit.ENABLE);                // 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 TCC0_Handler()                              // Interrupt Service Routine (ISR) for timer TCC0
{     
  // Check for match counter 0 (MC0) interrupt
  if (TCC0->INTFLAG.bit.MC0)             
  {   
    isrPeriod = REG_TCC0_CC0;                   // Copy the period
    periodComplete = true;                       // Indicate that the period is complete
  }

  // Check for match counter 1 (MC1) interrupt
  if (TCC0->INTFLAG.bit.MC1)           
  {
    isrPulsewidth = REG_TCC0_CC1;               // Copy the pulse-width
  }
}

void callback(){}                               // Dummy callback function
Title: Re: Arduino Zero TCC Capture
Post by: DR49 on Feb 17, 2018, 12:01 pm
Hi Martin L

Many thanks for this. I'll try to run it on my M0PRO.

W'll tell you about the results.


Yesterday I have tested this previous code from this thread (See attachment), unfortunately it doesn't work (no serial monitor output) with different inputs pulses on D12 like : 40 kHz or 5 kHz / pulse width 20 µs and 3,3V max
It seems the interrupt is not triggered by the signal....
A serial print in the TCC Handler never happened on the monitor...

I wonder if it is necessary to pull up / down Pin 12 with a resistor ?

I also not really understand the AttachInterrupt "NULL" function ? Could you clarify (for me) !

I come back with results of your last post,

Thanks again

Daniel
Title: Re: Arduino Zero TCC Capture
Post by: DR49 on Feb 17, 2018, 05:25 pm
Hi MartinL

The first code (HIGH) is not working even with a signal as low as 10 kHz / 3,3 V cc / 35 µS pulse width....

I didn't test the second one (RIZING) but will try later.

For the first code I think that the interrupt is not taken into account. Periodcomplete and TCC handler never passed...

I recall my devices and sofware I'm using :
M0PRO
IDE 1.8.5 from arduino.cc
I follow your indications of our last thread 2016 (See attachement)

Is there anything missing ?

Kind Regards

Daniel
 
Title: Re: Arduino Zero TCC Capture
Post by: MartinL on Feb 17, 2018, 05:32 pm
Hi Daniel,

I think the TC3_capture_pulseW_and_periode.ino file you attached isn't working, because the you're using the Serial port at a baud rate of 9600bps.

I just tested the first TCC0 example above with my Arduino Due providing the 40kHz PWM input signal. The Zero is able to read and output to the console, both the signal's pulse width and period. I'm using the Zero's native USB port: SerialUSB.

The reason why the attachInterrupt() function is using NULL as an argument, is that we're interested in setting up the D12 pin as an interrupt, so that it can trigger an event on the event channel. However, in this instance we don't need to make use of the interrupt's callback function, so instead of inserting the callback function's name, we instead insert a NULL or 0.

In the "WInterrupts.cpp" file, where the code for the attachInterrupt() function is located, you'll find the following comment and that the code tests to see that the callback function isn't 0 (NULL):

Code: [Select]
// Only store when there is really an ISR to call.
  // This allow for calling attachInterrupt(pin, NULL, mode), we set up all needed register
  // but won't service the interrupt, this way we also don't need to check it inside the ISR.
  if (callback)
  {

Kind regards,
Martin
Title: Re: Arduino Zero TCC Capture
Post by: MartinL on Feb 17, 2018, 05:40 pm
At 40kHz, 50% duty cycle, the example should be reading 1200 for the period, as:

48MHz / 1200 = 40kHz

and 600 for the pulse width, as:

600 / 1200 * 100 = 50% duty cycle
Title: Re: Arduino Zero TCC Capture
Post by: DR49 on Feb 17, 2018, 06:39 pm
Thanks Martin

Why the Serial port at a baud rate of 9600bps should'nt works ?

What is the advantage to use the USB serial link rather than the serial port ?

Ok for the NULL argument, noted !

I join the attachement in .pdf where you explained to me how to proceed to match M0PRO with Zero , including also two pictures showing my signal input of today.

The blue trace (max 3.3 V) is the one I apply on Pin 12 (from a 5 Vcc at the input of a translation circuit in 3.3 V in order to protect the input of the M0PRO.

Regards

Daniel
Title: Re: Arduino Zero TCC Capture
Post by: MartinL on Feb 17, 2018, 07:24 pm
Hi Daniel,

Quote
Why the Serial port at a baud rate of 9600bps should'nt works ?

What is the advantage to use the USB serial link rather than the serial port ?
It's because the SerialUSB port is capable of running at a high data rate. At 40kHz the TCC0 interrupt service routine is being called 40000 times a second, this amount of data is simply too much for the Serial port running at 9600bps to handle.

If you connect your M0 Pro up to the USB native port, upload the code in message #78 of this thread, hook up the signal to digtial pin D12 then open the console window, you should start to receive the pulse width and period data.

Kind regards,
Martin
Title: Re: Arduino Zero TCC Capture
Post by: DR49 on Feb 18, 2018, 03:02 pm
Hi Martin

After a number of tests, still nothing appears on Serial USB munitor.

The following has being tested without success (ATMEL EDBG installed on IDE 1.8.5):

M0 PRO with Genuino zero bootloader / Genuino Native Port Serial USB 115200 / Board Genuino Zero (Native port)

Idem with :                                   / M0PRO Native Port serial USB  115200 /  Board Genuino Zero (Programming port)
                               
M0PRO with M0PRO bootloader / M0PRO Native Port serial USB  115200 /  Board M0PRO (Native port)

Idem with :                          M0PRO Native Port serial USB  115200 /  Board M0PRO (Programming port)


When I put a Serial USB print "xx" in the Void setup, all configs provide the text on the USB monitor.


The signal input on Pin 12 is 40 kHz _ 12.5 µs pulse width (50 / 50 %)

I try what you recommended once to feed GCLK4 to TCC0 & TCC1, despite I got now the 1.8.5 IDE release :

// Feed GCLK4 to TCC0 and TCC1
  GCLK->CLKCTRL.reg = 0x441A;                      // Modified for compatibility IDE 1.7.11 MartinL forum Arduino.cc (https://forum.arduino.cc/index.php?topic=346731.15)

without result, but is it compatible with GCLK 5 used in the TCC sketch above ?

Thanks for help !

Regards

Daniel









       
Title: Re: Arduino Zero TCC Capture
Post by: MartinL on Feb 18, 2018, 06:29 pm
Hi Daniel,

I tested the code with an older build of the Arduino Zero core code and this worked fine. However, testing with the latest Arduino Zero core, as you mention doesn't work.

I've investigated and it appears that Arduino have recently made code changes to the attachInterrupt() that breaks some of the TCCCapture code examples on this thread.

This is because the new attachInterrupt() code now tests for a NULL pointer and bypasses the pin set-up if it finds it. This prevents the attachInterrupt() from being used to set-up events without a callback function.

I'm going to take this up with Arduino developers on Github. In the meantime, the easiest workaround is to enter a dummy callback function that does nothing.

I've updated the example code in #78 above.

Sorry about that, should've tested on the latest Arduino Zero core code to begin with. The code should finally work now.

Kind regards,
Martin
Title: Re: Arduino Zero TCC Capture
Post by: MartinL on Feb 18, 2018, 06:58 pm
I've now raised the issue with Arduino developers on Github: https://github.com/arduino/ArduinoCore-samd/issues/309 (https://github.com/arduino/ArduinoCore-samd/issues/309).
Title: Re: Arduino Zero TCC Capture
Post by: DR49 on Feb 18, 2018, 08:28 pm
Hi Martin !

We did work together to finally make it working !!
I explain :

You have found the pb thanks to your fine analysis of last changes for this crazy AttachInterrupt function / You are great as usual !!!

On my side I put in my code some recommandations given  in this # 56 thread and FORTUNATELY it mentionned another way to write the AttachInt !!! replacing it by a number of lignes...

Since I feared the Int was not taken into account, I decided to choose and put this code !

AND IT BEGAN TO WORK !!!!

I put in attachement, just for info,  the sketch which is working now both with Zero or M0 bootloader...

You will notice that I also add some lines (I put *** in comment to point it out), in particular the line after the one enabling event :
PM->APBCMASK.reg |= PM_APBCMASK_TCC0;     // Enable TCC0 Bus clock (Timer counter control clock) 
And because I am not used with serialUSB, I came back to serial Prog....

I will now test YOUR updated code (# 78), even with USB port (!!) and w'll be back to you for results...


Many thanks again

Kind regards

Daniel
Title: Re: Arduino Zero TCC Capture
Post by: MartinL on Feb 18, 2018, 08:47 pm
Hi Daniel,

Glad you got it working. The method in thead #58 that you used in your code looks easier and more efficient than calling the attachInterrupt(), (with or without a dummy callback function).

Thanks for pointing this out.

Kind regards,
Martin
Title: Re: Arduino Zero TCC Capture
Post by: DR49 on Feb 18, 2018, 10:17 pm
Hi Martin

I can confirm that the last code posted works very well on my M0PRO, including serialUSB !


Now I need to go on with my project, then I have some basic questions :

First, I do need to run my Tx and Rx on the same board.

For Tx I am using your PWM code, which generate 40 kHz on Pin 7 thanks to timers based on GCLK4 clock , TCC0 and TCC1 etc...

For Rx (our last exchanges above), I use Pin 12 and attached interuption, and timers based on GCLK5 , TCC0 and TCC1 etc...

Did I well understand that both timers / interruptions can work at the same time ?

How to STOP / START timers individualy ?

For the external interruption linked to Pin 12, can we use "Nointerrupts() / Interrupts() instructions to stop it ? I mean, does other interruptions in the system will be affected ? and if yes how to manage it selectively ?


Thanks

Daniel




Title: Re: Arduino Zero TCC Capture
Post by: MartinL on Feb 19, 2018, 09:43 am
Hi Daniel,

Quote
First, I do need to run my Tx and Rx on the same board.
That depends on what you're planning to do for your transmitter, but the PWM runs independently of the CPU, so isn't processor intensive. The PWM duty-cycle/frequency can be changed by just loading a register.

Quote
For Tx I am using your PWM code, which generate 40 kHz on Pin 7 thanks to timers based on GCLK4 clock , TCC0 and TCC1 etc...

For Rx (our last exchanges above), I use Pin 12 and attached interuption, and timers based on GCLK5 , TCC0 and TCC1 etc...
The TCC0 and TCC1 timers share the same generic clock (GCLK), but have separate timer prescalers. As you're running the timers at high speed the generic clock will be set to the maximum of 48MHz anyway, so this isn't an issue. I imagine you'll also be setting the timer prescalers to 1.

If you're using timer TCC0 for timer capture on the receiver, then it will be necessary to use TCC1 for PWM generation on the transmitter. This is because the TCC0 timer is reset at the end of a capture cycle (timer period), whereas for PWM generation the timer runs until its count (COUNT) value matches the value in its period (PER) register. If you were to run them on the same timer, they'd interfere with each other.

Quote
Did I well understand that both timers / interruptions can work at the same time ?
Yes, both timers can work at the same time. Each timer has it's own interrupt service routine and can be triggered by overflow (OVF) and counter compare match (MCx) for each timer channel.

Here's an example of the TCC0 interrupt service routine:

Code: [Select]
void TCC0_Handler()                            // Interrupt Service Routine (ISR) for timer TCC0
{     
  // Check for overflow (OVF) interrupt
  if (TCC0->INTFLAG.bit.OVF && TCC0->INTENSET.bit.OVF)             
  {
    // Put your timer overflow (OVF) code here... 
    // ...   
 
   REG_TCC0_INTFLAG = TCC_INTFLAG_OVF;        // Clear the OVF interrupt flag
  }

  // Check for match counter 0 (MC0) interrupt
  if (TCC0->INTFLAG.bit.MC0 && TCC0->INTENSET.bit.MC0)             
  {
    // Put your counter compare 0 (CC0) code here...
    // ... 
    REG_TCC0_INTFLAG = TCC_INTFLAG_MC0;        // Clear the MC0 interrupt flag
  }

  // Check for match counter 1 (MC1) interrupt
  if (TCC0->INTFLAG.bit.MC1 && TCC0->INTENSET.bit.MC1)           
  {
    // Put your counter compare 1 (CC1) code here...
    // ...
    REG_TCC0_INTFLAG = TCC_INTFLAG_MC1;       // Clear the MC1 interrupt flag
  }

  // TCC0 has 4 channels, so has MC2 and MC3 as well...
}

Quote
How to STOP / START timers individualy ?
To stop the timer:

Code: [Select]
REG_TCC0_CTRLBSET = TCC_CTRLBSET_CMD_STOP;
while(TCC0->SYNCBUSY.bit.CTRLB);

To re-start the timer again:

Code: [Select]
REG_TCC0_CTRLBSET = TCC_CTRLBSET_CMD_RETRIGGER;
while(TCC0->SYNCBUSY.bit.CTRLB);

Quote
For the external interruption linked to Pin 12, can we use "Nointerrupts() / Interrupts() instructions to stop it ? I mean, does other interruptions in the system will be affected ? and if yes how to manage it selectively ?
To manage the interrupts it's possible to use the timer's interrupt enable set (INTENSET) and interrupt enable clear registers (INTENCLR):

Code: [Select]
REG_TCC0_INTENSET = TCC_INTENSET_MC1 |     // Enable compare channel 1 (CC1) interrupts
                    TCC_INTENSET_MC0 |     // Enable compare channel 0 (CC0) interrupts
                    TCC_INTENSET_OVF;      // Enable overflow (OVF) interupts


Code: [Select]
REG_TCC0_INTENCLR = TCC_INTENSET_MC1 |     // Disable compare channel 1 (CC1) interrupts
                    TCC_INTENSET_MC0 |     // Disable compare channel 0 (CC0) interrupts
                    TCC_INTENSET_OVF;      // Disable overflow (OVF) interupts

Kind regards,
Martin
Title: Re: Arduino Zero TCC Capture
Post by: DR49 on Feb 19, 2018, 10:23 am
Thanks Martin,

In my PWM generation code I got :

// Connect the TCC0 timer to digital output D7 - port pins are paired odd PMUO and even PMUXE
  // F & E specify the timers: TCC0, TCC1 and TCC2
  PORT->Group[g_APinDescription[6].ulPort].PMUX[g_APinDescription[6].ulPin >> 1].reg = PORT_PMUX_PMUXO_F;

  // Feed GCLK4 to TCC0 and TCC1
  GCLK->CLKCTRL.reg = 0x441A;                     // Modifié pour compat IDE 1.7.11 MartinL forum Arduino.cc (https://forum.arduino.cc/index.php?topic=346731.15)
  while (GCLK->STATUS.bit.SYNCBUSY);              // Wait for synchronization

I am not sure that the "comments" are linked to the instructions., but my questions are :

Where (Page # doc SAMD21_Atmel user guide ?) can I get the info to use the correct syntax, for selecting the relevant TCCx to connect, in the first instruction above (According to your last post, I need using TCC1 rather than TCC0).

In the present code it seems that both TCC0 and TCC1 are used ? (Feed GCLK 4 to...)


Thanks for your last post other infos, I noted !

Kind regards

Daniel

Title: Re: Arduino Zero TCC Capture
Post by: MartinL on Feb 19, 2018, 11:24 am
Hi Daniel,

Here's some code that runs the TCC1 timer PWM at 50% duty-cycle, 40kHz on D9:

Code: [Select]
// Output 40kHz PWM on timer TCC1 (10-bit resolution)
void setup()
{
  REG_GCLK_GENDIV = GCLK_GENDIV_DIV(1) |          // Divide the 48MHz clock source by divisor 1: 48MHz/1=48MHz
                    GCLK_GENDIV_ID(4);            // Select Generic Clock (GCLK) 4
  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 GCLK4
                     GCLK_GENCTRL_SRC_DFLL48M |   // Set the 48MHz clock source
                     GCLK_GENCTRL_ID(4);          // Select GCLK4
  while (GCLK->STATUS.bit.SYNCBUSY);              // Wait for synchronization

  // Enable the port multiplexer for the PWM channel on pin D9
  PORT->Group[g_APinDescription[9].ulPort].PINCFG[g_APinDescription[9].ulPin].bit.PMUXEN = 1;
 
  // Connect the TCC1 timer to the port outputs - port pins are paired odd PMUO and even PMUXE
  // F & E peripherals specify the timers: TCC0, TCC1 and TCC2
  PORT->Group[g_APinDescription[9].ulPort].PMUX[g_APinDescription[9].ulPin >> 1].reg |= PORT_PMUX_PMUXO_E;

  // Feed GCLK4 to TCC0 and TCC1
  REG_GCLK_CLKCTRL = GCLK_CLKCTRL_CLKEN |         // Enable GCLK4 to TCC0 and TCC1
                     GCLK_CLKCTRL_GEN_GCLK4 |     // Select GCLK4
                     GCLK_CLKCTRL_ID_TCC0_TCC1;   // Feed GCLK4 to TCC0 and TCC1
  while (GCLK->STATUS.bit.SYNCBUSY);              // Wait for synchronization

  // Normal (single slope) PWM operation: timers countinuously count up to PER register value and then is reset to 0
  REG_TCC1_WAVE |= TCC_WAVE_WAVEGEN_NPWM;        // Setup single slope PWM on TCC1
  while (TCC1->SYNCBUSY.bit.WAVE);                // Wait for synchronization

  // Each timer counts up to a maximum or TOP value set by the PER register,
  // this determines the frequency of the PWM operation: 959 = 50kHz
  REG_TCC1_PER = 1199;      // Set the frequency of the PWM on TCC1 to 40kHz
  while(TCC1->SYNCBUSY.bit.PER);

  // The CCBx register value corresponds to the pulsewidth in microseconds (us)
  REG_TCC1_CC1 = 599;      // Set the duty cycle of the PWM on TCC1 to 50%
  while(TCC1->SYNCBUSY.bit.CC1);
 
  // Enable TCC1 timer
  REG_TCC1_CTRLA |= TCC_CTRLA_PRESCALER_DIV1 |    // Divide GCLK4 by 1
                    TCC_CTRLA_ENABLE;             // Enable the TCC0 output
  while (TCC1->SYNCBUSY.bit.ENABLE);              // Wait for synchronization
}

void loop() { }

Unfortunately, there isn't much information on how to set things up. The best source of information is the SAMD21 datasheet.

The register definitions are stored (at least on my Windows machine) at:

C:\Users\Computer\AppData\Local\Arduino15\packages\arduino\tools\CMSIS\4.0.0-atmel\Device\ATMEL\samd21\include\... directory.

In this directory there are two sub-directories, "instance" and "component".

The "instance" directory contains the header files with definitions for each instance of peripheral, for example: "tcc0.h", "tcc1.h", etc...

The "component" directory contains the header files with definitions for peripheral's register bitfields, for instance "tcc.h". This is common to all TCC peripherals, so in this case there's only on file per peripheral.

Quote
In the present code it seems that both TCC0 and TCC1 are used ? (Feed GCLK 4 to...)
The SAMD21 hardware doesn't offer the option of clocking the TCC timers with separate generic clocks. The generic clock is instead fed to timer pairs: TCC0 & TCC1, TCC2 & TC3, and finally TC4 & TC5.

In the present code the generic clock is fed to timer TCC0 and to timer TCC1, but TCC1 isn't being used.

Kind regards,
Martin
Title: Re: Arduino Zero TCC Capture
Post by: DR49 on Feb 19, 2018, 04:24 pm
Hi MartinL,

Ok , understood and noted !

Many thanks for this, I'll inquire inside these ATMEL directories

Kind Regards,

Daniel
Title: Re: Arduino Zero TCC Capture
Post by: DR49 on Feb 20, 2018, 07:58 pm
Hi MartinL

I have tested the above # 94 sketch (40 kHz via TCC1 on D9), but be obliged to do some adjustments to get it working (See attachement).

To summarise, I needed to do the 2 following modifications :

// Feed GCLK4 to TCC0 and TCC1
  GCLK->CLKCTRL.reg = 0x441A;                      // Modified for IDE 1.7.11 by MartinL forum Arduino.cc (https://forum.arduino.cc/index.php?topic=346731.15)

and the following one :

// Dual slope PWM operation: timers countinuously count up to PER register value then down 0
  REG_TCC1_WAVE |= TCC_WAVE_POL(0xF) |         // Reverse the output polarity on all TCC1 outputs
                    TCC_WAVE_WAVEGEN_DSBOTH;    // Setup dual slope PWM on TCC1
and following....

Since "Normal (single slope) PWM operation" seems not working.

Hopefuly I have now a sketch running under TCC1, but could you explain why these modifications are needed ?

Then I tried to change the pin output from 9 to 7...and I never got it working.

Could you please adjust the sketch to do this, so I can understand how to pass from D9 to D7 output pin ?

What I did ( but it's not working !) :

// Enable the port multiplexer for the PWM channel on pin D7
PORT->Group[g_APinDescription[7].ulPort].PINCFG[g_APinDescription[7].ulPin].bit.PMUXEN = 1;

And :
// Connect the TCC1 timer to the port outputs - port pins are paired odd PMUO and even PMUXE
  // F & E peripherals specify the timers: TCC0, TCC1 and TCC2
  PORT->Group[g_APinDescription[6].ulPort].PMUX[g_APinDescription[6].ulPin >> 1].reg |= PORT_PMUX_PMUXO_E;


I fear this last modification to be the pb since I don't understand which number needs to be put in ??


Many thanks again,

Kind Regards

Daniel




Title: Re: Arduino Zero TCC Capture
Post by: MartinL on Feb 20, 2018, 08:49 pm
Hi Daniel,

The changes shouldn't be necessary, I tested the TCC1, single slope PWM example #94 above and I'm getting 40kHz, 50% duty-cycle on D9.

In your code, you've done the same thing, but configured the output for dual slope PWM instead.

The TCC1 timer output isn't available on D7. Your next best option is to use D8 that can output TCC1 on channel 0.

To activate D8, change the pin multiplexer (PMUX) configuration to:

Code: [Select]
PORT->Group[g_APinDescription[8].ulPort].PMUX[g_APinDescription[8].ulPin >> 1].reg |= PORT_PMUX_PMUXE_E;
You'll also need to change the counter compare register to channel 0:

Code: [Select]
REG_TCC1_CC0 = 599;      // Set the duty cycle of the PWM on TCC1 to 50%
while(TCC1->SYNCBUSY.bit.CC0);

You'll need to set it to 299 if you're using dual slope PWM.

Kind regards,
Martin
Title: Re: Arduino Zero TCC Capture
Post by: DR49 on Feb 22, 2018, 12:19 pm
Hi MartinL

Everything is going perfectly ! I made a mistake (not hooking my oscilloscope to the correct Pin.....bah....)

So Many thanks again for your precious help !

Now I have aother idea, could we imagine to code a PSK rather than an FSK2 ?

Do you have an idea to control the phase of a generated PWM signal at 40 kHz ?

The idea would be to change by 180 ° each time I need to générate a different bit (0 or 1).


Have a good day

Kind Regards

Daniel

PS : Perhaps that we need to open a new thread for this approach ?
Title: Re: Arduino Zero TCC Capture
Post by: MartinL on Feb 23, 2018, 09:39 am
Hi Daniel,

Quote
Do you have an idea to control the phase of a generated PWM signal at 40 kHz ?
If you require a 180° phase shift then you could try using the polarity bits to invert the waveform in the WAVB (Waveform Buffer register).

Buffered registers such as CCxB, PERB etc..., should be used when changing the duty-cycle/period during operation. Loading these registers cause the output to update at the beginning of the next timer cycle (overflow), preventing the changes from causing glitches on your waveform output. (Changes to the CCx, PER, etc... take effect on the output immediately).

An example of loading a buffered counter compare register:

Code: [Select]
REG_TCC0_CCB0 = 1500;
while(TCC0->SYNCBUSY.bit.CCB0);

Another alternative, for fine control over the PWM signal is to use dual slope critical PWM, where two of the timer's counter compare channels (CCx) are used to control the position of both the rising and falling edges of the PWM output. This could be used to generate whatever phase shift in the signal you require.

Kind regards,
Martin
Title: Re: Arduino Zero TCC Capture
Post by: DR49 on Mar 03, 2018, 11:08 pm
Dear MartinL

I'm coming back to you since I got some pbs when associating the TTC0 code above to my previous designed sketches (RTC and LCD working both on I2C).  I'm also using millis() to display the time each 5 sec etc...

This is my code (joined piece).

In fact it works as TCC0 capture, but both serialUSB and RTC seem to be blocked by TCC interruptions.

When I comment TCC0 initialisation in the void setup, serialUSB and RTC are running well again !!

Could you help ?

I fear I2C and/or RTC as non compatible with TCC0 ?

Kind regards

Daniel

Title: Re: Arduino Zero TCC Capture
Post by: MartinL on Mar 04, 2018, 05:49 pm
Hi Daniel,

If you're using a 40kHz input, then I think it's most probably because your input frequency is too high for the SAMD21 with the additional RTC and I2C code.

Your options as I see it are as follows:

1) If possible use a lower input frequency.
2) If you don't need both pulse-width and period data then disable the interrupt for the one you don't need.
3) Use the SAMD21's Direct Memory Access Controller (DMAC) to trigger on the TCC0 input, get it to read the pulse-width/period and move this data to a memory location that can be accessed by your main program.
4) Use a faster micro-controller.

Kind regards,
Martin
Title: Re: Arduino Zero TCC Capture
Post by: MartinL on Mar 05, 2018, 11:07 am
Hi Daniel,

Ok, I think I've found a possible solution.

Rather than using the TCC0_Handler() interrupt service routine, it instead uses the SAMD21's Direct Memory Access Controller (DMAC). The DMAC shoulders the burden of receiving the period and pulse width data from the TCC0 timer, thereby freeing the CPU from this task.

The code uses DMAC channels 0 and 1 to continuously read the period and pulse width data from TCC0's registers and moves this information to the variables "dmacPeriod" and "dmacPulsewidth".

In the loop() portion of your sketch the CPU only has to read these variables. This should free up the CPU, giving it time to run your RTC and I2C routines.

Here's the code:

Code: [Select]
// Setup TCC0 to capture pulse-width and period with DMAC transfer
volatile uint32_t dmacPeriod;
volatile uint32_t dmacPulsewidth;

typedef struct                              // DMAC descriptor structure
{
  uint16_t btctrl;
  uint16_t btcnt;
  uint32_t srcaddr;
  uint32_t dstaddr;
  uint32_t descaddr;
} dmacdescriptor ;

volatile dmacdescriptor wrb[12] __attribute__ ((aligned (16)));               // Write-back DMAC descriptors
dmacdescriptor descriptor_section[12] __attribute__ ((aligned (16)));         // DMAC channel descriptors
dmacdescriptor descriptor __attribute__ ((aligned (16)));                     // Place holder descriptor

void setup()
{
  SerialUSB.begin(115200);                  // Send data back on the Zero's native port
  while(!SerialUSB);                        // Wait for the SerialUSB port to be ready
 
  DMAC->BASEADDR.reg = (uint32_t)descriptor_section;                // Set the descriptor section base address
  DMAC->WRBADDR.reg = (uint32_t)wrb;                                // Set the write-back descriptor base adddress
  DMAC->CTRL.reg = DMAC_CTRL_DMAENABLE | DMAC_CTRL_LVLEN(0xf);      // Enable the DMAC and priority levels
 
  DMAC->CHID.reg = DMAC_CHID_ID(0);                                 // Select DMAC channel 0
  // Set DMAC channel 1 to priority level 0 (lowest), to trigger on TCC0 match compare 1 and to trigger every beat
  DMAC->CHCTRLB.reg = DMAC_CHCTRLB_LVL(0) | DMAC_CHCTRLB_TRIGSRC(TCC0_DMAC_ID_MC_0) | DMAC_CHCTRLB_TRIGACT_BEAT;
  //DMAC->CHINTENSET.reg = DMAC_CHINTENSET_MASK ;                   // Enable all DMAC interrupts
  descriptor.descaddr = (uint32_t)&descriptor_section[0];           // Set up a circular descriptor
  descriptor.srcaddr = (uint32_t)&TCC0->CC[0].reg;                  // Take the contents of the TCC0 counter comapare 0 register
  descriptor.dstaddr = (uint32_t)&dmacPeriod;                       // Copy it to the "dmacPeriod" variable
  descriptor.btcnt = 1;                                             // This takes one beat
  descriptor.btctrl = DMAC_BTCTRL_BEATSIZE_HWORD | DMAC_BTCTRL_VALID;   // Copy 16-bits (HWORD) and flag the descriptor as valid
  memcpy(&descriptor_section[0], &descriptor, sizeof(dmacdescriptor));  // Copy to the channel 0 descriptor
 
  DMAC->CHID.reg = DMAC_CHID_ID(1);                                 // Select DMAC channel 1
  // Set DMAC channel 1 to priority level 0 (lowest), to trigger on TCC0 match compare 1 and to trigger every beat
  DMAC->CHCTRLB.reg = DMAC_CHCTRLB_LVL(0) | DMAC_CHCTRLB_TRIGSRC(TCC0_DMAC_ID_MC_1) | DMAC_CHCTRLB_TRIGACT_BEAT;
  //DMAC->CHINTENSET.reg = DMAC_CHINTENSET_MASK ;                   // Enable all DMAC interrupts
  descriptor.descaddr = (uint32_t)&descriptor_section[1];           // Set up a circular descriptor
  descriptor.srcaddr = (uint32_t)&TCC0->CC[1].reg;                  // Take the contents of the TCC0 counter comapare 1 register
  descriptor.dstaddr = (uint32_t)&dmacPulsewidth;                   // Copy it to the "dmacPulseWidth" variable
  descriptor.btcnt = 1;                                             // This takes 1 beat
  descriptor.btctrl = DMAC_BTCTRL_BEATSIZE_HWORD | DMAC_BTCTRL_VALID;    // Copy 16-bits (HWORD) and flag the descriptor as valid
  memcpy(&descriptor_section[1], &descriptor, sizeof(dmacdescriptor));   // Copy to the channel 1 descriptor
 
  REG_PM_APBCMASK |= PM_APBCMASK_EVSYS;     // Switch on the event system peripheral
 
  REG_GCLK_GENDIV = 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

  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_TCC0_TCC1;   // Feed the GCLK5 to TCC0 and TCC1
  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;

  //attachInterrupt(12, NULL, HIGH);                                        // Attach interrupts to digital pin 12 (external interrupt 3)
  REG_EIC_EVCTRL |= EIC_EVCTRL_EXTINTEO3;                                 // Enable event output on external interrupt 3
  REG_EIC_CONFIG0 |= EIC_CONFIG_SENSE3_HIGH;                              // Set event detecting a HIGH level
  REG_EIC_CTRL |= EIC_CTRL_ENABLE;                                        // Enable EIC peripheral
  while (EIC->STATUS.bit.SYNCBUSY);                                       // Wait for synchronization
 
  REG_EVSYS_USER = EVSYS_USER_CHANNEL(1) |                                // Attach the event user (receiver) to channel 0 (n + 1)
                   EVSYS_USER_USER(EVSYS_ID_USER_TCC0_EV_1);              // Set the event user (receiver) as timer TCC0, event 1

  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_TCC0_EVCTRL |= TCC_EVCTRL_MCEI1 |           // Enable the match or capture channel 1 event input
                     TCC_EVCTRL_MCEI0 |           //.Enable the match or capture channel 0 event input
                     TCC_EVCTRL_TCEI1 |           // Enable the TCC event 1 input
                     /*TCC_EVCTRL_TCINV1 |*/      // Invert the event 1 input         
                     TCC_EVCTRL_EVACT1_PPW;       // Set up the timer for capture: CC0 period, CC1 pulsewidth                                   
 
  REG_TCC0_CTRLA |= TCC_CTRLA_CPTEN1 |              // Enable capture on CC1
                    TCC_CTRLA_CPTEN0 |              // Enable capture on CC0
                    TCC_CTRLA_PRESCALER_DIV1 |      // Set prescaler to 1, 48MHz/1 = 48MHz
                    TCC_CTRLA_ENABLE;               // Enable TCC0
  while (TCC0->SYNCBUSY.bit.ENABLE);                // Wait for synchronization

  DMAC->CHID.reg = DMAC_CHID_ID(0);                 // Select DMAC channel 0
  DMAC->CHCTRLA.reg |= DMAC_CHCTRLA_ENABLE;         // Enable DMAC channel 0
  DMAC->CHID.reg = DMAC_CHID_ID(1);                 // Select DMAC channel 1
  DMAC->CHCTRLA.reg |= DMAC_CHCTRLA_ENABLE;         // Enable DMAC channel 1
}

void loop()
{
  SerialUSB.print(dmacPeriod);                      // Output the results
  SerialUSB.print(F("   "));
  SerialUSB.println(dmacPulsewidth);
}

Kind regards,
Martin
Title: Re: Arduino Zero TCC Capture
Post by: DR49 on Mar 05, 2018, 04:05 pm
Thanks MartinL

Just three comments :

1 / Has AttachInterrupt with NULL being repaired in the IDE 1.8.5 ? If not I need to put a callback routine as previously isn't it ?

2/ How can I know that a new period has been measured in your sketch, since there is no more "void TCC0_Handler()"   ?

3/ I also need "Count" data for my application. Can I declare isrCount variable and put isrCount++;  in the void loop, but as said in 2/ above, how can I increment it at the end of each period measurement ?


Thanks again,

Kind Regards,

Daniel
 
Title: Re: Arduino Zero TCC Capture
Post by: parnal on Mar 05, 2018, 04:14 pm
Hello ,

I am facing a problem to set an event (the purpose of this will be to make them work like Arduino Due's function of clock chaining):
1. I want to use a pair of TC4 - TC5 working in 32 bit mode.
2. I read that to set an event 2 parameters are important EVGEN (Event Generator) and USER (Event user).
   So TC4 should act as a event generator and TC5 as a user.
   which isTC4 should count upto the maximum value and then it should trigger TC5 to start counting i.e clock
   chaining.
3. My understanding goes like:  initialisation path ( PM -> APBCMASK ->EVSYS -> EVGEN ->USER)
4. The problem is to setup this connection. What all registers should be initialised to make it work
   and how ?

Thank you .
Title: Re: Arduino Zero TCC Capture
Post by: MartinL on Mar 05, 2018, 05:08 pm
Hi Daniel,

Quote
1 / Has AttachInterrupt with NULL being repaired in the IDE 1.8.5 ? If not I need to put a callback routine as previously isn't it ?
I raised the issue with the Arduino SAMD core development team and they've flagged it as a bug, so I expect it might be fixed in a subsequent Arduino Zero build.

In the DMAC example code above, I commented out attachInterrupt() function and replaced it by setting the interrupt registers directly.

Quote
2/ How can I know that a new period has been measured in your sketch, since there is no more "void TCC0_Handler()"   ?
The "dmacPeriod" and "dmacPulsewidth" variables now hold the latest measurements. The DMAC is reading them instead of the CPU having service the TCC0_Handler() each time. The DMAC is effectively handling the TCC0 timer for the CPU.

Quote
3/ I also need "Count" data for my application. Can I declare isrCount variable and put isrCount++;  in the void loop, but as said in 2/ above, how can I increment it at the end of each period measurement ?
I'll have to think about this one....

It might be possible to use the event system to get another of the SAMD21's timers to count the number of TCC0 periods that have elapsed.

Kind regards,
Martin

Title: Re: Arduino Zero TCC Capture
Post by: MartinL on Mar 05, 2018, 05:21 pm
Hi parnal,

Quote
I am facing a problem to set an event (the purpose of this will be to make them work like Arduino Due's function of clock chaining):
1. I want to use a pair of TC4 - TC5 working in 32 bit mode.
2. I read that to set an event 2 parameters are important EVGEN (Event Generator) and USER (Event user).
   So TC4 should act as a event generator and TC5 as a user.
   which isTC4 should count upto the maximum value and then it should trigger TC5 to start counting i.e clock
   chaining.
3. My understanding goes like:  initialisation path ( PM -> APBCMASK ->EVSYS -> EVGEN ->USER)
4. The problem is to setup this connection. What all registers should be initialised to make it work
   and how ?
Looks like your issue is similar to the Daniel's requirement in point (3) above. I'll see if I can come up with a solution using the TC timers in 32-bit mode.

Kind regards,
Martin
Title: Re: Arduino Zero TCC Capture
Post by: MartinL on Mar 05, 2018, 11:30 pm
Hi Daniel and parnal,

I've added the counter using the TC3 and TC4 timers in 32-bit mode. In this mode, TC4 acts as the master and TC3 the slave, this just means that the registers are accessed through TC4's interface.

The TC4 timer in 32-bit mode is set-up to increment each time it receives an event from interrupt pin on the event system.

Using 32-bits with a 40kHz input frequency will allow the TC timer to count for almost 30 hours before rolling over back to 0.

If you require longer than this then it'll be necessary to get TC4 to generate an overflow event to the 16-bit TC5 timer. Using TC5 as well will give you 223 years before it rolls over! Probably enough for most projects, in any case we won't be around when this happens.

Please find the code attached. Unfortunately it exceeds the 9000 character limit, so I can't display it in this message.

Kind regards,
Martin
Title: Re: Arduino Zero TCC Capture
Post by: MartinL on Mar 06, 2018, 09:47 am
Hi parnal,

Here's a cut down version of the above code, that only counts the number of incoming interrupt pulses on D12 using timer TC4 in 32-bit mode, (but doesn't use the DMAC or TCC0):

Code: [Select]
// Setup TC4 in 32-bit mode to count interrupt pulses using the event system on digital pin D12
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(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

  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_GCLK_CLKCTRL = GCLK_CLKCTRL_CLKEN |         // Enable the generic clock...
                     GCLK_CLKCTRL_GEN_GCLK5 |     // ....on GCLK5
                     GCLK_CLKCTRL_ID_TC4_TC5;     // Feed the GCLK5 to TC4 and TC5
  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;

  //attachInterrupt(12, NULL, HIGH);                                        // Attach interrupts to digital pin 12 (external interrupt 3)
  REG_EIC_EVCTRL |= EIC_EVCTRL_EXTINTEO3;                                 // Enable event output on external interrupt 3
  REG_EIC_CONFIG0 |= EIC_CONFIG_SENSE3_HIGH;                              // Set event detecting a HIGH level
  REG_EIC_CTRL |= EIC_CTRL_ENABLE;                                        // Enable EIC peripheral
  while (EIC->STATUS.bit.SYNCBUSY);                                       // Wait for synchronization

  REG_EVSYS_USER = 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
 
  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_TCC0_EVCTRL |= TCC_EVCTRL_MCEI1 |           // Enable the match or capture channel 1 event input
                     TCC_EVCTRL_MCEI0 |           //.Enable the match or capture channel 0 event input
                     TCC_EVCTRL_TCEI1 |           // Enable the TCC event 1 input
                     /*TCC_EVCTRL_TCINV1 |*/      // Invert the event 1 input         
                     TCC_EVCTRL_EVACT1_PPW;       // Set up the timer for capture: CC0 period, CC1 pulsewidth                                   

  REG_TC4_EVCTRL |= TC_EVCTRL_TCEI |              // Enable asynchronous events on the TC timer
                    TC_EVCTRL_EVACT_COUNT;        // Increment the TC timer each time an event is received

  REG_TC4_CTRLA |= TC_CTRLA_PRESCALER_DIV1 |      // Set prescaler to 1, 48MHz/1 = 48MHz
                   TC_CTRLA_MODE_COUNT32 |        // Set the TC4 timer to 32-bit mode in conjuction with timer TC3
                   TC_CTRLA_ENABLE;               // Enable TC4
  while (TC4->COUNT32.STATUS.bit.SYNCBUSY);       // Wait for synchronization

  REG_TC4_READREQ = TC_READREQ_RCONT |            // Enable a continuous read request
                    TC_READREQ_ADDR(0x10);        // Offset of the 32-bit COUNT register
  while (TC4->COUNT32.STATUS.bit.SYNCBUSY);       // Wait for (read) synchronization
}

void loop()
{
  SerialUSB.println(REG_TC4_COUNT32_COUNT);       // Output the results
}
Title: Re: Arduino Zero TCC Capture
Post by: parnal on Mar 06, 2018, 10:10 am
Thank you so much.
The only part where I have a problem is this :


Quote
// 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;

  //attachInterrupt(12, NULL, HIGH);                                        // Attach interrupts to digital pin 12 (external interrupt 3)
  REG_EIC_EVCTRL |= EIC_EVCTRL_EXTINTEO3;                                 // Enable event output on external interrupt 3
  REG_EIC_CONFIG0 |= EIC_CONFIG_SENSE3_HIGH;                              // Set event detecting a HIGH level
  REG_EIC_CTRL |= EIC_CTRL_ENABLE;                                        // Enable EIC peripheral
  while (EIC->STATUS.bit.SYNCBUSY);                                       // Wait for synchronization
Why is it necessary to attach an external interrupt, cant it be done using internal interrupt ?
In simple words when the TC4 counts to its maximum count it displays the count and again starts over .


Title: Re: Arduino Zero TCC Capture
Post by: MartinL on Mar 06, 2018, 10:29 am
Hi Parnal,

In the earlier builds of the Arduino core code, it was possible to set-up a pin as an interrupt by using the attachInterrupt() function and passing a NULL argument as the function callback pointer:

Code: [Select]
attachInterrupt(12, NULL, HIGH);           // Attach interrupts to digital pin 12 (external interrupt 3)
However, in the later builds this was changed so that attachInterrupt() would skip pin set-up if a NULL pointer was detected.

This means at the moment it's necessary to either use attachInterrupt() with a dummy (empty) callback function, or set-up the pin using regsiter manipulation.

I've raised the isse on Github with the Arduino SAMD core developers, as this changes reduces the flexibility of this function.

The first section of code just switches the pin from GPIO to the peripheral multiplexer and then selects the peripheral as the External Interrupt Controller (EIC):

Code: [Select]
// 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;

The following lines then just activate the interrupt on D12, which is on EIC channel 3 and set the pin sensing to interrupt on a HIGH level:

Code: [Select]
REG_EIC_EVCTRL |= EIC_EVCTRL_EXTINTEO3;       // Enable event output on external interrupt 3
REG_EIC_CONFIG0 |= EIC_CONFIG_SENSE3_HIGH;           // Set event detecting a HIGH level
REG_EIC_CTRL |= EIC_CTRL_ENABLE;                            // Enable EIC peripheral
while (EIC->STATUS.bit.SYNCBUSY);                             // Wait for synchronization
Title: Re: Arduino Zero TCC Capture
Post by: MartinL on Mar 06, 2018, 10:35 am
Hi parnal,

I think I probably got the wrong end of the stick with regard to your requirements, it's just that this thread was dealing with capturing an external signal and for this it's necessary to use an external interrupt.

If you're application doesn't require an external interrupt then it's possible to just use TC timer in 32-bit mode with internal interrupts and events.
Title: Re: Arduino Zero TCC Capture
Post by: parnal on Mar 06, 2018, 11:03 am
Hello Martin,

Thank you again, but that is where my problem is how to fed TC3 to TC4 internally ? :(
Title: Re: Arduino Zero TCC Capture
Post by: MartinL on Mar 06, 2018, 11:27 am
Hi parnal,

Setting the TC4 timer to 32-bit mode automatically feeds one timer from another. Essentially it turns timer TC4 into a 32-bit timer. The SAMD21's hardware does this for you, chaining TC4 to TC3 in the background.

Reading the TC4's 32-bit count register gives you a seamless 32-bit result.

It's also possible to set-up CCx counter compare and overflow interrupts in 32-bits.
Title: Re: Arduino Zero TCC Capture
Post by: parnal on Mar 06, 2018, 11:34 am
Hi Martin,

So ideally if i just remove the port and external interrupt commands, it should work right?

Well I did that and it shows TC4 value is zero on the serial monitor.
Title: Re: Arduino Zero TCC Capture
Post by: MartinL on Mar 06, 2018, 11:49 am
Hi parnal,

Quote
Well I did that and it shows TC4 value is zero on the serial monitor.
That's because with this code the TC timer is set up to read a high speed external interrupt.

How the TC timer is initialised depends on what you're trying to achieve. It can be set up in number of different ways depending on your requirement.

If you're just requiring a regular internal interrupt then the normal (NFRQ) or match frequency (MFRQ) modes are suitable. If however you require an external PWM output signal then normal (NPWM) or match (MPWM) PWM modes are probably best. There's also the event system, where one peripheral can be used to trigger another without CPU intervention.

How do you intend to use the TC timer?
Title: Re: Arduino Zero TCC Capture
Post by: DR49 on Mar 06, 2018, 01:49 pm
Many thanks Martin I'll try it on my application and be back to you soon


Kind Regards

Daniel
Title: Re: Arduino Zero TCC Capture
Post by: parnal on Mar 06, 2018, 03:45 pm
Hi Martin,

This is how my general TC4 counter code looks like:
Code: [Select]


void setup() {
  Serial.begin(9600);


    REG_GCLK_CLKCTRL = (uint16_t) (GCLK_CLKCTRL_CLKEN | GCLK_CLKCTRL_GEN_GCLK0 | GCLK_CLKCTRL_ID_TC4_TC5) ; 
   
   TcCount32* TC = (TcCount32*) TC4;              // The type cast must fit with the selected timer mode
   
   
   TC->CTRLA.reg |= TC_CTRLA_MODE_COUNT32;        // Set Timer counter Mode to 32 bits
 
   TC->CTRLA.reg |= TC_CTRLA_WAVEGEN_NFRQ;       // Set TC as normal Frequency
 
   TC->CTRLA.reg |= TC_CTRLA_PRESCALER_DIV256;   // Set perscaler
   
   TC->CTRLA.reg |= TC_CTRLA_ENABLE;            // Enable TC

   
    REG_TC4_READREQ = TC_READREQ_RCONT |            // Enable a continuous read request
                    TC_READREQ_ADDR(0x10);        // Offset of the 16 bit COUNT register
     while (TC4->COUNT32.STATUS.bit.SYNCBUSY);              // Wait for synchronization
}

void loop() {
 
  TcCount32* TC = (TcCount32*) TC4;              // The type cast must fit with the selected timer mode   TcCount32* TC = (TcCount32*) TC3;              // The type cast must fit with the selected timer mode
   
   
  Serial.println(REG_TC4_COUNT32_COUNT);
  Serial.println("---");
  delay(100);

}


Now I would just like the counter (when rolls over to zero ) it should go to TC5 from TC4 and should not start counting itself from zero. I know I am asking the same thing but I am still there. so,if you can help how can this connection be achieved.


This sounds to be my solution , but I need help in initialization :
There's also the event system, where one peripheral can be used to trigger another without CPU intervention.

EVGEN(Event Generator) TC3_MC0 should generate an event to the USER_TC4
Title: Re: Arduino Zero TCC Capture
Post by: MartinL on Mar 06, 2018, 06:07 pm
Hi parnal,

No problem, hopefully I can help with the initialization.

First of all I need to know the frequency you intend to clock the timers. I notice in your code you're using a 256 prescaler divider.

My questions are:

1) Are you intending to clock the timers at 187.5kHz (48MHz/256)?

2) Do you wish the timers to free run and overflow at their maximum (2^32)?

Regarding the TC timer chaining, setting the TC timers to 32-bit mode is effectively the same as TC timer chaining on the Arduino Due, like in ard_newbie's code:

Code: [Select]
TC0->TC_BMR = TC_BMR_TC1XC1S_TIOA0;                     // Timer Counter 0 channel 1 is internally clocked by TIOA0

  TC0->TC_CHANNEL[1].TC_CMR = TC_CMR_TCCLKS_XC1            // External clock XC1 selected
                              | TC_CMR_WAVE                // Waveform mode
                              | TC_CMR_WAVSEL_UP_RC        // UP mode with automatic trigger on RC Compare
                              | TC_CMR_ACPA_CLEAR          // Clear TIOA1 on RA compare match
                              | TC_CMR_ACPC_SET;           // Set TIOA1 on RC compare match

It's just that the SAMD21 requires less input from the programmer, as its internal hardware handles this for you. It means that you don't have to use the event system or interrupts to chain the timers.
Title: Re: Arduino Zero TCC Capture
Post by: DR49 on Mar 06, 2018, 10:30 pm
Hi MartinL

I'm using your "TCCCapture_DMAC_TCCount.ino".

What I need is to "restart" the TCCount timer (TC4 & slave TC3) to 0 when the value of REG_TC4_COUNT32_COUNT rises a dedicated number (i.e lenght of bit measurement in my application).

Then how can I reset the TC timer to 0 and let it count again ?

Kind Regards

Daniel
Title: Re: Arduino Zero TCC Capture
Post by: MartinL on Mar 06, 2018, 11:01 pm
Hi Daniel,

You can do this by setting the TC4 (plus TC3) to match frquency (MFRQ) mode.

In MFRQ mode the timer period can be set by the CC0 (counter compare 0) register, instead of the timer's maximum value:

Code: [Select]
REG_TC4_COUNT32_CC0 = 1919;                     // Set the TC4 CC0 register as the TOP value in match frequency mode
while (TC4->COUNT32.STATUS.bit.SYNCBUSY);       // Wait for synchronization

The timer will return to 0 when it reaches the value in the CC0 register.

The timer is set to MFRQ mode in the CTRLA register:

Code: [Select]
REG_TC4_CTRLA |= TC_CTRLA_PRESCALER_DIV1 |      // Set prescaler to 1, 48MHz/1 = 48MHz
                 TC_CTRLA_WAVEGEN_MFRQ |        // Put the timer TC4 into match frequency (MFRQ) mode                   
                 TC_CTRLA_MODE_COUNT32 |        // Set the TC4 timer to 32-bit mode in conjuction with timer TC3
                 TC_CTRLA_ENABLE;               // Enable TC4
while (TC4->COUNT32.STATUS.bit.SYNCBUSY);       // Wait for synchronization

Kind regards,
Martin

Title: Re: Arduino Zero TCC Capture
Post by: parnal on Mar 07, 2018, 10:03 am
Hii Martin,

This is my code for clock chaining in Due and I wish to do the same on Zero.

Code: [Select]

uint32_t *TC_CV = (uint32_t *)(0x40084010); //for timer 1 channel 0 (see datasheet p.888)
uint32_t startTime;
uint32_t wantedTime;
uint32_t endTime;
uint32_t addup = 0;


void setup() {

pmc_set_writeprotect(false);

/*************  Timer Counter 1 Channel 0 to generate PWM pulses thru MCK  ************/

PMC->PMC_PCER0 |= PMC_PCER0_PID30;                          //PID30 from the peripheral identifiers for Timer 1 channel 0, the command enables PWM
TC1->TC_CHANNEL[0].TC_CMR = TC_CMR_TCCLKS_TIMER_CLOCK1      // MCK/2, clk on rising edge
                              | TC_CMR_WAVE                 // Waveform mode
                              | TC_CMR_WAVSEL_UP_RC         // UP mode with automatic trigger on RC Compare
                              | TC_CMR_ACPA_CLEAR           // Clear TIOA0 on RA compare match
                              | TC_CMR_ACPC_SET   ;          // Set TIOA0 on RC compare match
                                           
// Frequency VARIANT_MCK is 84MHz, counter clocked with MCK/2 = 42MHz:
TC1->TC_CHANNEL[0].TC_RC =  VARIANT_MCK / 1000000 / 2;      //  count to 42 --> 1MHz on TIOA0
TC1->TC_CHANNEL[0].TC_RA = VARIANT_MCK / 1000000 / 4;       //  half value 21 (50% duty cycle on TIOA0)


TC1->TC_CHANNEL[0].TC_CCR = TC_CCR_SWTRG | TC_CCR_CLKEN;    // Software trigger TC0 counter and enable CLOCK


/*************  Timer Counter 1 Channel 1 to generate PWM pulses thru TIOA0  ************/

PMC->PMC_PCER0 |= PMC_PCER0_PID31;                        //PID31 from peripheral identifier from Timer 1 channel 1
TC1->TC_BMR = TC_BMR_TC1XC1S_TIOA0;                      // Timer Counter 1  XC1 is internally clocked by TIOA0
TC1->TC_CHANNEL[1].TC_CMR = TC_CMR_TCCLKS_XC1            // External clock XC1 (i.e. TIOA0) selected
                              | TC_CMR_WAVE                // Waveform mode
                              | TC_CMR_WAVSEL_UP_RC;        // UP mode with automatic trigger on RC Compare

TC1->TC_CHANNEL[1].TC_CV = 0;
TC1->TC_CHANNEL[1].TC_CCR = TC_CCR_SWTRG | TC_CCR_CLKEN;   // Software trigger TC1 counter and enable CLOCK


Serial.begin(250000);

}

void loop() {
 
    while (true){
    startTime = TC1->TC_CHANNEL[1].TC_CV; //*TC_CV
    //while(TC1->TC_CHANNEL[1].TC_CV == startTime)addup+=1;
    for(int i=0; i<5; i++);
    endTime=TC1->TC_CHANNEL[1].TC_CV;
   
    Serial.println(startTime);
    Serial.println(endTime);
    Serial.println(endTime-startTime);
    Serial.println(TC1->TC_CHANNEL[0].TC_RC);
 
    Serial.println("--");
    delay(500);
    }
}


and I wish to do this  : "2) Do you wish the timers to free run and overflow at their maximum (2^32)?"

Title: Re: Arduino Zero TCC Capture
Post by: MartinL on Mar 07, 2018, 10:21 am
Hi parnal,

Ok, so this is ard_newbie's Arduino Due timer chaining code from here: http://forum.arduino.cc/index.php?topic=531944.0 (http://forum.arduino.cc/index.php?topic=531944.0).

If you tell me what this code does, I'll implement for you on the SAMD21.

Kind regards,
Martin
Title: Re: Arduino Zero TCC Capture
Post by: parnal on Mar 07, 2018, 10:46 am
Hii Martin,

The program is:
1. Here I wanted to use Timer 1 which is having 3 channels (T1_CH0; T1_CH1; T1_CH2).
2. I initialised 2 channels channel 0 and 1.
3. I have set: channel 0 in waveform mode with  master clock (MCK/2 which is 96/2 for due)  with trigger on RC compare. After Timer 1  channel 0 is done counting it  generates a output TIOA0 and this is fed input to Timer 1 channel 1 .
4. Set Channel 1 with same parameters as channel 0 (waveform mode, rc compare ) then set the input as TIOA0 instead of master clock.
5. Then simply print the counter value. (check pg. 861 Clock chaining diagram from datasheet SAM3X / SAM3A Series )
Title: Re: Arduino Zero TCC Capture
Post by: MartinL on Mar 07, 2018, 12:15 pm
Hi parnal,

The following sets up the 16-bit TC3 timer at 48MHz and gets it to overflow every microsecond (1MHz). On each overflow the timer outputs an event that is received by the 32-bit TC4 (with TC5 as the slave). TC4 is set up to increment each time it receives an event, in other words it's clocked at 1MHz.

There appears to be an error in the SAMD21 datasheet. It says that TC4 is chained with TC3 for 32-bit mode, in reality however it's chained to TC5.

This code does the same as the Arduino Due TC timers:

Code: [Select]
// Setup TC3 to clock TC4/TC5 in 32-bit mode at 1MHz using the event system

uint32_t timeMicros;

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(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

  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_GCLK_CLKCTRL = GCLK_CLKCTRL_CLKEN |         // Enable the generic clock...
                     GCLK_CLKCTRL_GEN_GCLK5 |     // ....on GCLK5
                     GCLK_CLKCTRL_ID_TC4_TC5;     // Feed the GCLK5 to TC4 and TC5
  while (GCLK->STATUS.bit.SYNCBUSY);              // Wait for synchronization

  REG_EVSYS_USER = 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
 
  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_TC3_OVF) |         // Set event generator (sender) as TC3 overflow
                      EVSYS_CHANNEL_CHANNEL(0);                           // Attach the generator (sender) to channel 0                               

  REG_TC4_EVCTRL |= TC_EVCTRL_TCEI |              // Enable asynchronous input events on the TC timer
                    TC_EVCTRL_EVACT_COUNT;        // Increment the TC timer each time an event is received

  REG_TC3_EVCTRL |= TC_EVCTRL_OVFEO;              // Enable an event output on overflow of the TC3 timer

  REG_TC3_COUNT16_CC0 = 47;                       // Set the TC3 CC0 register to overflow every 1us (1MHz)
  while (TC3->COUNT16.STATUS.bit.SYNCBUSY);       // Wait for synchronization
 
  REG_TC4_CTRLA |= TC_CTRLA_PRESCALER_DIV1 |      // Set prescaler to 1, 48MHz/1 = 48MHz
                   TC_CTRLA_MODE_COUNT32 |        // Set the TC4 timer to 32-bit mode in conjuction with timer TC5
                   TC_CTRLA_ENABLE;               // Enable TC4
  while (TC4->COUNT32.STATUS.bit.SYNCBUSY);       // Wait for synchronization

  REG_TC4_READREQ = TC_READREQ_RCONT |            // Enable a continuous read request
                    TC_READREQ_ADDR(0x10);        // Offset of the 32-bit COUNT register
  while (TC4->COUNT32.STATUS.bit.SYNCBUSY);       // Wait for (read) synchronization

  REG_TC3_CTRLA |= TC_CTRLA_PRESCALER_DIV1 |      // Set prescaler to 1, 48MHz/1 = 48MHz
                   TC_CTRLA_WAVEGEN_MFRQ |        // Put the timer TC3 into match frequency (MFRQ) mode   
                   TC_CTRLA_ENABLE;               // Enable TC3
  while (TC3->COUNT16.STATUS.bit.SYNCBUSY);       // Wait for synchronization

  // Test timer TC3
  //REG_TC3_READREQ = TC_READREQ_RCONT |            // Enable a continuous read request
  //                  TC_READREQ_ADDR(0x10);        // Offset of the 32-bit COUNT register
  //while (TC3->COUNT16.STATUS.bit.SYNCBUSY);       // Wait for (read) synchronization

  timeMicros = micros();
}

void loop()
{
  SerialUSB.print(REG_TC4_COUNT32_COUNT);       // Output the results
  SerialUSB.print(F("   "));
  SerialUSB.println(micros() - timeMicros);
 
  //SerialUSB.println(REG_TC3_COUNT32_COUNT);     // Test timer TC3
}
Title: Re: Arduino Zero TCC Capture
Post by: parnal on Mar 07, 2018, 01:48 pm
Hii Martin,

Works perfect. Thanks a lot  :)
Title: Re: Arduino Zero TCC Capture
Post by: DR49 on Mar 07, 2018, 03:26 pm
Thanks Martin,

I understand your point using MFREQ.

But it would be simplest for me to stop / restart the TCCount when it is appropriate.

If it is more complex to do so, tell me how to calculate the elapsed time of the TC : For example what is the time for your 1919 example ?

Kind regards

Daniel
Title: Re: Arduino Zero TCC Capture
Post by: MartinL on Mar 07, 2018, 04:50 pm
Hi Daniel

The TC timer is simply counting the number of input pulses. So the time elapsed depends on how fast you're clocking the input and if that input's regular or not. If there's no input, the counter will stop counting.

The match frequency mode just resets the TC timer back to 0 when it's COUNT register matches the value in the CC0 register.

Taking the 1919 value in the example with a regular 40kHz PWM input gives you:

Time elapsed = (1 / 40000) * (1919 + 1) = 48ms

Add 1 to the 1919 to take into account that we're counting from 0.

If you want to stop the timer in your code:

Code: [Select]
REG_TC4_CTRLBSET = TC_CTRLBCLR_CMD_STOP;
while (TC4->COUNT32.STATUS.bit.SYNCBUSY);

...and to restart...

Code: [Select]
REG_TC4_CTRLBSET = TC_CTRLBCLR_CMD_RETRIGGER;
while (TC4->COUNT32.STATUS.bit.SYNCBUSY);

Kind regards,
Martin
Title: Re: Arduino Zero TCC Capture
Post by: DR49 on Mar 07, 2018, 05:43 pm
Great MartinL, in fact I realised this calculation afterwards !

The point is that I can get noisy frequencies and then TC elapsed time can be different from one bit to another.

So I prefer to control the start / stop.

I'll try this and give you my results back

Many thanks

Kind regards

Daniel
Title: Re: Arduino Zero TCC Capture
Post by: DR49 on Mar 07, 2018, 10:07 pm
Hi MartinL

It's working PERFECTLY !!! (RTC, I2C, Native port, TCC, TC, millis etc...) !!

Using "restart timer" at the relevent place, it's OK !

Thank you very much again,

Kind Regards

Daniel

Title: Re: Arduino Zero TCC Capture
Post by: mladek89 on Oct 13, 2018, 07:56 pm
Hi guys,

I hope someone can help me.
I am using a MKR1010 board with a SAMD21 and I am trying to change the frequency for the attachInterrupt function to use the external interrupt. I have a receiver on a pin and the external interrupt function is called via attachInterrupt(pin, ISR, CHANGE) to find a valid signal. I think that I need to change the frequency of the EIC (External Interrupt Controller). The SAMD21 uses 48MHz and I would need 16Mhz for the external interrupts. How can I change the frequency?  I have no experience in changing the registers :(

Kind Regards
Alex
Title: Re: Arduino Zero TCC Capture
Post by: MartinL on Oct 14, 2018, 10:21 am
Hi Alex,

What's the minimum pulse width and minimum/maximum period of the signal you're receiving?

Is the period (frequency) regular, or are the pulses generated at random or irregular intervals?

The reason I ask, is that if your requirement isn't too stringent then you could just use the attachInterrupt() function and measure the time between the edges using micros(). This is also good if the pulses occur at irregular intervals.

If however you require greater timing resolution then using interrupts connected to an internal timer via the SAMD21's event system is the way to go.
Title: Re: Arduino Zero TCC Capture
Post by: mladek89 on Oct 14, 2018, 02:35 pm
Hi Martin,

thank you for your fast reply :)
I am using the NewRemoteSwitch Library for 433Mhz signals. I have used that library with the Arduino Uno and YUN and it works great. I do not know exactly the signal width and frequency. The only thing I know is that the attachInterrupt function for external interrupts works with the Uno and YUN like a charm. Both of them are based on 16MHz oscillator. I tried to slow down the interrupt function of the MKR1010 with a fixed timer5 library and called the attachInterrupt function inside the timed based interrupt function. It worked but not reliable enough. It seems that this was the evidence that a frequency change could solve the problem. What do you think about that?

Thank you very much for your help!

Best regards
Alex
Title: Re: Arduino Zero TCC Capture
Post by: mladek89 on Oct 14, 2018, 03:07 pm
Hi Martin,

this is what I found out about the signal:

Protocol. (Copied from Wieltje, http://www.circuitsonline.net/forum/view/message/1181410#1181410,
but with slightly different timings, as measured on my device.)
      _   _
'0':   | |_| |_____ (T,T,T,5T)
      _       _
'1':   | |_____| |_   (T,5T,T,T)
      _   _
dim:   | |_| |_     (T,T,T,T)
T = short period of ~260µs. However, this code tries
to figure out the correct period
A full frame looks like this:
- start pulse: 1T high, 10.44T low
- 26 bit:  Address
- 1  bit:  group bit
- 1  bit:  on/off/[dim]
- 4  bit:  unit
- [4 bit:  dim level. Only present of [dim] is chosen]
- stop pulse: 1T high, 40T low

Title: Re: Arduino Zero TCC Capture
Post by: MartinL on Oct 18, 2018, 12:33 pm
Hi Alex,

Does the attachInterrupt interrupt service routine (ISR) funcion in Uno/Yun library happen to use the micros() function?

The reason I ask is because in the past I've found that the micros() function doesn't work in the ISR on the Arduino Zero. This might explain way it works on the Uno/Yun and not on the Zero.

Kind regards,
Martin
Title: Re: Arduino Zero TCC Capture
Post by: mladek89 on Oct 19, 2018, 01:12 pm
Hi Martin,

ohh thank you for the hint.
Yes the library I am using, uses the micros function:
https://github.com/hjgode/homewatch/blob/master/arduino/libraries/NewRemoteSwitch/NewRemoteReceiver.cpp

It is called in the void NewRemoteReceiver::interruptHandler() function.

Do you know how it can be handled with the MKR1010? I think the processor is the same SAMD21 as the ZERO uses?

Thank you very much!

Best regards,
Alex
Title: Re: Arduino Zero TCC Capture
Post by: MartinL on Oct 19, 2018, 01:51 pm
Hi Alex,

It may or may not solve the problem, but you could try replacing micros() with micros2() function.

The micros2() function uses the timer counter TC3 to calculate micros() in a similar way to the AVR Arduinos such as the Uno, Mega, etc...

I've successfully used it in attachInterrupt() interrupt service routines for my flight control firmware.

Here's the code, (together with test output to the console):

Code: [Select]
// Micros2 function
volatile unsigned long timer2Counter;

void setup() {
  Serial.begin(115200);                            // Set up the serial port for test purposes
 
  // Set up the generic clock (GCLK4) used to clock timers
  GCLK->GENDIV.reg = GCLK_GENDIV_DIV(3) |          // Divide the 48MHz clock source by divisor 3: 48MHz/3=16MHz
                    GCLK_GENDIV_ID(4);             // Select Generic Clock (GCLK) 4
  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 GCLK4
                      GCLK_GENCTRL_SRC_DFLL48M |   // Set the 48MHz clock source
                      GCLK_GENCTRL_ID(4);          // Select GCLK4
  while (GCLK->STATUS.bit.SYNCBUSY);               // Wait for synchronization
 
  // Feed GCLK4 to TCC2 (and TC3)
  GCLK->CLKCTRL.reg = GCLK_CLKCTRL_CLKEN |         // Enable GCLK4 to TCC2 (and TC3)
                      GCLK_CLKCTRL_GEN_GCLK4 |     // Select GCLK4
                      GCLK_CLKCTRL_ID_TCC2_TC3;    // Feed GCLK4 to TCC2 (and TC3)
  while (GCLK->STATUS.bit.SYNCBUSY);               // Wait for synchronization

  TC3->COUNT8.PER.reg = 0xFF;                      // Set period register to 255
  while (TC3->COUNT8.STATUS.bit.SYNCBUSY);         // Wait for synchronization

  TC3->COUNT8.INTENSET.reg = /*TC_INTENSET_MC1 | TC_INTENSET_MC0 |*/ TC_INTENSET_OVF; // Enable TC3 interrupts
 
  //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 TC3 to Nested Vector Interrupt Controller (NVIC)

  // Set the TC3 timer to tick at 2MHz, or in other words a period of 0.5us - timer overflows every 128us
  // timer counts up to (up to 255 in 128us)
  TC3->COUNT8.CTRLA.reg |= TC_CTRLA_PRESCALER_DIV8 |      // Set prescaler to 8, 16MHz/8 = 2MHz
                           TC_CTRLA_PRESCSYNC_PRESC |     // Set the reset/reload to trigger on prescaler clock
                           TC_CTRLA_MODE_COUNT8;          // Set the counter to 8-bit mode
                           
  TC3->COUNT8.CTRLA.bit.ENABLE = 1;               // Enable TC3
  while (TC3->COUNT8.STATUS.bit.SYNCBUSY);        // Wait for synchronization

  TC3->COUNT8.READREQ.reg = TC_READREQ_RCONT |            // Enable a continuous read request
                            TC_READREQ_ADDR(0x10);        // Offset of the 8 bit COUNT register
  while (TC3->COUNT8.STATUS.bit.SYNCBUSY);        // Wait for (read) synchronization
}

void loop() {
  Serial.println(micros2());                      // Testing the micros2() function
  delay(1000);                                    // Wait 1 second
}

// Micros2 is used to measure the receiver pulsewidths down to 1us accuracy
uint32_t micros2()
{
  uint32_t m;
  uint8_t t;
     
  noInterrupts();                                 // Disable interrupts
  m = timer2Counter;                              // Get the number of overflows
  t = TC3->COUNT8.COUNT.reg;                      // Get the current TC3 count value

  if (TC3->COUNT8.INTFLAG.bit.OVF && (t < 255))   // Check if the timer has just overflowed (and we've missed it)
  {
    m++;                                          // Then in this case increment the overflow counter
  } 
  interrupts();                                   // Enable interrupts
  return ((m << 8) + t) / 2;                      // Return the number of microseconds that have occured since the timer started
}

// This ISR is called every 128us
void TC3_Handler()           // ISR TC3 overflow callback function
{
  if (TC3->COUNT8.INTFLAG.bit.OVF)
  {
    timer2Counter++;           // Increment the overflow counter
  }
  TC3->COUNT8.INTFLAG.reg = TC_INTFLAG_OVF;   // Rest the overflow interrupt flag
}
Title: Re: Arduino Zero TCC Capture
Post by: mladek89 on Oct 19, 2018, 02:13 pm
Hi Martin,

wow cool... I will try it out.. thank you very much!
I will let you know if it helped or not.

Best regards,
Alex
Title: Re: Arduino Zero TCC Capture
Post by: mwolter on Oct 20, 2018, 07:20 am
Hello MartinL,
A couple of months ago you graciously assisted with generating a variable PWM frequency on pins that use TCC0 (Pin 3) and TCC1 (Pin 8). Using the same MCU, I am attempting to read the duty cycle of a 45hz PWM signal and running into a few issues.

First, the CPU is already a little taxed (SPI, calculating PID, sending serial data, etc.) and is producing erratic readings. Second, it does not appear that the examples that use TCCx will work since they will require feeding a different clock frequency to TCC0 and TCC1.

The basic pulseIn function worked but it added a 13ms delay to the loop processing that I am trying to avoid.

I saw that you were able to free the CPU by using DMAC in post #102.  Is it possible to use DMAC for TC3 example you posted?

Requirements: Read the duty cycle of a 45hz, not interested in frequency, utilize DMAC, utilize TCx.

Appreciate the help!
Title: Re: Arduino Zero TCC Capture
Post by: MartinL on Oct 20, 2018, 10:08 am
Hi mwolter,

What sort of accuracy do you require the pulse width (duty-cycle) measurement? This essentially determines the timer's speed. The examples earlier in this thread are designed to read a radio controlled receiver, accurate to 1 microsecond, would that be enough for your project?

It might also be possible to use timer TCC2, if you're not using it already?

Also, which pin would you like to receive the incoming signal, any pin can be used except the Non-Maskable Interrupt (NMI), which is digital pin 2 on the Arduino Zero and digital pin 4 on the M0 Pro/M0.

The pulseIn() function blocks while waiting for the pulse, thereby making it unsuitable for time critical projects.

Title: Re: Arduino Zero TCC Capture
Post by: mwolter on Oct 20, 2018, 04:58 pm
One microsecond should be more than enough. Not using TCC2 as far as I can tell. Pin 12 is available. Also is 11 if it is required to use TCC2.
Title: Re: Arduino Zero TCC Capture
Post by: MartinL on Oct 22, 2018, 09:47 am
Hi mwolter,

Here's the code that reads the period and pulse width of a 45Hz signal on D12 with TCC2/DMAC and outputs the values in microseconds (us) to the console:

Code: [Select]
// Setup TCC2 to capture pulse-width and period with DMAC transfer
volatile uint32_t dmacPeriod;
volatile uint32_t dmacPulsewidth;

typedef struct                              // DMAC descriptor structure
{
  uint16_t btctrl;
  uint16_t btcnt;
  uint32_t srcaddr;
  uint32_t dstaddr;
  uint32_t descaddr;
} dmacdescriptor ;

volatile dmacdescriptor wrb[12] __attribute__ ((aligned (16)));               // Write-back DMAC descriptors
dmacdescriptor descriptor_section[12] __attribute__ ((aligned (16)));         // DMAC channel descriptors
dmacdescriptor descriptor __attribute__ ((aligned (16)));                     // Place holder descriptor

void setup()
{
  SerialUSB.begin(115200);                  // Send data back on the Zero's native port
  while(!SerialUSB);                        // Wait for the SerialUSB port to be ready
 
  DMAC->BASEADDR.reg = (uint32_t)descriptor_section;                // Set the descriptor section base address
  DMAC->WRBADDR.reg = (uint32_t)wrb;                                // Set the write-back descriptor base adddress
  DMAC->CTRL.reg = DMAC_CTRL_DMAENABLE | DMAC_CTRL_LVLEN(0xf);      // Enable the DMAC and priority levels
 
  DMAC->CHID.reg = DMAC_CHID_ID(0);                                 // Select DMAC channel 0
  // Set DMAC channel 1 to priority level 0 (lowest), to trigger on TCC2 match compare 1 and to trigger every beat
  DMAC->CHCTRLB.reg = DMAC_CHCTRLB_LVL(0) | DMAC_CHCTRLB_TRIGSRC(TCC2_DMAC_ID_MC_0) | DMAC_CHCTRLB_TRIGACT_BEAT;
  //DMAC->CHINTENSET.reg = DMAC_CHINTENSET_MASK ;                   // Enable all DMAC interrupts
  descriptor.descaddr = (uint32_t)&descriptor_section[0];           // Set up a circular descriptor
  descriptor.srcaddr = (uint32_t)&TCC2->CC[0].reg;                  // Take the contents of the TCC2 counter comapare 0 register
  descriptor.dstaddr = (uint32_t)&dmacPeriod;                       // Copy it to the "dmacPeriod" variable
  descriptor.btcnt = 1;                                             // This takes one beat
  descriptor.btctrl = DMAC_BTCTRL_BEATSIZE_HWORD | DMAC_BTCTRL_VALID;   // Copy 16-bits (HWORD) and flag discriptor as valid
  memcpy(&descriptor_section[0], &descriptor, sizeof(dmacdescriptor));  // Copy to the channel 0 descriptor
 
  DMAC->CHID.reg = DMAC_CHID_ID(1);                                 // Select DMAC channel 1
  // Set DMAC channel 1 to priority level 0 (lowest), to trigger on TCC2 match compare 1 and to trigger every beat
  DMAC->CHCTRLB.reg = DMAC_CHCTRLB_LVL(0) | DMAC_CHCTRLB_TRIGSRC(TCC2_DMAC_ID_MC_1) | DMAC_CHCTRLB_TRIGACT_BEAT;
  //DMAC->CHINTENSET.reg = DMAC_CHINTENSET_MASK ;                   // Enable all DMAC interrupts
  descriptor.descaddr = (uint32_t)&descriptor_section[1];           // Set up a circular descriptor
  descriptor.srcaddr = (uint32_t)&TCC2->CC[1].reg;                  // Take the contents of the TCC2 counter comapare 0 register
  descriptor.dstaddr = (uint32_t)&dmacPulsewidth;                   // Copy it to the "dmacPulseWidth" variable
  descriptor.btcnt = 1;                                             // This takes 1 beat
  descriptor.btctrl = DMAC_BTCTRL_BEATSIZE_HWORD | DMAC_BTCTRL_VALID;    // Copy 16-bits (HWORD) and flag discriptor as valid
  memcpy(&descriptor_section[1], &descriptor, sizeof(dmacdescriptor));   // Copy to the channel 1 descriptor
 
  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 = 48MHz/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;

  //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 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_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

  TCC2->EVCTRL.reg |= TCC_EVCTRL_MCEI1 |            // Enable the match or capture channel 1 event input
                      TCC_EVCTRL_MCEI0 |            //.Enable the match or capture channel 0 event input
                      TCC_EVCTRL_TCEI1 |            // Enable the TCC event 1 input
                      /*TCC_EVCTRL_TCINV1 |*/       // Invert the event 1 input         
                      TCC_EVCTRL_EVACT1_PPW;        // Set up the timer for capture: CC0 period, CC1 pulsewidth                                   
 
  TCC2->CTRLA.reg |= TCC_CTRLA_CPTEN1 |             // Enable capture on CC1
                     TCC_CTRLA_CPTEN0 |             // Enable capture on CC0
                     TC_CTRLA_PRESCSYNC_PRESC |     // Set the reset/reload to trigger on prescaler clock
                     TCC_CTRLA_PRESCALER_DIV16;     // Set prescaler to 16, 16MHz/16 = 1MHz
                   
  TCC2->CTRLA.bit.ENABLE = 1;
  while (TCC2->SYNCBUSY.bit.ENABLE);                // Wait for synchronization

  DMAC->CHID.reg = DMAC_CHID_ID(0);                 // Select DMAC channel 0
  DMAC->CHCTRLA.reg |= DMAC_CHCTRLA_ENABLE;         // Enable DMAC channel 0
  DMAC->CHID.reg = DMAC_CHID_ID(1);                 // Select DMAC channel 1
  DMAC->CHCTRLA.reg |= DMAC_CHCTRLA_ENABLE;         // Enable DMAC channel 1
}

void loop()
{
  SerialUSB.print(dmacPeriod);                      // Output the results
  SerialUSB.print(F("   "));
  SerialUSB.println(dmacPulsewidth);
}

If you only require pulse width, then it's possible to just comment out the period DMAC channel.
Title: Re: Arduino Zero TCC Capture
Post by: mwolter on Oct 22, 2018, 04:16 pm
Thank you Martin! Appreciate the help and will test it today.
Title: Re: Arduino Zero TCC Capture
Post by: mladek89 on Oct 22, 2018, 08:08 pm
Hi Martin,

thank you soooo much, it is working now!
You are so right, the problem was the micros() function of the MKR1010.
It was working as soon as I replaced the micros() against your awesome micros2() function inside the NewRemoteSwitch library.

But I do not understand the whole code of you... How does the TC3_handler function() work?.. How will that function be called?
Do you have some literature to understand that assembler code? I would like to understand that code.

Thank you very much!

With best regards,
Alexander

Title: Re: Arduino Zero TCC Capture
Post by: mladek89 on Dec 14, 2018, 02:42 pm
Dear Martin,

is it possible to use your micros2() function for two libraries in parallel?
My problem is, if I am using two different libraries for reading different 433Mhz signals I would need in both libraries the new micros2() function.

If I am using the same function, I get an error message that some functions has been used already.
Do I need to set a second timer for that purpose?

Thank you so much for your help!

With best regards
Alexander
Title: Re: Arduino Zero TCC Capture
Post by: MartinL on Dec 17, 2018, 09:57 am
Hi Alexander,

To use the micros2() function in two different libraries it's necessary to declare the function in a header file, say "micros2.h". The function declaration just specifies its parameters and return value, terminated with a semicolon, (rather than the function definition that contains the function's code in the curly brackets {}):

Code: [Select]
extern unsigned long micros2(void);
This header file can be placed in the Arduino IDE's "libraries" directory (where the sketches are stored by default). The file can then be included at the beginning of both libraries:

Code: [Select]
#include <micros2.h>
Doing this essentially defers the resolution of the micros2() function from compiler to the linker, allowing the function to be called by both libraries.

For example, if you look at the standard micros() code, you'll see that the function is declared in the Arduino core file "delay.h":

Code: [Select]
#ifdef __cplusplus
extern "C" {
#endif

// ...

extern unsigned long micros( void ) ;

// ...

#ifdef __cplusplus
}
#endif

...but defined in the file "delay.c":

Code: [Select]
unsigned long micros( void )
{
  uint32_t ticks, ticks2;
  uint32_t pend, pend2;
  uint32_t count, count2;

  ticks2  = SysTick->VAL;
  pend2   = !!(SCB->ICSR & SCB_ICSR_PENDSTSET_Msk)  ;
  count2  = _ulTickCount ;

  do
  {
    ticks=ticks2;
    pend=pend2;
    count=count2;
    ticks2  = SysTick->VAL;
    pend2   = !!(SCB->ICSR & SCB_ICSR_PENDSTSET_Msk)  ;
    count2  = _ulTickCount ;
  } while ((pend != pend2) || (count != count2) || (ticks < ticks2));

  return ((count+pend) * 1000) + (((SysTick->LOAD  - ticks)*(1048576/(VARIANT_MCK/1000000)))>>20) ;
  // this is an optimization to turn a runtime division into two compile-time divisions and
  // a runtime multiplication and shift, saving a few cycles
}
Title: Re: Arduino Zero TCC Capture
Post by: mladek89 on Dec 19, 2018, 10:26 pm
Hi Martin,

thank you so much for your help!
I have tried to write your Code as .h and .cpp files.

But the problem is that it didn't know the GCLK etc. commands.
error: 'GCLK' was not declared in this scope
error: 'GCLK_GENDIV_DIV' was not declared in this scope etc.

I am already using the 1.8.7 Arduino.cc IDE.

Do you know where the problem is? Are these files correct written?

header file looks like:
Code: [Select]

#ifndef micros2_h
#define micros2_h

#include <Arduino.h>


class micros2{
public:
micros2();
~micros2();
void micros_2();
void TC3_Handler();
};


#endif


cpp File looks like this:
Code: [Select]
#include "micros2.h"
#include <Arduino.h>

micros2::micros2(){
  // Set up the generic clock (GCLK4) used to clock timers
  GCLK->GENDIV.reg = GCLK_GENDIV_DIV(3) |          // Divide the 48MHz clock source by divisor 3: 48MHz/3=16MHz
                    GCLK_GENDIV_ID(4);             // Select Generic Clock (GCLK) 4
  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 GCLK4
                      GCLK_GENCTRL_SRC_DFLL48M |   // Set the 48MHz clock source
                      GCLK_GENCTRL_ID(4);          // Select GCLK4
  while (GCLK->STATUS.bit.SYNCBUSY);               // Wait for synchronization
 
  // Feed GCLK4 to TCC2 (and TC3)
  GCLK->CLKCTRL.reg = GCLK_CLKCTRL_CLKEN |         // Enable GCLK4 to TCC2 (and TC3)
                      GCLK_CLKCTRL_GEN_GCLK4 |     // Select GCLK4
                      GCLK_CLKCTRL_ID_TCC2_TC3;    // Feed GCLK4 to TCC2 (and TC3)
  while (GCLK->STATUS.bit.SYNCBUSY);               // Wait for synchronization

  TC3->COUNT8.PER.reg = 0xFF;                      // Set period register to 255
  while (TC3->COUNT8.STATUS.bit.SYNCBUSY);         // Wait for synchronization

  TC3->COUNT8.INTENSET.reg = /*TC_INTENSET_MC1 | TC_INTENSET_MC0 |*/ TC_INTENSET_OVF; // Enable TC3 interrupts
 
  //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 TC3 to Nested Vector Interrupt Controller (NVIC)

  // Set the TC3 timer to tick at 2MHz, or in other words a period of 0.5us - timer overflows every 128us
  // timer counts up to (up to 255 in 128us)
  TC3->COUNT8.CTRLA.reg |= TC_CTRLA_PRESCALER_DIV8 |      // Set prescaler to 8, 16MHz/8 = 2MHz
                           TC_CTRLA_PRESCSYNC_PRESC |     // Set the reset/reload to trigger on prescaler clock
                           TC_CTRLA_MODE_COUNT8;          // Set the counter to 8-bit mode
                           
  TC3->COUNT8.CTRLA.bit.ENABLE = 1;               // Enable TC3
  while (TC3->COUNT8.STATUS.bit.SYNCBUSY);        // Wait for synchronization

  TC3->COUNT8.READREQ.reg = TC_READREQ_RCONT |            // Enable a continuous read request
                            TC_READREQ_ADDR(0x10);        // Offset of the 8 bit COUNT register
  while (TC3->COUNT8.STATUS.bit.SYNCBUSY);        // Wait for (read) synchronization
}

micros2::~micros2(){/*nothing to destruct*/}

volatile unsigned long timer2Counter;

// Micros2 is used to measure the receiver pulsewidths down to 1us accuracy
void micros2::micros_2()
{
  uint32_t m;
  uint8_t t;
     
  noInterrupts();                                 // Disable interrupts
  m = timer2Counter;                              // Get the number of overflows
  t = TC3->COUNT8.COUNT.reg;                      // Get the current TC3 count value

  if (TC3->COUNT8.INTFLAG.bit.OVF && (t < 255))   // Check if the timer has just overflowed (and we've missed it)
  {
    m++;                                          // Then in this case increment the overflow counter
  } 
  interrupts();                                   // Enable interrupts
  return ((m << 8) + t) / 2;                      // Return the number of microseconds that have occured since the timer started
}

// This ISR is called every 128us
void micros2::TC3_Handler()           // ISR TC3 overflow callback function
{
  if (TC3->COUNT8.INTFLAG.bit.OVF)
  {
    timer2Counter++;           // Increment the overflow counter
  }
  TC3->COUNT8.INTFLAG.reg = TC_INTFLAG_OVF;   // Rest the overflow interrupt flag
}




Thank you in advance with best regards
Alex
Title: Re: Arduino Zero TCC Capture
Post by: MartinL on Dec 20, 2018, 10:33 am
Hi Alex,

Including the "Arduino.h" header file like you've done, should be enough to make the CMSIS (Cortex Microcontroller Software Interface Standard) register definitions such as GCLK, TC3 etc... visible to your sketch.

When writing small C++ programs, I just use a simple editor such as Notepad++, then place the .h and .cpp files in a folder of the same name and deposit them in the Arduino IDE libraries directory. It's then possible to include the header file in your sketch and compile using the Arduino IDE. If there are any errors in your C++ code the compiler in the Arduino IDE will flag them up. This should allow the register definitions to become visible in your sketch.

In your code, I suggest removing #include <Arduino.h> line from your .cpp file, as it's already been included in the header file. Also, the micros2() function should return an unsigned long value.

The TC3_Handler() function has already been declared outside the scope of you C++ class, in the Atmel CMSIS "samd21g18a.h" file:

Code: [Select]
//...
void TCC0_Handler                ( void );
void TCC1_Handler                ( void );
void TCC2_Handler                ( void );
void TC3_Handler                 ( void );
void TC4_Handler                 ( void );
void TC5_Handler                 ( void );
//...

One way to implement this to create a static C++ member function, say: static void TC3_IrqHandler(), then call this from the TC3_Handler() function at the bottom of the .cpp file:

Code: [Select]
void TC3_Handler()            // TC3 Interrupt Service Routine
{
  micros2::TC3_IrqHandler();  // Call the micros2() interrupt handler function
}

Another option is to declare a micro2() object, by including the line:

Code: [Select]
extern micros2 micros2;
... at the bottom of the .h file, then instantiate (define) it at the bottom of the .cpp file:

Code: [Select]
micros2 micros2();
... following this you can just call on the non-static C++ member function: void TC3_IrqHandler(), also at the bottom of the .cpp file:

Code: [Select]
void TC3_Handler()            // TC3 Interrupt Service Routine
{
  micros2.TC3_IrqHandler();  // Call the micros2() interrupt handler function
}
Title: Re: Arduino Zero TCC Capture
Post by: mladek89 on Dec 23, 2018, 11:57 pm
Hi Martin,

thank you soo much for your support and help!
I haven't tried your hints but I will do that in the next days for sure.

I wish you a Merry Christmas and a Happy New Year!

Best regards,
Alex
Title: Re: Arduino Zero TCC Capture
Post by: mladek89 on Jan 02, 2019, 11:31 pm
Hi Martin,

I am very happy to let you know that its working now :D
thank you sooo much for your help!

With best regards,
Alex
Title: Re: Arduino Zero TCC Capture
Post by: mladek89 on Jan 17, 2019, 03:01 pm
Hi Martin,

At the moment, both receiver libraries working with ARDUINO MKR 1010 Wifi, the ISR callback functions and the micros2() function but for some mysterious reasons, the attachInterrupt - ISR functions stop working after about 30-40 mins. All other functions like webserver combined with transmitting signals etc. is still running. Just the receiving part with the micros2() and the attachInterrupt() function is not working anymore. I am using the CHANGE mode for the attachInterrupt() and I have directly connected the receiver output wire directly to two input pins of the microcontroller. One pin is for one receiver library and the other pin for the other library. So I am using two ISR callback functions at the same time with the same micros2() function. Do I need to reset the interrupt flag or something else? At the ARDUINO YUN it worked over years without any problem. Do you know what the reason could be?

Thank you very much!
With best regards
Alex
Title: Re: Arduino Zero TCC Capture
Post by: MartinL on Jan 17, 2019, 10:53 pm
Hi Alex,

It shouldn't be necessary to write to the interrupt flags to clear them, as this is done automatically by the Arduino code, (in the EIC_Handler() interrupt service routine).

Could it be the micros2() counter rolling over? Roll overs need to be handled in a particular way. An excellent explanation is provided on Nick Gammon's website: http://www.gammon.com.au/forum/?id=12127 (http://www.gammon.com.au/forum/?id=12127).

The micros2() function rolls over every 9 minutes or so.

EDIT: This is wrong, actually the micros2() function rolls over every 70 minutes or so like micros().

Kind regards,
Martin

Title: Re: Arduino Zero TCC Capture
Post by: mladek89 on Jan 18, 2019, 02:45 pm
Hi Martin,

thank you for the reply.
I am using that function for the micros2:

Code: [Select]

volatile unsigned long timer2Counter;

// Micros2 is used to measure the receiver pulsewidths down to 1us accuracy
uint32_t micros2()
{
  uint32_t m;
  uint8_t t;
     
  noInterrupts();                                 // Disable interrupts
  m = timer2Counter;                              // Get the number of overflows
  t = TC3->COUNT8.COUNT.reg;                      // Get the current TC3 count value

  if (TC3->COUNT8.INTFLAG.bit.OVF && (t < 255))   // Check if the timer has just overflowed (and we've missed it)
  {
    m++;                                          // Then in this case increment the overflow counter
  }
  interrupts();                                   // Enable interrupts
  return ((m << 8) + t) / 2;                      // Return the number of microseconds that have occured since the timer started
}

// This ISR is called every 128us
void TC3_Handler()           // ISR TC3 overflow callback function
{
  if (TC3->COUNT8.INTFLAG.bit.OVF)
  {
    timer2Counter++;           // Increment the overflow counter
  }
  TC3->COUNT8.INTFLAG.reg = TC_INTFLAG_OVF;   // Rest the overflow interrupt flag
}


Do you think there could the problem be?

Best regards
Alex
Title: Re: Arduino Zero TCC Capture
Post by: MartinL on Jan 18, 2019, 03:14 pm
Hi Alex,

Thanks for the code snippet.

Have you also set-up the continous read synchronization in the setup() portion of the code?:

Code: [Select]
REG_TC3_READREQ = TC_READREQ_RCONT |            // Enable a continuous read request
                  TC_READREQ_ADDR(0x10);        // Offset of the 8 bit COUNT register
while (TC3->COUNT8.STATUS.bit.SYNCBUSY);        // Wait for (read) synchronization

Might I ask how you're using or calling the micros2() function in your attachInterrupt()'s callback function?

Kind regards,
Martin
Title: Re: Arduino Zero TCC Capture
Post by: mladek89 on Jan 18, 2019, 04:39 pm
Hi Martin,

I think I added the important code snippet you described. I used the following code:

Code: [Select]
  //***********************************************************************************
  // Micros2 function for ARDUINO MKR SAMD21 Processor
  //***********************************************************************************

  // Set up the generic clock (GCLK4) used to clock timers
  GCLK->GENDIV.reg = GCLK_GENDIV_DIV(3) |          // Divide the 48MHz clock source by divisor 3: 48MHz/3=16MHz
                    GCLK_GENDIV_ID(4);             // Select Generic Clock (GCLK) 4
  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 GCLK4
                      GCLK_GENCTRL_SRC_DFLL48M |   // Set the 48MHz clock source
                      GCLK_GENCTRL_ID(4);          // Select GCLK4
  while (GCLK->STATUS.bit.SYNCBUSY);               // Wait for synchronization
 
  // Feed GCLK4 to TCC2 (and TC3)
  GCLK->CLKCTRL.reg = GCLK_CLKCTRL_CLKEN |         // Enable GCLK4 to TCC2 (and TC3)
                      GCLK_CLKCTRL_GEN_GCLK4 |     // Select GCLK4
                      GCLK_CLKCTRL_ID_TCC2_TC3;    // Feed GCLK4 to TCC2 (and TC3)
  while (GCLK->STATUS.bit.SYNCBUSY);               // Wait for synchronization

  TC3->COUNT8.PER.reg = 0xFF;                      // Set period register to 255
  while (TC3->COUNT8.STATUS.bit.SYNCBUSY);         // Wait for synchronization

  TC3->COUNT8.INTENSET.reg = /*TC_INTENSET_MC1 | TC_INTENSET_MC0 |*/ TC_INTENSET_OVF; // Enable TC3 interrupts
 
  //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 TC3 to Nested Vector Interrupt Controller (NVIC)

  // Set the TC3 timer to tick at 2MHz, or in other words a period of 0.5us - timer overflows every 128us
  // timer counts up to (up to 255 in 128us)
  TC3->COUNT8.CTRLA.reg |= TC_CTRLA_PRESCALER_DIV8 |      // Set prescaler to 8, 16MHz/8 = 2MHz
                           TC_CTRLA_PRESCSYNC_PRESC |     // Set the reset/reload to trigger on prescaler clock
                           TC_CTRLA_MODE_COUNT8;          // Set the counter to 8-bit mode
                           
  TC3->COUNT8.CTRLA.bit.ENABLE = 1;               // Enable TC3
  while (TC3->COUNT8.STATUS.bit.SYNCBUSY);        // Wait for synchronization

  TC3->COUNT8.READREQ.reg = TC_READREQ_RCONT |            // Enable a continuous read request
                            TC_READREQ_ADDR(0x10);        // Offset of the 8 bit COUNT register
  while (TC3->COUNT8.STATUS.bit.SYNCBUSY);        // Wait for (read) synchronization

  //***********************************************************************************
  //***********************************************************************************
 


I tried the two receiver libraries with the micros2() function without any other additional services like webserver etc. and now it seems to work for one hour now. I will try to figure it out if it works for a longer period of time.
Title: Re: Arduino Zero TCC Capture
Post by: mladek89 on Jan 18, 2019, 05:19 pm
Hi Martin,

I have to revise the previous findings. After about 60-70mins the interrupt function didn't work anymore. So it is still the problem of the receiving libraries or the micros2() function.

The attachedInterrupt calls a function of the receiver class which contains the micros2() function: (it is just a code snippet of the function)

Code: [Select]

static byte receivedBit; // Contains "bit" currently receiving
static NewRemoteCode receivedCode; // Contains received code
static NewRemoteCode previousCode; // Contains previous received code
static byte repeats = 0; // The number of times the an identical code is received in a row.
static unsigned long edgeTimeStamp[3] = {0, }; // Timestamp of edges
static unsigned int min1Period, max1Period, min5Period, max5Period;
static bool skip;

// Filter out too short pulses. This method works as a low pass filter.
edgeTimeStamp[1] = edgeTimeStamp[2];
edgeTimeStamp[2] = micros2();

if (skip) {
skip = false;
return;
}

if (_state >= 0 && edgeTimeStamp[2]-edgeTimeStamp[1] < min1Period) {
// Last edge was too short.
// Skip this edge, and the next too.
skip = true;
return;
}

unsigned int duration = edgeTimeStamp[1] - edgeTimeStamp[0];
edgeTimeStamp[0] = edgeTimeStamp[1];


Best regards
Title: Re: Arduino Zero TCC Capture
Post by: MartinL on Jan 18, 2019, 11:17 pm
Hi Alex,

I mistakenly thought that the micros2() function rolled over after approximately 9 minutes, but actually the roll over is the same as the micros() function, around 70 minutes (1us * 2^32). This coincidentally matches your 60-70min of run time.

I ran a test comparing the output of both micros() and micros2() functions for over 90 minutes. Both rolled over without any issues and remained in sync throughout.

This suggests that the issue is in the code that calls the micros2() function, rather than within the function itself.

Kind regards,
Martin
Title: Re: Arduino Zero TCC Capture
Post by: mladek89 on Jan 21, 2019, 08:34 pm
Hi Martin,

thank you very much for running tests to identify the problem!
I didn't found the problem yet, but I will let you know if I find something new.

With best regards
Alexander
Title: Re: Arduino Zero TCC Capture
Post by: mladek89 on Jan 22, 2019, 12:11 am
Hi Martin,

at the moment I am using for the micros2() function the following .h file:

Code: [Select]
#ifndef micros2_h
#define micros2_h

#include <Arduino.h>

#ifdef __cplusplus
extern "C" {
#endif


// ...

extern unsigned long micros2( void );

// ...




#ifdef __cplusplus
}
#endif

#endif


and for the .cpp file:

Code: [Select]
#include "micros2.h"

//***********************************************************************************
// Micros2 function for ARDUINO MKR SAMD21 Processor
//***********************************************************************************

volatile unsigned long timer2Counter;

// Micros2 is used to measure the receiver pulsewidths down to 1us accuracy
unsigned long micros2( void )
{
  uint32_t m;
  uint8_t t;
     
  noInterrupts();                                 // Disable interrupts
  m = timer2Counter;                              // Get the number of overflows
  t = TC3->COUNT8.COUNT.reg;                      // Get the current TC3 count value

  if (TC3->COUNT8.INTFLAG.bit.OVF && (t < 255))   // Check if the timer has just overflowed (and we've missed it)
  {
    m++;                                          // Then in this case increment the overflow counter
  } 
  interrupts();                                   // Enable interrupts
  return ((m << 8) + t) / 2;                      // Return the number of microseconds that have occured since the timer started
}

// This ISR is called every 128us
void TC3_Handler()                    // ISR TC3 overflow callback function
{
  if (TC3->COUNT8.INTFLAG.bit.OVF)
  {
    timer2Counter++;                             // Increment the overflow counter
  }
  TC3->COUNT8.INTFLAG.reg = TC_INTFLAG_OVF;      // Rest the overflow interrupt flag
}   

//***********************************************************************************
//***********************************************************************************


Do you think there is a mistake inside the code?
The thing is, I used for the receiver functions the micros() instead of the micros2() function with an Arduino YUN and it is working without any problems..
As soon as I am just using the micros2() and the Arduino MKR 1010 Wifi it is working but just for 60-70mins..

Thank you in advance.
With best regards
Alex
Title: Re: Arduino Zero TCC Capture
Post by: MartinL on Jan 22, 2019, 11:21 am
Hi Alex,

The micros2() code is essentially the same as the micros() code used by the Arduino Yun, except that the SAMD21's timer is running 8 times faster to provide an accuracy of 0.5us rather than 4us.

The timer will rollover after 71 minutes, 35 seconds.

Is your code failing at a roughly between 60-70 minutes, or does it occur at an exact moment everytime?

What is the output of the micros2() function at the time of failure?

Kind regards,
Martin
Title: Re: Arduino Zero TCC Capture
Post by: mladek89 on Jan 28, 2019, 12:08 am
Hi Martin,

sorry for the late reply.
I tried out a very simple code. I called every second the micros2() function without any additional functions inside loop. (no attachInterrupt function and no receiver functions)

Code: [Select]
  calls++;

  t = micros2();

  Serial.println("calls of micros function: ");
  Serial.println(calls);

  Serial.println(" ");
  Serial.println("micros output: ");
  Serial.println(t);

  delay(1000);



The output looks like follows:

...
micros output:
2147413951
calls of micros function:
2144

micros output:
930303
calls of micros function:
2145
...

-> the first rollover occurred after about 35.79 seconds.

...
micros output:
2146930303
calls of micros function:
4291

micros output:
930303
calls of micros function:
4292

-> the second rollover occurred after about 35.78 seconds.

the micros2() function works without any problems and after several rollovers.
I do not know why it occurs at every 35.7 seconds and not every 71,3 seconds.
But it works.

So the next tests will be, to call the micros2() function inside the receiver libraries.
The whole code with the receiver libraries called via an attachInterrupt function fails roughly between 60-70 minutes.

I will let you know if I will find something new.
Title: Re: Arduino Zero TCC Capture
Post by: MartinL on Jan 28, 2019, 09:55 am
Hi Alex,

Yes you're right, the micros2() function is rolling over every 35.7 minutes.

I missed this in my first test, because I only looked the results only after 71.3 minutes. At which point both outputs had rolled over (the micros2() function for the second time) and were therefore the same.

The reason is the return value of the micros2() function:

Code: [Select]
return ((m << 8) + t) / 2;
In this instance we're counting the number of 0.5us timer ticks and dividing by 2. The problem is that the 32-bit unsigned integer output overflows before the division by 2 occurs.

The Arduino Yun by comparison is counting the number of 4us timer ticks, then multiplying by 4:

Code: [Select]
return ((m << 8) + t) * 4;
...therefore this overflow issue doesn't happen.

To make the micros2() and micro() function rollover at the same time, is necessary to adjust the micros2() function to count 1us increments.

Just change the micros2() return value line, to remove the divide by 2:

Code: [Select]
return ((m << 8) + t);
...and slow down the timer by increasing its prescaler divider from 8 to 16:

Code: [Select]
TC3->COUNT8.CTRLA.reg |= TC_CTRLA_PRESCALER_DIV16 |     // Set prescaler to 16, 16MHz/16 = 1MHz
                         TC_CTRLA_PRESCSYNC_PRESC |     // Set the reset/reload to trigger on prescaler clock
                         TC_CTRLA_MODE_COUNT8;          // Set the counter to 8-bit mode

I don't think that the rollover time is the reason for your code not functioning after 60-70 minutes though. The only time this could become an issue, is if your time between calling the micros2() function is greater than 35.7 minutes, (or 71.3 minutes with the code adjustments above).
Title: Re: Arduino Zero TCC Capture
Post by: mladek89 on Feb 03, 2019, 11:08 am
Hi Martin,

I tried it out and changed the counting and the prescaler as you mentioned before.
The whole code incl. the receiver libraries still running for 15 hours without any problem now.

Thank you soo much for your help :D

Do you think this settings influence the attachinterrupt function of the MKR1010 in some case?

With best regards,
Alex
Title: Re: Arduino Zero TCC Capture
Post by: MartinL on Feb 03, 2019, 06:21 pm
Hi Alex,

Glad to hear that it's now working.

Quote
Do you think this settings influence the attachinterrupt function of the MKR1010 in some case?
I think the problem was caused by the micros2() function, but occured when subtracting the edgeTimeStamps[].

It's something that I hadn't considered until now, but subtracting two edgeTimeStamps[] unsigned long  values to get the time difference (in microseconds), only works if the micros2() function also rolls over at the unsigned long  maximum value. If the micros2() function rolls over too early and you subtract the unsigned long  variables, you get some horrendously large value instead of the difference between them.

Now that the micros2() and the edgeTimeStamps[] variables are rolling over at the same point, your code should now function correctly.

Thanks Alex, looks like I'm going to have to change the micros2() function in my code as well.

Kind regards,
Martin
Title: Re: Arduino Zero TCC Capture
Post by: trampas on Feb 04, 2019, 10:05 pm
I am looking to count events using TCC2 (SAMD21), specifically I have a step and dir pin and would like the dir pin to change direction of count and the step pin to increment/decrease count.

I have not been able to get his working any if anyone might see my issue?

#define WAIT_TCC2_SYNC() while(TCC2->SYNCBUSY.reg)



void enableEIC(void)
{
    PM->APBAMASK.reg |= PM_APBAMASK_EIC;
   if (EIC->CTRL.bit.ENABLE == 0)
   {
      // Enable GCLK for IEC (External Interrupt Controller)
      GCLK->CLKCTRL.reg = (uint16_t) (GCLK_CLKCTRL_CLKEN | GCLK_CLKCTRL_GEN_GCLK0 | GCLK_CLKCTRL_ID(GCM_EIC));

      // Enable EIC
      EIC->CTRL.bit.ENABLE = 1;
      while (EIC->STATUS.bit.SYNCBUSY == 1) { }
   }
}



void setupStepEvent(void)
{
   //we will set up the EIC to generate an even on rising edge of step pin
   //make sure EIC is setup
   enableEIC();


   // Assign step pin to EIC
   // Step pin is PA11, EXTINT11
   pinPeripheral(PIN_STEP_INPUT, PIO_EXTINT);

   //set up the direction pin PA10 to trigger external interrupt
   pinPeripheral(PIN_DIR_INPUT, PIO_EXTINT); //EXTINT10


   //***** setup EIC ******
   EIC->EVCTRL.bit.EXTINTEO11=1; //enable event for EXTINT11
   EIC->EVCTRL.bit.EXTINTEO10=1; //enable event for EXTINT10
   //setup up external interurpt 11 to be rising edge triggered
   //setup up external interurpt 10 to be both edge triggered
   EIC->CONFIG[1].reg |= EIC_CONFIG_SENSE3_RISE | EIC_CONFIG_SENSE2_BOTH;


   //diable actually generating an interrupt, we only want event triggered
   EIC->INTENCLR.reg = EIC_INTENCLR_EXTINT11;
   EIC->INTENCLR.reg = EIC_INTENCLR_EXTINT10;

   //**** setup the event system ***
   // Enable GCLK for EVSYS channel 0
   PM->APBCMASK.reg |= PM_APBCMASK_EVSYS;

   GCLK->CLKCTRL.reg = (uint16_t) (GCLK_CLKCTRL_CLKEN | GCLK_CLKCTRL_GEN_GCLK0 | GCLK_CLKCTRL_ID(GCM_EVSYS_CHANNEL_0));
   while (GCLK->STATUS.bit.SYNCBUSY);
   GCLK->CLKCTRL.reg = (uint16_t) (GCLK_CLKCTRL_CLKEN | GCLK_CLKCTRL_GEN_GCLK0 | GCLK_CLKCTRL_ID(GCM_EVSYS_CHANNEL_1));
   while (GCLK->STATUS.bit.SYNCBUSY);

   //setup the step pin to trigger event 0 on the TCC2 (step)
   EVSYS->CHANNEL.reg=EVSYS_CHANNEL_CHANNEL(0)
                        | EVSYS_CHANNEL_EDGSEL_RISING_EDGE
                        | EVSYS_CHANNEL_EVGEN(EVSYS_ID_GEN_EIC_EXTINT_11)
                        | EVSYS_CHANNEL_PATH_ASYNCHRONOUS;

   EVSYS->USER.reg =    EVSYS_USER_CHANNEL(1)
                        | EVSYS_USER_USER(EVSYS_ID_USER_TCC2_EV_0);

   //setup the dir pin to trigger event 2 on the TCC2 (dir change)
   EVSYS->CHANNEL.reg=EVSYS_CHANNEL_CHANNEL(1)
                        | EVSYS_CHANNEL_EDGSEL_RISING_EDGE
                        | EVSYS_CHANNEL_EVGEN(EVSYS_ID_GEN_EIC_EXTINT_10)
                        | EVSYS_CHANNEL_PATH_ASYNCHRONOUS;

   EVSYS->USER.reg =    EVSYS_USER_CHANNEL(2)
                        | EVSYS_USER_USER(EVSYS_ID_USER_TCC2_EV_1);

   //**** setup the Timer counter ******
   PM->APBCMASK.reg |= PM_APBCMASK_TCC2;
   // Enable GCLK for TC4 and TC5 (timer counter input clock)
   GCLK->CLKCTRL.reg = (uint16_t) (GCLK_CLKCTRL_CLKEN | GCLK_CLKCTRL_GEN_GCLK0 | GCLK_CLKCTRL_ID(GCM_TCC2_TC3));
   while (GCLK->STATUS.bit.SYNCBUSY);



   TCC2->CTRLA.reg= TCC_CTRLA_SWRST;  //reset TCC2
   WAIT_TCC2_SYNC();
   while(TCC2->CTRLA.bit.SWRST ==1);

   TCC2->EVCTRL.reg=TCC_EVCTRL_EVACT0_COUNTEV | TCC_EVCTRL_EVACT1_DIR | TCC_EVCTRL_TCEI0 | TCC_EVCTRL_TCEI1;
   WAIT_TCC2_SYNC();

   TCC2->PER.reg=0x07FFFFF;
   WAIT_TCC2_SYNC();

   TCC2->COUNT.reg=0;
   WAIT_TCC2_SYNC();

   //TCC2->CTRLBSET.bit.CMD=TCC_CTRLBSET_CMD_RETRIGGER;
   checkDirPin();


   WAIT_TCC2_SYNC();
   TCC2->CTRLA.reg |=TCC_CTRLA_ENABLE;
   WAIT_TCC2_SYNC();


   checkDirPin();
}
Title: Re: Arduino Zero TCC Capture
Post by: trampas on Feb 04, 2019, 11:23 pm
I was not sending the resync command before reading counter.

Now that this fixed I am counting but the direction is always counting positive.
Title: Re: Arduino Zero TCC Capture
Post by: MartinL on Feb 05, 2019, 10:25 am
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
}

Title: Re: Arduino Zero TCC Capture
Post by: aromring on Feb 20, 2019, 01:53 am
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!