AnsweredAssumed Answered

Audio double buffering on K66

Question asked by Jeff Gregorio on Oct 6, 2017
Latest reply on Oct 21, 2017 by Mark Butcher

Hello! I'm trying to implement a block-based audio processor using double buffering on the input and output and I'm getting some unexpected discontinuities in my output signal. I'm using the Arduino IDE Teensy 3.6, which uses the K66 processor, but I have no debugger capability. The configuration of the peripherals is as follows:

 

  • ADC0 is hardware triggered by PIT0, which runs at the audio sample rate
  • ADC0 conversion completions trigger transfers on DMA0 from ADC0_RA to dma0_dest_buffer, which is split into halves, input_buffer and proc_buffer
    • DMA0 interrupts on major and half major loop, whereupon the pointers to input_buffer and proc_buffer are swapped
  •  DMA0 is chained to DMA1, which transfers samples to DAC0_DAT0L from dma1_src_buffer, which is split into halves, render_buffer and output_buffer
    • DMA1 interrupts on major and half major loop, whereupon the pointers to render_buffer and output_buffer are swapped, and the contents of proc_buffer are copied into render_buffer after scaling the 16-bit ADC readings to 12 bits for the DAC

 

The discontinuities are shown in the attached image. Seems that it periodically re-plays samples that have already been sent to the output. The really strange thing is that the spacing of the discontinuities scales with the buffer length, but it's not at the buffer boundaries, as I would expect if I had messed up the double-buffer pointer swaps.

 

For buffer length 4 (83us) , they are spaced at ~650us, or every 8 buffers

For buffer length 8 (167us), they are spaced at ~2.6ms, or every 16 buffers

For buffer length 16 (333us), they are spaced at ~11ms, or every 32 buffers

For buffer length 32 (667us), they are spaced at ~48ms, or every 64 buffers

For buffer length 64 (1.3ms), they are spaced at ~170ms, or every 128 buffers

 

Beyond that, the discontinuities are spread so far apart that I can no longer see them on the scope at that time scale. I've been stuck on this problem for a few days, unable to figure out what could be causing that strange pattern.

 

Lowering the sample rate has no effect. I have also tried filling render_buffer with samples synthesized directly from a wavetable oscillator in dma_ch1_isr(), and I get no discontinuities. I also observe no discontinuities when I print samples from proc_buffer to Arduino's serial plotter from dma_ch0_isr(), so I believe the problem lies somewhere in the DMA transfers or double buffer handling. 

 

Any help is greatly appreciated. Code shown below:

 

#define BAUD_RATE (230400)
#define SAMPLE_RATE (48000.0f)
#define PIT_PERIOD ((uint32_t)(F_BUS / SAMPLE_RATE)-1)
#define BUFFER_LEN (16)
#define ADC_RES (16)
#define DAC_RES (12)

 

static volatile uint16_t dma0_dest_buffer[2*BUFFER_LEN];
static volatile uint16_t * input_buffer = &dma0_dest_buffer[0];
static volatile uint16_t * proc_buffer = &dma0_dest_buffer[BUFFER_LEN];
static volatile uint16_t dma1_src_buffer[2*BUFFER_LEN];
static volatile uint16_t * render_buffer = &dma1_src_buffer[BUFFER_LEN];
static volatile uint16_t * output_buffer = &dma1_src_buffer[0];

 

// See table 39.1.3.1 for pin mapping
int ch = 5; // ADC0 channel 5 => AD4b, input signal ADC0_SE4b
// (Mapped to pin A0 on Teensy 3.6)

 

void setup() {


Serial.begin(BAUD_RATE);

 

// Initialize DMA1 source buffer
for (int i = 0; i < 2*BUFFER_LEN; i++) {


dma0_dest_buffer[i] = (1 << ADC_RES) / 2;
dma1_src_buffer[i] = (1 << DAC_RES) / 2;

}

init_adc0();
init_dac0();
init_dma();
init_pit0();

}

 

