For my wedding, I decided to build an open photobooth using my Nikon D5300 camera. Since I had the camera and a few other items needed for the build already, this was a cost-effective build versus renting a photobooth at $700/night. The total build cost was around $250.
The camera is connected to a monitor with an HDMI cable for live viewing of pictures. A 16×32 RGB LED Panel, driven by an Arduino Uno, is used as a welcome sign and a countdown timer between picture-taking. A large push-button is used to activate both a camera remote and the LED panel countdown.

Bill of Materials (BOM)
Electronics
- 16×32 RGB LED Matrix Panel
- 5V 2A Switching Power Supply
- Arduino Uno R3
- Arduino Uno ProtoShield – 3 Pack (or custom board)
- Big Dome Pushbutton – Blue
- 2.1mm x 5.5mm Female Terminal Jack – 10 Pack
- PC817 Optocoupler – 10 Pack
- Amazon Basics Wireless Remote Control for Nikon Camera
- LCD Monitor
- HDMI Cable
Hardware
- 12″x12″ – 3mm Birch Plywood Sheets
- Pyle Tripod Speaker Stand
- 35mm Tripod Pole Mounting Bracket
- VESA Wall Mount
- Adhesive-Backed Velcro
- 19x M3x8 Screws
- 4x M3 Threaded Inserts (i used press-fit ones for plastic; they worked fine)
- 4x M5x10 Screws
- 4x M5 Washers
- 4x M5 Nuts
- 1x 1/4-20×3/8″ Screw
You can, of course, use english hardware instead. 4-40 screws would fit the hole sizes I have in the design.
Wooden Enclosure
The wooden enclosure of the photobooth is just a rectangular box with a few internal panels for mounting components. I used a laser cutter to cut some of the parts that required precision hole alignment, like the LED panel and arduino mount, but all of the parts could easily be cut by hand.
The original design was done in CAD for laying out the position parts and for creating the laser cutting files. For the front panel, I wanted to make it obvious where the camera was, so I made an outline of a camera around the hole for the lens and laser etched it into the panel.
I am a novice when it comes to woodworking, so there could very well be better way of building a box, but I used 1/2″x1/2″ strips to build an internal frame and cut 12″ panels of 3mm birch plywood to build the external panels.
The whole enclosure is held together with wood glue, except for the back panel which is screwed on so the inside can be easily accessed.
The rear panel has 4x holes drilled through, and these align with 4x holes into the internal frame of the box with threaded inserts. The threaded inserts are technically for plastic, but I did not have any for wood on hand. The inserts are just pressed in and they hold well enough to hold the cover.
The internal parts were cut with a laser cutter. These included the mount for the LED panel, the mount for the large dome push button, and the mount for the camera.
Four holes were drilled through the bottom of the enclosure for mounting to the speaker stand. Using the mount as a template, the four holes were drilled out to +5mm in diameter for using M5 screws to secure the enclosure.
Electronics
I originally made a shield for the Arduino Uno using a prototyping board with some headers and jumper wires. I followed the instructions by Adafruit for connecting a 16×32 RGB LED Panel to the Arduino Uno. Their instructions are great for determining the pinout, so I would recommend going there for this step.
The board was functional, but I think my soldering of the jumper wires was a little shoddy because the LED panel would flicker at times. So, I decided to make a more reliable board in Eagle CAD, and ordered it through Osh Park.
The custom board is much more reliable than the prototype board. I designed it as a ‘shield’ so that the board plugs directly into the Arduino Uno.
I originally planned on making a mechanical assembly with two separate switches for the button to physically push and that is how I designed the custom circuit board. But, I later decided I wanted to have the remote triggered electrically by the large push-button. So, I made a breakout board that adds an optocoupler to trigger the wireless remote when the the big button is pressed. An optocoupler, or relay, is needed to control the remote to keep the grounds separate between the remote and the arduino. Here is the updated schematic and board layout.
You can order this board from OSH Park here. I have not tested one of these boards, but the circuit is exactly as the original, but with the optocoupler added so it should be okay.
Because I did not want to order new boards, I built a cable to add the optocoupler to the assembly. One wire is soldered to pin 10 on the board, and another to ground (I know, I should have stayed with a proper wire color convention, but this wire was nearby…). A 2-pin female header connector is added to the ends of the wire to later connect to the optocoupler circuit.

