John Calder
Published © MIT

Mod-8mm

Mod-8mm: rescuing and improving old Super-8mm cameras by using stepper motors. Eliminating sprocket holes makes space for 16:9 images.

IntermediateWork in progress242
Mod-8mm

Things used in this project

Hardware components

STM32 Nucleo NUCLEO-F303K8
For modifying 1 x camera and controlling 1 x film digitiser. The Nucleo can well handle the multiple workloads of 3 x stepper motors and a laser source via a polling loop with timing in microseconds. Along with supporting monitoring and remote control by serial communications.
×2
A4988 Stepper Motor Driver
Camera "Prototype One" needs 3 of these - shutter motor; main drive motor; aperture actuator. Most cameras would not need the aperture actuator. Plus one for a trial configuration of the digitiser.
×4
NEMA 11 stepper motor
For modifying 1 x camera. This replaces the existing sound drive capstan and works with the existing pinch roller to become the main film advance motor. For creating 1 x digitiser which is a device to move the film frame by frame taking a still image of each frame. Video editors can read this "frame sequence" as a video.
×2
VITECH 20BY Mini 20mm Round Stepper Motor
Replaces the main drive motor of a Super-8mm camera. With the claw mechanism removed the Vitech 20BY has a lesser workload spinning the shutter and the take-up rotor. In Prototype One I discover it to be an easy drop in replacement being slightly smaller than the original and with a gear wheel compatible with the existing gear train.
×1
Micro Mini 8mm Stepper Motor
Very small stepper motor to open and close the aperture on Prototype One, which happens to be an especially complex Sankyo 620 Supertronic Super-8mm camera. As far as I know this would not be needed for almost any other camera. See Story video next page.
×1
secondhand old Super-8mm sound camera
I bought this secondhand about 1987. In 2025 it was dead and available to mod. See Story videos next page.
×1
520nm 10mw green dot laser module 6mm
To mark a "registration dot" beside each frame of film.
×1
BC337 Transistor
Camera MCU pin D10 can output 20mA max. The above green dot laser module draws about 80mA. The humble BC337 is working well as the switching transistor solution. The digitiser Canon Powershot camera needs a 5V signal to capture an image. I have tried it on the 3.3V output from STM32F303K8 MCU and no response. The BC337 works as a simple and effective boost from 3.3V to 5V. The "sketch" (program) needed a rewrite to work with the invert effect where digital output HIGH results in 0V to the camera and becomes the wait state. A brief digital output LOW triggers the camera.
×2
DFRobot DFR0571 Buck 5V Power Module
5V power supply for electronic sub systems. DFR0571 provides this from the camera power supply of 6 x NiMH AA batteries delivering 7.8V fully charged.
×1

Software apps and online services

Arduino IDE
Arduino IDE

Story

Read more

Schematics

Camera Circuits

Mod-8mm Individual Component Testing

Circuit and Sketch for testing stepper motors and developing code for camera use.

Mod-8mm - Document One

Project Story in detail

Mod-8mm - Super-8mm Camera Mods

Getting into the details of hacking old Super-8mm cameras to achieve Mod-8mm. Following the Prototype One case of a Sankyo 620 Supertronic.

Code

Camera_Sankyo620.ino

