MAX9814 and Arduino Due to output sound from the microphone

I’d like to know if it’s of any use, trying to output sound from the MAX9814 microphone through the Due’s DAC. I know I could output sound from it even without a MC, but after getting it to work, the goal is to add some kind of effects to the sound.

I’ve hooked up the Max mic to the A0 pin, and outputting data from the DAC0 to a PAM8403 Amp I’m not getting any results, but that’s obviously my code right now. The max9814 is working tough, I get some good feedback from the serial log.

Right now I’m not worried about delays, or any of that, just really if it theoretically would work?

Oh forgot to add the code I did so far, it’s basically a copy/paste from some exmaples, but I figured it should work, right? I’m not wiring the DAC directly to a speaker, have to enphasize that, it’s going through a PAM amp, with a 10K just to be sure.

#include <Audio.h>
#include <DAC.h>

const int sampleWindow = 50; // Sample window width in mS (50 mS = 20Hz)
unsigned int sample;
 
void setup() 
{
   Serial.begin(9600);
   analogWriteResolution(12);
}
 
void loop() 
{
   unsigned long startMillis= millis();  // Start of sample window
   unsigned int peakToPeak = 0;   // peak-to-peak level
 
   unsigned int signalMax = 0;
   unsigned int signalMin = 1024;
 
   // collect data for 50 mS
   while (millis() - startMillis < sampleWindow)
   {
      sample = analogRead(0);
      if (sample < 1024)  // toss out spurious readings
      {
         if (sample > signalMax)
         {
            signalMax = sample;  // save just the max levels
         }
         else if (sample < signalMin)
         {
            signalMin = sample;  // save just the min levels
         }
      }
   }
   peakToPeak = signalMax - signalMin;  // max - min = peak-peak amplitude
   double volts = (peakToPeak * 5.0) / 1024;  // convert to volts
   analogWrite(DAC0,sample); 
   Serial.println(volts);
}

Thanks!

Some rules with the DUE:

Analog input pins support voltages within the range 0V and 3.3V. Beyond these limits, the board will be destroyed.

For accurate ADC conversions, you'll have to calibrate the ADC peripheral. There is an Application Note from Atmel to calibrate the ADC. Search for the Atmel Application Note:
Analog-to-Digital Converter in the SAM3S4

DAC0 and DAC1 output pins output voltages within the range 1/6 * 3.3V and 5/6 * 3.3V. For full swing DACs, you'll have to add some hadware:

If you want to apply some digital filtering in between ADC conversions and DAC outputs, ADC conversions should be handled thru a PDC DMA. Search in the DUE sub forum for example sketches with ADC DMA conversions.

If you need to process some DSP, there is an ARM DSP library dedicated to ARM Cortex M3 uc usable with the DUE (plus some DSP examples).

Another method to realize an audio project with this board is I2S (SSC) and a CODEC.

It should be straightforward... Until you start adding effects/processing. :wink:

All you have to do is read the ADC in a fast-loop and write the data to the DAC. It's a simple analog-to-digital-to-analog conversion. The Audacity website has a little [u]tutorial[/u] about how digital audio works.

Since the Arduino can't read negative voltages the input should be biased at half the reference voltage (usually half of Vcc). If your microphone board was designed for the Arduino the output is probably biased already and you should be reading around 2048 with silence. (There will be some noise so that number will jump around and the AGC built-into the MAX9814 will make the noise worse.)

The output has to be biased for the same reason so if you just pass-through the data you'll be OK. You will need a capacitor in series between the Arduino-output and the amplifier-input to filter-out the DC. (The PAM amplifier may already have a capacitor on it's input.)

Once you start doing any real processing, you'll have to take the bias out of the audio data, and then add it back. (Just for example, you wouldn't want to amplify or attenuate the bias.)

And for any real DSP you'll need a known-fixed sample rate and all of the DSP has to be done in-between reading-writing at that sample rate. You'll also need to store several samples in a buffer because most DSP can't work on one sample at a time.

If you want the best quality there should be an (analog) anti-aliasing filter on the input. (You can get-by without a smoothing filter on the output as long as the sample rate is above audibility.)

The simplest processing is a volume change and that's done with multiplication. i.e. Multiply every sample by 2 for a 6dB increase and multiply by 0.5 for 6dB of attenuation, etc.

Right now I'm not worried about delays, or any of that

Latency (delay) should be negligible.

...Computers have latency because of the multitasking operating system which is always multitasking, even if you're running just one application.

