Timing measurement of analog signals

the_maxx

Member
Hello,

I am new in the microcontrollers world so excuse me if I ask stupid things.

I have a Teensy4.0 and I want to use it to measure analog data with the following structure:
1741887242940.png


The time between trigger and trigger is constant and the time of the signals between triggers can change. The width of the signals also change. Once the second trigger finishes the loop starts again.

I would like to measure the time and width of all the signals.
Right now I am feeding the signal in one of the analog pins and I see the signal in the serial plotter. I am using the following code:
Code:
/* Example
* https://www.pjrc.com/teensy/tutorial4.html
*
* Baudrate time res -> https://gammon.com.au/adc
*
* Conectar el volt al pin A8 = 22
* Conectar el grnd al pin GND
*
*/


void setup() {
  //Serial.begin(38400);
  Serial.begin(9600);
  //Serial.begin(16000000);
}

long val;
long valp;
long mcrTminus = 0.0;

void loop() {

  val = analogRead(A8);
  valp = analogRead(A0);

  long mcrT = micros();


  if(valp/310.0 > 0.40){
 
    Serial.print("Up_3000:");
    Serial.println(3000.0); // To freeze the upper limit
 
    Serial.print("Up_500:");
    Serial.println(500.0); // To freeze the upper limit
    Serial.print("Up_10:");
    Serial.println(10.0); // To freeze the upper limit
    Serial.print("Down_0:");
    Serial.println(0); // To freeze the upper limit


    Serial.print("analog_A0_is:");
    Serial.println(valp/310.0);  // en mV -> baudrate = 9600 para input de 1V valp/310

    Serial.print("Time_in_us_is:");
    Serial.println(mcrT);
 
    Serial.print("Time_diff (us):");
    Serial.println(mcrT-mcrTminus); // in us

    Serial.print("Time_diff (ms):");
    Serial.println((mcrT-mcrTminus)/1000); // in ms

    mcrTminus =mcrT;
  }
 
 
}

My questions are:
- Is there any better way to measure this type of signals. I would like very good timing precision and measure fast
- How should I decide the better baudrate in my case? I am not pretty sure how it works, I read that if you are connecting teensy through USB the baudrate doesn't affect the measurements but I don't know if it is true.
- How can I save arrays of the measured data and calculate the times and widths of the signals while I am measuring?
- Can I somehow calculate the Fourier transform of the measured data in order to obtain the frequencies of the signals?

Thank you in advance.
 

Attachments

  • 1741887197260.png
    1741887197260.png
    15.9 KB · Views: 138
Hello,

As ratnin suggested I am measuring using digitalRead().

To start with the measurements I am using a constant voltage source in order to estimate the time resolution of my measurement.

Taking a look to my results, the minimum step that I can measure with mcrT is 68-70 us. Is there a way of measuring with better time resolution? I need to measure in steps of 1 us.

Thank you in advance.
 
Maybe switch to digitalReadFast()
Same result. I read that with Teensy I should be able to measure with ns resolution. Am i mistaken?

My code looks like this:

Python:
void setup() {
}

long val;
long volt;

void loop() {

  val = digitalReadFast(A8);

  volt = analogRead(A8);

  long mcrT = micros();

  if(val==1){

  if(volt>0){

  Serial.print(mcrT);
  Serial.print(" ");
  Serial.println(volt/932.0*3.0);

  }
  } else
  {
  if(volt>950){
  Serial.print(" LOW ");

  Serial.print(mcrT);
  Serial.print(" ");
  Serial.println(volt);
}
  }

 
 
}
 
Have you done some tests to see how much time is being spent in the 1) very slow and 2) multiple, redundant Serial.print() calls?
 
Hello thebigg,

I have just done it, with the following example and I get 20 us resolution:

Python:
void setup() {
}

long val;
long volt;

void loop() {

  val = digitalReadFast(A8);


  long mcrT = micros();

  if(val==1){



  Serial.println(mcrT);
  }

 
}

Do you think that it will be faster using a compiled c++ program instead of the Arduino-IDE?
 
