xmega dual event input capture for high speed input

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

I was looking to use a cascaded timer system to enable myself to get more accurate readings of high speed signals. Measuring accurately around 100khz while running the uC at 2Mhz is not very feasible. I was thinking of using a cascaded timer arrangement where the first timer would count to overflow and the second timer would be linked to this overflow to measure frequency.
PORTE2 -> Event Ch0 -> TCD0 set to count to 10 then overflow -> TCD0 overflow -> Event Ch1 -> TCC0 CCA set to measure frequency

#include "TC_driver.h" 
#include "global.h" 

typedef struct InputCapture{ 
	uint16_t Timer; 
	uint16_t TimerBuffer[50]; 
	volatile bool Ready; 
	uint8_t Count; 
	uint32_t Sum; 
	uint16_t Min; 
	uint16_t Max; 
	uint32_t Mean; 
	uint32_t Freq; 
}InputCapture_t;

InputCapture_t Capture;

int main(void){
	uint16_t timer; 
	/*	PortD set as output.	*/
	PORTD.DIRSET = 0xFF; 
	/*	Configure PE2 for input, triggered on rising edge.	*/ 
	PORTE.PIN2CTRL = PORT_ISC_RISING_gc;
	PORTE.DIRCLR   = PIN2_bm;
	/*	Select PE2 as input to event channel 0.	*/ 
	EVSYS.CH0MUX = EVSYS_CHMUX_PORTE_PIN2_gc;
	/*	Select TCD0 overflow as input to event channel 1.	*/
	EVSYS.CH1MUX = EVSYS_CHMUX_TCD0_OVF_gc;
	/* Configure TCC0 for Input Capture using event channel 1.	*/ 
	TCC0.CTRLD = (TCC0.CTRLD & ~(TC0_EVSEL_gm | TC0_EVACT_gm)) | TC_EVSEL_CH1_gc | TC_EVACT_FRW_gc; 
	/*	Enable Input "Capture or Compare" channel A.	*/ 
	TCC0.CTRLB |= ((TC0_CCAEN_bm | TC0_CCBEN_bm | TC0_CCCEN_bm | TC0_CCDEN_bm) & TC0_CCAEN_bm);
	/* Set period on TCC0 to maximum.	*/
	TCC0.PER = 0xFFFF;
	/*	Set period on TCD0 to count to 10.	*/
	TCD0.PER = 10;
	/*	Clock TCC0 off system clock.	*/ 
	TCC0.CTRLA = (TCC0.CTRLA & ~TC0_CLKSEL_gm) | TC_CLKSEL_DIV1_gc;
	/*	Clock TCD0 off event chanel 0.	*/
	TCD0.CTRLA = (TCD0.CTRLA & ~TC0_CLKSEL_gm) | TC_CLKSEL_EVCH0_gc;
	/*	Enable CCA interrupt.	*/ 
	TCC0.INTCTRLB = (TCC0.INTCTRLB & ~TC0_CCAINTLVL_gm) | TC_CCAINTLVL_HI_gc;
	/*	Enable TCD0 overflow interrupt.	*/
	TCD0.INTCTRLA = ( TCD0.INTCTRLA & ~TC0_OVFINTLVL_gm ) | TC_OVFINTLVL_HI_gc;
	PMIC.CTRL |= PMIC_HILVLEN_bm;
	/*	Enable global interrupts.	*/
	sei(); 

	while(1){ 
		if(Capture.Ready){
			if(Capture.Count >= 34){
				Capture.Sum -= (Capture.Min + Capture.Max);
				Capture.Mean = Capture.Sum >> 5;
				Capture.Freq = 2000000/Capture.Mean;
				Capture.Sum = 0;
				Capture.Count = 0;
			}else{ 
				/*	Store count value locally.	*/
				timer = Capture.Timer;
				/*	Buffer count value for debugging.	*/
				Capture.TimerBuffer[Capture.Count] = timer; 
			//   Keep track of lowest value read.
			if (Capture.Timer < Capture.Min || Capture.Count == 0){ 
				Capture.Min = timer; 
		} 
		//   Keep track of highest value read. 
		if (Capture.Timer > Capture.Max || Capture.Count == 0){ 
			Capture.Max = timer; 
		}
			/*	Sum counts together and keep track of how many we have captured.	*/
			Capture.Sum += timer; 
			Capture.Count++; 
		}
			/*	Reset alert flag.	*/
			Capture.Ready = 0; 
		}
	} 
	return(0); 
} 

