PDA

View Full Version : Teensy 3.0 and interrupts



emertonom
10-27-2012, 12:51 AM
Accidentally posted this first in "Bug Reports," which clearly isn't right. Couldn't see how to move the post, so this is just a copy. Sorry for the double-post!



I'm porting over an Arduino sketch that relied on the chip for timer-based interrupts. It looks to me from the documentation as though the proper timer to use for this on Teensy 3.0 is the periodic interrupt timer, PIT. So I did some setup:




// Constants for bitvalues within the TCTRL1 register
#define TIE 2
#define TEN 1

void timer_setup() {
/* Arduino version of timer setup
TCCR2A = 0;
TCNT2=455; //455 outputs 1.007khz
TCCR2B = B00000010;
//Timer2 Overflow Interrupt Enable
TIMSK2 = 1<<TOIE2;
*/

// Teensy 3.0 version

// Turn on PIT
PIT_MCR = 0x00;

// Set the period of the timer. Unit is (1 / 50MHz) = 20ns
// So 1 kHz frequency -> 1 ms period -> 50000 * 20ns
PIT_LDVAL1 = 50000;
// Enable interrupts on Timer1
PIT_TCTRL1 = TIE;
// Start the timer
PIT_TCTRL1 |= TEN;
}


I'm not completely certain that's correct, but it's similar to code included in the chip datasheet.

But I'm not really sure how to declare an interrupt handler for the PIT. The Arduino code is



ISR(TIMER2_OVF_vect) {
// function body
}


but of course that's for the AVR timer interrupt. I tried to look through the hardware/teensy3 code, but I couldn't find the relevant bits. The datasheet just says "See the MCU specification for related vector addresses and priorities," and I couldn't work out what that meant. (My best guess was "http://cache.freescale.com/files/32bit/doc/data_sheet/K20P64M50SF0.pdf", but that doesn't have any relevant information.)

Is there a good source of documentation on this?

Thanks!
-Nick

emertonom
10-27-2012, 12:59 AM
Oh...I just noticed this aspect of the one of the kickstarter update pages, which talks about exactly this:



Missing Features, Coming Soon....

<...>

Also extremely urgent is an AVR-like ISR() macro and interrupt vector table scheme. Since beta 2, attachInterrupt() has worked. But if you want to use any of the interrupts directly, the only way to do so is editing the table in mk20dx128.c. Soon I'm going to rework that file and the linker script, to support a system that look and works very much like what everyone is used to on AVR.



Taking a look at both aspects (attachInterrupt and mk20dx128.c). I'll post what I find, in case anyone else is curious.
-Nick

emertonom
10-27-2012, 01:08 AM
Okay, yeah, for the time being I need to modify mk20dx128.c to include the address of my interrupt handler (the 47th table entry is PIT timer 1, currently "unused_isr"). Presumably a later version will make it easier. I just jumped the gun.

emertonom
10-27-2012, 11:57 PM
So, this is not entirely my code--I ported over the Arduino sketch from this article: http://blog.makezine.com/2008/05/29/makeit-protodac-shield-fo/

But it runs on Teensy 3.0 now, so that's something.

