Hello to all.
This is my first post and I hope you'll go easy on me. I spent a lot of hours experimenting with the DAC and the PDB (Programmable Delay Block) to drive the DAC. Perhaps this will help someone else. The code below details the following:
-Generation of extremely stable DAC output. A sine wave in this example.
-Usage of the DAC and built-in DAC buffer.
-The PDB (Programmable Delay Block) to trigger the DAC, saving on usage of any other interval timers or clocks.
The DAC has a 16 word buffer and three interrupts that can be associated with the state of the buffer: top, watermark and bottom. There is a lot of flexiblity here. I programmed the DAC in such a way that I treat it as an A/B ping-pong buffer. There are different ways to program the buffer but this seemed the simplest to me. The buffer has 16 word locations (0-15) with the watermark pointing to 4 words before the bottom (15th) location. When I reach the watermark an interrupt occurs and I know I can refill the lower half of the buffer. Next, when I reach 'top' (0th position) I know I have wrapped around the end and the top half of the buffer can be refilled.
The 12-bit DAC can be triggered by the PDB. The PDB uses a 'MODULUS' which is some multiple of the bus clock (48Mhz on my system).
Further, the PDB can have an additional clock the subdivides the modulus even further. For example, one could fire off the main PDB
trigger every 1000 bus clocks. One could additionally divide this into 10 parts (with the PDB DAC interval register) so that the DAC could be triggered every
100 bus clocks. Since I'm only using the PDB to clock the DAC I'm just concerned with setting the modulus. That is why I set the PDB DAC interval register to the same value as the modulus.
This works as of r1.18. Please let me know if you have any questions.
This is my first post and I hope you'll go easy on me. I spent a lot of hours experimenting with the DAC and the PDB (Programmable Delay Block) to drive the DAC. Perhaps this will help someone else. The code below details the following:
-Generation of extremely stable DAC output. A sine wave in this example.
-Usage of the DAC and built-in DAC buffer.
-The PDB (Programmable Delay Block) to trigger the DAC, saving on usage of any other interval timers or clocks.
The DAC has a 16 word buffer and three interrupts that can be associated with the state of the buffer: top, watermark and bottom. There is a lot of flexiblity here. I programmed the DAC in such a way that I treat it as an A/B ping-pong buffer. There are different ways to program the buffer but this seemed the simplest to me. The buffer has 16 word locations (0-15) with the watermark pointing to 4 words before the bottom (15th) location. When I reach the watermark an interrupt occurs and I know I can refill the lower half of the buffer. Next, when I reach 'top' (0th position) I know I have wrapped around the end and the top half of the buffer can be refilled.
The 12-bit DAC can be triggered by the PDB. The PDB uses a 'MODULUS' which is some multiple of the bus clock (48Mhz on my system).
Further, the PDB can have an additional clock the subdivides the modulus even further. For example, one could fire off the main PDB
trigger every 1000 bus clocks. One could additionally divide this into 10 parts (with the PDB DAC interval register) so that the DAC could be triggered every
100 bus clocks. Since I'm only using the PDB to clock the DAC I'm just concerned with setting the modulus. That is why I set the PDB DAC interval register to the same value as the modulus.
This works as of r1.18. Please let me know if you have any questions.
Code:
/*
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);
}
// 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;
}
}
}