Fujifilm Instax 210 hack(s)






Film Eject Mechanism

Disabled internal electronics. Replaced electronics with a simple relay.

The relay, when combined with the internal push-button trigger, and film eject position sensor, replaces the entire electronics board.

The eject sensor is a switch that opens when the ejector is retracted fully.

The push button is normally open.

The relay applies power to the motor when the relay is energized. When the relay is de-energized, it shorts out the leads to the motor. This short acts as a brake, keeping the motor from coasting freely.

Ejector operation

When you press the trigger, power is applied to the relay, which turns on the motor and applies power to the relay, through the eject sensor switch. When you release the trigger, the relay stays energized until the eject sensor switch opens the circuit, de-energizing the relay, and stopping the motor.

Why Not use a microcontroller

This project would have been a lot more complicated for having a microcontroller, and wouldn’t work any better for it.

Lens mounting

I had an Agfa Commander laying around. I removed all the internal electronics and optics, and opened up the hole on the front of the camera, then mounted the lens.

Focus was achieved by inserting an empty film cartridge with a piece of frosted tape where the film normally resides. I retracted the original lens barrel with the lens set to infinity and looked at the image on the tape with a loupe, looking at a distant target. I then taped the original lens barrel into place.

Judicious use of epoxy and aluminum flashing tape to hold things in place, and to seal against light leaks.

2015-01-03 06.02.45 2015-01-03 06.03.26 IMG

scan-1 scan-2 scan-3 scan-6 scan-5

Here I am a few years later playing with Fuji Instax cameras again after finding one in a shop for $10.

This time I went the opposite route. I replaced the on-board electronics with a microcontroller, display, motor driver and light meter.
Instead of a lens, I used a pinhole, and I re-purposed the shutter to work with the pinhole.

Theory of operation

The only compelling reason to use the Fuji body to begin with is the motorized film transport mechanism. It is an interesting bit of work, one motor serves to extend the lens (now removed) and eject the film. It does this with a neat little clutch and a direction reversal of the motor. Turn one way, lens actuates, turn the other and film ejects.

The important bit here from a control perspective is the little switch inside that closes when the film is finished ejecting. In my previous camera, I made a latching relay that activated the motor until the switch closed and shut off the motor (and shorted its leads to act as a brake). In this version I sense this switch with the microcontroller, and signal the motor controller to shut off the motor and short it out .

The next important bit in the camera is the trigger. This too is sensed by the microcontroller, causing the camera to take an exposure in a variety of different modes of operation.

The last bit of kit from the original camera I use is the shutter mechanism. This is a little electromagnetic thing that acts similar to a motor. Drive the voltage into it one direction and the shutter opens, reverse direction and the shutter closes. When it is left open or closed, the microcontroller shuts off current to it so it does not burn out.


Yes it is fugly.

So the camera is not easy on the eyes. It isn’t meant to be. It only has to do one thing: inform me as to whether or not instant pinhole images are satisfying.

The brains

The brains are the bit surrounded by all the wires. The push buttons for selecting the mode, exposure values and exposure compensation are on the left. The motor driver board is on the top left.

The lcd display

The lcd display show the mode, Auto or Manual, and in Auto mode shows the exposure in seconds and in Manual mode, it shows the exposure as stops from ~1/16th of a second out to several hours, expressed in fractions of a second, seconds, or minutes.

boring light meter

The light meter reads out in lux, and from that I convert to exposure time based on the assumption that my pinhole is about an f118 and my ISO is 800. The black tube narrows the field of view of the sensor to about 20 degrees.

initially there were light leaks, see upper right.

There were some serious light leaks, but I covered every orifice in the case and I think they are under control . (scan from school)

After the light leaks were cleaned up and the auto exposure was roughly calibrated, things are starting to look good.

Now I will take it out and play, see if I like this thing and whether to proceed or not.

Here is my horrible code, come what may.

// Attempt to make a fuji pinhole camera with camera like functions
// input buttons on 20,21,22,23. 
/*
Modes 
Automatic mode with lux sensor (not implemented)
Manual mode 

Automatic mode.
up/down are exposure compensation

Manual mode.
up/down are actual time. 

what does the logic look like. 
 adafruit i2c 128x32 display
 pololu motor controller. p/n 2135, DRV8835 dual motor driver


fstop^2 = lux*ISO
-------   -------
shutter      C

C is an arbitrary correction factor.

 solving for shutter and ignoring C

 shutter=   118^2
            -------
            lux * 800

 X=13924/(lux*800)
 
*/


