Focus stacking and other modifications for the Bresser Infinity Microscope

Work in progress – further details to be added!

Important note regarding powering the relay board used to switch the ring-light power supply! It turns out that the relay board I used and probably many others, draw too much current to be powered from the Arduino 5V or 3.3V pins which allow a maximum current of 50mA. The answer is to power the relay board from the main power supply (12V) via a dd40ajsa or similar step-down converter (1A/5W). If powered from the Arduino there is the risk of damage to the Mega – the fault did not manifest itself until the hot weather came – I guess the heat caused the on-board rectifier to fold-back.

Adding a stepping motor to the fine focus knob of the Bresser Infinity microscope is relatively straightforward. I used the RHS focus control. I removed the small screw that holds the knob onto the focusing mechanism. I measured the curvature of the surface of the knob by creating a 3d-printed gauge. I then printed two parts to couple the motor to the microscope via a flexible coupling. I also printed a carrier and a bracket to enable a small Nema 17 motor to be held at the same height as the centre of the focusing knob. All the parts are shown in the figure below. I chose a pancake motor because I found that using anything more powerful was not only unnecessary but also resisted manual focusing more than I wanted. Also, the motor stalling is the only thing preventing damage to the microscope’s focusing mechanism. Obviously, a less powerful motor will stall more readily than a more powerful one. The fine focus knob on the Bresser moves the stage 200 microns for each revolution. Thus, a 1600 micro-step motor will move the focus by 0.125 microns for each step; more than fine enough for most purposes. In fact, even with a 40X lens you would be very hard put to it to notice a change of focus that fine.

