/*
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;
}
}
}