// include libraries for specific hardware
#include <Wire.h>                 //i2c
#include <Adafruit_GFX.h>         // graphhics
#include <Adafruit_SSD1306.h>     // 128x32 display
#include <Adafruit_Sensor.h>      // sensors
#include "Adafruit_TSL2591.h"     // light meter

#define SCREEN_WIDTH 128 // OLED display width, in pixels
#define SCREEN_HEIGHT 32 // OLED display height, in pixels

// Declaration for an SSD1306 display connected to I2C (SDA, SCL pins)
#define OLED_RESET     17 // Reset pin # (or -1 if sharing Arduino reset pin)
Adafruit_SSD1306 display(SCREEN_WIDTH, SCREEN_HEIGHT, &Wire, OLED_RESET);

//setup stuff for light sensor
Adafruit_TSL2591 tsl = Adafruit_TSL2591(2591); // pass in a number for the sensor identifier (for your use later)

// adafruit 128,32 display, reset on 17, clock n 19, data on 18
// pin assignments
const byte downButton = 20; //down button
const byte  setButton = 21; // set button
const byte   upButton = 22; // up button
const byte modeButton = 23; // mode button
const byte motorSense = 1;// motor position sense 
const byte triggerSense = 0; // trigger sense 
// pertaining to the drve chip
const byte drvBEnable = 3;
const byte drvBPhase = 4;
const byte drvAEnable = 5;
const byte drvAPhase = 6;
const byte drvMode = 7;

// state variables for button presses
volatile byte downState = 0;
volatile byte setState = 0;
volatile byte upState = 0;
volatile byte modeState = 0;
volatile byte motorState = 0;
volatile byte triggerState = 0;
volatile byte nullStates = 0; // any button pressed. for test?
volatile byte textCase =0; // for the debug text case statement

//other variables
byte  modeValue=0;// auto, manual, setup
String modeString;// for debug?
int count =0; //the up/down count value
// list of pre-set exposures in milliseconds. 
int exposureList[46]={62,78,99,125,157,198,250,314,396,500,629,793,1000,1259,1587,2000,2519,3174,4000,5039,6349,8000,10079,12699,16000,20158,25398,32000,40317,50796,64000,80634,101593,128000,161269,203187,256000,322539,406374,512000,645079,812749,1024000,1290159,1625498,2048000};
int exposureValue; //the value we want to expose by
String displayString; // the string to put on the screen
String timeUnits; // seconds  dunno

// exposure factor for 1/3rd stops. multiply by this to get next third stop, divide to get previous third stop. 
const float expFactOneThird=pow(2,1.0/3.0);
// light sensor return variables
float sensorLux ;

void setup() {
  // setup serial
Serial.begin(115200);
Serial.print("working1");
// setup display
// SSD1306_SWITCHCAPVCC = generate display voltage from 3.3V internally
if(!display.begin(SSD1306_SWITCHCAPVCC, 0x3C)) { // Address 0x3C for 128x32
  Serial.println(F("SSD1306 allocation failed"));
  for(;;); // Don't proceed, loop forever
}
// Clear the buffer
display.clearDisplay();
  display.setTextSize(2);      // Normal 1:1 pixel scale
  display.setTextColor(WHITE); // Draw white text
  display.setCursor(0, 0);     // Start at top-left corner
  display.cp437(true);         // Use full 256 char 'Code Page 437' font
display.println(F("InstaPin"));
// display the display
display.display();
delay(1000);
display.clearDisplay();
display.display();
//define pins state, all buttons to pull up
pinMode(downButton,INPUT_PULLUP);//down
pinMode( setButton,INPUT_PULLUP);//set
pinMode(  upButton,INPUT_PULLUP);//up
pinMode(modeButton,INPUT_PULLUP);//mode
pinMode(motorSense,INPUT_PULLUP);//motor switch
pinMode(triggerSense,INPUT_PULLUP);//trigger press
// outputs to motor driver
pinMode( drvBEnable,OUTPUT);
pinMode( drvBPhase,OUTPUT);
pinMode( drvAEnable,OUTPUT);
pinMode( drvAPhase,OUTPUT);
pinMode( drvMode,OUTPUT);
// set to motor drive mode this works
digitalWrite(drvMode,HIGH); // see data sheet , there are 2 modes of operation 
//openShutter();
//delay(1000);
//closeShutter();
//startMotor();
//delay(1000);
//stopMotor();

// attach interrupts

attachInterrupt(digitalPinToInterrupt(downButton), downHandle, LOW); // button
attachInterrupt(digitalPinToInterrupt(setButton), setHandle, LOW); // button
attachInterrupt(digitalPinToInterrupt(upButton), upHandle, LOW); // button
attachInterrupt(digitalPinToInterrupt(modeButton), modeHandle, LOW); // button
attachInterrupt(digitalPinToInterrupt(motorSense), motorHandle, FALLING); //motor internal switch
attachInterrupt(digitalPinToInterrupt(triggerSense), triggerHandle, LOW);// button


// setup the light sensor
int sensor=tsl.begin(); // start up the sensor, demo has us checking for it. 
displaySensorDetails(); // print a bunch of light sensor stuff
configureSensor();      // configure the sensor
}