ISR(TCC0_CCA_vect){ 
	/*	Store timer value */
	Capture.Timer = TCC0.CCA;
	/*	Toggle PortD low byte.	*/
	PORTD.OUTTGL = 0x0F;
	/*	Alert when new measurement is ready.	*/ 
	Capture.Ready = 1; 
}

ISR(TCD0_OVF_vect){
	/*	Toggle PortD high byte.	*/
	PORTD.OUTTGL = 0xF0;
}
  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

...and did it work or?
/Lars

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

No it doesn't seem to work. I am not sure if this is a fundamentally flawed in how I am utilizing the event system or something else. I can't easily debug the event system it seems like so if you could offer any tips to do that it would be great. I can see that the first timer is overflowing by look at its interrupt but the event never seems to reach the second timer.

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

Why are you crippling yourself at 2MHz?

I've got a 20MHz Mega88 app. So that is 50ns clocks. It is used for industrial ultrasonics, where the frequency is 100kHz-500kHz. It seems to work fine with bog-standard 16-bit timer work. I guess I am defacto cascading timers by counting overflows.

Anyway, I'm not an Xmega person but on the surface it would seem that you are crippling yourself at 2MHz.

You can put lipstick on a pig, but it is still a pig.

I've never met a pig I didn't like, as long as you have some salt and pepper.

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

The final product will most likely work at 32Mhz but for now I want to see what I can get done with 2Mhz as if it works at 2Mhz it should work well at 32Mhz. On another note it seems like my overflow isn't causing an event. I can manually trigger an event with strobe and data in my first timer overflow isr and my program works correctly but if I attempt to use the built in function to trigger off the overflow event nothing happens.

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

Does this look like a silicon problem to anyone else? I can trigger a counter with a manual event set from an overflow isr but I can not trigger a timer with an event from the same overflow.

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

I did a quick test with a xmega64a1 and the counting seems to work but there is never a frequency capture interrupt.

I do get the capture interrupt if I change the capture mode to the plain input capture using TC_EVACT_CAPT_gc. This is btw what happens in the simulator also.

Maybe the frequency capture mode is not documented correctly, I note that the pulse width capture (and this makes sense) is limited:

Quote:

The event source must be an I/O pin and the sense configuration for the pin must be set up to generate an event on both edges.
Could it be that this restriction is there for the frequency capture mode also?
/Lars

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

This is the latest rev of my code with more commenting. It works correctly for me of course I am manually triggering events to link the two counters as I mentioned earlier.

#include "global.h" 

typedef struct InputCapture{ 
	uint16_t Timer; 
	uint16_t TimerBuffer[50]; 
	volatile bool Ready; 
	uint8_t Count; 
	uint32_t Sum; 
	uint16_t Min; 
	uint16_t Max; 
	float Mean; 
	float Freq; 
}InputCapture_t;

InputCapture_t Capture;

