Does this continuous simultaneous ADC sampling look reasonable?

Status
Not open for further replies.

zapta

Well-known member
Hi everybody,

I am new to teensy and am trying to understand the ADC library. For my application I need to sample two analog channels that represent the currents through a stepper motor coils and track the position of the stepper. Below is a skeleton program that samples two channels continuously.

1. Does the code look reasonable? Any red flags? Any suggestion? I think that ~10k samples/sec should be more than enough.

2. I run built this with Arduino IDE for Teensy 3.2. Will same code work also on Teensy 4.0?


Thanks
Z.

Code:
// Skeleton program for reading two input analog channels.

#include <stdio.h>
#include <ADC.h>

const int adcPin1 = A9;
const int adcPin2 = A3;

ADC *adc = new ADC();

elapsedMicros time;

void setup() {
  Serial.begin(9600);
  pinMode(LED_BUILTIN, OUTPUT);

  pinMode(adcPin1, INPUT);
  pinMode(adcPin2, INPUT);

  adc->setReference(ADC_REFERENCE::REF_3V3, ADC_0);
  adc->setReference(ADC_REFERENCE::REF_3V3, ADC_1);

  adc->setAveraging(4, ADC_0);
  adc->setAveraging(4, ADC_1);

  adc->setResolution(12, ADC_0);
  adc->setResolution(12, ADC_1);

  adc->setConversionSpeed(ADC_CONVERSION_SPEED::MED_SPEED, ADC_0);
  adc->setConversionSpeed(ADC_CONVERSION_SPEED::MED_SPEED, ADC_1);

  adc->setSamplingSpeed(ADC_SAMPLING_SPEED::MED_SPEED, ADC_0); 
  adc->setSamplingSpeed(ADC_SAMPLING_SPEED::MED_SPEED, ADC_1); 

  // We use adc0_isr to read also adc1.
  adc->enableInterrupts(ADC_0);

  // NOTE: this return a error status if pins are not supported.
  adc->startSynchronizedContinuous(adcPin1, adcPin2);
  delay(100);
}

// TODO: should these be volaitile?
int isr_count = 0;
ADC::Sync_result result;

void loop() {
  // Take a snapshot of isr values and reset isr_count
  int _isr_count;
  ADC::Sync_result _result;
  __disable_irq();
  {
    _isr_count = isr_count;
    _result = result;
    isr_count = 0;
  }
  __enable_irq();

  // Print snapshot
  char buffer[60];
  sprintf(buffer, "%d %5d %5d", _isr_count, _result.result_adc0, _result.result_adc1);
  Serial.println(buffer);

  adc->printError();  // Print adc errors, if any
  digitalWriteFast(LED_BUILTIN, !digitalReadFast(LED_BUILTIN));
  delay(1000);
}

void adc0_isr(void) {
  isr_count++;
  // NOTE: if sampling 16 bits, need t clear the high
  // 16 bits of result's fields to undo the 16 bit to 32 bit sign extension.
  result = adc->readSynchronizedContinuous();
  // TODO: insert stepper position tracking here.  
}
 
I assume the same code would work on T4 but remember there are a few differences. The T4 does not have an external Vref input (the internal 3.3V is the only option) and also it uses a 12-bit ADC (but maybe 10 bits in practice) while the T3.2 uses a 16 bit ADC (but maybe 12-13 in practice).
 
You can use the analog read function. I have not tried to see how fast they are yet, It might be able to handle 10K samples without any problem. That isn't too high for a teensy. Actually reading your code now, I notice some things:

I would get rid of the continuous reads, and the synchronized read isn't magic, it just reads one then the other. Try reading each individually with the T4 and see if it is fast enough
Is your 10k sample/sec for each channel or both together? Why so many? Does the timing of the reads matter?
Did this work on the T3.2? I don't see anything actually triggering the ISR
You do not want a stepper tracker code in your ISR. Keep that short.
What does your buffer do? You never write anything to it

For this, if you actually want 10K samples per second and want the ease of the ADC library right now, stick with one of the T3's (with the PDB). They are amazing little devices and everything works great. With the ADC library you can get 100K's samples per second. If you want the 4, try analog read, read the manual (very dense read), or peruse the forum for bits of code people have pasted about the ADC's.
 
Bumbler@, it needs to read both channels continuously, ~10k per channel per sec so it doesn't lose stepper steps. This works on a T3.2 though I am considering using a timer to trigger the samplings to have a more predictable sampling interval. I am using two ADCs to sample the two channels at the same time so I can correlate them. The ISR is triggered at the completion of a conversion, it's implicit in the ADC.h api.

The buffer is to format the output string. It is written by the sprintf() method.
 
Yes, sorry reading too quick.
I believe my concern still stands however. As far as I understand the ADC, the continuous conversion setting of the ADC will raise the ISR on every conversion complete, but it won't buffer that value until you read it. If another conversion happens before you get to it, you lose the value, and it tries to set the interrupt bit again. So, if you have a bunch of code in your ISR that isn't tied to reading the data register, you could just be tying up some resources for nothing as you run through that code without being able to read the next value. Additionally, if you have an interrupt with a higher priority, then you may just have a hole in your readings.

I don't see this method getting you precise timing or probably even precise number of readings per second. I think the PDB is continuous in the way you want. It is a timer that does hardware triggering of the ADC at the frequency you tell it. So if you want 10 KHz, you set the PDB to that and it plods along precisely getting your 10KHz readings until you tell it to stop. Your ISR will read the values from the register into a buffer or something and your main loop will do whatever needs doing to track your servos. You get the exact number of readings you want at the frequency you expect. Every reading will be correlated.

What was your reasoning for wanting the T4?
 
I may try the hardware timer to pace the sampling. Currently its every ~30usec with ~1usec isr time.

The T4 seems to be superior to the T3.2 and at same size and price so no brainer from my perspective.
 
Status
Not open for further replies.
Back
Top