This code doesn't address the posted situation directly, but does show using an interrupt for pin CHANGE the resolution is showing 166 clock cycles here - with more ISR code and other in loop() it would still be under 200 cycles - or one third of a microsecond at 600 MHz.

It uses Pin #22 i.e. A8 as Input. So with modification the use of A9 wired here to A8 could be eliminated and with your external signal the digitalToggleFast(A9) would be removed.

The Storage array indexing would need to be accounted for so the interrupt stores numbers in a safe known space, and in the ISR a digitalReadFast(A8) would be needed to track if the CHANGE was to a HIGH or LOW value. As done here the loop() code would assist in keeping the array index in bounds and printing/using the results as needed.

Using the ARM_DWT_CYCCNT is cpu cycle counts where there are F_CPU_ACTUAL per second based on clock speed. That requires conversion to desired units, but is only usable for 7.158 seconds between CHANGE before it will over flow. micros() can be used for monitoring slower changes.

Code:
// Wire Pin #22==A8 to Pin #23==A9
// https://forum.pjrc.com/index.php?threads/timing-measurement-of-analog-signals.76685/post-360758
uint32_t seeTimes[64];

volatile uint32_t myCnt = 0;
void catchTimes() {
  // seeTimes[myCnt] = micros(); // not enough resolution as it runs 3+ times per us
  seeTimes[myCnt] = ARM_DWT_CYCCNT;
  //asm("dsb" ::: "memory"); // Not Needed - adds a few cycles
}

void setup() {
  Serial.begin(2);
  pinMode(A9, OUTPUT);
  pinMode(A8, INPUT);
  Serial.println(F_CPU_ACTUAL);
  delay(1000);
  attachInterrupt(A8, catchTimes, CHANGE);
}

uint32_t setCnt = 0;
void loop() {
  if (myCnt % 100 < 16) {
    digitalToggleFast(A9);
    digitalToggleFast(A9);
  } else if (myCnt == 99) {
    for (int ii = 3, jj = 1; ii < 13; ii++, jj++) {
      Serial.printf("[%5d] %2d == %d CLK cycles\n", setCnt, jj, seeTimes[ii] - seeTimes[ii - 1]);
    }
    Serial.printf("\n");
    setCnt++;
    delay(3000);
    myCnt = 0;
  }
  myCnt++;
}

Adding output sample for fun:
Code:
[11256]  1 == 189 CLK cycles
[11256]  2 == 166 CLK cycles
[11256]  3 == 166 CLK cycles
[11256]  4 == 166 CLK cycles
[11256]  5 == 166 CLK cycles
[11256]  6 == 166 CLK cycles
[11256]  7 == 166 CLK cycles
[11256]  8 == 166 CLK cycles
[11256]  9 == 166 CLK cycles
[11256] 10 == 166 CLK cycles
Funny number in the print for() to skip the change in myCnt, and the long 3 seconds delay between restarts. Even then the #1 count on restart always longer then the rest of the repeats, even across a loop() exit re-enter they are then constant to the twin toggle's in a row.
 
Last edited:
Decided to update the sketch to record the triggering pin state to see the added time in the ISR. Having paired (2 or 4) digitalToggle's in loop() to trigger the ISR resulted in it always reading '0' on every change - Odd. Every change was coming in based on the recorded cycle counts - just never reading alternate values. With a single toggle the results were as expected - a few cycles longer:
Code:
uint32_t seeTimes[64][2];

volatile uint32_t myCnt = 0;
void catchTimes() {
  // seeTimes[myCnt] = micros(); // not enough resolution as it runs 3+ times per us
  seeTimes[myCnt][0] = ARM_DWT_CYCCNT;
  seeTimes[myCnt][1] = digitalReadFast(A8);
}

void setup() {
  Serial.begin(2);
  pinMode(A9, OUTPUT);
  pinMode(A8, INPUT);
  Serial.println(F_CPU_ACTUAL);
  delay(1000);
  attachInterrupt(A8, catchTimes, CHANGE);
}