void loop() {

// change mode on mode press, testing three modes now. THis is for cycling between modes
if(modeState==1){
  modeState=0;
  modeValue=modeValue+1;
  if(modeValue>2){
    modeValue=0;
  }
}
// this bit makes an alpha label to display 
switch (modeValue){
  case 0:
  modeString="Auto";
  break;
  case 1:
  modeString="Manu";
  break;
  case 2:
  modeString="Setup";
  break;
  
}
// while we are in manual mode, up/down presses inc/dec count for exposure. 
if(modeValue==1){ // if we are in manu mode
  if(upState==1){ // if the up button is pressed
    upState=0;    // reset the state variable
    count=count+1; // increment the counter
      if(count>45){ //cap the counter
        count=45;
      } 
  }
  if(downState==1){ // if the down button is pressed
    downState=0;    // if down button, reset state
    count=count-1;  // decrement counter
      if(count<0){  // cap the counter
        count=0;
      } 
  }
  // what we put on the screen
  exposureValue=exposureList[count]; //look up the exposure value from the list and put it into exposure value
  // if it is a fraction of a second show as 1/x
    if(exposureValue<1000){
      displayString = String("1/") + String((1000.0/float(exposureValue)),0);
    }
    //if it is less than a minute and equal to a second or more but lens than a minute, show as just seconds with 1 decimal precision
    else if(exposureValue>=1000&&exposureValue<60000){
      displayString= String((float(exposureValue)/1000),1)+String("S");
    }
    // if it is more than a minute, show as minutes with 1 decimal in 1/10 of a minute
  else{
     displayString=String((float(exposureValue)/60000),1)+String("M");
     }
  // should rewrite to use min/max
}

// while we are in auto mode, we are going to use this as a light meter for now
// readLux and sensorLux
if(modeValue==0){
  readLux();
  exposureValue=int(13924.0/sensorLux*800.0/25);
  displayString=String(exposureValue/1000.0,3);
}

// this bit stops the motor
if(motorState==1){ // if the internal sensor has actuated
  motorState=0; // reset the flag
  stopMotor();
}

// if the trigger goes off and the mode is manual then initiate an exposure. 
if(triggerState==1 ){
  triggerState=0; // reset the flag
    if (modeValue==1 || modeValue==0){ // man or auto
      triggerState=0;
      openShutter();
      delay(exposureValue);
      closeShutter();
      startMotor();
      delay(250);// since this is in the part that handles an interrupt flag, this delay allows the motor to run off the sensor. 
  }
}

// display the mode and count. 
display.clearDisplay();
display.setCursor(0, 0);     // Start at top-left corner
display.setTextSize(2); 
display.print(modeString);
Serial.println(exposureValue);
display.println(" ");
//Serial.print(exposureValue);Serial.print(" ");
//Serial.println(displayString);
//display.setCursor(0,1);     // Start at top-left corner
display.print(displayString);
//display.print(count);
display.display();

  // put your main code here, to run repeatedly:
//Serial.print("test");
//Serial.println(digitalRead(triggerSense));
delay(10);
}

void downHandle(){
  static unsigned long last_interrupt_time=0;
  unsigned long interrupt_time=millis();
  if (interrupt_time - last_interrupt_time > 1000){
    nullStates=1;
    downState=1;
    textCase=4;
  }
}

void setHandle(){
  static unsigned long last_interrupt_time=0;
  unsigned long interrupt_time=millis();
  if (interrupt_time - last_interrupt_time > 1000){
    nullStates=1;
    setState=1;
    textCase=4;
  }
}

