Pictures: https://photonicsdesign.jimdo.com/physics-at-home/ (scroll down to the section "Artificial 22°-halo machine")
Publication: "Artificial Halos", American Journal of Physics, Vol. 83, Issue 9, 751-760, M. Selmke, 2015. (referred to as Selmke2015 below, URL: https://aapt.scitation.org/doi/10.1119/1.4923458, downloadable from my webpage: https://photonicsdesign.jimdo.com/publications/)
Movies / Videos: https://aapt.scitation.org/doi/suppl/10.1119/1.4923458 (e.g. iterative accumulation of images; mergeanimation.mov (1 MB), halomachine.mov (4 MB))
Instructions (see also Selmke2015)
1) Set up your Arduino, https://learn.adafruit.com/adafruit-mot ... ll-headers
2) Follow the motor shield stacking install instructions first (especially the jumper as written below for AFMStop / AFMSbot):
2a) Install bottom shield, https://learn.adafruit.com/adafruit-mot ... ll-headers
2b) Install top shield, https://learn.adafruit.com/adafruit-mot ... ng-shields
3) Then install software: https://learn.adafruit.com/adafruit-mot ... l-software
4) Set the VIN jumper to run the shields on Arduino's DC Barrel Jack, see https://learn.adafruit.com/adafruit-mot ... l-software
5) Connect all 3 stepper motors as shown in Selmke2015, see also below in code definitions of *myStepper#. Arrange and fix the stepper motors in a triangle as shown in Selmke2015. Beware of the correct coil wirings per stepper as described in the shield instructions linked above.
6) Attach 5mm to 10mm Flex Shaft Couplers (https://www.adafruit.com/product/1177) to the motor axes and equip them with 18mm diameter rubber O-rings.
7) Optional: install LEDs with resistors as shown in images of Selmke2015, i.e. between pins 5&6 and GND. Otherwise: Set the variable LEDm=0 in the code.
8) Add power supply (https://www.adafruit.com/product/63) to Arduino's DC Barrel. I found the device also to work with a 9V block battery (https://www.adafruit.com/product/67).
9) Upload Arduino code as provided below.
10) Place a sphere on top.
The code below could be further optimized to avoid the slight skidding of the sphere: instead of collective rotation modes of all motors with each having the same rotational speed, the collective modes should rather involve 1 stepper at full speed and 2 steppers with half the rotational speed (credit goes to Gautier Corgne for this idea)...
Code: Select all
#include <Wire.h>
#include <Adafruit_MotorShield.h> //includes the previously installed Motor Shield library
#include "utility/Adafruit_PWMServoDriver.h"
Adafruit_MotorShield AFMSbot(0x60); // Default address, no jumpers (name bottom shield)
Adafruit_MotorShield AFMStop(0x61); // Rightmost jumper closed (name top shield)
// On the top/upper motor shield, connect two steppers, each with 200 steps
Adafruit_StepperMotor *myStepper2 = AFMStop.getStepper(200, 1); //(name stepper2: Fig.9 Selmke2015: top right / M1 & M2, top shield)
Adafruit_StepperMotor *myStepper3 = AFMStop.getStepper(200, 2); //(name stepper3: Fig.9 Selmke2015: top left / M3 & M4, top shield)
// On the bottom motor shield connect a stepper to port M3/M4 with 200 steps
Adafruit_StepperMotor *myStepper1 = AFMSbot.getStepper(200, 2); //(name stepper1: Fig.9 Selmke2015: bottom left / M3 & M4, bottom shield)
//LEDs: Connect 2 LEDs between Digital I/O Pins 5&6 and Gnd (optional)
int LEDm=1; //set to zero to keep leds dark, to 1 to light them according to angular step magnitude
int rpm=20;
int MaxStep=555;
void setup() {
pinMode(5, OUTPUT); //LED 1 pin
pinMode(6, OUTPUT); //LED 2 pin
while (!Serial);
Serial.begin(9600); // set up Serial library at 9600 bps
Serial.println("Halomator 2015");
//initialize seed for random number generation using open input port voltage
randomSeed(analogRead(0));
AFMSbot.begin(); // Start the bottom shield
AFMStop.begin(); // Start the top shield
myStepper1->setSpeed(rpm);
myStepper2->setSpeed(rpm);
myStepper3->setSpeed(rpm);
}
int i,x,y,z;
void loop() {
//x,y,z are the three axis. Each axis moves 3 steppers in synch (defining a collective rotation "mode" to affect 1 type of movement of the sphere).
x = round(random(-MaxStep,MaxStep));
Movex(x);
delay(100);
y = round(random(-MaxStep,MaxStep));
Movey(y);
delay(100);
z = round(random(-MaxStep,MaxStep));
Movez(z);
delay(100);
}
//define collective rotation modes; negative values reverse all directions in mode
void Movex(int x) {
if (x>= 0) {
analogWrite(6, round(LEDm*255*abs(x)/MaxStep));
for (i=0; i<abs(x); i++) {
myStepper1->onestep(FORWARD, MICROSTEP);
myStepper2->onestep(FORWARD, MICROSTEP);
myStepper3->onestep(BACKWARD, MICROSTEP);
}
digitalWrite(6, LOW);
} else {
analogWrite(5, round(LEDm*255*abs(x)/MaxStep));
for (i=0; i<abs(x); i++) {
myStepper1->onestep(BACKWARD, MICROSTEP); //cf. above: all inverted
myStepper2->onestep(BACKWARD, MICROSTEP);
myStepper3->onestep(FORWARD, MICROSTEP);
}
digitalWrite(5, LOW);
}
}
void Movey(int y) {
if (y>= 0) {
analogWrite(6, round(LEDm*255*abs(y)/MaxStep));
for (i=0; i<abs(y); i++) {
myStepper1->onestep(BACKWARD, MICROSTEP);
myStepper2->onestep(FORWARD, MICROSTEP);
myStepper3->onestep(FORWARD, MICROSTEP);
}
digitalWrite(6, LOW);
} else {
analogWrite(5, round(LEDm*255*abs(y)/MaxStep));
for (i=0; i<abs(y); i++) {
myStepper1->onestep(FORWARD, MICROSTEP);
myStepper2->onestep(BACKWARD, MICROSTEP);
myStepper3->onestep(BACKWARD, MICROSTEP);
}
digitalWrite(5, LOW);
}
}
void Movez(int z) {
if (z>= 0) {
analogWrite(6, round(LEDm*255*abs(z)/MaxStep));
for (i=0; i<abs(z); i++) {
myStepper1->onestep(BACKWARD, MICROSTEP);
myStepper2->onestep(BACKWARD, MICROSTEP);
myStepper3->onestep(BACKWARD, MICROSTEP);
}
digitalWrite(6, LOW);
} else {
analogWrite(5, round(LEDm*255*abs(z)/MaxStep));
for (i=0; i<abs(z); i++) {
myStepper1->onestep(FORWARD, MICROSTEP);
myStepper2->onestep(FORWARD, MICROSTEP);
myStepper3->onestep(FORWARD, MICROSTEP);
}
digitalWrite(5, LOW);
}
}