davis_nanogroup
Member
Hello! I have been experiencing some seemingly periodic noise spikes (~15mV) when using the ADC of the Teensy 3.5. (Code below.)
For my project, I take one row of data every 10 milliseconds. Each row is composed of 131 columns: the first columns is the microseconds elapsed since the previous row was taken (used for debugging and such), and the other 130 columns are the results of ADC conversions. Each ADC data column is taken 10 microseconds after the previous column (i.e. the conversions across a row are 10 microseconds apart). The data is stored in two two-dimensional arrays, and essentially while one array is being filled the other is being written to an SD card to allow continuous data collection and saving. All this seems to be working as intended.
However, I’ve noticed when analyzing the data that there are significant spikes in noise about every 20 seconds (or 130 columns * ~2000 rows ~= 260,000 recorded ADC conversions). See attached graph:

I provide the number of recorded ADC conversions because it seems that the noise is periodic in number of recorded conversions rather than in time. For example, when I reduce each row’s columns of ADC conversions from 130 to 65 (reducing COLUMNS_OF_DATA to 66), the temporal period of the noise shifts to ~40 seconds:

The data for these graphs was taken using the version of my code provided below. Each graph is just one column over 30,000 rows, but the noise is visible in all columns. The only things connected to the Teensy 3.5 were:
Power supply connected to ADC pin (A0 in this case) and ground (problem persists when converting voltage from other sources)
0.2 uF, 2 uF, and 20 uF decoupling capacitors between ADC input pin (Teensy pin A0) and ground
USB power from my computer (problem persists when Teensy is powered from Vin pin using batteries)
One thing that is especially confusing to me about this noise is that if I understand my code correctly I am making an ADC conversion every 10 microseconds whether I save it to a data array or not (i.e. once I begin making ADC conversions triggered by the PDB, I never stop or change the frequency from the specified 100 kHz for the duration of the program)—so why does changing the number of columns of recorded ADC conversions per row impact the period of the noise? Also, the exact “shape” (position of first instance from the beginning of data, width, etc) of the noise is not extremely consistent run to run; there have even been a few times that I have taken data and not had this noise occur despite identical software and hardware.
I would love some help in understanding possible sources of and solutions to this problem. Let me know if I can provide any more information, and thank you so much for your help!
For my project, I take one row of data every 10 milliseconds. Each row is composed of 131 columns: the first columns is the microseconds elapsed since the previous row was taken (used for debugging and such), and the other 130 columns are the results of ADC conversions. Each ADC data column is taken 10 microseconds after the previous column (i.e. the conversions across a row are 10 microseconds apart). The data is stored in two two-dimensional arrays, and essentially while one array is being filled the other is being written to an SD card to allow continuous data collection and saving. All this seems to be working as intended.
However, I’ve noticed when analyzing the data that there are significant spikes in noise about every 20 seconds (or 130 columns * ~2000 rows ~= 260,000 recorded ADC conversions). See attached graph:

I provide the number of recorded ADC conversions because it seems that the noise is periodic in number of recorded conversions rather than in time. For example, when I reduce each row’s columns of ADC conversions from 130 to 65 (reducing COLUMNS_OF_DATA to 66), the temporal period of the noise shifts to ~40 seconds:

The data for these graphs was taken using the version of my code provided below. Each graph is just one column over 30,000 rows, but the noise is visible in all columns. The only things connected to the Teensy 3.5 were:
Power supply connected to ADC pin (A0 in this case) and ground (problem persists when converting voltage from other sources)
0.2 uF, 2 uF, and 20 uF decoupling capacitors between ADC input pin (Teensy pin A0) and ground
USB power from my computer (problem persists when Teensy is powered from Vin pin using batteries)
One thing that is especially confusing to me about this noise is that if I understand my code correctly I am making an ADC conversion every 10 microseconds whether I save it to a data array or not (i.e. once I begin making ADC conversions triggered by the PDB, I never stop or change the frequency from the specified 100 kHz for the duration of the program)—so why does changing the number of columns of recorded ADC conversions per row impact the period of the noise? Also, the exact “shape” (position of first instance from the beginning of data, width, etc) of the noise is not extremely consistent run to run; there have even been a few times that I have taken data and not had this noise occur despite identical software and hardware.
I would love some help in understanding possible sources of and solutions to this problem. Let me know if I can provide any more information, and thank you so much for your help!
Code:
#define FILENAME "adc_0.csv"
//1 timing + 130 ADC conversions
#define COLUMNS_OF_DATA 131 // One "sample" is composed of this many columns
#include <ADC.h>
#include <SdFat.h>
SdFatSdio SD;
#define RESOLUTION 16 // Sets bits of resolution in ADC output (2^number)
#define INTEGERMAX float((2<<(RESOLUTION-1))-1) // 2^RESOLUTION-1 for accurate voltage readouts
#define ADC_CONVERSION_FREQUENCY 100000 //for 100kHz ADC triggering
#define PDB_DELAY F_BUS/ADC_CONVERSION_FREQUENCY * 1/8 //this sets the time delay as a fraction of ADC_CONVERSION_FREQUENCY between the rising edge of the FTM clock and the beginning of the ADC conversion
#define SAMPLES_BUFFER_SIZE 150 //number of "samples"
volatile uint16_t data_buffer_true[SAMPLES_BUFFER_SIZE][COLUMNS_OF_DATA];
volatile uint16_t data_buffer_false[SAMPLES_BUFFER_SIZE][COLUMNS_OF_DATA];
volatile bool buffer_switch = true;
volatile uint16_t sample_index_true = 0;
volatile uint16_t sample_index_false = 0;
//Used to give first column data, microseconds between rows
unsigned long this_time = 0;
unsigned long last_time = 0;
const int AD_VIDEO = A0; //Input signal to be converted by ADC
const int WRITE_PIN_CLOCK = A7; // Clock written to this pin
const int WRITE_PIN_RESET = A9; // Reset written to this pin
const int BEGIN_DATA_COLLECTION = 3; //This is the number of clock cycles after the reset signal goes low before we start collecting data
const int END_DATA_COLLECTION = COLUMNS_OF_DATA + BEGIN_DATA_COLLECTION;
const uint8_t ADC_INTERRUPT_PRIORITY = 64; // Needs to be smaller than 128; Default priority is 128, higher priorities are lower numbers, steps of sixteen
const double CLOCK_PERIOD = 10.0; // Clock period in microseconds (10.0 = 10.0us)
volatile bool resetPulseValue = true; // Boolean to set reset high and low
volatile uint16_t readCount; //Keep track of how many ADC conversions we do in a row
volatile uint16_t num_times_called = 0; //Used in ftm0_isr (ftm0_isr gets called every clock cycle, but things only happen on Reset rising and falling edges)
volatile bool permission_to_write_to_sd; //Only write to SD card when not collecting video data, so this value keeps track of when writing is allowed
volatile bool begin_write_to_sd = false; //Indicates when one of the buffers is full and data needs to be written to SD card
volatile uint32_t bufferIndex = 1; //increment to fill buffers, let timing column be first
volatile uint16_t bufvalue = 0; //ADC readings temporarily stored here
ADC *adc = new ADC(); // Create ADC object
File myFile; //Create SD file object
//this function is very similar to the startPDB function in ADC_Module, but it is not the same
void PDBstart(uint32_t freq) {
if (!(SIM_SCGC6 & SIM_SCGC6_PDB)) { // setup PDB
SIM_SCGC6 |= SIM_SCGC6_PDB; // enable pdb clock
}
if(freq>F_BUS) return; // too high
if(freq<1) return; // too low
// mod will have to be a 16 bit value
// we detect if it's higher than 0xFFFF and scale it back accordingly.
uint32_t mod = (F_BUS / freq);
uint8_t prescaler = 0; // from 0 to 7: factor of 1, 2, 4, 8, 16, 32, 64 or 128
uint8_t mult = 0; // from 0 to 3, factor of 1, 10, 20 or 40
// if mod is too high we need to use prescaler and mult to bring it down to a 16 bit number
const uint32_t min_level = 0xFFFF;
if(mod>min_level) {
if( mod < 2*min_level ) { prescaler = 1; }
else if( mod < 4*min_level ) { prescaler = 2; }
else if( mod < 8*min_level ) { prescaler = 3; }
else if( mod < 10*min_level ) { mult = 1; }
else if( mod < 16*min_level ) { prescaler = 4; }
else if( mod < 20*min_level ) { mult = 2; }
else if( mod < 32*min_level ) { prescaler = 5; }
else if( mod < 40*min_level ) { mult = 3; }
else if( mod < 64*min_level ) { prescaler = 6; }
else if( mod < 128*min_level ) { prescaler = 7; }
else if( mod < 160*min_level ) /*16*10*/ { prescaler = 4; mult = 1; }
else if( mod < 320*min_level ) /*16*20*/ { prescaler = 4; mult = 2; }
else if( mod < 640*min_level ) /*16*40*/ { prescaler = 4; mult = 3; }
else if( mod < 1280*min_level ) /*32*40*/ { prescaler = 5; mult = 3; }
else if( mod < 2560*min_level ) /*64*40*/ { prescaler = 6; mult = 3; }
else if( mod < 5120*min_level ) /*128*40*/ { prescaler = 7; mult = 3; }
else /*frequency too low*/ { return; }
mod >>= prescaler;
if(mult>0) {
mod /= 10;
mod >>= (mult-1);
}
}
adc->adc0->setHardwareTrigger(); // trigger ADC with hardware
PDB0_IDLY = 1; // the pdb interrupt happens when IDLY is equal to CNT+1
PDB0_MOD = (uint16_t)(mod-1);
// software trigger enable PDB PDB interrupt continuous mode load immediately
PDB0_SC = PDB_SC_TRGSEL(8) | PDB_SC_PDBEN | PDB_SC_PDBIE | PDB_SC_CONT | PDB_SC_LDMOD(0) | PDB_SC_PRESCALER(prescaler) | PDB_SC_MULT(mult) | PDB_SC_LDOK; // load all new values
PDB0_SC &= ~PDB_SC_CONT;
PDB0_CH0C1 = 0x0101; // enable pretrigger 0 (SC1A)
}
void setup() {
pinMode(AD_VIDEO, INPUT); // Set up measurement pin as an input
digitalWrite(AD_VIDEO, LOW);
pinMode(WRITE_PIN_RESET, OUTPUT); // Set up reset pin as an output
//---------------------------------------------------------------------------------------------------------------------
//SD card reader setup
SD.begin();
myFile = SD.open(FILENAME, O_WRITE | O_CREAT);
//---------------------------------------------------------------------------------------------------------------------
//ADC 0 setup
adc->adc0->setReference(ADC_REFERENCE::REF_3V3); //Voltage reference 0 - 3.3 V
adc->adc0->setAveraging(8); //Set number of averages (ADC_SC3_AVGE = 8 which means averaging 8 samples)
adc->adc0->setResolution(RESOLUTION); //Set bits of resolution (ADC_CFG1_MODE = 11 if RESOLUTION == 16 which means single-ended 16-bit conversion)
adc->adc0->setConversionSpeed(ADC_CONVERSION_SPEED::VERY_HIGH_SPEED); //Set conversion speed (ADC_CFG2_ADHSC = 1 which means high speed congiguration, ADC_CFG1_ADLPC = 0 which means normal power, ADC_CFG2_ADACKEN = 0 which means disable async. clk)
adc->adc0->setSamplingSpeed(ADC_SAMPLING_SPEED::VERY_HIGH_SPEED); //Set sampling speed (ADC_CFG1_ADLSMP = 0 which means short sample time)
adc->adc0->enableInterrupts(adc0_isr, ADC_INTERRUPT_PRIORITY); //Enables conversion complete interrupts (ADC_SC1_AIEN = 1 which means when COCO bit is high, interrupt is asserted)
//---------------------------------------------------------------------------------------------------------------------
//PIT module setup
SIM_SCGC6 |= SIM_SCGC6_PIT; //Enable the clock gate to PIT module -- for some reason this is needed even though we aren't using the PIT module
//---------------------------------------------------------------------------------------------------------------------
//FTM 0 setup
analogWriteFrequency(WRITE_PIN_CLOCK, 100000); //Set up PWM signal
analogWrite(WRITE_PIN_CLOCK, 25); //Set up PWM signal to be high for 25 counts (period is 256 counts)
FTM0_EXTTRIG = FTM_EXTTRIG_INITTRIGEN; //Enable external trigger on FTM0 Channel 0 for PDB -> corresponds to pin 23
FTM0_CONF = FTM_CONF_NUMTOF(24); //Trigger interrupt once every 25 times the timer overflows (set to 24 because the setting is "trigger once, then don't trigger x times")
FTM0_SC = FTM_SC_CLKS(1) | FTM_SC_PS(0) | FTM_SC_TOIE; //update status register > sys clock | div by 1 | enable TOF interrupt
NVIC_SET_PRIORITY(IRQ_FTM0, 96); //Needs to be smaller than 128; Default priority is 128, higher priorities are lower numbers, steps of sixteen
NVIC_ENABLE_IRQ(IRQ_FTM0); //Enable FTM0 interrupt controller
//---------------------------------------------------------------------------------------------------------------------
//PDB setup
adc->adc0->stopPDB();
adc->adc0->startSingleRead(AD_VIDEO); //call this to setup everything before the pdb starts, differential is also possible
PDBstart(ADC_CONVERSION_FREQUENCY); //calls function at the top of this file
PDB0_IDLY = PDB_DELAY; //This delay was chosen to be 3/4 (or 5/8 if averaging) of clock period so PDB will trigger ADC 3/4 (or 5/8) of clock period after rising edge of clock
PDB0_CH0DLY0 = PDB_DELAY; //This delay was chosen to be 3/4 (or 5/8 if averaging) of clock period so PDB will trigger ADC 3/4 (or 5/8) of clock period after rising edge of clock
PDB0_SC |= PDB_SC_LDOK; //load everything we just set into the registers
//---------------------------------------------------------------------------------------------------------------------
//This line fixes mysterious problems we were having before; also present above
PIT_MCR = 0x00; //Activates PIT module
//Initialize values
for (uint16_t i = 0; i < SAMPLES_BUFFER_SIZE; i++) {
for (uint16_t j = 0; j < COLUMNS_OF_DATA; j++) {
data_buffer_true[i][j] = 0;
data_buffer_false[i][j] = 0;
}
}
}
//SD card writes are performed in this function -- They have lower priority than everything else so they get interrupted when needed and paused when Video data is getting collected
//If SD writes and Video data collection happen at the same time, Video data is very noisy (amplitude 20 mV when ADC resolution is set to 16-bit)
void loop() {
if (begin_write_to_sd) { //When either of the sample buffers is full
begin_write_to_sd = false;
if (!buffer_switch) { //Write from the _true buffer
sample_index_true = 0;
for (uint16_t sample = 0; sample < SAMPLES_BUFFER_SIZE; sample++) {
for (uint16_t column = 0; column < COLUMNS_OF_DATA; column++) {
while (!permission_to_write_to_sd) {} //If ADC data is getting read, wait until it finishes
myFile.print(data_buffer_true[sample][column]);
myFile.print(", ");
}
while (!permission_to_write_to_sd) {} //If ADC data is getting read, wait until it finishes
myFile.println();
}
}
else { //Write from the _false buffer
sample_index_false = 0;
for (uint16_t sample = 0; sample < SAMPLES_BUFFER_SIZE; sample++) {
for (uint16_t column = 0; column < COLUMNS_OF_DATA; column++) {
while (!permission_to_write_to_sd) {} //If ADC data is getting read, wait until it finishes
myFile.print(data_buffer_false[sample][column]);
myFile.print(", ");
}
while (!permission_to_write_to_sd) {} //If ADC data is getting read, wait until it finishes
myFile.println();
}
}
while (!permission_to_write_to_sd) {} //If video data is getting read, wait until it finishes
myFile.flush(); //Saves what was written
} //End write conditional
} //End loop()
//This interrupt is called once per ADC conversion, triggered by completion of ADC conversion -- this interrupt has highest priority
//Video data and MAX data are written to buffers in this function
void adc0_isr() {
bufvalue = ADC0_RA; //Read ADC0 data register
if ((readCount > BEGIN_DATA_COLLECTION) && (readCount < END_DATA_COLLECTION)) {
++readCount;
if (buffer_switch) {
data_buffer_true[sample_index_true][bufferIndex] = bufvalue;
}
else {
data_buffer_false[sample_index_false][bufferIndex] = bufvalue;
}
++bufferIndex;
}
else if (readCount < END_DATA_COLLECTION) {
++readCount;
}
else if (readCount == END_DATA_COLLECTION) {
this_time = micros();
if (buffer_switch) {
data_buffer_true[sample_index_true][0] = this_time - last_time; //Store time between rows in column 1
++sample_index_true;
if (sample_index_true >= SAMPLES_BUFFER_SIZE) {
buffer_switch = false; //Begin filling _false buffer
begin_write_to_sd = true; //Begin writing to SD card in main loop
}
}
else {
data_buffer_false[sample_index_false][0] = this_time - last_time;
++sample_index_false;
if (sample_index_false >= SAMPLES_BUFFER_SIZE) {
buffer_switch = true; //Begin filling _true buffer
begin_write_to_sd = true; //Begin writing to SD card in main loop
}
}
bufferIndex = 1;
last_time = this_time;
++readCount;
}
}
//This interrupt gets called once per 25 Clock periods, triggered by Clock rising edge -- num_times_called is incremented until it is time for either a rising or falling edge on Reset signal
//Reset signal is controlled in this function
void ftm0_isr() {
FTM0_SC &= ~FTM_SC_TOF; //clear module interrupt
++num_times_called;
if (!resetPulseValue && (num_times_called == 20)) {
permission_to_write_to_sd = false;
}
else if (!resetPulseValue && (num_times_called > 33)) { // If Reset signal is high
num_times_called = 0;
digitalWriteFast(WRITE_PIN_RESET, resetPulseValue);
resetPulseValue = !resetPulseValue; //Switch from high to low, so invert boolean value
readCount = 0;
}
else if (resetPulseValue && (num_times_called > 5)) { // Else if Reset signal is low
num_times_called = 0;
digitalWriteFast(WRITE_PIN_RESET, resetPulseValue);
resetPulseValue = !resetPulseValue; //Switch from low to high, so invert boolean value
permission_to_write_to_sd = true;
}
}