void upHandle(){
  static unsigned long last_interrupt_time=0;
  unsigned long interrupt_time=millis();
  if (interrupt_time - last_interrupt_time > 1000){
    nullStates=1;
    upState=1;
    textCase=4;
  }
}

void modeHandle(){
  static unsigned long last_interrupt_time=0;
  unsigned long interrupt_time=millis();
  if (interrupt_time - last_interrupt_time > 1000){
    nullStates=1;
    modeState=1;
    //textCase=4;
  }
}

void motorHandle(){
// the little switch that says a shot has been ejected. SHort both 
// motor leads to ground
motorState=1;
}

void triggerHandle(){
// what to do if a trigger event is sensed  
  static unsigned long last_interrupt_time=0;
  unsigned long interrupt_time=millis();
  if (interrupt_time - last_interrupt_time > 1000){
    nullStates=1;
    triggerState=1;
    //textCase=4;
  }
}

void openShutter(){
// open the shutter at full power
analogWrite(drvBEnable,255);
digitalWrite(drvBPhase,LOW);
// wait a short while
delay(50);
// turn off shutter solenoid
analogWrite(drvBEnable,0);
digitalWrite(drvBPhase,LOW);

}
void closeShutter(){
// close the shutter at full power
analogWrite(drvBEnable,255);
digitalWrite(drvBPhase,HIGH);
// wait a while
delay(50);
// shut off the shutter motor
analogWrite(drvBEnable,0);
digitalWrite(drvBPhase,HIGH);

}
void startMotor(){
pinMode(drvAPhase,OUTPUT);
analogWrite(drvAEnable,255);
digitalWrite(drvAPhase,HIGH);
}
void stopMotor(){
pinMode(drvAPhase,INPUT);
analogWrite(drvAEnable,0);
digitalWrite(drvAPhase,HIGH);
}
// light sensor display stuff
void displaySensorDetails(void)
{
  sensor_t sensor;
  tsl.getSensor(&sensor);
  Serial.println(F("------------------------------------"));
  Serial.print  (F("Sensor:       ")); Serial.println(sensor.name);
  Serial.print  (F("Driver Ver:   ")); Serial.println(sensor.version);
  Serial.print  (F("Unique ID:    ")); Serial.println(sensor.sensor_id);
  Serial.print  (F("Max Value:    ")); Serial.print(sensor.max_value); Serial.println(F(" lux"));
  Serial.print  (F("Min Value:    ")); Serial.print(sensor.min_value); Serial.println(F(" lux"));
  Serial.print  (F("Resolution:   ")); Serial.print(sensor.resolution, 4); Serial.println(F(" lux"));  
  Serial.println(F("------------------------------------"));
  Serial.println(F(""));
  delay(500);
}
// light sensor stuff
void configureSensor(void)
{
  // You can change the gain on the fly, to adapt to brighter/dimmer light situations
  //tsl.setGain(TSL2591_GAIN_LOW);    // 1x gain (bright light)
  tsl.setGain(TSL2591_GAIN_MED);      // 25x gain
  //tsl.setGain(TSL2591_GAIN_HIGH);   // 428x gain
  
  // Changing the integration time gives you a longer time over which to sense light
  // longer timelines are slower, but are good in very low light situtations!
  //tsl.setTiming(TSL2591_INTEGRATIONTIME_100MS);  // shortest integration time (bright light)
  // tsl.setTiming(TSL2591_INTEGRATIONTIME_200MS);
  tsl.setTiming(TSL2591_INTEGRATIONTIME_300MS);
  // tsl.setTiming(TSL2591_INTEGRATIONTIME_400MS);
  // tsl.setTiming(TSL2591_INTEGRATIONTIME_500MS);
  // tsl.setTiming(TSL2591_INTEGRATIONTIME_600MS);  // longest integration time (dim light)

  /* Display the gain and integration time for reference sake */  
  Serial.println(F("------------------------------------"));
  Serial.print  (F("Gain:         "));
  tsl2591Gain_t gain = tsl.getGain();
  switch(gain)
  {
    case TSL2591_GAIN_LOW:
      Serial.println(F("1x (Low)"));
      break;
    case TSL2591_GAIN_MED:
      Serial.println(F("25x (Medium)"));
      break;
    case TSL2591_GAIN_HIGH:
      Serial.println(F("428x (High)"));
      break;
    case TSL2591_GAIN_MAX:
      Serial.println(F("9876x (Max)"));
      break;
  }
  Serial.print  (F("Timing:       "));
  Serial.print((tsl.getTiming() + 1) * 100, DEC); 
  Serial.println(F(" ms"));
  Serial.println(F("------------------------------------"));
  Serial.println(F(""));
}

