Solved: IR led signal emission unreliable (remote camera trigger).

Hi everyone,

I have a project that involve moving and triggering my canon dslr camera with arduino uno. Being honest, I went a bit empirically there.

This is a quick description of my system: one nema 23 is moving the camera on the X axis, two nema 23 are used to move the Y axis. Each have a PB6600 driver. The movement is a simple grid pattern programmed with “for loop”: move one step in a given direction, take a shot, move.. etc. then go back to initial position. The push button is used to start or restart the pattern. The SystemLED is on when the system is moving. I found what seemed like an IR led in my stuff and used it to trigger the camera. Since I did not have the spec of the thing, I assumed something like 1.5V, 20mA. I used the multiCameraIrControl library (Arduino/libraries/multiCameraIrControl at master · dharmapurikar/Arduino · GitHub) to take care of the signal.

I provide the diagram in attachment.

I tested the “IR module” with a simple loop:
test=

void loop() {
myCamera.shotnow();
delay(500);
}

And check that it worked continuously without fail. So the IR Led, Camera and Control library are fine.

BUT

When my final code is running, there is a ~ 25% rate of failure (i.e. camera not triggering).

Here it is:

#include <multiCameraIrControl.h>
Canon DSLR(12); /* IR led */

volatile bool systemState = false;
volatile byte systemLedState = LOW;
volatile long switchTime = 0;
volatile long prevSwitchTime = 0;

// Define stepper motor connections and steps per revolution:
const int interruptPin = 2;
const int XdirPin = 3;
const int XstepPin = 4;
const int YdirPin = 5;
const int YstepPin = 6;
volatile int systemLedPin = 13;
const int stepsPerRevolution = 1600;
const int debouncingDelay = 400;

void setup() {
  // Declare pins as output:
  for (byte i = 3; i < 14; i++) {
    pinMode(i, OUTPUT);
  }
  pinMode(interruptPin, INPUT);
  // Set the spinning direction CW/CCW:
  digitalWrite(XdirPin, HIGH);
  digitalWrite(YdirPin, HIGH);
  digitalWrite(systemLedPin, systemLedState);
  attachInterrupt(digitalPinToInterrupt(interruptPin), switchOn, RISING);
  systemState = false;
}

void switchOn() {
  switchTime = millis();
  if (switchTime - prevSwitchTime > debouncingDelay) {
    prevSwitchTime = switchTime;
    systemState = !systemState;
    systemLedState = !systemLedState;
  }
}

void sendpulse(byte stepPin, int pulsefreq) {
  digitalWrite(stepPin, HIGH);
  delayMicroseconds(pulsefreq);
  digitalWrite(stepPin, LOW);
  delayMicroseconds(pulsefreq);
}

void course(byte stepPin, int steps, int pulsefreq) {
  for (int i = 0; i < steps; i++) {
  // These four lines result in 1 step:
  if (!systemState) {return;}
    sendpulse(stepPin, pulsefreq);
  }
}

void scanStage() {
  bool Xdir = LOW;
  bool Ydir = LOW;
  //Set the spinning direction clockwise
  digitalWrite(YdirPin, Ydir);
  digitalWrite(XdirPin, Xdir);

  for (byte i = 0; i < 6; i++){
    course(YstepPin, 100, 400); /* commented out in debug */
    delay(250);
    DSLR.shotNow();
    delay(250);
    for (byte j = 0; j < 6; j++){  
      course(XstepPin, 100, 400); /* commented out in debug */
      delay(250);
      DSLR.shotNow();
      delay(250);
    }
    Xdir = !Xdir;
    digitalWrite(XdirPin, Xdir);
    }
    digitalWrite(YdirPin, !Ydir);
    course(YstepPin, 600, 400); /* commented out in debug */
}

void loop() {

  digitalWrite(systemLedPin, systemLedState);
 
  // Run once then stop system
  if (systemState){
    digitalWrite(systemLedPin, systemLedState);
    scanStage();
    systemState = false;
    systemLedState = LOW;
  }
}