Arduino
Code for camera "Prototype One" working as at 13 May 2026. This codes a single MCU, using a polling loop to "multitask" the running of 3 x stepper motors and a 10mw laser light source. "Prototype One" uses an STM32F303K8 "Nucleo" development board selected for its fast running of digitalRead, digitalWrite and Serial.print operations. This code also works on an "Arduino Nano" board.
/*
Project "Mod-8mm"
Code to run a modified Super-8mm camera in "Mod-8" mode
with sprocketless film advance by stepper motor with pinch roller.
Copyright (C) 2026 John Calder some rights reserved
This Mod-8mm code applies the Open Source "MIT License".
https://choosealicense.com/licenses/mit/
Mod-8mm general (other than code) documents apply Creative Commons CC4.0-BY licensing.
Details in those documents.
Sankyo 620 Camera special mod is the use of a mini stepper motor for aperture control.
Started 2026-04-07 JPC John Calder
2026-04-19 test with drive motor - add diagnostics
2026-04-25 add aperture control
2026-05-08 JPC aperture control is available at all times including RUN
2026-05-09 JPC trials of power supplies, batteries versus bench supply - acting the same
2026-05-13 JPC problem with "Nucleo" pin 9 not responding to digitalWrite code
 solution - discover this STM32 Nucleo is not liking 5V inputs into A1, A2, A3
 but it is going OK after moving these inputs to D11, D12 and D3
*/
//*******************************************************
//Configuration Section START - alter values here for individual camera setup
//all pulse widths are the time on HIGH
//the pulse step cycle is 2 x these values
const unsigned long APERTURE_PULSEWIDTH = 40000;
const unsigned long SHUTTER_PULSEWIDTH = 1389;  //18fps (1/18) * (1/20) * (1/2)
//const unsigned long SHUTTER_PULSEWIDTH = 1389 * 18 / 2; //2fps testing
const unsigned long DRIVE_PULSEWIDTH = 300;  //nominally 300, increase for testing
//const unsigned long DRIVE_PULSEWIDTH = 3000; // advance in about 1/6 sec
//adjust time for optimal mark exposure adjusting according to film results
const unsigned long LASER_ZAP_TIME = 20000;  //start with 1/50 second
//
//On building the camera it is a challenge to know which direction
//drives will run from stepper motors.
//Therefore on first testing, observe the direction
//each mechanical sub-system is turning,
//and if going in reverse change its LOW to HIGH
const byte DIR_SHUTTER_DEFAULT = HIGH;
const byte DIR_DRIVE_DEFAULT = LOW;
//Configuration Section END
//*******************************************************
// Outport ports for motor control
const byte ADVANCE_SIGNAL = 2;
const byte STEP_APERTURE = 4;
const byte DIR_APERTURE = 5;
const byte STEP_DRIVE = 6;
const byte DIR_DRIVE = 7;
const byte STEP_SHUTTER = 8;  //2026-05-07 testing was 11
const byte DIR_SHUTTER = 9;   //2026-05-07 testing was 12
const byte REGISTRATION_MARK = 10;
// Input ports for control switches
const byte RUN_SWITCH = A0;
//2026-05-13 JPC discover this STM32 Nucleo is not liking 5V inputs into A1, A2, A3
// but it is going OK with moving these inputs to D11, D12 and D3
const byte APERTURE_PLUS = 11;
const byte APERTURE_MINUS = 12;
const byte SINGLE_FRAME = 3;
//Shutter is a repeating run while the run switch is ON,
//toggling the output every SHUTTER_PULSEWIDTH microseconds
unsigned long shutterPulseStartTime;
//Main drive is more complex because of tracking 27 steps of varying pulse width
byte driveState = 0;                  // 0,1,2 ... 26, 27
unsigned long drivePulseStartTime;    //timing of each of the 27 steps
unsigned long driveAdvanceStartTime;  //timing of the frame advance
unsigned long aperturePulseStartTime;
unsigned long prevTime;
unsigned long thisTime;
unsigned long cycleHalf;
unsigned long cycleTime;
unsigned long startTime;
void setup() {
  // pin defaults, Serial monitoring setup, initialise polling check values
  pinMode(ADVANCE_SIGNAL, INPUT);
  pinMode(STEP_APERTURE, OUTPUT);  //Aperture special for Sankyo 620
  pinMode(DIR_APERTURE, OUTPUT);   //Aperture special for Sankyo 620
  pinMode(STEP_DRIVE, OUTPUT);     //Drive Motor Step control
  pinMode(DIR_DRIVE, OUTPUT);      //Drive Motor Configure Direction
  pinMode(STEP_SHUTTER, OUTPUT);   //Shutter Motor Step control
  pinMode(DIR_SHUTTER, OUTPUT);    //Shutter Motor Configure Direction
  pinMode(REGISTRATION_MARK, OUTPUT);
  pinMode(RUN_SWITCH, INPUT);  // RUN!
  pinMode(SINGLE_FRAME, INPUT);
  pinMode(APERTURE_PLUS, INPUT);   //Aperture Step control special for Sankyo 620
  pinMode(APERTURE_MINUS, INPUT);  //Aperture Step control special for Sankyo 620
  //for indication that this code is running
  pinMode(LED_BUILTIN, OUTPUT);
  //2026-05-13 set all digitalWrite pins to gnd
  for (int i = 4; i <= 10; i++) {
    digitalWrite(i, LOW);
  }
  //then apply exceptions from config section above
  //2026-05-07 set above
  digitalWrite(DIR_SHUTTER, DIR_SHUTTER_DEFAULT);
  digitalWrite(DIR_DRIVE, DIR_DRIVE_DEFAULT);
  // aperture applied below in void loop()
  Serial.begin(115200);
  digitalWrite(LED_BUILTIN, HIGH);
  //give the human time to do startup things like clear the serial monitor
  delay(5000);
  Serial.println("PROGRAM START. A2 = " + (String)A2);
  thisTime = micros();
  prevTime = thisTime;
  startTime = thisTime;
  cycleHalf = thisTime;
  cycleTime = thisTime;
}
void loop() {
  //timing
  prevTime = thisTime;
  thisTime = micros();
  //manage case of running more than 70 minutes causing micros() to reset
  if (thisTime < prevTime) {
    Serial.println("micros() reset at " + (String)(prevTime / 60000000) + " min.");
    prevTime = thisTime;
    startTime = thisTime;
    cycleHalf = thisTime;
    cycleTime = thisTime;
    delayMicroseconds(10);
    return;
  }
  if (digitalRead(RUN_SWITCH) == HIGH) {
    delayMicroseconds(10);
    shutter();
    drive();
  } else {
    //reset motors
    if (driveState > 0) {
      digitalWrite(STEP_SHUTTER, LOW);
      digitalWrite(STEP_DRIVE, LOW);
      driveState = 0;
    }
    delayMicroseconds(100);
    shutterPulseStartTime = thisTime;
  }
  if (digitalRead(APERTURE_MINUS) == HIGH) {
    digitalWrite(DIR_APERTURE, LOW);
    if (SHUTTER_PULSEWIDTH >= 10000) Serial.print("AL, ");
    aperture();
  } else if (digitalRead(APERTURE_PLUS) == HIGH) {
    digitalWrite(DIR_APERTURE, HIGH);
    if (SHUTTER_PULSEWIDTH >= 10000) Serial.print("AH, ");
    aperture();
  } else {
    digitalWrite(STEP_APERTURE, LOW);
    aperturePulseStartTime = thisTime;
  }
  //flash LED on an 8 second cycle, include state reporting
  if (thisTime - cycleHalf >= 4000000) {  //half cycle for each of led-on and led-off
    byte ledStatus = digitalRead(LED_BUILTIN);
    bool isMonitor = (thisTime / 1000000 <= 54);
    //Limit serial monitoring to checking near the beginning of the run
    if (isMonitor) {
      Serial.println("LED=" + (String)ledStatus + "; RUN=" + (String)digitalRead(A0));
    }
    if (ledStatus == HIGH) {
      digitalWrite(LED_BUILTIN, LOW);
    } else {
      digitalWrite(LED_BUILTIN, HIGH);
      if (isMonitor) {  //stop serial monitoring but allow led to keep flashing
        Serial.println("Run time = " + (String)((micros() - startTime) / 1000000) + " sec; Cycle time = " + (String)(thisTime - cycleTime) + " microsec.");
      }
      cycleTime = thisTime;
    }
    cycleHalf = thisTime;
  }
}
void drive() {
  if (driveState == 0) {
    if (digitalRead(ADVANCE_SIGNAL) == LOW) {
      return;
    } else {
      driveState = 1;
      drivePulseStartTime = thisTime;
      driveAdvanceStartTime = thisTime;
      //TESTING 2026-04-19
      if (DRIVE_PULSEWIDTH >= 100000) {
        Serial.println("Advance. driveState =");
        Serial.print((String)driveState);
      }
      //delay(1000);
    }
  }
  // registration mark laser zap device
  if (thisTime - driveAdvanceStartTime <= LASER_ZAP_TIME) {
    digitalWrite(REGISTRATION_MARK, HIGH);
  } else {
    digitalWrite(REGISTRATION_MARK, LOW);
  }
  long unsigned pulseTime = thisTime - drivePulseStartTime;
  switch (driveState) {
    case 1:
    case 27:
      // check for step ending
      if (pulseTime >= DRIVE_PULSEWIDTH * 2 * 2) {
        if (driveState == 1) {
          driveState = 2;
        } else {
          driveState = 0;
        }
        drivePulseStartTime = thisTime;  //needed for 2
        if (DRIVE_PULSEWIDTH >= 100000) {
          Serial.print(", " + (String)driveState);  //TESTING
          if (driveState == 0) Serial.println(", DONE.");
        }
        return;
      } else if (pulseTime >= DRIVE_PULSEWIDTH * 2) {
        digitalWrite(STEP_DRIVE, LOW);
      } else {
        digitalWrite(STEP_DRIVE, HIGH);
      }
      break;
    case 2:
    case 26:
      if (pulseTime >= DRIVE_PULSEWIDTH * 3) {
        driveState++;
        //TESTING  do more monitoring when configured to run slowly
        if (DRIVE_PULSEWIDTH >= 100000) Serial.print(", " + (String)driveState);
        drivePulseStartTime = thisTime;
        return;
      } else if (pulseTime >= DRIVE_PULSEWIDTH * 3 / 2) {
        digitalWrite(STEP_DRIVE, LOW);
      } else {
        digitalWrite(STEP_DRIVE, HIGH);
      }
      break;
    default:  //cases 3 to 25
      if (pulseTime >= DRIVE_PULSEWIDTH * 2) {
        driveState++;
        //TESTING  do more monitoring when configured to run slowly
        if (DRIVE_PULSEWIDTH >= 100000) Serial.print(", " + (String)driveState);
        drivePulseStartTime = thisTime;
        return;
      } else if (pulseTime >= DRIVE_PULSEWIDTH) {
        digitalWrite(STEP_DRIVE, LOW);
      } else {
        digitalWrite(STEP_DRIVE, HIGH);
      }
      break;
  }
}
void shutter() {
  long unsigned pulseTime = thisTime - shutterPulseStartTime;
  if (pulseTime >= SHUTTER_PULSEWIDTH * 2) {
    shutterPulseStartTime = thisTime;
    return;
  }
  if (pulseTime >= SHUTTER_PULSEWIDTH) {
    digitalWrite(STEP_SHUTTER, LOW);
  } else {
    digitalWrite(STEP_SHUTTER, HIGH);
  }
}
void aperture() {
  long unsigned pulseTime = thisTime - aperturePulseStartTime;
  if (pulseTime >= APERTURE_PULSEWIDTH * 2) {
    //2026-04-26 JPC aperture into state of waiting for a switch on
    aperturePulseStartTime = thisTime;
    digitalWrite(STEP_APERTURE, LOW);
    return;
  }
  if (pulseTime >= APERTURE_PULSEWIDTH) {
    digitalWrite(STEP_APERTURE, LOW);
  } else {
    digitalWrite(STEP_APERTURE, HIGH);
  }
}

