Nice little project that has been keeping me busy for this last while: building a fake Super8 projector for a theatre play. For his piece “Quitter la terre” Joel Maillard wanted a Super8 projector – but this can be a bit fiddly and unreliable on stage, also light output of original Super8, is quite poor. Turn around time is too long, every new idea has to be shot, sent to the lab, edited by hand back into the movie. Too much time passes in between for the short theatre production times. I first imagined to retrofit a real vintage projector with a small LED video beamer, but I finally decided to build one from scratch.
Drawing
Design was made on a beach in Greece in Sketchup:
Laser cutting

All pieces (over 100) are then color coded by thickness, copied and laid out flat in Sketchup, exported to DXF using this plugin and laser cut from 3mm and 5mm HDF sheets, 10mm plywood, and 3mm and 10mm acrylic.
Glue, fill and paint





Then after some rounding of the edges, sanding all faces, filling unwanted holes and sanding again, a first layer of acrylic primer is sprayed on.
Some more sanding with fine (400) paper, more filling and more sanding later we can roll on the final colour.
Mechanical buttons and shutter
Film transport and video playback is started with a big turning button. This button also moves a mechanical shutter that blocks all light from the beamer when the projector is supposed to be “off”. Projected black is never totally black, and I don’t want any error messages or logos appear by accident.
Super8-Shutter from Michael Egger on Vimeo.
All moving wooden parts are lubricated with graphite powder.
Film guide and lens
To support the illusion a real film is transported from one reel to the other, but it has to be outside of the video projectors light path, so a construction made from laser cut acrylic guides the film around the beamer.
The “lens” is just a tube assembled from several laser cut wood circles.
Electronics
The system is comprised of
- 12V power supply for Arduino, motor and fan
- 5V power supply for Raspberry Pi
- 50:1 gear motor for film transport
- Exhaust fan
- 2 Hall sensors for Start/Stop and reset buttons
- 1 additional mechanical push button
- 1 potentiometer for motor speed control
- TC1047 Temperature sensor for overheating detection
- Arduino for reading sensors, motor and fan control
- Raspberry Pi (controlled by Arduino through GPIO) for video playback
- Vivitek Qumi 7+ 1000 lumes LED video projector
Probably all control could have been implemented directly on the Raspberry Pi, but my Pi-Fu is still weak – that’s why the script running is just a lazy copy-pasta from here and here, every thing else is done on the Arduino.
Software
videoplayer.py
import RPi.GPIO as GPIO
import os
import sys
from subprocess import Popen
import pygame
import time
pygame.init()
pygame.mouse.set_visible(False)
screen = pygame.display.set_mode((0, 0), pygame.FULLSCREEN)
screen.fill((0, 0, 0))
GPIO.setmode(GPIO.BCM)
GPIO.setup(4, GPIO.IN, pull_up_down=GPIO.PUD_UP)
GPIO.setup(17, GPIO.IN, pull_up_down=GPIO.PUD_UP)
GPIO.setup(22, GPIO.IN, pull_up_down=GPIO.PUD_UP)
movie1 = ("/home/pi/Videos/01.mp4")
movie2 = ("/home/pi/Videos/02.mp4")
last_state1 = True
last_state2 = True
input_state1 = True
input_state2 = True
quit_video = True
player = False
done = False
while not done:
#Read states of inputs
input_state1 = GPIO.input(4)
input_state2 = GPIO.input(17)
quit_video = GPIO.input(22)
#If GPIO(4) is shorted to Ground
if input_state1 != last_state1:
if (player and not input_state1):
os.system('killall omxplayer.bin')
omxc = Popen(['omxplayer','-bz', '-o','hdmi', movie1])
player = True
elif not input_state1:
omxc = Popen(['omxplayer','-bz', '-o','hdmi', movie1])
player = True
#If GPIO(17) is shorted to Ground
elif input_state2 != last_state2:
if (player and not input_state2):
os.system('killall omxplayer.bin')
omxc = Popen(['omxplayer','-bz', '-o','hdmi', movie2])
player = True
elif not input_state2:
omxc = Popen(['omxplayer','-bz', '-o','hdmi', movie2])
player = True
#If omxplayer is running and GIOP(17) and GPIO(18) are not shorted to Ground
elif (player and input_state1 and input_state2):
os.system('killall omxplayer.bin')
player = False
#GPIO(24) to close omxplayer manually - used during debug
if quit_video == False:
os.system('killall omxplayer.bin')
player = False
#Set last_input states
last_state1 = input_state1
last_state2 = input_state2
for event in pygame.event.get():
if event.type == pygame.QUIT:
done = True
elif event.type == pygame.KEYDOWN:
if event.key == pygame.K_ESCAPE:
done = True
time.sleep(0.01)
pygame.quit()
Super8.ino
/*---------------------------------------------------------------------------------------------
[ a n y m a | SUPERBEAM 2000 ]
Controller firmware
cc) 2017 by Michael Egger
Relased under GNU GPL 3.0
--------------------------------------------------------------------------------------------- */
#include "Timer.h"
//Specify the links and initial tuning parameters
//PID myPID(&Input, &Output, &Setpoint,2,5,1, DIRECT);
Timer t;
const char DEBUG = 0;
unsigned char btn_start, btn_reset, btn_push;
unsigned char current_speed, target_speed,fan_speed;
unsigned int motor_speed;
unsigned char current_movie;
//- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
// setup
void setup() {
pinMode(3,OUTPUT); // Motor on Pin 3
pinMode(5,OUTPUT); // Fan on Pin 5
analogWrite(5,0);
pinMode(6,OUTPUT); // to Raspi GPIO 22
pinMode(7,OUTPUT); // to Raspi GPIO 17
pinMode(8,OUTPUT); // to Raspi GPIO 4
digitalWrite(6,HIGH);
digitalWrite(7,HIGH);
digitalWrite(8,HIGH);
pinMode(12,INPUT_PULLUP); // safety push button
//analogReference(INTERNAL);
//initialize the PID
//Setpoint = 28;
//myPID.SetMode(AUTOMATIC);
btn_push = digitalRead(12);
if (DEBUG) {
Serial.begin(9600);
t.every(1000, check_btns);
t.every(4, check_motor);
t.every(1000, check_temperature);
} else {
t.every(10, check_btns);
t.every(4, check_motor);
t.every(5000, check_temperature);
}
//analogWrite(5,100);
}
//- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
// motors
void check_motor() {
if (current_speed < target_speed) current_speed++; if (current_speed > target_speed) current_speed--;
unsigned int output;
output = current_speed * motor_speed / 255;
analogWrite(3,output);
}
//- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
// btns
void check_btns() {
unsigned int temp;
motor_speed = map(analogRead(A3),0,1024,0,255);
temp = analogRead(A4);
if (DEBUG) {Serial.print("A4:");Serial.println(temp);}
if (btn_reset) {
if (temp > 970) {
btn_reset = 0;
}
} else {
if (temp < 950) { btn_reset = 1; current_movie = 0; } } temp = analogRead(A5); if (DEBUG) {Serial.print("A5:");Serial.println(temp);} if (btn_start) { if (temp > 980) {
btn_start = 0;
target_speed = 0;
}
} else {
if (temp < 970) { btn_start = 1; current_movie++; target_speed = 255; } } temp = digitalRead(12); if (temp != btn_push) { btn_push = temp; if (temp == 0) { if (target_speed == 0) { current_movie++; target_speed = 255; } else { target_speed = 0; } } } current_movie %= 2; if (target_speed) { if (current_movie == 0) digitalWrite(7,LOW); if (current_movie == 1) digitalWrite(8,LOW); } else { digitalWrite(6,HIGH); digitalWrite(7,HIGH); digitalWrite(8,HIGH); } } //- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - // temp sens void check_temperature() { int temp = analogRead(A0); if (DEBUG) {Serial.print("Temperature:");Serial.println(temp);} if (temp > 210) fan_speed = 255;
else if (temp > 180) {
fan_speed = map(temp,180,210,100,255);
} else if (temp < 170) { fan_speed = 0; }
analogWrite(5,fan_speed);
}
//- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
// loop
//- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
void loop() {
t.update();
}
Done
Here it is, the Superbeam2000 in its natural habitat next to its new friend a real old school overhead projector:
And in action:
Super8 from Michael Egger on Vimeo.
















