Close
0%
0%

Remote control and image retrieval from Nikon DSLR

This post explains how to control a Nikon D3100 DSLR, and then get the images off of it.

Public Chat
Similar projects worth following
It seems like I had to perform a significant number of contortions in order to remote control and retrieve the photos off of a Nikon DSLR. Here are the steps to do it. I also figured out how to automatically convert the RAW (NEF) images to TIFF (16 bit) so they can be processed (MATLAB, etc.).

I think I've published all the details needed. If you need any help with anything, just message me through hackaday! Cheers! :)

I wanted to control a Nikon D3100 camera, but for whatever reason, remote control of cameras (in general) is some kind hidden thing.  Honestly, why can't they just be controlled over USB through some standard interface?  Anyway, I'll stop grumbling and get on with it.

First, get yourself a MC-DC2 shutter cable.  Open the case (remove the two screws and gently pry the two sides apart).  There are 3 contacts.  The "middle" one is the common one.  When you press the button, the top contact presses the middle (focus), and then you press the upper two into the bottom one (takes a picture).

Solder one wire to each of the contacts of the MC-DC2 and land them on your breadboard.

Not knowing what the signals are for the camera, I played it safe and built a completely isolated switch.

The MCU (atmega328pb xplained board) is connected to the Dual Channel Photovoltaic MOSFET Driver (the Avago ASSR-V621).  The 330 Ohms resistors limit the current into the photodiode ports (the MCU runs at ~5V).  The outputs are connected to N-channel mosfets arranged source-to-source.  This means that the body diodes of the transistors won't conduct, and from the point of view of the camera, it is a true switch.

The mosfets were Taiwan Semiconductor TSM170N06, but these were just transistors I had lying around.

So, the MCU just has to bring one output high, and then the second output high to take a picture.  In my case, I connected to PB0 and PB1, so just had to write 1, then 3 to register 0x25 (see my ATMEGA328PB code in one of the posts below).

Here is the whole setup on the "bench".

I used the setup to create a timelapse of melting ice:

Here is the MATLAB code to take the time-lapse video.  The activex control required a lot of effort to make sure the SendKeys method actually worked...

%% Identification
% Science Dude 1990
% December 10, 2020
%
%% Code
% Take a photo with a Nikon D3100 and transfer to local PC, for making
% timelapse movies!!!

%% Clean up
clc
close all
clear
drawnow

%% Parameters
% Pause after all the other tasks (i.e., the main timelapse parameter)
pause_tasks = 0;

% Number of pictures to take
N_pictures = 1024;

% COM port
com_port = 5;

% BAUD rate
baud_rate = 9600;

% Data bits
data_bits = 8;

% Camera directory
camera_dir = 'Computer\D3100\Removable storage\DCIM\100TEST_';

% Current directory
current_dir = pwd;

% The filename
temp = clock;

% Filename for saving
f_name = ['Timelapse_Record_', num2str(temp(1), '%04d'), '_', num2str(temp(2), '%02d'), '_', num2str(temp(3), '%02d'), '_', ...
          num2str(temp(4), '%02d'), '_', num2str(temp(5), '%02d'), '_', num2str(round(temp(6)), '%02d')];

%% Let the operator steady the setup
beep
pause(3)
beep
pause(3)
beep
pause(3)

%% Open the COM port
% The serial port
s = serial(['COM' num2str(com_port)], 'BaudRate', baud_rate, 'DataBits', data_bits, 'Terminator', 'CR');
fopen(s);
pause(1);
% Save the response from the microcontroller (opening com port resets the
% controller)
scan_count = 1;
tmp = {};
while s.BytesAvailable > 1
    tmp{scan_count} = fscanf(s);
    scan_count = scan_count + 1;
end

%% Active X to be able to copy the pictures from the camera to the local PC
% Create active x server
h = actxserver('WScript.Shell');

%% Main loop
% Cell array to hold the COM port communications
com_record = cell(N_pictures, 3);

% The pause to let windows react
pause_ui = 0.15;

% Pause for the snapshot
pause_snapshot = 1.5;