Digitiser Prototype One

Arduino
MCU Code to control a stepper motor to advance film by its frame height in a repeating cycle. After advance, the stepper motor pauses for 2.5 seconds to trigger the camera to capture an image of the film frame.
/*
  Digitiser Test v01 John Calder JPC started 2026-05-16 
  Advance frame by frame capturing images of film frames
  The camera trigger signal is OUTPUT from pin 10.
  The camera needs 5V. Pin 10 of the STM32F303K8 Nucleo MCU provides 3.3V
  A BC337 transistor provides the boost. 

  Mod-8mm Project code applies the Open Source "MIT License"
  Copyright (C) 2026 John Calder some rights reserved
  https://choosealicense.com/licenses/mit/

  2026-05-17 JPC needs BC337 transistor to boost 3.3V signal to 5V 
  with that needs LOW and HIGH reversed, HIGH is OFF, LOW triggers the camera
*/
//*******************************************************
//Configuration Section START - alter values here for individual camera setup
//all pulse width is the time on HIGH
//the pulse step cycle is 2 x these values
//NOTE because of slower speed run, timing is with milliseconds 
//rather than microseconds
//Digitiser runs slowly, with frame change in about 0.5sec rather than 0.02 sec
const byte DIR_DRIVE_DEFAULT = LOW;
const int NSTEPS = 61; //how many steps to do a frame advance
const unsigned long DRIVE_PULSEWIDTH = 5;
const unsigned long CAMERA_DELAY = 150;
const unsigned long CAMERA_PULSE = 200; //standard is 200, more for testing
const unsigned long CAPTURE_TIME = 2500; //standard is 2500, more for testing
//Configuration Section END
//*******************************************************
//output ports
const byte STEP_DRIVE = 6; //control pin D6
const byte DIR_DRIVE = 7; //control pin D7
const byte CAPTURE_IMAGE = 10; //signal camera to take photo of movie frame
unsigned long frameCount = 0;

