Triggering the DAC with the ADC COnversion COmplete (COCO) Flag

Status
Not open for further replies.

willie.from.texas

Well-known member
I am trying to synchronize the DAC with the ADC COCO Flag. In section 44.1.5 of the reference manual (page 1105) it states, "Application code can set the PDBx_DACINTCn[EXT] bit to allow DAC external trigger input when the corresponding ADC Conversion complete flag, ADCx_SC1n[COCO], is set." I have searched the forum, Github, and the NXP site for any examples of how to trigger the DAC in this way. From the documentation I have the understanding that ADC0 can only trigger DAC0, and ADC1 can only trigger DAC1, but I've tried code to set this up with no success. In my view, the documentation doesn't really tell you how it is done, only that you can. Does anyone have any insight into how to set this up? I know how to set it up using the PDB with DMAs (and have the battle scars to prove it) but I don't believe I can accomplish what I need taking this approach.
Thanks!
 
Sorry for not getting back with you sooner. I was on the road. Thanks for responding. Yes, that is exactly what I tried to do. Enclosed is a sketch that reads the 16-bit ADC and writes the result out to the 12-bit DAC. Line 210 is a boolean flag called, external_trig. It is set to false. If you compile this sketch it will run up to a sample rate of 2.5 microseconds. If you don't have a function generator I programmed a PWM pin 2 to output a pulse at 5 kHz. Simply connect that output to pin A9, which is ADC0 channel 0 (or you could connect A9 to a function generator if you have one). The data comes out on DAC0 with a 32 sample delay (because of the buffer sizes that I'm using). Lines 224 and 225 is where the external_trig is tested and the DAC trigger is selected. If you set that flag to "true" and compile, the sketch will not run.

Code:
/*

  waveformDMA

  Send real-time waveform sampled by the ADC through the DAC using DMA.

  Hardware connection:
PWM Pin 2 provides a 5 kHz signal that is digitized by A9 and output using DAC0
  WR
*/

//static volatile uint16_t buf2[dmaBufSize];


// Common libraries:
#include <math.h>
#include <DMAChannel.h>
#include "ADC.h" 
#include "arm_math.h"
#include <array>

#define dmaBufSize 16 // Smallest buffer of size 16 (same as Teensy DAC buffer) results in more overhead but minimizes latency
#define DAC0BufSize 16
#define LoopsPerBuf dmaBufSize*2 / DAC0BufSize

// Common to all waveforms:
const uint8_t adc_pin = A9;
const uint8_t out_pin = 2;
static volatile uint16_t   __attribute__((aligned(16)))  buf1[dmaBufSize * 2]; // buf1 is the DAC buffer and is double buffered
static volatile uint16_t   __attribute__((aligned(16)))  buf2[dmaBufSize]; // buf2 is the ADC buffer

int index1;
const float busFrequency = 6.e7; //in Hz. Bus clock frequency is from Table 10 of the MK66FX1MOVMD18DataSheet.pfd of the Technical Data Sheet.

int MOD_Start = 8; // Bus frequency = 60 MHz -> 8 bus clocks. PDB minimum is 8 for DAC. DAC update rate = MOD x 1./(busFrequency) sec.
const float DACConversionTime = MOD_Start / busFrequency; // It takes 8 clocks (?) at 60 MHz (bus clock) to perform a DAC conversion
float interval = DACConversionTime;  // Variable interval is the DAC conversion rate. Reduced from DACConversionTime value if processing a low-frequency waveform
float sample_frequency;

ADC *adc = new ADC(); // adc object

DMAChannel* dma0 = new DMAChannel(false); // dma object
DMAChannel* dma1 = new DMAChannel(false); // dma object