for ii = 1 : N_pictures
    %% Send the COM commands
    pause(pause_ui);
    % Read the register for reference
    fprintf(s, 'r37');
    pause(pause_ui);
    % Set PORTB to 1, then 3, to take a picture
    fprintf(s, 'w37=1');
    pause(pause_ui);
    fprintf(s, 'w37=3');
    % Note the time the picture was requested
    com_record{ii, 1} = clock;
    pause(pause_ui);
    fprintf(s, 'w37=0');
    
    % Let the picture get taken
    pause(pause_snapshot);
    
    % Save the response from the microcontroller
    scan_count = 1;
    tmp = {};
    while s.BytesAvailable...
Read more »

  • 1 × MC-DC2 Remote release cord for Nikon cameras
  • 1 × ASSR-V621 Dual Channel Photovoltaic MOSFET Driver
  • 1 × ATMEGA328PB Microcontroller
  • 1 × D3100 Nikon digital SLR camera

  • Ice melting...

    sciencedude199012/10/2020 at 21:35 0 comments

    Using the code in one of the posts, I made a timelapse using my Nikon camera of some ice cubes melting.  Since it is computer controlled and the files are copied back to a local PC, you could run this for days!  You could also increase the resolution to 4K and beyond if you wanted.  This video was then assembled with shotcut.  Enjoy!

  • Parse the string for \

    sciencedude199012/10/2020 at 03:34 2 comments

    Here is the helper function that, for every '\' in the path, it makes sure it is \\ so that printf works correctly...

    parse_dir_add_slash.m

    function [ret] = parse_dir_add_slash(dir_in)
    
    % Search for the '\' character in the string
    temp = strfind(dir_in, '\');
    
    % For every \ in the string, make sure it is \\ so that it will print correctly
    ret = '';
    if ~isempty(temp)
        for ii = 1 : length(temp)
            if ii == 1
                ret = [ret dir_in(1 : (temp(ii) - 1)) '\\'];
            else
                ret = [ret dir_in((temp(ii - 1) + 1) : (temp(ii) - 1)) '\\'];
            end
        end
    end
    
    % If the input string doesn't end with a \, add the rest of the string
    if temp(end) < length(dir_in)
        ret = [ret dir_in((temp(end) + 1) : end)];
    end
            
            

  • Complete MATLAB script file

    sciencedude199012/09/2020 at 16:59 0 comments

    Here is the complete MATLAB script file to automatically take a picture, copy to the local PC, convert to TIFF, and then open in MATLAB.  Note that my "pause" times worked for my PC - you might have to adjust them for your PC.  I tried to comment the heck out of it.  Enjoy! 

    %% Identification
    % Science Dude 1990
    % December 9, 2020
    %
    %% Code
    % Take a photo with a Nikon D3100 and transfer to local PC, load it in
    % MATLAB
    
    %% Clean up
    clc
    close all
    clear
    drawnow
    
    %% Parameters
    % Pause after picture taken (allow time for shutter, and file write to
    % camera SD card)
    pause_after_pic = 6;
    
    % COM port
    com_port = 5;
    
    % BAUD rate
    baud_rate = 9600;
    
    % Data bits
    data_bits = 8;
    
    % Camera directory
    camera_dir = 'Computer\D3100\Removable storage\DCIM\100TEST_';
    
    % Current directory
    current_dir = pwd;
    
    %% Open the COM port, set the register to take the picture
    
    % The serial port
    s = serial(['COM' num2str(com_port)], 'BaudRate', baud_rate, 'DataBits', data_bits, 'Terminator', 'CR');
    fopen(s);
    
    % Send the commands
    pause(1);
    % Read the register for reference
    fprintf(s, 'r37');
    pause(1);
    % Set PORTB to 1, then 3, to take a picture
    fprintf(s, 'w37=1');
    pause(1);
    fprintf(s, 'w37=3');
    pause(pause_after_pic);
    fprintf(s, 'w37=0');
    
    % Save the response from the microcontroller
    scan_count = 1;
    tmp = {};
    while s.BytesAvailable > 1
        tmp{scan_count} = fscanf(s);
        scan_count = scan_count + 1;
    end
    % Close the serial port
    fclose(s);
    
    %% Go get the photo from the camera to the local PC
    % Assumes the explorer views of the camera and current directory are
    % already open, ready to copy the file from the camera to the local
    % directory
    
    % Create active x server
    h = actxserver('WScript.Shell');
    
    % Raise the camera explorer view
    temp = h.AppActivate(camera_dir);
    pause(1);                   
    
    if temp ~= 1
        error('Could not raise camera directory');
    end
    
    % Work around to get focus to the picture in the camera
    h.SendKeys('%{TAB}');
    pause(0.1);
    h.SendKeys('%{TAB}');
    pause(0.1);
    h.SendKeys('{TAB}');
    pause(0.05);
    h.SendKeys('{TAB}');
    pause(0.05);
    h.SendKeys('{TAB}');
    pause(0.05);
    h.SendKeys('{TAB}');
    pause(0.05);
    h.SendKeys('{TAB}');
    pause(0.05);                    
    h.SendKeys('{DOWN}');                   
    pause(1);
    
    % "Cut" the file from the camera
    h.SendKeys('^x');
    pause(1);
    
    % Raise the local folder
    temp = h.AppActivate(current_dir);
    pause(1);
    if temp ~= 1
        error('Could not raise local directory');
    end
    
    % Work around to get focus to the directory
    h.SendKeys('%{TAB}');
    pause(0.1);
    h.SendKeys('%{TAB}');
    pause(0.1);
    h.SendKeys('{TAB}');
    pause(0.1);
    h.SendKeys('{TAB}');
    pause(0.1);
    h.SendKeys('{TAB}');
    pause(0.1);
    h.SendKeys('{TAB}');
    pause(0.1);
    h.SendKeys('{TAB}');
    pause(0.1);
    h.SendKeys('{DOWN}');
    pause(0.1);
    
    % "Paste" the picture from the camera
    h.SendKeys('^v');
    % Let the file get moved
    pause(5);
    
    % Copy the filename of the new picture to the clipboard
    h.SendKeys('{F2}');
    pause(0.1);
    h.SendKeys('^c');
    % Let the filename get picked up by the clipboard
    pause(0.1);
    
    %% Back to MATLAB, create the .bat file to raise Nikon NX-D to convert to TIFF
    % Back to MATLAB
    temp = h.AppActivate('MATLAB R2019a');
    
    if temp ~= 1
        error('Could not raise MATLAB window');
    end
    
    % Get the filename of the picture from the clipboard
    pic_filename = clipboard('paste');
    
    % Create the bat file to launch Nikon NX-D
    fid = fopen('test.bat', 'w');
    
    fprintf(fid, 'cd \\\r\n');
    fprintf(fid, 'cd "Program Files"\r\n');
    fprintf(fid, 'cd Nikon\r\n');
    fprintf(fid, 'cd "Capture NX-D"\r\n');
    fprintf(fid, 'cd Module\r\n');
    fprintf(fid, ['start "" "CaptureNX-D.exe" ' parse_dir_add_slash(current_dir) '\\' pic_filename '.NEF"\r\n']);
    fprintf(fid, 'exit\r\n');
    fclose(fid);
    pause(1);
    
    %% Run the bat file to raise Nikon Capture NX-D
    % Run the bat file
    status = system('test.bat &');
    
    % Let NX-D open
    pause(12);
    
    % Get the java robot
    import java.awt.Robot
    import java.awt.event.*
    my_robot = Robot;
    
    % Bring the window to the foreground
    temp = h.AppActivate('Capture NX-D');
    
    if (temp ~= 1)
        error('Could not raise NX-D');
    end
    
    disp('Kick off conversion');
    
    % Work...
    Read more »

  • MATLAB code to convert RAW to TIFF

    sciencedude199012/05/2020 at 01:58 0 comments

    I couldn't find a way to load and process the Nikon 12-bit RAW files from the D3100 directly with MATLAB.  So, I found a way from MATLAB to kick off the NX-D software and perform the conversion.

    First, to run the program, I had to use a .bat file.  This could be created in MATLAB, then executed.  The main idea is to have the Capture NX-D program open with the required file highlighted.  Also, you need to have run NX-D before to set all the settings (output directory, 16-bit TIFF, etc.).

    cd \
    cd "Program Files"
    cd Nikon
    cd "Capture NX-D"
    cd Module
    start "" "CaptureNX-D.exe" C:\path_to_file\DSC_5097.NEF"
    exit

    Once NX-D is running, then you can use the Java robot to send the keys to start.  Here is the MATLAB code to run the .bat file, and then kick off the conversion.

    %% Code
    % Raise the Nikon Capture NX-D program with a specified filename, and kick
    % off a capture
    
    close all
    clear
    
    % Raise Nikon Capture NX-D program with the correct file highlighted (see
    % test.bat)
    status = system('test.bat &');
    
    % Create active x server
    h = actxserver('WScript.Shell');
    
    % Get the java robot
    import java.awt.Robot
    import java.awt.event.*
    my_robot = Robot;
    
    % Bring the window to the foreground
    temp = h.AppActivate('Capture NX-D');
    % Check to see that the window came up
    if temp == 1
        % Kick off the conversion, i.e., CTRL-E
        pause(0.1);
        my_robot.keyPress(17);
        pause(0.1);
        my_robot.keyPress(69);
        pause(0.1);
        my_robot.keyRelease(69);
        pause(0.1);
        my_robot.keyRelease(17);
        % Let window raise
        pause(0.5);
        % Press enter (assumes TIFF 16 bit already selected)
        my_robot.keyPress(10);
        pause(0.1);
        my_robot.keyRelease(10);
    end

  • Main code for ATMEGA328PB

    sciencedude199012/05/2020 at 01:47 0 comments

    Here is the main code for the ATMEGA328PB.  Note that this is for a xplained board.

    The main idea of the code is to allow read and write of the MCU registers over the USART port (which for the xplained board, is a virtual COM port that is accessible over USB).  So, you set the serial port program to 9600 baud, 8 bits, no parity, and then to write the PORTB register, you type in something like w37=1  or w37=3  (i.e., decimal 37 is 0x25).  Please see the latest ATMEGA328PB datasheet, page 445 for the register map.

    #include <avr/io.h>
    
    // For the ATMEGA328PB xplained, 16 MHz is available on the external clock
    // So, in the fuses section, LOW.CKDIV8 is unchecked
    // LOW.SUT_CKSEL is set to Ext. Clock; Start-up time PWRDWN/RESET: 6 CK/14 CK + 65 ms
    #define F_CPU 16000000UL
    #include <util/delay.h>
    
    #include <avr/interrupt.h>
    #include "main.h"
    
    // Place to store a string for USART communication
    static char temp_string[64];
    // Simple routine to output a serial string to the UART
    void print_serial(const char * const print_string) {
        uint8_t temp = 0;
        
        while (temp < strlen(print_string)) {
            if ((UCSR0A & 0x20) == 0) {
                // Do nothing
                _delay_us(10);
            }
            else {
                // Send a byte
                UDR0 = (uint8_t) print_string[temp];
                temp = temp + 1;
            }
        }
        
        // Send ASCII 13 and 10
        temp = 0;
        while (temp < 2) {
            if ((UCSR0A & 0x20) == 0) {
                // Do nothing
                _delay_us(10);
            }
            else {
                if (temp == 0) {
                    UDR0 = 13;
                }
                
                if (temp == 1) {
                    UDR0 = 10;
                }
                temp = temp + 1;
            }
        }
    }
    
    // Place to store the user input
    static uint8_t rx_in_buffer[64];
    // The count of user input
    static uint8_t rx_in_buffer_count = 0;
    
    ISR(USART0_RX_vect) {
    
        // Read the character from the buffer
        uint8_t temp = UDR0;
        
        // Put the character in the buffer
        rx_in_buffer[rx_in_buffer_count] = temp;
        // Increment the count
        rx_in_buffer_count = rx_in_buffer_count + 1;
        // Count has to be less than 63
        if (rx_in_buffer_count > 63) {
            rx_in_buffer_count = 63;
        }
    
        // If the character is carriage return, process the command
        if (temp == 13) {
            go_process_command();
        }
    }
    
    void go_process_command() {
        // Print some characters in the buffer
        //snprintf(temp_string, 64, "Rx: %d, %d, %d, %d, %d, %d", rx_in_buffer[0], rx_in_buffer[1], rx_in_buffer[2], rx_in_buffer[3], rx_in_buffer[4], rx_in_buffer[5]);
        //print_serial(temp_string);
        
        // The read command
        if ((rx_in_buffer[0] == 114) || (rx_in_buffer[0] == 82)) { // The read command, r or R, expect decimal, for example, expect r72 to read 0x48, OCR0B
            //print_serial("r/R");
            
            // The location of the carriage return
            uint8_t ind_13 = 0;
            
            // Find character 13
            for (uint8_t ii = 1; ii < 64; ii++) {
                if (rx_in_buffer[ii] == 13) {
                    ind_13 = ii;
                    break;
                }
            }
            // Where character 13 was found
            //snprintf(temp_string, 64, "Char13: %d", ind_13);
            //print_serial(temp_string);
            
            // For processing the register address (convert ASCII characters to addr)
            uint8_t found_it = 0;
            uint16_t addr = 0;
            uint16_t pow_10 = 1;
            
            // If ind_13 was found, build up addr
            if (ind_13 > 1) {
                for (uint8_t ii = (ind_13 - 1); ii > 0; ii--) {
                    if ((rx_in_buffer[ii] >= 48) || (rx_in_buffer[ii] <= 57)) {
                        addr = addr + (rx_in_buffer[ii] - 48) * pow_10;
                        pow_10 = pow_10 * 10;
                        found_it = 1;
                    }
                    else { // Unexpected character
                        found_it = 0;
                        break;
                    }
                }
            }
            // Print the address and whether the parsing was as expected
            //snprintf(temp_string, 64, "addr: %u, found_it: %d", addr, found_it);
            //print_serial(temp_string);
            
            // Print the register value
            if (found_it == 1) {
                volatile uint8_t * reg_addr = (uint8_t *) addr;
                uint8_t temp_value = *reg_addr;
                snprintf(temp_string, 64, "Reg: %u (%#x) = %d (%#x)", addr, addr, temp_value, temp_value);
                print_serial(temp_string);
            }
        }
        else if ((rx_in_buffer[0] == 119) || (rx_in_buffer[0] == 87)) { // The write command, w or W, for example, expect w72=4
            //print_serial("w/W");
            
            // The location of the carriage return
            uint8_t ind_13 = 0;
            // Find character 13, carriage return
            for (uint8_t ii = 1; ii < 64; ii++)...
    Read more »

  • Header file for ATMEGA328PB

    sciencedude199012/05/2020 at 01:45 4 comments

    Here is the header file (main.h) for the ATMEGA328PB (xplained board).

    #ifndef MAIN_H_
    #define MAIN_H_
    
    // Serial port stuff
    #include <string.h>
    #include <stdio.h>
    
    // Printing a string to the serial port
    void print_serial(const char * const print_string);
    
    // Given an input array, process the command
    void go_process_command();
    
    #endif // MAIN_H_

View all 6 project logs

Enjoy this project?

Share

Discussions

sciencedude1990 wrote 04/05/2021 at 15:53 point

I did a project related to imaging and the diffraction limit!  Check it out:

https://hackaday.io/project/178655-getting-cozy-with-the-diffraction-limit

  Are you sure? yes | no

sciencedude1990 wrote 02/21/2021 at 04:00 point

And - if you have a bunch of old lenses - check out my 3D printed project to use the raspberry pi and an old pentax lens...

https://hackaday.io/project/177321-upgraded-lens-for-raspberry-pi-v2-camera

  Are you sure? yes | no

sciencedude1990 wrote 02/02/2021 at 04:22 point

well, someone told me about the raspberry pi, and given the speed and ease of that system, I think you would be hard pressed to use the dslr camera.  Now that you can easily attach your good lens, I think the pi is probably the way to go.

  Are you sure? yes | no

Similar Projects

Does this project spark your interest?

Become a member to follow this project and never miss any updates