SAMD21 TCC PPW or PWP capture, how?

Go To Last Post
9 posts / 0 new
Author
Message
#1
  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

I haven't found a complete example of code for the use of TCC PPW/PWP

In Fig. 31-10 http://ww1.microchip.com/downloads/en/DeviceDoc/40001882A.pdf#page=545 it shows "external signal/event"

If we use an external signal, which input pins can one use, and how do we associate the TCC with the pin?

If we use events, what events generators are used, and which TCC users? How does the TCC know that one event is from the leading flank, and which from the trailing edge? 

The same method can also used for TC

Could someone point me in the right direction

Thanks, Jerry

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

If you haven't got a complete description then a short description of the necessary steps would be very useful

Jerry

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

External signal directly to the TC for capture is not possible, i.e., this looks like a documentation error:

 To enable the capture from the IO pin, the Capture On Pin x Enable bit in CTRLC register (CTRLC.COPENx) must be written to '1'. 

 D21 has no such bits in TC CTRLC.

So it has to be an event, typically it would be from EIC (i.e., from one of the EXTINT). From my testing it will only work when setting up an asynchronous event. You need only one event btw, the edge detection happens in the TCC (or TC). The event user should be obvious since there is only one place where you can configure PPW and PWP (EVACT1[2:0]: Timer/Counter Event Input 1 Action for TCC, and TC has only one input event with setting in EVACT[2:0]: Event Action).

The example captures 128 periods and pulse widths from PA04. Select TCC or TC with the define USE_TCC. Put a breakpoint on the done++ lines to see the result. The first capture should be skipped, i.e., cap0[0] and cap1[0] will not be valid.

#include "sam.h"
#include <stdlib.h>

static volatile uint16_t cap0[128]; // Period with PPW capture
static volatile uint16_t cap1[128]; // Pulse width with PPW capture
static volatile size_t nCap;
#define USE_TCC

void setupInput(void)
{
    PM->APBAMASK.bit.EIC_ = 1;

    GCLK->CLKCTRL.reg = GCLK_CLKCTRL_CLKEN | GCLK_CLKCTRL_GEN_GCLK0 | GCLK_CLKCTRL_ID(GCLK_CLKCTRL_ID_EIC);
    while (GCLK->STATUS.bit.SYNCBUSY);
    
    // Input on PA04
    PORT->Group[0].PMUX[4/2].bit.PMUXE = MUX_PA04A_EIC_EXTINT4;
    const PORT_PINCFG_Type pincfg = {
        .bit.PMUXEN = 1,
        .bit.INEN = 1,
        .bit.PULLEN = 1
    };
    PORT->Group[0].PINCFG[4].reg = pincfg.reg;
   
    EIC->CONFIG[0].bit.SENSE4 = EIC_CONFIG_SENSE4_HIGH_Val; // None of RISE,FALL or BOTH work for this
    EIC->EVCTRL.bit.EXTINTEO4 = 1;
    
    // The EIC interrupt is only for verifying the input
    //REG_EIC_INTENSET = EIC_INTFLAG_EXTINT4;
    //NVIC_EnableIRQ(EIC_IRQn);
    while (EIC->STATUS.reg & EIC_STATUS_SYNCBUSY);
    EIC->CTRL.bit.ENABLE = 1;                  
    while (EIC->STATUS.reg & EIC_STATUS_SYNCBUSY); 
}

