Timer Interrupts on Teensy

Status
Not open for further replies.
Hi,

I've been using the Teensy 3.6 for a while.
I'm still relatively new to programming, but I am trying to port the below code, intended for Arduino Due to Teensy (original page: http://rcarduino.blogspot.dk/2012/12/arduino-due-dds-part-1-sinewaves-and.html).

The way timer interrupts are used on each seems to be quite different.
I understand (on a basic level) the way in which they work on Teensy from here:
https://www.pjrc.com/teensy/td_timing_IntervalTimer.html
But I'm getting stuck with some of the more specific/advanced parts of how interrupts are used on the Due and how these can be accessed with the Teensy. For example: "NVIC_EnableIRQ(TC4_IRQn);", not sure how to find the timer number, etc...

Any help with this would really be massively appreciated.

Code:
// RCArduino DDS Sinewave for Arduino Due
// RCArduino DDS Sinewave by RCArduino is licensed under a Creative Commons Attribution 3.0 Unported License.
// Based on a work at rcarduino.blogspot.com.

// For helpful background information on Arduino Due Timer Configuration, refer to the following link
// thanks to Sebastian Vik
// http://arduino.cc/forum/index.php?action=post;topic=130423.15;num_replies=20

// For background information on the DDS Technique see 
// http://interface.khm.de/index.php/lab/experiments/arduino-dds-sinewave-generator/

// For audio sketches making extensive use of DDS Techniques, search the RCArduino Blog
// for the tags Audio or synth

// These are the clock frequencies available to the timers /2,/8,/32,/128
// 84Mhz/2 = 42.000 MHz
// 84Mhz/8 = 10.500 MHz
// 84Mhz/32 = 2.625 MHz
// 84Mhz/128 = 656.250 KHz
// 
// 44.1Khz = CD Sample Rate
// Lets aim for as close to the CD Sample Rate as we can get -
//
// 42Mhz/44.1Khz = 952.38
// 10.5Mhz/44.1Khz = 238.09 // best fit divide by 8 = TIMER_CLOCK2 and 238 ticks per sample 
// 2.625Hmz/44.1Khz = 59.5
// 656Khz/44.1Khz = 14.88

// 84Mhz/44.1Khz = 1904 instructions per tick

// the phase accumulator points to the current sample in our wavetable
uint32_t ulPhaseAccumulator = 0;
// the phase increment controls the rate at which we move through the wave table
// higher values = higher frequencies
volatile uint32_t ulPhaseIncrement = 0;   // 32 bit phase increment, see below

// full waveform = 0 to SAMPLES_PER_CYCLE
// Phase Increment for 1 Hz =(SAMPLES_PER_CYCLE_FIXEDPOINT/SAMPLE_RATE) = 1Hz
// Phase Increment for frequency F = (SAMPLES_PER_CYCLE/SAMPLE_RATE)*F
#define SAMPLE_RATE 44100.0
#define SAMPLES_PER_CYCLE 600
#define SAMPLES_PER_CYCLE_FIXEDPOINT (SAMPLES_PER_CYCLE<<20)
#define TICKS_PER_CYCLE (float)((float)SAMPLES_PER_CYCLE_FIXEDPOINT/(float)SAMPLE_RATE)

// to represent 600 we need 10 bits
// Our fixed point format will be 10P22 = 32 bits


// We have 521K flash and 96K ram to play with

// Create a table to hold the phase increments we need to generate midi note frequencies at our 44.1Khz sample rate
#define MIDI_NOTES 128
uint32_t nMidiPhaseIncrement[MIDI_NOTES];

// fill the note table with the phase increment values we require to generate the note
void createNoteTable(float fSampleRate)
{
  for(uint32_t unMidiNote = 0;unMidiNote < MIDI_NOTES;unMidiNote++)
  {
    // Correct calculation for frequency
    Serial.print(unMidiNote);
    Serial.print(" ");
    float fFrequency = ((pow(2.0,(unMidiNote-69.0)/12.0)) * 440.0);
    Serial.print(fFrequency);
    Serial.print(" ");
    
    nMidiPhaseIncrement[unMidiNote] = fFrequency*TICKS_PER_CYCLE;
    Serial.println(nMidiPhaseIncrement[unMidiNote]);
  }
}

// Create a table to hold pre computed sinewave, the table has a resolution of 600 samples
#define WAVE_SAMPLES 600
// default int is 32 bit, in most cases its best to use uint32_t but for large arrays its better to use smaller
// data types if possible, here we are storing 12 bit samples in 16 bit ints
uint16_t nSineTable[WAVE_SAMPLES];

// create the individual samples for our sinewave table
void createSineTable()
{
  for(uint32_t nIndex = 0;nIndex < WAVE_SAMPLES;nIndex++)
  {
    // normalised to 12 bit range 0-4095
    nSineTable[nIndex] = (uint16_t)  (((1+sin(((2.0*PI)/WAVE_SAMPLES)*nIndex))*4095.0)/2);
    Serial.println(nSineTable[nIndex]);
  }
}