// the setup function runs once when you press reset or power the board
void setup() {
  // initialize digital pin LED_BUILTIN as an output.
  double stepSpeedAssess = 0;
  pinMode(STEP_DRIVE, OUTPUT); //Step control
  pinMode(DIR_DRIVE, OUTPUT); //Direction 
  pinMode(LED_BUILTIN, OUTPUT);
  pinMode(CAPTURE_IMAGE, OUTPUT);
    
  Serial.begin(115200);
  //initialise ports, supposed to be default but we are seeing a possible need
  digitalWrite(STEP_DRIVE, LOW);
  digitalWrite(DIR_DRIVE, DIR_DRIVE_DEFAULT);
  digitalWrite(CAPTURE_IMAGE, HIGH);

  //2025-11-24 JPC give a distinctive startup display
  delay(3000);
 
  digitalWrite(LED_BUILTIN, HIGH);
  delay(3000);
  //digitalWrite(STEP, LOW);
  digitalWrite(LED_BUILTIN, LOW);

  //STM32F303K8 and Arduino Nano clones are returning 13 as the LED port
  //Serial.println("LED_BUILTIN = " + (String)LED_BUILTIN);
  Serial.println("INTERVAL = " + (String) (NSTEPS*2*DRIVE_PULSEWIDTH + CAMERA_DELAY + CAMERA_PULSE + CAPTURE_TIME) + " ms");

  //2025-11-19 JPC start with led on for first cycle
  digitalWrite(LED_BUILTIN, HIGH);
  //ledToggle = false; initialised above
}


