Double synchronous measurements on T4.1

krisgambhir

New member
Hi,

I am trying to measure 4 analog signals in 2 pairs; 2 voltages and 2 currents, along with one accelerometer using I2C on the Teensy 4.1. In the end, the voltages will be measured using the LV25-P voltage transformer and the currents are measured using the LTS6-NP current transformer, while the accelerometer is the ADXL343. For test purposes, I am connecting a signal generator directly to the pins of my T4.1.

The measurements have to be done synchronously in pairs of voltages and currents, as the phase angle in between them has to be calculated for further calculations. I have adapted the synchronizedMeasurement code from the ADC library, where I start synchronized continuous readings on A8 and A9, read a value and print it, and repeat for pins A3 and A4. I delay by 100 microseconds at the end of the loop as well. The code is attached below.

When I run measurements with the same 50Hz 3Vpp sinusoidal signal from 0V:+3V, I get good measurements for a couple of seconds with approximately 110us between each measurement in the times columns t0 and t1, respectively. After the first couple of seconds, the time between measurements is increased by a huge amount, up to 6000us.

I haven't tried to add the accelerometer, as I need the voltages and currents to be measured properly first.

Does anyone have any idea on how to fix this issue, and if there is a more robust way of measuring the analog signal, for example using interrupts?

Code:
#include <ADC.h>
#include <ADC_util.h>

const int P1 = A3; // Voltage 1
const int P2  = A4; // Current 1

const int P3 = A8; // Voltage 2
const int P4 = A9; // Current 2

ADC *adc = new ADC(); // adc object

elapsedMicros time;

void setup() {
  pinMode(LED_BUILTIN, OUTPUT);
  pinMode(P1, INPUT);
  pinMode(P2, INPUT);
  pinMode(P3, INPUT);
  pinMode(P4, INPUT);

  Serial.begin(9600);

  //## 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::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::HIGH_SPEED); // change the sampling speed

  Serial.println("t0,A8,A9,t1,A3,A4");


}

ADC::Sync_result result;

void loop() {
 adc->startSynchronizedContinuous(P1,P2);
 result = adc->readSynchronizedContinuous();
 
 result.result_adc0 = (uint16_t)result.result_adc0;
 result.result_adc1 = (uint16_t)result.result_adc1;

  Serial.print(time, DEC);
  Serial.print(",");
  Serial.print(result.result_adc0*3.3/adc->adc0->getMaxValue(), DEC);
  Serial.print(",");
  Serial.print(result.result_adc1*3.3/adc->adc1->getMaxValue(), DEC);
  
  adc->startSynchronizedContinuous(P3,P4);
  result = adc->readSynchronizedContinuous();
 
  result.result_adc0 = (uint16_t)result.result_adc0;
  result.result_adc1 = (uint16_t)result.result_adc1;
  
  Serial.print(",");
  Serial.print(time, DEC);
  Serial.print(",");
  Serial.print(result.result_adc0*3.3/adc->adc0->getMaxValue(), DEC);
  Serial.print(",");
  Serial.println(result.result_adc1*3.3/adc->adc1->getMaxValue(), DEC);

  //digitalWriteFast(LED_BUILTIN, !digitalReadFast(LED_BUILTIN));
  delayMicroseconds(100);

 }
 
Possibly something to do with the Serial.print/.println filling up a buffer and then slowing down.
Which USB Serial are you using?
There is a problem with the Arduino serial monitor, in that it is not fast enough for T4.x, and Paul rewrote a monitor for the T4.x.
If you are not doing so, select the Tools/Port/and one of the Teensy Ports (NOT Serial Ports).
 
I am trying to measure 4 analog signals in 2 pairs; 2 voltages and 2 currents, along with one accelerometer using I2C on the Teensy 4.1.
...
Does anyone have any idea on how to fix this issue, and if there is a more robust way of measuring the analog signal, for example using interrupts?

I'm doing something similar to what you want to do with a T3.5, which I'll get to below. First, regarding your example code, read "continuous" means start once, and then read many times. You don't want to start continuous before each read. Also, read "continuous" means wait for completion and then get data, i.e. read as often as possible, so you don't want a delay(100). I modified your code by moving the call to start continuous to setup() and comment out the call to delay(), and that results in new data about every 10 us.

In my project I want to sample 4 x ADC channels in two pairs at a fixed frequency of 10 kHz. I have an IntervalTimer set for 10 kHz and I do both sets of ADC reads in the ISR. The method I use is (a) start the synchronized read, wait for completion, read, start the 2nd set, wait for completion, read, exit the ISR. There are other ways of doing it, such as using ADC interrupts, but I found it simpler to do it all within the timer ISR. Based on the results from the continuous read on T4.1, you need about 20 us to read 2 pairs of channels, so you can easily sample all 4 channels at 10-20 kHz without too much trouble. To go faster than that, you would have to get into lower-level stuff with the ADC library.
 
I modified your code by moving the call to start continuous to setup() and comment out the call to delay(), and that results in new data about every 10 us.

I get that we can shift the startSynchronizedContinuous call to setup when we only have 2 analog signals, but to measure 4 as you also have mentioned, wouldn't we still need to call startSynchronizedContinuous for P1 and P2, then read, then call it again for P3 and P4 and finally read them? Is this not what I am already doing in the code, or am I missing something?

(a) start the synchronized read, wait for completion, read, start the 2nd set, wait for completion, read, exit the ISR.

