Code:
/*
* Distributed under the 2-clause BSD licence (See LICENCE.TXT file at the
* repository root).
*
* Copyright (c) 2017 Tilo Nitzsche. All rights reserved.
*
* https://github.com/tni/teensy-samples
*/
// Modified version of:
// -----------------------------------------------------------------------------
//
// Example for Teensy 3.x DAC output using the FIFO.
// Precise, jitter-free timing is achieved, using the PDB to trigger
// loading of new values from the FIFO. The FIFO is filled using DMA.
//
// The FIFO is not really a FIFO (there is no support for a push or pop operation),
// but rather a ringbuffer. There is an index to the currently used entry.
//
// DMA transfers can be triggered by the FIFO index being at 0 and
// the FIFO watermark. This code uses these triggers to transfer 4 entries
// at a time. The lower half of the FIFO (entries 0 - 3) is updated when
// the FIFO index reaches 4 (the watermark), the upper half (entries 4 - 7)
// is updated when the FIFO index wraps to 0.
//
// The FIFO DMA destination is set up using the DMA modulo feature,
// wrapping the DMA destination pointer at 8 FIFO entries.
//
// While the FIFO theoretically has 16 entries, they can't be utilized since
// the watermark pointer is limited. So only 8 FIFO entries are used.
//
// -----------------------------------------------------------------------------
//
// Changes - 07/07/2017, Dizzixx:
//
// - Added second DMAChannel object to drive DAC1
// - Added helper functions for SAW and SINE waveforms for debugging
// - Re-packedged setup of DMA and PDB to helper functions for modularity
// - Changed timing to be: output sampling rate = F_BUS/PDB_MODIFIER
// - Moved status reporting to behelper functions called from main loop
//
// -----------------------------------------------------------------------------
#include <array>
#include <DMAChannel.h>
#include "kinetis.h"
//#define FASTRUN __attribute__ ((section(".fastrun"), noinline, noclone ))
//For some reason these would not pull from kinetis.h
// for this reasons they are re-defined below
#define DAC_SR_DACBFWMF 0x04 // Buffer Watermark Flag
#define DAC_SR_DACBFRTF 0x02 // Pointer Top Position Flag
#define DAC_SR_DACBFRBF 0x01 // Pointer Bottom Position Flag
#define PDB_DACINTC_TOE 0x01 // Interval Trigger Enable
using aliased_uint16 = uint16_t __attribute__((__may_alias__));
using aliased_uint16_vptr = volatile aliased_uint16*;
const uint16_t PDB_MODIFIER = 47; // output sampling rate = F_BUS/PDB_MODIFIER
const uint16_t num_buffered_cycles = 1; // Determines how many "cycles" of a waveform to store in memory
//DMAChannel objects to set DAC DMA
DMAChannel dma0; //dma for DAC channel 0
DMAChannel dma1; //dma for DAC channel 1
// 128 value 12-bit sine LUT
uint16_t sinetable[] = {
2047, 2147, 2248, 2348, 2447, 2545, 2642, 2737,
2831, 2923, 3012, 3100, 3185, 3267, 3346, 3422,
3495, 3564, 3630, 3692, 3750, 3804, 3853, 3898,
3939, 3975, 4007, 4034, 4056, 4073, 4085, 4093,
4095, 4093, 4085, 4073, 4056, 4034, 4007, 3975,
3939, 3898, 3853, 3804, 3750, 3692, 3630, 3564,
3495, 3422, 3346, 3267, 3185, 3100, 3012, 2923,
2831, 2737, 2642, 2545, 2447, 2348, 2248, 2147,
2047, 1948, 1847, 1747, 1648, 1550, 1453, 1358,
1264, 1172, 1083, 995, 910, 828, 749, 673,
600, 531, 465, 403, 345, 291, 242, 197,
156, 120, 88, 61, 39, 22, 10, 2,
0, 2, 10, 22, 39, 61, 88, 120,
156, 197, 242, 291, 345, 403, 465, 531,
600, 673, 749, 828, 910, 995, 1083, 1172,
1264, 1358, 1453, 1550, 1648, 1747, 1847, 1948,
};
// 128 value 12-bit sawtooth LUT
uint16_t sawtable[] = {
0, 64, 128, 192, 256, 320, 384, 448,
512, 576, 640, 704, 768, 832, 896, 960,
1024, 1088, 1152, 1216, 1280, 1344, 1408, 1472,
1536, 1600, 1664, 1728, 1792, 1856, 1920, 1984,
2048, 2112, 2176, 2240, 2304, 2368, 2432, 2496,
2560, 2624, 2688, 2752, 2816, 2880, 2944, 3008,
3072, 3136, 3200, 3264, 3328, 3392, 3456, 3520,
3584, 3648, 3712, 3776, 3840, 3904, 3968, 4032,
4032, 3968, 3904, 3840, 3776, 3712, 3648, 3584,
3520, 3456, 3392, 3328, 3264, 3200, 3136, 3072,
3008, 2944, 2880, 2816, 2752, 2688, 2624, 2560,
2496, 2432, 2368, 2304, 2240, 2176, 2112, 2048,
1984, 1920, 1856, 1792, 1728, 1664, 1600, 1536,
1472, 1408, 1344, 1280, 1216, 1152, 1088, 1024,
960, 896, 832, 768, 704, 640, 576, 512,
448, 384, 320, 256, 192, 128, 64, 0,
};
const uint16_t len_tables = sizeof(sinetable)/2; // could just as easily type = 128
const uint16_t len_buffer = len_tables*num_buffered_cycles; // buffer size, set dynamically to integrate with serial data later
//Channel 0 Buffer array
std::array<uint16_t,len_buffer> buffer0;
constexpr size_t buffer0_byte_count = sizeof(buffer0);
static_assert(buffer0.size() % 8 == 0, "Buffer0 size must be multiple of DAC buffer.");
//Channel 1 Buffer array
std::array<uint16_t,len_buffer> buffer1;
constexpr size_t buffer1_byte_count = sizeof(buffer1);
static_assert(buffer1.size() % 8 == 0, "Buffer1 size must be multiple of DAC buffer.");
void initBuffer_stairStep(int arg) {
for(size_t i = 0; i < len_buffer; i += 4) {
if(arg==0 || arg==2){
//Channel 0
buffer0[i+0] = 0;
buffer0[i+1] = 1000;
buffer0[i+2] = 2000;
buffer0[i+3] = 3000;
}
if(arg==1 || arg==2){
//Channel 1
buffer1[i+0] = 0;
buffer1[i+1] = 1000;
buffer1[i+2] = 2000;
buffer1[i+3] = 3000;
}
}
}
void initBuffer_sine(int arg){
for(int itr=0; itr<num_buffered_cycles; itr+=1){
for(int sample=0; sample<len_tables; sample+=1){
//Channel 0
if(arg==0 || arg==2)buffer0[itr*len_tables+sample]=sinetable[sample];
//Channel 1
if(arg==1 || arg==2)buffer1[itr*len_tables+sample]=sinetable[sample];
}
}
}
void initBuffer_saw(int arg){
for(int itr=0; itr<num_buffered_cycles; itr+=1){
for(int sample=0; sample<len_tables; sample+=1){
//Channel 0
if(arg==0 || arg==2)buffer0[itr*len_tables+sample]=sawtable[sample];
//Channel 1
if(arg==1 || arg==2)buffer1[itr*len_tables+sample]=sawtable[sample];
}
}
}
void setup_DAC0(){
//Enable DAC0
SIM_SCGC2 |= SIM_SCGC2_DAC0; // enable DAC clock
DAC0_C0 |= DAC_C0_DACEN; // enable DAC
DAC0_C0 |= DAC_C0_DACRFS; // use 3.3V VDDA as reference voltage
DAC0_C0 |= DAC_C0_DACBWIEN; // enable DMA trigger at watermark
DAC0_C0 |= DAC_C0_DACBTIEN; // enable DMA trigger at 0
DAC0_C1 = DAC_C1_DACBFWM(2) | // watermark for DMA trigger
// --> DMA triggered when DAC buffer index is 4
DAC_C1_DMAEN | // enable DMA
DAC_C1_DACBFEN; // enable DAC buffer
DAC0_C2 = DAC_C2_DACBFUP(7); // set buffer size to 8
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
// Init DAC FIFO with the last 8 buffer elements. This makes setting up the circular
// DMA transfer easier.
for(size_t i = 0; i < 8; i++) {
((aliased_uint16_vptr) &DAC0_DAT0L)[i] = buffer0[buffer0.size() - 8 + i];
}
// The modulo feature of the DMA controller is used. The destination
// pointer wraps at +16 bytes.
dma0.destinationCircular(((volatile uint16_t*) &DAC0_DAT0L), 16);
dma0.TCD->SADDR = buffer0.data(); // source data buffer
dma0.TCD->SOFF = 2; // advance by 2 bytes (16 bits) per read
dma0.TCD->ATTR_SRC = 1;
dma0.TCD->NBYTES = 8;
dma0.TCD->SLAST = -buffer0_byte_count;
dma0.TCD->BITER = buffer0_byte_count / 8;
dma0.TCD->CITER = buffer0_byte_count / 8;
dma0.triggerAtHardwareEvent(DMAMUX_SOURCE_DAC0);
dma0.enable();
}
void setup_DAC1(){
//Enable DAC1
SIM_SCGC2 |= SIM_SCGC2_DAC1; // enable DAC clock
DAC1_C0 |= DAC_C0_DACEN; // enable DAC
DAC1_C0 |= DAC_C0_DACRFS; // use 3.3V VDDA as reference voltage
DAC1_C0 |= DAC_C0_DACBWIEN; // enable DMA trigger at watermark
DAC1_C0 |= DAC_C0_DACBTIEN; // enable DMA trigger at 0
DAC1_C1 = DAC_C1_DACBFWM(2) | // watermark for DMA trigger
// --> DMA triggered when DAC buffer index is 4
DAC_C1_DMAEN | // enable DMA
DAC_C1_DACBFEN; // enable DAC buffer
DAC1_C2 = DAC_C2_DACBFUP(7); // set buffer size to 8
DAC1_SR &= ~(DAC_SR_DACBFWMF); // clear watermark flag
DAC1_SR &= ~(DAC_SR_DACBFRTF); // clear top pos flag
DAC1_SR &= ~(DAC_SR_DACBFRBF); // clear bottom pos flag
// Init DAC FIFO with the last 8 buffer elements. This makes setting up the circular
// DMA transfer easier.
for(size_t i = 0; i < 8; i++) {
((aliased_uint16_vptr) &DAC1_DAT0L)[i] = buffer1[buffer1.size() - 8 + i];
}
// The modulo feature of the DMA controller is used. The destination
// pointer wraps at +16 bytes.
dma1.destinationCircular(((volatile uint16_t*) &DAC1_DAT0L), 16);
dma1.TCD->SADDR = buffer1.data(); // source data buffer
dma1.TCD->SOFF = 2; // advance by 2 bytes (16 bits) per read
dma1.TCD->ATTR_SRC = 1;
dma1.TCD->NBYTES = 8;
dma1.TCD->SLAST = -buffer1_byte_count;
dma1.TCD->BITER = buffer1_byte_count / 8;
dma1.TCD->CITER = buffer1_byte_count / 8;
dma1.triggerAtHardwareEvent(DMAMUX_SOURCE_DAC1);
dma1.enable();
}
void setup_and_run_PDB(){
SIM_SCGC6 |= SIM_SCGC6_PDB; // PDB Clock Gate Control, enable PDB clock
PDB0_SC |= PDB_SC_PDBEN; // PDB Enable
PDB0_SC |= PDB_SC_TRGSEL(15); // Trigger Input Source Select, SW trigger
PDB0_SC |= PDB_SC_CONT; // run continiously
// Pre-scalers not used as high PDB rate is desired
//PDB0_SC |= PDB_SC_PRESCALER(0b111); // prescaler (from 0-7..?)
//PDB0_SC |= PDB_SC_MULT(0b0); // Prescaler multipliers other than 1x don't work correctly.
PDB0_MOD = PDB_MODIFIER; // Adjust for output frequency.
// out frequency == F_BUS / PRESCALER / (PDB0_MOD + 1)
// The hardware doesn't do what the manual claims. The DAC trigger counter is
// not reset on PDB counter overflow.
// Things work correctly, ONLY if the PDB mod is used.
// DAC Channel 0
PDB0_DACINT0 = PDB_MODIFIER; // trigger DAC once per PDB cycle
PDB0_DACINTC0 = PDB_DACINTC_TOE; // enable DAC interval trigger
// DAC Channel 1
PDB0_DACINT1 = PDB_MODIFIER; // trigger DAC once per PDB cycle
PDB0_DACINTC1 = PDB_DACINTC_TOE; // enable DAC interval trigger
// Sync the PDB registers with written values and start the PDB timer
PDB0_SC |= PDB_SC_LDOK; // sync buffered PDB registers
PDB0_SC |= PDB_SC_SWTRIG; // start PDB
}
unsigned src0_idx_prev = 0;
unsigned dac0_out_idx_prev = 0;
unsigned src1_idx_prev = 0;
unsigned dac1_out_idx_prev = 0;
void reportStatus_DAC0(){
noInterrupts();
unsigned src_idx_curr = ((uintptr_t) dma0.sourceAddress() - (uintptr_t) buffer0.data()) / 2;
unsigned dest_idx = ((uintptr_t) dma0.destinationAddress() - (uintptr_t) &DAC0_DAT0L) / 2;
unsigned dac_out_idx_curr = DAC0_C2 >> 4;
unsigned dac_val = ((aliased_uint16_vptr) &DAC0_DAT0L)[dac_out_idx_curr];
interrupts();
if(src1_idx_prev != src_idx_curr && dac1_out_idx_prev != dac_out_idx_curr){
src1_idx_prev = src_idx_curr;
dac1_out_idx_prev = dac_out_idx_curr;
Serial.printf("Channel 1 - DMA src idx: %4u DMA dest idx: %4u DAC out idx: %4u DAC value: %4u\n",
src_idx_curr, dest_idx, dac_out_idx_curr, dac_val);
}
}
void reportStatus_DAC1(){
noInterrupts();
unsigned src_idx_curr = ((uintptr_t) dma1.sourceAddress() - (uintptr_t) buffer1.data()) / 2;
unsigned dest_idx = ((uintptr_t) dma1.destinationAddress() - (uintptr_t) &DAC1_DAT0L) / 2;
unsigned dac_out_idx_curr = DAC1_C2 >> 4;
unsigned dac_val = ((aliased_uint16_vptr) &DAC1_DAT0L)[dac_out_idx_curr];
interrupts();
if(src1_idx_prev != src_idx_curr && dac1_out_idx_prev != dac_out_idx_curr){
src1_idx_prev = src_idx_curr;
dac1_out_idx_prev = dac_out_idx_curr;
Serial.printf("Channel 0 - DMA src idx: %4u DMA dest idx: %4u DAC out idx: %4u DAC value: %4u\n",
src_idx_curr, dest_idx, dac_out_idx_curr, dac_val);
}
}
void setup() {
//Start serial
Serial.begin(9600);
delay(2000);
Serial.println("PDB DAC sample. Starting...");
//fill outer buffer with values
//Channel 0
initBuffer_sine(0);
//Channel 1
initBuffer_saw(1);
//Configure the DAC's to use PDB timing and FIFO buffer
//setup DAC0
setup_DAC0();
//setup DAC1
setup_DAC1();
//setup and start the PDB timer
setup_and_run_PDB();
}
void loop(){
reportStatus_DAC0();
reportStatus_DAC1();
}