[ a n y m a | research – blog ]
news from the lab…
Superbeam 2000
Categories: 3D-Printing, video

IMG_7229Nice 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:

 

Screen Shot 2017-06-01 at 16.50.37

Laser cutting

Screen Shot 2017-06-01 at 15.47.10
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.

IMG_8084

IMG_8087

Glue, fill and paint

 

IMG_7176IMG_7177IMG_7180

 

 

IMG_7198

IMG_7200

 

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.

IMG_7205

IMG_8099IMG_8101

IMG_7209

Some more sanding with fine (400) paper, more filling and more sanding later we can roll on the final colour.

IMG_7214

IMG_7218

Internal support structure

 

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.

 

Screen Shot 2017-06-01 at 16.18.57

Sketchup drawing…

IMG_7216

… and assembled filmguide.

The “lens” is just a tube assembled from several laser cut wood circles.

IMG_7219

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

IMG_7223

IMG_7224

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.

IMG_7228

Software

videoplayer.py

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
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

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
/*---------------------------------------------------------------------------------------------
 
	[ 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:

IMG_8075

And in action:

 

Super8 from Michael Egger on Vimeo.

 

Leave a Reply