int main(void){
	uint16_t timer; 
	/*	PortD set as output.	*/
	PORTD.DIRSET = 0xFF; 
	/*	Configure PE2 for input, triggered on rising edge.	*/
	PORTE.PIN2CTRL = PORT_ISC_RISING_gc;
	PORTE.DIRCLR   = PIN2_bm;
	/*	Select PE2 as input to event channel 0.	*/
	EVSYS.CH0MUX = EVSYS_CHMUX_PORTE_PIN2_gc;
	/*	Select TCC0 overflow as input to event channel 1.	*/
	EVSYS.CH1MUX = EVSYS_CHMUX_TCC0_OVF_gc;
	/* Configure TCC1 for Frequency Measurement using event channel 1.	*/
	TCC1.CTRLD = (TCC1.CTRLD & ~(TC1_EVSEL_gm | TC1_EVACT_gm)) | TC_EVSEL_CH1_gc | TC_EVACT_FRW_gc; 
	/*	Enable Input "Capture or Compare" channel A for TCC1.	*/
	TCC1.CTRLB |= ((TC1_CCAEN_bm | TC1_CCBEN_bm) & TC1_CCAEN_bm);
	/*	Set period on TCC0 to count to 127.	*/
	TCC0.PER = 127;
	/* Set period on TCC1 to maximum.	*/
	TCC1.PER = 0xFFFF;
	/*	Clock TCC0 off event chanel 0.	*/
	TCC0.CTRLA = (TCC0.CTRLA & ~TC0_CLKSEL_gm) | TC_CLKSEL_EVCH0_gc;
	/*	Clock TCC1 off system clock.	*/
	TCC1.CTRLA = (TCC1.CTRLA & ~TC1_CLKSEL_gm) | TC_CLKSEL_DIV1_gc;
	/*	Enable TCC0 overflow interrupt.	*/
	TCC0.INTCTRLA = (TCC0.INTCTRLA & ~TC0_OVFINTLVL_gm) | TC_OVFINTLVL_HI_gc;
	/*	Enable TCC1 CCA interrupt.	*/
	TCC1.INTCTRLB = (TCC1.INTCTRLB & ~TC1_CCAINTLVL_gm) | TC_CCAINTLVL_HI_gc;
	PMIC.CTRL |= PMIC_HILVLEN_bm;
	/*	Enable global interrupts.	*/
	sei();

	while(1){ 
		if(Capture.Ready){
			if(Capture.Count >= 18){
				/*	Subtract the min and max values.	*/
				Capture.Sum -= (Capture.Min + Capture.Max);
				/*	Calculate the mean by dividing Sum by (Capture.Count * TCC0 Period).	*/
				Capture.Mean = ((float)Capture.Sum)/2048;
				/*	Divide the CPU Clock by the Capture.Mean to find the frequency.	*/ 
				Capture.Freq = 2000000/Capture.Mean;
				/*	Reset values for next measurement.	*/
				Capture.Sum = 0;
				Capture.Count = 0;
			}else{ 
				/*	Store count value locally.	*/
				timer = Capture.Timer;
				/*	Buffer count value for debugging.	*/
				Capture.TimerBuffer[Capture.Count] = timer;
				/*	Keep track of lowest value read.	*/
				if (Capture.Timer < Capture.Min || Capture.Count == 0){
					Capture.Min = timer;
				}
				/*	Keep track of highest value read.	*/
				if (Capture.Timer > Capture.Max || Capture.Count == 0){
					Capture.Max = timer;
				}
				/*	Sum counts together and keep track of how many we have captured.	*/
				Capture.Sum += timer;
				Capture.Count++;
			}
			/*	Reset alert flag.	*/
			Capture.Ready = 0;
		}
	}
	return(0);
}

ISR(TCC0_OVF_vect){
	/*	Toggle PortD high byte.	*/
	PORTD.OUTTGL = 0xF0;
	/*	Manually trigger event on channel 1.  Why is this needed?! There should be an overflow event!	*/
	EVSYS.DATA = 0b00000010;
	EVSYS.STROBE = 0b00000010;
}

ISR(TCC1_CCA_vect){ 
	/*	Store timer value */
	Capture.Timer = TCC1.CCA;
	/*	Toggle PortD low byte.	*/
	PORTD.OUTTGL = 0x0F;
	/*	Alert when new measurement is ready.	*/ 
	Capture.Ready = 1; 
}

If you want to test just the counter setup for frequency capture (TCC1) then you can select its event trigger off event channel 0. TCC1 CCA interrupt should fire on every event into TCC1. Note the fact that I do link event channel 1 to the TCC0 overflow thus my manual trigger should not be needed but if the manual trigger is commented out the timers are no longer linked. Also if you don't trigger and event on channel 0 i.e. pulse PORTE2 with a large enough waveform nothing will happen. So i'm guessing you manually toggled PORTE2 in the simulator? If you have any more questions you are welcome to shoot them over. Just don't ask me why I need to manually trigger. ;)

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