I used my phone camera to check that the IR led was blinking each time, which it does, even when the camera is not responding. The failure is random (I cannot predict the step that will miss). To debug, I commented out the "course" function calls in the loops (i.e the loops will be executed, the SystemLED will be on, the camera will be trigger but the motors won’t move). The camera is now taking all the shots.

A working hypothesis is that the led may require 100mA instead of 20mA and that it should not be powered by a digital pin. What is theoretically happening in my IR led circuit ? The digital pin provide 5V and up to 40mA right. There should be 3.5 V at the 220 ohm resistor, so ~15 mA in the circuit right ?

BUT

I ruled out signal strength (because not a problem in test and debug, plus I moved the led just in front of the receiver without improvement). The camera can follow the triggering rate (I tested with a manual IR command, also debug occurs at a higher rate that normal function). If I don’t power the motors but still call the "course" function, I still miss some shots, so this has to do with emitting the pulses to move the steppers. But because the pulses are not emitted when the shot is triggered, I don't understand how this could be a problem :o.

So, I am under this impression than this has something to do with the signal being corrupted. Any ideas of what could go wrong and how to fix ?

Cheers,
MM

Scheme.png

Here we are designing based on assumptions. Look at the actual current requirement for your IR LED. They are not your normal red LED.

Paul

So as long I don't have the led datasheet, all observations I made are meaningless ? Ah, I guess I am ordering a new led then.

Thanks
MM

S1 is a short circuit from +5V to GND.

megamin:
So as long I don't have the led datasheet, all observations I made are meaningless ? Ah, I guess I am ordering a new led then.

Thanks
MM

No, look at the specs for several IR LEDs and put yours in the middle of the current requirements you find.

Try your project using the new specs.

Paul

JCA34F:
S1 is a short circuit from +5V to GND.

Drawing error on my side. The 10k resistor is between GND and 5V when circuit is closed, thanks for pointing it.

About the led, my assumption were not really drawn from the red led. Mine is colorless, no reference. Example of similar looking IR led are VF:1.2-1.4V, If: 100mA (940nm).
I expected to have it doing the job under low current, that was comforted by the test and debug, but now I should get rid of the unknown factor. I also don't have resistor of small value to check what happen with more current, I will need to get some anyway. I'll let you know.

best,
MM

Can you clarify what isn't working? Are the motors failing to step, or the camera shutter failing to fire, or both? If it's just the shutter, are you sure it isn't the camera? If you have auto-focus turned on, the Canon will not take a picture if there isn't enough contrast for autofocus to work. That might explain the randomness of the problem. Well I'm just guessing, but you might see if setting it to manual focus makes any difference.

Sure. The problem is the camera shutter failing to fire, the motors will always step.

I am sure this is not the camera. The camera is in manual mode, no autofocus. Further tests were done with a manual IR remote, and again, with the simple code "test" that simply loop on "trigger shutter, delay, trigger shutter, delay..." there is no problem even without the objective mounted. I think I was thorough =)

Because this test and debug give the expected result, I am puzzled, though I will test with more current through the IR led as I am probably way under the spec.

IR remotes typically use a carrier frequency of about 40 kHz so that the receiver can distinguish that frequently changing signal from continuous ambient light.

IR remote current can be up to 100mA (pulsed). Use a transistor to increase the LED current and try again.

Well it seems to me that you eliminated IR power as the problem by placing the IR LED at the camera, and you said that made no difference. It's also interesting that you still get the errors under any condition when the TB6600 drivers are being triggered, even if the motors are not being driven. Perhaps the current drawn by the drivers is enough to disrupt or corrupt the power supply which in turn messes up the IR signal being transmitted to the point that the Canon can't read it. Do you have a scope or an analog multimeter that would let you see if there's any sag on the Arduino's 5V pin? Do you have a large capacitor you could add at that pin?