#ifdef USE_TCC
void setupTimer(void)
{
    PM->APBCMASK.bit.TCC0_ = 1;

    REG_GCLK_CLKCTRL = GCLK_CLKCTRL_CLKEN | GCLK_CLKCTRL_GEN_GCLK0 | GCLK_CLKCTRL_ID_TCC0_TCC1;
    while (GCLK->STATUS.bit.SYNCBUSY);
    
    TCC0->CTRLA.bit.ENABLE = 0;
    while (TCC0->SYNCBUSY.bit.ENABLE);

    TCC0->CTRLA.reg |= TCC_CTRLA_PRESCALER_DIV1 | TCC_CTRLA_CPTEN0 | TCC_CTRLA_CPTEN1;
    TCC0->PER.reg = 0xFFFFFF;
    while (TCC0->SYNCBUSY.bit.PER);

    TCC0->EVCTRL.reg = TCC_EVCTRL_TCEI1 |  // enable input event
        TCC_EVCTRL_EVACT1_PPW; // event action = PPW
        // | TCC_EVCTRL_MCEI0 | TCC_EVCTRL_MCEI1;

    // Interrupts
    TCC0->INTENSET.bit.MC0 = 1;
    //TCC0->INTENSET.bit.MC1 = 1; // Not needed, can read out both in MC0 interrupt
    NVIC_EnableIRQ(TCC0_IRQn);

    // Enable TCC
    TCC0->CTRLA.bit.ENABLE = 1;
    while (TCC0->SYNCBUSY.bit.ENABLE); // wait for sync

    PM->APBCMASK.bit.EVSYS_ = 1;
    // EVSYS GCLK is needed for interrupts and sync path (so not now)
    // GCLK->CLKCTRL.reg = GCLK_CLKCTRL_CLKEN | GCLK_CLKCTRL_GEN_GCLK0 | GCLK_CLKCTRL_ID(GCLK_CLKCTRL_ID_EVSYS_1);
    // while (GCLK->STATUS.bit.SYNCBUSY);
    
    // TCC_EVCTRL_TCEI1 is used, must be matched with EVSYS_ID_USER_TCC0_EV_1 here
    REG_EVSYS_USER = EVSYS_USER_USER(EVSYS_ID_USER_TCC0_EV_1)|EVSYS_USER_CHANNEL(2); // Channel n-1 selected so 1 here
    while(!EVSYS->CHSTATUS.bit.USRRDY1);
    // EVSYS_CHANNEL_EDGSEL_BOTH_EDGES // not usable with  PATH_ASYNCHRONOUS
    // For this capture it looks like it needs to be PATH_ASYNCHRONOUS 
    REG_EVSYS_CHANNEL = EVSYS_CHANNEL_PATH_ASYNCHRONOUS|EVSYS_CHANNEL_EVGEN(EVSYS_ID_GEN_EIC_EXTINT_4)|EVSYS_CHANNEL_CHANNEL(1);
    while(EVSYS->CHSTATUS.bit.CHBUSY1);
    // Below interrupt is for testing the event (not possible with PATH_ASYNCHRONOUS)
    //EVSYS->INTENSET.reg = EVSYS_INTENSET_EVD1;
    //NVIC_EnableIRQ(EVSYS_IRQn);  
}

