USB noise in ADC readings?

Status
Not open for further replies.
I am working on a project to collect scientific data from sensors via the ADC, at high sample rates (right now at about 150-160ksps). The data is not being stored on the Teensy, but is being piped into a linux computer for a variety of processing and machine learning tasks. The Teensy is basically a buffer right now though we might do a few more things in the future.

Everything seems to be working well but transmitting the data over USB seems to be producing noise in the readings. The sensors are still under development so right now I am using an adafruit microphone to test. Sample buffers are sent over USB once they are full and the rate at which they get sent matched perfectly with the frequency of the noise I see in the data (when I adjust sample rate or buffer size).

Circuit & Noise:
circuit.JPG USB noise.PNG

I am using platformIO with makefiles (Clion), and the arduino framework. Only using the ADC library right now. Code attached. Any advice or help would be great :)

Code:
/**
 * TeensyADC - High speed audio sampling with the teeny 3.2 ARM board This code samples the ADC on the teensy3.2 at
 *   the highest stable rate possible and dumps that data out the USB port using a double buffer. The purpose of
 *   this is for high speed bio-acustics, where most of the processing will be done on a second board
 *   (intel edison at the moment)
 *
 *   Enable the debug and hook pin 2 and 3 to an oscilloscope to watch the timing. Further optimizations
 *   Could be possible to increase speed. As well as analog signal processing on
 *   the input before sampling.
 *
 * @author Ethan Slattery
 * @contact Ethan.Slattery@yahoo.com
 * @date 15-July-2016
 */
#include <ADC.h>

#define BIT_DEPTH 12            // 13-bit usable ADC sample, Mask with 0x1FFF (8, 10, 12 or 16)
#define SAMPLE_AVERAGES 1       // Increases sample time linearly (0, 4, 8, 16 or 32)
#define SAMPLE_RATE_HZ 150000    // Highest stable sample rate at the moment is 160000, which leaves a 2.6us gap for USB wich takes 2.5us
#define BUFFER_SIZE 8192        // Size of the buffer in samples (should be multiple of 512, 64k of ram avail)

#define DEBUG_ON 0              // enables the debug GPIO (disable for depoyment)
#define DEBUG_PIN_1 2           // GPIO pin for timing the read ISR
#define DEBUG_PIN_2 3           // GPIO pin for timing the PDB frequency
#define LED_PIN LED_BUILTIN     // Teensy 3.2 Built in LED pin
#define READ_PIN A2             // Pin for sampling the audio input

//elapsedMicros benchmark1;       // Benchmark timer, auto increasing obj :)
ADC *adc = new ADC();             // The ADC object

/*** GLOBAL DATA VARIABLES ***/
uint16_t  buff0[BUFFER_SIZE];  // Buffer for the samples to send over USB
uint16_t  buff1[BUFFER_SIZE];  // Buffer for the samples to send over USB
uint8_t   header[]  = {0xFF, 0xFF}; // Header bytes for delimiting packets
const int HEADER_SIZE = 2;          // Size of the header buffer

/*** GLOBAL LOGIC VARIABLES ***/
volatile int  buffCounter   = 0;    // Keeps track of the samples in buffer
volatile int  currentBuffer = 0;    // Tracks the currently active buffer
volatile bool buff0Ready = false;   // Flag to set buffer 0 ready for sending
volatile bool buff1Ready = false;   // Flag to set buffer 1 ready for sending