void init_adc0() {

 

 

// (13.2.16) System clock gating control register 6
SIM_SCGC6 |= SIM_SCGC6_ADC0; // Enable ADC0 clock

 

// (39.4.2) ADC Configuration Register 1
ADC0_CFG1 |= ADC_CFG1_ADICLK(0); // Input clock = Bus clock
ADC0_CFG1 |= ADC_CFG1_MODE(3); // Single-ended, 16-bit
ADC0_CFG1 &= ~ADC_CFG1_ADLSMP; // Short sample time
ADC0_CFG1 |= ADC_CFG1_ADIV(1); // Input clock / 2
ADC0_CFG1 &= ~ADC_CFG1_ADLPC; // Normal power configuration
// ADC Clock = F_BUS / 2 = 30MHz

 

// (39.4.3) ADC Configuration Register 2
ADC0_CFG2 |= ADC_CFG2_ADLSTS(2); // 6 extra ADCK cycles, 10 total sample time
ADC0_CFG2 &= ~ADC_CFG2_ADHSC; // Normal speed conversion
ADC0_CFG2 |= ADC_CFG2_MUXSEL; // ADxxB channels are selected (See table 39.1.3.1)

 

// (39.4.6) Status and Control Register 2
ADC0_SC2 |= ADC_SC2_REFSEL(0); // Voltage reference = Vcc
ADC0_SC2 |= ADC_SC2_DMAEN; // Enable DMA
ADC0_SC2 &= ~ADC_SC2_ACREN; // Disable comp. function range
ADC0_SC2 &= ~ADC_SC2_ACFGT; // Disable comp. function greater than
ADC0_SC2 &= ~ADC_SC2_ACFE; // Disable comp. function
ADC0_SC2 &= ~ADC_SC2_ADTRG; // Software trigger (for calibration)

// (39.4.6) Status and Control Register 3
ADC0_SC3 |= ADC_SC3_AVGS(0); // Hardware average 4 samples
ADC0_SC3 |= ADC_SC3_AVGE; // Enable hardware averaging
ADC0_SC3 &= ~ADC_SC3_ADCO; // Single conversion

 

// Run calibration
adc_calibrate();

 

// (39.4.6) Status and Control Register 2
ADC0_SC2 |= ADC_SC2_ADTRG; // Enable hardware trigger

 

// (13.2.7) System options register 7
SIM_SOPT7 |= SIM_SOPT7_ADC0ALTTRGEN; // Alt trigger select
SIM_SOPT7 |= SIM_SOPT7_ADC0TRGSEL(4); // PIT trigger 0 select

 

// (39.4.1) ADC Status and Control Registers 1 (ADCx_SC1n)
uint32_t sc1a_config;
sc1a_config |= ADC_SC1_ADCH(ch); // Channel selection
sc1a_config &= ~ADC_SC1_DIFF; // Single-ended mode
sc1a_config &= ~ADC_SC1_AIEN; // Disable interrupts
ADC0_SC1A = sc1a_config;

}

 

void adc_calibrate() {

 

uint16_t sum;

ADC0_SC3 |= ADC_SC3_CAL; // Initiate automatic calibration sequence

while (ADC0_SC3 & ADC_SC3_CAL) {/* wait for it... */}

 

if (ADC0_SC3 & ADC_SC3_CALF)
   Serial.println("Calibration Failed");
else
   Serial.println("Calibration Successful");

 

// Plus-side gain calibration
sum = (ADC0_CLP0 + ADC0_CLP1 + ADC0_CLP2 + ADC0_CLP3 + ADC0_CLP4 + ADC0_CLPS) >> 1;
sum |= (1 << 15);
ADC0_PG = sum;

 

// Minus-side gain calibration
sum = (ADC0_CLMS + ADC0_CLM4 + ADC0_CLM3 + ADC0_CLM2 + ADC0_CLM1 + ADC0_CLM0) >> 1;
sum |= (1 << 15);
ADC0_MG = sum;

}

 

 

void init_dac0() {


// (13.2.12) System clock gating control register 2
SIM_SCGC2 |= SIM_SCGC2_DAC0; // Enable DAC0 clock

// (41.5.4) DAC Control Register 0
DAC0_C0 |= DAC_C0_DACEN; // Enable DAC
DAC0_C0 |= DAC_C0_DACRFS; // Select DACREF_2 (3.3V)

}

 

 