void TCC0_Handler()
{  
    if (TCC0->INTFLAG.bit.MC0) { 
        // The interrupt flag is cleared by reading CC
        cap0[nCap] = TCC0->CC[0].bit.CC;
        cap1[nCap] = TCC0->CC[1].bit.CC;
        if (++nCap == sizeof(cap0)/sizeof(cap0[0])) {
            static volatile size_t done;
            done++;
            nCap = 0;
        }     
    }
}
#else
void setupTimer(void)
{
    PM->APBCMASK.bit.TC3_ = 1;

    REG_GCLK_CLKCTRL = GCLK_CLKCTRL_CLKEN | GCLK_CLKCTRL_GEN_GCLK0 | GCLK_CLKCTRL_ID_TCC2_TC3;
    while ( GCLK->STATUS.bit.SYNCBUSY);

    GCLK->CLKCTRL.reg = (uint16_t) (GCLK_CLKCTRL_CLKEN | GCLK_CLKCTRL_GEN_GCLK0 | GCLK_CLKCTRL_ID(GCLK_CLKCTRL_ID_EIC));
    while ( GCLK->STATUS.bit.SYNCBUSY);
        
    TC3->COUNT16.CTRLA.bit.ENABLE = 0;
    while (TC3->COUNT16.STATUS.bit.SYNCBUSY);
    TC3->COUNT16.CTRLA.bit.MODE = TC_CTRLA_MODE_COUNT16;
    TC3->COUNT16.CTRLA.bit.PRESCALER |= TC_CTRLA_PRESCALER_DIV1_Val;
    while (TC3->COUNT16.STATUS.bit.SYNCBUSY);
    TC3->COUNT16.CTRLC.bit.CPTEN0 = 1;
    TC3->COUNT16.CTRLC.bit.CPTEN1 = 1;   
    while (TC3->COUNT16.STATUS.bit.SYNCBUSY);
    TC3->COUNT16.EVCTRL.bit.TCEI = 1;
    //TC3->COUNT16.EVCTRL.bit.TCINV = 1; // Optional invert
    TC3->COUNT16.EVCTRL.bit.EVACT = TC_EVCTRL_EVACT_PPW_Val;
    // Interrupts
    TC3->COUNT16.INTENSET.bit.MC0 = 1;
    //TC3->COUNT16.INTENSET.bit.MC1 = 1; // Not needed, can read out both in MC0 interrupt

    // Enable InterruptVector
    NVIC_EnableIRQ(TC3_IRQn);

    // Enable TC
    TC3->COUNT16.CTRLA.bit.ENABLE = 1;
    while (TC3->COUNT16.STATUS.bit.SYNCBUSY);

    PM->APBCMASK.reg |= PM_APBCMASK_EVSYS;
     
    // EVSYS GCLK is needed for interrupts and sync path (so not now)
    //GCLK->CLKCTRL.reg = GCLK_CLKCTRL_CLKEN | GCLK_CLKCTRL_GEN_GCLK0 | GCLK_CLKCTRL_ID(GCLK_CLKCTRL_ID_EVSYS_1);
    //while (GCLK->STATUS.bit.SYNCBUSY);
    REG_EVSYS_USER = EVSYS_USER_USER(EVSYS_ID_USER_TC3_EVU)|EVSYS_USER_CHANNEL(2); // Channel n-1 selected so 1 here
    while(!EVSYS->CHSTATUS.bit.USRRDY1);
        
    // EVSYS_CHANNEL_EDGSEL_BOTH_EDGES // not usable with  PATH_ASYNCHRONOUS
    REG_EVSYS_CHANNEL = EVSYS_CHANNEL_PATH_ASYNCHRONOUS|EVSYS_CHANNEL_EVGEN(EVSYS_ID_GEN_EIC_EXTINT_4)|EVSYS_CHANNEL_CHANNEL(1);
    while(EVSYS->CHSTATUS.bit.CHBUSY1);
    // Below interrupt is for testing the event (not possible with PATH_ASYNCHRONOUS)
    //EVSYS->INTENSET.reg = EVSYS_INTENSET_EVD1;
    //NVIC_EnableIRQ(EVSYS_IRQn);
}

void TC3_Handler()
{
    if (TC3->COUNT16.INTFLAG.bit.MC0) {
        // The interrupt flag is cleared by reading CC
        cap0[nCap] = TC3->COUNT16.CC[0].bit.CC;
        cap1[nCap] = TC3->COUNT16.CC[1].bit.CC;
        if (++nCap == sizeof(cap0)/sizeof(cap0[0])) {
            static volatile size_t done;
            done++;
            nCap = 0;
        }
    }       
}
#endif

int main(void)
{
    SystemInit();
    
    setupInput();
    setupTimer();

    while (1) {
    }
}

/Lars
 

 

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

Thanks again for your quick answer, especially considering the amount of work that you must have done.

I'd tried some more with my application, and it at least captured the pulse width. I'll adapt it in line with your results, and report if it worked

In spite of its length, the SAMD21 datasheet seems to have many omissions and inaccuracies, a spell checker would also not been amiss

My application must do very accurate timing, and this section measures the exact CPU frequency by timing the period of the PPS signal from a GPS receiver (error 10-50ns). The PPS signal pin generates the appropriate event.

Best wishes, Jerry

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