uint32_t setCnt = 0;
void loop() {
  if (myCnt % 100 < 16) {
    digitalToggleFast(A9);
    //digitalToggleFast(A9); // seems this was executing in parallel? There was never a '1' seen by the ISR? Same with 2 or 4 toggles?
  } else if (myCnt == 99) {
    for (int ii = 3, jj = 1; ii < 13; ii++, jj++) {
      Serial.printf("[%5d] %2d == %d CLK cycles\t state=%d\n", setCnt, jj, seeTimes[ii][0] - seeTimes[ii - 1][0], seeTimes[ii][1]);
    }
    Serial.printf("\n");
    setCnt++;
    delay(3000);
    myCnt = 0;
  }
  myCnt++;
  // Enable this line to add 6000 clock cycles to the counts as shown in second output below:
  // delayMicroseconds(10);
}

And sample output:
Code:
[ 2604]  1 == 204 CLK cycles     state=1
[ 2604]  2 == 180 CLK cycles     state=0
[ 2604]  3 == 180 CLK cycles     state=1
[ 2604]  4 == 180 CLK cycles     state=0
[ 2604]  5 == 180 CLK cycles     state=1
[ 2604]  6 == 180 CLK cycles     state=0
[ 2604]  7 == 180 CLK cycles     state=1
[ 2604]  8 == 180 CLK cycles     state=0
[ 2604]  9 == 180 CLK cycles     state=1
[ 2604] 10 == 180 CLK cycles     state=0

Just added final commented line [end of loop()] above stalling loop() 10us - and here are the results - if the CHANGE events are perhaps at least this far apart then micros() could be used for timing:
Code:
[    2]  1 == 6056 CLK cycles     state=1
[    2]  2 == 6064 CLK cycles     state=0
[    2]  3 == 6064 CLK cycles     state=1
[    2]  4 == 6064 CLK cycles     state=0
[    2]  5 == 6064 CLK cycles     state=1
[    2]  6 == 6064 CLK cycles     state=0
[    2]  7 == 6064 CLK cycles     state=1
[    2]  8 == 6064 CLK cycles     state=0
[    2]  9 == 6064 CLK cycles     state=1
[    2] 10 == 6064 CLK cycles     state=0
 
Last edited:
Hello defragster,

Thanks for your suggestions.
This is my first time using a micro-controller and there are some concepts that I don't understand yet. I will have to read your suggestions slowly and make some tests. I will be back once I have made some tests.
 
Thanks for your suggestions.
You are welcome, it was fun to make the test sketch :)
What are the minimum times you need to measure? That is time the 'analog' is HIGH and time between sitting LOW and going HIGH?

Of course doing these Digital pin changes and reads will only catch the actual transitions going over and under the digital cutoff value in the middle of 0 volts and 3.3 volts - so it won't record the actual start of a low analog signal or the slow fall with any accuracy if that is needed. But if the rise and fall are smooth transitions the attachInterrupt() may be enough to give consistent timing values for those events.
 
Some minor tweaks to also show the us between changes, and change a bit of the trigger and sample recording closer to might what work in your case. The issue is working with the interrupt ISR writing and changing the recorder data while the loop() code will be looking at it!

For actual use there will need to be a way to assure the loop() reading the data isn't working on the data while the ISR is writing the data where they can overlap. This example uses a single array and the (myCnt%16) to take 16 samples into the array and then stop the Toggle to print the data. Other ways would be twin buffers allowing the ISR to run continuously, or an interrupt safe buffer or queue storage system allowing the ISR to store data and the loop() to read any data observed safely, as long as the loop() processes the data faster than it comes in to fill the storage chosen.

Code:
// Wire Pin #22==A8 to Pin #23==A9

uint32_t seeTimes[64][3];

volatile uint32_t myCnt = 0;
void catchTimes() {
  seeTimes[myCnt][0] = ARM_DWT_CYCCNT;
  seeTimes[myCnt][2] = micros();  // not enough resolution as it runs 3+ times per us
  seeTimes[myCnt][1] = digitalReadFast(A8);
  myCnt++;
}

void setup() {
  Serial.begin(2);
  pinMode(A9, OUTPUT);
  pinMode(A8, INPUT);
  Serial.println(F_CPU_ACTUAL);
  delay(1000);
  attachInterrupt(A8, catchTimes, CHANGE);
}