void setup(void) {
    // Set all the pin modes
    pinMode(LED_PIN, OUTPUT);      // LED PIN
    pinMode(DEBUG_PIN_1, OUTPUT);  // hardware debug pin
    pinMode(DEBUG_PIN_2, OUTPUT);  // hardware debug pin
    pinMode(READ_PIN, INPUT);      // ADC sampling for hydrophone

    // Start serial output, 12Mbit/sec no matter what over teensy USB
    Serial.begin(115200);

    /*** Setup the ADC samples ***/
    adc->setAveraging(SAMPLE_AVERAGES);
    adc->setResolution(BIT_DEPTH);
    adc->setReference(ADC_REF_3V3);

    // Set the conversion and sampling speed of the single ADC measurements
    // Options: ADC_VERY_LOW_SPEED, ADC_LOW_SPEED, ADC_MED_SPEED,
    //          ADC_HIGH_SPEED_16BITS, ADC_HIGH_SPEED or ADC_VERY_HIGH_SPEED
    adc->setConversionSpeed(ADC_HIGH_SPEED);
    adc->setSamplingSpeed(ADC_HIGH_SPEED);

    // Enable intterrupts on the ADC module (ADC_0 or ADC_1)
    adc->enableInterrupts(ADC_0);

    // Perform one read to burn in settings
    adc->analogRead(READ_PIN, ADC_0);

    // Start the PDB timer at the given freq.
    adc->adc0->stopPDB();
    adc->adc0->startPDB(SAMPLE_RATE_HZ);

    // slight delay to let everything settle and flash light to show setup is complete
    digitalWrite(LED_BUILTIN, HIGH);
    delay(500);
    digitalWrite(LED_BUILTIN, LOW);
    delay(250);

    while(!Serial){}

    // blink LED to show serial is connected and sampling starts
    digitalWrite(LED_BUILTIN, HIGH);
    delay(250);
    digitalWrite(LED_BUILTIN, LOW);

} // END of setup()

void loop(void) {

    // If buff0 is ready to send, send it and toggle the ready flag
    if(buff0Ready) {
#if DEBUG_ON
            digitalWriteFast(DEBUG_PIN_1, HIGH);
            digitalWriteFast(LED_PIN, HIGH);
#endif
            Serial.write(header, HEADER_SIZE);
            Serial.write(reinterpret_cast<char *>(buff0), BUFFER_SIZE * 2);
#if DEBUG_ON
            digitalWriteFast(DEBUG_PIN_1, LOW);
#endif
        buff0Ready = false;
    }

    // If buff1 is ready to send, send it and toggle the ready flag
    if(buff1Ready) {
        // Only write to serial if the port is open
#if DEBUG_ON
            digitalWriteFast(DEBUG_PIN_1, HIGH);
#endif
            Serial.write(header, HEADER_SIZE);
            Serial.write(reinterpret_cast<char *>(buff1), BUFFER_SIZE * 2);
#if DEBUG_ON
            digitalWriteFast(DEBUG_PIN_1, LOW);
            digitalWriteFast(LED_PIN, LOW);
#endif
        buff1Ready = false;
    }

    // If the ADC fail flag is set, output the error
    if(adc->adc0->fail_flag) {
#if DEBUG_ON
        Serial.print("ADC0 error flags: 0x");
        Serial.println(adc->adc0->fail_flag, HEX);
#endif
        if(adc->adc0->fail_flag == ADC_ERROR_COMPARISON) {
            adc->adc0->fail_flag &= ~ADC_ERROR_COMPARISON; // clear that error
#if DEBUG_ON
            Serial.println("Comparison error in ADC0");
#endif
        }
    } // End of error message handler

} // END OF LOOP()

/**
 * ADC Interrupt Service Routine
 * This ISR is called once the ADC (triggered by the PDB) is done with it's
 * measurement. The appropriate buffer is then selected and the sample is placed
 * into the appropriate buffer, split into two bytes if necessary. This entire
 * ISR currrently takes 1.2usec to execute.
 */
void adc0_isr() {
    #if DEBUG_ON
    digitalWriteFast(DEBUG_PIN_1, HIGH);
    #endif

    // Switch buffer if necessary
    if(buffCounter == BUFFER_SIZE && currentBuffer == 0) {
        buffCounter = 0;
        currentBuffer = 1;
        buff0Ready = true;
    }
    else if(buffCounter == BUFFER_SIZE && currentBuffer == 1) {
        buffCounter = 0;
        currentBuffer = 0;
        buff1Ready = true;
    }

    // Write to the propper buffer (8-bit Readings!)
    if (currentBuffer == 0) { buff0[buffCounter++] = uint16_t(adc->readSingle()); }
    if (currentBuffer == 1) { buff1[buffCounter++] = uint16_t(adc->readSingle()); }

    #if DEBUG_ON
    digitalWriteFast(DEBUG_PIN_1, LOW);
    #endif
}