You could try doubling the IR power by temporarily borrowing the indicator's 220R and adding it in parallel with the existing IR resistor. But again, if moving the IR LED right up to the camera doesn't work, then increasing IR power isn't likely to work either.

You might try increasing the delays before triggering the shutter to make sure any power instability has a chance to settle down.

Just to be clear - if you run the sketch as is, calling all the functions, and with nothing commented out, but physically disconnect the drivers, does the camera trigger correctly?

ShermanP:
Well it seems to me that you eliminated IR power as the problem by placing the IR LED at the camera, and you said that made no difference.

Yes, that was my point. I just rerun all the tests, under low ambient light, led close to the camera. I obtained the same results.

ShermanP:
Just to be clear - if you run the sketch as is, calling all the functions, and with nothing commented out, but physically disconnect the drivers, does the camera trigger correctly?

No.

I disconnected the drivers from the arduino (output digital pins 3,4,5,6 unplugged), and the problem is still there. I increased the delay to 1000ms (1 sec between output digital pins emitting the pulses and the shots, this is really long I think), the problem is still there. I added a 220R parallel to the IR 220R. It did not improve the system.

I don't have a scope or a capacitor unfortunately. Nor a second camera to check if mine is too sensitive to the signal corruption (or to test with another brand, i.e. different signal).

I am a bit too tired to continue tonight. Tomorrow I will remove everything from the board except the IR led, and simply the code to keep only the led IR, and the loop to trigger the "PUL" pins, to find the minimal conditions of failure...

MM

I was going to try to duplicate your code on a Nano and see if I could figure out the problem with my scope. But I can't figure out how to include the library you used. It's 10 years old, and doesn't seem to be set up like modern libraries. Can anyone tell me how to get the IDE to recognize the library as a library?

Edit: Nevermind. It was trying to call WProgram.h, which no longer exists. So I'll try to do some tests.

Ok, I ran your code on a Nano, with a regular LED on D12 and a switch on D2. Hooking up my scope to D12, everything looked pretty good. It appears you are taking 42 pictures, and the pulses look the same for each one. They can't be absolutely exactly the same because the millis interrupt comes in every now and then, but that's not really noticable on my scope display.

The only things that seemed a bit odd were that, first, the IR carrier is about 31.6KHz, which is a bit below what Canon's remote is said to use, and second, your library produces 15 pulses per burst versus 16 or 18 for the Canon remote. The inter-burst dead period was right on the money at 7.3mS.

None of these differences should matter, but if you want to try somewhat different values, you can change the Canon section at the bottom of the .cpp file in your library so that _freq is 32 instead of 33, and the first parameter in the 'high' calls is 576 instead of 488. That produces 18 pulses at about 32.5KHz.

So basically, I didn't see anything that would cause a 25% failure rate. It did the same thing every time. I can't test my setup with my Canon DSLR because I don't have an IR LED. Speaking of which, I don't know what the wavelength of the IR LED is supposed to be. Most are 940nM, but it probably doesn't make any difference as long as it's IR.

I want to again raise the question of whether it's your camera doing this. In addition to the focus issue, which you've said doesn't apply, the possible issues of overheating and SD card buffering come to mind as possible causes of the trigger failure. If the first few pictures always work correctly, and failures start happening later on, that would indicate that something is happening in the camera that prevents it from taking the picture, but only for a short time. Of course I admit these are unlikely because you said you've tried it with a 1S delay and still get failures, but I'm kinda running out of other possibilities.

If in the end it just won't work, the other possibility, if you are close enough, is to trigger the camera through the wired remote port on the side. You would just have to bring the "full-press" terminal low to take a picture. I believe that's the tip of the 2.5mm plug (but fancier models use a different connector I believe). You can do that from an I/O port, through a diode.

Hi, I solved it.

I recognized that the problem is actually within the code. It seems that the arduino execution does not appreciate the functions I created to avoid code duplication and make is more easy to configure. Basically: removing the "course" and the "sendpulse" functions (and of course replacing the function calls with their content) results in flawless execution. Why those functions impact the trigger of the camera with no -visible- effect on motor movement? I don't know.