The wireless remote just has a simple domed push-button switch for triggering the camera. Remove the battery, and then remove the elastomer pad over the switch. The dome switch will be taped down against the board – remove it. You do not need to take apart the housing (which is ultrasonically welded together). One wire is soldered to the center pad (positive voltage) and the other to the concentric circle around it (ground). (It is probably best if you remove the battery from the remote before soldering the wires to the board.) A 2-pin female header connector is added to the ends of the wire to later connect to the optocoupler circuit.
The circuit for the optocoupler is very simple, as shown in the schematic above. One wire goes from the arduino uno digital pin, with a 180 Ohm resistor in series, to the anode of the optocoupler. Another wire goes from the arduino ground to the pin next to the anode. The ground wire from the remote goes to the pin opposite the ground next to the anode, and the positive wire from the remote goes to the pin opposite the anode. Instead of putting the components on a board, I just made a cable out of it with 2-pin headers on either side.
The remote connects to one end, and the wires from the shield to the other and with that, the electronics are complete!

Final Assembly
With the electronics complete and enclosure built, the final assembly could be done. The LED panel and arduino were mounted from the inside, along with the wireless remote which was attached to the sidewall with adhesive backed velcro.
The mount for the camera was laser cut from 1/8″ birch plywood. There is a slot positioned for a 1/4-20 screw to fasten to the camera. The mount is positioned such that the camera lens is centered on the camera hole cutout.
Holes were drilled out of the bottom for cables to enter and exit the box.

A power strip was attached to the inside roof of the box for connecting all the internal cables, as well as cables from the two photography lights that would be used alongside the box.
The LCD monitor that sits below the photobooth box is an old 19″ Viewsonic that I had laying around. Luckily, it had the features for mounting to a VESA mount.

Two holes were drilled through the tripod for mounting the LCD monitor, and were positioned by holding the LCD monitor below the mounted photobooth box to ensure it was at a good height.

