Teensy 3.0 and interrupts

Status
Not open for further replies.

emertonom

Active member
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:

Code:
// 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
Code:
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
 
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
 
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.
 
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.

Code:
/* 
  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
 
Last edited:
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
 
Could you elaborate on what this line does?

NVIC_ENABLE_IRQ(IRQ_PIT_CH1); // Another step to enable PIT channel 1 interrupts
 
The ARM processor has an interrupt controller called the NVIC. It controls which interrupts are enabled and disabled. This line enables the interrupt.
 
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:

Code:
// 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.
 
@jakob:
I got your code working with one mod to mk20dx128.h At about line 29 there is this code:
Code:
#ifdef __cplusplus
extern "C" {
#endif
Just add this after it:
Code:
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
 
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
 
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...
 
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!
 
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.
 
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;
 
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
 
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;
 
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.
 
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.
 
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:

Code:
#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:

Code:
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
 
Last edited:
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
 
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:

Code:
PIT_LDVAL(n) = F_BUS * float(dur);
 
Last edited:
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:
Code:
#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:
Code:
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);
}

Code:
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
 
Status
Not open for further replies.
Back
Top