@ShermanP
Thank you for your effort in reproducing the system ! Interesting that you checked the small inconsistencies between the remote trigger library and the canon remote. It works well as it is. I also understand you being thorough about camera possible limitation. I currently don't have problem of writing time on the CF. It is worth keeping in mind that I should consider debuffering time for my final design.

I was also considering a wire trigger as a last resort. It seems that is won't be necessary.

Thank you all for your insights, it was interesting to think about it =)

MM

That's great. Could you post your final code? I'd like to see if it makes any difference on my test setup. It would be nice to know why using the functions cause a problem.

Sure. Here it is.

#include <multiCameraIrControl.h>
Canon DSLR(12); /* IR led */

volatile bool systemState = LOW;
volatile long switchTime = 0;
volatile long prevSwitchTime = 0;

// Define stepper motor connections and steps per revolution:
const byte interruptPin = 2;
const byte XdirPin = 3;
const byte XstepPin = 4;
const byte YdirPin = 5; /* Dir+ =  greenish jumper cables */
const byte YstepPin = 6; /* Pul+ = yellowish jumper cables */
volatile byte systemLedPin = 13;
const int stepsPerRevolution = 1600; /* Accordingly to motor spec / driver setting: (200 * 1/8 = 1600)   */
const int debouncingDelay = 400;

void setup() {
  // Declare pins as output:
  for (byte i = 3; i < 14; i++) {
    pinMode(i, OUTPUT);
  }
  pinMode(interruptPin, INPUT);
  digitalWrite(XdirPin, HIGH);
  digitalWrite(YdirPin, HIGH);
  digitalWrite(systemLedPin, systemState);
  attachInterrupt(digitalPinToInterrupt(interruptPin), switchOn, RISING);
}

void switchOn() {
  switchTime = millis();
  if (switchTime - prevSwitchTime > debouncingDelay) {
    prevSwitchTime = switchTime;
    systemState = !systemState;
  }
}

void scanStage() {
  bool Xdir = LOW;
  bool Ydir = LOW;
  //Set the spinning direction clockwise
  digitalWrite(YdirPin, Ydir); /* LOW = Y forward */
  digitalWrite(XdirPin, Xdir); /* LOW = X left */

  for (byte i = 0; i < 6; i++){
    if (!systemState) {return;}
    for (int i = 0; i < 100; i++) {
      digitalWrite(YstepPin, HIGH);
      delayMicroseconds(400);
      digitalWrite(YstepPin, LOW);
      delayMicroseconds(400);
      }
    delay(250);
    DSLR.shotNow();
    delay(250);
    for (byte j = 0; j < 6; j++){
      if (!systemState) {return;}  
      for (int i = 0; i < 100; i++) {
        digitalWrite(XstepPin, HIGH);
        delayMicroseconds(400);
        digitalWrite(XstepPin, LOW);
        delayMicroseconds(400);
        }
      delay(250);
      DSLR.shotNow();
      delay(250);
      }
    Xdir = !Xdir;
    digitalWrite(XdirPin, Xdir);
    }
  digitalWrite(YdirPin, !Ydir);
  for (int i = 0; i < 600; i++) {
    digitalWrite(YstepPin, HIGH);
    delayMicroseconds(400);
    digitalWrite(YstepPin, LOW);
    delayMicroseconds(400);
    }
}

void loop() {
  // Run once then stop system
  if (systemState){
    digitalWrite(systemLedPin, systemState);
    scanStage();
    systemState = LOW;
    digitalWrite(systemLedPin, systemState);
  }
}

I'm not at all a C++ expert, but in comparing your original and new versions, I don't see anything that explains to me why one doesn't work but the other does. The only thing I see that's technically wrong in the original is that you define XStepPin and YStepPin as const int, but the course function is looking for a byte. But I thought that kind of thing was easily taken care of by the compiler, particularly for a constant.