/**
 * PDB interrupt
 * This interrupt is called when the PDB starts an ADC sample. The starting of
 * a sample is automatic, so the only code needed here is to clear the PDB flag.
 * Once the sample is done the ADC will trigger an interrupt, currently each
 * Sample is ready in 1.72usec
 */
void pdb_isr(void) {
    #if DEBUG_ON
    digitalWriteFast(DEBUG_PIN_2, HIGH);
    #endif

    // Clears the interrupt flag from the PDB
    PDB0_SC &=~PDB_SC_PDBIF;

    #if DEBUG_ON
    digitalWriteFast(DEBUG_PIN_2, LOW);
    #endif
}
 

Attachments

  • main.cpp
    6.8 KB · Views: 247
Last edited:
You should always shield analogue signal wires. Also decouple the microphone VCC to GND at the device

Buff0/1 should be volatile as your interrupt changes them

If you want high speed I'd trigger a DMA to transfer the ADC0 output into a circular buffer and interrupt on complete. You could use two in a ping pong configuration
Scratch that just DMA your ADC data directly into the USB Serial FIFO. Trigger it off ADC complete

I'm not sure if Serial.print() triggers an interrupt but it might be worth setting ADC0_ISR to have the highest priority. What frequency is that noise?

Try using the CODE tags so people don't need to download files


Just my two cents but normally for double buffering the pointers are swapped in the main loop(). This reduces the processing done in the ISR() routine and allows you to instinctively know which buffer to use as front[] will always be the complete buffer and back[] will always be the one being updated

Also welcome to the forum ;)


EDIT -
Why are you buffering your data before transmission? Is USB serial too slow for you?
The USB buffer is 64bytes large so if you try to write more than that Serial.print() will block until the buffer is sent. Reading on the topic over here
 
Last edited:
Everything up to the ADC pins is being designed by another student and it is very nice and shielded. The sensor we are working with now is a hydrophone to record echolocation clicks, but they want it designed very generically so it can be used for other bio-acoustic projects. The other student is still getting the analog board built so that is why I am testing with the adafruit mic. The noise seems to be white noise from 0-20kHz, but the frequency of its occurrence is 18Hz, which matches exactly with what one would expect given the current sample rate and buffer size is cross-talk from the USB is the source. I scoped the microphone signal and the 18Hz noise isn't present.

Thanks for the advice on the code as well :) this is my first time using a processor with DMA so I had planned on using that in the my next iteration but I am still researching all the details on how to implement it. putting it straight into the USB FIFO sounds awesome, I will have to look into that. The major thing limiting my sample rate right now is the Serial.write() call which seems to block for 2.5us (i think). if the adc interrupt collides with a USB write everything just stops.
 
I will put the code inline next time, just thought it was kind of long :)

The USB writes seem to take 2.5us no matter how big a buffer i send it. I don't think I ever tried just sending it exactly 64bytes before, I will experiment with that.
 
Does it help to put the green/signal wire directly to pin 16? That takes out a piece of breadboard between the +/- power, one connection, and that black jumper wire.

The pic looks like AGND is wired to "+" on breadboard? is the Teensy shifted up one?
 
Give this a try perhaps?
Code:
/**
 * TeensyADC - High speed audio sampling with the teeny 3.2 ARM board This code samples the ADC on the teensy3.2 at
 *   the highest stable rate possible and dumps that data out the USB port using a double buffer. The purpose of
 *   this is for high speed bio-acustics, where most of the processing will be done on a second board
 *   (intel edison at the moment)
 *
 *   Enable the debug and hook pin 2 and 3 to an oscilloscope to watch the timing. Further optimizations
 *   Could be possible to increase speed. As well as analog signal processing on
 *   the input before sampling.
 *
 * @author Ethan Slattery
 * @contact Ethan.Slattery@yahoo.com
 * @date 15-July-2016
 */
