Remote Camera Shutter Using Recycled PCBs!

by boysenberry in Circuits > Cameras

89 Views, 0 Favorites, 0 Comments

Remote Camera Shutter Using Recycled PCBs!

Remote Shutter Cover Image.jpg

Hi everyone, my name is Daniel and I'm an engineering student. I have an upcoming camping trip, and wanted a remote shutter for astrophotography. This is what I came up with. It is designed to imitate the signal from the Nikon ML-L3 remote, and is compatible with the following cameras: D40 series, D50, D60, D70 series, D80, D90, D600, D610, D3000, D3200, D3300, D3400, D5000, D5100, D5200, D5300, D5500, D7000, D7100, D7200, D7500, D750, COOLPIX P900.


A problem I've always had is that I have leftover PCBs from my previous projects. Many PCB manufacturers only allow you to order a minimum of 5x boards, and this can be pretty wasteful especially when you only need one or two. In the spirit of being off the grid, I used two leftover PCBs from a GPS tracker project and did not order new PCBs for this project.


Of course, I don't expect you to have the same leftover PCBs, so I've designed a PCB specifically for this project. You can download the gerber files and submit them directly to the PCB manufacturer of your choice to get started. I hope, however, I've inspired you to try and hack something together with leftover PCBs.


References:

[1] https://www.nikonproductsupport.com/s/article/13803?language=en_US

Supplies

PCB Box Animation

I was unable to upload either the zip file containing the gerbers or the individual gerber files to Instructables due to an unknown issue, so I've uploaded them into a Google Drive folder linked below. All you need to do download the gerber zip file and upload it to any PCB manufacturer.


https://drive.google.com/drive/folders/12jIRgTlAF1imqDHopTtEr385BC2W_Efc?usp=sharing


I also designed a PCB box that you can 3D print if you'd like, and the video above shows how it all fits together. It was modelled in Autodesk Fusion, and I used the Animation tab to create the video. It's super convenient for these proof of concept demonstrations, and it's pretty straightforward to learn. If you're a student, Autodesk offers free student access, which is pretty great.


If you'd like to follow along with this provided PCB, you'll need:

  1. 1x PCB
  2. 1x Raspberry Pi Pico
  3. 1x 940nm 1.2V IR (infrared) LED
  4. 1x 220Ω resistor
  5. 3D printer and filament (optional, for PCB box)
  6. 4x M6 bolts (optional, for PCB box)
  7. 4x M6 nuts (optional, for PCB box)


I used:

  1. 2x leftover PCBs from my GPS tracker project
  2. 1x Raspberry Pi Pico
  3. 1x 940nm 1.2V IR (infrared) LED
  4. 2x 100Ω resistor
  5. 2x 4 pin Seeed Studio Grove Female Header
  6. 1x 4 pin Seeed Studio Grove Cable

The Circuit

imageThe circuit itself is pretty straightforward. We simply need to turn the LED on and off in a specific pattern to make the camera fire its shutter. To do this, we can connect the LED to one of the Raspberry Pi Pico's GPIO (general purpose input/output) pins.


We need just one more thing: a resistor to limit the current and make sure we don't exceed either of two things:

  1. Pi Pico GPIO max current rating (12 mA)
  2. LED max current rating (100 mA)


I used two resistors because I needed a way to bridge across the PCB (more on that in Step 2). The LED has a forward voltage drop of 1.2V, and I want to limit the current to 10mA, so we can simply use Ohm's law to approximate the resistor value needed.


VS = 3.3V

VLED = 1.2V

I = 10mA = 0.010A

R = (VS − VLED) / I

R = (3.3V − 1.2V) / 0.010A = 210Ω


The closest I could get to 210Ω was 2x 100Ω resistors. These will limit the current to 10.5 mA, which is still well within the max current rating of both the Pico GPIO and LED.

Repurposing Leftover PCBs

This is the most interesting step, where all the magic happens. Here are the schematic and PCBs from my GPS tracker project, with polygon pours indicated in white text:


image



1-cleaned



2-cleaned

This GPS tracker board was designed to connect to a GPS sensor through the 4-pin connector, which has TX (transmit) and RX (receive) for data transfer, and VCC (3.3V) and GND for power transfer. I decided to use two of these boards: one for the Pi Pico and one for the IR LED and resistors. They are connected by a Seeed Studio Grove 4 pin cable that mates to the 4 pin connectors on each board. This way, the LED board is detachable from the Pico board.


For Board #1, we can use RX (which is already connected to GPIO 0/physical pin 1) to drive the LED signal. The return path can then simply be through GND, which makes things really easy. So, the signal leaves Board #1 through the RX pin, then returns through the GND pin. Here's what that looks like:


image

