Teensy400Hz

/*
Pre-computing the sin wave into a lookup table (LUT).
Uses the PDB (Programmable Delay Buffer) and DAC buffer and interrupts
// from https://forum.pjrc.com/threads/25275-Very-Stable-DAC-Output-using-PDB-(Programmable-Delay-Block)-and-DAC-Buffer?p=43873&viewfull=1#post43873
// modified with 8x and +0.5 in array generator.
// works rock solid on teensy 3.1
// teensy 3.1 also seems to drive the synchro exciter just fine.

NB DAC_C1_DACBFEN is misdefined as 0x00 in mk20dx128.h (as of release 1.18). It should be 0x01!!!
*/

// hardware defines not found elsewhere
#define PDB0_DACINT0 *(volatile uint32_t *)0x40036154 // PDB DAC interval register
#define PDB0_DACINTC0 *(volatile uint32_t *)0x40036150 // PDB DAC interval control register
#define PDB_DACINTC0_TOE 0x01 // 0x01 -> PDB DAC interal trigger enabled

#define DAC0_SR_DACBFWMF 0x04 // buffer watermark flag
#define DAC0_SR_DACBFRTF 0x02 // buffer top flag
#define DAC0_SR_DACBFRBF 0x01 // buffer bottom flag (not used)
#define DAC0_DAT0 (uint16_t *)0x400CC000 // DAC word buffer base address
#define DAC0_DAT7 (uint16_t *)0x400CC010 // DAC word buffer top eight words base address.

#define audioRate 48000 // desired audio rate
#define BUS_CLOCK 48000000 // bus clock (1/2 system clock, on my sys anyway)
#define maxAmplitude 3500

#define PDB0_MOD_TIME ((int)(BUS_CLOCK/audioRate))  // modulus time for the DAC in PDB
// PDB0_MOD_TIME == 1000 in this case. This number will be the multiple of the
// bus clock (48000000 / 48000 = 1000). The PDB will trigger the DAC and output a
// value from the next element in the DAC buffer.

#define twopi (3.14159265359 * 2.0) // needed to fill the LUT with a sine wave

uint16_t *p1;

// an array to hold a sine wave
#define LUT_SIZE 1024
uint16_t lut[LUT_SIZE];
int lutndx = 0; // wave buffer pointer

// variables to copy the DAC interrupt flags
volatile uint32_t dacBufferWatermarkFlag = 0;
volatile uint32_t dacBufferTopFlag = 0;
volatile uint32_t dacBufferBottomFlag = 0; // not used

// my DAC interrupt service routine. Simply copying the state of two flags
void dac0_isr(void) {
  if ( DAC0_SR & DAC0_SR_DACBFWMF ) { // see if watermark flag is set        
    DAC0_SR &= ~(DAC0_SR_DACBFWMF); // clear the watermark flag
    dacBufferWatermarkFlag = 1; // copy it
  }
  
  if ( DAC0_SR & DAC0_SR_DACBFRTF ) { // see if top flag is set
    DAC0_SR &= ~(DAC0_SR_DACBFRTF); // clear the top flag
    dacBufferTopFlag = 1; // copy it
  }  
}

void setup() {
  
  //fill the LUT with a sine wave
  double midpoint = maxAmplitude / 2.0;
  for (int i=0; i<LUT_SIZE; i++) {
    lut[i] = (int)(sin(twopi*8*((double)i/(double)LUT_SIZE)) * midpoint + midpoint +0.5);
  }

  // reset everything in the DAC first  
  SIM_SCGC2 |= SIM_SCGC2_DAC0; // turn on clock to the DAC
  DAC0_C0 |= DAC_C0_DACEN; // enable the DAC; must do before any of the following
  
  /* Notes
  By default, the DAC trigger resets to 0 => hardware trigger. I want this
  By default, the DAC uses the high power mode. Good.
  By default, DMA is disabled. Good
  */

  DAC0_C0 |= DAC_C0_DACRFS; // 3.3V VDDA is DACREF_2  
  DAC0_C0 |= DAC_C0_DACBWIEN; // Enable interrupts on watermark reached
  DAC0_C0 |= DAC_C0_DACBTIEN; // Enable interrupts on top of buffer (zeroth position)  
  DAC0_C1 |= DAC_C1_DACBFWM(3); // set watermark 4 words away from upper limit
  DAC0_C2 |= DAC_C2_DACBFUP(15); // resets to 15 but setting it anyway...
   
  // clear the buffer flags
  DAC0_SR &= ~(DAC0_SR_DACBFWMF);
  DAC0_SR &= ~(DAC0_SR_DACBFRTF);
  DAC0_SR &= ~(DAC0_SR_DACBFRBF); // not used but clearing anyway
  
  NVIC_ENABLE_IRQ(IRQ_DAC0); // enable irq defined above
  
  DAC0_C1 |= (0x01) << 0; // 0x01 enables the DAC buffer! See note above
 
  // prefill the DAC buffer with first 16 values from the lut
  p1 = DAC0_DAT0;
  for (int i = 0; i < 16; i++) {
    *p1++ = lut[lutndx++];
    if (lutndx == (LUT_SIZE-1)) lutndx = 0;
  }

  // Setup the PDB
  /* PDB Notes
  -Setup the PDB to start on a software trigger. Run in continuous mode.
  -By default, the multiplier (MULT) value (prescaler multiplier value) is '1'.
  -By default, the prescaler is 1 * MULT = 1 * 1 which implies that the PDB
   is running at full bus clock speed. (Which is 48Mhz)
  -Every time the counter reaches the MOD value the DACINT0 counter is reset
   to 0. 
  */  
 
  SIM_SCGC6 |= SIM_SCGC6_PDB; // turn on the PDB clock  
  PDB0_SC |= PDB_SC_PDBEN; // enable the PDB  
  PDB0_SC |= PDB_SC_TRGSEL(15); // trigger the PDB on software start (SWTRIG)  
  PDB0_SC |= PDB_SC_CONT; // run in continuous mode  
  PDB0_MOD = PDB0_MOD_TIME; // modulus time for the PDB  
  PDB0_DACINT0 = (uint16_t)(1 * PDB0_MOD_TIME); // we won't subdivide the clock... 
  PDB0_DACINTC0 |= PDB_DACINTC0_TOE; // enable the DAC interval trigger  
  PDB0_SC |= PDB_SC_LDOK; // update pdb registers  

  PDB0_SC |= PDB_SC_SWTRIG; // ...and start the PDB
}

void loop() {  
  
  if ( dacBufferWatermarkFlag ) { // refill the lower half of circular DAC buffer
    dacBufferWatermarkFlag = 0;
    p1 = DAC0_DAT0;
    for (int i=0; i<8; i++) {
      *p1++ = lut[lutndx++];
      if (lutndx == (LUT_SIZE-1)) lutndx = 0;
    }    
  }
  
  if ( dacBufferTopFlag ) { // refill the upper half of circular DAC buffer
    dacBufferTopFlag = 0;
    p1 = DAC0_DAT7;
    for (int i=0; i<8; i++) {
      *p1++ = lut[lutndx++];
      if (lutndx == (LUT_SIZE-1)) lutndx = 0;
    }
  }  
}