#include <ADC.h>

#define BIT_DEPTH 12                // 13-bit usable ADC sample, Mask with 0x1FFF (8, 10, 12 or 16)
#define SAMPLE_AVERAGES 1           // Increases sample time linearly (0, 4, 8, 16 or 32)
#define SAMPLE_RATE_HZ 150000       // Highest stable sample rate at the moment is 160000, which leaves a 2.6us gap for USB wich takes 2.5us
#define HEADER_SIZE = 2;            // Size of the header buffer
#define BUFFER_SIZE 64-HEADER_SIZE  // Size of the buffer in samples - the header. This should total 64 bytes

#define DEBUG_ON 0              // enables the debug GPIO (disable for depoyment)
#define DEBUG_PIN_1 2           // GPIO pin for timing the read ISR
#define DEBUG_PIN_2 3           // GPIO pin for timing the PDB frequency
#define LED_PIN LED_BUILTIN     // Teensy 3.2 Built in LED pin
#define READ_PIN A2             // Pin for sampling the audio input

//elapsedMicros benchmark1;       // Benchmark timer, auto increasing obj :)
ADC *adc = new ADC();             // The ADC object

/*** GLOBAL DATA VARIABLES ***/
volatile uint16_t   buffer[BUFFER_SIZE];                    // Buffer for the samples to send over USB
uint8_t             header[HEADER_SIZE]  = {0xFF, 0xFF};    // Header bytes for delimiting packets
volatile uint8_t    bufferReady = false;

void setup(void) {
    // Set all the pin modes
    pinMode(LED_PIN, OUTPUT);      // LED PIN
    pinMode(DEBUG_PIN_1, OUTPUT);  // hardware debug pin
    pinMode(DEBUG_PIN_2, OUTPUT);  // hardware debug pin
    pinMode(READ_PIN, INPUT);      // ADC sampling for hydrophone

    // Start serial output, 12Mbit/sec no matter what over teensy USB
    Serial.begin(115200);

    /*** Setup the ADC samples ***/
    adc->setAveraging(SAMPLE_AVERAGES);
    adc->setResolution(BIT_DEPTH);
    adc->setReference(ADC_REF_3V3);

    // Set the conversion and sampling speed of the single ADC measurements
    // Options: ADC_VERY_LOW_SPEED, ADC_LOW_SPEED, ADC_MED_SPEED,
    //          ADC_HIGH_SPEED_16BITS, ADC_HIGH_SPEED or ADC_VERY_HIGH_SPEED
    adc->setConversionSpeed(ADC_HIGH_SPEED);
    adc->setSamplingSpeed(ADC_HIGH_SPEED);

    // Enable intterrupts on the ADC module (ADC_0 or ADC_1)
    adc->enableInterrupts(ADC_0);

    // Perform one read to burn in settings
    adc->analogRead(READ_PIN, ADC_0);

    // Start the PDB timer at the given freq.
    adc->adc0->stopPDB();
    adc->adc0->startPDB(SAMPLE_RATE_HZ);

    // Get rid of that pesky unused pdb_isr()... I think this may work no idea
    NVIC_DISABLE_IRQ(pdb_isr);

    // Lets give that ADC irq the high priority it deserves
    NVIC_SET_PRIORITY(IRQ_ADC0, 0); // 0 = highest priority

    // slight delay to let everything settle and flash light to show setup is complete
    digitalWrite(LED_BUILTIN, HIGH);
    delay(500);
    digitalWrite(LED_BUILTIN, LOW);
    delay(250);

    while(!Serial){}

    // blink LED to show serial is connected and sampling starts
    digitalWrite(LED_BUILTIN, HIGH);
    delay(250);
    digitalWrite(LED_BUILTIN, LOW);

} // END of setup()