void setup() // this function runs once when the sketch starts up
{

  // We are using pins 12 and 13 to drive the oscilloscope, so we must configure them
  // as an output.
  PORTC_PCR4 = ( unsigned long ) 0x00000101; // Section 12.5.1, pg. 221 - 222, see Pin Mux Control
                                             // Bit 0 -> Pull Select -> Internal pullup resistor is enabled
                                             // Bit 8 -> ALT1 -> GPIO
  PORTC_PCR5 = ( unsigned long ) 0x00000101; // Section 12.5.1, pg. 221 - 222, see Pin Mux Control
                                             // Bit 0 -> Pull Select -> Internal pullup resistor is enabled
                                             // Bit 8 -> ALT1 -> GPIO
  GPIOC_PDDR = ( unsigned long ) 0x00000030; // Configure pins 10 & 13 as output. See Section 63.3.6
                                             // Pin 10 is PTC4
                                             // Pin 13 is PTC5 (on-board LED)



  GPIOC_PSOR |= (1 << 4); // Section 63.3.2 - Set pin 10 HIGH
  GPIOC_PSOR |= (1 << 5); // Section 63.3.2 - Set pin 13 HIGH

 pinMode(adc_pin, INPUT);
 Serial.begin (115200);
    delay(5000);

  // Load DAC buffer with ramp functions to generate a static triangle wave
  for (int i = 0; i < dmaBufSize; i++) {
    buf1[i] = 2048 * i / (dmaBufSize - 1); // Build triangle wave for buf1
    buf1[dmaBufSize + i] = 2048 * ((dmaBufSize - 1) - i) / (dmaBufSize - 1); // Build triangle wave for buf1
    buf2[i] = 65535 * i / (dmaBufSize - 1); // Build sawtooth. Will indicate ADC is not acquiring data (buf1 = buf2/16)
  }
  
  MOD_Start = 0x10000;
  while (MOD_Start > 0xffff) {
    Serial.print("Input sample rate in microseconds. ");
    while (!Serial.available()) {}
    sample_frequency = 1.e6/Serial.parseFloat();
    MOD_Start = busFrequency/sample_frequency; 
    if (MOD_Start > 0xffff) Serial.println("Sample rate must be less than 1.1 milliseconds."); //Prescaler required
     while (Serial.available()) byte garbage = Serial.read();
  }
  Serial.print("Maximum waveform frequency = ");
  Serial.println(sample_frequency/4.); //Niquist limit is 2 so double it.

  DAC_Setup();
  ADC_Setup();
  DMA_Setup();
  PDB_Setup();

  analogWriteFrequency(out_pin, 5000); // Available for ADC0 if needed
  analogWrite(out_pin, 10);

  while ((DMA_TCD0_CITER_ELINKNO & 0x01FF) <= LoopsPerBuf) {} // Wait until the first DMA buffer is being processed
  GPIOC_PCOR |= (1 << 5); // Section 63.3.1 - Set pin 13 LOW

  while ((DMA_TCD0_CITER_ELINKNO & 0x01FF) > LoopsPerBuf) {} // Wait until DMA of the second half of the DMA buffer begins
  GPIOC_PCOR |= (1 << 4); // Section 63.3.1 - Set pin 10 LOW
}

void DAC_Setup () // Set up the programmable delay block required to trigger the DMA requests
{
  // Initialize the System Clock Gating Control Register to enable the DAC module.
  SIM_SCGC2 |= SIM_SCGC2_DAC0; // System Clock Gating Control Register 2 enables the DAC0 module. See Section 13.2.12. (Enable DAC1 here too.)

// Initialize DAC0
  DAC0_C0 = DAC_C0_DACEN       // DAC Control Register 0 enables enables DAC0. See Section 41.5.4.
            | DAC_C0_DACRFS;     // Use the 3.3V reference

  // Slowly ramp up to mid-range voltage
  for (int16_t i = 0; i < 2048; i += 1) {
    *(int16_t *)&(DAC0_DAT0L) = i;
    delay(1);
  }

// Fill up the DAC0 buffer with a downward ramp beginning at 2048. Will indicate dma is not working.
    using aliased_uint16 = uint16_t __attribute__((__may_alias__));
    using aliased_uint16_vptr = volatile aliased_uint16*;
    for (size_t i=0; i<16; i+=1) {
       ((aliased_uint16_vptr) &DAC0_DAT0L)[i] = 2048-(i*127);  //256*(16-i) - 1;
    }
}