Clearly we don't have the same results, the timers work for me but not the input capture in frequency mode. What xmega are you using?

Quote:
If you want to test just the counter setup for frequency capture (TCC1) then you can select its event trigger off event channel 0.
I did test that, works fine.
To test in the simulator I clocked TCC0 from the system clock so no manual toggling needed.
/Lars

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

Lajon wrote:
Clearly we don't have the same results, the timers work for me but not the input capture in frequency mode. What xmega are you using?
Quote:
If you want to test just the counter setup for frequency capture (TCC1) then you can select its event trigger off event channel 0.
I did test that, works fine.
To test in the simulator I clocked TCC0 from the system clock so no manual toggling needed.
/Lars

I am using a 16A4, I have an xplain I can try out at some point as well. We also have some 32A4s, if those are much different than the 16A4 that will be quite odd. Also you mean you are clocking the EVENT that TCC0 is tied to from the clock correct? And to make sure we are talking about the latest code I have posted, as I may have migrated some of the timers.

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

I guess we see the same issue after all, it's only the frequency capture that needs your manual event triggering so I kind of focused on that input capture mode beeing the problem. Since manually triggering the event will introduce errors (from interrupt latency) I would prefer the normal capture mode if I were you. Just need this

TCC1.CTRLD = (TCC1.CTRLD & ~(TC1_EVSEL_gm | TC1_EVACT_gm)) | TC_EVSEL_CH1_gc | TC_EVACT_CAPT_gc;

and ISR change (also skip the first Capture.Ready):

ISR(TCC1_CCA_vect){ 
   /*   Store timer value */ 
   static uint16_t last;
   Capture.Timer = TCC1.CCA - last;
   last = TCC1.CCA; 
   /*   Toggle PortD low byte.   */ 
   PORTD.OUTTGL = 0x0F; 
   /*   Alert when new measurement is ready.   */ 
   Capture.Ready = 1; 
} 

About simulation, I skipped the clocking of TCC0 from PE2 via event channel 0 (since that seems to work anyway).

   TCC0.CTRLA = (TCC0.CTRLA & ~TC0_CLKSEL_gm) | TC_CLKSEL_DIV1_gc;

I read the simulator help btw, and it is possible to generate signals. For your case the needed "stimuli" file can look like this:

$repeat 1000
#40
0x688 ^= 4
$endrep

So it toggles PORTE.IN (thats 0x688) bit 2 every 40 cycles.
/Lars

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

The question is why does the overflow not triggering an interrupt? Both a manual event and an event fired off an IO pin seem to work fine for the frequency capture mode. Should the capture mode not be blind to the creation of the event? By my understanding TCC1 should only know that an event has occurred not what has caused it. So why the discrepancies?

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

Indeed, it's strange and possibly a bug.
/Lars

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

I am also working this problem with some local Atmel FAEs in parallel. I will update this thread with any revelations. I really hope this isn't some undocumented errata.

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

I just got this from my friendly neighborhood Atmel FAE:

Quote:
There are two different types of events possible. Signalling events that indicate some action, and data events which also supply data information. Data events are typically either from IO or clock signals, while the timer overflow is a signalling event. See the events chapter in the XMEGA manual for details on this.

The problem is that the frequency measurement requires a data event, and will not work with the signalling event from timer overflow.

I now understand why this doesn't work. It would be nice to have a list of what events make what type of event and what event triggers work off what type.

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

Yes, it does look like there is a documentation problem (the list you ask for would be very useful). But now when I know this :) the situation, for this case, is documented:

Quote:
Selecting the frequency capture event action, makes the enabled capture channel perform an input capture and restart on positive edge events.
"positive edge events" implies a data event.
The normal input capture has
Quote:
Selecting the input capture event action, makes the enabled capture channel perform an input capture on any event.

/Lars