Timing a pulse train of microsecond events on a 15Hz period

Status
Not open for further replies.
I'm trying to generate a set of timing pulses out of a teensy 3.2, into a pair of external lasers and flashlamps, and a camera. Currently using TeensyTimerTool and the tck64 timers and delaymicroseconds. I'm on windows, but I'm not interacting with the microcontroller after it is initiated.

I'm getting some results, but I'm not out of the woods, and I'd like advice. I would happily change teensies, libraries, clockrate...
I'm pretty sure I'm making some basic mistakes, and I've marched right out on a skinny limb of inexperience and hubris.
I am a certified coding impostor: I can show you my classics degree if you need to see it.

Here's my goal:
8 signals on a microseconds scale, repeating at 15 Hz.
There's two sets of lasers which need warm-up times (~135us) which work to illuminate a scene.
There's a camera which needs an 'expose' signal.
There is an analog-in (10kpot) that I'd like to use to vary the timing between the two exposures (2nd illuminator and 2nd frame exposure).
The manual trigger is on an interrupt with a debounce in the callback.
Attached is a timing diagram. Sorry, it's a little busy.

What works.
15 hz is good. The whole pulse train crunches along at a very periodic rate:14.997.
Signals happen in proper sequence: exposures happen over the strobes.
Jitter is low enough, looks like 1-2us over the 200us of a pulse train.

What's still broken:
Each restart, I have an indeterminacy due to the way I'm initializing the timers. It's amazing that
it works as it does: (8)64 bit counter screaming along at 98MHz lining up most of the time. but for example, a requested 98us delay measures to 108, 122 or 146. Might work in practice, but it's unsettling.
Also, the pulse train degrades after multiple manual interrupts. That's not how it will be used,
but it is a sign of instability.

When I try to do analog sampling of the duration variable, things get unstable a bit more quickly.

What I've tried.
I used IntervalTimer to start. That worked amazingly well. Felt like hardware. But it is limited to 16 bit, and I can have only 4. I could use a separate teensy... but.
Also, the 1/15Hz = 66,666 us. (mark of the devil!) which is > 65,536. Rats.
I tried TCK, but, for some reason, TCK64 is more stable than TCK. So I have been using that. I don't think it will rollover till global warming has long effected human extinction. I gave an approach with elapsedms a try, but didn't work out how to integrate that.

I will probably figure this out eventually, but if anyone feels like lending an experienced suggestion, specific or general, I'd be grateful.
Code below:
Code:
#include "Arduino.h"
#include "TeensyTimerTool.h"

using namespace TeensyTimerTool;

const int buttonPin = 15;

int potPin = A9;                  // 0-3.3v on pin 23
volatile int potValue = 0;
volatile int usVal = 100;         // default start value for sample delay

PeriodicTimer lamp1(TCK64);             // trying software timers. these all are 66.6k, so >32bit
PeriodicTimer qTrig1(TCK64) ;           // 
PeriodicTimer lamp2(TCK64)  ;
PeriodicTimer qTrig2(TCK64) ;
PeriodicTimer camTrig10n(TCK64);
PeriodicTimer camTrig1Off(TCK64);
PeriodicTimer camTrig2On(TCK64);
PeriodicTimer camTrig2Off(TCK64);

//Callbacks
void pulseLamp1()
{
  digitalWriteFast(1,HIGH);
  delayMicroseconds(10);          //10us min, 11 for saftey?
  digitalWriteFast(1,LOW);
}
void pulseLamp2()
{
  digitalWriteFast(3,HIGH);
  delayMicroseconds(10);
  digitalWriteFast(3,LOW);
}
void pulseQ1()
{
  digitalWriteFast(2,HIGH);
  delayMicroseconds(10);
  digitalWriteFast(2,LOW);  
}
void pulseQ2()
{
  digitalWriteFast(4,HIGH);
  delayMicroseconds(10);
  digitalWriteFast(4,LOW);
}