uint32_t setCnt = 0;
void loop() {
  if (myCnt % 100 < 16) {
    digitalToggleFast(A9);
    //digitalToggleFast(A9); // seems this was executing in parallel? There was never a '1' seen by the ISR? Same with 2 or 4 toggles?
  } else {
    for (int ii = 1, jj = 1; ii < 13; ii++, jj++) {
      Serial.printf("[%5d] %2d == %d CLK cycles\t state=%d\t %d us\n", setCnt, jj, seeTimes[ii][0] - seeTimes[ii - 1][0], seeTimes[ii][1], seeTimes[ii][2] - seeTimes[ii - 1][2]);
    }
    Serial.printf("\n");
    setCnt++;
    delay(3000); // wait 3 seconds before next set of sample toggles to slow display output
    myCnt = 0;
  }
  delayMicroseconds(10);  // DELAY to at 10us between loop() entry and he next input Toggle
}

And current ouput:
Code:
[  323]  1 == 6055 CLK cycles     state=0     10 us
[  323]  2 == 6060 CLK cycles     state=1     10 us
[  323]  3 == 6060 CLK cycles     state=0     10 us
[  323]  4 == 6060 CLK cycles     state=1     10 us
[  323]  5 == 6060 CLK cycles     state=0     10 us
[  323]  6 == 6060 CLK cycles     state=1     10 us
[  323]  7 == 6060 CLK cycles     state=0     11 us
[  323]  8 == 6060 CLK cycles     state=1     10 us
[  323]  9 == 6060 CLK cycles     state=0     10 us
[  323] 10 == 6060 CLK cycles     state=1     10 us
[  323] 11 == 6060 CLK cycles     state=0     10 us
[  323] 12 == 6060 CLK cycles     state=1     10 us
 
Hello,
I have changed the code, here is my version:

C:
uint32_t seeTimes[64][3];

volatile uint32_t myCnt = 0;
void catchTimes() {
  seeTimes[myCnt][0] = ARM_DWT_CYCCNT;
  seeTimes[myCnt][2] = micros();  // not enough resolution as it runs 3+ times per us
  seeTimes[myCnt][1] = digitalReadFast(A8);
  myCnt++;
}

void setup() {
  Serial.begin(1);
  pinMode(A8, INPUT);
  Serial.println(F_CPU_ACTUAL);
  delay(2000);
  attachInterrupt(A8, catchTimes, CHANGE);
}

uint32_t setCnt = 0;
void loop() {


    for (int ii = 1, jj = 1; ii <20; ii++, jj++) {
      //Serial.printf("[%5d] %2d == %d CLK cycles\t state=%d\t %d us\t micros=%d us\n", setCnt, jj, seeTimes[ii][0] - seeTimes[ii - 1][0], seeTimes[ii][1], seeTimes[ii][2] - seeTimes[ii - 1][2], seeTimes[ii][2]);
      Serial.printf("[%5d] %2d == %d CLK cycles\t state=%d\t %d us\n", setCnt, jj, seeTimes[ii][0] - seeTimes[ii - 1][0], seeTimes[ii][1], seeTimes[ii][2] - seeTimes[ii - 1][2]);
      
    }
    Serial.printf("\n");
    setCnt++;
    delay(1000); // wait 1 seconds before next set
    myCnt = 0;

  delayMicroseconds(1);  // DELAY to at 10us between loop()


}

I have injected different signals in A8 and my conclusion is that each cycle lasts around 0.04 us, which I don't understand so I will try to do some more tests. Furthermore, I have tried to measure 5 us input pulses but I couldn't obtain 5us because the measured time was unstable (it was fluctuating between 6 and 10 us). Maybe my input source is not stable...

Next step is to include a queue to store the data. I have found several options but I don't know which one is the best one:
1) Queue: https://github.com/SMFSW/Queue
2) Arduino queue: https://github.com/EinarArnason/ArduinoQueue
3) Circular queue: https://github.com/Francis-Magallanes/CircularQueue
4) Use the Teensy queue: https://forum.pjrc.com/index.php?threads/basic-audio-record-queue-and-playback.56547/

