Arduino Zero TCC Capture

I've now raised the issue with Arduino developers on Github: Changes to attachInterrupt() prevent pin set-up with NULL callback pointer · Issue #309 · arduino/ArduinoCore-samd · GitHub.

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

TCC_PulseW_Period_Count.ino (5.85 KB)

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

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

Hi Daniel,

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.

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.

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:

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...
}

How to STOP / START timers individualy ?

To stop the timer:

REG_TCC0_CTRLBSET = TCC_CTRLBSET_CMD_STOP;
while(TCC0->SYNCBUSY.bit.CTRLB);

To re-start the timer again:

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

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):

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

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 (Changing Arduino Zero PWM Frequency - Arduino Zero - Arduino Forum)
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

Hi Daniel,

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

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

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

Hi MartinL,

Ok , understood and noted !

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

Kind Regards,

Daniel

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 (Changing Arduino Zero PWM Frequency - Arduino Zero - Arduino Forum)

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
40kHz_TCC1.ino (2.88 KB)

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:

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:

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

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 ?

Hi Daniel,

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:

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

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

Rx_TCC_PW_P_C_essai.ino (8.83 KB)

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

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:

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

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

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 .

Hi Daniel,

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.

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.

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

Hi parnal,

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

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

TCCCapture_DMAC_TCCount.ino (8.95 KB)