I at last persuaded the program to capture the period using an event. The TC3 with XOSC32M clock generates an event on overflow every second, TCC1 counted the period in units of DFFL48M/48. 

DFLL48M is not constant, the measured frequency varies by about +-6ppm, the value SYSCTRL->DFLLVAL.DIFF (the difference between the set reference frequency multiplier and the actual value) is 0 for about half the time, +-1 for about the same time, and rarely +-2.

If needed I can send the complete code on request

Thanks again for Lars' help

Jerry

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

Just wanted to say, was having problems getting the PPW and.or PWP working using the EIC to the EVSYS to the TC/TCC.  

 

This post help me determine that the EIC needed to be set to High Detect Edge, and the EVSYS needed to be set to Asynchronous.

 

Originally was getting the right period, but the pulse width was wrong.  Playing around with things at some point I got the pulse width to equal the period, but this wasnt helping.

 

I'm hoping by highlighting these two values may help someone else trying to figure this out.

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0


Lajon wrote:

External signal directly to the TC for capture is not possible, i.e., this looks like a documentation error:

 To enable the capture from the IO pin, the Capture On Pin x Enable bit in CTRLC register (CTRLC.COPENx) must be written to '1'. 

 D21 has no such bits in TC CTRLC.

So it has to be an event, typically it would be from EIC (i.e., from one of the EXTINT). From my testing it will only work when setting up an asynchronous event. You need only one event btw, the edge detection happens in the TCC (or TC). The event user should be obvious since there is only one place where you can configure PPW and PWP (EVACT1[2:0]: Timer/Counter Event Input 1 Action for TCC, and TC has only one input event with setting in EVACT[2:0]: Event Action).

The example captures 128 periods and pulse widths from PA04. Select TCC or TC with the define USE_TCC. Put a breakpoint on the done++ lines to see the result. The first capture should be skipped, i.e., cap0[0] and cap1[0] will not be valid.

#include "sam.h"
#include <stdlib.h>

static volatile uint16_t cap0[128]; // Period with PPW capture
static volatile uint16_t cap1[128]; // Pulse width with PPW capture
static volatile size_t nCap;
#define USE_TCC

void setupInput(void)
{
    PM->APBAMASK.bit.EIC_ = 1;

    GCLK->CLKCTRL.reg = GCLK_CLKCTRL_CLKEN | GCLK_CLKCTRL_GEN_GCLK0 | GCLK_CLKCTRL_ID(GCLK_CLKCTRL_ID_EIC);
    while (GCLK->STATUS.bit.SYNCBUSY);
    
    // Input on PA04
    PORT->Group[0].PMUX[4/2].bit.PMUXE = MUX_PA04A_EIC_EXTINT4;
    const PORT_PINCFG_Type pincfg = {
        .bit.PMUXEN = 1,
        .bit.INEN = 1,
        .bit.PULLEN = 1
    };
    PORT->Group[0].PINCFG[4].reg = pincfg.reg;
   
    EIC->CONFIG[0].bit.SENSE4 = EIC_CONFIG_SENSE4_HIGH_Val; // None of RISE,FALL or BOTH work for this
    EIC->EVCTRL.bit.EXTINTEO4 = 1;
    
    // The EIC interrupt is only for verifying the input
    //REG_EIC_INTENSET = EIC_INTFLAG_EXTINT4;
    //NVIC_EnableIRQ(EIC_IRQn);
    while (EIC->STATUS.reg & EIC_STATUS_SYNCBUSY);
    EIC->CTRL.bit.ENABLE = 1;                  
    while (EIC->STATUS.reg & EIC_STATUS_SYNCBUSY); 
}

