
Originally Posted by
Theremingenieur
Tested it today. Works like a charm! Just I have still a problem when I want to increase the lutndx pointer by more than 1 in order to output higher frequencies. That gives ugly spikes in the output signal because read operations beyond the lut table end may happen since there is actually the increment first and then a "end of table check" with a "==" comparison. I'll try to fix that in the next days and post a (hopefully) improved version here.
I quickly tested this update which generates multiples of the ~ 46.83 Hz signal. Also, I think the original was wrong (it skipped the last LUT value) when it used
if (lutndx == (LUT_SIZE-1)) lutndx = 0; I think this was wrong -- should be just LUT_SIZE
also, the line which computes the LUT should have a 0.5 added (because the int() rounds down) to get the nearest sine point
Code:
#define STEP 1 // tested up to 31, should go higher/*
Pre-computing the sin wave into a lookup table (LUT).
Uses the PDB (Programmable Delay Buffer) and DAC buffer and interrupts
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*((double)i/(double)LUT_SIZE)) * midpoint + midpoint + 0.5);
// added 0.5 here to make int() round to nearest (positive) integer
}
// 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]; // remove ++
lutndx += STEP; // add step here
// if (lutndx == (LUT_SIZE-1)) lutndx = 0; I think this was wrong -- should be just LUT_SIZE
if (lutndx >= LUT_SIZE) lutndx -= 0; // effectively a modulo counter
}
// 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]; // remove ++
lutndx += STEP; // add step here
// if (lutndx == (LUT_SIZE-1)) lutndx = 0; I think this was wrong -- should be just LUT_SIZE
if (lutndx >= LUT_SIZE) lutndx -= 0; // effectively a modulo counter
}
}
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]; // remove ++
lutndx += STEP; // add step here
// if (lutndx == (LUT_SIZE-1)) lutndx = 0; I think this was wrong -- should be just LUT_SIZE
if (lutndx >= LUT_SIZE) lutndx -= 0; // effectively a modulo counter
}
}
}