Need help with an interrupt / timer project.

Status
Not open for further replies.

jonesyro

Active member
Hi -

My project uses a fiber optics sensor to detect holes on a film strip, and after X amount of holes detected, a signal is sent to a camera to take a picture. This happens fast and continuously, up to 96 holes detected per second.

This is the sensor amplifier I use:
https://www.keyence.com/products/sensor/fiber-optic/fs-n40/models/fs-n41p/

..and this is the fiber optics sensor:
https://www.keyence.com/products/sensor/fiber-optic/fu/models/fu-20/

I chose the Teensy 4.1 for this project because of it's speed. The hardware for my machine is hooked up and working properly.

The code I have is very flawed and I can see issues when I run my machine - I should mention that I am very novice at coding.

This is the code I currently use, it detects 4 holes and then takes a picture.

Code:
const int sensor = 23;
int perf = 0; 
int var = 0;
int frame_counting = 0;
int cameraTrigger= 32;


void setup() {

   Serial.begin(115200);
  pinMode(led, OUTPUT);
     attachInterrupt(digitalPinToInterrupt(sensor), count, FALLING);

}


//
void count() {
  if(digitalRead(sensor) > var)
{
var = 1;
perf++;
if((perf % 1) == 0){
  frame_counting++;

  digitalWrite(cameraTrigger, HIGH);
delay(1);
digitalWrite(cameraTrigger, LOW);

    
Serial.print(frame_counting);
Serial.print(" perf");
Serial.println(" detected.");
}

}

if(digitalRead(sensor) == 0) {
    var = 0;}

delay(1);

}


void loop() {

}


I understand this is probably the wrong way to do this and that I am not using the fast timers on the Teensy. This is where I need some help.

How could I make this efficient and precise.

Also not sure if it's appropriate to ask here but I would offer this as a paid gig for anyone interested. Please let me know if that's not within the forum rules here and I'll delete this bit. If it's ok - and anyone is interested please PM me.

Picture of the prototype machine below:

thanks!

IMG_4089.jpg
 
Just remove the delay() and the Serial.print.
Delay() is unpredictable with the short times.

If a delay is really needed, replace it by delayNanoseconds().
 
Just remove the delay() and the Serial.print.
Delay() is unpredictable with the short times.

If a delay is really needed, replace it by delayNanoseconds().

Thanks for the recommendation - I just tried it but it breaks the code, the counter gets out whack without the delay :(
 
Firstly there's no need for interrupts for something this slow (from the Teensy 4's perspective :)). You have two
tasks, counting incoming pulses and generating outgoing pulses - the delay you use for the outgoing pulse should
not be affecting the former.

Have you looked at the BlinkWithoutDelay example sketch - this is basically how to drive the output pulses without
slowing down the input detection.
 
Ok, make the variables you use volatile. Then it should not happen anymore.

Thanks Frank - Appreciate the help, I tried but it's still not working.

When hooking an oscilloscope on the sensor itself I see every pulses (1 per hole), on the Teensy with my code, and with the "perfs" variable set to 1 (to replicate the pulses as if it was hooked up directly to sensor) it randomly skips pulses. When I set it to 4 pulses to one output trigger it's the same..

If anyone has any recommendations where to find microcontroller programmers for hire who work with Teensy/Arduino please let me know.
 
I'd first check the hardware with a simple sketch like shown below. Whenever it registers a falling edge at the sensor input it simply toggles the cameraTrigger.
Code:
#include "Arduino.h"

constexpr int sensor        = 23;
constexpr int cameraTrigger = 32;

void onDetect()
{
    digitalToggleFast(cameraTrigger);
}

void setup()
{
    pinMode(cameraTrigger, OUTPUT);
    attachInterrupt(sensor, onDetect, FALLING);
}

void loop() {
}

Connect your scope to the sensor pin and the cameraTrigger pin. You should get something like

Screenshot 2021-11-29 072828.png

Depending on the signal quality of the sensor the interrupt might trigger a couple of times per one signal edge, so look out for fast (<µs) signal transitions on the trigger output. Would be good if you could post a screenshot/photo of the scope trace.
 
I'd first check the hardware with a simple sketch like shown below. Whenever it registers a falling edge at the sensor input it simply toggles the cameraTrigger.
Code:
#include "Arduino.h"

