The 8mm film scanner (Telecine) project, part 5 – Programming

Last time, in Part 4, I described how the Arduino is wired with the two servos, two switches, and the hacked Canon camera. Now, it’s time to pass through the code of the Arduino and show you how easy it actually is to get the Arduino to power your projects.

Flow chart of the program
Flow chart of the program

This flowchart shows you how the process runs. The Arduino rotates the film gate continuously. When the film frame is in the proper place, the gate closes the film gate switch. This tells Arduino to grab a picture. The film transport servo also runs continuously, but if the film moves too fast after the gate, and the film goes too tight, the film transport switch closes. That causes Arduino to rotate the film transport servo slower, until the transport switch opens again, and normal speed resumes. Here’s a flow chart to help you:

I had much trouble timing the servo rotation speeds. Getting the values just right took me hours, and sometimes the speeds wouldn’t work after I added the camera to the loop, because the camera script also has delay features, but eventually I got all the pulse lengths right, and now the system takes 23 images a minute. This gives me just a little over a second of film scanned every minute, so a 4 minute film is scanned in about 3½ hours.

While the Arduino can be programmed in practically any language that can be compiled into binary executable, it is very handy to use the environment provided by the Arduino open source project. When you install it, you get a Notepad-like coding software with context awareness, and code checking. You also can test the code first before uploading to the Arduino, which saves time, and when the code itself passes the test, you can compile it and send it to the Arduino via USB. This is how it looks when you first install the environment:

Arduino environment
Arduino environment

The code you write in here is C++, and it is called a sketch. The sketches are saved with the file extension .ino. A sketch is merely a text file that is then compiled into an executable and sent to the Arduino for running.

The file starts with any header files you may need. Header files contain information for the program about any elements that constitute the Arduino robot. There are also declarations  for variables and constants up here. The headers are followed by the two main functions that have to be part of any Arduino program, the “setup” and “main”, both of the the type void, as they do not return anything. In the setup, you need to declare any variables and connections you make. The Main function is a loop that runs indefinitely, and all the actions you want to have go here.

In my file that runs the 8mm Telecine robot, the headers look like this:

#include <Servo.h> // Include servo library
Servo myGateServo;
Servo myTransportServo;
int cameraPin = 7; 
int filmgatePin = 4; 
int filmgateButtonDown = false; 
int filmTransportPin = 6;
int filmTransportButtonDown = false;
int frameCounter = 0;

Note that in this language syntax, every line must end with a semicolon, and comments can be written after a double slash //. Now, let’s look at the commands.

  • Servo.h is a header file the Arduino needs to know that servos are attached.
  • The “Servo myGateservo” command initiates a servo under the name myGateServo for the film gate
  • The “Servo myTransportservo” command initiates a servo under the name myTransportServo for the film transport
  • “int cameraPin” tells the Arduino that its pin 7 is now reserved for sending the +5 volt signal to the camera via USB.
  • “int filmgatePin” reserves pin 4 for monitoring the film gate switch
  • “int filmgateButtonDown creates a Boolean variable for the state of the film gate switch – and it is declared as FALSE to start with, since the switch is initially open
  • “int filmTransportPin” reserves pin 6 for monitoring the film transport switch
  • “int film TransportButtonDown” creates a Boolean variable for the state of the film transport switch – and it is declared as FALSE to start with, since the switch is open, unless the film is too tight

You should consider naming the variables as clearly as possible, to enable easier debugging and editing later.

After the header part, my Setup looks like this:

void setup() {
 Serial.begin(9600);
 pinMode (cameraPin, OUTPUT); 
 pinMode(filmgatePin, INPUT); 
 digitalWrite(filmgatePin, HIGH); 
 digitalWrite(filmTransportPin, HIGH); 
 myGateServo.attach(2);  
 myTransportServo.attach(8);  
}

First, note that this is a function. Its name is setup, and the contents are contained between curly brackets { and }. Since it doesn’t have to return a value, it is called void, and functions that do return some value are called functions, as we can see later. The contents are as follows:

  • Serial.begin commands the Arduino to send back information to the computer at a rate of 9600 bits per second –  this is necessary only if you want to hear from Arduino how it is doing and I am using this to count the shots the camera takes
  • pinMode is a command to set the pin state as either input or output, and since cameraPin is the one that triggers the camera, it is an output pin
  • pinMode (filmgatePin) sets the filmgatePin as input, because it reads the filmgate switch.
  • digitalWrite(filmgatePin, HIGH) sets a pull-up resistor for the film gate switch, and this is necessary to make sure the pin is grounded, unless the switch is closed
  • myGateServo.attach(2) tells the Arduino that the servo called myGateServo is wired to the pin 2 – similarly pin 8 is the one for myTransportServo