void loop() {  
  //main advance
  for(int i = 1; i <= NSTEPS; i++){
    digitalWrite(STEP_DRIVE, HIGH);
      delay(DRIVE_PULSEWIDTH);
    digitalWrite(STEP_DRIVE, LOW);
    delay(DRIVE_PULSEWIDTH);
  }                       
  delay(CAMERA_DELAY);
  //capture image of the film frame
  digitalWrite(CAPTURE_IMAGE, LOW);
  delay(CAMERA_PULSE);
  digitalWrite(CAPTURE_IMAGE, HIGH);
  delay(CAPTURE_TIME);
  frameCount++;

  if(frameCount%(100) == 0){
    Serial.println((String)frameCount + " frames");
//    if(ledToggle) {
//      digitalWrite(LED_BUILTIN, HIGH);
//      ledToggle = false;
//    } else {
//      digitalWrite(LED_BUILTIN, LOW);
//      ledToggle = true;
//    }
  }
}

Camera_NEMA_Test01_self-advance.ino

Arduino
Basic simple stepper motor code used for testing and configuration of stepper motors for movie camera use. We especially need to test for high speed capability.
/*
  Camera NEMA Test01 John Calder JPC started 2026-01-02 
  Updated 2026-03-15
  Advance in less than 0.02 sec with pause between
  This code tests the stepper motor on its own,
  The plan for later versions is to trigger the advance from the shutter.

  Mod-8mm Project code applies the Open Source "MIT License"
  Copyright (C) 2026 John Calder some rights reserved
  https://choosealicense.com/licenses/mit/
*/