constexpr int sensor        = 23;
constexpr int cameraTrigger = 32;

void onDetect()
{
    digitalToggleFast(cameraTrigger);
}

void setup()
{
    pinMode(cameraTrigger, OUTPUT);
    attachInterrupt(sensor, onDetect, FALLING);
}

void loop() {
}

Connect your scope to the sensor pin and the cameraTrigger pin. You should get something like

View attachment 26705

Depending on the signal quality of the sensor the interrupt might trigger a couple of times per one signal edge, so look out for fast (<µs) signal transitions on the trigger output. Would be good if you could post a screenshot/photo of the scope trace.


That's a very good call - I don't have a 2 channels scope but will order one.
 
@luni could you describe the conditions where an edge interrupt can be triggered multiple times for the same edge?
 
@shawn: see e.g. here: https://forum.pjrc.com/threads/6674...sy-4-0-problem?p=274793&viewfull=1#post274793. Also, the sensor seems to have an open collector output. How is that connected? Does it work reliably with 3.3V? Lot of stuff can go wrong on the hardware side which I personally would check before debugging software.

@jonesyro: Here a quick sketch showing how to generate the camera pulses in case the hardware works. I assume that you need some delay after you detected a 'hole' to optimize timing and added this. When the sensor detects the edge it triggers a one shot timer with the required delay time. The callback of this timer sets the cameraTrigger high and triggers another timer which resets the cameraTrigger after the integration time. Pretty straight forward.

Code:
#include "Arduino.h"
#include "TeensyTimerTool.h"
using namespace TeensyTimerTool;

constexpr int sensor        = 23;
constexpr int cameraTrigger = 32;

int delayTime       = 2500; // camera opens 2.5ms after sensor edge detected
int integrationTime = 1000;
int nrOfHoles       = 4;

OneShotTimer t_start(TCK);
OneShotTimer t_stop(TCK);

void onDetect()
{
    static int cnt = 0;
    if (++cnt == nrOfHoles)
    {
        cnt = 0;
        t_start.trigger(delayTime); // switch on cammera after the delay time
    }
}

void onStartIntegration()
{
    digitalWriteFast(cameraTrigger, HIGH); // start integration
    t_stop.trigger(integrationTime);       // switch camera off after integration time
}

void onStopIntegration()
{
    digitalWriteFast(cameraTrigger, LOW); // stop integration
}

void setup()
{
    pinMode(cameraTrigger, OUTPUT);

    t_start.begin(onStartIntegration);
    t_stop.begin(onStopIntegration);

    attachInterrupt(sensor, onDetect, FALLING);
}

void loop()
{
}

Result with a delay of 2.5ms and an integration time of 1ms:

Screenshot 2021-11-30 080844.png

Zoomed in:

Screenshot 2021-11-30 080922.png
 
Thanks very much @Luni for debugging this and your code.

I really appreciate it. I'm getting the scope tomorrow or the day after and will update with results. Makes a lot of sense to check hardware first, the 2 channels scope will take care of that.



@shawn: see e.g. here: https://forum.pjrc.com/threads/6674...sy-4-0-problem?p=274793&viewfull=1#post274793. Also, the sensor seems to have an open collector output. How is that connected? Does it work reliably with 3.3V? Lot of stuff can go wrong on the hardware side which I personally would check before debugging software.

@jonesyro: Here a quick sketch showing how to generate the camera pulses in case the hardware works. I assume that you need some delay after you detected a 'hole' to optimize timing and added this. When the sensor detects the edge it triggers a one shot timer with the required delay time. The callback of this timer sets the cameraTrigger high and triggers another timer which resets the cameraTrigger after the integration time. Pretty straight forward.

Code:
#include "Arduino.h"
#include "TeensyTimerTool.h"
using namespace TeensyTimerTool;

constexpr int sensor        = 23;
constexpr int cameraTrigger = 32;

int delayTime       = 2500; // camera opens 2.5ms after sensor edge detected
int integrationTime = 1000;
int nrOfHoles       = 4;

OneShotTimer t_start(TCK);
OneShotTimer t_stop(TCK);