void ADC_Setup ()
{
/*
 * With ADC_CONVERSION_SPEED::HIGH_SPEED & ADC_SAMPLING_SPEED::HIGH_SPEED, maximum sampling rate is 480 kHz
 * With ADC_CONVERSION_SPEED::HIGH_SPEED & ADC_SAMPLING_SPEED::very_HIGH_SPEED, maximum sampling rate is 510 kHz
 *                                                                              
 * For 16-bit resolution, max sample rate is 2.2 microseconds                                                                         
 */
  adc->adc0->setAveraging(1);
  adc->adc0->setResolution(16); // Set bits of resolution
  adc->adc0->setConversionSpeed(ADC_CONVERSION_SPEED::HIGH_SPEED);
  adc->adc0->setSamplingSpeed(ADC_SAMPLING_SPEED::HIGH_SPEED);
  adc->adc0->analogRead(adc_pin); // performs various ADC setup stuff
  ADC0_SC1B = ADC0_SC1A; // The value for ADC0_SC1A is set by analogRead above. SC1B is not set by the analog library.
  adc->adc0->enableDMA();
    ADC0_SC1A = 0x44; // Section 39.4.1. With a value of 3 & DIFF=0, A9 is selected as input.
}

void DMA_Setup ()
{  
// Channel Configuration Register is used to enable one of the DMA channels to one of the DMA slots on the system
// Need to configure the DMA to align with feeding the DAC output channel.
// The DMA Multiplexer allows up to 63 DMA request signals to map to any of the 32 DMA channels. Map DAC as a request source.dacintc


// Initialize the DAC DMA
// We are using Channel 0 for driving the DAC.
// Use the System Clock Gating Control Register to initialize and enable the DMA MUX and the DMA clock.
// The DMA multiplexer (DMAMUX) routes DMA sources, called slots, to any of 32 DMA channels.
  dma0->begin(true); // Enable requests on DMA channel 0 - See Section 24.3.3
  dma0->TCD->SADDR = buf1;   // Set the address of the first byte in the DMA output buffer as the source address. See Section 24.3.18
  dma0->TCD->SOFF = 4; // advance 32 bits, or 4 bytes per read
  dma0->TCD->DOFF = 4; // advance 32 bits, or 4 bytes per read
  dma0->TCD->ATTR = DMA_TCD_ATTR_SSIZE(DMA_TCD_ATTR_SIZE_32BIT); // See Section 24.3.20
  DMA_TCD0_ATTR |= DMA_TCD_ATTR_DSIZE(DMA_TCD_ATTR_SIZE_32BIT)
                | DMA_TCD_ATTR_DMOD(31 - __builtin_clz(32));  // set the data transfer size to 32 bit for both the source and the destination
// Set the number of bytes transferred per minor loop, or request. See Section 24.3.21
  dma0->TCD->NBYTES = 16; //We want to fill half of the DAC buffer, which is 16 words in total, so we need 8 words - or 16 bytes - per transfer
  dma0->TCD->SLAST = -4 * dmaBufSize; //Set the size of the DMA transfer (major loop)
  dma0->TCD->DADDR = &DAC0_DAT0L; // Set the first data register of DAC0 as the destination address. See Section 41.5.
  dma0->TCD->DLASTSGA = 0;
  dma0->TCD->BITER = 4 * dmaBufSize / DAC0BufSize; // CITER value is volatile. Four minor loops occur per DMA cycle.
  dma0->TCD->CITER = 4 * dmaBufSize / DAC0BufSize; // BITER value is loaded back into the CITER field at the end of the buffer transfer.
  dma0->TCD->CSR = 0;
  dma0->triggerAtHardwareEvent(DMAMUX_SOURCE_DAC0);  //Select DAC as request source #45. See Section 23.1.1. Select DMA Channel 0 as the point of control.
  dma0->enable(); //Enable DMA channel 0. See Section 24.3.3.

// Enable the DAC interrupts and use them to drive the DMA requests
  DAC0_C0 |= DAC_C0_DACBWIEN   // enable DMA trigger at watermark
          |  DAC_C0_DACBTIEN;   // enable DMA trigger at at top of buffer (when it toggles from 15 to 0)
  DAC0_C1 |= DAC_C1_DACBFWM(3)   // watermark for DMA trigger
          | DAC_C1_DMAEN      // Enable the DMA request - See Section 41.5.5
          | DAC_C1_DACBFEN ;    // Enable the DAC Buffer

  DAC0_C2 |= DAC_C2_DACBFRP(0); // Keeps the current value of the buffer read pointer - See Sction 41.5.6
  DAC0_C2 |= DAC_C2_DACBFUP(15);

  DAC0_SR &= ~(DAC_SR_DACBFWMF); // clear watermark flag
  DAC0_SR &= ~(DAC_SR_DACBFRTF); // clear top pos flag
  DAC0_SR &= ~(DAC_SR_DACBFRBF); // clear bottom pos flag
  DAC0_C2 |= DAC_C2_DACBFRP(12); //Initial condition required for the DAC buffer read pointer

  dma1->begin(true);              // allocate the ADC DMA channel   
  dma1->TCD->SADDR = &ADC0_RA;    // where to read from
  dma1->TCD->SOFF = 0;            // source increment each transfer
  dma1->TCD->ATTR = 0x101;  // Set SSIZE and DSIZE for 16-bits
  dma1->TCD->NBYTES = 2;     // bytes per transfer
  dma1->TCD->SLAST = 0;
  dma1->TCD->DADDR = buf2; // where to write to
  dma1->TCD->DOFF = 2; 
  dma1->TCD->DLASTSGA = -2 * dmaBufSize;
  dma1->TCD->BITER = dmaBufSize;
  dma1->TCD->CITER = dmaBufSize;    
  dma1->triggerAtHardwareEvent(DMAMUX_SOURCE_ADC0);
  dma1->enable();
if(adc->adc0->fail_flag) {
        Serial.print("ADC error: ");
        Serial.println(adc->adc0->fail_flag, HEX);
    }
}