Could you give me some advice?
 
Not sure about any of those storage schemes : I'd start with this from @tonton81 :: https://github.com/tonton81/Circular_Buffer

Not having setup there p#15 code edited to self toggle with IntervalTimer: and it seems to be working as expected:
Code:
// https://forum.pjrc.com/index.php?threads/timing-measurement-of-analog-signals.76685/post-360758
// Wire Pin #22==A8 to Pin #23==A9

#define MAX_FILL_CNT 20
uint32_t seeTimes[64][3];
IntervalTimer myTimer;
uint32_t usDelay[4] = { 2, 5, 10, 15 };

volatile uint32_t myCnt = 0;
void catchTimes() {
  seeTimes[myCnt][0] = ARM_DWT_CYCCNT;
  seeTimes[myCnt][2] = micros();  // not enough resolution as it runs 3+ times per us
  seeTimes[myCnt][1] = digitalReadFast(A8);
  myCnt++;
}

void sendTimes() {
  if (myCnt < MAX_FILL_CNT) {
    digitalToggleFast(A9);
    myTimer.update(usDelay[myCnt % 4]);
  }
}

void setup() {
  Serial.begin(1);
  pinMode(A9, OUTPUT);
  pinMode(A8, INPUT);
  Serial.print("Hello World CPU_Hz==");
  Serial.println(F_CPU_ACTUAL);
  delay(2000);
  Serial.println("Starting ...");
  attachInterrupt(A8, catchTimes, CHANGE);
  myTimer.begin(sendTimes, usDelay[3]);
}

uint32_t setCnt = 0;
void loop() {
  if (MAX_FILL_CNT == myCnt) {
    for (int ii = 1, jj = 1; ii < MAX_FILL_CNT; ii++, jj++) {
      Serial.printf("[%5d] %2d == %d CLK cycles\t state=%d\t %d us\n", setCnt, jj, seeTimes[ii][0] - seeTimes[ii - 1][0], seeTimes[ii][1], seeTimes[ii][2] - seeTimes[ii - 1][2]);
    }
    Serial.printf("\n");
    setCnt++;
    delay(1000);  // wait 1 seconds before next set
    myCnt = 0;
  }
}

Output showing this cycling of recorded us's:
Code:
[   70]  1 == 8995 CLK cycles     state=0     15 us
[   70]  2 == 1200 CLK cycles     state=1     2 us
[   70]  3 == 3000 CLK cycles     state=0     5 us
[   70]  4 == 6000 CLK cycles     state=1     10 us
[   70]  5 == 9000 CLK cycles     state=0     15 us
[   70]  6 == 1200 CLK cycles     state=1     2 us
[   70]  7 == 3000 CLK cycles     state=0     5 us
[   70]  8 == 6000 CLK cycles     state=1     10 us
[   70]  9 == 9000 CLK cycles     state=0     15 us
[   70] 10 == 1200 CLK cycles     state=1     2 us
[   70] 11 == 3000 CLK cycles     state=0     5 us
[   70] 12 == 6000 CLK cycles     state=1     10 us
[   70] 13 == 9000 CLK cycles     state=0     15 us
[   70] 14 == 1200 CLK cycles     state=1     2 us
[   70] 15 == 3000 CLK cycles     state=0     5 us
[   70] 16 == 6000 CLK cycles     state=1     10 us
[   70] 17 == 9000 CLK cycles     state=0     15 us
[   70] 18 == 1200 CLK cycles     state=1     2 us
[   70] 19 == 3000 CLK cycles     state=0     5 us
 
Thread Title is 'analog signals'.

These examples are using Digital reads as noted - unless/until the "analog" value crosses the threshold midpoint there will be no recorded change.

I edited the example to SELF cycle the output with analogWrite and it is Very TRICKY - of course the anaolog write is PWM and there are no smoothing capacitors and analogWrite is slower?

The times are all reasonable and consistent - but not clean and right like with digital.

Not much info was given about the expected LOW and HIGH times and the nature of the analog signal - this may be the wrong approach ...
 
Back
Top