IntervalTimer::UpdateInterval(void (*funct)(), uint32_t cycles) addition?

Status
Not open for further replies.

paynterf

Well-known member
Paul,

I have been attempting to create a high-accuracy square-wave frequency sweep generator in the 50-1000Hz range, and have tried several different techniques suggested in this forums. The best one so far has been the IntervalTimer technique, except the requirement to stop and then restart the timer creates fairly significant frequency artifacts when changing frequencies.

A friend noted that the countown registers in Kinetis K64 PIT module can, at least in theory, be reloaded with a new countdown value without having to restart the timer. This feature would make it perfect for sweep generator applications, as there would be no need for the current teardown/setup delay.

Any chance something like a 'IntervalTimer::UpdateInterval(void (*funct)(), uint32_t cycles)' function could be added to the IntervalTimer.cpp/h files? I have looked at the code (to the extent that I am able), and it seems like this function could be very similar to the current 'beginCycles()' function.

Actually, it just occurred to me that I might be able to simply call 'begin()' multiple times without calling 'end()' - would that work?

I started with Paul's IntervalTimer sample code, and set up a loop to step through several frequencies. In one test, I called begin() at the beginning of each freq step, and end() at the end. In another, I called begin() at the beginning of each step, but only called end() after all steps were completed. Nothing blew up - so that was a plus ;-). I have attached a screenshot showing the two results, and the complete code. Comments?

170729_IntervalTimerStepTests.JPG

Code:
// Create an IntervalTimer object 
IntervalTimer myTimer;

const int ledPin = LED_BUILTIN;  // the pin with a LED
const int OUTPUT_PIN = 33;
const int TSTAMP_ARRAY_LENGTH = 5000;

int aTimeStamp[TSTAMP_ARRAY_LENGTH];
// The interrupt will blink the LED, and keep
// track of how many times it has blinked.
int ledState = LOW;
volatile unsigned long TransCount = 0; // use volatile for shared variables

void setup(void)
{
	Serial.begin(460800);
	pinMode(ledPin, OUTPUT);
	pinMode(OUTPUT_PIN, OUTPUT);

	for (size_t i = 0; i < TSTAMP_ARRAY_LENGTH; i++)
	{
		aTimeStamp[i] = 0;
	}

	Serial.println("Freq\tTimerVal");
	for (size_t i = 501; i <= 531; i++)
	{
		float period = 1e6 / i;
		//Serial.print(i); Serial.print("\t"); Serial.println(period);
		myTimer.begin(blinkLED, period / 2);
		//Serial.print(i); Serial.print("\t"); Serial.println(period);
		delay(500);
		myTimer.end();
	}
	//myTimer.end(); 
}


// functions called by IntervalTimer should be short, run as quickly as
// possible, and should avoid calling other functions if possible.
void blinkLED(void)
{
	if (ledState == LOW)
	{
		ledState = HIGH;
	}
	else 
	{
		ledState = LOW;
	}
	digitalWrite(ledPin, ledState);
	digitalWrite(OUTPUT_PIN, ledState);
	aTimeStamp[TransCount] = micros();
	TransCount++;

	if (TransCount >= TSTAMP_ARRAY_LENGTH)
	{
		myTimer.end();
		Serial.println("TimeStamps");
		for (size_t i = 0; i < TSTAMP_ARRAY_LENGTH; i++)
		{
			if (aTimeStamp[i] > 0)
			{
				Serial.println(aTimeStamp[i]);
			}
		}

		Serial.println("Exiting - Bye!");
		//infinite loop
		while (true)
		{

		}

	}
}

// The main program will print the blink count
// to the Arduino Serial Monitor
void loop(void) {
	unsigned long TransCountCopy;  // holds a copy of the blinkCount

	 //to read a variable which the interrupt code writes, we
	 //must temporarily disable interrupts, to be sure it will
	 //not change while we are reading.  To minimize the time
	 //with interrupts off, just quickly make a copy, and then
	 //use the copy while allowing the interrupt to keep working.
	noInterrupts();
	 TransCountCopy = TransCount;
	interrupts();
	Serial.println(TransCountCopy);

	delay(100);
}


Frank
 
I was hoping this thread would get some replies. When I looked at the interval timer library code a while ago, it appeared to me that you could call begin only 4 times before it would start returning fail/false and your new frequency would not get set up. It also appears to me that if you register the same interrupt function for each call to begin, you would get multiple calls to your function blinkLED and the frequency output would be double, triple, and 4 times what you want. But it seems you are still getting the correct frequency. So hopefully someone will explain.
 
