combining Teensy Audio and Pedvide's ADC libraries

Status
Not open for further replies.

teunos

Member
Hi all,

For a while i have been using pedvide's ADC library to use PDB to trigger DMA to move ADC conversions.
I would like to add the Audio library for pulsing sine waves modulated using the envelope function.
To this end, i used the Audio generation tool.
Upon testing the sine wave alone, it works flawlessly sampling DAC0 on my scope looks exactly like it should.
However, including the ADC object into the code instantly makes any output disappear.

I tried ploughing through the ADC.cpp and ADCmodule.cpp source code to find the origin of this error but cannot find it.

My code:
Code:
#include <Audio.h>
#include <Wire.h>
#include <SPI.h>
#include <SD.h>
#include <SerialFlash.h>
#include <Arduino.h>
#include <ADC.h>
#include <DMAChannel.h>


#define MeasurementPin  A11            // read signal connected to analog pin 0, Connects to ADC1

// Samples
int       pts     = 3000;               // this is the default amount of measurement points transfered to the Serial interface
const int ptsmax  = 32767;              // maximum # of measurement points. (2^15-1) ( due to register bit CITER in TCD (15-bit if channel linking disabled) ), any larger and the DMA wont work.
uint16_t  samples[ptsmax];              // saving the samples that are read to the internal memory
volatile boolean  sampleDone      = false;
String    readInput   = "";             // String used to determine what the MCU has to do
uint32_t  Fs = 12000;                   // sample rate in Hz

//ADC object and DMA channel used for sampling.
// CREATE THE ADC OBJECT FIRST, BEFORE ALL AUDIO STUFF!!
ADC *adc = new ADC();                     // adc object. Use ADC1 (and thus PDB0_CH1C1) since the Audio library already uses ADC0 (and thus PDB0_CH0C1)
int adcStat = 0;                          // status of ADC, keeps check of status for either default measurement(0) or other (1)
DMAChannel dmaADC;                           // dmaADC Channel used to move data from the ADC to the memory location o samples[]

// GUItool: begin automatically generated code // DEFINE ANY AUDIO STUFF AFTER ADC has been called!!!!!!!!!
AudioSynthWaveformSine   sine1;
AudioEffectEnvelope      envelope1;
AudioOutputAnalog        dac0;
AudioConnection          patchCord1(sine1, envelope1);
AudioConnection          patchCord2(envelope1, dac0);

void measure() {   // To start measuring, all that needs to happen is to start the PDB, the rest happens automatically.
  adc->adc1->startPDB(Fs); //frequency in Hz
}

void pdb_isr() {
  PDB0_SC &= ~PDB_SC_PDBIF; // clear interrupt
  Serial.println("pdb isr");
}

void adc1_isr() {
  Serial.println("adc1 isr"); // you can uncomment this, but the dmaADC already clears the interrupt so this should never print!
}

void dmaADC_isr() {
  dmaADC.clearInterrupt();
  adc->adc1->stopPDB();
  sampleDone = true;
  //Serial.println("dmaADC isr");
}

void dmaADCInit() {
  dmaADC.source(*(uint16_t*) &ADC1_RA);
  dmaADC.destinationBuffer(samples, sizeof(samples));
  dmaADC.attachInterrupt(dmaADC_isr);
  dmaADC.interruptAtCompletion();
  dmaADC.triggerAtHardwareEvent(DMAMUX_SOURCE_ADC1);
  dmaADC.enable();
}

void setAdcSig() {
  adc->enableInterrupts(ADC_1); // it's necessary to enable interrupts for PDB to work
  adc->enableDMA(ADC_1); // enable DMA requrest;    ADC1_SC2 |= | ADC_SC2_ADTRG | ADC_SC2_DMAEN;
  adcStat = 0;
}

void setup() {

  // Setup the Audio output DAC sine and envelope values.
  AudioMemory(20);
  //sine1.phase(0);
  sine1.frequency((float)2000);
  sine1.amplitude(1);  // default disable!
  envelope1.delay(0);
  envelope1.attack(0.5);
  envelope1.hold(0);
  envelope1.decay(0);
  envelope1.sustain(1); //when the note is in the sustain phase, it is ON.
  envelope1.release(0.5);
  
  // setup DMA
  dmaADCInit();                        delay(50);

  // initialize the ADC for ADC1 (measurement signal pin)
  adc->setReference(ADC_REFERENCE::REF_3V3, ADC_1);   // set voltage reference
  adc->setAveraging(8, ADC_1);                        // set number of averages
  adc->setResolution(12, ADC_1);                      // set bits of resolution

  // it can be any of the ADC_CONVERSION_SPEED enum: VERY_LOW_SPEED, LOW_SPEED, MED_SPEED, HIGH_SPEED_16BITS, HIGH_SPEED or VERY_HIGH_SPEED
  // where the numbers are the frequency of the ADC clock in MHz and are independent on the bus speed.
  adc->setConversionSpeed(ADC_CONVERSION_SPEED::MED_SPEED, ADC_1); // change the conversion speed
  // it can be any of the ADC_SAMPLING_SPEED enum: VERY_LOW_SPEED, LOW_SPEED, MED_SPEED, HIGH_SPEED or VERY_HIGH_SPEED
  adc->setSamplingSpeed(ADC_SAMPLING_SPEED::MED_SPEED, ADC_1); // change the sampling speed         // max 35kHz with MED_SPEED, but 32kHz to get to the Hz accurate. (This is also the limit set in Matlab)
  adc->analogRead(MeasurementPin, ADC_1); // call this to setup everything before the pdb starts

  // setup final ADC; enable interrupts and enable DMA.
  setAdcSig();                      delay(50);

    // initialize Serial communications to the host computer:
  Serial.begin(9600);                 // the Teensy does not care at what speed it is running, will always be USB Full speed
}


