diff --git a/CHANGELOG.md b/CHANGELOG.md
new file mode 100644
index 0000000..6269b43
--- /dev/null
+++ b/CHANGELOG.md
@@ -0,0 +1,13 @@
+
+# Version 0.2.0 29/11/2024
+
+- Changed pulley diameters to pulley teeth
+- Added `float epsilon` for calibrating substeps.
+- Added circuit diagram.
+- Updated documentation and README.md
+- updated `spin_degs()` if statements and compensation.
+- Added unit tests `calibrate_pulley_teeth()` and `test_spin_degs_multi()`.
+
+# Version 0.1.0 22/11/2024
+
+Initial release. Development till 22/11/2024.
diff --git a/README.md b/README.md
index 2841940..dcc7fa8 100644
--- a/README.md
+++ b/README.md
@@ -7,27 +7,20 @@ Open-source 3D clinostat. A clinostat is a small scale [microgravity simulator](
This is based on the European Space Agency's work, specifically on [Jack Van Loon's clinorotation work](https://doi.org/10.3389/fpls.2019.01577).
-It is very difficult to access a 3D clinostat, there are some companies that sell it but can be prohibitively expensive for gravity research. OpenClino can be built for £100 using off the shelf parts. OpenClino can run in continuous clinorotation or as a Random Positioning Machine (RPM).
-
-This is a side project for me and is very much work in progress.
-
-OpenClino is designed to be simple, accessible, affordable, and **reliable**. It is designed to make use of 3D printing's strengths and requires *no machining* and minimum tools. To build OpenClino all non-printed parts are available off the shelf, mainly 3d printer stepper motors, belts, controllers, and skateboard bearings. All these parts are rated for thousands of hours of operation, and I have fully tested OpenClino to run for a minimum of 100 hrs without fault.
+OpenClino is designed to be simple, accessible, affordable, and **reliable**. It is designed to make use of 3D printing's strengths and requires *no machining* and minimum tools. To build OpenClino all non-printed parts are available off the shelf, mainly 3d printer stepper motors, belts, controllers, and skateboard bearings. All these parts are rated for thousands of hours of operation, and I have fully tested OpenClino to run for a minimum of 100 hours.
## Documentation
I have provided:
-- Docs in [`docs/1_.md/`](docs/1_documentation.md)
+
+- Docs in [`docs/1_documentation.md/`](docs/1_documentation.md) including circuit diagram.
- Code in [`src/`](src/openclino.ino)
- 3D print files as .3MF in [`3d_files/`](3d_files/)
- Bill of materials in [`docs/2_BOM.md/`](docs/2_BOM.md/)
- Build guide in [`docs/3_build_guide.md/`](docs/3_build_guide.md/)
-
-I will provide (TODO):
-
-- code documentation.
-
+- Code documentation in [`docs/4_code_documentation.md/`](docs/4_code_documentation.md/)
## Quick Usage
@@ -41,7 +34,6 @@ void loop() {
Or to run as a random positioning machine, this will run a random walk routine as specified in ESA's work:
-
```cpp
void loop() {
RPM();
@@ -50,12 +42,6 @@ void loop() {
Don't forget to set the output pins for you motor controllers!
-## Collaboration
-
-Please contact me on LinkedIn or raise an issue.
-
-I would be happy to collaborate on this.
-
## Contributors
[Dan Dragomirescu @dragomda](https://github.com/dragomda)
diff --git a/docs/1_documentation.md b/docs/1_documentation.md
index 6540b59..cafe563 100644
--- a/docs/1_documentation.md
+++ b/docs/1_documentation.md
@@ -1,5 +1,9 @@
# OpenClino
+It is very difficult to access a 3D clinostat, there are some companies that sell it but can be prohibitively expensive for gravity research. OpenClino can be built for £100 using off the shelf parts. OpenClino can run in continuous clinorotation or as a Random Positioning Machine (RPM).
+
+This is a side project for me and is very much work in progress.
+
## Contents list
- [`Bill of Materials`](2_BOM.md)
@@ -9,45 +13,19 @@
- [Notation](#notation)
- [Code](#code)
-## Design requirements
-
-Clinostats are usually used in biology for plants, cell cultures, and small animals such as fish.
-
-1. OpenClino shall be robust to failures to not harm the animals or samples.
-2. Openclino must be simple, accessible, and affordable, requiring no machining.
-3. OpenClino should operate in hot and humid environments such as incubators.
-4. OpenClino should operate as a clinostat or RPM.
-
-At the moment it is a work in progress.
-I'm not an electrical engineer so this is 12v. Be careful with your electronics.
-At the moment it functions as a clino but not a true RPM. The motors can't start a new direction or speed independently.
-You can buy waterproof nema17s and have the electronics outside of the incubator if needed.
-
-TODO material requirements
-
-## Notation
-TODO make a figure defining part names
-
-Definitions:
-motor_pulley
-x_pulley
-y_pulley
-m2y pulley
-y_turn
-y_turn_pulley
-y_guide
-motor_mount
-motor_enclosure
-which is x and y
+## Convention & Definitions
+- X body is the external larger square.
+- Y body is the internal smaller body.
+- Motor pulleys attach to the motor.
+- X/Y pulleys attach to the body.
+- The "`m2y`" pulley is the one with 2 belts attached.
+- The "`y_turn`" is the bearings bending the long y belt.
+- The "`y_guide`" is the L shaped bracket holding the y pulley.
## Code
-The code is work in progress, it requires the following:
- - control multiple motors separately.
- - acceleration.
- - docs.
- - Implementing the airbus algorithm for variable G.
+[OpenClino Documentation.](4_code_documentation.md)
### Usage
@@ -61,7 +39,6 @@ void loop() {
Or to run as a random positioning machine, this will run a random walk routine as specified in ESA's work:
-
```cpp
void loop() {
RPM();
@@ -70,3 +47,8 @@ void loop() {
Don't forget to set the output pins for you motor controllers!
+# Circuit design
+
+This is a very simple arduino circuit with a button and 2 stepper motor controllers.
+
+![Clinostat circuit.](images/openclino_circuit_diagram.png "Circuit diagram. Made with Cirkit Designer.")
diff --git a/docs/4_code_documentation.md b/docs/4_code_documentation.md
new file mode 100644
index 0000000..48cc716
--- /dev/null
+++ b/docs/4_code_documentation.md
@@ -0,0 +1,70 @@
+# OpenClino Documentation
+
+This document provides reference information about the functions used to control stepper motors in the Arduino project. Each function is described with its purpose, parameters, and behavior.
+
+## Functions
+
+### `void yClockwise(bool foo)`
+
+Sets the direction of the Y-axis motor.
+
+- **Parameters:**
+ - `foo` (bool): If `true`, sets the direction to clockwise; if `false`, sets it to counterclockwise.
+
+---
+
+### `void spin_continuous(float speedRPMX = 60, float speedRPMY = 60, int finalDelay = 100)`
+
+Spins the X and Y motors continuously at specified speeds until a button is pressed.
+
+- **Parameters:**
+ - `speedRPMX` (float, default = 60): Speed of the X-axis motor in RPM.
+ - `speedRPMY` (float, default = 60): Speed of the Y-axis motor in RPM.
+ - `finalDelay` (int, default = 100): Delay in milliseconds after stopping the motors.
+
+---
+
+### `void spin_degs(float degX, float degY, float speedX = 10, float speedY = 10, int finalDelay = 100)`
+
+Spins the X and Y motors by specified degrees at specified speeds.
+
+- **Parameters:**
+ - `degX` (float): Degrees to rotate the X-axis motor.
+ - `degY` (float): Degrees to rotate the Y-axis motor.
+ - `speedX` (float, default = 10): Speed of the X-axis motor in RPM.
+ - `speedY` (float, default = 10): Speed of the Y-axis motor in RPM.
+ - `finalDelay` (int, default = 100): Delay in milliseconds after stopping the motors.
+
+---
+
+### `void RPM()`
+
+Placeholder function for future implementation.
+
+---
+
+### `void calibrate_pulley_teeth()`
+
+Calibrates the pulley teeth by rotating the motors in specific patterns.
+
+---
+
+### `void test_spin_degs_multi()`
+
+Tests the `spin_degs` function with multiple rotations.
+
+---
+
+### `void setup()`
+
+Initializes the Arduino pins and serial communication, and prints a welcome message.
+
+---
+
+### `void loop()`
+
+Main loop that reads the button state and performs actions based on the button press.
+
+- **Behavior:**
+ - Turns off motors when the button is not pressed.
+ - When the button is pressed, it turns on the motors, lights up the built-in LED, and runs calibration and test functions.
diff --git a/docs/images/openclino_circuit_diagram.png b/docs/images/openclino_circuit_diagram.png
new file mode 100644
index 0000000..046d5cf
Binary files /dev/null and b/docs/images/openclino_circuit_diagram.png differ
diff --git a/src/openclino.ino b/src/openclino.ino
index 9a9980f..0adca1e 100644
--- a/src/openclino.ino
+++ b/src/openclino.ino
@@ -1,7 +1,4 @@
-// Define steps per revolution
-const int motorSteps = 200;
-const int subStep = 16;
-const unsigned long stepsPerRevolution = motorSteps * subStep;
+#include
// pins
const int buttonPin = 2;
@@ -12,19 +9,37 @@ const int enablePinY = 6;
const int stepPinY = 7;
const int dirPinY = 8;
-// pulley diameters
-const float xMotD = 9.5;
-const float xPulleyD = 31.5;
-const float yMotD = 9.5;
-const float yTurnD = 31.5;
-const float yPulleyD = 18.5;
-const double xRatio = xMotD / xPulleyD; // diameters
-const double yRatio = yMotD / yPulleyD;
-const int nStepsXperRot = stepsPerRevolution / xRatio;
-const int nStepsYperRot = stepsPerRevolution / yRatio;
+// Define steps per revolution
+const int motorSteps = 200;
+const int subStep = 16;
+const unsigned long stepsPerRevolution = motorSteps * subStep;
+// eposilon to fix discrepancy between motor substeps
+// set to 1 to undo calibration.
+const double epsilon = 1.5;
+
+// Pulley teeth
+const float xMotTeeth = 16;
+const float xPulleyTeeth = 50;
+const float yMotTeeth = 16;
+const float yTurnTeeth = 50;
+const float yPulleyTeeth = 20;
+
+// Calculate the gear ratios based on pulley teeth
+const double xRatio = xPulleyTeeth / xMotTeeth;
+const double yRatio1 = yTurnTeeth / yMotTeeth;
+const double yRatio2 = yPulleyTeeth / yTurnTeeth;
+const double yRatio = yRatio1 * yRatio2 * epsilon;
+
+// Calculate effective steps per rotation
+const double nStepsPerRotX = stepsPerRevolution * xRatio;
+const double nStepsPerRotY = stepsPerRevolution * yRatio;
int buttonState = 0;
+// Define AccelStepper objects for X and Y axes
+AccelStepper stepperX(1, stepPinX, dirPinX);
+AccelStepper stepperY(1, stepPinY, dirPinY);
+
void wake(bool x, bool y)
{
if (x == true)
@@ -69,194 +84,92 @@ void yClockwise(bool foo)
}
}
-// function to continuous spin x and y given and rpm where if 0 keep still
-// function to spin and
-// void spin_degs():
-// think about smoothing
-
-void spin_continuous(int t = 1, float speedX = 10, float speedY = 10, int finalDelay = 100)
+void spin_continuous(float speedRPMX = 60, float speedRPMY = 60, int finalDelay = 100)
{
- bool xClock;
- bool yClock;
+ // Convert RPM to steps per second for speed settings
+ double speedX = (speedRPMX * nStepsPerRotX) / 60.0;
+ double speedY = (speedRPMY * nStepsPerRotY) / 60.0;
- if (speedX >= 0)
- {
- xClock = true;
- }
- else
- {
- xClock = false;
- }
- if (speedY >= 0)
- {
- yClock = true;
- }
- else
- {
- yClock = false;
- }
+ // Set speeds and accelerations for each motor
+ stepperX.setMaxSpeed(speedX);
+ stepperY.setMaxSpeed(speedY);
+ stepperX.setAcceleration(speedX * 2); // Adjust acceleration factor as needed
+ stepperY.setAcceleration(speedY * 2);
- long IntervalX = (6e7 / speedX) / nStepsXperRot;
- long IntervalY = (6e7 / speedY) / nStepsYperRot;
- unsigned long stepsY = 0;
- unsigned long stepsX = 0;
- unsigned long previousTimeX = micros();
- unsigned long previousTimeY = micros();
+ delay(1000); // Final delay to settle motors
- while (true)
+ // set motors to run until button is pressed
+ while (buttonState == LOW)
{
- unsigned long currentTimeX = micros();
- unsigned long currentTimeY = micros();
- bool xGo = false; // whether to step x
- bool yGo = false; // whether to step y
- bool skipCompensation = false;
-
- digitalWrite(stepPinX, HIGH);
- digitalWrite(stepPinY, HIGH);
-
- if (currentTimeX - previousTimeX > IntervalX)
- {
- xGo = true;
- }
- if (currentTimeY - previousTimeY > IntervalY)
- {
- yGo = true;
- }
- if (yGo == true && xGo == true && yClock == false && xClock != yClock)
- {
- skipCompensation = true;
- }
-
- if (xGo)
- {
- xClockwise(xClock);
- digitalWrite(stepPinX, LOW);
- digitalWrite(stepPinX, HIGH);
- previousTimeX = currentTimeX;
-
- if (skipCompensation == false)
- {
- yClockwise(!xClock); // spin y motor with x
-
- digitalWrite(stepPinY, LOW); // compensator
- digitalWrite(stepPinY, HIGH);
- }
- stepsX++;
- }
-
- if (yGo)
- {
- yClockwise(yClock);
- digitalWrite(stepPinY, LOW);
- digitalWrite(stepPinY, HIGH);
- previousTimeY = currentTimeY;
- stepsY++;
- }
- // if (stepsX >= nStepsX && stepsY >= nStepsY) {keepGoing = false;} // check if finished
+ stepperX.runSpeed();
+ stepperY.runSpeed();
+ stepperX.run();
}
- delay(finalDelay);
+
+ // Optionally, stop the motors after the button is pressed
+ stepperX.stop();
+ stepperY.stop();
}
-void spin_degs(float degX, float degY, float speedX = 60, float speedY = 60, int finalDelay = 100)
+void spin_degs(float degX, float degY, float speedX = 10, float speedY = 10, int finalDelay = 100)
{
- // wake(true, true);
-
- // define directions because y motor has to constantly change direction to compensate
- bool xClock;
- bool yClock;
-
- if (degX >= 0)
- {
- xClock = true;
- }
- else
- {
- xClock = false;
- }
- if (degY >= 0)
- {
- yClock = true;
- }
- else
- {
- yClock = false;
- }
+ // This function is perfect without accel
+ // Initialize motor direction based on target degrees
+ bool xClock = (degX >= 0);
+ bool yClock = (degY >= 0);
- int nStepsX = nStepsXperRot * abs(degX / 360.); // define fraction of full turn
- int nStepsY = nStepsYperRot * abs(degY / 360.);
+ // Calculate steps needed for each axis based on degree input
+ int nStepsX = nStepsPerRotX * abs(degX / 360.0);
+ int nStepsY = nStepsPerRotY * abs(static_cast(degY) / 360.0);
- bool keepGoing = true;
+ // Determine intervals for each motor based on speed
+ long intervalX = (6e7 / speedX) / nStepsPerRotX;
+ long intervalY = (6e7 / speedY) / nStepsPerRotY;
- long IntervalX = (6e7 / speedX) / nStepsXperRot;
- long IntervalY = (6e7 / speedY) / nStepsYperRot;
- unsigned long stepsY = 0;
+ // Initialize step counters and timing variables
unsigned long stepsX = 0;
+ unsigned long stepsY = 0;
unsigned long previousTimeX = micros();
unsigned long previousTimeY = micros();
- while (keepGoing)
+ while (stepsX < nStepsX || stepsY < nStepsY)
{
- unsigned long currentTimeX = micros();
- unsigned long currentTimeY = micros();
- bool xGo = false; // whether to step x
- bool yGo = false; // whether to step y
+ unsigned long currentTime = micros();
bool skipCompensation = false;
- digitalWrite(stepPinX, HIGH);
- digitalWrite(stepPinY, HIGH);
-
- if (currentTimeX - previousTimeX > IntervalX && stepsX <= nStepsX)
- {
- xGo = true;
- }
- if (currentTimeY - previousTimeY > IntervalY && stepsY <= nStepsY)
- {
- yGo = true;
- }
- if (yGo == true && xGo == true && yClock == false && xClock != yClock)
- {
- skipCompensation = true;
- }
-
- if (xGo)
+ // Check if it's time to move motor X
+ if ((currentTime - previousTimeX > intervalX) && (stepsX < nStepsX))
{
+ // Move X motor
xClockwise(xClock);
digitalWrite(stepPinX, LOW);
digitalWrite(stepPinX, HIGH);
- previousTimeX = currentTimeX;
+ previousTimeX = currentTime;
+ stepsX++;
- if (skipCompensation == false)
+ // Compensation logic: Skip compensation when both axes move, Y motor moves in opposite direction to X
+ if (!(stepsY < nStepsY && (currentTime - previousTimeY > intervalY) && yClock != xClock))
{
- yClockwise(!xClock); // spin y motor with x
-
- digitalWrite(stepPinY, LOW); // compensator
+ // Move Y motor to compensate
+ yClockwise(!xClock);
+ digitalWrite(stepPinY, LOW);
digitalWrite(stepPinY, HIGH);
}
- stepsX++;
}
- if (yGo)
+ // Check if it's time to move motor Y
+ if ((currentTime - previousTimeY > intervalY) && (stepsY < nStepsY))
{
+ // Move Y motor
yClockwise(yClock);
digitalWrite(stepPinY, LOW);
digitalWrite(stepPinY, HIGH);
- previousTimeY = currentTimeY;
+ previousTimeY = currentTime;
stepsY++;
}
- if (stepsX >= nStepsX && stepsY >= nStepsY)
- {
- keepGoing = false;
- } // check if finished
}
- delay(finalDelay);
-}
-void show_off()
-{
- spin_degs(180, 180);
- spin_degs(-180, -180);
- spin_degs(-180, 180);
- spin_degs(180, -180);
+ delay(finalDelay); // Final delay to settle motors
}
void RPM()
@@ -264,9 +177,28 @@ void RPM()
spin_degs(random(-360, 360), random(-360, 360));
}
+// Unit tests
+
+void calibrate_pulley_teeth()
+{
+ spin_degs(90.0, 0);
+ spin_degs(-90.0, 0);
+ spin_degs(0, 90.0);
+ spin_degs(0, -90.0);
+}
+
+void test_spin_degs_multi()
+{
+ spin_degs(90, -90);
+ spin_degs(-90, 90);
+ spin_degs(180, -180);
+ spin_degs(-180, 180);
+ spin_degs(360, 360);
+ spin_degs(-360, -360);
+}
+
void setup()
{
- // put your setup code here, to run once:
pinMode(buttonPin, INPUT);
pinMode(LED_BUILTIN, OUTPUT);
pinMode(stepPinX, OUTPUT);
@@ -276,8 +208,7 @@ void setup()
pinMode(enablePinX, OUTPUT);
pinMode(enablePinY, OUTPUT);
Serial.begin(9600);
- Serial.print("--\nHello\n--");
-
+ Serial.print("--\nHello from OpenClino.\n--");
wake(false, false);
}
@@ -285,17 +216,18 @@ void loop()
{
buttonState = digitalRead(buttonPin);
+ // Turn off motors when button is not pressed.
wake(false, false);
if (buttonState == HIGH)
{
digitalWrite(LED_BUILTIN, HIGH);
wake(true, true);
- spin_continuous(1, 60, 60);
- // show_off();
- // RPM();
+ calibrate_pulley_teeth();
+ test_spin_degs_multi();
+ // uncomment to run continuous spin
+ // spin_continuous();
+ // uncomment to run RPM
+ // RPM();
}
-
- digitalWrite(LED_BUILTIN, LOW);
- delay(10); // Wait a second
}
diff --git a/src/raspberry_pi/pulley_calc.py b/src/raspberry_pi/pulley_calc.py
deleted file mode 100644
index efb6f22..0000000
--- a/src/raspberry_pi/pulley_calc.py
+++ /dev/null
@@ -1,14 +0,0 @@
-import numpy as np
-
-def belt_length(p1, p2, centre_distance):
- return np.pi * (p1+p2) * 0.5 + (2*centre_distance) + ((p2-p1)**2/(4*centre_distance))
-
-
-def main():
- b = belt_length(10, 30, 70)
- print(b)
- b = belt_length(10, 30, 70)
- print(b)
-
-if __name__ == "__main__":
- main()
\ No newline at end of file