-
Ice melting...
12/10/2020 at 21:35 • 0 commentsUsing 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 \
12/10/2020 at 03:34 • 2 commentsHere 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
12/09/2020 at 16:59 • 0 commentsHere 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 around to get proper focus in NX-D % Press the tab key twice pause(0.5); my_robot.keyPress(9); pause(0.5); my_robot.keyRelease(9); pause(0.5); my_robot.keyPress(9); pause(0.5); my_robot.keyRelease(9); % Kick off the conversion, i.e., CTRL-E pause(0.2); my_robot.keyPress(17); pause(0.2); my_robot.keyPress(69); pause(0.2); my_robot.keyRelease(69); pause(0.2); my_robot.keyRelease(17); % Let window raise pause(0.8); % Press enter (assumes TIFF 16 bit already selected) my_robot.keyPress(10); pause(0.2); my_robot.keyRelease(10); % NX-D will raise the window for conversion - it will force focus as it % converts pause(5); %% Back to MATLAB to load the file figure(1); clf text(0.4, 0.4, 'Waiting for TIFF creation...'); set(1, 'currentchar', ' ') title_str = {'.', '..', '...', '....'}; title_count = 0; % Wait for the TIF file to be created, or until the user presses something while get(1, 'currentchar') == ' ' % Set MATLAB to be the window of focus h.AppActivate('MATLAB R2019a'); % Bring the figure window to the top figure(1); % Give it the changing title so the user knows code is executing title(title_str{title_count + 1}); title_count = mod(title_count + 1, 4); temp = dir([pic_filename '.tif']); if isempty(temp) pause(0.5); else set(1, 'currentchar', 'a'); end end % Expect the .tif file to be present if isempty(temp) error('File not created'); end % Activate MATLAB again temp = h.AppActivate('MATLAB R2019a'); if temp ~= 1 error('Failed to raise MATLAB'); end % Load the picture temp_pic = imread([pic_filename '.tif']); % Show the picture figure(2); imshow(temp_pic);
-
MATLAB code to convert RAW to TIFF
12/05/2020 at 01:58 • 0 commentsI 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
12/05/2020 at 01:47 • 0 commentsHere 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++) { if (rx_in_buffer[ii] == 13) { ind_13 = ii; break; } } // The location of the = uint8_t ind_eq = 0; for (uint8_t ii = 1; ii < 64; ii++) { if (rx_in_buffer[ii] == 61) { ind_eq = ii; break; } } // Where character = and 13 were found //snprintf(temp_string, 64, "Char=: %d, 13: %d", ind_eq, ind_13); //print_serial(temp_string); if ((ind_eq > 1) && ((ind_eq + 1) < ind_13)) { // For processing the register address (convert ASCII characters to addr) uint8_t found_it = 0; uint16_t addr = 0; uint16_t pow_10 = 1; // Get the register address for (uint8_t ii = (ind_eq - 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); // Attempt to set the register value if (found_it == 1) { // Print the current value of the register volatile uint8_t * reg_addr = (uint8_t *) addr; //snprintf(temp_string, 64, "Reg: %u = %d, old value", addr, *reg_addr); //print_serial(temp_string); // Now, parse the value found_it = 0; pow_10 = 1; uint16_t in_val = 0; // Get the value for (uint8_t ii = (ind_13 - 1); ii > ind_eq; ii--) { if ((rx_in_buffer[ii] >= 48) || (rx_in_buffer[ii] <= 57)) { in_val = in_val + (rx_in_buffer[ii] - 48) * pow_10; pow_10 = pow_10 * 10; found_it = 1; } else { // Unexpected character found_it = 0; break; } } // Print the value and whether the parsing was as expected //snprintf(temp_string, 64, "New value: %d, found_it: %d", in_val, found_it); //print_serial(temp_string); if ((found_it == 1) && (in_val <= 255)) { // Write the value *reg_addr = (uint8_t) in_val; snprintf(temp_string, 64, "Reg: %u (%#x) = %d, new value", addr, addr, in_val); print_serial(temp_string); } } } } else { print_serial("Command error"); } // Last things to do // Set the buffer count to 0 rx_in_buffer_count = 0; // Clear the buffer for (uint8_t ii = 0; ii < 64; ii++) { rx_in_buffer[ii] = 0; } } int main(void) { // Get the UART ready UBRR0H = 0; // Oscillator is 16 MHz, 9600 is decimal 103 UBRR0L = 103; // Enable Rx interrupt, enable Rx, enable Tx UCSR0B = (1 << 7) | (1 << 4) | (1 << 3); // Enable RXCIE1, Enable Tx and Rx UCSR0C = (0 << 6) | (0 << 4) | (0 << 3) | (3 << 1); // Asynchronous, No parity, 8 bits _delay_ms(1); // Enable interrupts SREG |= (1 << 7); // PB5 is the USER_LED, labeled D200 on the ATMEGA328PB xplained board // PB0 and PB1 for the shutter control DDRB = (1 << 0) + (1 << 1) + (1 << 5); PORTB = 0; // Blink the LED waiting for serial port input print_serial("Go time!"); while (1) { //PORTB &= 255 - (1 << 5); //_delay_ms(500); //PORTB |= (1 << 5); //_delay_ms(500); _delay_ms(1); } }
-
Header file for ATMEGA328PB
12/05/2020 at 01:45 • 4 commentsHere 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_