I am working on a project trying to sample 4 analog mics as fast as possible using a Teensy 4.
I was wondering if there was anyway that I could increase the samples/second I am getting. I am using Pedvide's ADC library and modified his analogReadIntervalTimer sketch to incorporate ADC1 as well. So i have 2 timers, the first calls two startSingleRead on pins A10 and A12 (belonging to different ADCs) which triggers an interrupt to record the value in a buffer and increase buffer count.
After a delay, (letting the conversion take place ~.8us) I call the second timer which calls startSingleRead on pins A11 and A13 which, once again, triggers an interrupt to record the value in a buffer and increase buffer count.
I am getting around 340KSPS (total loop of around 3us) but would like to have more if possible. I am running with 1 sample and 8 bit conversion on the fastest settings.
I know that continuous reading is faster than the single reads, can I incorporate that to speed this up? I tried to overclock the Teensy 4, but it slowed it down (might have something to do with the way the library is set up?). Open to any suggestions.
I was wondering if there was anyway that I could increase the samples/second I am getting. I am using Pedvide's ADC library and modified his analogReadIntervalTimer sketch to incorporate ADC1 as well. So i have 2 timers, the first calls two startSingleRead on pins A10 and A12 (belonging to different ADCs) which triggers an interrupt to record the value in a buffer and increase buffer count.
After a delay, (letting the conversion take place ~.8us) I call the second timer which calls startSingleRead on pins A11 and A13 which, once again, triggers an interrupt to record the value in a buffer and increase buffer count.
I am getting around 340KSPS (total loop of around 3us) but would like to have more if possible. I am running with 1 sample and 8 bit conversion on the fastest settings.
I know that continuous reading is faster than the single reads, can I incorporate that to speed this up? I tried to overclock the Teensy 4, but it slowed it down (might have something to do with the way the library is set up?). Open to any suggestions.
Code:
#include <ADC.h>
// and IntervalTimer
#include <IntervalTimer.h>
//const int ledPin = LED_BUILTIN;
const int delay_time = 800; //ns
const int period = 3; // us
const int readPin0 = A10;
const int readPin1 = A11;
const int readPin2 = A12;
const int readPin3 = A13;
const int readPeriod = 1000000; // us
ADC *adc = new ADC(); // adc object
IntervalTimer timer0, timer1; // timers
#define BUFFER_SIZE 500
uint16_t buffer_0[BUFFER_SIZE];
uint16_t buffer_0_count = 0;
uint32_t delta_time_0 = 0;
uint16_t buffer_1[BUFFER_SIZE];
uint16_t buffer_1_count = 0;
uint32_t delta_time_1 = 0;
uint16_t buffer_2[BUFFER_SIZE];
uint16_t buffer_2_count = 0;
uint32_t delta_time_2 = 0;
uint16_t buffer_3[BUFFER_SIZE];
uint16_t buffer_3_count = 0;
uint32_t delta_time_3 = 0;
elapsedMicros timed_read_elapsed;
int startTimerValue0 = 0, startTimerValue1 = 0;
void setup() {
pinMode(readPin0, INPUT);
pinMode(readPin1, INPUT);
pinMode(readPin2, INPUT);
pinMode(readPin3, INPUT);
Serial.begin(9600);
delay(1000);
timed_read_elapsed = 0;
///// ADC0 ////
adc->adc0->setAveraging(1); // set number of averages
adc->adc0->setResolution(8); // set bits of resolution
adc->adc0->setConversionSpeed(ADC_CONVERSION_SPEED::HIGH_SPEED); // change the conversion speed
adc->adc0->setSamplingSpeed(ADC_SAMPLING_SPEED::VERY_HIGH_SPEED); // change the sampling speed
///// ADC1 ////
adc->adc1->setAveraging(1); // set number of averages
adc->adc1->setResolution(8); // set bits of resolution
adc->adc1->setConversionSpeed(ADC_CONVERSION_SPEED::HIGH_SPEED); // change the conversion speed
adc->adc1->setSamplingSpeed(ADC_SAMPLING_SPEED::VERY_HIGH_SPEED); // change the sampling speed
Serial.println("Starting Timers");
// start the timers, if it's not possible, startTimerValuex will be false
startTimerValue0 = timer0.begin(timer0_callback, period);
//startTimerValue2 = timer2.begin(timer2_callback, period2);
// wait enough time for the first timer conversion to finish (depends on resolution and averaging),
// with 16 averages, 12 bits, and ADC_MED_SPEED in both sampling and conversion speeds it takes about 36 us.
delayNanoseconds(delay_time); // if we wait less than 36us the timer1 will interrupt the conversion
// initiated by timer0. The adc_isr will restart the timer0's measurement.
// You can check with an oscilloscope:
// Pin 14 corresponds to the timer0 initiating a measurement
// Pin 15 the same for the timer1
// Pin 16 is the adc_isr when there's a new measurement on readpin0
// Pin 17 is the adc_isr when there's a new measurement on readpin1
// Timer0 starts a comversion and 25 us later timer1 starts a new one, "pausing" the first, about 36 us later timer1's conversion
// is done, and timer0's is restarted, 36 us later timer0's conversion finishes. About 14 us later timer0 starts a new conversion again.
// (times don't add up to 120 us because the timer_callbacks and adc_isr take time to execute, about 2.5 us and 1 us, respectively)
// so in the worst case timer0 gets a new value in about twice as long as it would take alone.
// if you change the periods, make sure you don't go into a loop, with the timers always interrupting each other
startTimerValue1 = timer1.begin(timer1_callback, period);
//startTimerValue3 = timer3.begin(timer3_callback, period3);
adc->adc0->enableInterrupts(adc0_isr);
adc->adc1->enableInterrupts(adc1_isr);
Serial.println("Timers started");
delay(500);
}
int value = 0;
char c=0;
void loop() {
delayMicroseconds(readPeriod);
if(startTimerValue0==false) {
Serial.println("Timer0 setup failed");
}
if(startTimerValue1==false) {
Serial.println("Timer1 setup failed");
}
// See if we have a timed read test that finished.
if (delta_time_0)
{
Serial.println("timer0");
Serial.println(buffer_1_count);
printTimedADCInfo(ADC_0, buffer_0, delta_time_0);
}
if (delta_time_1)
{
Serial.println("timer1");
Serial.println(buffer_0_count);
printTimedADCInfo(ADC_0, buffer_1, delta_time_1);
}
if (delta_time_2)
{
Serial.println("timer2");
Serial.println(buffer_3_count);
printTimedADCInfo(ADC_1, buffer_2, delta_time_2);
}
if (delta_time_3)
{
Serial.println("timer3");
Serial.println(buffer_2_count);
printTimedADCInfo(ADC_1, buffer_3, delta_time_3);
}
}
void printTimedADCInfo(uint8_t adc_num, uint16_t *buffer, uint32_t &delta_time) {
uint32_t min_value = 0xffff;
uint32_t max_value = 0;
uint32_t sum = 0;
for (int i = 0; i < BUFFER_SIZE; i++) {
if (buffer[i] < min_value) min_value = buffer[i];
if (buffer[i] > max_value) max_value = buffer[i];
sum += buffer[i];
}
float average_value = (float)sum / BUFFER_SIZE; // get an average...
float sum_delta_sq = 0;
for (int i = 0; i < BUFFER_SIZE; i++) {
int delta_from_center = (int)buffer[i] - average_value;
sum_delta_sq += delta_from_center * delta_from_center;
}
int rms = sqrt(sum_delta_sq / BUFFER_SIZE);
Serial.printf("ADC:%d delta time:%d freq:%d - min:%d max:%d avg:%d rms:%d\n", adc_num,
delta_time, (1000000 * BUFFER_SIZE) / delta_time,
min_value, max_value, (int)average_value, rms);
delta_time = 0;
}
// This function will be called with the desired frequency
// start the measurement
// in my low-res oscilloscope this function seems to take 1.5-2 us.
void timer0_callback(void) {
adc->adc0->startSingleRead(readPin0); // also: startSingleDifferential, analogSynchronizedRead, analogSynchronizedReadDifferential
adc->adc1->startSingleRead(readPin2);
}
// This function will be called with the desired frequency
// start the measurement
void timer1_callback(void) {
//
adc->adc0->startSingleRead(readPin1);
adc->adc1->startSingleRead(readPin3);
}
// when the measurement finishes, this will be called
// first: see which pin finished and then save the measurement into the correct buffer
void adc0_isr() {
uint8_t pin = ADC::sc1a2channelADC0[ADC1_HC0&0x1f]; // the bits 0-4 of ADC0_SC1A have the channel
// add value to correct buffer
if(pin==readPin0) {
//digitalWriteFast(ledPin+3, HIGH);
uint16_t adc_val = adc->adc0->readSingle();
if (buffer_0_count < BUFFER_SIZE) {
buffer_0[buffer_0_count++] = adc_val;
if (buffer_0_count == BUFFER_SIZE) delta_time_0 = timed_read_elapsed;
}
//digitalWriteFast(ledPin+3, LOW);
} else if(pin==readPin1) {
//digitalWriteFast(ledPin+4, HIGH);
uint16_t adc_val = adc->adc0->readSingle();
if (buffer_1_count < BUFFER_SIZE) {
buffer_1[buffer_1_count++] = adc_val;
if (buffer_1_count == BUFFER_SIZE) delta_time_1 = timed_read_elapsed;
}
//digitalWriteFast(ledPin+4, LOW);
} else { // clear interrupt anyway
adc->readSingle();
}
// restore ADC config if it was in use before being interrupted by the analog timer
if (adc->adc0->adcWasInUse) {
// restore ADC config, and restart conversion
adc->adc0->loadConfig(&adc->adc0->adc_config);
// avoid a conversion started by this isr to repeat itself
adc->adc0->adcWasInUse = false;
}
//digitalWriteFast(ledPin+2, !digitalReadFast(ledPin+2));
asm("DSB");
}
void adc1_isr() {
uint8_t pin = ADC::sc1a2channelADC1[(ADC1_HC0+2)&0x1f]; // the bits 0-4 of ADC0_SC1A have the channel
// add value to correct buffer
if(pin==readPin2) {
//digitalWriteFast(ledPin+7, HIGH);
uint16_t adc_val = adc->adc1->readSingle();
if (buffer_2_count < BUFFER_SIZE) {
buffer_2[buffer_2_count++] = adc_val;
if (buffer_2_count == BUFFER_SIZE) delta_time_2 = timed_read_elapsed;
}
//digitalWriteFast(ledPin+7, LOW);
} else if(pin==readPin3) {
//digitalWriteFast(ledPin+8, HIGH);
uint16_t adc_val = adc->adc1->readSingle();
if (buffer_3_count < BUFFER_SIZE) {
buffer_3[buffer_3_count++] = adc_val;
if (buffer_3_count == BUFFER_SIZE) delta_time_3 = timed_read_elapsed;
}
//digitalWriteFast(ledPin+8, LOW);
} else { // clear interrupt anyway
adc->readSingle();
}
// restore ADC config if it was in use before being interrupted by the analog timer
if (adc->adc0->adcWasInUse) {
// restore ADC config, and restart conversion
adc->adc0->loadConfig(&adc->adc0->adc_config);
// avoid a conversion started by this isr to repeat itself
adc->adc0->adcWasInUse = false;
}
if (adc->adc1->adcWasInUse) {
// restore ADC config, and restart conversion
adc->adc1->loadConfig(&adc->adc1->adc_config);
// avoid a conversion started by this isr to repeat itself
adc->adc1->adcWasInUse = false;
}
asm("DSB");
}