void init_pit0() {


SIM_SCGC6 |= SIM_SCGC6_PIT; // (13.2.16) Send system clock to PIT
PIT_MCR = 0x00; // (46.4.1) Turn on PIT
PIT_LDVAL0 = PIT_PERIOD; // (46.4.4) Set timer 0 period
PIT_TCTRL0 |= PIT_TCTRL_TEN; // (46.4.6) Enable timer 0

}

 

void init_dma() {


// (13.2.16-17) System clock gating control registers 6 and 7
SIM_SCGC6 |= SIM_SCGC6_DMAMUX; // Enable DMAMUX clock
SIM_SCGC7 |= SIM_SCGC7_DMA; // Enable DMA clock

// (24.3.1) Control Register
DMA_CR |= DMA_CR_EMLM; // Enable minor loop mapping

init_dma0();
init_dma1();

}

 

void init_dma0() {


/* === Source === */
// (24.3.18) TCD Source Address
DMA_TCD0_SADDR = (volatile uint16_t *)&ADC0_RA;

// (24.3.20) TCD Transfer Attributes
DMA_TCD0_ATTR = 0x0000;
DMA_TCD0_ATTR |= DMA_TCD_ATTR_SSIZE(1); // Source data transfer size (16-bit)

// (24.3.19) TCD Signed Source Address Offset
DMA_TCD0_SOFF = 0; // Don't advance source pointer after minor loop

// (24.3.23) TCD Signed Minor Loop Offset (Minor Loop Enabled and Offset Enabled)
DMA_TCD0_NBYTES_MLOFFYES = sizeof(uint16_t); // 2 bytes per minor loop
DMA_TCD0_NBYTES_MLOFFYES &= ~DMA_TCD_NBYTES_SMLOE; // Source minor loop offset disable (src)
DMA_TCD0_NBYTES_MLOFFYES |= DMA_TCD_NBYTES_DMLOE; // Source minor loop offset enable (dest)

// TCD Current Minor Loop Link, Major Loop Count
DMA_TCD0_BITER_ELINKYES = sizeof(dma0_dest_buffer) / sizeof(uint16_t); // (24.3.32) Begining count
DMA_TCD0_BITER_ELINKYES |= DMA_TCD_BITER_ELINKYES_ELINK;
DMA_TCD0_BITER_ELINKYES |= DMA_TCD_BITER_ELINKYES_LINKCH(1);
DMA_TCD0_CITER_ELINKYES = sizeof(dma0_dest_buffer) / sizeof(uint16_t); // (24.3.28) Current count
DMA_TCD0_CITER_ELINKYES |= DMA_TCD_CITER_ELINKYES_ELINK;
DMA_TCD0_CITER_ELINKYES |= DMA_TCD_CITER_ELINKYES_LINKCH(1);

// (24.3.24) TCD Last Source Address Adjustment
DMA_TCD0_SLAST = 0; // Don't advance source pointer after major loop

 

/* === Destination === */
// (21.3.24) TCD Destination Address
DMA_TCD0_DADDR = (volatile uint16_t *)&dma0_dest_buffer;

// (24.3.20) TCD Transfer Attributes
DMA_TCD0_ATTR |= DMA_TCD_ATTR_DSIZE(1); // Destination data transfer size (16-bit)

// (24.3.26) TCD Signed Destination Address Offset
DMA_TCD0_DOFF = sizeof(uint16_t); // Advance dest pointer by 2 bytes per value

// (24.3.29) TCD Last Destination Address Adjustment/Scatter Gather Address
DMA_TCD0_DLASTSGA = -sizeof(dma0_dest_buffer); // Return to &dma_dest_buffer[0] after major loop

/* === Interrupts === */
// (24.3.30) TCD Control and Status
DMA_TCD0_CSR |= DMA_TCD_CSR_INTMAJOR; // Interrupt at major loop (CITER == 0)
DMA_TCD0_CSR |= DMA_TCD_CSR_INTHALF; // Also interrupt at half major loop (CITER == BITER/2)

// (24.3.8) Set Enable Request Register
DMA_SERQ |= DMA_SERQ_SERQ(0); // Enable channel 0 DMA requests

NVIC_ENABLE_IRQ(IRQ_DMA_CH0); // Enable interrupt request line for DMA channel 0

/* === Triggering === */
// (23.4.1) Channel Configuration Register
DMAMUX0_CHCFG0 = DMAMUX_SOURCE_ADC0; // Trigger after ADC0 transfers
DMAMUX0_CHCFG0 |= DMAMUX_ENABLE; // DMA channel enable

}

 