void loop() {
  if (Serial.available() > 0) {   // some
    readInput = Serial.readStringUntil('\n');
    if (readInput == "on") {
      Serial.println("note_on");
      envelope1.noteOn();
    }    else if (readInput == "off") {
      Serial.println("note_off");
      envelope1.noteOff();
    }    else if (readInput == "measure") {
      Serial.println("measuring");
//    measure();
    }
  }
}

I found in some posts on the ADC library that the ADC object should be called first before any audio stuff is done, but that does not solve the problem.
Basically, commenting all the ADC stuff except for even calling the ADC object stops DAC output.
Code:
ADC *adc = new ADC();                     // adc object. Use ADC1 (and thus PDB0_CH1C1) since the Audio library already uses ADC0 (and thus PDB0_CH0C1)
I'm stuck. Can anybody offer any insights as to what the reason is and how to fix it?
Thanks in advance for reading :)
 
I've had to relearn about ADC0 and ACD1 a few times over the last couple of years.
I still feel like I have only part of the story.
I know the Audio lib's AudioInputAnalog object conflicts with plain old analogRead(). The library heavily uses ADC_0.
I have added an analog device to my project (which already uses the audio lib) so I had to re-figure this out.
I also had to shift some pins around.

I can get Audio and Pedvide's ADC to work together (and to fail) by commenting lines in and out of this sample code:
Code:
#include <Audio.h>
#include <ADC.h>

AudioInputAnalog adc1(A7);  // commenting out this line allows any of the 3 analogRead lines below to succeed
ADC             *adc = new ADC();

void setup() {
    pinMode(13, OUTPUT);
    Serial.begin(9600);
    Serial.println("Started...");
    AudioMemory(12);    
}

bool ledOn = false;
void loop() {
    int x = 42;

// Uncomment one of the following at a time.  All of these work if AudioInputAnalog object is commented out
//-----------------------
    x = analogRead(A3);                 // AudioInputAnalog plus analogRead() are incompatible, regardless of pins. Teensy crashes
//    x = adc->analogRead(A3, ADC_0);   // This returns -70000 continuously, regardless of input - should be 0-1024.  Teensy continues to run (blinks)
//    x = adc->analogRead(A3, ADC_1);   // This works regardless of AudioInputAnalog (see ADH.H documentation for pin number limitations)
//------------------------

    Serial.println(x);
    digitalWrite(13, ledOn);
    ledOn = !ledOn;
    delay(100);
}

So with the right pins (A2, A3, A10-A13) I can use ACD_1 for adc->analogRead(pin, ADC_1) while Audio is running.
I've only really tested A2 and A3, and only with AudioInputAnalog (not with the Audio Lib's DAC).

If there's more to the story, I'd love to hear it.
I hope this helps, @teunos
 
Last edited:
Yes, if you have the Audio library use the ADC0 then you can use the ADC library to measure with the ADC1. However there's only one PDB module, so only one of the libraries can use that at a given time.
 
Thanks, Pedvide
So, PDB has nothing to do with the Audio Lib (unless your put both in a project), right?

I expect I should be able to use any Audio objects with ADC_1 - except, perhaps, the audio lib's AudioInputAnalogStereo. Does this latter object occupy both ACD's
 
What I'd like to see is extending the Audio Library to fill queues with other ADC pins.

This doesn't quite make sense to pass a bunch of pins with a constructor but you get the idea.

This would tell ADC to sample from 5 different pins at the <sampleRate>/1000.
When those queues fill up they would get written to sdcard in a different file with DMA just as it does with the audio.

Code:
AudioInputAnalogMulti   adc1(16,17,18,19,20);
AudioADCConnection  patchCord1(adc1, 0, queue1, 0, 1000);
 
Well, I guess that's something that ADC library should allow. I think the idea of sampling several pins periodically is interesting so maybe I can try to implement it. Probably I'd implement it so that each pin has a buffer that then the user can use or save.
 
I think that basic functionality as described would be very valuable.

In the long run, I think it would be worthwhile to have it use the same method as the audio library. Would it make sense to require the audio library, or to separate out a DMA queue library that the audio library and the ADC library include. Cause ultimately the same would be great for the i2c and SPI library.

And ideally it would be great to have a divisor for each pin, such as A1=1000, A2=2000, A3=44117.
Not sure the best way to handle the slightly off sample rates based on the 44117.###

I've done interrupt sampling with divisors for each pin in a sketch, but was not all that great without DMA.
 
Status
Not open for further replies.
Back
Top