#ifdef USE_TCC
void setupTimer(void)
{
    PM->APBCMASK.bit.TCC0_ = 1;

    REG_GCLK_CLKCTRL = GCLK_CLKCTRL_CLKEN | GCLK_CLKCTRL_GEN_GCLK0 | GCLK_CLKCTRL_ID_TCC0_TCC1;
    while (GCLK->STATUS.bit.SYNCBUSY);
    
    TCC0->CTRLA.bit.ENABLE = 0;
    while (TCC0->SYNCBUSY.bit.ENABLE);

    TCC0->CTRLA.reg |= TCC_CTRLA_PRESCALER_DIV1 | TCC_CTRLA_CPTEN0 | TCC_CTRLA_CPTEN1;
    TCC0->PER.reg = 0xFFFFFF;
    while (TCC0->SYNCBUSY.bit.PER);

    TCC0->EVCTRL.reg = TCC_EVCTRL_TCEI1 |  // enable input event
        TCC_EVCTRL_EVACT1_PPW; // event action = PPW
        // | TCC_EVCTRL_MCEI0 | TCC_EVCTRL_MCEI1;

    // Interrupts
    TCC0->INTENSET.bit.MC0 = 1;
    //TCC0->INTENSET.bit.MC1 = 1; // Not needed, can read out both in MC0 interrupt
    NVIC_EnableIRQ(TCC0_IRQn);

    // Enable TCC
    TCC0->CTRLA.bit.ENABLE = 1;
    while (TCC0->SYNCBUSY.bit.ENABLE); // wait for sync

    PM->APBCMASK.bit.EVSYS_ = 1;
    // EVSYS GCLK is needed for interrupts and sync path (so not now)
    // GCLK->CLKCTRL.reg = GCLK_CLKCTRL_CLKEN | GCLK_CLKCTRL_GEN_GCLK0 | GCLK_CLKCTRL_ID(GCLK_CLKCTRL_ID_EVSYS_1);
    // while (GCLK->STATUS.bit.SYNCBUSY);
    
    // TCC_EVCTRL_TCEI1 is used, must be matched with EVSYS_ID_USER_TCC0_EV_1 here
    REG_EVSYS_USER = EVSYS_USER_USER(EVSYS_ID_USER_TCC0_EV_1)|EVSYS_USER_CHANNEL(2); // Channel n-1 selected so 1 here
    while(!EVSYS->CHSTATUS.bit.USRRDY1);
    // EVSYS_CHANNEL_EDGSEL_BOTH_EDGES // not usable with  PATH_ASYNCHRONOUS
    // For this capture it looks like it needs to be PATH_ASYNCHRONOUS 
    REG_EVSYS_CHANNEL = EVSYS_CHANNEL_PATH_ASYNCHRONOUS|EVSYS_CHANNEL_EVGEN(EVSYS_ID_GEN_EIC_EXTINT_4)|EVSYS_CHANNEL_CHANNEL(1);
    while(EVSYS->CHSTATUS.bit.CHBUSY1);
    // Below interrupt is for testing the event (not possible with PATH_ASYNCHRONOUS)
    //EVSYS->INTENSET.reg = EVSYS_INTENSET_EVD1;
    //NVIC_EnableIRQ(EVSYS_IRQn);  
}