void onDetect()
{
    static int cnt = 0;
    if (++cnt == nrOfHoles)
    {
        cnt = 0;
        t_start.trigger(delayTime); // switch on cammera after the delay time
    }
}

void onStartIntegration()
{
    digitalWriteFast(cameraTrigger, HIGH); // start integration
    t_stop.trigger(integrationTime);       // switch camera off after integration time
}

void onStopIntegration()
{
    digitalWriteFast(cameraTrigger, LOW); // stop integration
}

void setup()
{
    pinMode(cameraTrigger, OUTPUT);

    t_start.begin(onStartIntegration);
    t_stop.begin(onStopIntegration);

    attachInterrupt(sensor, onDetect, FALLING);
}

void loop()
{
}

Result with a delay of 2.5ms and an integration time of 1ms:

View attachment 26725

Zoomed in:

View attachment 26726
 
@shawn: see e.g. here: https://forum.pjrc.com/threads/6674...sy-4-0-problem?p=274793&viewfull=1#post274793. Also, the sensor seems to have an open collector output. How is that connected? Does it work reliably with 3.3V? Lot of stuff can go wrong on the hardware side which I personally would check before debugging software.

@jonesyro: Here a quick sketch showing how to generate the camera pulses in case the hardware works. I assume that you need some delay after you detected a 'hole' to optimize timing and added this. When the sensor detects the edge it triggers a one shot timer with the required delay time. The callback of this timer sets the cameraTrigger high and triggers another timer which resets the cameraTrigger after the integration time. Pretty straight forward.

Result with a delay of 2.5ms and an integration time of 1ms:

View attachment 26725

Zoomed in:

View attachment 26726


I got the 2 channel scope this am.

Hooked up channel 1 (yellow) to the trigger ground and signal. Channel 2 is on ground and Teensy pin 32. (this is the output to the camera)

The original code I posted above confirms what I was seeing in camera. It randomly miss 1 hole. see screenshot below:

OriginalCode.jpg

With @luni code it seems to send multiple little signals here's the scope for this code.

Luni.jpg

and zoomed in:

luni_zoomed.jpg

More details:

- The trigger outputs 12V so I send the signal through an "IC 7805 Voltage Regulator" and then a 5V to 3.3V regulator so I don't fry the Teensy 4.1
- The direct signal from the trigger is constant, not missing holes.

I'm new to scopes so bare with me. I hooked up the probes and did an "Auto Setup", I ran the machine and captured some signals, hit the "RUN/STOP" button to freeze what I was seeing and zoomed in using the horizontal& vertical positions. Maybe there's a better way, I haven't messed with triggers etc.. very basic setup but it shows the issue.

Thanks for checking this out.
 
- The trigger outputs 12V so I send the signal through an "IC 7805 Voltage Regulator" and then a 5V to 3.3V regulator so I don't fry the Teensy 4.1

This is absolutely not the way how to do it. I'm surprised that this even works. You should use a real level shifter, something like a 74LVC125 or similar. (I need to leave now, so no time to explain further but I'm sure others will chime in).

Anyway, since in seems to "work" somehow I'd be interested in what happens if you do a pinmode(sensor, INPUT_PULLUP)? This will activate the schmitt trigger feature on the input pin which might be enough to get rid of the multiple pulses.

Edit: sorry, the 74LVC can probably not handle your 12V output...
 
This is absolutely not the way how to do it. I'm surprised that this even works. You should use a real level shifter, something like a 74LVC125 or similar. (I need to leave now, so no time to explain further but I'm sure others will chime in).

Anyway, since in seems to "work" somehow I'd be interested in what happens if you do a pinmode(sensor, INPUT_PULLUP)? This will activate the schmitt trigger feature on the input pin which might be enough to get rid of the multiple pulses.

Edit: sorry, the 74LVC can probably not handle your 12V output...

Thanks @luni

The input_pullup fixed the multiple pulses but it's still acting whacky. Skipping pulses from time to time.

I went back to basics with this code and I'm getting really precise triggers on every hole - no missed pulses, rising edges are matched perfectly.

This is more precise than my original code and the new one.

Code:
const int sensor = 23;
int cameraTrigger= 32;

void setup() {

   pinMode(sensor, INPUT);
  pinMode(cameraTrigger, OUTPUT);

}