The only other thing I can think of is that you have course and sendpulse being called within FOR loops, and maybe you're just running out of room in the stack. But usually that kind of thing causes much bigger problems. So I don't know.

If you want to mess with it, it would be interesting to know if definingXStepPin and YStepPin as byte constants would make the original version run properly.

But you know, you still have the situation where the call to the library to take a picture has a quarter-second delay before and after, so it's hard to see how using functions or not could affect that. So as far as I can see, it's a mystery.

Yes, I noticed the inconsistency with the X/YStepPin definition and fixed it while debugging and now in the new code. However, that would hardly explain why this would mess with the led, and in fact it did not help.

Here are two codes I just ran (I changed a bit the timings and removed the delay between the shots for my purpose but otherwise it is similar to the previous one).

Number one (with no extra functions) is OK, also showing that the delay between the shot is not necessary.

Number two (with the "course" function) has the problem with the camera sometime not triggering. I dropped the "sendpulse" function, as it does not really make sense in the first place. Fixing the byte / int things does not help. Another modification is that I removed "if" condition between each pulse (I don't need this kind of accuracy in interrupting the system, and I though that it could maybe mess with the pulse frequency).
Still, it would not explain why it would mess with the led, and indeed, the modification does not fix the problem.

Number one (OK):

#include <multiCameraIrControl.h>
Canon DSLR(12); /* IR led */
volatile bool systemState = LOW;
volatile long switchTime = 0;
volatile long prevSwitchTime = 0;
const byte interruptPin = 2;
const byte XdirPin = 3;
const byte XstepPin = 4;
const byte YdirPin = 5;
const byte YstepPin = 6;
volatile byte systemLedPin = 13;
const int debouncingDelay = 400;

void setup() {
  // Set Pins 3--13 as output (yet 7 to 11 are not used):
  for (byte i = 3; i < 14; i++) {
    pinMode(i, OUTPUT);
  }
  pinMode(interruptPin, INPUT);
  digitalWrite(XdirPin, HIGH);
  digitalWrite(YdirPin, HIGH);
  digitalWrite(systemLedPin, systemState);
  attachInterrupt(digitalPinToInterrupt(interruptPin), switchOn, RISING);
}

void switchOn() {
  switchTime = millis();
  if (switchTime - prevSwitchTime > debouncingDelay) {
    prevSwitchTime = switchTime;
    systemState = !systemState;
  }
}

void scanStage() {
  bool Xdir = LOW;
  bool Ydir = LOW;
  digitalWrite(YdirPin, Ydir); /* LOW = Y forward */
  digitalWrite(XdirPin, Xdir); /* LOW = X left */

  for (byte i = 0; i < 4; i++){
    if (!systemState) {return;}
    for (int i = 0; i < 100; i++) {
      digitalWrite(YstepPin, HIGH);
      delayMicroseconds(1500);
      digitalWrite(YstepPin, LOW);
      delayMicroseconds(1500);
      }
    DSLR.shotNow();
    for (byte j = 0; j < 6; j++){
      if (!systemState) {return;}  
      for (int i = 0; i < 100; i++) {
        digitalWrite(XstepPin, HIGH);
        delayMicroseconds(1500);
        digitalWrite(XstepPin, LOW);
        delayMicroseconds(1500);
        }
      DSLR.shotNow();
      }
    Xdir = !Xdir;
    digitalWrite(XdirPin, Xdir);
    }
  digitalWrite(YdirPin, !Ydir);
  for (int i = 0; i < 400; i++) {
    digitalWrite(YstepPin, HIGH);
    delayMicroseconds(1500);
    digitalWrite(YstepPin, LOW);
    delayMicroseconds(1500);
    }
}

void loop() {
  // Run one when button is pushed then wait for next push
  if (systemState){
    digitalWrite(systemLedPin, systemState);
    scanStage();
    systemState = LOW;
    digitalWrite(systemLedPin, systemState);
  }
}

Number two (Not OK)

#include <multiCameraIrControl.h>
Canon DSLR(12); /* IR led */
volatile bool systemState = LOW;
volatile long switchTime = 0;
volatile long prevSwitchTime = 0;
const byte interruptPin = 2;
const byte XdirPin = 3;
const byte XstepPin = 4;
const byte YdirPin = 5;
const byte YstepPin = 6;
volatile byte systemLedPin = 13;
const int debouncingDelay = 400;

void setup() {
  // Set Pins 3--13 as output (yet 7 to 11 are not used):
  for (byte i = 3; i < 14; i++) {
    pinMode(i, OUTPUT);
  }
  pinMode(interruptPin, INPUT);
  digitalWrite(XdirPin, HIGH);
  digitalWrite(YdirPin, HIGH);
  digitalWrite(systemLedPin, systemState);
  attachInterrupt(digitalPinToInterrupt(interruptPin), switchOn, RISING);
}

void switchOn() {
  switchTime = millis();
  if (switchTime - prevSwitchTime > debouncingDelay) {
    prevSwitchTime = switchTime;
    systemState = !systemState;
  }
}

void course(byte stepPin, int pulseFreq, int numSteps) {
    for (int i = 0; i < numSteps; i++) {
      digitalWrite(stepPin, HIGH);
      delayMicroseconds(pulseFreq);
      digitalWrite(stepPin, LOW);
      delayMicroseconds(pulseFreq);
      }
}
void scanStage() {
  bool Xdir = LOW;
  bool Ydir = LOW;
  digitalWrite(YdirPin, Ydir); /* LOW = Y forward */
  digitalWrite(XdirPin, Xdir); /* LOW = X left */
  for (byte i = 0; i < 4; i++){
    if (!systemState) {return;}
    course(YstepPin, 1500, 100);
    DSLR.shotNow();
    for (byte j = 0; j < 6; j++){
      if (!systemState) {return;}  
      course(XstepPin, 1500, 100);
      DSLR.shotNow();
      }
    Xdir = !Xdir;
    digitalWrite(XdirPin, Xdir);
    }
  digitalWrite(YdirPin, !Ydir);
  course(YstepPin, 1500, 400);
}

void loop() {
  // Run one when button is pushed then wait for next push
  if (systemState){
    digitalWrite(systemLedPin, systemState);
    scanStage();
    systemState = LOW;
    digitalWrite(systemLedPin, systemState);
  }
}

I don't think one could go really further without precisely recording the signal at pin 12 :-/

Cheers,
MM

I'll look at the new examples.

And just to confirm something I think you said earlier, you're sure that even when the camera fails to trigger, the LED IS transmitting something? Have you made a video of the LED with your phone showing a string of triggers including some failures? Do you see any LED differences at all between successful and unsuccessful triggers?

I looked at the library before, and didn't see anything unusual. But I'll look again.

It's just really difficult to see how any of this could affect the library's actions.

I liberated an IR LED from an old remote, and tried your latest examples on a Nano triggering my T2i. I got some trigger failures on the "Not OK" example, but at most one or two per run. I also got failures with the OK example, but not as many - maybe one failure every three runs

Then I hooked up my scope, and captured the first half of a failed transmission. It looked just like all the others. My scope is too limited to pick up both halves in one capture, but I doubt there would be anything I would be able to notice. A better scope than mine would be needed to see everything in enough detail to see what's wrong, if anything.

I still don't see any reason why your use of functions should have any effect on the library triggering the camera. The library is subject to being interrupted by the millis interrupt (timer zero), so maybe every now and then things get delayed a bit too much and the camera balks. But the library uses the millis interrupt to do its thing, so you can't turn it off. Perhaps there's another LED transmitter library that uses one of the other timers to generate the carrier, which wouldn't be subject to interrupts, and would be more accurate.

If you have a version that works well enough, that's good. But pending an actual answer on what's going on, I think I would be using the wired remote trigger.