Teensy 3.6 as a precision event timer

Status
Not open for further replies.

BenS

Member
First timer here and very little experience with the Teensy IDE. I am willing to pay for help (Paypal). Here's the problem. Data collection at the optical SETI observatory occurs as timestamped events. These need to be accurate to a microsecond or better. I have an Arduino mega board running, but the timing accuracy is only about +/-10 microseconds. The Teensy 3.6, with the much faster clock, should do much better, but it does not.

There also needs to be a way to reset the StartTime when the data acquisition begins, i.e., when a QB64 program starts grabbing the usb data. The inability to reset micros() is frustrating.

My programming days date back to the 1980s, so I'm way, way out of date and at 80, don't wish to spend the time learning new languages. This seems like a simple project, but I've already spent too many hours trying to sort it out.
 
Welcome! Certainly the Teensy can do better than the Mega.

Can the code be posted showing :: 'The Teensy 3.6, with the much faster clock, should do much better, but it does not'
> would be interesting to see:
>> the recording of the micros() value as done
>> the trigger that initiates the recording

As for micros() it is a constantly increasing value with rollover at overflow of the unsigned 32 bit integer. It generally reports the microseconds +/- 1 tied to the millisecond running timer ticks. But you can self ZERO that by recording the initial value and then easily track offset from that in subsequent readings.

The T_3.6 stock speed is 180 MHz - but can typically overclock to 256 MHz. The ARM MCU used in the Teensy has a cycle counter which increments with each clock cycle of the processor. This can result in finer resolution closer to micros()/180 or micros()/256. And ideally in the next month using a Teensy 4 the clock ticks per second goes up to 600 Mhz.

All of these clocks are subject to the variability of the crystal installed on the Teensy at hand that is subject to change based on environmental effects. If there is an accurate controlled '1 second' PPS signal available to input to the Teensy it can be aligned with that atomic clock value rather than the changing observed cycles per second on the teensy at hand if absolute time ref is critical.

It isn't noted how long a 'Data collection' period lasts or the nature/frequency of the samples where time needs to be recorded, or if the Teensy is doing anything but watching to record the event timestamp.

The 80's were a cool time to start programming.
 
Welcome! Certainly the Teensy can do better than the Mega.

Can the code be posted showing :: 'The Teensy 3.6, with the much faster clock, should do much better, but it does not'
> would be interesting to see:
>> the recording of the micros() value as done
>> the trigger that initiates the recording

As for micros() it is a constantly increasing value with rollover at overflow of the unsigned 32 bit integer. It generally reports the microseconds +/- 1 tied to the millisecond running timer ticks. But you can self ZERO that by recording the initial value and then easily track offset from that in subsequent readings.

The T_3.6 stock speed is 180 MHz - but can typically overclock to 256 MHz. The ARM MCU used in the Teensy has a cycle counter which increments with each clock cycle of the processor. This can result in finer resolution closer to micros()/180 or micros()/256. And ideally in the next month using a Teensy 4 the clock ticks per second goes up to 600 Mhz.

All of these clocks are subject to the variability of the crystal installed on the Teensy at hand that is subject to change based on environmental effects. If there is an accurate controlled '1 second' PPS signal available to input to the Teensy it can be aligned with that atomic clock value rather than the changing observed cycles per second on the teensy at hand if absolute time ref is critical.

It isn't noted how long a 'Data collection' period lasts or the nature/frequency of the samples where time needs to be recorded, or if the Teensy is doing anything but watching to record the event timestamp.

The 80's were a cool time to start programming.




Hi Defragster. Thanks for getting back on this. Here is the simple code I have used and modified variously with similar or worse results.

Code:
void setup() {

  pinMode(2, INPUT);
  Serial.begin(1);
  }

void loop()
{   
    unsigned long start_time;
    start_time=micros();
    while(digitalRead(2)<1) {}
    while (digitalRead(2)) {    
    Serial.send_now();
    Serial.print(start_time); 
    Serial.print("*");
    //delayMicroseconds(100);
    }
}


I have tried the send.now and Serial.write command, used if statements, digitalReadFast, etc. Have also varied the pulse widths from 50 ns to 50 us and checked the integrity of the pulse signal. I can make things worse or inoperative, but not better. Clearly, I don't know what I'm doing.

Reading other info, interrupts may be useful, but they seem complicated and hopefully not necessary. The board has gobs of speed.

The observation dwell times are usually 20 minutes, (soon to be 30 minutes), well below that of a Micros rollover. The QB64 program looks for a bytestr, records the time stamp info and later processes the stored data for periodic events. Even with the Arduino's 10 us time stamp accuracy the QB program easily picks out any three periodic events (to 1 pulse/400 seconds) in many thousands of hits. More accuracy is wanted so as to expand the observation period and improve sensitivity by accepting more hits.

