DAC resolution - Audio board - Teensy breakout board

Status
Not open for further replies.
The project that I am working on requires short (20-50 ms) bursts of white noise (75 to 125 dB) over a constant background of low volume white noise (65-68 dB). The wide dynamic range (60 dB) required to do this just exceeds the dynamic range of the DAC (A14: 12 bit resolution), using my particular speakers and amp.

I am thinking of switching to using the audio board (16 bit resolution) to solve this problem, but I am reluctant to change because everything else is working quite well. The code is shown below. The board is driven by commands from a python program.

My questions are:
1. How easy would it be to convert this code to use the audio board?
2. Is the audio board compatible with the Teensy Breakout board? I have found this breakout board to be useful in allowing easy access to the differential inputs of the 2nd ADC since I have to capture two signals during the presentation of the white noise pulse.

Code:
#include <ADC.h>
#include "IntervalTimer.h"

// define pins
const int readPin1 = A10;       // ADC_0 accelerometer box 1
const int readPin1diff = A11;   // ADC_0 accelerometer box 1 differential input
const int readPin2 = A12;       // ADC_1 accelerometer box 2
const int readPin2diff = A13;   // ADC_1 accelerometer box 2 differential input
const int DACPin = A14;         // Audio output
const int ledPin = 13;          // use to look at timing

// data storage
const int BUFFERSIZE = 1024;
uint16_t buf0[BUFFERSIZE];
uint16_t buf1[BUFFERSIZE];
byte val[2];

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

// constants
const int period1 = 50; // us    20kHz
const int baudrate = 115200;
const long DAC_midpoint = 2048; // half of maximum DAC output

// trial parameters
const unsigned long pulse2Start = 200000;  // 200 ms, start of second pulse is fixed
int Background_Level = 2;   // background noise level
int Background_half = 1;       // half background level
int Volume1 = 0;        // volume of first pulse
int Volume1_half = 0;     // half volume1
unsigned long Duration1 = 0;  // duration of first pulse
int Volume2 = 0;        // volume of second pulse
int Volume2_half = 0;     // half volume2
unsigned long Duration2 = 0;  // duration of first pulse
long PulseInterval = 0;  // delay between end of first pulse and start of second pulse
unsigned long start = 0;  // time at start of trial


void setup() {
  // setup DAC
  pinMode(DACPin, OUTPUT);
  analogWriteResolution(12);

  // set up read pins
  pinMode(readPin1, INPUT);
  pinMode(readPin1diff, INPUT);
  pinMode(readPin2, INPUT);
  pinMode(readPin2diff, INPUT);

  // digital pin
  pinMode(ledPin, OUTPUT);

    // ADC_0 setup
  adc->setAveraging(32, ADC_0); // set number of averages
  adc->setResolution(16, ADC_0); // set bits of resolution
  adc->setConversionSpeed(ADC_VERY_LOW_SPEED , ADC_0); // change the conversion speed
  adc->setSamplingSpeed(ADC_VERY_LOW_SPEED , ADC_0); // change the sampling speed
  adc->enablePGA(1, ADC_0); // only works in differential mode
  adc->setReference(ADC_REF_3V3, ADC_0);
  // ADC_1 setup
  adc->setAveraging(32, ADC_1); // set number of averages
  adc->setResolution(16, ADC_1); // set bits of resolution
  adc->setConversionSpeed(ADC_VERY_LOW_SPEED , ADC_1); // change the conversion speed
  adc->setSamplingSpeed(ADC_VERY_LOW_SPEED , ADC_1); // change the sampling speed
  adc->enablePGA(1, ADC_1); // only works in differential mode
  adc->setReference(ADC_REF_3V3, ADC_1);
  
  timer1.begin(timer1_callback, period1);
  Serial.begin(baudrate);
}

void loop() {
  while ( Serial.available() > 0 ) {
    String inString = Serial.readStringUntil( '\n' );
        
    // parse the string for a command, use 'startsWith' to ignore newline character
    if ( inString.startsWith( "handshake" ) ) {
      Serial.println("handshake"); // return handshake
    } else if ( inString.startsWith( "Background" ) ) {
      Background_Level = inString.substring( 10 ).toInt();
      Background_half = Background_Level / 2;
    } else if ( inString.startsWith( "Volume1" ) ) {
      Volume1 = inString.substring( 7 ).toInt();
      Volume1_half = Volume1 / 2;
    } else if ( inString.startsWith( "Volume2" ) ) {
      Volume2 = inString.substring( 7 ).toInt();
      Volume2_half = Volume2 / 2;
    } else if ( inString.startsWith( "Duration1" ) ) {
      Duration1 = inString.substring( 9 ).toInt();
    } else if ( inString.startsWith( "Duration2" ) ) {
      Duration2 = inString.substring( 9 ).toInt();
    } else if ( inString.startsWith( "PulseInterval" ) ) {
      PulseInterval = inString.substring( 13 ).toInt();
    } else if ( inString.startsWith( "ASR" ) ) {
      run_Trial();
    } else {
      // ignore unknown commands
    }
  }
} // main loop