What do you mean by "wait for completion"? Is it a bit that gets flipped or a flag somewhere that you look out for or is it built into the readSynchronizedContinuous call to wait for the initialization to happen?
 
I get that we can shift the startSynchronizedContinuous call to setup when we only have 2 analog signals, but to measure 4 as you also have mentioned, wouldn't we still need to call startSynchronizedContinuous for P1 and P2, then read, then call it again for P3 and P4 and finally read them? Is this not what I am already doing in the code, or am I missing something?

What do you mean by "wait for completion"? Is it a bit that gets flipped or a flag somewhere that you look out for or is it built into the readSynchronizedContinuous call to wait for the initialization to happen?

I don't think you want to read "continuously" (read multiple times). You want to read chan 1/2 once, then read channel 3/4 once, then back to 1/2, 3/4, etc. Reading continuously means starting the ADC and having it do continuous conversions on the same channel(s). Regarding wait for completion, yes, I mean wait for a conversion complete flag to be set.

Here is a test program based on what I did for my T3.5 project. This works on T3.5 and T4.x. I had to strip out a bunch of stuff, so you may want to add in some statements to measure the time of the conversions. The key thing is that in the IntervalTimer function, I use lower-level startReadFast(A0) and startReadFast(A2) rather than a single call to startSynchronizedSingleRead( A0, A2 ). startReadFast() has a lot less overhead, which I learned just by studying the ADC library source code. As long as you're using the same channels over and over, this works great and is a lot faster.

Also note that I set the priority of IntervalTimer to 0 (highest) for minimum jitter between reads. Also, the reads are not truly synchronized. They are just "really close together". The ADC library does not support true synchronized reads.

Code:
/*********************************************************************************
* ADCExample.ino -- test program for 2 pairs of "synchronized" ADC reads
*********************************************************************************/
#include <IntervalTimer.h>	// IntervalTimer for OS tickpop
#include <ADC.h>		// ADC library by pedvide
ADC *adc = new ADC();		// ADC object
ADC::Sync_result resultA0A2;	// results for channels A0+A2
ADC::Sync_result resultA1A3;	// results for channels A1+A3

#define F_CTRL	(10000)		// 10 kHz

IntervalTimer timerF_CTRL;	// 10-kHz timer

uint32_t adc_read_complete;	// flag for ISR->loop signal
uint32_t nreads;		// count of ADC reads
uint32_t isr_t[3];		// measure ADC conversions in ARM cycles

// periodic timer interrupt calls this function
FASTRUN static void timerF_CTRLFunc( void )
{
  adc->adc0->startReadFast( A0 );			// start A0 read
  adc->adc1->startReadFast( A2 );			// start A2 read
  //adc->startSynchronizedSingleRead( A0, A2 );		// start A0,A2 read

  // wait for A0A2 read complete, get A0A2 results, start A1A3 read	
  while (!(adc->adc0->isComplete() && adc->adc1->isComplete())) { /*wait*/ }
  resultA0A2 = adc->readSynchronizedSingle();		// get A0,A2 result
  adc->adc0->startReadFast( A1 );			// start A1 read
  adc->adc1->startReadFast( A3 );			// start A3 read
  
  // wait for A1A3 read complete, get A1A3 results	
  while (!(adc->adc0->isComplete() && adc->adc1->isComplete())) { /*wait*/ }
  resultA1A3 = adc->readSynchronizedSingle();		// get A1,A3 result
  
  // set flag to signal read complete
  adc_read_complete = 1;
}

// setup() is called once at bootup, before loop()
void setup( void )
{
  
  Serial.begin( 115200 );
  uint32_t start = millis();
  while (!Serial && (millis()-start) < 1000) {}
  Serial.print( "ADCExample.ino -- " );
  Serial.print( __DATE__ );
  Serial.print( " " );
  Serial.println( __TIME__ );

  pinMode( LED_BUILTIN, OUTPUT );		// configure LED digital out
  
  // configure ADC pins as INPUT
  pinMode( A0,  INPUT  );
  pinMode( A1,  INPUT  );
  pinMode( A2,  INPUT  );
  pinMode( A3,  INPUT  );
     
  ///// ADC0 ////
  adc->adc0->setAveraging(1); // set number of averages
  adc->adc0->setResolution(12); // set bits of resolution
  adc->adc0->setConversionSpeed(ADC_CONVERSION_SPEED::HIGH_SPEED); // change the conversion speed
  adc->adc0->setSamplingSpeed(ADC_SAMPLING_SPEED::MED_SPEED); // change the sampling speed

  ////// ADC1 /////
  adc->adc1->setAveraging(1); // set number of averages
  adc->adc1->setResolution(12); // set bits of resolution
  adc->adc1->setConversionSpeed(ADC_CONVERSION_SPEED::HIGH_SPEED); // change the conversion speed
  adc->adc1->setSamplingSpeed(ADC_SAMPLING_SPEED::MED_SPEED); // change the sampling speed
 
  // start tick timer after calling creating tasks
  timerF_CTRL.begin( timerF_CTRLFunc, 1000000/F_CTRL );	// control cycle timer/ISR
  timerF_CTRL.priority( 0 );				// hi pri -> minimum jitter
}

uint32_t us, prev_us = micros();

void loop( void )
{
  if (adc_read_complete != 0) {
    adc_read_complete = 0;
    if ((++nreads % 10) == 0) {
      us = micros();
      Serial.printf( "%10lu   %10lu\n", nreads, us-prev_us );
      prev_us = us;
    }
  }
}
 
Back
Top