Reliably and repeatably stretch samples to a specific strain value while recording the tension force being applied as well as record synchronized measurements from an external instrument.
Solution:Build a linear actuator with a built in load cell and output ports that can be used to trigger external sensors. The system can measure strains between 0-400mm and can move in increments as small as 1/400mm. The load cell used in this example can measure from 0-5N but can be swapped for other load cells easily.
1. 3D print all the parts.
2. Insert Heat Set inserts into all 4.25mm holes on printed parts
3. Attach stepper motor and limit switch to one of the end pieces.
4. Attach the bushings and holders for the linear rods to both end pieces.
5. Slide one of the end pieces onto the end of the aluminum extrusion and secure with T slot nuts.
6. Attach the threaded rod and coupler to the stepper motor.
7. Insert linear rods into the block with the stepper motor.
8. Slide a linear bearing onto each linear rod.
9. Attach the brass nut to the 3D printed motion carriage.
10. Slide the motion carriage into the threaded rod and secure to the linear bearings.
11. Slide the assembly onto the opposite end of the aluminum extrusion and slide it until the linear rods and threaded rod are securely in the opposite piece.
12. Tighten all screws.
13. Solder all components to the PCB.
14. Attach the PCB to the Arduino and screw it into the electronics mount.
15. Slide the electronics mount onto the aluminum extrusion.
16. Plug in Stepper motor and limit switch wires to PCB.
17. Mount your modular mounting plate of choice to the end piece and motion carriage and wire up your load cell if applicable. **if you are using at ATO load cell and it is not giving you tension values swap the green and white wires.**
The PCB has been updated since the one used in the project and the new version has several ways to interface with other devices.
The ports on the PCB are as follows (from left to right in the orientation shown above):
- LV LCR (a 5v external trigger signal)
- HV LCR (an external trigger that uses a relay to output the supply voltage)
- Limit Switch
- A supply voltage output header
- A load cell transmitter port
- A stepper motor port
- An E-Stop Port (Not Pictured bellow) -- part of Rev 3
#include <LiquidCrystal.h>
#include <TMCStepper.h>
#include <SPI.h>
#include <SDADA.h>
#include <movingAvg.h>
// SD /////////////////////////////////////////
const int chipSelect = 36;
const int Mosi = 35;
const int Miso = 34;
const int Sck = 33;
File dataFile;
// LCD /////////////////////////////////////////
#define RS 8
#define RW 3
#define EN 9
LiquidCrystal lcd(RS, RW, EN, 4, 5, 6, 7);
// Encoder /////////////////////////////////////
#define CLK 40
#define DT 39
#define SW 38
// LCR /////////////////////////////////////
#define LCRTrigger 31
//Input & Button Logic /////////////////////////////////////////
const int numOfInputs = 3;
const int inputPins[numOfInputs] = { DT, CLK, SW };
int inputState[numOfInputs];
int lastInputState[numOfInputs] = { LOW, LOW, LOW };
bool inputFlags[numOfInputs] = { LOW, LOW, LOW };
long lastDebounceTime[numOfInputs] = { 0, 0, 0 };
long debounceDelay = 1;
//Global Variables /////////////////////////////////////////
bool motorenablestate = false;
bool ishomed = false;
int testiterations = 0;
double sensorsize = 0;
double actualsensorlength = 0;
double testdistance = 0;
bool booted = false;
double currentposition = 0;
bool infunction = false;
long counter = 0;
long cyclecount = 0;
bool stoponfail = false;
bool paused = false;
bool intest = false;
int upperpositionlimit = 400; // need to measure and update value
double MMconstant = 400; // steps per mm
double MMPerSeconds = 7.8125;// seconds per mm: 0.128 // step delay = (((1/MMPerSeconds)/400)*1000000)/2
int step_delay = int((((1 / MMPerSeconds) / 400) * 1000000) / 2);
bool hasfailed = false;
double stepsize = 1;
//Emergency Stop /////////////////////////////////////////
bool Estop = false;
bool enableEstop = true; // change this if you have an estop connected
#define EstopPin 45
//LCD Menu Logic /////////////////////////////////////////
const int numOfScreens = 12; // to add screens increment this number. Also add the screen info to the screens string and update the number lables in the parameter change function
int currentScreen = 0;
String screens[numOfScreens][2] = { { "Home Machine", "Activate?" }, { "Move Machine", "Position" }, { "Sensor Position", "mm" } , { "Sensor Length", "mm" }, { "Number of Cycles", "#" }, { "Test Distance", "mm" }, { "Travel Speed", "mm/s" }, { "Stop on Fail?", "Y/N" }, { "Start Test", "Start?" }, { "Failure Test", "Start?" }, { "Toggle Motor", "On/Off" }, { "Step Size", "mm" }};
double parameters[numOfScreens];
// LoadCell /////////////////////////////////////////
int loadcellpin = A3;
int loadcellvalue = 0;
movingAvg loadcellave(40);
movingAvg loadcellave2(10);
double avevalue = 0;
double avevalue2 = 0;
// Limit /////////////////////////////////////////
int Limitpin = 42;
bool limitstate = false;
// Stepper Motor Parameters /////////////////////////////////////////
#define MAX_SPEED 40 // In timer value
#define MIN_SPEED 1000
#define STALL_VALUE 15 // [-64..63]
#define EN_PIN 27 // Enable
#define DIR_PIN 28 // Direction
#define STEP_PIN 29 // Step
#define CS_PIN 26 // Chip select
#define SW_MOSI 24 // Software Master Out Slave In (MOSI)
#define SW_MISO 23 // Software Master In Slave Out (MISO)
#define SW_SCK 25 // Software Slave Clock (SCK)
bool shaft = false;
#define R_SENSE 0.11f // Match to your driver
TMC2130Stepper driver(CS_PIN, R_SENSE, SW_MOSI, SW_MISO, SW_SCK); // Software SPI
using namespace TMC2130_n;
// Using direct register manipulation can reach faster stepping times
#define STEP_PORT PORTF // Match with STEP_PIN
#define STEP_BIT_POS 0 // Match with STEP_PIN
ISR(TIMER1_COMPA_vect) {
//STEP_PORT ^= 1 << STEP_BIT_POS;
digitalWrite(STEP_PIN, !digitalRead(STEP_PIN));
}
void setup() {
Serial.begin(115200);
for (int i = 0; i < numOfInputs; i++) {
pinMode(inputPins[i], INPUT);
digitalWrite(inputPins[i], HIGH); // pull-up 20k
}
lcd.begin(16, 2);
// Emergency Stop
if (enableEstop) {
pinMode(EstopPin, INPUT_PULLUP);
}
// LCR
pinMode(LCRTrigger, OUTPUT);
// limit switch
pinMode(Limitpin, INPUT_PULLUP);
//SD
Serial.print("Initializing SD card...");
// make sure that the default chip select pin is set to
// output, even if you don't use it:
pinMode(SS, OUTPUT);
// see if the card is present and can be initialized:
if (!SD.begin(chipSelect, Mosi, Miso, Sck)) { // chipSel, mosi, miso, sck
Serial.println("Card failed, or not present");
lcd.clear();
lcd.print("Insert SD");
lcd.setCursor(0, 1);
lcd.print("then reboot");
// don't do anything more:
delay(2000);
while (1);
}
Serial.println("card initialized.");
//Stepper
parameters[11] = 1;
pinMode(EN_PIN, OUTPUT);
pinMode(STEP_PIN, OUTPUT);
pinMode(DIR_PIN, OUTPUT);
// digitalWrite(EN_PIN, LOW); // Enable driver in hardware
driver.begin(); // SPI: Init CS pins and possible SW SPI pins
// UART: Init SW UART (if selected) with default 115200 baudrate
driver.toff(5); // Enables driver in software
driver.rms_current(400); // Set motor RMS current
driver.microsteps(16); // Set microsteps to 1/16th
driver.blank_time(24);
driver.TCOOLTHRS(0xFFFFF); // 20bit max
driver.THIGH(0);
driver.semin(5);
driver.semax(2);
driver.sedn(0b01);
driver.sgt(STALL_VALUE);
driver.en_pwm_mode(true); // Toggle stealthChop on TMC2130/2160/5130/5160
//driver.en_spreadCycle(false); // Toggle spreadCycle on TMC2208/2209/2224
driver.pwm_autoscale(true); // Needed for stealthChop
// load cell
loadcellave.begin();
loadcellave2.begin();
}
void loop() {
setInputFlags();
resolveInputFlags();
checkcanmove();
infunction = false;
intest = false;
//Serial.println(digitalRead(Limitpin));
}
void setInputFlags() {
if (digitalRead(EstopPin) == HIGH && millis() > 10000 && (enableEstop)) {
Estop = true;
digitalWrite(EN_PIN, HIGH);
Serial.println("Emergency Stop");
lcd.clear();
lcd.print("Emergency Stop");
lcd.setCursor(0, 1);
lcd.print("Restart to Reset");
dataFile.flush();
while (1) {
//do nothing in here
}
}
for (int i = 0; i < numOfInputs; i++) {
int reading = digitalRead(inputPins[i]);
if (reading != lastInputState[i]) {
lastDebounceTime[i] = millis();
}
if ((millis() - lastDebounceTime[i]) > debounceDelay) {
if (reading != inputState[i]) {
Serial.print("reading");
Serial.print(i);
Serial.print(":");
Serial.println(reading);
inputState[i] = reading;
if (inputState[i] == HIGH && i == 2) {
inputFlags[i] = HIGH;
}
}
}
lastInputState[i] = reading;
}
if (lastInputState[0] != inputState[0] && inputState[0] == HIGH) {
if (inputState[0] != inputState[1]) {
inputFlags[1] = HIGH;
} else {
inputFlags[0] = HIGH;
}
}
}
void resolveInputFlags() {
for (int i = 0; i < numOfInputs; i++) {
if (inputFlags[i] == HIGH) {
inputAction(i);
inputFlags[i] = LOW;
printScreen();
}
}
}
void inputAction(int input) {
if (!infunction) {
Serial.print("input: ");
Serial.println(input);
if (input == 0) { // CCW rotation
if (currentScreen == 0) {
currentScreen = numOfScreens - 1;
} else {
currentScreen--;
}
} else if (input == 1) { // CC Roation
if (currentScreen == numOfScreens - 1) {
currentScreen = 0;
} else {
currentScreen++;
}
} else if (input == 2) { // Click
cyclecount = 0;
parameterChange();
}
} else {
if (input == 0) {
counter--;
}
else if (input == 1) {
counter++;
}
else if (input == 2 && cyclecount > 1000 && intest == false) {
Serial.println("Exiting Function");
infunction = !infunction;
lcd.noBlink();
}
else if (input == 2 && cyclecount > 1000 && intest == true) {
Serial.println("pausing");
paused = !paused;
}
}
}
void parameterChange() { // this section is used to trigger functions based on what menu was displayed during a click
delay(100);
Serial.println("Entering Function");
if (millis() > 5000) {
if (currentScreen == 0) {
infunction = true;
lcd.blink();
home();
}
if (currentScreen == 1) { // the number coresponds to the index in the screens string
// Serial.print("infunction");
// Serial.println(infunction);
infunction = true;
lcd.blink();
movetopos();
}
if (currentScreen == 2) {
infunction = true;
lcd.blink();
setsensorsize();
}
if (currentScreen == 3) {
infunction = true;
lcd.blink();
sensorlength();
}
if (currentScreen == 4) {
infunction = true;
lcd.blink();
Testnum();
}
if (currentScreen == 5) {
infunction = true;
lcd.blink();
Testdistance();
}
if (currentScreen == 6) {
infunction = true;
Setspeed();
}
if (currentScreen == 7) {
infunction = true;
testtofail();
}
if (currentScreen == 8) {
infunction = true;
starttest();
}
if (currentScreen == 9) {
infunction = true;
starttesttofailure();
}
if (currentScreen == 10) {
infunction = true;
togglemotor();
}
if (currentScreen == 11) {
infunction = true;
setstepsize();
}
}
}
// bellow are the various functions that are currently built into the testing rig
void home() { // homes machine
Serial.println("homing machine");
if (!motorenablestate) {
togglemotor();
Serial.println("MotorEnabled");
}
driver.shaft(false); // change motor direction
while (!limitstate && !(Estop)) {
checkcanmove();
Serial.println("moving");
digitalWrite(STEP_PIN, HIGH);
delayMicroseconds(step_delay);
digitalWrite(STEP_PIN, LOW);
delayMicroseconds(step_delay);
//Serial.print("Limit Switch state");
//Serial.println(digitalRead(Limitpin));
if (digitalRead(Limitpin) == HIGH) {
limitstate = true;
currentposition = 0;
}
}
limitstate = false;
ishomed = true;
}
void movetopos() { // moves machine to position as rotary encoder is rotated
Serial.println("moving machine");
if (!ishomed) {
home();
}
if (!motorenablestate) {
togglemotor();
}
counter = 0;
delay(1000);
while (infunction) {
cyclecount++;
setInputFlags();
resolveInputFlags();
if (counter * stepsize + currentposition != currentposition) {
if (!(currentposition == 0 && counter * stepsize + currentposition < 0)) {
double delta = counter * stepsize;
Serial.print("Delta :");
Serial.println(delta);
Serial.print("Counter :");
Serial.println(counter);
Serial.print("Current Possition :");
Serial.println(currentposition);
Serial.print("stepsize :");
Serial.println(stepsize);
if (delta > 0) {
shaft = true;
currentposition += stepsize;
} else if (delta < 0) {
shaft = false;
currentposition -= stepsize;
}
driver.shaft(shaft);
for (uint16_t i = 0; i <= (abs(delta) * MMconstant); i++) {
if (limitstate == false && (Estop == false) && currentposition <= upperpositionlimit) {
digitalWrite(STEP_PIN, HIGH);
delayMicroseconds(step_delay);
digitalWrite(STEP_PIN, LOW);
delayMicroseconds(step_delay);
}
else {
home();
return;
}
}
counter = 0;
delta = 0;
printScreen();
}
else {
counter = 0;
}
}
}
}
void setsensorsize() { // sets the start position for the sample
if (!ishomed) {
home();
}
if (!motorenablestate) {
togglemotor();
}
Serial.println("define length");
counter = 0;
delay(1000);
while (infunction) {
cyclecount++;
setInputFlags();
resolveInputFlags();
if (counter * stepsize + currentposition != currentposition) {
if (!(currentposition == 0 && counter * stepsize + currentposition < 0)) {
double delta = counter * stepsize;
Serial.print("Delta :");
Serial.println(delta);
Serial.print("Counter :");
Serial.println(counter);
Serial.print("Current Possition :");
Serial.println(currentposition);
if (delta > 0) {
shaft = true;
currentposition += stepsize;
} else if (delta < 0) {
shaft = false;
currentposition -= stepsize;
}
driver.shaft(shaft);
for (uint16_t i = 0; i <= (abs(delta) * MMconstant); i++) {
checkcanmove();
if (limitstate == false && (Estop == false) && currentposition <= upperpositionlimit) {
digitalWrite(STEP_PIN, HIGH);
delayMicroseconds(step_delay);
digitalWrite(STEP_PIN, LOW);
delayMicroseconds(step_delay);
}
else {
home();
return;
}
}
counter = 0;
delta = 0;
sensorsize = currentposition;
parameters[2] = sensorsize;
printScreen();
} else {
counter = 0;
}
}
}
actualsensorlength = sensorsize;
testdistance = currentposition;
}
void sensorlength() { // sets the length of the sample
Serial.println("enter sensor size");
counter = 0;
delay(1000);
while (infunction) {
cyclecount++;
actualsensorlength += counter * stepsize;
parameters[3] = actualsensorlength;
if (counter != 0) {
printScreen();
}
counter = 0;
setInputFlags();
resolveInputFlags();
}
}
void Testnum() { // sets the nubmer of cycles to do
Serial.println("enter number of tests");
counter = 0;
delay(1000);
while (infunction) {
cyclecount++;
testiterations += counter;
parameters[4] = testiterations;
if (counter != 0) {
printScreen();
}
counter = 0;
setInputFlags();
resolveInputFlags();
}
}
void Testdistance() { // sets the distance that the tester with strech the sample
Serial.println("enter Test distance");
delay(1000);
while (infunction) {
cyclecount++;
testdistance += counter * stepsize;
parameters[5] = testdistance;
if (counter != 0) {
printScreen();
}
counter = 0;
setInputFlags();
resolveInputFlags();
}
}
void Setspeed() { // sets the travel speed
Serial.println("Set speed (mm/s)");
counter = 0;
delay(1000);
int mmPerS = 7;
while (infunction) {
cyclecount++;
if (mmPerS + counter > 0) {
mmPerS += counter;
parameters[6] = mmPerS;
}
if (counter != 0) {
printScreen();
}
counter = 0;
setInputFlags();
resolveInputFlags();
}
MMPerSeconds = mmPerS;
step_delay = int((((1 / MMPerSeconds) / 400) * 1000000) / 2);
}
void testtofail() { // sets the length of the sample
Serial.println("Stop on Fail Y/N");
counter = 0;
delay(1000);
while (infunction) {
cyclecount++;
if (counter != 0) {
stoponfail = !stoponfail;
lcd.clear();
lcd.print(screens[currentScreen][0]);
lcd.setCursor(0, 1);
if (stoponfail) {
lcd.print("Yes");
lcd.print(" ");
}
else {
lcd.print("No");
lcd.print(" ");
}
lcd.print(screens[currentScreen][1]);
lcd.print(" ");
lcd.print(currentposition);
lcd.print(" mm");
}
counter = 0;
setInputFlags();
resolveInputFlags();
}
}
void starttest() { // starts the automated testing cycle
// load cell
loadcellave.reset();
loadcellave2.reset();
intest = true;
hasfailed = false;
if (!ishomed) {
home();
}
if (!motorenablestate) {
togglemotor();
}
gotoposition(sensorsize);
// Opens Datalogger File /////////////////////////////////////////
bool havefile = false;
int i = 0;
String filename;
//Serial.print("GOT here2");
while (!havefile) {
filename = "";
filename.concat((int)actualsensorlength);
filename.concat("_");
filename.concat((int)i);
filename.concat(".csv");
Serial.println(filename);
Serial.println(SD.exists(filename.c_str()));
havefile = !SD.exists(filename.c_str());
i++;
}
Serial.print("Filename is listed here: ");
Serial.println(filename);
// "data_output.csv"
dataFile = SD.open(filename.c_str(), FILE_WRITE);
if (!dataFile) {
Serial.println("error opening file");
lcd.clear();
lcd.print("File Error");
delay(2000);
// see if the card is present and can be initialized:
if (!SD.begin(chipSelect, Mosi, Miso, Sck)) { // chipSel, mosi, miso, sck
Serial.println("Card failed, or not present");
lcd.clear();
lcd.print("Insert SD");
lcd.setCursor(0, 1);
lcd.print("then reboot");
// don't do anything more:
delay(2000);
return;
}
Serial.println("card initialized.");
return;
}
String dataString = "";
dataString += String("New Experiment");
dataString += ",";
dataString += String("Sensor_size(mm)");
dataString += ",";
dataString += String(actualsensorlength);
dataString += ",";
dataString += String("Test distance (how far from 1 length it will strain) (mm)");
dataString += String(testdistance);
dataString += ",";
dataString += String("Sensor Postition");
dataString += String(sensorsize);
dataString += ",";
dataString += String("Number of Cycles");
dataString += String(testiterations);
dataFile.println(dataString);
dataString = "";
dataString += String("Current_Position");
dataString += ",";
dataString += String("Load_Cell_Value");
dataString += ",";
dataString += String("MotorDiagnostic1");
dataString += ",";
dataString += String("MotorDiagnostic2");
dataFile.println(dataString);
dataFile.flush();
//Serial.print("GOT here4");
// Starts actual test /////////////////////////////////////////
Serial.println("testing");
for (int j = 0; j <= testiterations; j++) {
//Serial.print("GOT her e5");
driver.shaft(true);
for (uint16_t z = 0; z <= int(testdistance / stepsize); z++) {
currentposition += stepsize;
for (uint16_t i = 0; i <= int(MMconstant * stepsize); i++) {
checkcanmove();
if (limitstate == false && !(stoponfail == true && hasfailed == true) && (Estop == false) && currentposition <= upperpositionlimit) {
digitalWrite(STEP_PIN, HIGH);
delayMicroseconds(step_delay);
digitalWrite(STEP_PIN, LOW);
delayMicroseconds(step_delay);
}
else {
dataFile.flush();
home();
return;
}
}
getdata(); // get sensor readings
lcd.clear();
lcd.print("Testing STR");
lcd.print(int(((currentposition - sensorsize) / actualsensorlength) * 100 + 100));
lcd.print("%");
lcd.setCursor(0, 1);
lcd.print("n: ");
lcd.print(j);
lcd.print("Pos: ");
lcd.print(currentposition);
lcd.print(" mm");
bool tempbool = false;
while (paused) {
if (!tempbool) {
dataFile.flush();
tempbool = true;
}
setInputFlags();
resolveInputFlags();
lcd.clear();
lcd.print("Test Paused");
}
}
driver.shaft(false); // change motor direction
for (uint16_t z = 0; z <= int(testdistance / stepsize); z++) {
currentposition -= stepsize;
for (uint16_t i = 0; i <= int(MMconstant * stepsize); i++) {
checkcanmove();
if (limitstate == false && !(stoponfail == true && hasfailed == true) && (Estop == false)) {
digitalWrite(STEP_PIN, HIGH);
delayMicroseconds(step_delay);
digitalWrite(STEP_PIN, LOW);
delayMicroseconds(step_delay);
}
else {
dataFile.flush();
home();
return;
}
}
getdata(); // get sensor readings
lcd.clear();
lcd.print("Testing STR");
lcd.print(int(((currentposition - sensorsize) / actualsensorlength) * 100));
lcd.print("%");
lcd.setCursor(0, 1);
lcd.print("n: ");
lcd.print(j);
lcd.print("Pos: ");
lcd.print(currentposition);
lcd.print(" mm");
bool tempbool = false;
while (paused) {
if (!tempbool) {
dataFile.flush();
tempbool = true;
}
setInputFlags();
resolveInputFlags();
lcd.clear();
lcd.print("Test Paused");
}
}
// log begining of new cycle to SD card
String dataString = "";
dataString += String("Current_Position");
dataString += ",";
dataString += String("Load_Cell_Value");
dataString += ",";
dataString += String(j);
dataString += ",";
dataString += String("Placeholder");
dataFile.println(dataString);
dataFile.flush(); // save data to sd
}
// at end of experiment
dataFile.flush(); // save data to sd
}
void starttesttofailure() { // starts the automated test to failure or limit
// load cell
loadcellave.reset();
loadcellave2.reset();
intest = true;
stoponfail = true;
hasfailed = false;
if (!ishomed) {
home();
}
if (!motorenablestate) {
togglemotor();
}
gotoposition(sensorsize);
// Opens Datalogger File /////////////////////////////////////////
bool havefile = false;
int i = 0;
String filename;
//Serial.print("GOT here2");
while (!havefile) {
filename = "";
filename.concat(((int)actualsensorlength));
filename.concat("_");
filename.concat((int)i);
filename.concat(".csv");
Serial.println(filename);
Serial.println(SD.exists(filename.c_str()));
havefile = !SD.exists(filename.c_str());
i++;
}
Serial.print("Filename is listed here: ");
Serial.println(filename);
// "data_output.csv"
dataFile = SD.open(filename.c_str(), FILE_WRITE);
if (!dataFile) {
Serial.println("error opening file");
lcd.clear();
lcd.print("File Error");
delay(2000);
// see if the card is present and can be initialized:
if (!SD.begin(chipSelect, Mosi, Miso, Sck)) { // chipSel, mosi, miso, sck
Serial.println("Card failed, or not present");
lcd.clear();
lcd.print("Insert SD");
lcd.setCursor(0, 1);
lcd.print("then reboot");
// don't do anything more:
delay(2000);
return;
}
Serial.println("card initialized.");
return;
}
String dataString = "";
dataString += String("New Experiment");
dataString += ",";
dataString += String("Sensor_size(mm)");
dataString += ",";
dataString += String(actualsensorlength);
dataString += ",";
dataString += String("Test distance (how far from 1 length it will strain) (mm)");
dataString += String(testdistance);
dataString += ",";
dataString += String("Sensor Postition");
dataString += String(sensorsize);
dataString += ",";
dataString += String("Number of Cycles");
dataString += String(testiterations);
dataFile.println(dataString);
dataString = "";
dataString += String("Current_Position");
dataString += ",";
dataString += String("Load_Cell_Value");
dataString += ",";
dataString += String("MotorDiagnostic1");
dataString += ",";
dataString += String("MotorDiagnostic2");
dataFile.println(dataString);
dataFile.flush();
//Serial.print("GOT here4");
// Starts actual test /////////////////////////////////////////
Serial.println("testing");
//Serial.print("GOT her e5");
driver.shaft(true);
for (uint16_t z = 0; z <= int(testdistance / stepsize); z++) {
currentposition += stepsize;
for (uint16_t i = 0; i <= int(MMconstant * stepsize); i++) {
checkcanmove();
if (limitstate == false && !(stoponfail == true && hasfailed == true) && (Estop == false) && currentposition <= upperpositionlimit) {
digitalWrite(STEP_PIN, HIGH);
delayMicroseconds(step_delay);
digitalWrite(STEP_PIN, LOW);
delayMicroseconds(step_delay);
}
else {
dataFile.flush();
home();
return;
}
}
getdata(); // get sensor readings
lcd.clear();
lcd.print("Testing STR");
lcd.print(int(((currentposition - sensorsize) / actualsensorlength) * 100 + 100));
lcd.print("%");
lcd.setCursor(0, 1);
lcd.print("n: ");
lcd.print(1);
lcd.print("Pos: ");
lcd.print(currentposition);
lcd.print(" mm");
bool tempbool = false;
while (paused) {
if (!tempbool) {
dataFile.flush();
tempbool = true;
}
setInputFlags();
resolveInputFlags();
lcd.clear();
lcd.print("Test Paused");
}
}
// at end of experiment
dataFile.flush(); // save data to sd
}
void checkcanmove() {
// Estop Logic
if (enableEstop) {
if (digitalRead(EstopPin) == HIGH) {
Estop = true;
digitalWrite(EN_PIN, HIGH);
Serial.println("Emergency Stop");
lcd.clear();
lcd.print("Emergency Stop");
lcd.setCursor(0, 1);
lcd.print("Restart to Reset");
dataFile.flush();
while (1) {
//do nothing in here
}
}
}
}
void gotoposition(int position) { // move actuator to an absolute postion
cyclecount++;
setInputFlags();
resolveInputFlags();
int delta = position - currentposition;
Serial.print("Delta :");
Serial.println(delta);
Serial.print("Counter :");
Serial.println(counter);
Serial.print("Current Possition :");
Serial.println(currentposition);
if (delta > 0) {
shaft = true;
currentposition += delta;
} else if (delta < 0) {
shaft = false;
currentposition += delta;
}
driver.shaft(shaft);
for (uint16_t i = 0; i <= (abs(delta) * MMconstant); i++) {
checkcanmove();
if (limitstate == false && (Estop == false) && currentposition <= upperpositionlimit) {
digitalWrite(STEP_PIN, HIGH);
delayMicroseconds(step_delay);
digitalWrite(STEP_PIN, LOW);
delayMicroseconds(step_delay);
}
else {
home();
return;
}
}
}
void togglemotor() { // enables/dissables stepper motor
motorenablestate = !motorenablestate;
if (motorenablestate) {
digitalWrite(EN_PIN, LOW); // Enable driver in hardware
} else {
digitalWrite(EN_PIN, HIGH); //diable motor in hardware
}
}
void setstepsize() { // sets movment increment
Serial.println("Set movement increment (mm)");
counter = 0;
delay(1000);
int tempcount = 0;
while (infunction) {
cyclecount++;
tempcount += counter;
parameters[11] = 1 * pow(10, tempcount);
if (counter != 0) {
printScreen();
}
counter = 0;
setInputFlags();
resolveInputFlags();
}
stepsize = 1 * pow(10, tempcount);
}
void getdata() { // fetches and logs sensor data
String dataString = "";
int placeholder2 = 0;
int placeholder3 = 0;
getLCR(); // Sends trigger to external device
loadcellvalue = analogRead(loadcellpin);
loadcellvalue = map(loadcellvalue, 0, 1023, 0, 5000);
avevalue = loadcellave.reading(loadcellvalue);
avevalue2 = loadcellave2.reading(loadcellvalue);
if (avevalue2 < .1 * avevalue && stoponfail) { // detects sudden drops in load cell values and assumes that sample has failed
hasfailed = true;
Serial.println("Sample Failed");
lcd.clear();
lcd.print("Sample Failed");
delay(5000);
}
DRV_STATUS_t drv_status{0};
drv_status.sr = driver.DRV_STATUS();
double loadcellnewtons = loadcellvalue / 1000;
double temp = currentposition - sensorsize;
Serial.print("Current Position: ");
Serial.println(temp);
Serial.print("Loadcell value: ");
Serial.println(loadcellvalue);
dataString += String(temp);
dataString += ",";
dataString += String(loadcellnewtons);
dataString += ",";
dataString += String(drv_status.sg_result);
dataString += ",";
dataString += String(driver.cs2rms(drv_status.cs_actual));
// attempting to fetch stepper diagnostic data
Serial.print("Stepper Info: ");
Serial.print("0 ");
Serial.print(drv_status.sg_result, DEC);
Serial.print(" ");
Serial.println(driver.cs2rms(drv_status.cs_actual), DEC);
dataFile.println(dataString);
// Take 1 measurement every 500 milliseconds
//delay(500);
}
// sends 10ms pulse to external trigger port to trigger LCR measurement
void getLCR() { // Sends trigger to external device
digitalWrite(LCRTrigger, HIGH);
delay(10);
digitalWrite(LCRTrigger, LOW);
}
void printScreen() { // Prints menu on LCD Display
lcd.clear();
lcd.print(screens[currentScreen][0]);
lcd.setCursor(0, 1);
if (parameters[currentScreen] != 0) {
lcd.print(parameters[currentScreen]);
lcd.print(" ");
}
lcd.print(screens[currentScreen][1]);
lcd.print(" ");
lcd.print(currentposition);
lcd.print(" mm");
}Performing a Test:1. Plug in the machine
2. Home the machine by pressing the knob
3. You can manually move the axis from this menu. Click knob and twist, Click to exit. (it will home if you have not homed the machine)
4. You can manually move the axis to the correct position for you sample from this menu. Click knob and twist, Click to exit. (it will home if you have not homed the machine)
5. You can set the actual size of your sample from this menu. Click knob and twist, Click to exit.
6. You can set the number of test cycles you want to perform. Click knob and twist, Click to exit.
7. You can set the distance to which you want to stretch your sample. Click knob and twist, Click to exit.
8. Click to start test. Clicking again will pause/unpause the test.
9. Click to toggle stepper motor power (for manual moment). Rehoming is required afterwards if you want the system to have accurate positional information. It will not automatically re-home just in case you want to have a manual offset in your test.
10. Travel Speed (allows you to change the movement speed (mm/s))
11. Stop on fail (allows to decide if you want a test to stop if a sample breaks)
12. Step Size (allows you to change the movement interval by powers of 10, (E.g. .01mm, 0.1mm , 1mm, 10mm, 100mm)) This movement interval is also the interval at which data about the sample will be collected.
13. Test Till Failure (a test that keeps stretching till the sample fails or it hits the max distance.)
The testing rig saves a CSV file for every test to the SD card.
An example python notebook for how to plot / analyze / synchronize the data with other data sets (from external sensors) is provided in the code section.
Adapt this code to plot the data that you want to analyze



_wzec989qrF.jpg?auto=compress%2Cformat&w=48&h=48&fit=fill&bg=ffffff)







Comments