Capture pulse width & duration

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

I need to use TC2 as a capture of both period and duration on PA05 (8b mode, maximum period).

It also needs to generate an interrupt on overflow.

 

What I know thus far:

- the datasheet says it does both, delivering the results in CC[0] and CC[1]. So far so good.

- I need to relay a pin interrupt to the event system line that controls the capture (all timers share a single line)

 

That seems doable. Now on to how to configure the TC2. The datasheet says:

Capture Operations - Pulse Width

Pulse Width Capture mode makes it possible to measure the pulse width and period of PWM signals. This mode
uses two capture channels of the counter. This means that the counter module used for Pulse Width Capture
can not be used for any other purpose. There are two modes for pulse width capture; Pulse Width Period (PWP)
and Period Pulse Width (PPW). In PWP mode, capture channel 0 is used for storing the pulse width and capture
channel 1 stores the observed period. While in PPW mode, the roles of the two capture channels is reversed.
As in the above example it is necessary to poll on interrupt flags to see if a new capture has happened and check
that a capture overflow error has not occurred.

This is quite an inexact way of specifying things. I need register names and bits within them, sigh! Somehow I found the CTRLC register (never once mentioned in the datasheet!), which apparently control capture. In particular the EVACT field apparently controls the capture mode, so I have this code:

 

TC_EVCTRL_Type tc_evctrl =
{
	.bit.MCEO1 = 0,
	.bit.MCEO0 = 0,
	.bit.OVFEO = 1,  // overflow results in event
	.bit.TCEI = 1,   // enable capture event
	.bit.TCINV = 0,
	.bit.EVACT = TC_EVCTRL_EVACT_PPW,  // capture period in CC[0], width in CC[1]
};
TC2->COUNT8.EVCTRL.reg = tc_evctrl.reg;

I am quite unsure as how to proceed. This code may do the trick, maybe not. There is no info to be found on the net, neither in this forum (still inaccessible to Google). Maybe somehow has a solution to share.

This topic has a solution.

Last Edited: Thu. Dec 10, 2015 - 01:32 PM
  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 1

Did you solve the problem?

This is my attempt. I seem to get pin interrupts and events. But the events doesn't cause a capture.

I'm using the SAMD10 Explained board, which has a push button on pin PA25.

 

Can someone see my error? I seem to be stuck. I've read the relevant chapters in the datasheet many times now.

#define CAPTURE_PIN 25
static void init_capture(void)
{
  PortGroup *const  PortA = &(PORT->Group[0]);
  //Enable pull-up, input and pmux
  PortA->PINCFG[CAPTURE_PIN].reg =  PORT_PINCFG_PMUXEN | PORT_PINCFG_INEN | PORT_PINCFG_PULLEN;
  //Set pmux to extint[5]
  PortA->PMUX[CAPTURE_PIN / 2].reg = PORT_PMUX_PMUXE_A << ((CAPTURE_PIN & 1)*4);
 //Disable output driver
  PortA->DIRCLR.reg = 1 << CAPTURE_PIN;

  //Turn on EIC clock
  GCLK->CLKCTRL.reg = GCLK_CLKCTRL_CLKEN | GCLK_CLKCTRL_ID_EIC;
  //Set extint[5] to generate events
  EIC->EVCTRL.reg = EIC_EVCTRL_EXTINTEO5;
  EIC->CONFIG[0].reg = EIC_CONFIG_SENSE5_FALL;
  EIC->CTRL.reg = EIC_CTRL_ENABLE;
  while (EIC->STATUS.reg & EIC_STATUS_SYNCBUSY);   //Wait for sync  

  //Turn on TC1 clock
  PM->APBCMASK.reg |= PM_APBCMASK_TC1;
  GCLK->CLKCTRL.reg = GCLK_CLKCTRL_CLKEN | GCLK_CLKCTRL_ID_TC1_TC2_Val;

  //Init TC1 to capture period into CC[0] and pulswidth into CC[1]
  TC1->COUNT32.CTRLA.reg  = TC_CTRLA_MODE_COUNT32;
  TC1->COUNT32.EVCTRL.reg = TC_EVCTRL_TCEI | TC_EVCTRL_EVACT_PPW;

  TC1->COUNT32.CTRLC.reg  = TC_CTRLC_CPTEN0;
  while (TC1->COUNT32.STATUS.reg & TC_STATUS_SYNCBUSY);   //Wait for sync

  TC1->COUNT32.CTRLA.reg = TC_CTRLA_MODE_COUNT32 | TC_CTRLA_ENABLE;
  while (TC1->COUNT32.STATUS.reg & TC_STATUS_SYNCBUSY);   //Wait for sync  

  //Turn on EVSYS clock
  PM->APBCMASK.reg |= PM_APBCMASK_EVSYS;
  GCLK->CLKCTRL.reg = GCLK_CLKCTRL_CLKEN | GCLK_CLKCTRL_ID_EVSYS_1_Val;

  //Connect user TC1_EVU (0x0A) to event channel 1
  EVSYS->USER.reg = EVSYS_USER_USER(0x0A) | EVSYS_USER_CHANNEL(1);
  //Connect source extint[5] to event channel 1
  EVSYS->CHANNEL.reg =  EVSYS_CHANNEL_EDGSEL_RISING_EDGE |
            EVSYS_CHANNEL_EVGEN(0x11) |
            EVSYS_CHANNEL_CHANNEL(1);
}