const byte STEP = 9; //control pin D9
const byte DIR = 10; //control pin D10
const int SPEED = 18;
const int INTERVAL = 1000/SPEED;
//2026-03-15 JPC small change from 280 to 300
const int STEP_PULSE = 300;
const int STEP_PULSE_A = STEP_PULSE * 3;
const int STEP_PULSE_B = STEP_PULSE_A/2;

//2025-11-24 Step Angle is 1.8 for NEMA - make it a constant
bool ledToggle = false;
// start with an estimate of time to advance the film
// 20 milliseconds for 18 fps (frames per second)
int advanceTime = 20;
int holdTime = INTERVAL - advanceTime;
int moveState = 0;
long count = 0;

// the setup function runs once when you press reset or power the board
void setup() {
  // initialize digital pin LED_BUILTIN as an output.
  double stepSpeedAssess = 0;
  pinMode(STEP, OUTPUT); //Step control
  pinMode(DIR, OUTPUT); //Direction 
  pinMode(LED_BUILTIN, OUTPUT);
  Serial.begin(9600);
  //initialise ports, supposed to be default but we are seeing a possible need
  digitalWrite(STEP, LOW);
  digitalWrite(DIR, HIGH);

  //2025-11-24 JPC give a distinctive startup display
  delay(3000);
 
  digitalWrite(LED_BUILTIN, HIGH);
  delay(3000);
  //digitalWrite(STEP, LOW);
  digitalWrite(LED_BUILTIN, LOW);

  //Arduino Nano clones ATMEGA328 processor with CH340 USB driver are returning 13 as the LED port
  Serial.println("LED_BUILTIN = " + (String)LED_BUILTIN);
  Serial.println("STEP_PULSE = " + (String) STEP_PULSE + " microseconds");
  Serial.println("INTERVAL = " + (String) INTERVAL + " ms");

  //2025-11-19 JPC start with led on for first cycle
  digitalWrite(LED_BUILTIN, HIGH);
  //ledToggle = false; initialised above
}


void loop() {
  //2026-01-02 JPC
  // pause between film advance
  // later the shutter sends a pulse that governs this
  if(moveState == 0) {
    delay(holdTime);
    moveState = 1;
    return;
  }

  //advance
  //time advance in milliseconds
  unsigned long snapshotTime = millis();
  
  //gradual acceleration into advance
  digitalWrite(STEP, HIGH);
  delayMicroseconds(STEP_PULSE_A);
  digitalWrite(STEP, LOW);
  delayMicroseconds(STEP_PULSE_A);
  digitalWrite(STEP, HIGH);
  delayMicroseconds(STEP_PULSE_B);
  digitalWrite(STEP, LOW);
  delayMicroseconds(STEP_PULSE_B);
  
  //main advance
  for(int i = 3; i <= 25; i++){
    digitalWrite(STEP, HIGH);
    delayMicroseconds(STEP_PULSE);
    digitalWrite(STEP, LOW);
    delayMicroseconds(STEP_PULSE);
  }

  //gradual deceleration out of advance
  digitalWrite(STEP, HIGH);
  delayMicroseconds(STEP_PULSE_B);
  digitalWrite(STEP, LOW);
  delayMicroseconds(STEP_PULSE_B);
  digitalWrite(STEP, HIGH);
  delayMicroseconds(STEP_PULSE_A);
  digitalWrite(STEP, LOW);
  delayMicroseconds(STEP_PULSE_A);

  moveState = 0;
  count += 1;
  //timing
  advanceTime = (int)(millis() - snapshotTime);
   
   if(count%(SPEED * 10) == 0){
    Serial.println((String)count + " frames; advanceTime = " + (String)advanceTime + " ms");
    if(ledToggle) {
      digitalWrite(LED_BUILTIN, HIGH);
      ledToggle = false;
    } else {
      digitalWrite(LED_BUILTIN, LOW);
      ledToggle = true;
    }
   }
}

Mod-8-film Repository on Github

Most of the information from here, presented in the format of a Github repository.

Credits

John Calder
2 projects • 1 follower
Enthusiast since 2000 for web applications as the best way to develop software and I have enjoyed watching this idea catch on with others.

Comments