void loop(void) {

    // Load the header into the USB fifo
    Serial.write(header, HEADER_SIZE);

    while(!bufferReady) {} // Wait for the buffer to be filled
    Serial.write(reinterpret_cast<char *>(buffer), BUFFER_SIZE);
    bufferReady = false;

    //Serial.flush() Maybe use this just to ensure the data has been sent

}

/**
 * ADC Interrupt Service Routine
 * This ISR is called once the ADC (triggered by the PDB) is done with it's
 * measurement. The appropriate buffer is then selected and the sample is placed
 * into the appropriate buffer, split into two bytes if necessary. This entire
 * ISR currrently takes 1.2usec to execute.
 */
void adc0_isr() {
    static uint8_t buffCounter = 0; // Keeps track of the samples in buffer

    #if DEBUG_ON
    digitalWriteFast(DEBUG_PIN_1, HIGH);
    #endif

    buffer[buffCounter++] = uint16_t(adc->readSingle());

    // Switch buffer if necessary
    if(buffCounter >= BUFFER_SIZE/2) {
        buffCounter = 0;
        bufferReady = true;
    }

    #if DEBUG_ON
    digitalWriteFast(DEBUG_PIN_1, LOW);
    #endif
}

/**
 * PDB interrupt
 * This interrupt is called when the PDB starts an ADC sample. The starting of
 * a sample is automatic, so the only code needed here is to clear the PDB flag.
 * Once the sample is done the ADC will trigger an interrupt, currently each
 * Sample is ready in 1.72usec
 */
void pdb_isr(void) {
    #if DEBUG_ON
    digitalWriteFast(DEBUG_PIN_2, HIGH);
    #endif

    // Clears the interrupt flag from the PDB
    PDB0_SC &=~PDB_SC_PDBIF;

    #if DEBUG_ON
    digitalWriteFast(DEBUG_PIN_2, LOW);
    #endif
}

Looking at the source files I do believe Serial.write() is triggering an interrupt. You'd best have a higher priority


Here's that double buffering version I was referring to:
Code:
/**
 * TeensyADC - High speed audio sampling with the teeny 3.2 ARM board This code samples the ADC on the teensy3.2 at
 *   the highest stable rate possible and dumps that data out the USB port using a double buffer. The purpose of
 *   this is for high speed bio-acustics, where most of the processing will be done on a second board
 *   (intel edison at the moment)
 *
 *   Enable the debug and hook pin 2 and 3 to an oscilloscope to watch the timing. Further optimizations
 *   Could be possible to increase speed. As well as analog signal processing on
 *   the input before sampling.
 *
 * @author Ethan Slattery
 * @contact Ethan.Slattery@yahoo.com
 * @date 15-July-2016
 */
#include <ADC.h>

#define BIT_DEPTH 12                // 13-bit usable ADC sample, Mask with 0x1FFF (8, 10, 12 or 16)
#define SAMPLE_AVERAGES 1           // Increases sample time linearly (0, 4, 8, 16 or 32)
#define SAMPLE_RATE_HZ 150000       // Highest stable sample rate at the moment is 160000, which leaves a 2.6us gap for USB wich takes 2.5us
#define HEADER_SIZE = 2;            // Size of the header buffer
#define BUFFER_SIZE 64-HEADER_SIZE  // Size of the buffer in samples - the header. This should total 64 bytes

#define DEBUG_ON 0              // enables the debug GPIO (disable for depoyment)
#define DEBUG_PIN_1 2           // GPIO pin for timing the read ISR
#define DEBUG_PIN_2 3           // GPIO pin for timing the PDB frequency
#define LED_PIN LED_BUILTIN     // Teensy 3.2 Built in LED pin
#define READ_PIN A2             // Pin for sampling the audio input

//elapsedMicros benchmark1;       // Benchmark timer, auto increasing obj :)
ADC *adc = new ADC();             // The ADC object

/*** GLOBAL DATA VARIABLES ***/
uint8_t             header[HEADER_SIZE]  = {0xFF, 0xFF};    // Header bytes for delimiting packets
volatile uint8_t    bufferReady = false;