void loop() {
  
int val = digitalRead(sensor);

if(val == HIGH)

{
  digitalWrite(cameraTrigger, HIGH);
}
else
{
    digitalWrite(cameraTrigger, LOW);
}

}

Now if the counting could be done using this simple code that would fix all my issues. Detect 4 holes, send the signal to the camera.. [repeat]

Maybe I don't need to mess with interrupts? It's not like this is going that fast and the code above seems to be solid for single hole triggers.

Here's the scope:

IMG_4118.jpg

and zoomed:

IMG_4119.jpg
 
Can you zoom into the falling edge? Since the signal is going down very slow, I'd be surprised if you don't see multiple transitions if you look closely. This would generate the same counting issues as you see with the interrupt based code.

Anyway, you can try this:
Code:
#include "Arduino.h"

constexpr int sensor        = 23;
constexpr int cameraTrigger = 32;

void setup()
{
    pinMode(sensor, INPUT_PULLDOWN);
    pinMode(cameraTrigger, OUTPUT);

    analogWriteFrequency(0, 96);
    analogWrite(0, 128);
}

unsigned cnt    = 0;
unsigned oldVal = LOW;

void loop()
{
    unsigned val = digitalRead(sensor);
    if (val != oldVal)
    {
        if (val == HIGH) // rising edge
        {
            cnt++;
            if (cnt >= 4)
            {
                digitalWrite(cameraTrigger, HIGH);
                cnt = 0;

                delayMicroseconds(500);
            }
        }
        else // falling edge
        {
            digitalWrite(cameraTrigger, LOW);
            delayMicroseconds(500);
        }
        oldVal = val;
    }
}

Screenshot 2021-12-03 080243.jpg

I changed the pin mode to INPUT_PULLDOWN to activate the Schmitt trigger on the pin which should help reducing multiple transitions. I also added a delay after detection of a signal edge which might also help. You can increase the time if necessary.

And: did I mention already that voltage regulators are not meant to be used for level shifting? :eek:
 
Can you zoom into the falling edge? Since the signal is going down very slow, I'd be surprised if you don't see multiple transitions if you look closely. This would generate the same counting issues as you see with the interrupt based code.

Anyway, you can try this:
Code:
#include "Arduino.h"

constexpr int sensor        = 23;
constexpr int cameraTrigger = 32;

void setup()
{
    pinMode(sensor, INPUT_PULLDOWN);
    pinMode(cameraTrigger, OUTPUT);

    analogWriteFrequency(0, 96);
    analogWrite(0, 128);
}

unsigned cnt    = 0;
unsigned oldVal = LOW;

void loop()
{
    unsigned val = digitalRead(sensor);
    if (val != oldVal)
    {
        if (val == HIGH) // rising edge
        {
            cnt++;
            if (cnt >= 4)
            {
                digitalWrite(cameraTrigger, HIGH);
                cnt = 0;

                delayMicroseconds(500);
            }
        }
        else // falling edge
        {
            digitalWrite(cameraTrigger, LOW);
            delayMicroseconds(500);
        }
        oldVal = val;
    }
}

View attachment 26768

I changed the pin mode to INPUT_PULLDOWN to activate the Schmitt trigger on the pin which should help reducing multiple transitions. I also added a delay after detection of a signal edge which might also help. You can increase the time if necessary.

And: did I mention already that voltage regulators are not meant to be used for level shifting? :eek:


Well that did it! Super precise trigger now. Thanks a lot @Luni - now I got to figure out how to shift the levels correctly :) Next part will be adding ability to set the pulse length out of pin 32
 
Note that logic input signals have a _minimum_ slew-rate specification, typically for a fast CMOS chip it might be over 100V/µs.
The T4.x chip requires logic transitions to be 25ns or faster when a pin is not in hysteresis mode, which is ~130V/µs.

This is to prevent false transitions due to noise on the inputs and supply rail while the input voltage is in the forbidden zone
(which is 1V--2.3V for the T4.x when not in hysteresis mode). CMOS inputs, unless Schmitt-trigger, can act as high gain
analog amplifiers and amplify noise greatly at the mid-voltage point.
 
Status
Not open for further replies.
Back
Top