It requires two changes to the C source, though. First, in mk20dx128.c, change "unused_isr" to "pit1_isr" at line 87. And second, you need to declare pit1_isr in a header file--I put it in mk20dx128.h, though that's sort of an odd place for it, but then, the sketch doesn't seem to get its own .h file. (I'm a little new to Arduino and Teensy, so bear with me if all of this is a little rough around the edges.) That's just a line in the .h file which says "void pit1_isr(void);" so that mk20dx128.c knows the function will be defined somewhere else.

These changes won't be necessary once a version of the Teensy environment comes out that implements something like the Arduino / AVR "ISR(somevector)" macros, which a recent update said was considered an "urgent" change, so double-check whether this is all still necessary before you go using the same technique!

With those two changes, though, this sketch (which provides the implementation of pit1_isr) runs and produces a pretty nice 8-bit sound output. It's silly, of course, because the Teensy does I2S and supports much, much better DACs at much higher speeds than this. It was mostly useful because of the experience with the toolchain and whatnot. Still, if anyone wants to see it, it's here. Bits are copied from pins_teensy.c and the mk20 reference manual, and the rest is the modified sketch from MAKE.



/*
Copyright 2007 Richard Cappels used with his permission
www.projects.cappels.org

This assumes an R/2R DAC connected to pins 2-9:

GND--2R--+--R--+--R--+--R--+--R--+--R--+--R--+--R--+--OUT
| | | | | | | |
2R 2R 2R 2R 2R 2R 2R 2R
| | | | | | | |
pin2 pin3 pin4 pin5 pin6 pin7 pin8 pin9

(That is:
2R from ground to point A
2R from pin 2 to point A
R from point A to point B
2R from pin 3 to point B
R from point B to point C
2R from pin 4 to point C
R from point C to point D
2R from pin 5 to point D
R from point D to point E
2R from pin 6 to point E
R from point E to point F
2R from pin 7 to point F
R from point F to point G
2R from pin 8 to point G
R from point G to point H
2R from pin 9 to point H
Direct wire from point H to headphone output (with optional buffer)
)


Also assumes headphone ground is connected to pin MIDRANGE via a low-pass filter.
We use PWM on pin 10 to get roughly half of the regulated voltage as the headphone ground,
so the 8-bit signal can show up as properly A/C.

Also assumes a pot attached to pin POTPIN (varying between VIN and GND) for pitch,
and a button attached between BUTTONPIN and GND, with pullup resistor, to
temporarily switch to triangle wave.

Also toggles the LED on LEDPIN (13) when the wavetable loops.

Note PIT counter frequency on Kinetis 20 (Teensy 3.0) is 50MHz

Original sketch:
- Hotcarrier 2008/04/14

Modified for potentiometer controlled pitch on analogpin0
- Collin Cunningham 2008/05/29

Modified for Teensy 3.0
- Emertonom 2012/10/27

*/

// Teensy 3.0 isn't AVR, so we don't need the AVR interrupt stuff
// #include <avr/io.h>
// #include <avr/interrupt.h>

#include <math.h> // for sin, round, and M_PI

// Pin for midrange reference voltage
#define MIDRANGE 10
// Pin for button input (sine/triangle selector)
#define BUTTONPIN 11
// Pin for LED
#define LEDPIN 13

// Number of steps in wave table
// Originally 32, but that's a little harsh--
// 256 bits sounds WAY better.
#define WAVESTEPS 256

// Constants for bitvalues within the TCTRL1 register
#define TIE 2
#define TEN 1

unsigned char sinetable[WAVESTEPS];
unsigned char tritable[WAVESTEPS];
volatile int i;
bool buttonval;
int potval;
volatile bool ledVal;

void ioinit (void)
{
pinMode(2,OUTPUT);
pinMode(3,OUTPUT);
pinMode(4,OUTPUT);
pinMode(5,OUTPUT);
pinMode(6,OUTPUT);
pinMode(7,OUTPUT);
pinMode(8,OUTPUT);
pinMode(9,OUTPUT);
pinMode(MIDRANGE,OUTPUT);
pinMode(BUTTONPIN, INPUT);
pinMode(A0, INPUT);
pinMode(LEDPIN, OUTPUT);
analogReference(INTERNAL);
analogReadRes(16);
}

void timer_setup() {

/* Old Arduino version of timer setup
TCCR2A = 0;
TCNT2=455; //455 outputs 1.007khz
TCCR2B = B00000010;
//Timer2 Overflow Interrupt Enable
TIMSK2 = 1<<TOIE2;
*/

// Teensy 3.0 version

SIM_SCGC6 |= SIM_SCGC6_PIT; // Activates the clock for PIT

// Turn on PIT
PIT_MCR = 0x00;

// Set the period of the timer. Unit is (1 / 50MHz) = 20ns
// So 1 kHz frequency -> 1 ms period -> 50000 * 20ns
PIT_LDVAL1 = 50000;

// Enable interrupts on timer1
PIT_TCTRL1 = TIE;
// Start the timer
PIT_TCTRL1 |= TEN;

NVIC_ENABLE_IRQ(IRQ_PIT_CH1); // Another step to enable PIT channel 1 interrupts
}

void setup(){
ioinit();
arraysetup();
cli();
timer_setup();
i = 0;
buttonval = false;
sei();
analogWrite(MIDRANGE, 127); // Output midrange signal as offset ground for headphone
digitalWrite(LEDPIN, LOW); // Start the LED off
ledVal = false;
}

void pit1_isr(void) {
PIT_TFLG1 = 1;
if (buttonval) {
digitalWriteFast(2, tritable[i] & 1);
digitalWriteFast(3, tritable[i] & 2);
digitalWriteFast(4, tritable[i] & 4);
digitalWriteFast(5, tritable[i] & 8);
digitalWriteFast(6, tritable[i] & 16);
digitalWriteFast(7, tritable[i] & 32);
digitalWriteFast(8, tritable[i] & 64);
digitalWriteFast(9, tritable[i] & 128);
} else {
digitalWriteFast(2, sinetable[i] & 1);
digitalWriteFast(3, sinetable[i] & 2);
digitalWriteFast(4, sinetable[i] & 4);
digitalWriteFast(5, sinetable[i] & 8);
digitalWriteFast(6, sinetable[i] & 16);
digitalWriteFast(7, sinetable[i] & 32);
digitalWriteFast(8, sinetable[i] & 64);
digitalWriteFast(9, sinetable[i] & 128);
}
++i;
// Arduino version of updating pitch
// TCNT2=potval;

// Teensy 3.0 Update Pitch
// If PIT_LDVAL1 gets low enough that the counter trips
// faster than this interrupt routine finishes, the
// sketch hangs...so we add 1000 just to be safe
PIT_LDVAL1 = round(potval / (WAVESTEPS / 8.0) + 1000);

if (i==WAVESTEPS) {
i=0; // Go back to start of wavetable
ledVal = !ledVal; // Toggle LED
if (ledVal) digitalWriteFast(LEDPIN, LOW);
else digitalWriteFast(LEDPIN, HIGH);
}
}

void arraysetup(void) {
int k;
// Set up sinetable with an 8-bit quantized sine wave of WAVESTEPS steps
for (k = 0; k < WAVESTEPS; k++) {
sinetable[k] = round(127.5 + 127.5 * sin( 2 * k * M_PI / WAVESTEPS ));
}

// Set up tritable with an 8-bit quantized triangle wave of WAVESTEPS steps
for (k = 0; k < WAVESTEPS; k++) {
if (k < WAVESTEPS / 4) {
tritable[k] = round(127.5 + 510.0 / WAVESTEPS * k);
} else if (k < 3 * WAVESTEPS / 4) {
tritable[k] = round(382.5 - 510.0 / WAVESTEPS * k);
} else {
tritable[k] = round(510.0 / WAVESTEPS * k - 382.5);
}
}
}

void loop() {
while (1) {
potval = analogRead(A0);
buttonval = (digitalRead(BUTTONPIN) == LOW);
}
}



Hope this helps someone!
-Nick

el_supremo
11-06-2012, 10:21 PM
Hi Nick,
I've used your fixes in a program of mine and I've run into a problem. The program needs to use Serial.print to the Serial monitor but the timer_setup() somehow blows away the serial port. After uploading the sketch, when I try to start the serial monitor, the IDE says it can't find COM9 (which is the correct one). When I run the example "Hello World" sketch, the serial monitor starts up fine and it also works if I just comment out the call to timer_setup() in my sketch.
Any idea how I can have timer interrupts and the Serial monitor in one sketch?

Pete

slomobile
11-08-2012, 06:29 PM
Could you elaborate on what this line does?

NVIC_ENABLE_IRQ(IRQ_PIT_CH1); // Another step to enable PIT channel 1 interrupts

Paul
11-08-2012, 09:42 PM
The ARM processor has an interrupt controller called the NVIC. It controls which interrupts are enabled and disabled. This line enables the interrupt.

jakob
11-09-2012, 09:04 AM
Hello All

I have been trying to get emertonom's suggesting for making interrupts work for me, but alas with no luck.
I am using Teensyduino Beta7 were it seemed Paul has already made the change suggested my emertonom in mk20dx128.c, it is now just in line 128 (along with corrections to all the other vector interrupts it looks like). I manually put the line "void pit1_isr(void);" near the top in the mk20dx128.h file.
After that I tried cutting emertonom's example code down to a bare minimum, that should enable me to test if the interrupts are working:


// Pin for LED
#define LEDPIN 13

// Constants for bitvalues within the TCTRL1 register
#define TIE 2
#define TEN 1

// Frequency to run the interrupt at
#define FREQ 2 // in Hertz

volatile int ledVal;

void timer_setup() {
// Teensy 3.0 version
SIM_SCGC6 |= SIM_SCGC6_PIT; // Activates the clock for PIT
// Turn on PIT
PIT_MCR = 0x00;
// Set the period of the timer. The C runs at 50MHz
// So interrupt length can be determined by 50Mhz/FREQ.
PIT_LDVAL1 = 50000000/FREQ;
// Enable interrupts on timer1
PIT_TCTRL1 = TIE;
// Start the timer
PIT_TCTRL1 |= TEN;
NVIC_ENABLE_IRQ(IRQ_PIT_CH1); // Another step to enable PIT channel 1 interrupts
}

void setup(){
pinMode(LEDPIN, OUTPUT);
cli();
timer_setup();
sei();
ledVal = 0;
digitalWrite(LEDPIN, ledVal); // Start the LED off
}

void pit1_isr(void) {
PIT_TFLG1 = 1;
digitalWrite(LEDPIN, ledVal ^= 1);
}

void loop() {
}

With this code I expect to see the LED (on pin 13) blink with a frequency of 2 Hz, which doesn't happen. Further, when I try to upload code again it won't access the HalfKay bootloader, which indicates that it is hanging due to some problem (but it compiles fine).

Does anybody have an idea what I might need to change to make it work?

Running Teensyduino on OSX 10.6.8.

el_supremo
11-10-2012, 05:14 PM
@jakob:
I got your code working with one mod to mk20dx128.h At about line 29 there is this code:

#ifdef __cplusplus
extern "C" {
#endif
Just add this after it:

void pit1_isr(void);

I presume that without this, the pit1_isr function in the sketch is not being found and is replaced with unused_isr().

Pete

jakob
11-10-2012, 07:12 PM
Pete

You completely saved my day. Thank you.
I had previously written the that line 6 lines higher, above the extern "C" part. I understand there is a reason why that wouldn't work, but your directions did.
Now it's on to making sound synthesis! :)

Jakob

crlab
11-18-2012, 02:15 PM
Thanks Jakob and Pete for the nice demo program and patch for mk20dx128.h. This works nicely.

emertonom
11-24-2012, 05:46 PM
Sorry all! I guess I wasn't getting notifications about updates to this thread. Glad you were able to suss it out without me! I'm completely a noob at this stuff...I just cobbled it together through looking at the Teensy 3 source files and the K20 family manual. That's why the NVIC line had such a vague comment...I wasn't actually sure what it did, just that it was in the Teensy source and my version based on the k20 manual source didn't work until I added it. Glad people are finding this useful. I think I'm going to start a new thread on my actual project, because I'm doing kind of a terrible job on it without guidance...

linuxgeek
01-29-2013, 05:28 AM
Why is it 50000000 (50MHz) & not 48MHz?

And should it change if the Teensy 3.0 is at 24 or 96MHz?

emertonom
01-29-2013, 08:39 PM
Honestly, I'm not entirely clear on why it's 50MHz instead of 48MHz; I was just going by the code in http://cache.freescale.com/files/32bit/doc/ref_manual/K20P64M50SF0RM.pdf in chapter 36, the Programmable Interrupt Controller, which assumes the PIT clock is 50MHz. It's entirely possible that it actually *is* 48MHz; I'm not confident that I would notice a 4% change in pitch in a song if I wasn't doing a direct comparison, and that's the only further test I've done on this setup. There's an earlier chapter that describes clock distribution, and I think that the PIT is driven by the bus clock, for which it says "MCGOUTCLK divided by OUTDIV2 clocks the bus slaves and peripheral (excluding memories)". So you could probably use that information to find the actual value.

I'll wind up coming back to this when I've got recording working, because playing back at the wrong speed would seriously compromise a live performance device, but at the moment I just don't have all the information. Sorry!

PaulStoffregen
01-29-2013, 08:46 PM
It is 48 MHz.

The best thing to use is "F_BUS", which is defined as the bus clock (the timers use the bus clock, not the CPU core clock). If the speed is changed to 24 MHz, F_BUS will automatically change, so your code can continue to work.

linuxgeek
01-30-2013, 12:04 AM
To show that it was reasonably accurate I just set the FREQ to 1Hz, and put a simple serial output for millis() in the interrupt section:

void pit1_isr(void) {
PIT_TFLG1 = 1;
Serial.println(millis());
}
It showed that it was consistently 1 second apart.

I know this is a beginner question but a quick google search did not point me in the right direction. How does one access F_BUS?

Is it as simple as this for the code above?:

PIT_LDVAL1 = F_BUS/FREQ;

linuxgeek
01-30-2013, 12:43 AM
Ok, I guess you can. Just tried it & it compiles. Don't have teensy around, but just downloaded IDE on my laptop to try it.

I see that it's in mk20dx128.h

Jp3141
01-30-2013, 05:50 PM
Of course that will show 1 s since both the PIT and the millis() counters run from the same clock source.

If you used a 32 kHz RTC crystal to generate the 1 s PIT, you could read it, and see that (after a few hours) the millis() don't track.


To show that it was reasonably accurate I just set the FREQ to 1Hz, and put a simple serial output for millis() in the interrupt section:

void pit1_isr(void) {
PIT_TFLG1 = 1;
Serial.println(millis());
}
It showed that it was consistently 1 second apart.

I know this is a beginner question but a quick google search did not point me in the right direction. How does one access F_BUS?

Is it as simple as this for the code above?:

PIT_LDVAL1 = F_BUS/FREQ;

linuxgeek
01-30-2013, 08:11 PM
It makes sense that they are on the same clock, but initially we were providing the interrupt a clock speed that we thought it might be at.

I was trying to see if the clock we were providing (50MHz or 48MHz) was the same as what millis() was using. Well, I didn't know for sure if millis() was using the same clock but I was figuring it does a pretty good job at tracking time to clock speed.
If they were considerably off, I was expecting to see some drift. At least that's how I think it would work.

PaulStoffregen
01-30-2013, 09:47 PM
The millis() counting is based on the CPU clock, which is synchronous with the bus clock, since they're derived from the same PLL, which ultimately depends on the 16 MHz crystal.

If you want to measure the clock accurately, there's pretty much only 2 ways. You could solder a 32 kHz crystal for the RTC. Then you'll have an accurate reference for 1 second. The other way would be using some external equipment, like a frequency counter or oscilloscope, which has an accurate reference which it uses to measure a frequency.

In its default state, Teensy 3.0 has only 1 accurate clock source... the 16 MHz crystal. You can't hope to measure its clock without another time reference with similar or better accuracy. The 16 MHz crystals we use on Teensy are very good: rated for 15 ppm initial accuracy (25C) and 15 ppm over -10C to +70C. The frequency counter build into many low cost multimeter's probably isn't that accurate!

If you have a Teensy 2.0, you could use analogWriteFrequency() to set the PWM frequency, and then use the FreqCount library on Teensy 2.0 to measure the signal. Both boards are made with the same crystal, so you'd be measuring with something based on a reference with the same inherent error as the signal you're measuring... but at least it's a cheap and easy way to measure if you don't have an oscilloscope or accurate (expensive) frequency counter.

loglow
01-31-2013, 01:13 AM
I used the code posted here to replace a bunch of elapsedMillis/Micros checks with timers. Thanks so much to all, it works great, and the timers are actually relatively simple to setup and use! I took it one step further and setup some macros to simplify the process, making the code much more readable and concise:


#define PIT_LDVAL(n) (PIT_LDVAL##n)
#define PIT_TCTRL(n) (PIT_TCTRL##n)
#define IRQ_PIT_CH(n) (IRQ_PIT_CH##n)
#define PIT_TFLG(n) (PIT_TFLG##n)
#define initPIT() {SIM_SCGC6 |= SIM_SCGC6_PIT; PIT_MCR = 0;}
#define startPIT(n, dur) {cli(); PIT_LDVAL(n) = F_BUS * float(dur); PIT_TCTRL(n) = 3; NVIC_ENABLE_IRQ(IRQ_PIT_CH(n)); sei();}
#define stopPIT(n) {cli(); NVIC_DISABLE_IRQ(IRQ_PIT_CH(n)); PIT_TCTRL(n) = 0; sei();}
#define clearPIT(n) {PIT_TFLG(n) = 1;}
Note: Updated slightly based on info below.

Usage is simple. Pass startPIT the timer number and the duration in seconds. Pass clearPIT just the timer number.

Here's an example for a 5 second timer:


void setup() {
initPIT();
startPIT(0, 5);
}

void pit0_isr() {
clearPIT(0);
}

Whenever you want to stop it, just call stopPIT (heh) with the timer number, even from inside the interrupt routine (for a one-time event).

This seems to be working well, but please let me know if I've made any mistakes. -Dan

Jp3141
01-31-2013, 02:45 AM
Thanks loglow.

I ran into a minor issue -- I wanted to set a timer for 1/2 s -- but using startPIT(0, 1/2); didn't work -- because the macro uses F_BUS*(dur) which expands to F_BUS*(1/2), and the 1/2 gets evaluated first (to 0 as in an integer)... So I changed to FBUS*dur (which has its own risks when macros are involved.

For anyone else, there are 4 PITs available -- 0..3

loglow
01-31-2013, 03:53 AM
I ran into a minor issue -- I wanted to set a timer for 1/2 s -- but using startPIT(0, 1/2); didn't work -- because the macro uses F_BUS*(dur) which expands to F_BUS*(1/2), and the 1/2 gets evaluated first (to 0 as in an integer)... So I changed to FBUS*dur (which has its own risks when macros are involved.

Good catch. Using a float constant (like 0.5) would work in this case.

I think the following change should address the problem though:


PIT_LDVAL(n) = F_BUS * float(dur);

linuxgeek
01-31-2013, 04:43 AM
I added a frequency and period function. I also used an include at the end of mk20dx128.h
#include <mk20dx128Extras.h>

and placed all the new stuff in mk20dx128Extras.h

I figure it'd be easier when there are updates to add it back in if need be.

Here's the header changes I did:


#define PIT_LDVAL(n) (PIT_LDVAL##n)
#define PIT_TCTRL(n) (PIT_TCTRL##n)
#define IRQ_PIT_CH(n) (IRQ_PIT_CH##n)
#define PIT_TFLG(n) (PIT_TFLG##n)
#define initPIT() {SIM_SCGC6 |= SIM_SCGC6_PIT; PIT_MCR = 0;}
#define startPITPeriod(n, period) {cli(); PIT_LDVAL(n) = F_BUS * float(period); PIT_TCTRL(n) = 3; NVIC_ENABLE_IRQ(IRQ_PIT_CH(n)); sei();}
#define startPITFreq(n, freq) {cli(); PIT_LDVAL(n) = F_BUS / (freq); PIT_TCTRL(n) = 3; NVIC_ENABLE_IRQ(IRQ_PIT_CH(n)); sei();}
#define stopPIT(n) {cli(); NVIC_DISABLE_IRQ(IRQ_PIT_CH(n)); PIT_TCTRL(n) = 0; sei();}
#define clearPIT(n) {PIT_TFLG(n) = 1;}


Here's the test code showing 2 different timers, one using frequency and the other period:


void setup() {
initPIT();
startPITFreq(0, 2);
startPITPeriod(1, 1);
}

void pit0_isr() {
clearPIT(0);
Serial.print("Timer0: ");
Serial.println(millis());
}

void pit1_isr() {
clearPIT(1);
Serial.print("Timer1: ");
Serial.println(millis());
}

void loop() {
delay(1000);
}



Output
Timer0: 2500
Timer0: 3000
Timer1: 3000
Timer0: 3500
Timer0: 4000
Timer1: 4000
Timer0: 4500
Timer0: 5000
Timer1: 5000
Timer0: 5500
Timer0: 6000
Timer1: 6000
Timer0: 6500
Timer0: 7000
Timer1: 7000
Timer0: 7500
Timer0: 8000
Timer1: 8000
Timer0: 8500
Timer0: 9000
Timer1: 9000
Timer0: 9500
Timer0: 10000
Timer1: 10000

loglow
01-31-2013, 09:50 AM
I added a frequency and period function.

Cool. That got me thinking that these timers should have a proper library implementation, so I wrote one.

Please note: This compiles fine, but I won't be able to check that it actually works until tomorrow.

https://github.com/loglow/PITimer

Jp3141
01-31-2013, 06:04 PM
Actually these lines:

#define startPITPeriod(n, period) {cli(); PIT_LDVAL(n) = F_BUS * float(period); PIT_TCTRL(n) = 3; NVIC_ENABLE_IRQ(IRQ_PIT_CH(n)); sei();}
#define startPITFreq(n, freq) {cli(); PIT_LDVAL(n) = F_BUS / (freq); PIT_TCTRL(n) = 3; NVIC_ENABLE_IRQ(IRQ_PIT_CH(n)); sei();}

should be:
#define startPITPeriod(n, period) {cli(); PIT_LDVAL(n) = F_BUS * float(period)-1; PIT_TCTRL(n) = 3; NVIC_ENABLE_IRQ(IRQ_PIT_CH(n)); sei();}
#define startPITFreq(n, freq) {cli(); PIT_LDVAL(n) = F_BUS / (freq)-1; PIT_TCTRL(n) = 3; NVIC_ENABLE_IRQ(IRQ_PIT_CH(n)); sei();}


The -1 is because the counter counts to 0 from the load value. This is only important it you have high frequency interrupts; you can prove it by setting a PIT at (say) 1 ms (==48000), and printing ( micros() % 1000) in it -- the result will creep up because you will be actually incrementing (delaying) by 48001 cycles instead of the expected 48000

linuxgeek
01-31-2013, 07:52 PM
Nice to know.

Hopefully this bug no longer exist in the freescale chips:
https://community.freescale.com/thread/105046

loglow
01-31-2013, 08:11 PM
I've tested and updated the PITimer library, and posted a new thread (http://forum.pjrc.com/threads/17543) about it. The library accounts for the off-by-one issue mentioned by Jp3141, and provides a bunch of other nice functionality as well. -Dan

Dave X
03-15-2013, 06:14 PM
Thanks Jakob and Pete for the nice demo program and patch for mk20dx128.h. This works nicely.

I think the mk20dx128.c & mk20dx128.h files now have the appropriate lines to let you set up your own interrupts without modification . The only one that isn't predefined as a weak alias to 'unused_isr' is systick_isr, which is a a weak alias to systick_default_isr. You replace these weak aliases with your own code by defining a new handler function.

My Mac's /Applications/Arduino.app/Contents/Resources/Java/hardware/teensy/cores/teensy3/mk20dx128.h is

void (* const gVectors[])(void) =
{
(void (*)(void))((unsigned long)&_estack), // 0 ARM: Initial Stack Pointer
ResetHandler, // 1 ARM: Initial Program Counter
nmi_isr, // 2 ARM: Non-maskable Interrupt (NMI)
hard_fault_isr, // 3 ARM: Hard Fault
memmanage_fault_isr, // 4 ARM: MemManage Fault
bus_fault_isr, // 5 ARM: Bus Fault
usage_fault_isr, // 6 ARM: Usage Fault
fault_isr, // 7 --
fault_isr, // 8 --
fault_isr, // 9 --
fault_isr, // 10 --
svcall_isr, // 11 ARM: Supervisor call (SVCall)
debugmonitor_isr, // 12 ARM: Debug Monitor
fault_isr, // 13 --
pendablesrvreq_isr, // 14 ARM: Pendable req serv(PendableSrvReq)
systick_isr, // 15 ARM: System tick timer (SysTick)
dma_ch0_isr, // 16 DMA channel 0 transfer complete
dma_ch1_isr, // 17 DMA channel 1 transfer complete
dma_ch2_isr, // 18 DMA channel 2 transfer complete
dma_ch3_isr, // 19 DMA channel 3 transfer complete
dma_error_isr, // 20 DMA error interrupt channel
unused_isr, // 21 DMA --
flash_cmd_isr, // 22 Flash Memory Command complete
flash_error_isr, // 23 Flash Read collision
low_voltage_isr, // 24 Low-voltage detect/warning
wakeup_isr, // 25 Low Leakage Wakeup
watchdog_isr, // 26 Both EWM and WDOG interrupt
i2c0_isr, // 27 I2C0
spi0_isr, // 28 SPI0
i2s0_tx_isr, // 29 I2S0 Transmit
i2s0_rx_isr, // 30 I2S0 Receive
uart0_lon_isr, // 31 UART0 CEA709.1-B (LON) status
uart0_status_isr, // 32 UART0 status
uart0_error_isr, // 33 UART0 error
uart1_status_isr, // 34 UART1 status
uart1_error_isr, // 35 UART1 error
uart2_status_isr, // 36 UART2 status
uart2_error_isr, // 37 UART2 error
adc0_isr, // 38 ADC0
cmp0_isr, // 39 CMP0
cmp1_isr, // 40 CMP1
ftm0_isr, // 41 FTM0
ftm1_isr, // 42 FTM1
cmt_isr, // 43 CMT
rtc_alarm_isr, // 44 RTC Alarm interrupt
rtc_seconds_isr, // 45 RTC Seconds interrupt
pit0_isr, // 46 PIT Channel 0
pit1_isr, // 47 PIT Channel 1
pit2_isr, // 48 PIT Channel 2
pit3_isr, // 49 PIT Channel 3
pdb_isr, // 50 PDB Programmable Delay Block
usb_isr, // 51 USB OTG
usb_charge_isr, // 52 USB Charger Detect
tsi0_isr, // 53 TSI0
mcg_isr, // 54 MCG
lptmr_isr, // 55 Low Power Timer
porta_isr, // 56 Pin detect (Port A)
portb_isr, // 57 Pin detect (Port B)
portc_isr, // 58 Pin detect (Port C)
portd_isr, // 59 Pin detect (Port D)
porte_isr, // 60 Pin detect (Port E)
software_isr, // 61 Software interrupt
};


I'm interested in using a void ftm1_isr(void) in "Output Compare Mode" as a non-periodic timer based on this bit:
Using the Output Compare mode is possible with (ELSnB:ELSnA = 0:0). In this case, when the counter reaches the value in the CnV register, the CHnF bit is set and the channel (n) interrupt is generated if CHnIE = 1, however the channel (n) output is not modified and controlled by FTM. -- user guide page 752

By default, the _init_Teensyduino_internal_() function in pins_teensy.c sets up FTM1 to enable PWM on pins 3 & 4 with a 488.28 Hz frequency with:


FTM1_CNT = 0;
FTM1_MOD = DEFAULT_FTM_MOD;
FTM1_C0SC = 0x28;
FTM1_C1SC = 0x28;
FTM1_SC = FTM_SC_CLKS(1) | FTM_SC_PS(DEFAULT_FTM_PRESCALE);


The FTM1_C1SC = 0x28; sets FTM1 to be Edge-aligned PWM, High-true, clear channel 1 on match.

analogWrite() sets up the value and the output pin 4 MUX for FTM1_C1SC PWM control with:



...
case 4: // PTA13, FTM1_CH1
FTM1_C1V = cval;
CORE_PIN4_CONFIG = PORT_PCR_MUX(3) | PORT_PCR_DSE | PORT_PCR_SRE;
break;



So, looking at all of that, here's an example of using FTM1 with interrupts:



/* testing FTM interrupts on Teensy 3.0 as arduino

*/


// Pin for LED
#define LEDPIN 13


//Constants for bitmasks for FTM
#define FTMx_CnSC_CHIE (1<<6) // per UG p 702
#define FTMx_CnSC_MSB (1 <<4) //
#define FTMx_CnSC_CHF (1<<7) // per UG pg 702


volatile int ledVal;

void timer_setup() {
// FTM1_SC = FTM_SC_CLKS(1) | 7; // system clock, div by 2^n
// FTM1_MOD = 65535;
FTM1_C1V = (FTM1_MOD & 0xff)/2; // Halfway up the modulo
FTM1_C1SC = FTMx_CnSC_CHIE | FTMx_CnSC_MSB; // Interupt enable, output compare mode
NVIC_ENABLE_IRQ(IRQ_FTM1); // enable the interrupt
}

void setup(){
pinMode(LEDPIN, OUTPUT);
cli();
timer_setup();
sei();
ledVal = 0;
digitalWrite(LEDPIN, ledVal); // Start the LED off
}

void ftm1_isr(void) { //
FTM1_C1SC &= ~FTMx_CnSC_CHF; // clear channel 1 interrupt
digitalWrite(LEDPIN, ledVal ^= 1);
FTM1_C1V = random(FTM1_MOD);
}

#define OTHERPIN 4
void loop() {
digitalWrite(OTHERPIN,!digitalRead(OTHERPIN));
delayMicroseconds(500); // 1KHz square wave on pin 4

}

Donziboy2
04-13-2013, 11:48 AM
Loglow in your description.

Period: 0.000013312 to 89.478485312 seconds (14 ns to 89 s)
should be
Period: 0.00000002083( 20.83ns ) to 89.478485312 seconds ( 89.47s )

0.000013312 is 13.312us, not sure how you got this.


Also

// ------------------------------------------------------------
// this version of period() (with an argument) is used to set the
// period of the timer in terms of units of time (seconds).
// for 48 MHz bus, range is about 14 ns (0.000014) to 89 s (89.0)
// ------------------------------------------------------------
void PITimer::period(float newPeriod) {
uint32_t newValue = roundFloat(F_BUS * newPeriod) - 1;
value(newValue);
}

I don't think you could get 14ns (0.000000014) unless your running at 96Mhz.

Also with PITimer period i think it should be used as clock cycles and not time. Similar to how they use the examples at page 827 in the datasheet.
roundFloat is also confusing me.. 32bit resolution and your rounding it?

Maybe create a Pitimer Cycles function?

Donziboy2
04-13-2013, 12:38 PM
This code should fire 2 pins every 0-255 clock cycles. (5.31249uS)
Instead im seeing it fire every 13.3uS


#include "PITimer.h"
#define BLANK 4
#define XLAT 5




void setup() {
PITimer1.period(0.000005312499999);
pinMode(BLANK, OUTPUT);
pinMode(XLAT, OUTPUT);

}
void loop() {
PITimer1.start(pulse);

}
void pulse()
{
digitalWriteFast(BLANK, HIGH);
digitalWriteFast(XLAT, HIGH);
digitalWriteFast(XLAT, LOW);
digitalWriteFast(BLANK, LOW);
}


This was taken with my Open Bench Logic Sniffer with a resolution of 5nS
384

Changing it to 1023 cycles or 21.3125uS ends up almost spot on at 21.3uS every time. Just call me picky but I want more resolution under 13uS :rolleyes:


PITimer1.period(0.0000213125);


385


Other then that this looks to be a very nice library, will Paul be including this library at some time?

linuxgeek
04-14-2013, 05:10 AM
Try IntervalTimer, that's what will be included in next release.

Donziboy2
04-14-2013, 08:50 PM
It looks interesting linuxgeek.
Its maximum resolution appears to be 48 clock cycles or about 1us.
If you try setting it to anything under 1 it does nothing. Making 1us your minimum.

linuxgeek
04-15-2013, 01:04 AM
For simplicity, it's done that way but you could change it.

I believe I remember that the clock (48MHz) was divideded by 1 million, which is why you can only go down to 48 clock cycles.
You could change that line to dividie by 48 million and you would have clock cycles.

PaulStoffregen
04-15-2013, 08:54 AM
If you try setting it to anything under 1 it does nothing. Making 1us your minimum.

Are you really going to execute more than 1 million interrupts per second?

The ARM Cortex-M4 takes at least 12 cycles to respond to the interrupt, and 12 more to return to your program (plus maybe a few more depending on flash cache misses). Inside the interrupt, you have to at least manipulate the PIT registers, which involves a few instructions to initialize registers, and at least one write which goes over the peripheral bridge, adding a cycle or two of bus time. Then your code has to actually DO something.....

If you're intending to design a project involving so many interrupts per second, perhaps it would be better to start a new thread to discuss what you're trying to accomplish and ideas for using the on-chip hardware (timers, DMA, etc) to handle such incredibly high speeds without excessive CPU overhead?

Donziboy2
04-15-2013, 11:11 AM
Im not looking for anything even close to one million interrupts per second, im looking for the accuracy to stay within 100ns of a project design. If a project is running at say 7Khz(142.857us), and updating that timing data every say 576 project cycles as an example.

So rounding up gives 143us * 576 = 82.368ms, adding a whole 116us to the timing.

and if rounding down 142us * 576 = 81.792ms, taking a whole 460us from the timing.

but if looking for say 100ns accuracy 142.8us * 576 = 82.252ms.... which is still 32.9us off but closer then rounding to within 1us with the current code.

For my project at least I think I will look into using counters, to get the number of clock cycles and set the PIT that way. I will probably change the IntervalTimer library here.

// check range and calc value based on period
if (newPeriod == 0 || newPeriod > MAX_PERIOD) return false;
uint32_t newValue = F_BUS * (newPeriod / 1000000.0) - 1;

to


// check range and calc value based on period
if (newPeriod == 0 || newPeriod > MAX_PERIOD) return false;
uint32_t newValue = newPeriod - 1;

Which is closer to the examples used in the datasheet for setting timing based on FBUS clock cycles.

And set newPeriod based on subtracting the clock values between two 576 cycle periods.

Im not looking to cause headaches Paul, althou I probably am anyway.

PaulStoffregen
04-18-2013, 05:20 PM
Im not looking for anything even close to one million interrupts per second, im looking for the accuracy to stay within 100ns of a project design. If a project is running at say 7Khz(142.857us), and updating that timing data every say 576 project cycles as an example.

I'm working on a modification to IntervalTimer (as it will appear in Teensyduino 1.14) to accept either integer or float values for the microseconds. So you'll be able to use "mytimer.begin(myfunction, 142.857);" and have it produce at accurate 7 kHz rate.

I'm also working on having it do the calc within an inline function, so for a numeric constant, the compiler will do the float math at compile time and embed the actual integer cycles number into the executable image.

Donziboy2
04-19-2013, 09:20 PM
That sounds good. More accuracy is always helpful.
Most likely for my main project it will be adjusting the frequency based on the rotation speed. So it wont be a constant value, it will be changing.
But my early testing will be using a constant value, and maybe a few other projects I have been looking at.
I still have a ways to go, but I do have some TLC5940 test boards on the way. I will keep working on my code and using my logic sniffer to see my failures in all their glory.
409

btmcmahan
05-30-2013, 03:47 AM
I need help with another type of interrupt.
This is the interrupt that is called by SPI transfers.

It uses these registers for configuration (I think):
• NVICISER0
• NVICICER0
• NVICISPR0
• NVICICPR0
• NVICIABR0
• NVICIPR3

Can anyone provide some help on this? I haven't been able to find info on these registers.
Here's my basic code:



void setup(){
NVIC_ENABLE_IRQ(12); //SPI is IRQ 12
//NVIC_SET_PENDING(12);
//NVIC_CLEAR_PENDING(12);
//__enable_irq();
}



void spi0_isr(void){
data = SPI0_POPR;
packetsReceived++;
}

PaulStoffregen
05-30-2013, 11:31 PM
Here is documentation on the NVIC:

http://infocenter.arm.com/help/index.jsp?topic=/com.arm.doc.ddi0337e/Cihcffda.html

GeoffT
05-31-2013, 10:30 PM
Hi, my first post here. I still can't get an intervalTimer to trigger an interrupt on Teensy 3.0. I had timer interrupts working in an earlier Arduino/Teensyduino install, but no more. Here is a stripped down sketch I think should work:

void smpltimerisr(void);
const int ledPin = 13; // Teensy3 has LED on 13
volatile unsigned long smplidx = 0;

void setup(){
pinMode(ledPin, OUTPUT);
IntervalTimer smpltimer;
smplidx = 0;
smpltimer.begin(smpltimerisr, 1000000);
}
void loop(){
if(smplidx == 0)
digitalWrite(ledPin, HIGH);
else
digitalWrite(ledPin, LOW);
}

void smpltimerisr(void)
{
smplidx++;
}

//The above compiles fine, loads and executes, but the LED stays ON, indicating no interrupts active; any ideas?

dave
07-26-2013, 07:49 PM
Hi
I m new here. I receive today my first Teensy 3.0, the first time I use an ARM.In the past I had very good experience with Arduino, MicroChip and other 8-bit Microcontrtoller, but for the ARM this is the first time.
Rigth now I have problems with interrupts, since I didn t find any example or tutorial to use interrupts with an ARM. For my porpouse I need a Timer interrupt und a raising/falling interrupt (to read some PWM signals from an RX receiver). Since I dont know really how to start, could you me please send me a simple example? The link provided by Paul is directly from ARM, but it is not clear to me.

Thanks in advance and gratulations for your products!!
Regards
Dave

firejoss
10-21-2013, 12:42 PM
Hello,

I am working on the OV7670 camera and would like to capture the output byte of it via teensy 3.0 port C. Each byte is shifted from the camera when PCLK(pixel clock) rise up.
I am trying to use interrupts on port C (or other anyway) to detect PCLK and start an interrupt which would implement GPIOC_PDIR to capture the byte on port C.

I used attachInterrupts before, but for some speed reasons, I would like to use directly portc_isr in mk20dx128.c .
I am working under visual micro in Visual Studio 2012. When I uncomment the lines concerning NVIC register, I think the program freeze or there's an infinite loop somewhere because I can't see my serial.println in my loop() {} on serial windows.
Furthermore, to check if the interrupt was working, I wrote "GPIOC_PSOR = (1 << 5);" in the unsused_isr() in mk20dx128.c, but the teensy leds neither seems to light up once the program is downloaded. (no errors in the program compilation & uploading)

void setup()
{

NVIC_ICER1 |= (1<<10); // 42 mod 32
NVIC_ISER1 |= (1<<10);
NVIC_ENABLE_IRQ(IRQ_PORTC); // Interrupt N42

PORTA_PCR7 = PORT_PCR_MUX(0x1); // GPIO is alt1 function for this pin
PORTA_PCR7 = PORT_PCR_IRQC(0x9); // rising edge interrupts (on portC 5=> teensy3 pin 12)

//__enable_irq(); IS THIS REQUIRED ?

}


Thanks so much for any help, and sorry for my english, I'm actually a french student. =D

firejoss.