Top: two 3D views of the printed parts used to couple the stepping motor to the focus knob. Lower: a picture of the complete focusing mechanism including the motor carriage and bracket, flexible coupling and the cruciform two-part coupler shown above. The white disk between the focus knob and the coupling is a piece of thin packing foam that helps prevent vibration being transmitted from the stepping motor through to the microscope body. The mount for the stepping motor is from Treonon (https://www.thingiverse.com/thing:990946). The L-bracket can be found in the parts list at the end of this post.

I have used an Arduino Mega to control the stepping motor. The Mega is coupled to the motor via an ‘EasyDriver’. This combination makes it very simple and convenient to control the stepping motor. Essentially, all one needs to do to shift the motor through a step is set a pin high (or low) for the direction of motion, enable the motor via another pin and then send alternate high and low commands to the ‘step’ pin. The more complicated stuff all comes in the program that enables stacking and focusing. I used a white OLED I2C display to provide feedback on the depth of a stack, the number of images and the delay between each frame. A 4N35 optocoupler is used to control the camera shutter. A five button keypad using a resistor network and an analogue input is used to select options. There is a slight drawback to these keypads in that occasionally ‘switch bounce’ or incomplete contact leads to false values being returned to the Uno. However, for reason of its simplicity, I have stuck with the keypad and in any case, these problems are reasonably infrequent. In the event of annoying problem like starting a long stack that one wishes to abort, the reset pin on the Arduino is the shortest route back to the entry level menu or, you can just switch the power off and on again! The enclosure for the project was printed using the SCad program you can find here: https://www.thingiverse.com/thing:2938921 – thanks to Jbebel for this fabulous piece of code! Here is a picture of the ‘gubbins’ within the box.

There is a video of the control box in action here: https://www.facebook.com/peter.mobbs.31/videos/1434133794290415?idorvanity=667490850057874

Yes, it has been wired by a spider on LSD! The green large board (upper LHS) is a prototyping shield sitting on top of an Arduino Mega. The smaller red board piggy-backing on it is an EasyDriver. The small pcb to the upper right is a relay board – these are good – they make adding a relay-controled object to an Arduino very simple. The power sockets on the rear provide a 12V input and a relay controlled output for an auxiliary light like the ring light I use for my Z-stacking. There is an unconnected power socket waiting for when I have built a PSU for the microscope’s base light; for now I will just use the psu that came with the Bresser. The brown perf board carries the 4N35 optocouplers. Only one is really required but a second may in the future be used to drive a flash – that isn’t implemented at the current time – instead the flash is driven by a radio trigger. There are four mini-jacks on the front panel. Two are used for the opto-isolator outputs and two are for future use for analog inputs. The black pcb is the back of the OLED display. Two aviation-style sockets are for the keypad (right) and the stepper-motor output (left). IMPORTANT: see note above regarding powering the relay board.

The program (essentially in the Arduino version of C++) first presents the users with the choice of stacking, focusing or doing other stuff. At the present time, the ‘other stuff’ is simply switching an auxillary light-source on or off or triggering the camera, though I plan to add some code from another stacking device I have built that will calculate the optimal number of frames etc. for the lens and distance involved, and also an option to help when stacking from movies. In the meantime there are calculators available from Xerene Stacker and others that will do this for you. I use my older Olympus EM-1 Mk2 to take pictures. Its silent and vibration-free electronic shutter is controlled by the Mega, all its setting are controlled via a laptop running ‘Olympus Capture’. This is a fantastically convenient setup via which you have control of the ISO, shutter speed and just about everything else. The ‘magnified view’ option on the Olympus camera allows a precise check on the focus. The images are accumulated on my laptop’s hard drive. I have added a cheap and cheerful radio trigger (Godox CT-16) to control a flash gun that is used for transparent objects. The flash is mounted on a bracket to the left of the microscope and its light passes through diffusion material before hitting a mirror inclined at 45o – I used a small teleprompter 50/50 mirror so that the light from the microscope’s field lens remains available. The mount for the flash is shaped such that I can still operate the focus knobs on the LHS of the microscope. I have built a 3rd light source; a ring light made from LED tape (see description following the program code below). This is incredibly bright and enables stacking for objects that are opaque without the use of a flash. Indeed, it is possible to operate at base ISO with shutter speeds of about a 1/50s. Allowing for the time it takes to move the focus and for the camera to send its data to the laptop one can take 30 to 50 frames a minute enabling deep stacks to built very quickly.

The ‘focus’ part of the program allows the obvious thing – focusing but it also keeps count of how the focus has been shifted, returning a values in microns. Thus, one can assess how far the stage needs to travel to encompass the depth of an object.

Though I am reasonably pleased with how the system works, I have no doubt the program and hardware can be improved. I have provided the program below but would ask that you don’t blame me for any problems you may encounter and let me know of any improvements you are able to make. I cannot offer to help if you choose to build a similar system and I would only encourage those who understand electronics, Arduino’s and C++ to attempt to do so. This little YouTube video shows how the system works (xxxx).

There are some other ‘tricks’ built into the control box for the stepper motor these include a couple of analog inputs and some extra digital outputs – I hope to use these for some experiments involving the reactions of unicellular organisms to electric and magnetic fields but for now, they are not implemented in the control program. The tube used to attach the camera to the trinocular port is described here (https://petermobbs.com/adding-a-camera-to-the-bresser-infinity-microscope/). It now features a sliding draw to house a linear polariser or other filters. Quick tip – the Bresser and many other microscopes like it do not have a means of switching between the eyepieces and the trinocular port. If you forget to cover the eyepieces room light may ruin your photos.

This image shows the flash bracket and the 50/50 teleprompter mirror used to send light up through the condenser. The mirror allows for the continued use of light from the microscope’s built-in illuminator. I have put a switch in the lead from the power supply for the built-in illuminator so that I can easily blip it off while when the flash fires. The stacking device/camera will automatically fire the flash during the acquisition of each image in a stack – this is done via a cheap radio trigger (Godox CT16).

Your best guide to the construction of the stacking system described above is to read the code below. I am not an expert programmer but I hope most of it is fairly clear. There are good descriptions on the web of how to wire up an EasyDriver and the use of optocouplers to trigger a camera from an Arduino.

Note: from time-to-time this code will be updated – check back for changes.

// New conroller for microscope - button keypad plus oled display and Arduino Mega
// this version with 'move for movie' is as of 2 July 2025
#include <Wire.h>
#include <Adafruit_GFX.h>
#include <Adafruit_SSD1306.h>
#include <Fonts/FreeSerifBold9pt7b.h>
#include <Fonts/FreeMono9pt7b.h>
#define SCREEN_WIDTH 128 // OLED display width, in pixels
#define SCREEN_HEIGHT 64 // OLED display height, in pixels
// Declaration for an SSD1306 display connected to I2C (SDA, SCL pins)
const int analogPin = A8;   // Initialize pin A8 as analog input
const int tolerance = 10;   // Tolerance for reading analog values ​​(adjusted as needed)
const int numButtons = 5;   // The number of buttons used
const int enable_pin = 7;
const int drive_pin = 16;
const int dir_pin = 15;
const int camera_pin = 24;
const int aux_pin = 23;
const int light_pin = 19;
const int aux_light_pin = 14;
int jog_stop_flag;
int key;
int stage_up = 1;
int stage_down = -1;
int Position = 0;
int max_step;
int Direction;
int depth;
int frame_numbers;
int delay_between_frames;
int send_back;
int buttonValues[] = {0, 144, 505, 329, 741};  // Array for storing the corresponding push-on button analog values
Adafruit_SSD1306 display(SCREEN_WIDTH, SCREEN_HEIGHT, &Wire, -1);
void setup() {
  pinMode(enable_pin, OUTPUT);
  pinMode(drive_pin, OUTPUT);
  pinMode(dir_pin, OUTPUT);
  pinMode(camera_pin, OUTPUT);
  pinMode(aux_pin, OUTPUT);
  pinMode(aux_light_pin, OUTPUT);
  pinMode(light_pin, OUTPUT);
  digitalWrite(aux_light_pin, LOW);
  digitalWrite(light_pin, LOW);
  Serial.begin(115200);
  display.begin(SSD1306_SWITCHCAPVCC, 0x3C);
  display.setTextColor(WHITE, BLACK);
  display.clearDisplay();
}

void loop()
{
  digitalWrite(enable_pin, HIGH);
  display.clearDisplay();
  display.setCursor(12, 15);
  display.print("MICROSCOPE CONTROL");
  display.setCursor(40, 25);
  display.print("Focus (L)");
  display.setCursor(40, 35);
  display.print("Stack (R)");
  display.setCursor(40, 45);
  display.print("Other (Up)");
  display.drawRect(0, 0, 128, 64, WHITE);
  display.display();
  which_button(key);
  delay (10);
  switch (key) {
    case (0):
      display.clearDisplay();
      display.setCursor(20, 32);
      display.print("Focus");
      focus();
      display.display();
      break;
    case (2):
      display.clearDisplay();
      display.setCursor(20, 32);
      display.print("Stack");
      stack();
      display.display();
      break;
    case (1):
      display.clearDisplay();
      display.setCursor(20, 32);
      display.print("Other");
      other();
      display.display();
      break;
  }
}
//
//
//
void focus()
{
  int jog_stop_flag = 0;
  Position = 0;
  display.clearDisplay();
  display.drawRect(0, 0, 128, 64, WHITE);
  display.setCursor(20, 32);
  display.print("Ready to focus");
  display.display ();
  delay(500);
  display.clearDisplay();
  display.drawRect(0, 0, 128, 64, WHITE);
  display.setCursor(30, 30);
  display.print("Position: ");
  display.display ();
  //
  //
  //now get key presses
  do {
    which_button(key);
    delay(10);
    // Now get speed and direction for jog and call jog_motor
    switch (key) {
      case 0: //left
        max_step = 1;
        Direction = stage_down;
        jog_motor(Direction);
        Position = Position - 1;
        break;
      case 1: //up
        max_step = 8;
        Direction = stage_up;
        jog_motor(Direction);
        Position = Position + 8;
        break;
      case 2: //right
        max_step = 1;
        Direction = stage_up;
        jog_motor(Direction);
        Position = Position + 1;
        break;
      case 3: //down
        max_step = 8;
        Direction = stage_down;
        jog_motor(Direction);
        Position = Position - 8;
        break;
      case 4: // select
        // stop jog
        jog_stop_flag = 1;
        break;
    }
    display.setCursor(85, 30);
    display.print("     ");
    display.setCursor(85, 30);
    display.print(Position / 8); // 8 microsteps per step
    display.display ();
    digitalWrite(enable_pin, HIGH);
  } while (jog_stop_flag != 1);
  display.clearDisplay();
}
//
//
//
void jog_motor(int a)
{
  int counter = 1;
  digitalWrite(enable_pin, LOW);
  if (Direction == stage_up) {
    digitalWrite(dir_pin, LOW);
  }
  else if (Direction == stage_down) {
    digitalWrite(dir_pin, HIGH);
  }
  do {
    digitalWrite(drive_pin, LOW);
    delayMicroseconds(50);
    digitalWrite(drive_pin, HIGH);
    delayMicroseconds(50);
    counter ++;
  } while (counter <= max_step);
}
//
//
//
int stack()
{
  bool stage_direction_stop_flag = false;
  bool frame_stop_flag = false;
  bool depth_stop_flag = false;
  bool delay_between_frames_stop_flag = false;
  int frame_numbers = 1;
  int depth = 1;
  int delay_between_frames = 1;
  display.clearDisplay();
  display.drawRect(0, 0, 128, 64, WHITE);
  display.setCursor(25, 20);
  display.print("Stage up/down?");
  display.setCursor(25, 30);
  display.print("Up=Up Down=Down");
  display.display ();
  //get direction for stage to move - stage up is focus toward object
  key = 0; // got here by pressing up...
  do {
    which_button(key);
    delay(10);
    switch (key) {
      case 1: //up
        display.setCursor(60, 40 );
        display.print("UP");
        display.display ();
        Direction = stage_up;
        break;
      case 3: //down
        display.setCursor(60, 40);
        display.print("DOWN");
        display.display ();
        Direction = stage_down;
        break;
      case 4: // select
        stage_direction_stop_flag = true;
        delay (100); //not necessary
        break;
    }
    delay(200);
  } while (stage_direction_stop_flag == false);
  // get frames
  display.clearDisplay();
  display.drawRect(0, 0, 128, 64, WHITE);
  display.setCursor(30, 20);
  display.print("# of frames?");
  display.setCursor(30, 30);
  display.print("Frames: ");
  display.display ();
  button_value (75, 30, 1000, 1, 10, 1);
  frame_numbers = send_back;
  delay (200);
  // get depth
  display.clearDisplay();
  display.drawRect(0, 0, 128, 64, WHITE);
  display.setCursor(30, 20);
  display.print("Depth (um)?");
  display.setCursor(30, 30);
  display.print("Depth:");
  display.display ();
  button_value (75, 30, 10000, 1, 10, 1);
  depth = send_back;
  delay(200);
  // get delay between frames - this time will be divided between settling microscope, the exposure,
  // and leaving enough time for charging flash (if in use)
  display.clearDisplay();
  display.drawRect(0, 0, 128, 64, WHITE);
  display.setCursor(30, 20);
  display.print("Delay (mS)?");
  display.setCursor(30, 30);
  display.print("Delay:  ");
  display.display ();
  button_value (75, 30, 25000, 1, 10, 1);
  delay_between_frames = send_back;
  delay(200);
  // call motor_driver with the values above
  motor_driver(Direction, frame_numbers, depth, delay_between_frames);
}

//
//
//drive the motor for stacking
void motor_driver(int Direction, int frame_numbers, int depth, int delay_between_frames)
{
  // The value for steps_per_micron is crucial for accuracy - do real-world check
  float steps_per_micron = 0.125; //1 rotation of ine is 200um so 1600 steps (with 8 microsteps per step) so 0.125 steps/micron!
  long int steps_between_photos;
  long int distance_between_photos;
  long int total_steps;
  int carry_direction;
  bool go_flag = false;
  digitalWrite(dir_pin, LOW);
  digitalWrite(drive_pin, LOW);
  digitalWrite(enable_pin, LOW);
  distance_between_photos = depth / (frame_numbers - 1);
  steps_between_photos = (float)distance_between_photos / steps_per_micron; // check loss of precision!
  display.clearDisplay();
  display.drawRect(0, 0, 128, 64, WHITE);
  display.setCursor(20, 10);
  display.print("Stage:");
  display.setCursor(90, 10);
  if (Direction == stage_up) {
    display.print("Up");
  }
  else if (Direction == stage_down) {
    display.print("Down");
  }
  display.display ();
  display.setCursor(20, 20);
  display.print("Frames:");
  display.setCursor(90, 20);
  display.print(frame_numbers);
  display.display ();
  display.setCursor(20, 30);
  display.print("Depth (um):");
  display.setCursor(90, 30);
  display.print(depth);
  display.display ();
  display.setCursor(20, 40);
  display.print("Delay (mS):");
  display.setCursor(90, 40);
  display.print(delay_between_frames);
  display.display ();
  display.setCursor(15, 50);
  display.print("GO? (S) SKIP (Up)");
  display.display ();
  delay_between_frames = delay_between_frames / 2;
  do
  {
    which_button(key);
    delay(10);
    switch (key) {
      case 1: //up
        goto bailout;
        break;
      case 4: // select
        // go
        go_flag = true;
        break;
    }
  } while (go_flag == false);
  if (Direction == stage_up) {
    digitalWrite(dir_pin, LOW);
  }
  if (Direction == stage_down) {
    digitalWrite(dir_pin, HIGH);
  }
  //set camera_pin high - first photo is where the rail is now
  //tell operator taking first photo
  display.clearDisplay();
  display.drawRect(0, 0, 128, 64, WHITE);
  display.setCursor(20, 20);
  display.print("Photo #:");
  display.setCursor(95, 20);
  display.print("1");
  display.setCursor(20, 30);
  display.print("In stack of:");
  display.setCursor(95, 30);
  display.print(frame_numbers);
  display.display();
  digitalWrite(camera_pin, HIGH);
  digitalWrite(aux_pin, HIGH);
  delay(50);
  digitalWrite(camera_pin, LOW);
  digitalWrite(aux_pin, LOW);
  delay(delay_between_frames); // Delay between photo 1 & 2
  for (int x_count_photos = 1; x_count_photos <= frame_numbers - 1; x_count_photos++) {
    //move rail position by steps_between_photos
    //motor speed is set by delays....going slowly to avoid lost steps.....
    for (int y_count_steps = 1; y_count_steps <= steps_between_photos; y_count_steps++) {
      digitalWrite(drive_pin, LOW);
      delayMicroseconds(500);
      digitalWrite(drive_pin, HIGH);
      delayMicroseconds(500);
    }
    //Tell user which photo is being taken
    display.clearDisplay();
    display.drawRect(0, 0, 128, 64, WHITE);
    display.setCursor(20, 20);
    display.print("Photo #:");
    // wait half delay time for camera to settle take the photo and set flash_pin high with camera_pin long enough to trigger camera
    delay(delay_between_frames); // 1/2 delay time spent settling microscope
    digitalWrite(camera_pin, HIGH);
    //digitalWrite(flash_pin, HIGH); // if in use
    delay(50);
    digitalWrite(camera_pin, LOW);
    // digitalWrite(flash_pin, LOW); // if in use
    display.setCursor(95, 20);
    display.print(x_count_photos + 1);
    display.setCursor(20, 30);
    display.print("In stack of:");
    display.setCursor(95, 30);
    display.print(frame_numbers);
    display.display();
    delay(delay_between_frames); // half delay time here so time for exposure
  }
  //Warn that rewind about to take place..
  display.clearDisplay();
  display.drawRect(0, 0, 128, 64, WHITE);
  display.setCursor(30, 20);
  display.print("Rewinding!");
  display.setCursor(30, 30);
  display.print(">>>>>>>>>>");
  display.display();
  delay(500);
  //  carry_direction = focus_direction;
  if (Direction == stage_up) {
    digitalWrite(dir_pin, HIGH);
  }
  if (Direction == stage_down) {
    digitalWrite(dir_pin, LOW);
  }
  //digitalWrite(dir_pin, focus_direction);
  total_steps = (frame_numbers - 1) * steps_between_photos;
  for (int x_count_rewind = 1; x_count_rewind <= total_steps; x_count_rewind ++) {
    digitalWrite(drive_pin, LOW);
    delayMicroseconds(500);
    digitalWrite(drive_pin, HIGH);
    delayMicroseconds(500);
  }
bailout:
  delay(100); //stops falling through to "other"?
  display.clearDisplay();
  digitalWrite(enable_pin, HIGH); // kill power to motor to prevent heating
}
//
//
//
void other()
{
  bool other_stop_flag = false;
  display.clearDisplay();
  display.drawRect(0, 0, 128, 64, WHITE);
  display.setCursor(15, 10);
  display.print("Take photo (R)");
  display.setCursor(15, 20);
  display.print("Aux on (U)");
  display.setCursor(15, 30);
  display.print("Aux off (D)");
  display.setCursor(15, 40);
  display.print("Move for movie (L)");
  display.setCursor(15, 50);
  display.print("Exit to main (S)");
  display.display();
  do {
    which_button(key);
    delay(10);
    switch (key) {
      case 0: //left
        movie();// no action at present
        break;
      case 1: //up
        digitalWrite(aux_light_pin, HIGH);
        break;
      case 2: //right
        digitalWrite(camera_pin, HIGH);
        delay(50);
        digitalWrite(camera_pin, LOW);
        break;
      case 3: //down
        digitalWrite(aux_light_pin, LOW);
        break;
      case 4: // select
        other_stop_flag = true;
        break;
    }
  } while (other_stop_flag == false);
  delay(200);
  display.clearDisplay();
  display.display();
}
//
//
//
void movie()
{
  bool movie_stop_flag = false;
  int movie_distance;
  int pace = 1;
  display.clearDisplay();
  display.drawRect(0, 0, 128, 64, WHITE);
  display.setCursor(25, 20);
  display.print("Stage up/down?");
  display.setCursor(25, 30);
  display.print("Up=Up Down=Down");
  display.display();
  // get direction for stage to move
  do {
    which_button(key);
    delay(10);
    switch (key) {
      case 0: //left
        // no action at present
        break;
      case 1: //up
        display.setCursor (60, 40);
        display.print ("UP");
        display.display();
        Direction = stage_up;
        break;
      case 2: //right
        // no action at present
        break;
      case 3: //down
        display.setCursor (60, 40);
        display.print ("DOWN");
        display.display();
        Direction = stage_down;
        break;
      case 4: // select
        movie_stop_flag = true;
        delay (100); // not clear why this is essential!!!
        break;
    }
  } while (movie_stop_flag == false);
  display.clearDisplay();
  display.drawRect(0, 0, 128, 64, WHITE);
  display.setCursor(20, 20);
  display.print("Move in microns?");
  display.setCursor(20, 30);
  display.print("Microns: ");
  display.display();
  button_value (75, 30, 10000, 1, 10, 1);
  movie_distance = send_back * 8; // 8 microsteps per micron
  display.clearDisplay();
  display.drawRect(0, 0, 128, 64, WHITE);
  display.setCursor(45, 20);
  display.print("Pace?");
  display.setCursor(45, 30);
  display.print("Delay:");
  display.display();
  delay (500);
  button_value (85, 30, 50, 1, 10, 1);
  pace = send_back * 100;
  display.clearDisplay();
  display.drawRect(0, 0, 128, 64, WHITE);
  display.setCursor(15, 20);
  display.print("Will move in 2.5s");
  display.display();
  delay (2500);
  display.clearDisplay();
  display.drawRect(0, 0, 128, 64, WHITE);
  display.setCursor(40, 20);
  display.print("HIT GO!");
  display.display();
  delay(500);
  display.setCursor(40, 30);
  display.print("Moving!");
  display.display();
  if (Direction == stage_up) {
    digitalWrite(dir_pin, LOW);
  }
  else if (Direction == stage_down) {
    digitalWrite(dir_pin, HIGH);
  }
  digitalWrite(enable_pin, LOW);
  int counter = 1;
  do {
    digitalWrite(drive_pin, LOW);
    delayMicroseconds(pace);
    digitalWrite(drive_pin, HIGH);
    delayMicroseconds(pace);
    counter ++;
  } while (counter <= movie_distance);
  //rewind after delay
  display.clearDisplay();
  display.drawRect(0, 0, 128, 64, WHITE);
  display.setCursor(20, 20);
  display.print("    HIT STOP!  ");
  display.setCursor(20, 30);
  display.print("Return in 2.5s!");
  display.display();
  delay(2500);
  display.setCursor(20, 40);
  display.print("HIT GO!");
  delay(500);
  display.clearDisplay();
  display.drawRect(0, 0, 128, 64, WHITE);
  display.setCursor(35, 25);
  display.print("Returning!");
  display.display();
  if (Direction == stage_up) {
    digitalWrite(dir_pin, HIGH);
  }
  else if (Direction == stage_down) {
    digitalWrite(dir_pin, LOW);
  }
  counter = 1;
  do {
    digitalWrite(drive_pin, LOW);
    delayMicroseconds(pace);
    digitalWrite(drive_pin, HIGH);
    delayMicroseconds(pace);
    counter ++;
  } while (counter <= movie_distance);
  digitalWrite(enable_pin, HIGH);
  display.clearDisplay();
  display.drawRect(0, 0, 128, 64, WHITE);
  display.setCursor(30, 20);
  display.print("Move complete");
  display.display();
  display.setCursor(30, 30);
  display.print("(S) for Menu");
  display.display();
}
//
//
//
int which_button(int a)
{
  int analogValue;
  key = -1;
  analogValue = analogRead(analogPin);
  for (int i = 0; i < numButtons; i++) {
    if (abs(analogValue - buttonValues[i]) <= tolerance) {
      key = i;
    }
  }
  return key;
}

int button_value (int x_pos, int y_pos, int max_val, int min_val, int big_inc, int small_inc)
{
  // int send_back = 0;
  bool stop_flag_button_value = false;
  send_back = 0;
  do {
    which_button (key);
    delay(5);
    switch (key) {
      case 0: //left
        send_back = send_back - small_inc;
        if (send_back <= min_val) {
          send_back = min_val;
        }
        display.setCursor(x_pos, y_pos);
        display.print("       ");
        display.setCursor(x_pos, y_pos);
        display.print(send_back);
        display.display();
        break;
      case 1: //up
        send_back = send_back + big_inc;
        if (send_back >= max_val) {
          send_back = max_val;
        }
        display.setCursor(x_pos, y_pos);
        display.print("       ");
        display.setCursor(x_pos, y_pos);
        display.print(send_back);
        display.display();
        break;
      case 2: //right
        send_back = send_back + small_inc;
        if (send_back >= max_val) {
          send_back = max_val;
        }
        display.setCursor(x_pos, y_pos);
        display.print("       ");
        display.setCursor(x_pos, y_pos);
        display.print(send_back);
        display.display();
        break;
      case 3: //down
        send_back = send_back - big_inc;
        if (send_back <= min_val) {
          send_back = min_val;
        }
        display.setCursor(x_pos, y_pos);
        display.print("       ");
        display.setCursor(x_pos, y_pos);
        display.print(send_back);
        display.display();
        break;
      case 4: //select
        //stop routine
        stop_flag_button_value = true;
        // delay (10);
        break;
    }
  } while (stop_flag_button_value == false);
  return (send_back); // (send_back);
}

The program above will be updated as modifications are made. I intend to add a ‘calculator’ for the best step size. Note: the ‘move’ command that can be used to drive the focus through a given distance may be useful for making movies for focus-stacking – the routine is a working ‘beta’ version. Current version is as of 2nd July 2025.

The ring illuminator I built is simplicity itself. It is just a 3d printed tube 22.5mm high and 53mm in diameter (no particular reason for that dimension). The wall of the tube is 3.5mm thick. The tube is printed using PETG. The reason for the choice of this material is because the heat given off by the powerful LEDs in the tape would soften a lower melting point plastic like PLA. The secret if there is one, is the tape itself. I wrapped white 2835 double row LED tape with 480 LEDs per metre around the internal diameter of the tube with the leads exiting via a small hole about half way up the height of the tube. This stuff consumes about 12W per metre at 12V. Since the internal circumference of the tube is about 165mm the illuminator is equivalent to a 2W light source. This may not sound like a lot of light but believe me, it is so bright that I cannot look at it and I have had to install a shield on the microscope to protect my eyes – it’s fine to look through the eyepieces. The advantage of this tape is that because it is so very bright, you can take pictures with relatively short exposure times at base ISO (200 on my camera). As long as reasonable precautions are taken to prevent vibration, the need for a flash is eliminated. That’s a good thing because it is quite hard to use a flash for epi-illumination unless you have a specialist illuminator that squirts light through the objective. I use the electronic shutter on my camera to avoid shake. The other plus is that with a motor driven focusing/stacking device like that described above, you do not have to wait for a flash to recharge and it is perfectly possible to stack images taken at 1 or 2 second intervals (or less). The down-side of the illuminator is that like any light source, there are reflections to consider and something illuminated from all around can appear a bit ‘flat’. The answer to the later problem if it is one, is to create a semicircular shield to stop the light from one side. Personally I quite like the lighting from the ring illuminator and if you cut a strip of diffusion material and place it in front of the tape, the reflections are not too much of a problem. Another possibility that I am experimenting with is to place a strip of polarizing material in front the tape and a second rotatable polarizing filter in front of the camera. This does not eliminate all the reflections but produces an effect a bit like shields do. Using shields, a diffuser or the polarizing filter will of course, increase exposure times. The ring illuminator sits on a circular 3d printed base with a retaining ring. It is possible to turn the base on the stage of the microscope to achieve the angle you want for the object under investigation. I don’t see any reason why it shouldn’t be possible to make the tube taller so that it could incorporate two strips of tape. However, I haven’t tried this and it might mean that rotating the turret would no longer be possible with more than one objective in place. I use a 12V 1A ‘wall wart’ power supply to power the ring light and it is switchable from within the program above.

The images below show the structure of the ring-light and a few pictures that I have taken using it.

The ring-light, diffuser and base. The tube the LED tape sits in is a bit higher than it needs to be but it does offer the option of turning it over to illuminate taller specimens. The diffuser is just a strip of plastic cut from a file folder.

Head of a Tabanid fly photographed using the ring light and stacking device described above. In this case, I have used a white base for the ring-light. I have forgotten how many photos have been used in this stack but I think it was about 50.

Head of a Box Moth. 55 images – not enough – part of the tongue is still out of focus!

Top and middle images – hind leg of a honeybee. Lower – frontal view honeybee head. 75, 75 and 60 images respectively.

The STL files for the projects described above can be found here:

LED light base – https://www.tinkercad.com/things/8UvE1DaXUEr-ledringlightholder

LED light ring – https://www.tinkercad.com/things/0w63Nlcmj7i-led-ring-light

Flash bracket – https://www.tinkercad.com/things/5RJ0ARBT1zp-finalflashstand

Focus drive spider – https://www.tinkercad.com/things/bd17RFVrYYR-spiderfocusdrivebresser

45o tube for 50:50 mirror – https://www.tinkercad.com/things/5CIDScQ0VtE-fantabulous-bruticus

Stepping motor L bracket – https://www.tinkercad.com/things/40xeHSQI7CA-bracket-for-bresser-microscope-focus-motor

Mount for NEMA17 stepping motor (all credit to the designer Rufinos who has made the design available on Thingiverse) –https://www.thingiverse.com/thing:7060166

2835 dual row LED tape – search AliExpress for  “Double Row Flexible LED Strip 2835 240leds/m 480leds” – light uses the 480 LED per metre strip.

Stepping motor – search AliExpress for “17HS08-1004S Micro Hybrid Stepper Motor 42 Motor Nema17”.

You can search AliExpress for the Easy Driver board and also get a non-genuine Arduino Mega from the same source (super cheap!) but a genuine one is recommended. Prototyping shields for Megas can also be had from AliExpress.

Leave a Reply

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

Translate »