Go Down

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

MartinL

#105
Mar 05, 2018, 05:08 pm Last Edit: Mar 05, 2018, 05:15 pm by MartinL
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


MartinL

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

MartinL

#107
Mar 05, 2018, 11:30 pm Last Edit: Mar 05, 2018, 11:36 pm by MartinL
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

MartinL

#108
Mar 06, 2018, 09:47 am Last Edit: Mar 06, 2018, 09:55 am by MartinL
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
}

parnal

#109
Mar 06, 2018, 10:10 am Last Edit: Mar 06, 2018, 10:26 am by parnal
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 .



MartinL

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

MartinL

#111
Mar 06, 2018, 10:35 am Last Edit: Mar 06, 2018, 10:38 am by MartinL
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.

parnal

Hello Martin,

Thank you again, but that is where my problem is how to fed TC3 to TC4 internally ? :(

MartinL

#113
Mar 06, 2018, 11:27 am Last Edit: Mar 06, 2018, 11:31 am by MartinL
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.

parnal

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.

MartinL

#115
Mar 06, 2018, 11:49 am Last Edit: Mar 06, 2018, 01:18 pm by MartinL
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?

DR49

Many thanks Martin I'll try it on my application and be back to you soon


Kind Regards

Daniel

parnal

#117
Mar 06, 2018, 03:45 pm Last Edit: Mar 06, 2018, 04:11 pm by parnal
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

MartinL

#118
Mar 06, 2018, 06:07 pm Last Edit: Mar 06, 2018, 06:28 pm by MartinL
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.

DR49

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

Go Up