From main loop:
    if (EIC->INTFLAG.reg & (1 << 5)) //Flag is set when button is pushed
    {
    EIC->INTFLAG.reg = (1 << 5);
    n += 'A' - '1';
    }

    if (EVSYS->INTFLAG.reg & EVSYS_INTFLAG_EVD1) //Flag is set when button is pushed
    {
    EVSYS->INTFLAG.reg = EVSYS_INTFLAG_EVD1;
    n += 'a' - 'A';
    }

    if (TC1->COUNT32.INTFLAG.reg & TC_INTFLAG_MC0) //Flag is never set
    {
    TC1->COUNT32.INTFLAG.reg = TC_INTFLAG_MC0;
    n = '0';
    }

    if (TC1->COUNT32.INTFLAG.reg & TC_INTFLAG_MC1) //Flag is set after init, then never again
    {
    TC1->COUNT32.INTFLAG.reg = TC_INTFLAG_MC1;
    n = '1';
    }

 

Last Edited: Sat. Dec 5, 2015 - 02:27 PM
This reply has been marked as the solution. 
  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 1

I believe you have a mistake in the EVENT configuration where you want channel 1 so USER.CHANNEL = 2.

Table 23-5. Channel Event Selection

In your code you should have:

  //Connect user TC1_EVU (0x0A) to event channel 1
  EVSYS->USER.reg = EVSYS_USER_USER(0x0A) | EVSYS_USER_CHANNEL(2);

Another comment for clarity in your code I would suggest to set the alternate function before enabling the multiplexer.

#define CAPTURE_PIN PIN_PA25
#define CAPTURE_PORT_PIN PORT_PA25

static void init_capture(void)
{
  PortGroup *const  PortA = &(PORT->Group[0]);
  PortA->PMUX[CAPTURE_PIN / 2].bit.PMUXO = PORT_PMUX_PMUXO_A_Val;
  //Enable pull-up, input and pmux
  PortA->PINCFG[CAPTURE_PIN].reg =  PORT_PINCFG_PMUXEN | PORT_PINCFG_INEN | PORT_PINCFG_PULLEN;
  //Set pmux to extint[5]
  //Disable output driver
  PortA->DIRCLR.reg = CAPTURE_PORT_PIN;

Hope this helps

Regards,

-Chris

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

Thank you, I realise my mistake. Changing from 1 to 2 solved the problem.

 

But isn't there an error in the datasheet?

Shouldn't the table be:

CHANNEL[3:0]    Channel Number
0x0             No Channel Output Selected
0x1-0x6         Channel n-1 selected
0x7-0xff        Reserved

Since there are 6 channels, not 5.

Last Edited: Sun. Dec 6, 2015 - 04:09 PM
  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 1

Yes you are correct. Another mistake in documentation from Atmel.  

 

There is also a mistake in the ADC for the MUXNEG selection in the D10. It is wrong for GND and IOGND, the values should be 0x18 and 0x19 respectively.

Last Edited: Sun. Dec 6, 2015 - 04:19 PM
  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

Adding a thanks from me also :)

 

On the topic of code clarity, and regarding the PORT vs PIN issue, instead of this: 

 

cwunder wrote:

#define  CAPTURE_PIN        PIN_PA25
#define  CAPTURE_PORT_PIN  PORT_PA25

...

//Disable output driver
PortA->DIRCLR.reg = CAPTURE_PORT_PIN;

 

 

I prefer to write this: 

 

#define  BIT(pin_number)   (1<<pin_number)
...

#define  CAPTURE_PIN     PIN_PA25
...

//Disable output driver
PortA->DIRCLR.reg = BIT(CAPTURE_PIN);

 

Or even:

 

#define  BIT(pin_number)   (1<<pin_number)
...

#define  CAPTURE_PIN        PIN_PA25
#define  CAPTURE_PIN_BIT    BIT(CAPTURE_PIN)
...

//Disable output driver
PortA->DIRCLR.reg = CAPTURE_PIN_BIT;

 

I believe this relation of PIN to PORT always holds, then again, I'm inexperienced.

 

This way allows me to standardize on a single symbol. I'm not a fan of defining duplicate symbols, because it is easy to change one and not other, or introduce an error in one of them. I also think BIT() is more explicit than the subtle PORT vs PIN distinction.

 

Hope this helps.

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

Just a comment and caution to the PIN_Pxxx usage. Atmel has numbered these sequentially and uses this index to know which port it is on. Example, if you choose PB00 with PIN_PB00 the value is defined as 32. So you would need to subtract multiples of 32 depending on the Port (i.e. B, C, and D).

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

bearing wrote:

#define CAPTURE_PIN 25
static void init_capture(void)
{
  PortGroup *const  PortA = &(PORT->Group[0]);

 

My comment is regarding the PortA definition. Shouldn't it always be better to use:

 

  PortGroup *const  PortA = &(PORT_IOBUS->Group[0]);

 

Because that gets us the performance and lack of synchronization problems of the single cycle IOBUS? Any downsides?

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

Better performance? maybe. Down sides? no idea=) I didn't know PORT_IOBUS existed. I'm very new to this microcontroller. I've never used an ARM/Cortex before. It took me days of testing and reading the manual to write the code I posted above. If I was to write it today, it would be less than an hour.

 

On the AVR there were instruction for faster access of some special registers, like I/O. If there are similar instructions on the Cortex, it would probably be faster to use PORT_IOBUS, if it is what is suspect it is. I looked at some generated assembly when trying to understand this CPU, and remember that the instructions LD/ST were used to access port memory when I used the PORT definition.

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

When using the PORT_IOBUS to read from a port you also need to enable the synchronizer for the port (CTRL register). This can add to additional power consumption.

 

The PORT is connected to the high-speed bus matrix through an AHB/APB bridge. The Pin Direction, Data Output Value and Data Input Value registers may also be accessed using the low-latency CPU local bus (IOBUS; ARM® single-cycle I/O port).

Last Edited: Tue. Dec 8, 2015 - 12:19 AM
  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 1

I've not entirely understood these sync mechanism. What is the advantage of polling the syncbusy bit, like I did above?

Or using the readreq register?

 

And what to do when peripherals have registers which are read-synchronized, but there is no readreq register in the peripheral?

Like the ADC. The result register is read-synchronized, but there is no readreq reg in the ADC?

Last Edited: Tue. Dec 8, 2015 - 02:39 AM
  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

The above code works.

 

My case is a bit different. I use an 8b counter and many periods pass before registering a rise or fall event. Ideally one register captures a rise and another the fall. Periodically I then process the contents.

 

I tried with EVACT off, but then both CC[0] and CC[1] capture the same count.

 

The PPW/PWP mode promises to capture both events, but adds a period interpretation which is wrong in my case. The counter overflows long before any period can be detected. I tried anyway and got one CC containing always 0 or 1.

 

So it seems my case is unsupported. Correct?

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

Why not use the 16-bit counter?

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

bearing wrote:

Why not use the 16-bit counter?

 

That was the original plan, to get a bit more resolution for the PWM. I decided to go the safe route. I guess 8b is enough, I can dither the PWMs around an average when more resolution is required. In 16b mode there is no .PERiod register and though the event system can restart timer channels, this was too complicated for me to start with.

Handling the event system should be much, much easier (there is a big opportunity here and I guess something must be in the works).

 

Someone here has said: 8b mode should not exist on a 32b processor. I agree. It would have avoided much code fragmentation.

 

--

Congratulations on getting the code working :)

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

BTW, I have the capture working nicely with EVACT OFF and software. It just won't handle 2 events withing the 256 ticks. Nobody will notice ;)

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

Thanks, it was a big relief to finally start understanding this microcontroller, and its peripherals. When cwunder found my error, capture was finally working, and I could continue with the software.

 

Are you using TC2 in 8bit to measure period/pulsewidth. And 8-bit pwm in TC1?

 

Isn't it possible to use TC2 in 16bit, while TC1 is 8bit?

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

A relief it is. Should not be this way. At least this forum is getting livelier. It seems Atmel really needs us.

 

TC2 is started just before TC1, and thereafter runs in sync. TC2 overflow triggers the ADC (right before the end of the period of the PWM), to measure the voltage under load. It also generates an interrupt to set a new PWM period (as the TCs don't buffer their registers :/).

 

 

 

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

BTW, this thread still has open questions, see:

 

1. when to sync?

 

2. how to capture the rise and fall on a pin independently?

 

Regarding 2), I am now using software in conjunction with EVACT off, but naturally there are races, so this is an approximation.

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

"how to capture the rise and fall on a pin independently?"

 

Looks like the TC module doesn't have that feature. Maybe it would be possible by using two event channels, and by having both modules configured to capture on one channel. And then use the other channel in each module for PWM.

 

 

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

Removed the post as it did not answer the open questions

Last Edited: Wed. Dec 9, 2015 - 02:28 AM
  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

I'm not sure, but I think he meant that he wanted to capture the rising edge into CC[0], and falling into CC[1], but without COUNT resetting on one of the edges, like it does in in PPW-mode.

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

bearing wrote:

I'm not sure, but I think he meant that he wanted to capture the rising edge into CC[0], and falling into CC[1], but without COUNT resetting on one of the edges, like it does in in PPW-mode.

 

Right.

 

Indeed it seems possible using both TC1 and TC2.

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

I'm curious to why the TC doesn't have a period reg (PER) in 16 and 32 bit modes. The address space (32 bit) is "reserved". If I get the time, I'll a try to write PER in 16 or 32 bit mode to see what happens.

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

When operating the TC in 16-bit or 32-bit mode you have only two choices for the period. The maximum value 2^16, 2^32 or use CC0 register to hold the period.

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

Oh, didn't know that. Thank you. A shame though, that it's not possible to have two PWMs when you limit the period.