With the monitor mounted, the assembly is complete!
The photobooth worked fantastically at my wedding and was a great success. I did have to switch out the battery on the camera midway through the wedding because I did not build in a way to charge the camera while installed, but it worked out.
Overall, I am happy with the result, although it was not as refined as I would have liked. It worked well, though!
Arduino Code
The code is based on the LED panel tutorial written by Limor Fried of Adafruit. Their tutorial for the panel can be found here.
Along with the code below, you will need to download the RGBmatrixPanel library and the Adafruit_GFX library; which can be found here.
Arduino code:
// scrolltext demo for Adafruit RGBmatrixPanel library.
// Demonstrates double-buffered animation on our 16×32 RGB LED matrix:
// http://www.adafruit.com/products/420
// Written by Limor Fried/Ladyada & Phil Burgess/PaintYourDragon
// for Adafruit Industries.
// BSD license, all text above must be included in any redistribution.
// Code modified by Jim Bilodeau to add button interface
#include <Adafruit_GFX.h> // Core graphics library
#include <RGBmatrixPanel.h> // Hardware-specific library
// Similar to F(), but for PROGMEM string pointers rather than literals
#define F2(progmem_ptr) (const __FlashStringHelper *)progmem_ptr
#define CLK 8
#define LAT A3
#define OE 9
#define A A0
#define B A1
#define C A2
//Variables
const int remotePin = 10; //pin of arduino for wireless remote
const int buttonPin = 11; //pin of arduino for button
int remoteState = LOW; //state of remote is OFF (LOW) at startup
// Last parameter = ‘true’ enables double-buffering, for flicker-free,
// buttery smooth animation. Note that NOTHING WILL SHOW ON THE DISPLAY
// until the first call to swapBuffers(). This is normal.
RGBmatrixPanel matrix(A, B, C, CLK, LAT, OE, false);
void setup() {
matrix.begin();
pinMode(buttonPin, INPUT_PULLUP); //use internal pullup resistor for switch button
pinMode(remotePin, OUTPUT); //wireless remote pin is an output
//Serial.begin(9600); //serial output for debugging purposes
}
void loop() {
//read state of switch into a local variable
int buttonState = digitalRead(11);
//Serial.print(buttonState); //buttonstate printout for debugging purposes
if (buttonState == HIGH) {
matrix.setTextSize(1); // size 1 == 8 pixels high; size 2 == 16 pixels high
matrix.setCursor(4, 0); // start at top left, with one pixel of spacing
matrix.setTextColor(matrix.Color333(7, 0, 0)); //color: red
matrix.print(‘P’);
matrix.setTextColor(matrix.Color333(7, 4, 0)); //color: yellow
matrix.print(‘U’);
matrix.setTextColor(matrix.Color333(7, 7, 0)); //color: yellowish-green
matrix.print(‘S’);
matrix.setTextColor(matrix.Color333(4, 7, 0)); //color: green
matrix.print(‘H’);
matrix.setCursor(1, 9); // next line
matrix.setTextColor(matrix.Color333(0, 7, 7)); //color: aqua
matrix.print(‘B’);
matrix.setTextColor(matrix.Color333(0, 4, 7)); //color: mid-blue
matrix.print(‘E’);
matrix.setTextColor(matrix.Color333(0, 0, 7)); //color: blue
matrix.print(‘L’);
matrix.setTextColor(matrix.Color333(4, 0, 7)); //color: bluish-purple
matrix.print(“O”);
matrix.setTextColor(matrix.Color333(7, 0, 4)); //color: purple
matrix.print(“W”);
// Update display
matrix.swapBuffers(false);
}
else {
int x = 0;
while (x < 4) { //number of pictures to take (4)
x++;
//fill the screen with ‘black’
matrix.fillScreen(matrix.Color333(0, 0, 0)); //reset screen to black (clear text)
matrix.setCursor(11, 1); // text position
matrix.setTextSize(2); // size 1 == 8 pixels high
//countdown is from 9 to 0 to allow the camera to refresh and show the image taken for a couple seconds before resetting
matrix.setTextColor(matrix.Color333(0, 0, 7)); //color: blue
matrix.print(‘9’);
delay(1000);
matrix.fillScreen(matrix.Color333(0, 0, 0)); //clear text
matrix.setCursor(11, 1);
matrix.print(‘8’);
delay(1000);
matrix.fillScreen(matrix.Color333(0, 0, 0)); //clear text
matrix.setCursor(11, 1);
matrix.print(‘7’);
delay(1000);
matrix.fillScreen(matrix.Color333(0, 0, 0)); //clear text
matrix.setCursor(11, 1);
matrix.print(‘6’);
delay(1000);
matrix.fillScreen(matrix.Color333(0, 0, 0)); //clear text
matrix.setCursor(11, 1);
matrix.print(‘5’);
delay(1000);
matrix.fillScreen(matrix.Color333(0, 0, 0)); //clear text
matrix.setCursor(11, 1);
matrix.print(‘4’);
delay(1000);
matrix.fillScreen(matrix.Color333(0, 0, 0)); //clear text
matrix.setCursor(11, 1);
matrix.print(‘3’);
delay(1000);
matrix.fillScreen(matrix.Color333(0, 0, 0)); //clear text
matrix.setCursor(11, 1);
matrix.print(‘2’);
delay(1000);
matrix.fillScreen(matrix.Color333(0, 0, 0)); //clear text
matrix.setCursor(11, 1);
matrix.print(‘1’);
delay(300);
digitalWrite(remotePin, HIGH); //send signal to remote to trigger camera
delay(50);
digitalWrite(remotePin, LOW);
delay(650);
matrix.fillScreen(matrix.Color333(0, 0, 0)); //clear text
//flash white to replicate camera flash
matrix.fillRect(8, 4, 16, 8, matrix.Color333(7, 7, 7)); //center rectangle lit
matrix.drawRect(0, 0, 32, 16, matrix.Color333(7, 7, 7)); //perimiter lit
delay(100); //flash for 0.1s
matrix.fillScreen(matrix.Color333(0, 0, 0)); //clear text
delay(500);
// Update display
matrix.swapBuffers(false);
}
}
}
