Hello Arduino forums!
I've been struggling with a problem for the last few days that I just can't work out, maybe someone here has the expertise to help.
I'm attempting to run a linear actuator (Actuonix L12-50-210-6-I datasheet here) very, very slowly. The eventual application is to push a syringe full of fluid, and I'm looking to achieve around 100 microliters per minute as a minimum. Pushing on a 5mL syringe this ends up needing the actuator to run at around 0.6 mm/min, which through trial and error calculations is close to 0.2% of full speed of the actuator. I fully appreciate this is probably impossible to get accurately but this is the absolute lower limit of flow I'll need, and the fluid in the syringe will provide some elasticity, and help smooth out small steps-per-second. So, I decided to run the motor off an Arduino Nano which was bought off Amazon before I realised there were such things as fake Arduinos, so quite possibly a fake/copy/knock-off - but seems to run as expected in every other respect.
The basic idea I had was to loop through a function, with an in-built delay, and step the motor forward by increasing a Servo.writeMicroseconds command by 1 on each loop. Now here's the bit I don't understand - at very low delays, this runs fine, so at 100% - 20% of the actuator's top speed, it's nice and smooth, but at longer delays (so lower speeds) the shaft will randomly stop in the middle of operation, wait an apparently random amount of time, and then "catch up" with where it should be.
I then tried a different approach - setting the delay very short (9ms), but decreasing the step size, so rather than adding +1 to the step per loop, I'm adding +0.1 (or less, depending on the desired speed) into a float variable and then sending the (int) version of this variable to Servo.writeMicroseconds - long story short, this increases the delay by a factor of 10. This approach has been way more successful, allowing me to get down to around 8% of the servo's full speed, but still starts the "freeze and catch-up" behaviour at lower speeds.
The main thing I don't understand is why the servo seems to be having more problems receiving fewer, more spaced out commands than it does when it receives a greater number of commands per second. I have two of this model of servo and both behave the same. I'm powering the actuator from a L7806CV voltage regulator powered from a 24V mains supply, but have also tried powering it from the 5V pin on the Arduino, with identical results. I'll post my most successful code below.
Any ideas on how to better code it will be welcome, as will any ideas on how to do this smarter, whether that's different hardware or dedicated motor controller board or whatever. I know that a DC motor attached to a threaded shaft and fixed nut to move a block along the shaft would probably give me more control via the rpm and thread pitch etc etc, but the goal here was to simplify/minimise the size by using a linear actuator as a single package.
Most successful code so far:
#include <Servo.h>
Servo myServo;
#define PIN_SERVO (6) //motor is connected to pin 6 on the Arduino
long unsigned lastUpdate = 0; //variable used for non-blocking delay function
long unsigned currentTime = millis(); //variable used to store current time for non-blocking delay function
int Started = 0; //flag used to control loop
unsigned long StartTime = 0; //variable to store time motor movement starts
unsigned long EndTime = 0; //variable to store time motor movement ends
float i = 0; //variable used to count loops for SetStrokeMMSpeed function
float usec = 1050.0; //variable used to set position 0 for actuator
const float syringeDiameter = 15.0; //syringe internal diameter for volume and flow rate calculations
void SetStrokeMMSpeed(float strokeMM, float strokeSpeed) //Servo movement function, inputs are final position expressed as "mm from 0", speed as "% of full speed"
{
int interval = 9; //interval time (in mS) for updating motor position
int finalPos = 1020 + strokeMM * (2005 - 1020) / 50; //calculates final position in uS based on entered mm position value
if (strokeMM >= 0.0 && strokeMM <=50.0 && strokeSpeed > 0.0 && strokeSpeed <= 100.0) //checks that mm value and speed values given are in range
{
if (i <= finalPos-1020){ //checks that i has not reached limit
currentTime = millis(); //records current run time
if (currentTime - lastUpdate >= interval) { //checks whether "interval" time has been exceeded since last motor position update
lastUpdate = currentTime; //updates lastUpdate variable to current time
i = i + (strokeSpeed/100); //increases counter "i" by strokeSpeed
usec = 1050 + (int)i; //adds the integer value of i to the motor position variable
myServo.writeMicroseconds(usec); //moves the motor to this position
}
}
}
}
void setup() {
Serial.begin(9600);
myServo.attach(PIN_SERVO, 1020, 2005);
myServo.writeMicroseconds(1020); //moves servo back to 0 position for start of test
delay(20000); //waits for servo to get to 0
}
void loop() {
SetStrokeMMSpeed(50, 1); //Moves to 50mm (full travel) at 1% full speed
if (Started == 0) {StartTime = millis();} //Records start time
Started = 1; //Changes "started" flag to avoid overwriting StartTime variable
int sensorValue = analogRead(A0); //Positional feedback from actuator recorded (3.3V proportional signal)
float travelPos = (1 - ((sensorValue-12)/654.0))*100; //Calculates current position as a percentage of full travel - the "12" and the "654" values were determined experimentally for this servo
if (travelPos>99.5 ) { //Checks if actuator has reached 99.5% of total travel
EndTime = millis(); //Records end time
Serial.print("Run time: ");
Serial.print(EndTime - StartTime); //Prints total run time to serial monitor
Serial.println("mS");
Serial.println("############################");
usec = 1050; //Resets usec value to initial position (useful when looping through different speeds etc)
i = 0; //Resets i to 0 (useful for looping through different speeds etc)
Started = 0; //Resets "Started" flag to 0
}
if (Started == 0) {exit(0);} //Stops everything once the test is run. (Failsafe to avoid accidentally crashing the servo by looping movement commands through "void loop()"
}