Next, we have to decide how to lay out the IR LED and resistors on Board #2. The signal arrives from board #1 through the RX pin, and we need it to return through the GND pin. There are two things here that make it much easier for us:

  1. The LED fits in the C1 footprint because it has a 2.54mm pitch (just like the capacitor C1)
  2. The C1 footprint bridges from the +BATT polygon pour to GND


In other words, if we place the LED in the C1 footprint and find a way to connect the RX pin to the +BATT polygon pour, we're done! To do this, I used two resistors. RX is already connected to GPIO 0/physical pin 1, so resistor #1 bridges from GPIO 0/physical pin 1 to the round diode pad (D1). Resistor #2 bridges from the round diode pad (D1) to the middle pad of component U1, which is connected to +BATT. Finally, the LED is placed in C1, with its longer leg (anode) connected to +BATT and its shorter leg (cathode) connected to GND. Here's what that looks like:


Board-2

References:

[1] https://freesvg.org/plus-minus-of-led-2

[2] https://freesvg.org/resistor

Assembly

Time to solder everything up! The one thing to watch out for is the LED polarity - the longer led (anode) should be connected to the GPIO pin, while the shorter one (cathode) should be connected to GND. See below:


image


Here's what my completed boards looked like:


Untitled-drawing


References:

[1] https://freesvg.org/plus-minus-of-led-2

The Signal

ML-L3-r3

I found a website where somebody else has managed to deduce that the signal is a 38.4kHz PWM. This means that if we turn the signal on, the Pico blinks the LED on and off 38400 times a second. The timings are as follows:

  1. Signal on for 2000 microseconds
  2. Signal off for 27830 microseconds
  3. Signal on for 400 microseconds
  4. Signal off for 1580 microseconds
  5. Signal on for 400 microseconds
  6. Signal off for 3580 microseconds
  7. Signal on for 400 microseconds
  8. Signal off for 62300 microseconds

The signal is then repeated again.


The duty cycle is 50%, which means that the LED spends an equal amount of time on and off. For our Pico, when we want to turn the signal on, we alternate between writing GPIO 0 high (providing 3.3V) and low (0V).


References:

[1] https://www.bigmike.it/ircontrol/

Software

Time to code it up! I'll do my best to explain my thought process in snippets, but feel free to jump to Step 6 for the complete code.


Let's start with importing the libraries we need, and setting the exposure time and the GPIO pin. Please note that I used GPIO pin 0. If you are following along with the board I provided, you need to set your GPIO pin to 28.

#include <stdio.h>
#include "pico/stdlib.h"
#include "hardware/pwm.h"

// Exposure time in milliseconds
static const uint exp_time = 250;

// Sets GPIO pin used for output
// If you are following along with
// the board I provided,
// change your GPIO pin to 28
static const uint IR_PIN = 0;


To get the Pico to generate a PWM, we need to do four things:

  1. Get the slice number
  2. Get the channel number
  3. Set the frequency
  4. Set the duty cycle


On the Pico, a slice is a hardware block that allows us to generate a PWM signal. Each slice controls two channels. So, the first thing we need to do is find out what slice and channel our chosen GPIO pin corresponds to. Next is the frequency. The fPWM is 38.4 kHz, but the Pico's main clock runs at 125 MHz by default. To set the frequency and duty cycle, we need to set three things:

  1. Divider - the main clock is divided by this frequency to give us the frequency at which the slice counts. So for example if the Divider is set to 1.0, then the slice counts at 125 MHz / 1.0 = 125 MHz.
  2. Wrap - this setting controls the value that the clock counts to.
  3. Level - this setting controls the value at which we switch from high to low. So, for a 50% duty cycle, where the LED is on for the same amount of time it is off, this value should be half of Wrap.


To give you an analogy, let's say we have two runners in a relay race. Divider controls the speed of the runners, Wrap controls the length of the track, and Level controls when they hand off the baton. The relevant formulas are:


PWM frequency: fPWM = fCLK / [Divider × (Wrap + 1)]

Therefore, the required divider is: Divider = fCLK / [fPWM × (Divider + 1)]


As you can see, there are many different combinations of Divider and Wrap that satisfy our requirement for fPWM = 38.4 kHz. In general, however, choosing a smaller Divider value and a large Wrap value give you more flexibility when choosing a duty cycle. Compare Wrap = 2 and Wrap = 100, for example. For Wrap = 2, you can only set Level to 0, 1, or 2, corresponding to 0%, 50%, and 100% duty cycle. For Wrap = 100, you can set Level to a much greater variety of values. I ended up choosing the following values:

  1. Divider = 1.0
  2. Wrap = 3254
  3. Level = 1627


// Sets divider, wrap, and level values
static const float divider = 1.0f;
static const uint16_t wrap = 3254;
static const uint16_t level = 1627;

// Timing table
static const uint16_t timing[] = {2000, 27830, 400, 1667, 400, 3580, 400, 63200};