A GPS disciplined clock is used to continuously generate optical test pulses (1 pulse per 64 seconds) which are included as events and confirm system performance.
The Teensy clock frequency accuracy (correctable in software using the test pulse data) or minor drift (regulated temperature environment) aren't big concerns so long as the time stamp increments can be ~1 us.

Side note, I started programming in 1964, but never, ever though of myself as a programmer.
 
Last edited by a moderator:
I can look more shortly.

Is a continuous stream as shown needed for the duration of the event? A T_3.6 will overwhelm or overburden the host computer without constraining the print.

Perhaps a once per second <NAK> for no event

Followed by a one time START TIME STAMP

Then followed by a once per second <Ongoing> until the event stops
> perhaps the real needed <Ongoing> would be decorated as needed with other info?

On Stop a one time STOP EVENT TIME STAMP could be sent

Then returning to <NAK>
 
I have tried all kinds of delays from 1us to many milliseconds and placed them where they should do some good, but no help.

The "events" are pulses with widths that can be adjusted from a few nanoseconds to many microseconds. At the observatory, the pulse width
is normally set at 20us, but that can be changed if need be. Here in the little lab, the pulse width can be set over a much wider range.

The input to the Teensy board is terminated with 50 ohms and while there is a little ringing on the signal pulse top, it does not dip into active territory and would not
be expected to cause problems. Otherwise, the leading edge is clean.

BenS
 
Hi PaulS, thanks for the reply. I have spent a bit of time looking at posts for others trying to do something similar. but without luck. Interestingly, the next step for the kind of accuracy I need is the White Rabbit technology, but that is way beyond the need and my means. I'm hopeful that the Teensy can do the job.

BenS
 
Oh sorry, I didn't really answer your question about the pulse stream. Normally, the pulse rate is between 3 and 10 pulses per second. There are constraints in the QB (host) software that disallow pulses occurring to close together, i.e., <10ms between pulses.

BenS
 
We cross posted - 3 to 10 per second is good ... at least 10 ms apart.

I have tried all kinds of delays from 1us to many milliseconds and placed them where they should do some good, but no help.

The "events" are pulses with widths that can be adjusted from a few nanoseconds to many microseconds. At the observatory, the pulse width
is normally set at 20us, but that can be changed if need be. Here in the little lab, the pulse width can be set over a much wider range.

The input to the Teensy board is terminated with 50 ohms and while there is a little ringing on the signal pulse top, it does not dip into active territory and would not
be expected to cause problems. Otherwise, the leading edge is clean.

BenS

That helps, though adds some complexity? As Paul noted - it seems a few more details of what happens how often is needed.

So it seems the events are one time marks - not timing the duration of an event as it seemed the code showed. If they were held 50 us or 100 us that should allow an interrupt to capture them.

How frequent are the events? There was another thread where somebody wanted to count emitted things over a short interval but had a repeat rate that could be too high.

That one didn't have immediate output - just logging. Interrupts were not as fast as polling non-stop - but that was because the events came at a rate of perhaps 20M/sec in bursts, an interrupt was good for a couple million and polling could do a few million/sec but only when streaming to memory.

If these events are 100's of micros() apart then the code I did for that should be easy to map to this. That thread never got followed up - I'd have to go find the code I was testing - and I probably did it on Beta T4 just as a way to prove it working as expected.
 
The time stamping is only relative to the beginning of the session. It can be brought close to universal time through other means.
To clear up any confusion, the accuracy needed is from pulse leading edge to pulse leading edge while the total time accumulates through
the observing session. Even more explicitly, the time accuracy needed can be from the 1st pulse to the last pulse occurring over 30 minutes.
 
The time stamping is only relative to the beginning of the session. It can be brought close to universal time through other means.
To clear up any confusion, the accuracy needed is from pulse leading edge to pulse leading edge while the total time accumulates through
the observing session. Even more explicitly, the time accuracy needed can be from the 1st pulse to the last pulse occurring over 30 minutes.

That sounds easy enough. Is there another pin/signal that says 'Start' and 'End' Session?

Did I forget … how many total events per 30 minute session … up to 600 per minute for 30 minutes?

Just a list of 18,000 start times?
 
To clear up any confusion, the accuracy needed is from pulse leading edge to pulse leading edge while the total time accumulates through the observing session. Even more explicitly, the time accuracy needed can be from the 1st pulse to the last pulse occurring over 30 minutes.