When you record with a computer, audio from the ADC (which has it's own clock independent from the processor) comes into a buffer at a smooth-constant rate. Then whenever the operating system gets-around to it, the buffer is read in a quick burst and the audio is written to the hard drive. That buffer obviously introduces a delay. You can get a shorter (sometimes unnoticeable) delay with a smaller buffer, but if the buffer doesn't get read in time you get buffer overflow and a glitch in your audio.

There is also a playback buffer which works the opposite way. It gets filled in a quick-burst and the audio goes out to the DAC (which also has an independent clock) at a smooth-constant rate. Here the danger is buffer underflow.

An example of guitar pedal shield with an Arduino DUE to produce echo or chorus effects:

https://www.electrosmash.com/pedalshield

ard_newbie:

DVDdoug:
And for any real DSP you'll need a known-fixed sample rate and all of the DSP has to be done in-between reading-writing at that sample rate. You'll also need to store several samples in a buffer because most DSP can't work on one sample at a time.

+1

ADC conversions with a PDC DMA provides a known-fixed sample rate (e.g. 44.1 KHz or 48 KHz) and e.g. a 128 samples buffer to work with.

Thank you all for the suggestions. Even tough I work with arduino for years, since the first released board, it's my first time ever working with audio signals, and also since I don't own an oscilloscope anymore, things get a bit scary when I can't see what's going on...

So to sum up my steps:

First of all I must calibrate the ADC, to have the best accuracy possible.

Then just to get the initial project working from MIC > Arduino A0 > DAC0 > PAM > Speakers all I have to do it write the data to the DAC? What could I change then in my code, I thought what I did would be enough to have something out already... (I'm writing this without opening the link before, I have little time to reply now, sorry =)

DVDdoug:
It should be straightforward... Until you start adding effects/processing. :wink:

**All you have to do is read the ADC in a fast-loop and write the data to the DAC. **
[/quote]
Also, about the processing, as @ard_newbie said:
> ard_newbie:
> An example of guitar pedal shield with an Arduino DUE to produce echo or chorus effects:
>
>
I was already going to base my inital DSP processing on this effects pedal project. The main difference would be the input sound coming from the MIC and the output directly to a speaker (amplified by any amp I use onboard).
Since that project is very old, is it still relevant if I start using the ARM DSP library? Maybe for reference...

Unfortunately there is no chance that you can release a correct audio project with sound effects using arduino functions analogread(), analogwrite() because they won’t give you the accuracy you need. IMO audio projects are amongst the most complex ones.

First of all, you have to understand what’s under the hood of the uc for ADC and DAC peripherals, plus the Timer Counter TC. Then you have to understand how works a PDC DMA to reduce the number of interruptions and fill 128 samples buffers. And finally process these buffers according to the effects you are looking for.

I can give you the example sketch below. Connect an LED to pin 12 (plus a resistor in serie of course), that’s handy for debugging. Once you have understood the different parts, you can comment the blinking parts to give more time to the digital filtering process in loop().

/****************************************************************************************************/
/*  44.1 KHz ADC conversions of 1 analog input (A0) triggered by Timer Counter 0 channel 2 TIOA2    */
/*  44.1 KHz DAC output on channel 1 (DAC1) triggered by Timer Counter 0 channel 2 TIOA2            */
/****************************************************************************************************/

volatile uint8_t bufn, bufn_dac, Oldbufn;
const uint16_t bufsize = 128;            // size of buffer of samples
const uint8_t bufnumber = 2;             // better be a power of 2
const uint8_t _bufnumber = bufnumber - 1;
uint16_t buf[bufnumber][bufsize];        // bufnumber buffers of bufsize samples,

volatile boolean Flag;

void setup()
{

  pinMode(LED_BUILTIN, OUTPUT);
  pinMode(12, OUTPUT);
  adc_setup();
  dac_setup();
  tc_setup();
}

void loop()
{
  if (Flag)
  {
    Flag = false;
    
    // Todo : digital filtering before DAC output whenever Flag is true
    // Digital filtering process on buf[Oldbufn], i.e. the last filled buffer by ADC
    // Note that this process should last (much) less than 1/44100  * 128 = 2.9 ms
   
  }
}

/*************  Configure adc_setup function  *******************/
void adc_setup() {

  PMC->PMC_PCER1 |= PMC_PCER1_PID37;                    // ADC power ON

  ADC->ADC_CR = ADC_CR_SWRST;                           // Reset ADC
  ADC->ADC_MR |=  ADC_MR_TRGEN_EN                       // Hardware trigger select
                  | ADC_MR_TRGSEL_ADC_TRIG3             // Trigger by TIOA2
                  | ADC_MR_PRESCAL(1);

  ADC->ADC_ACR = ADC_ACR_IBCTL(0b01);                 // For frequencies > 500 KHz

  ADC->ADC_IER = ADC_IER_ENDRX;                         // End Of Conversion interrupt enable for channel 7
  NVIC_EnableIRQ(ADC_IRQn);                             // Enable ADC interrupt
  ADC->ADC_CHER = ADC_CHER_CH7;                         // Enable Channel 7 = A0

  /*********  code for PDC/DMA  buffer filling sequence **********/
  ADC->ADC_RPR = (uint32_t)buf[1];                      // DMA buffer - First one will be buf[1]
  ADC->ADC_RCR = bufsize;
  ADC->ADC_RNPR = (uint32_t)buf[0];                     // next DMA buffer
  ADC->ADC_RNCR = bufsize;
  bufn = 2;
  ADC->ADC_PTCR |= ADC_PTCR_RXTEN;                      // Enable PDC Receiver channel request
  ADC->ADC_CR = ADC_CR_START;
}

/*********  Call back function for ADC PDC/DMA **************/
void ADC_Handler () {

  //  if ( ADC->ADC_ISR & ADC_ISR_ENDRX) {  // Useless because the only one
  Oldbufn = bufn;  //Keep the previous buffer number
  
  bufn = (bufn + 1) & _bufnumber;
  ADC->ADC_RNPR = (uint32_t)buf[bufn];
  ADC->ADC_RNCR = bufsize;

  Flag = true;

  // For debugging only
  static uint32_t Count;
  if (Count++ == 344) { //84000000/8/238/128 = ~344
    Count = 0;
    PIOB->PIO_ODSR ^= PIO_ODSR_P27;  // Toggle LED_BUILTIN every 1 Hz
  }
  //  }
}

/*************  Configure dac_setup function  *******************/
void dac_setup ()
{

  PMC->PMC_PCER1 = PMC_PCER1_PID38;                   // DACC power ON

  DACC->DACC_CR = DACC_CR_SWRST ;                     // Reset DACC
  DACC->DACC_MR = DACC_MR_TRGEN_EN                    // Hardware trigger select
                  | DACC_MR_TRGSEL(0b011)             // Trigger by TIOA2
                  | DACC_MR_USER_SEL_CHANNEL1         // select channel 1
                  | DACC_MR_REFRESH (1)
                  | DACC_MR_STARTUP_8
                  | DACC_MR_MAXS;

  DACC->DACC_IER |= DACC_IER_ENDTX;                   // TXBUFE works too !!!
  NVIC_EnableIRQ(DACC_IRQn);
  DACC->DACC_CHER = DACC_CHER_CH1;                    // enable channel 1 = DAC1

  /*************   configure PDC/DMA  for DAC *******************/
  DACC->DACC_TPR  = (uint32_t)buf[0];                 // DMA buffer
  DACC->DACC_TCR  = bufsize;
  DACC->DACC_TNPR = (uint32_t)buf[1];                 // next DMA buffer
  DACC->DACC_TNCR =  bufsize;
  bufn_dac = 1;
  DACC->DACC_PTCR = DACC_PTCR_TXTEN;                  // Enable PDC Transmit channel request

}

/*********  Call back function for DAC PDC/DMA **************/
void DACC_Handler() {                                 // move Sinus/PDC/DMA pointers to next buffer

  //  if ( DACC->DACC_ISR & DACC_ISR_ENDTX) {             // Should be useless because the only one
  bufn_dac = (bufn_dac + 1) & _bufnumber;
  DACC->DACC_TNPR = (uint32_t)buf[bufn_dac];
  DACC->DACC_TNCR = bufsize;


  // For debugging only
  static uint32_t Count;
  if (Count++ == 344) { //84000000/8/238/128 = ~344
    Count = 0;
    PIOD->PIO_ODSR ^= PIO_ODSR_P8;  // Toggle LED_BUILTIN every 1 Hz
  }
  //  }
}
/*************  Timer Counter 0 Channel 2 to generate PWM pulses thru TIOA2  ************/
void tc_setup() {

  PMC->PMC_PCER0 |= PMC_PCER0_PID29;                      // TC2 power ON : Timer Counter 0 channel 2 IS TC2
  TC0->TC_CHANNEL[2].TC_CMR = TC_CMR_TCCLKS_TIMER_CLOCK2  // MCK/8, clk on rising edge
                              | TC_CMR_WAVE               // Waveform mode
                              | TC_CMR_WAVSEL_UP_RC       // UP mode with automatic trigger on RC Compare
                              | TC_CMR_ACPA_CLEAR         // Clear TIOA2 on RA compare match
                              | TC_CMR_ACPC_SET;          // Set TIOA2 on RC compare match


  TC0->TC_CHANNEL[2].TC_RC = 238;  //<*********************  Frequency = (Mck/8)/TC_RC  Hz = 44.117 Hz
  TC0->TC_CHANNEL[2].TC_RA = 20;  //<********************   Any Duty cycle in between 1 and TC_RC

  TC0->TC_CHANNEL[2].TC_CCR = TC_CCR_SWTRG | TC_CCR_CLKEN; // Software trigger TC2 counter and enable

}

BTW, be careful with DAC0 and DAC1, always connect at least a 1.5 K Ohms resistor in serie or they will be destroyed.