This completes the setting up of the environment for the Arduino. Now it knows what servos are attached to it, and what pins it uses to run them, as well as what other input and output pins it needs to govern.

The main loop, then, is a void function too, but inside it, I have real functions that return values. The reason I went for this approach is that my friend and colleague Tero Karvinen, a world-renowned Arduino guru, told me to build it this way. It helps in the debugging, and it makes the code look really neat and easy.

void loop() {
 myGateServo.writeMicroseconds(1585); 
 if (frameOnGate()) { 
 takePicture();
 } else {
 delay(500); // milliseconds
 }
 if (filmTransportSwitchOpen()) {
 myTransportServo.writeMicroseconds(1528);
 delay(100);
 } else {
 myTransportServo.writeMicroseconds(1505);
 delay(1000);
 }
}

The loop runs until the Arduino loses power. There is actually just three things in it:

First, myGateServo.writeMicroseconds(1585) is a command that causes the Arduino to send a pulse to the gate rotating servo. The width of the pulse is 1585 microseconds, which I found during extensive testing to be the perfect rotating speed for the gate, at 23 frames a minute. If the value is less than 1500, the servo rotates clockwise, and if it is larger than 1500, it rotates counter-clockwise. The smaller the number, the faster the rotation, and at > 1500, the larger the number, the faster the rotation.

This is followed by an IF structure. The system checks to see if the Boolean variable frameOnGate is true (you’ll see the variable in action in a moment). If this is true, the system calls the function takePicture. Else, the Arduino hangs around for 500 milliseconds. All commands to output elements and read commands for input elements need to have a delay attached, otherwise the state of the elements may not be properly set.

Then the program asks, is the filmTransportSwitch open. If it is, it means the film is hanging slack and all is well. Therefore it sends the command writeMicroseconds(1528) to the transport servo, and that means it rotates at the basic speed, which I found by testing. The ELSE in this clause means the switch is closed – the film is too taut, and the servo must run at slower speed until the switch is opened again. This is achieved with the writeMicroseconds(1505) command sent to the myTransportServo. An extra delay of one second keeps the system running until it is time to check again whether the film frame is now in place, and another picture can be taken.

The takePicture function is the crown jewel of the operation. This is how it looks:

void takePicture() {
 digitalWrite(cameraPin, HIGH);
 delay(500);
 digitalWrite(cameraPin, LOW);
 delay(300);
 frameCounter++;
 Serial.print("FrameCounter: ");
 Serial.println(frameCounter);
}

This is yet another void function, because it doesn’t return a value. When this function is entered, it will use the command digitalWrite(cameraPin, HIGH) to send +5 volts from the cameraPin to the camera. The cameraPin has a lead in it that goes into a hacked USB cable’s red wire. This sends a voltage to the camera, and the Canon Hack Development Kit grabs the picture. The Arduino sends the +5V for 500 milliseconds, then another command sets the voltage back to 0: digitalWrite(cameraPin, LOW). The 300 ms delay merely affirms the voltage drop. A variable called frameCounter is then added to once, using the classic C++ notation of frameCounter++. The Serial.print command sends the current frame number to the computer. Since the CHDK counts frames too, I can see if frames have been duplicated or dropped, if the two counters disagree.

The final two functions are the Boolean true/false values needed.

boolean frameOnGate() {
 filmgateButtonDown = digitalRead(filmgatePin);
 if (HIGH == filmgateButtonDown) {
 return true;
 } else {
 return false;
 }
}

The calling of the function by the keyword Boolean implies the function does return either TRUE or FALSE. The line “filmgateButtonDown = digitalRead(filmgatePin)” assigns the filmgateButtonDown variable the value that is found on the filmgatePin, which is digitally read. If it is HIGH, ie. there is current flowing through the switch, and the frame is therefore in place, the function will return the value TRUE, else, it will return FALSE. Check the main program to see how the main loop then reacts to this: if frameOnGate is TRUE, the frame needs to be shot, hence, call the function takePicture.

The other function for the film transport switch works equally:

boolean filmTransportSwitchOpen() {
 filmTransportButtonDown = digitalRead(filmTransportPin);
 if (HIGH == filmTransportButtonDown) {
 return false;
 } else {
 return true;
 }
}

And there you have the entire code that runs the robot that grabs the pictures that form a home movie shot on 8mm film. I hope my walkthrough is clear, but if you have any comments, please leave me a comment in the box provided below. I also hope you have enjoyed the project as much as I have.

 

 

Loading

Leave a Reply

Your email address will not be published. Required fields are marked *

*

This site uses Akismet to reduce spam. Learn how your comment data is processed.