By "leading edge", do you mean the transition from logic low to logic high?

If so, perhaps restructure your program like this. I added some comments to explain things a bit...

Code:
void setup() {
  pinMode(2, INPUT);
  Serial.begin(1);
}

void loop() {
  while (digitalRead(2) == LOW) {
    // Wait while the signal is LOW
  }
  
  // The signal has just gone from LOW to HIGH
  // so capture the timestamp
  unsigned long start_time;
  start_time = micros();
  
  // Print the timestamp just once, since this
  // is the beginning (LOW to HIGH) of a pulse
  Serial.print(start_time);
  Serial.print("*");
  Serial.send_now();
  
  while (digitalRead(2) == HIGH) {
    // Wait while the signal is HIGH
  }

  // Do nothing at the HIGH to LOW transition
}

Also, if you're just trying things, I highly recommend using the Arduino Serial Monitor to view the output instead of your own program. Maybe also change Serial.print("*") to Serial.println("*"), so each output prints on its own line which is easier to read.

Trying to make a project work by writing & troubleshooting code on both sides can be very frustrating. Use the serial monitor to get the Teensy side working perfectly. Then after you know the Teensy side is doing what you want, return to the PC side code. This may seem like an extra unnecessary step, but it really is so much easier.
 
No other pin has been needed. The host software ends after 30 min. Teensy is then on its own.
Right now, the next session picks up on the current Micros(), but a reset would be a plus.
The processing time for that many hits gets to be very long. You might wonder why not use FFT? I have in the past,
but it is no good for so few periodic events over such a long time period.
So, each interval between time stamps is compared with all others plus the sum of others plus a bunch of contingencies.
It's a triple nested mess that really works a desk top. Writing it did permanent damage to my head.
 
Thanks Paul,
That code looks a lot like stuff I have tried, but I'll definitely try it in the AM.
I had understood that the println took a lot of time. With the Arduino, it seemed to and that's
why I added the asterisk as a word terminator. I guess with Teensy's speed it's no longer necessary.
OK on the monitor thing. I had not considered it, but it makes a lot of sense.
 
Here is a version using interrupt - tested quickly.

Hopefully good enough to test the concept.
Code:
//	https://forum.pjrc.com/threads/57030-Teensy-3-6-as-a-precision-event-timer?p=211068#post211068
#define PIN_isr 2

#define MAX_SAMPLES 10*60*30
volatile uint32_t dSample[MAX_SAMPLES];

volatile uint32_t dScount = 0;
uint32_t L_dScount = 0;
uint32_t L_dSus = 0;

#define T_DEBOUNCE 3000
FASTRUN void pulse() { // this is not properly debounced if input has bounce
	dSample[dScount] = micros();
	detachInterrupt(digitalPinToInterrupt(PIN_isr));
	digitalWriteFast(LED_BUILTIN, !digitalReadFast(LED_BUILTIN));
	if ( dScount < (MAX_SAMPLES - 2) )
		dScount++;
}

void setup() {
	pinMode(LED_BUILTIN, OUTPUT);
	digitalWriteFast(LED_BUILTIN, HIGH);
	while (!Serial && millis() < 4000 );
	Serial.println("\n" __FILE__ " " __DATE__ " " __TIME__);

	// pinMode(PIN_isr, INPUT );
	pinMode(PIN_isr, INPUT_PULLDOWN );
	attachInterrupt(digitalPinToInterrupt(PIN_isr), pulse, RISING);
	// NVIC_SET_PRIORITY( ??? , 0);
}

void loop() {
	if ( L_dScount != dScount ) {
		delayMicroseconds( T_DEBOUNCE );
		attachInterrupt(digitalPinToInterrupt(PIN_isr), pulse, RISING);
		if ( dSample[L_dScount] > (L_dSus + (9000) ) ) {
			Serial.print( "Sample #" );
			Serial.print( L_dScount );
			Serial.print( " @us= " );
			Serial.print( dSample[L_dScount] );
			Serial.print( " us diff= " );
			Serial.print( dSample[L_dScount] - L_dSus );
			Serial.println();
			L_dSus = dSample[L_dScount];
		}
		L_dScount++; // just print one at a time
	}
}

NOTE: No provisions for last sample recover at this point - it could time out - but without a start signal that would be from when? Powerup?
Sorta got debounce aimed at 9 ms sample rate limit of 10 ms - seems to be close to working with a crummy switch
interrupt left off perhaps too long as written if the pin input #2 is clean
 
I had understood that the println took a lot of time. With the Arduino, it seemed to and that's
why I added the asterisk as a word terminator.

