Forum Rule: Always post complete source code & details to reproduce any issue!
Results 1 to 14 of 14

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

  1. #1
    Junior Member
    Join Date
    Jun 2020
    Location
    Massachusetts
    Posts
    6

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

    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);
        
    }
    Click image for larger version. 

Name:	TimerDiagram5.png 
Views:	29 
Size:	60.4 KB 
ID:	21465
    Click image for larger version. 

Name:	TDSPulseTrain5.jpg 
Views:	14 
Size:	82.9 KB 
ID:	21466

  2. #2
    Senior Member
    Join Date
    May 2015
    Location
    USA
    Posts
    631
    I'd look at turning off interrupts and just polling ARM_DWT_CYCCNT to do a variety of tasks with perhaps .1 usec accuracy.

  3. #3
    Senior Member
    Join Date
    Apr 2014
    Location
    Germany
    Posts
    1,118
    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.

  4. #4
    Senior Member
    Join Date
    Apr 2014
    Location
    Germany
    Posts
    1,118
    Quote Originally Posted by jonr View Post
    I'd look at turning off interrupts and just polling ARM_DWT_CYCCNT ...
    The tick timers do exactly that.

  5. #5
    Senior Member Jp3141's Avatar
    Join Date
    Nov 2012
    Posts
    486
    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 ?

  6. #6
    Senior Member
    Join Date
    Apr 2014
    Location
    Germany
    Posts
    1,118
    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.

  7. #7
    Junior Member
    Join Date
    Jun 2020
    Location
    Massachusetts
    Posts
    6
    Quote Originally Posted by jonr View Post
    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!

  8. #8
    Junior Member
    Join Date
    Jun 2020
    Location
    Massachusetts
    Posts
    6
    Quote Originally Posted by luni View Post
    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.

  9. #9
    Senior Member
    Join Date
    Apr 2014
    Location
    Germany
    Posts
    1,118
    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.

  10. #10
    Junior Member
    Join Date
    Jun 2020
    Location
    Massachusetts
    Posts
    6
    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.

  11. #11
    Junior Member
    Join Date
    Jun 2020
    Location
    Massachusetts
    Posts
    6
    Quote Originally Posted by Jp3141 View Post
    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.

  12. #12
    Senior Member
    Join Date
    Apr 2014
    Location
    Germany
    Posts
    1,118
    I released version 0.2.1 of the TeensyTimerTool on gitHub and added the example DoubleExposure
    https://github.com/luni64/TeensyTime...DoubleExposure. Documentation will be added later.

    Quote Originally Posted by Chrisstokes View Post
    ...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.

    Click image for larger version. 

Name:	timing_measurement1.jpg 
Views:	15 
Size:	54.9 KB 
ID:	21479

    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 by luni; 08-26-2020 at 11:32 AM.

  13. #13
    Junior Member
    Join Date
    Jun 2020
    Location
    Massachusetts
    Posts
    6
    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: Click image for larger version. 

Name:	triggerDelayedOverwrite Cam.jpg 
Views:	12 
Size:	114.5 KB 
ID:	21488

    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!

  14. #14
    Senior Member
    Join Date
    Apr 2014
    Location
    Germany
    Posts
    1,118
    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/TeensyTime...er-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.

Posting Permissions

  • You may not post new threads
  • You may not post replies
  • You may not post attachments
  • You may not edit your posts
  •