void camStartExpose()
{
  digitalWriteFast(5,HIGH);
}

void camStopExpose()
{
   digitalWriteFast(5,LOW);
}

void trigEvent()
{
  delay(10);                      // 10 ms delay after trigger event for debounce
  if (digitalRead(buttonPin) == LOW){
    digitalWriteFast(13,HIGH);
    delayMicroseconds(100000);      // long enough to see it.
    digitalWriteFast(13,LOW);  
 }
}
// elapsedMicros camStart1;            // needs to be global, so as to not reset with each loop.
// elapsedMicros camStop1;

void setup()
{
  for (unsigned pin =0; pin<=13; pin++) pinMode(pin,OUTPUT);  //include 13 for led diags.
  //pinMode(buttonPin, INPUT_PULLUP);       // 15 is input, normally pulled up, pulled down by hand switch
  lamp1.begin(pulseLamp1,   66'666);      //no delay, start whole process
  delayMicroseconds(98);                  //todo: turn this const to var fr AD(var from const makes unstable)
  lamp2.begin(pulseLamp2,   66'666);      // 98us delay becomes ~108, or 122
  delayMicroseconds(25);                  // 138-(108+5usLatency)=25 how to param this? quadr enc? 
  camTrig10n.begin(camStartExpose,66'666);    // Trigger for first frame.
  delayMicroseconds(2);
  qTrig1.begin(pulseQ1,           66'666);    // tests out to 3us. ok.
  delayMicroseconds(25);                      // exposure randomly selected to bracket laser pulse.         
  camTrig1Off.begin(camStopExpose, 66'666);   // 25us gets stopped at 62us exposure.or 49,or 38 hmm.
  delayMicroseconds(30);                      // remainder buffer to space exposures. >2us, 30 catches pulse 
  camTrig2On.begin(camStartExpose, 66'666);   // same function, but different timer name for 2nd frame. 
  delayMicroseconds(2);
  qTrig2.begin(pulseQ2,            66'666);
  delayMicroseconds(30);                      // Expose  long enough to catch wherever laser pulse lands.
  camTrig2Off.begin(camStopExpose, 66'666);
  // Manual trigger pushbutton: 
  attachInterrupt(digitalPinToInterrupt(buttonPin),trigEvent,FALLING);    
}
void loop()
{   
    //potValue = analogRead(potPin);
    //usVal = map(potValue, 0,1023, 100, 500);  //takes 0-3.3V analog value, maps to 100-500
    //delay(100);
    
}

TimerDiagram5.png
TDSPulseTrain5.jpg
 
I'd look at turning off interrupts and just polling ARM_DWT_CYCCNT to do a variety of tasks with perhaps .1 usec accuracy.
 
Interesting way to do this. I'd pack that into 3 simple state machines. One for each laser and one for the camera. Anyway should work with the tick timers as well.

Following remarks:

The tick timers rely on calling an internal tick function during yield() as often as possible. delayMicroseconds blocks yield, so during a delayMicroseconds the timers have no chance to fire the callback. They don't loose counts (the underlying cycle counter runs always) but you can get weird effects. E.g. all callbacks scheduled while you are in a delayMicroseconds will fire after the delay period. If your delayMicroseconds is longer than the wrap period of the cyclecounter (7s for a T4) you'll mess things up.

Relying on the delays between begin is kind of optimistic. The begin functions itself need some time. It might be better to begin all timers, one after the other, in stopped mode, i.e., use lamp1.begin(pulseLamp1, 66'666, false); Then try
Code:
  noInterrupts();
  lamp1.start();
  delayMicroseconds(98);
  lamp2.start();
  delayMicroseconds(25);
  //....
  interrupts();
(Actually I'm not sure if the disabled interrupts will allow delayMicroseconds to work, just try it...)

The long delayMicroseconds in your pin interrupt handler will definitely do some harm. If a callback is scheduled in this time they will fire after that. Use delay(100) instead this calls yield while waiting.

I'm about to release a new version of the lib this evening (Germany). Besides others it will have a much more stable frequency for the periodic tick timers. You can try the debug branch if you want.
 
for this part - "Also, the 1/15Hz = 66,666 us. (mark of the devil!) which is > 65,536." --could you have a timer for 1/30 Hz and then in the callback just skip every 2nd one ?
 
I used IntervalTimer to start. That worked amazingly well. Felt like hardware. But it is limited to 16 bit, and I can have only 4. I could use a separate teensy... but.
Also, the 1/15Hz = 66,666 us. (mark of the devil!) which is > 65,536. Rats.

for this part - "Also, the 1/15Hz = 66,666 us. (mark of the devil!) which is > 65,536." --could you have a timer for 1/30 Hz and then in the callback just skip every 2nd one ?

The interval timers are definitely 32bit. But as you mentioned, you only have 4 of them.
 
I'd look at turning off interrupts and just polling ARM_DWT_CYCCNT to do a variety of tasks with perhaps .1 usec accuracy.

ok. I found your thread, 'Sub microsecond timing...' where you implement this. I think I follow that code. Thanks!
 
Interesting way to do this. I'd pack that into 3 simple state machines.

I'm off to learn about state machines and case switches. Hope I'm done before you're done with the new library.

delayMicroseconds blocks yield
did not realize this. thanks.
Relying on the delays between begin is kind of optimistic.
How generous. : )
..might be better to begin all timers, one after the other, in stopped mode, i.e., use lamp1.begin(pulseLamp1, 66'666, false); Then try
Code:
  noInterrupts();
  lamp1.start();
  delayMicroseconds(98);
  lamp2.start();
  delayMicroseconds(25);
  //....
  interrupts();
Great. I was a bit confused about how to use the begin and start functions. Thanks. I'll take a stab at it now.
The long delayMicroseconds in your pin interrupt handler will definitely do some harm. If a callback is scheduled in this time they will fire after that. Use delay(100) instead this calls yield while waiting.
yes! that was a debug for a different mistake. (failed to pullup; fixed) Nonetheless, reduced to even delay(1) scrambles the pulses. Debounce will probably have to happen differently.
I'm about to release a new version of the lib this evening (Germany). Besides others it will have a much more stable frequency for the periodic tick timers. You can try the debug branch if you want.
Yahoo. I'll look for it on github and see if I can download it properly.
 
Actually I already worked out something which is working nice (submicrosecond jitter on all timings). Since it turned out to be a nice application of the TeensyTimerTool I'd like to publish it as an example to the lib if you don't care.

I'm currently busy with my daytime job. So it will take a few hours before I can put it on GitHub.
 
Wow, that's too kind. But fantastic! Thank you! Hopefully I can contribute some working snapshots to the wiki application page over the next month as the system comes up.
 
for this part - "Also, the 1/15Hz = 66,666 us. (mark of the devil!) which is > 65,536." --could you have a timer for 1/30 Hz and then in the callback just skip every 2nd one ?

clever. would _have_ to work... (sound of very rusty gears turning in my brain...) The other limit of the Intervaltimers was that there were only 4. That's what pushed me to software timers. It seems jonr and luni are able to get low jitter out of polling the cycle counters. Seems if I'm a bit more careful in the rest of the program, these could work. I will resort to the '30Hz callback flip flop skipper' if I can't get the software timers to behave. Thanks jp3141.
 
I released version 0.2.1 of the TeensyTimerTool on gitHub and added the example DoubleExposure
https://github.com/luni64/TeensyTimerTool/tree/master/examples/DoubleExposure. Documentation will be added later.

...Hopefully I can contribute some working snapshots to the wiki application page over the next month as the system comes up.
Yes, I'd be very much interested...


Here what you can do with it:

Code:
#include "ResponsiveAnalogRead.h"
#include "SystemController.h"

constexpr unsigned potPin = A0;
ResponsiveAnalogRead pot(potPin, false);

SystemController controller;

void setup()
{
    controller.begin();

    controller.setExposureDelay(250); // set exposure delay (time between two exposures) to 250 µs
    controller.shoot();               // do a manual exposure
    delay(10);
    controller.setExposureDelay(500); // same with 500µs delay between exposures
    controller.shoot();

    controller.continousMode(true);   // start continously shooting
    delay(1000);
    controller.continousMode(false);  // stop after one second

    delay(500);
    controller.continousMode(true);   // start again
}

void loop()
{
    // --> Uncomment if you have a control voltage on the pot pin  <--
    // pot.update();
    // if (pot.hasChanged())
    // {
    //     unsigned expDelay = map(pot.getValue(), 0, 1023, 100, 500); // 0-3.3V analog value, maps to 100-500
    //     controller.setExposureDelay(expDelay);
    //     Serial.printf("Exposure Delay: %u µs\n", expDelay);
    // }
}

Here a measurement for an exposure delay of 500µs. I didn't do very intensive testing yet. By simply looking at a few events and measuring with the LA I didn't observe any jitter larger than 1µs.

timing_measurement1.jpg

I'll add some explanation to the WIKI later but the code should be pretty much self explaining I hope. Let me know if this works for you.
 
Last edited:
Pretty dang solid!
Small changes:
I added 10us to the camdelay constant to slide it under the light pulse. Might've worked, but it was on the edge.
I enabled the analog in, and riding up and down from 100us to 500us is super smooth!
I did run into trouble at 135us delay: triggerDelayedOverwrite Cam.jpg

A buddy helped me trace it back to PulsedDevice::triggerDelayed(). So I took out the Write low, or moved it up 6 lines, and the problem went away.

Code:
void PulsedDevice::begin(unsigned pin, float delay, float duration)
{
    this->pin = pin;
    pinMode(pin, OUTPUT);
    digitalWriteFast(pin, LOW);             //CS added this line, to start out low.

    pulseTimer.begin([this] { this->callback(); });
    pulseTimer.getTriggerReload(duration, &durationReload); // precalculate reload values to speed up trigger functions
    pulseTimer.getTriggerReload(delay, &delayReload);
}

void PulsedDevice::triggerDelayed()
{
    //digitalWriteFast(pin, LOW);           // CS triggerDelayed was overwriting CamPin at 135us CamDelay
    pulseTimer.triggerDirect(delayReload);
}

He also had a question about SystemController::continuousMode: is there an extra semicolon, or else how does it work?
Code:
void SystemController::continousMode(bool on)
{
    if (on)
        mainTimer.start();
    else
        mainTimer.stop();
}

Thank you Luni64. I will be studying the code for a while yet. Lots in there for me to learn, but now I can learn with something that works!
 
I will be studying the code for a while yet. Lots in there for me to learn, but now I can learn with something that works!
This is why I thought it might make a good example for the library on GitHub :))

I added 10us to the camdelay constant to slide it under the light pulse.
Good, I was wondering why you placed the integration time such that it is barely overlapping the trigger pulse. But since the laser fires 140ns after the trigger I thought this was by purpose...

I did run into trouble at 135us delay
Ah, I didn't consider the case that the cam pin is triggered while it is still active. Your fix is perfect of course


He also had a question about SystemController::continuousMode: is there an extra semicolon, or else how does it work?
Code:
void SystemController::continousMode(bool on)
{
if (on)
mainTimer.start();
else
mainTimer.stop();
}
Hm, I tried hard but I don't see anything strange here? It is a simple if/else clause? All semicolons are placed correctly?

Wiki
I meanwhile simplified the code a bit and added a first version of the description in the WIKI. https://github.com/luni64/TeensyTimerTool/wiki/Double-Exposure-Laser-Illuminator Adding some photos and/or some explanation of the setup would be great of course. If you want, just open an issue on GitHub and post anything you want me to add.
 
Status
Not open for further replies.
Back
Top