// Initialization
void init()
{
gpio_set_function(IR_PIN, GPIO_FUNC_PWM);

// Get the slice_num and channel corresponding to our GPIO pin
slice_num = pwm_gpio_to_slice_num(IR_PIN);
channel = pwm_gpio_to_channel(IR_PIN);

// Set clock divider
pwm_set_clkdiv(slice_num, divider);

// Set wrap
pwm_set_wrap(slice_num, wrap);

// Set channel level (duty cycle)
pwm_set_chan_level(slice_num, channel, level);

// DISABLE PWM (we will turn it on when we need it)
pwm_set_enabled(slice_num, false);
}


Note the command pwm_set_enabled(slice_num, false); which turns the pwm signal on and off.


// Turns pwm on
void pwm_on()
{
pwm_set_enabled(slice_num, true);
}

// Turns pwm off
void pwm_off()
{
pwm_set_enabled(slice_num, false);
}


Since we declared all the timing values in an array at the beginning, we can write a simple loop to turn the PWM signal on and off in the specific timing pattern we need.


// Uses timing table, pwm_on(), and pwm_off() to complete one signal
void signal()
{
for (int i = 0; i < 8; i++)
{
if (i % 2 == 0)
{
pwm_on();
}
else
{
pwm_off();
}

// Use a busy-wait style precise delay loop
busy_wait_us_32(timing[i]);
}

// Ensure shutoff at end
pwm_off();
}


Note that we're using busy_wait_us_32(timing[i]); instead of sleep_us(timing[i]); because it's more precise and we need the signals timed very precisely here.


Since the signal repeats once, we can simply call signal() twice.


// Opens shutter, keeps it open for exp_time, and closes it
void fire_shutter(uint exp_time)
{
signal();
signal();

sleep_ms(exp_time);

signal();
signal();
}


Finally, here is an example main function that waits 500 ms in between images:


int main()
{
init();

while (true)
{
sleep_ms(500);

fire_shutter(exp_time);
}
}

The Complete Code

#include <stdio.h>
#include "pico/stdlib.h"
#include "hardware/pwm.h"

// Exposure time in milliseconds
static const uint exp_time = 250;

// Sets GPIO pin used for output
// If you are following along with
// the board I provided,
// change your GPIO pin to 28
static const uint IR_PIN = 0;

// Declare slice_num and channel
uint slice_num;
uint channel;

// Sets divider, wrap, and level values
static const float divider = 1.0f;
static const uint16_t wrap = 3254;
static const uint16_t level = 1627;

// Timing table
static const uint16_t timing[] = {2000, 27830, 400, 1667, 400, 3580, 400, 63200};

// Initialization
void init()
{
gpio_set_function(IR_PIN, GPIO_FUNC_PWM);

// Get the slice_num and channel corresponding to our GPIO pin
slice_num = pwm_gpio_to_slice_num(IR_PIN);
channel = pwm_gpio_to_channel(IR_PIN);

// Set clock divider
pwm_set_clkdiv(slice_num, divider);

// Set wrap
pwm_set_wrap(slice_num, wrap);

// Set channel level (duty cycle)
pwm_set_chan_level(slice_num, channel, level);

// DISABLE PWM for now (we will turn it on when we need it)
pwm_set_enabled(slice_num, false);
}

// Turns pwm on
void pwm_on()
{
pwm_set_enabled(slice_num, true);
}

// Turns pwm off
void pwm_off()
{
pwm_set_enabled(slice_num, false);
}

// Uses timing table, pwm_on(), and pwm_off() to complete one signal
void signal()
{
for (int i = 0; i < 8; i++)
{
if (i % 2 == 0)
{
pwm_on();
}
else
{
pwm_off();
}

// Use a busy-wait style precise delay loop
busy_wait_us_32(timing[i]);
}

// Ensure shutoff at end
pwm_off();
}

// Opens shutter, keeps it open for exp_time, and closes it
void fire_shutter(uint exp_time)
{
signal();
signal();

sleep_ms(exp_time);

signal();
signal();
}

int main()
{
init();

while (true)
{
sleep_ms(500);

fire_shutter(exp_time);
}
}

Testing

Remote Shutter Demonstration

Here's a video demonstrating the project. The shutter speed in the video is 500 ms with a 1 s delay in between each image. Thanks for reading this Instructable and feel free to let me know if you have any questions.


Troubleshooting Tips:

  1. Check: is the LED wired correctly? (see Step 3)
  2. If you're not sure whether or not the LED is working, try filming it with the front (selfie) camera of your phone or your computer's webcam. On most newer phones, the rear camera blocks most infrared light. Also try using a TV remote - they use the same frequency and you should see a purplish white light.
  3. Use a multimeter to check the voltages across your resistor and LED. If the GPIO is outputting power correctly, the sum of those voltages should be the same as the voltage across the GPIO and GND.