void PDB_Setup()
{
bool external_trig = false; // Used to run code to support external trigger of the DAC from COCO of the ADC
  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_SC |= PDB_SC_PRESCALER(0b000); // prescaler field

// Prescaler multipliers other than 1x don't work correctly.
//    PDB0_SC |= PDB_SC_MULT(0b0);

  PDB0_MOD = (uint16_t)(MOD_Start - 1);
  PDB0_DACINT0 = (uint16_t)(MOD_Start - 1); // - See Section 44.4.10
  if (external_trig) PDB0_DACINTC0 |= PDB_DACINTC_EXT; // enable the DAC external trigger. - See Section 44.4.9
   else PDB0_DACINTC0 |= PDB_DACINTC_TOE; // enable the DAC interval trigger. - See Section 44.4.9
  DAC0_C2 |= DAC_C2_DACBFRP(12); //Initial condition required for the DAC buffer read pointer
  PDB0_SC |= PDB_SC_LDOK; // update pdb registers
  PDB0_SC |= PDB_SC_SWTRIG; // ...and start the PDB
  adc->adc0->stopPDB();
  adc->adc0->startPDB(sample_frequency);

}

void pdb_isr(void) {
    PDB0_SC &=~PDB_SC_PDBIF; // clear interrupt
}

void loop() { // The DAC is double buffered. Data from the ADC buffer is transferred to the DAC buffer that is not going out
int i;
int j;
uint16_t temp;
while (1) {
  GPIOC_PCOR |= (1 << 5); // Section 63.3.1 - Set pin 13 LOW -- Use to indicate which buffer is going out to the DAC
    
  temp = DMA_TCD0_CITER_ELINKNO & 0x01FF;
  while (temp <= LoopsPerBuf) temp = DMA_TCD0_CITER_ELINKNO & 0x01FF;
  GPIOC_PSOR |= (1 << 5); // Section 63.3.2 - Set pin 13 HIGH -- Use for timing
// Wait until the second DMA buffer is being processed
  i = 0;
//Processing on first DAC0 DMA buffer has started.
    j = dmaBufSize - (DMA_TCD1_CITER_ELINKNO & 0x01FF); // j is the current position in the ADC buffer. 
  while (1) {
    if(adc->adc0->fail_flag) {
        Serial.print("ADC error: ");
        Serial.println(adc->adc0->fail_flag, HEX);
    }
    buf1[i] = buf2[j] >> 4; // Convert ADC data from 16-bit to 12-bit value and write into DAC buffer
    i++;
    j++;
    if (j == dmaBufSize) j = 0;
    if (i == dmaBufSize) break;
  }
     temp = DMA_TCD0_CITER_ELINKNO & 0x01FF;
 while (temp > LoopsPerBuf) temp = DMA_TCD0_CITER_ELINKNO & 0x01FF;
  GPIOC_PCOR |= (1 << 5); // Section 63.3.1 - Set pin 13 LOW -- Use to indicate which buffer is going out to the DAC


  while (1) {

  buf1[i] = buf2[j] >> 4; // Convert from 16-bit to 12-bit value
    i++;
    j++;
    if (j == dmaBufSize) j = 0;
    if (i == dmaBufSize * 2) break;
  }
 }
}
 
Status
Not open for further replies.
Back
Top