// Buffers for the samples to send over USB
volatile uint16_t buffer1[BUFFER_SIZE];
volatile uint16_t buffer2[BUFFER_SIZE];

// Buffer pointers
volatile uint16_t *volatile frontBuffer = &buffer1[0];
volatile uint16_t *volatile rearBuffer  = &buffer2[0];

void setup(void) {
    // Set all the pin modes
    pinMode(LED_PIN, OUTPUT);      // LED PIN
    pinMode(DEBUG_PIN_1, OUTPUT);  // hardware debug pin
    pinMode(DEBUG_PIN_2, OUTPUT);  // hardware debug pin
    pinMode(READ_PIN, INPUT);      // ADC sampling for hydrophone

    // Start serial output, 12Mbit/sec no matter what over teensy USB
    Serial.begin(115200);

    /*** Setup the ADC samples ***/
    adc->setAveraging(SAMPLE_AVERAGES);
    adc->setResolution(BIT_DEPTH);
    adc->setReference(ADC_REF_3V3);

    // Set the conversion and sampling speed of the single ADC measurements
    // Options: ADC_VERY_LOW_SPEED, ADC_LOW_SPEED, ADC_MED_SPEED,
    //          ADC_HIGH_SPEED_16BITS, ADC_HIGH_SPEED or ADC_VERY_HIGH_SPEED
    adc->setConversionSpeed(ADC_HIGH_SPEED);
    adc->setSamplingSpeed(ADC_HIGH_SPEED);

    // Enable intterrupts on the ADC module (ADC_0 or ADC_1)
    adc->enableInterrupts(ADC_0);

    // Perform one read to burn in settings
    adc->analogRead(READ_PIN, ADC_0);

    // Start the PDB timer at the given freq.
    adc->adc0->stopPDB();
    adc->adc0->startPDB(SAMPLE_RATE_HZ);

    // Get rid of that pesky unused pdb_isr()... I think this may work no idea
    NVIC_DISABLE_IRQ(pdb_isr);

    // Lets give that ADC irq the high priority it deserves
    NVIC_SET_PRIORITY(IRQ_ADC0, 0); // 0 = highest priority

    // slight delay to let everything settle and flash light to show setup is complete
    digitalWrite(LED_BUILTIN, HIGH);
    delay(500);
    digitalWrite(LED_BUILTIN, LOW);
    delay(250);

    while(!Serial){}

    // blink LED to show serial is connected and sampling starts
    digitalWrite(LED_BUILTIN, HIGH);
    delay(250);
    digitalWrite(LED_BUILTIN, LOW);

} // END of setup()

void loop(void) {

    // Load the header into the USB fifo
    Serial.write(header, HEADER_SIZE);

    while(!bufferReady) {} // Wait for the buffer to be filled
    Serial.write(reinterpret_cast<char *>(frontBuffer), BUFFER_SIZE);
    bufferReady = false;

    //Serial.flush() Maybe use this just to ensure the data has been sent

}

/**
 * ADC Interrupt Service Routine
 * This ISR is called once the ADC (triggered by the PDB) is done with it's
 * measurement. The appropriate buffer is then selected and the sample is placed
 * into the appropriate buffer, split into two bytes if necessary. This entire
 * ISR currrently takes 1.2usec to execute.
 */
void adc0_isr() {
    static uint8_t buffCounter = 0; // Keeps track of the samples in buffer

    #if DEBUG_ON
    digitalWriteFast(DEBUG_PIN_1, HIGH);
    #endif

    rearBuffer[buffCounter++] = uint16_t(adc->readSingle());

    // Switch buffer if necessary
    if(buffCounter >= BUFFER_SIZE/2) {
        buffCounter = 0;
        bufferReady = true;

        // Swap the buffers around
        if(frontBuffer == &buffer1) {
            frontBuffer = &buffer2;
            rearBuffer  = &buffer1;
        } else {
            frontBuffer = &buffer1;
            rearBuffer  = &buffer2;
        }
    }

    #if DEBUG_ON
    digitalWriteFast(DEBUG_PIN_1, LOW);
    #endif
}