void setup()
{
  Serial.begin(9600);

  createNoteTable(SAMPLE_RATE);
  createSineTable();
  
  /* turn on the timer clock in the power management controller */
  pmc_set_writeprotect(false);
  pmc_enable_periph_clk(ID_TC4);

  /* we want wavesel 01 with RC */
  TC_Configure(/* clock */TC1,/* channel */1, TC_CMR_WAVE | TC_CMR_WAVSEL_UP_RC | TC_CMR_TCCLKS_TIMER_CLOCK2);
  TC_SetRC(TC1, 1, 238); // sets <> 44.1 Khz interrupt rate
  TC_Start(TC1, 1);
  
  // enable timer interrupts on the timer 
  TC1->TC_CHANNEL[1].TC_IER=TC_IER_CPCS;
  TC1->TC_CHANNEL[1].TC_IDR=~TC_IER_CPCS;
  
  /* Enable the interrupt in the nested vector interrupt controller */
  /* TC4_IRQn where 4 is the timer number * timer channels (3) + the channel number (=(1*3)+1) for timer1 channel1 */
  NVIC_EnableIRQ(TC4_IRQn);

  // this is a cheat - enable the DAC
  analogWrite(DAC0,0);
}

void loop()
{
  // read analog input 0 drop the range from 0-1024 to 0-127 with a right shift 3 places,
  // then look up the phaseIncrement required to generate the note in our nMidiPhaseIncrement table
  uint32_t ulInput = analogRead(0);
  ulPhaseIncrement = nMidiPhaseIncrement[ulInput>>3]; 
}

void TC4_Handler()
{
  // We need to get the status to clear it and allow the interrupt to fire again
  TC_GetStatus(TC1, 1);
  
  ulPhaseAccumulator += ulPhaseIncrement;   // 32 bit phase increment, see below

  // if the phase accumulator over flows - we have been through one cycle at the current pitch,
  // now we need to reset the grains ready for our next cycle
  if(ulPhaseAccumulator > SAMPLES_PER_CYCLE_FIXEDPOINT)
  {
   // DB 02/Jan/2012 - carry the remainder of the phase accumulator
   ulPhaseAccumulator -= SAMPLES_PER_CYCLE_FIXEDPOINT;
   }

  // get the current sample   
  uint32_t ulOutput = nSineTable[ulPhaseAccumulator>>20];
  
  // we cheated and user analogWrite to enable the dac, but here we want to be fast so
  // write directly  
  dacc_write_conversion_data(DACC_INTERFACE, ulOutput);
}
 
quick glance - the td_timing_IntervalTimer handles all the timer setup without that overhead in setup() to enable in the posted code.

By the example doing : myTimer.begin(blinkLED, 150000); // blinkLED to run every 0.15 seconds

Will call the blinkLED() as indicated - no other code or timer# is needed.

There may be better ways to do it but to use this remove the DUE specific ( anything "TC_" ) code and try : myTimer.begin(TC4_Handler, 1/44100);
 
For generating waveforms at 44.1 kHz sample rate, you really should consider the Teensy Audio Library.

https://www.pjrc.com/teensy/td_libs_Audio.html

The latest code on github in particular has greatly improved DDS waveform code, including using other signals for full audio rate frequency or phase modulation. It's far more efficient and much more flexible than this simple timer interrupt & DAC approach. It generates the waveform 128 samples at a time. This is part of a larger system that lets you create many waveforms and other synthesis & effects and mix them together for many "voices", with efficient DMA transfers to the DAC or I2S digital audio.

Here's a link to the waveform documentation (right side panel):

https://www.pjrc.com/teensy/gui/?info=AudioSynthWaveform

If you really want to dig into the DDS code, it's within synth_waveform.cpp.

https://github.com/PaulStoffregen/Audio/blob/master/synth_waveform.cpp

A similar lookup table is used for the sine wave, but it does 2 lookups and uses the remaining DDS fractional phase bits for linear interpolation between them, so you get a really great result for a modestly sized table.


If you *really* want to use this simple but limited code from Arduino Due, and if you want to actually use the timers directly instead of using IntervalTimer, you can find all the info about the timers in the reference manual.

https://www.pjrc.com/teensy/datasheets.html

Control of the NVIC is done using macros defined in kinetis.h. The interrupt names are defined there too. They're different than Arduino Due, but otherwise things work more or less the same.

But if your goal is actually synthesizing waveforms for audio, the Teensy Audio Library is already well developed and uses a far more efficient approach that scales up to allow dozens of simultaneous waveforms & effects, with DMA and a much lower interrupt rate. I highly recommend using the audio library, unless the thing you want to do is tinker with interrupts & low level code.
 
Thanks everyone for the replies!
I managed to pick up an audio adapter board and I'm now looking into the audio library at the moment.
 
Status
Not open for further replies.
Back
Top