I was hoping this thread would get some replies. When I looked at the interval timer library code a while ago, it appeared to me that you could call begin only 4 times before it would start returning fail/false and your new frequency would not get set up. It also appears to me that if you register the same interrupt function for each call to begin, you would get multiple calls to your function blinkLED and the frequency output would be double, triple, and 4 times what you want. But it seems you are still getting the correct frequency. So hopefully someone will explain.

Always happy to help;)
 
Not calling 'end()' doesn't buy you much. Calling 'begin()' still aborts the current timer cycle and restarts the timer.

To update the interval for the next cycle (avoiding the restart), you can do it like this:
Code:
void updateInterval(IntervalTimer& timer, float period) {
    size_t channel_nr = IRQ_PIT_CH0 - timer.operator IRQ_NUMBER_t();
    if(channel_nr >= 4) return; // no PIT timer assigned
    KINETISK_PIT_CHANNEL_t& channel = (KINETISK_PIT_CHANNELS)[channel_nr];
    channel.LDVAL = (float)(F_BUS / 1000000) * period - 0.5f;
}

\\

For deterministic timing, don't use branches like:
Code:
	if (ledState == LOW)
	{
		ledState = HIGH;
	}
	else 
	{
		ledState = LOW;
	}

Do it like this:
Code:
ledState = !ledState;

\\

Don't use digitalWrite, it has a lot of unnecessary overhead, use digitalWriteFast.

\\

For high-resolution timing, use the CPU cycle counter:
https://forum.pjrc.com/threads/28407-Teensyduino-access-to-counting-cpu-cycles


When I looked at the interval timer library code a while ago, it appeared to me that you could call begin only 4 times before it would start returning fail/false and your new frequency would not get set up.

That's wrong. There are 4 PIT channels. IntervalTimer objects start out without having a PIT channel allocated. The first call to 'begin()' allocates a channel and starts the timer. Subsequent calls to 'begin()' update the interval and restart the timer.
 
tni, thanks for the prompt and very helpful reply. I added your function to my IntervalTimer test program, and got the following results.

170730_TimeStampsUsingUpdateInterval.JPG

If you compare this screenshot with the previous ones, you will see that tni's update function did the trick - no ugly transition spikes - YAY!!

After seeing this, I modified my sweep generator program to use updateInterval(), and ran a sweep on my 'degenerate N-path band-pass filter' set for 520Hz center freq, with the following results

170730_FinValvsFreqUsingUpdateInterval.JPG

For reference, here is that same test using IntervalTimer, but without the updateInterval() magic


170729_FreqResponseUsingIntervalTimer.JPG

Thanks to Paul for creating the wonderful Teensy 3.x SBCs, to Daniel for creating the IntervalTimer class, and to tni for the updateInterval() fcn - I have been fighting this frequency sweep problem for a while now, and these latest results really made my day!

Daniel, would you consider modifying your IntervalTimer class to add the updateInterval() function as a class method?

Now that I have the thing working (correctly), I'm thinking of creating an Arduino library class so others can use it. Right now it uses standard serial port prompts and user entry parameters to generate frequency or amplitude sweeps (as shown below), but I think this might be too clunky and/or restrictive for a library implementation. Any suggestions along these lines would be appreciated, up to and including "that's a really bad idea!" ;-).

170730_SweepGenSerialIO.JPG

Again, thanks to all here who put up with my blatherings and stupid questions - I definitely have a karma deficit to work off now ;-).

Frank
 
When I looked at the interval timer library code a while ago, it appeared to me that you could call begin only 4 times before it would start returning fail/false and your new frequency would not get set up.

Yes, the chip has only 4 of these PIT hardware timers.
 
I know this is an old thread, but just wanted to let you know I've *finally* added this missing IntervalTimer update() function.

https://github.com/PaulStoffregen/cores/commit/5ecc5626038543cb5254eb70b94759ef22d02ef8

This will be released in Teensyduino 1.41. I plan to update the website documentation sometime after 1.41 releases.

myTimer.update(microseconds);

Change the interval. For an active IntervalTimer, the current timing interval completes as scheduled, then this new interval is automatically used for the next timing interval. For an inactive IntervalTimer, this function does nothing.
 
Status
Not open for further replies.
Back
Top