void TCC0_Handler()
{  
    if (TCC0->INTFLAG.bit.MC0) { 
        // The interrupt flag is cleared by reading CC
        cap0[nCap] = TCC0->CC[0].bit.CC;
        cap1[nCap] = TCC0->CC[1].bit.CC;
        if (++nCap == sizeof(cap0)/sizeof(cap0[0])) {
            static volatile size_t done;
            done++;
            nCap = 0;
        }     
    }
}
#else
void setupTimer(void)
{
    PM->APBCMASK.bit.TC3_ = 1;

    REG_GCLK_CLKCTRL = GCLK_CLKCTRL_CLKEN | GCLK_CLKCTRL_GEN_GCLK0 | GCLK_CLKCTRL_ID_TCC2_TC3;
    while ( GCLK->STATUS.bit.SYNCBUSY);

    GCLK->CLKCTRL.reg = (uint16_t) (GCLK_CLKCTRL_CLKEN | GCLK_CLKCTRL_GEN_GCLK0 | GCLK_CLKCTRL_ID(GCLK_CLKCTRL_ID_EIC));
    while ( GCLK->STATUS.bit.SYNCBUSY);
        
    TC3->COUNT16.CTRLA.bit.ENABLE = 0;
    while (TC3->COUNT16.STATUS.bit.SYNCBUSY);
    TC3->COUNT16.CTRLA.bit.MODE = TC_CTRLA_MODE_COUNT16;
    TC3->COUNT16.CTRLA.bit.PRESCALER |= TC_CTRLA_PRESCALER_DIV1_Val;
    while (TC3->COUNT16.STATUS.bit.SYNCBUSY);
    TC3->COUNT16.CTRLC.bit.CPTEN0 = 1;
    TC3->COUNT16.CTRLC.bit.CPTEN1 = 1;   
    while (TC3->COUNT16.STATUS.bit.SYNCBUSY);
    TC3->COUNT16.EVCTRL.bit.TCEI = 1;
    //TC3->COUNT16.EVCTRL.bit.TCINV = 1; // Optional invert
    TC3->COUNT16.EVCTRL.bit.EVACT = TC_EVCTRL_EVACT_PPW_Val;
    // Interrupts
    TC3->COUNT16.INTENSET.bit.MC0 = 1;
    //TC3->COUNT16.INTENSET.bit.MC1 = 1; // Not needed, can read out both in MC0 interrupt

    // Enable InterruptVector
    NVIC_EnableIRQ(TC3_IRQn);

    // Enable TC
    TC3->COUNT16.CTRLA.bit.ENABLE = 1;
    while (TC3->COUNT16.STATUS.bit.SYNCBUSY);

    PM->APBCMASK.reg |= PM_APBCMASK_EVSYS;
     
    // EVSYS GCLK is needed for interrupts and sync path (so not now)
    //GCLK->CLKCTRL.reg = GCLK_CLKCTRL_CLKEN | GCLK_CLKCTRL_GEN_GCLK0 | GCLK_CLKCTRL_ID(GCLK_CLKCTRL_ID_EVSYS_1);
    //while (GCLK->STATUS.bit.SYNCBUSY);
    REG_EVSYS_USER = EVSYS_USER_USER(EVSYS_ID_USER_TC3_EVU)|EVSYS_USER_CHANNEL(2); // Channel n-1 selected so 1 here
    while(!EVSYS->CHSTATUS.bit.USRRDY1);
        
    // EVSYS_CHANNEL_EDGSEL_BOTH_EDGES // not usable with  PATH_ASYNCHRONOUS
    REG_EVSYS_CHANNEL = EVSYS_CHANNEL_PATH_ASYNCHRONOUS|EVSYS_CHANNEL_EVGEN(EVSYS_ID_GEN_EIC_EXTINT_4)|EVSYS_CHANNEL_CHANNEL(1);
    while(EVSYS->CHSTATUS.bit.CHBUSY1);
    // Below interrupt is for testing the event (not possible with PATH_ASYNCHRONOUS)
    //EVSYS->INTENSET.reg = EVSYS_INTENSET_EVD1;
    //NVIC_EnableIRQ(EVSYS_IRQn);
}

void TC3_Handler()
{
    if (TC3->COUNT16.INTFLAG.bit.MC0) {
        // The interrupt flag is cleared by reading CC
        cap0[nCap] = TC3->COUNT16.CC[0].bit.CC;
        cap1[nCap] = TC3->COUNT16.CC[1].bit.CC;
        if (++nCap == sizeof(cap0)/sizeof(cap0[0])) {
            static volatile size_t done;
            done++;
            nCap = 0;
        }
    }       
}
#endif

int main(void)
{
    SystemInit();
    
    setupInput();
    setupTimer();

    while (1) {
    }
}

/Lars

 

 

I have one problem, whenever i include the asf.h, the code is giving me errors. I hope you could help me out. This error occurs for ENABLE in compiler.h.

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

Add

#undef ENABLE

after the asf.h include

/Lars

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

thanks, i resolved the issue by renaming and works, I should have taken deleted my question but was busy with my boss so could not. thanks though i will try your solution also