// read sensor and display
void simpleRead(void)
{
  // Simple data read example. Just read the infrared, fullspecrtrum diode 
  // or 'visible' (difference between the two) channels.
  // This can take 100-600 milliseconds! Uncomment whichever of the following you want to read
  uint16_t x = tsl.getLuminosity(TSL2591_VISIBLE);
  //uint16_t x = tsl.getLuminosity(TSL2591_FULLSPECTRUM);
  //uint16_t x = tsl.getLuminosity(TSL2591_INFRARED);

  Serial.print(F("[ ")); Serial.print(millis()); Serial.print(F(" ms ] "));
  Serial.print(F("Luminosity: "));
  Serial.println(x, DEC);
}
// advanced read and print
void advancedRead(void)
{
  // More advanced data read example. Read 32 bits with top 16 bits IR, bottom 16 bits full spectrum
  // That way you can do whatever math and comparisons you want!
  uint32_t lum = tsl.getFullLuminosity();
  uint16_t ir, full;
  ir = lum >> 16;
  full = lum & 0xFFFF;
  Serial.print(F("[ ")); Serial.print(millis()); Serial.print(F(" ms ] "));
  Serial.print(F("IR: ")); Serial.print(ir);  Serial.print(F("  "));
  Serial.print(F("Full: ")); Serial.print(full); Serial.print(F("  "));
  Serial.print(F("Visible: ")); Serial.print(full - ir); Serial.print(F("  "));
  Serial.print(F("Lux: ")); Serial.println(tsl.calculateLux(full, ir), 6);
}

// read using the api...
void unifiedSensorAPIRead(void)
{
  /* Get a new sensor event */ 
  sensors_event_t event;
  tsl.getEvent(&event);
 
  /* Display the results (light is measured in lux) */
  Serial.print(F("[ ")); Serial.print(event.timestamp); Serial.print(F(" ms ] "));
  if ((event.light == 0) |
      (event.light > 4294966000.0) | 
      (event.light <-4294966000.0))
  {
    /* If event.light = 0 lux the sensor is probably saturated */
    /* and no reliable data could be generated! */
    /* if event.light is +/- 4294967040 there was a float over/underflow */
    Serial.println(F("Invalid data (adjust gain or timing)"));
  }
  else
  {
    Serial.print(event.light); Serial.println(F(" lux"));
  }
}
// my stripped down versions of light sensors
void readLux(){
  // More advanced data read example. Read 32 bits with top 16 bits IR, bottom 16 bits full spectrum
  // That way you can do whatever math and comparisons you want!
  uint32_t lum = tsl.getFullLuminosity();
  uint16_t ir, full;
  ir = lum >> 16;
  full = lum & 0xFFFF;
  sensorLux=tsl.calculateLux(full, ir), 6;
}

10 thoughts on “Fujifilm Instax 210 hack(s)

  1. Hi,
    I very much like your hack, but are you positive one needs a relay? From what I understand you could use the push-button trigger to get the eject mechanism started, this would engage the eject sensor switch that would stay on until the full retraction of the eject lever that also disengages the eject sensor switch.
    Cheers,
    Uki

    • the switch that senses when the film has ejected closes when the film has finished. You need to start the motor, then the motor needs to shut off when the switch closes.

  2. hello, nice comment on how to shortcut the electronic.
    i’m working a projet to put and instax back on anoter camera. would it be possible to have a diagram (pic a draw paper would be fine) of your relay solution 🙂 thanks a lot jay

  3. Hi, can you post a scheme (or a photo) of how you managed to connect the internal push-button trigger and film eject position sensor to the relay. Since the camera uses 4 AA Batteries, i guess the relay works at 6v. Is that correct?

    Thanks in advance

  4. Hey, I can’t believe you just posted this. I just got a 210 and I’d like to do something similar to it so I can have more flexibility with it. Vould you share any more info about your microcontroller setup, circuits, or code? Thanks

  5. Hey, not sure if this is still active. I am doing something similar and took the 210 to pieces. I now have a constant feed of film pushing through but want one at a time like a camera should. Can you help an amateur?

Leave a Reply

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