/**
 * PDB interrupt
 * This interrupt is called when the PDB starts an ADC sample. The starting of
 * a sample is automatic, so the only code needed here is to clear the PDB flag.
 * Once the sample is done the ADC will trigger an interrupt, currently each
 * Sample is ready in 1.72usec
 */
void pdb_isr(void) {
    #if DEBUG_ON
    digitalWriteFast(DEBUG_PIN_2, HIGH);
    #endif

    // Clears the interrupt flag from the PDB
    PDB0_SC &=~PDB_SC_PDBIF;

    #if DEBUG_ON
    digitalWriteFast(DEBUG_PIN_2, LOW);
    #endif
}
 
Last edited:
AGND is wired to GND (redundant i think), Vin is 4.5V, 3.3V goes to the mic, and AGND to the mic as well. there is a .1uf cap between AGND and 3.3V since there was a small ripple in the teensy voltage when I was troubleshooting this bigger problem. Just tried hooking the mic right into pin 16, didn't help :(
 
AGND and GND are separated by a inductive noise choke on the Teensy board, so I would think they should NOT be connected, to keep the mic AGND away from the digital noise on GND
 
Holy cow, your code is great. The IDC_ISR runs in 540ns (mine ran in 1.2us) so between that and the interrupt priority I am now sampling at 400ksps! My pie in the sky goal was 300ksps so that is great (the current animal vocalizes at around 150kHz), and gives me hope for my attempts at a tiny amount of on-board processing later. I've never had to worry about speeds like this before so seeing how you organized those two examples is really awesome to learn from. Thanks again.

Getting rid of the PDB_ISR didn't work, so Its just commented out for now. No big deal since I am getting the timing I want and its actually nice to see on my scope when the PDB gets triggered.

On line 95 it needs to be
Code:
Serial.write(const_cast<char *>(reinterpret_cast<volatile char *>(buffer)), BUFFER_SIZE);
which is some casting magic I had never seen before, but works great. I had tried making the buffer volatile before but got errors I didn't understand. Now I do, so another great learning point :)

I haven't tested the noise yet, my mentor has me reading some papers right now. But I am super excited about the speed increase! (yellow is the pdb_isr, blue is the adc_isr)
scope1.JPG
 
UPDATE:
USB noise still present, in fact with the new code it seems to present itself as a constant hum at 3kHz, 6kHz, 9kHz, 12kHz... etc when I look at spectogram in audacity. Probably due to a ton of small writes versus a bunch all at once a few times a second. Still not sure why the USB would cause so much noise, maybe I should look into an external ADC communicating over SPI or parallel?

Also, scope shows 192k sample rate, but on the computer when I play the audio my voice is very high pitched. Sounds more normal when I play it back at 96000Hz... need to figure out some way to test if I am losing samples along the way. Maybe on the computer side?
 
Last edited:
Getting good low-noise analog signals into any digital system (including a Teensy board) can be a challenge. The cleanest systems use a separate PCB with the ADC isolated from everything else, and careful attention paid to where the ground currents flow. Remember what you call "Ground" is not 0 volts everywhere, if there is any current flowing through the system you always have the I*R voltage difference.

The first thing to try is to make a single-point ground, iin other words all your external circuits return to ground at the very same pin. Also, having your ADC Vref the same 3.3V as your processor supply power is not good practice for stable low-noise ADC operation due to the voltage noise on the power bus as the processor current spikes and fluctuates with time.
 
If your values are less than 1.2V high then maybe you could use the internal reference, that should be more isolated.
Or even an external reference that is isolated from the USB power somehow.
 
I'm glad this has given you the speed upgrade you were looking for! It's always good to keep your time specific code at a high priority and to reduce it's processing time as much as possible