void init_dma1() {

 

/* === Source === */
// (24.3.18) TCD Source Address
DMA_TCD1_SADDR = (volatile uint16_t *)&dma1_src_buffer;

// (24.3.20) TCD Transfer Attributes
DMA_TCD1_ATTR = 0x0000;
DMA_TCD1_ATTR |= DMA_TCD_ATTR_SSIZE(1); // Source data transfer size (16-bit)

// (24.3.19) TCD Signed Source Address Offset
DMA_TCD1_SOFF = sizeof(uint16_t); // Advance source pointer by 2 bytes per value

// (24.3.23) TCD Signed Minor Loop Offset (Minor Loop Enabled and Offset Enabled)
DMA_TCD1_NBYTES_MLOFFYES = sizeof(uint16_t); // 2 bytes per minor loop
DMA_TCD1_NBYTES_MLOFFYES |= DMA_TCD_NBYTES_SMLOE; // Src. minor loop offset enable
DMA_TCD0_NBYTES_MLOFFYES &= ~DMA_TCD_NBYTES_DMLOE; // Dest. minor loop offset disable

// TCD Current Minor Loop Link, Major Loop Count
DMA_TCD1_BITER_ELINKNO = sizeof(dma1_src_buffer) / sizeof(uint16_t); // (24.3.32) Begining count
DMA_TCD1_CITER_ELINKNO = sizeof(dma1_src_buffer) / sizeof(uint16_t); // (24.3.28) Current count

// (24.3.24) TCD Last Source Address Adjustment
DMA_TCD1_SLAST = -sizeof(dma1_src_buffer); // Return to &dma1_src_buffer[0] after major loop

 

/* === Destination === */

// (21.3.24) TCD Destination Address
DMA_TCD1_DADDR = (volatile uint16_t *)&DAC0_DAT0L;

// (24.3.20) TCD Transfer Attributes
DMA_TCD1_ATTR |= DMA_TCD_ATTR_DSIZE(1); // Destination data transfer size (16-bit)

// (24.3.26) TCD Signed Destination Address Offset
DMA_TCD1_DOFF = 0;

// (24.3.29) TCD Last Destination Address Adjustment/Scatter Gather Address
DMA_TCD1_DLASTSGA = 0;

 

/* === Interrupts === */
// (24.3.30) TCD Control and Status
DMA_TCD1_CSR |= DMA_TCD_CSR_INTMAJOR; // Interrupt at major loop (CITER == 0)
DMA_TCD1_CSR |= DMA_TCD_CSR_INTHALF; // Also interrupt at half major loop (CITER == BITER/2)

// (24.3.8) Set Enable Request Register
DMA_SERQ |= DMA_SERQ_SERQ(1); // Enable channel 1 DMA requests

NVIC_ENABLE_IRQ(IRQ_DMA_CH1); // Enable interrupt request line for DMA channel 1

}

 

void loop() {}

 

void dma_ch0_isr(void) {

 

 

// Swap pointers to input and proc buffers
static volatile uint16_t * tmp;
tmp = input_buffer;
input_buffer = proc_buffer;
proc_buffer = tmp;

DMA_CINT = DMA_CINT_CINT(0); // (24.3.12) Clear interrupt request register

}

 

void dma_ch1_isr(void) {

 

 

// Swap pointers to output and render buffers
static volatile uint16_t * tmp;
tmp = output_buffer;
output_buffer = render_buffer;
render_buffer = tmp;

 

// Copy samples to render_buffer

for (int i = 0; i < BUFFER_LEN; i++) {


render_buffer[i] = proc_buffer[i] >> (ADC_RES - DAC_RES);

}

DMA_CINT = DMA_CINT_CINT(1); // (24.3.12) Clear interrupt request register

}

Attachments

Outcomes