void run_Trial (void) {

  digitalWrite(ledPin, HIGH);
  start = micros();  // determines the start time of the trace, needed to time white noise output

  for (int i = 0 ; i < BUFFERSIZE ; i++) {
    buf0[i] = adc->analogReadDifferential(readPin1, readPin1diff, ADC_0);
    buf1[i] = adc->analogReadDifferential(readPin2, readPin2diff, ADC_1);
  } // for
  
  digitalWrite(ledPin, LOW);
  
  // send data as bytes
  for (int i = 0; i < BUFFERSIZE; i++) {
    val[0] = lowByte(buf0[i]);
    val[1] = highByte(buf0[i]);
    Serial.write(val, 2);
  }
  for (int i = 0; i < BUFFERSIZE; i++) {
    val[0] = lowByte(buf1[i]);
    val[1] = highByte(buf1[i]);
    Serial.write(val, 2);
  }
  Serial.flush();
  
} // run_Trial

void timer1_callback(void) {
  unsigned long timeNow = micros() - start; //time at start of function

  // timing of prepulse
  if ( (timeNow >= (pulse2Start - Duration1 - PulseInterval))  &&  (timeNow < (pulse2Start - PulseInterval)) ) {
    analogWrite(DACPin, random(-Volume1_half, Volume1_half) + DAC_midpoint);
  } // timing of stimulus pulse
  else if ( (timeNow >= pulse2Start)  &&  (timeNow < (pulse2Start + Duration2)) ){
    analogWrite(DACPin, random(-Volume2_half, Volume2_half) + DAC_midpoint); 
  }
  else {
    analogWrite(DACPin, random(-Background_half, Background_half) + DAC_midpoint); // background noise
  }
}  // timer1_callback
 
The Audio board does not use pins 0-5, 8, A2-A3, A6-A7 on the top pins. It does not use any of the bottom pins, nor the back pins. If you have i2c devices, you can hook them up to A4/A5. I don't know what i2c devices the audio board enables right now.

It does use pins:
  • pin 6 (MEMCS - only if you solder a flash memory chip on to the audio card);
  • pin 7 (alt MOSI);
  • pin 9 (BCLK);
  • pin 10 (SDCS);
  • pin 11 (MCLK);
  • pin 12 (MISO);
  • pin 13 (I2S RX);
  • pin 14/A0 (SCLK);
  • pin 15/A1 (Volume);
  • pin 18/A4 (SDA0 - i2c);
  • pin 19/A5 (SCL0 - i2c);
  • pin 22/A8 (I2S TX);
  • pin 23/A9 (LRCLK).

I am just at the stage of soldering my audio board, and I put together a little spreadsheet to show the pin usage: https://docs.google.com/spreadsheets/d/1FccEZK2k174_TJw44ZridMGn-Yw2zyfYeeePysx2DKE/edit?usp=sharing
 
Last edited:
1. How easy would it be to convert this code to use the audio board?

Not so easy, I'm afraid. The audio shield uses I2S communication, which is generally done with buffers and DMA transfers.

Perhaps you could give the audio library a try? It has pink and white noise generators, and software mixers. We recently made this tutorial video. There's section about synthesis starting at 25:04 in the video, but we didn't demo the noise object.

https://www.youtube.com/watch?v=wqt55OAabVs

These tutorial examples can be adapted to use the DAC. Just replace the I2S output object with the DAC one, and delete the SGTL5000 object (and code which calls its functions) since you don't have that hardware connected. Using the audio lib would mean massively restructuring your program, but maybe that's not so bad? Most of the changes would be just using the library instead of your own interrupt.

On the plus side, the library has a very good pink noise object and lots of features to make these sorts of projects very easy.
 
These tutorial examples can be adapted to use the DAC.

The reason I have to change the hardware is that the 12 bit resolution of the DAC is just not quite good enough for the required dynamic range, so I wouldn't do this. It is frustrating because the DAC is close to perfect for this application. I thought I might be able to use the audio board with limited changes to the code. Apparently not.
 
Status
Not open for further replies.
Back
Top