Nope, println is as fast as printing any other 2 chars.

I guess with Teensy's speed it's no longer necessary.

Comparing with Arduino Mega, Teensy 3.6 is so much faster, and not just by CPU speed. Use of Serial is native USB, not slow serial at a low baud rate.

The code you shared will almost certainly overwhelm a QuickBasic program on your PC with a flood of data while the signal remains high. You might not be able to really observe this well, depending on how your QB program is designed. The slow behavior of Mega might seem better, because it's able to transmit only a small fraction of the amount of data that Teensy can.

That's why I recommend using the serial monitor to get the Teensy side working well. Focus on getting just 1 side working perfectly in a way you can visually confirm in the serial monitor.
 
Nope, println is as fast as printing any other 2 chars.



Comparing with Arduino Mega, Teensy 3.6 is so much faster, and not just by CPU speed. Use of Serial is native USB, not slow serial at a low baud rate.

The code you shared will almost certainly overwhelm a QuickBasic program on your PC with a flood of data while the signal remains high. You might not be able to really observe this well, depending on how your QB program is designed. The slow behavior of Mega might seem better, because it's able to transmit only a small fraction of the amount of data that Teensy can.

That's why I recommend using the serial monitor to get the Teensy side working well. Focus on getting just 1 side working perfectly in a way you can visually confirm in the serial monitor.




Thanks to both of you for all the help. Paul's sketch is working well and I'm ever so pleased. I'm having trouble getting the interpreter version to go, but will work with it
until it does. For the next step, moving into the nanosecond range, the interpreter version may be a good solution. You guys have been great. Best from BenS
 
Glad it's working for you.

The way to get to ~20 ns precision is with FTM timer input capture. Probably the easiest way to get started is with the FreqMeasure library. (the hard way is to dive into the FTM chapter in the chip's reference manual)

The FTM timer is 16 bits and FreqMeasure extends it to 32 bits, but even 32 bits might not be enough numerical range for your application. Still, start with FreqMeasure. The code needed to extend the range is quite tricky. FreqMeasure is well tested, so it's definitely the best way to start.
 
Glad it's working for you.

The way to get to ~20 ns precision is with FTM timer input capture. Probably the easiest way to get started is with the FreqMeasure library. (the hard way is to dive into the FTM chapter in the chip's reference manual)

The FTM timer is 16 bits and FreqMeasure extends it to 32 bits, but even 32 bits might not be enough numerical range for your application. Still, start with FreqMeasure. The code needed to extend the range is quite tricky. FreqMeasure is well tested, so it's definitely the best way to start.



I'm anxious to start looking ahead to the nanosecond region. Even with a 500ns errorband, the advantages are great.

Back to the reality of today.

I took the board and software to the observatory and found a new problem. Teensy was happy with com5 in the lab, but now assigned itself to port 10. Ports 5 and 6 are available. I tried to change it and even though the check mark is on port 5, it hasn't really changed.

I tested the port 10 operation with the serial monitor and all is good, but QB64 doesn't like port 10 for some reason. I've fiddled and fiddled, but without success.

The lab and observatory computers are the same and I've made sure the QB64 versions are the same. Any thoughts?
 
Interrupt code above was tested on a Teensy 4 - just put it on a T_3.5 and it works except for the debounce timing with same clumsy button.



To access the cycle counter on T_3.6 it has to be turned on:
Code:
  ARM_DEMCR |= ARM_DEMCR_TRCENA; // Assure Cycle Counter active
  ARM_DWT_CTRL |= ARM_DWT_CTRL_CYCCNTENA;

Then reading it is just :: uint32_t foo = ARM_DWT_CYCCNT;

Where 2^32 rolls over continuously counting in less than 24 seconds at 180 MHz, and about 17.895 seconds at 240 Mhz, sonner at 256 MHz. So the ARM_DWT_CYCCNT and some more significant digits [ millis() ? ] will need to be taken with each sample to distinguish them in time with some extra math.

And on T_3.6 each tick is : (1/F_CPU) parts of a second.
 
Found the problem with the serial port. Glad I got back before anyone spent time on it.

Also, thank you defragster for the last. I hope that will get me going.
best from BenS
 
One more little bit. The system is now working in the observatory with short term accuracy of less than a microsecond. I'm running long term tests now and for the next 4 or 5 hours. Color me extremely happy.
 
No, it was a Windows thing. Another deep dark corner I'd never visited before.

Yes windows is what I meant and 'decoration' like this?::
Code:
You need to use the following format for COM ports greater than 9:
\\\\.\\COM%d
 
Status
Not open for further replies.
Back
Top