It is very unlikely you are dropping packets due to the way the USB transmission works. Your samples are not being transmitted at fixed intervals and the data received does not properly represent the time at which they were collected.
You either need to timestamp the data ,which would be slow, or you can take another approach. What I would do would be to measure the average time between Serial data receives. Then when new data arrives we know we have 31 readings that have been collected in that time. This is because we have 64 bytes received, 2 of which are the header and the final 62 are halfed as they are 16bit values. Now we can assume that these 31 readings have been taken at even intervals across the average time between Serial data reception. So put in a simplier algebraic form:
Let T = average time between Serial transmission
Let n = number of samples in that time (31)
Therefore time between samples are T(n) = T/n

This can then be used to properly reconstruct the data in the correct time space it was collected in

Again I will mention the requirement for shielded cables from the microphone to the device. You should also put a decoupling capacitor on the microphone power supply itself on the same PCB as it. Many recommend putting a inductor in series with the power supply to isolate the high frequency digital signal from the analogue circuit. This is effectively already done on the Teensy schematic. Do yourself a favour and if you have a decent oscilloscope attach one probe to AREF and see if there are any fluctuations

Another programming technique that can be useful to avoid that strange cast you've had to do is:
Code:
union {
    uint16_t array[BUFFERSIZE/2];
    uint8_t  arrayBytes[BUFFERSIZE];
};
Now you can access individual bytes of array using arrayBytes[]. Some may argue this is bad practice though. Having a named union would make accidental data change less likely


To remove the interrupt of the PDB see Page 755 of the datasheet. By clearing bit PDBIE in the register PDBx_SC you can disable the interrupt
Code:
PDB0_SC &= ~(1<<5)
 
Last edited:
Is any part of the system (besides Teensy) getting external power? If so, you might have a ground loop, where ground current unexpectedly flows through sensitive ground paths and ends up creating a false signal. Usually the quick way to test involves running various parts from completely isolated batteries, to completely eliminate the possibility of unexpected ground currents. If it's quiet with (impractical) batteries running all parts, then at least you know what the problem is when you try to find a more practical solution.
 
I figured it would be unlikely to drop packets over USB, so maybe the buffer is losing samples? in the single buffer setup some samples could be overwritten during a write if it is going really fast. Theoretically if I am getting evenly spaced samples at 96ksps (as I can see on my scope) and all the samples are making it to the computer, then as long as I play it back at 96ksps it should be good. unless I am missing something. This is basically audio, just really fast audio (silly biologists and their animals making high pitched noises!)

That use of a union is also very cool, much more interesting than the reasons I learned in CS class. I read up on all the reasons it is bad and some back and forth but still sounds fun. Also thanks for the datasheet reference, I have been reading it, but it is a big tomb to fully consume in a week or two!

I had a MCP3008 breakout laying around so I am going to integrate that today and see if that solves the problem. If so I may just move to an external ADC that meets our requirements and just use the teensy as an SPI FIFO for now.
 
Paul: I am already re-wired for the external ADC but I will whip up some battery packs and try that out, thanks for the advice. In the final deployed configuration there is only one battery pack though. I will talk to the EE about it, see what he thinks. I am just curious at this point, and it would certainly be nice to keep the BOM down if I didn't need an external ADC.
 
...as long as I play it back at 96ksps it should be good. unless I am missing something
... woops, yes you are quite right! I was thinking of data plotting for some reason

maybe the buffer is losing samples?
More than possible if your ADC interrupt is triggering fast enough, try the double buffered version

Yeah you don't see unions discussed so much in education these days. It's non-portable sure but I never find myself changing microprocessor family mid project


I'd strongly recommend cutting the VUSB line on the Teensy and giving the Teensy it's own power supply isolated from the PC. Since the issues occur during USB transfers it makes sense to look there. I've had a lot of issues with connections to the PC causing noise. Optocouplers anyone?
Have you tried analogReference(INTERNAL)? I think this would help a lot

There's a gold mine of information in this thread
 
Last edited:
Status
Not open for further replies.
Back
Top