Synchronous motion between mutliple linear actuators can be vital to the success of some customer applications, one common one being two linear actuators opening a trapdoor. In order to achieve this we recommend using the dedicated Firgelli synchronous control box FA-SYNC-2 and FA-SYNC-4. However some DIYers and hackers prefer the freedom a microcontroller such as the Arduino offers and prefer instead to write their own synchronous control program. This tutorial aims to provide an overview on how to achieve this using the Optical Series linear actuator.
Foreword
This tutorial is not a rigorous treatment of the steps required to achieve synchronous control with Arduino, rather a broad overview to assist you writing your own custom program. This tutorial is advanced and assumes you are already familiar with Arduino hardware, software, and ideally have experience with pulse width modulation (PWM) signals, interrupt service routine (ISR), debouncing of sensors, and motor encoders. The example provided in this tutorial is a primitive proportional controller. Many improvements can be implemented on the following example including, but not limited to: implementing a PID control loop and scaling to more than two linear actuators. Please be aware that we do not have the resources to provide technical support for Arduino applications and will not debug, edit, provide code or wiring diagrams outside these publicly available tutorials.
Overview Synchronous Control
Synchronous control is achieved by comparing the length of two linear actuators and proportionally adjusting the speed; if one actuator starts moving faster than another we will slow it down. We can read the position of the linear actuator via the inbuilt optical encoder. The optical encoder is a small plastic disk with 10 holes in it that is connected to the DC motor such that when the motor spins the plastic disk does too. An infrared LED is directed towards the plastic disk so that as it spins the light with either be transmitted through the holes in the optical disk or blocked by the plastic of the disk. An infrared sensor on the other side of the disk detects when the light is transmit through the hole and outputs a square wave signal. By counting the number of pulses the receiver detects we can both calculate the RPM of the motor and the distance the linear actuator has travelled. The 35lb optical linear actuator has 50(+/-5) optical pulses per inch of travel while the 200lb and 400lb actuators both have 100(+/-5) pulses per inch. By comparing how far each linear actuator has extended, we are able to proportionally adjust the speed of the two actuators so that they always stay at the same length while extending.
Required Components
- Two optical linear actuators
- Two IBT-2 motor drivers
- Arduino Uno
- 12V power source
- 3 momentary buttons (not sold by Firgelli)
- Additional wiring
Wiring Diagram
Make the above wiring connections. Always check the wire colors coming out of the linear actuator as the coloring convention may change from what is shown in the above diagram. Be careful not to overlook the 3 momentary buttons connected between digital pins 7, 8, 9, and GND.
Quick Tutorial
If you just want to get your two linear actuators moving in synchronous simply follow these steps:
- Make the connections as shown in the wiring diagram.
- Upload and run the first program, below.
- Copy the two values output by this program into line 23 of the second program, below.
- Upload and run the second program.
- Fine tune your system by varying the variable K_p (line 37, second program). This is most easily done by attaching a potentiometer to analog pin A0 and modify thing the code to read the potentiometer and using the map() function: K_p=map(analogRead(A0), 0, 1023, 0, 20000);
The rest of this tutorial will go over in more detail some of the key features of the programs. Again we reiterate that this is not an exhaustive tutorial, rather an overview of things to consider when creating your own program.
Overview of Calibration Program
Before synchronous control can be achieved we must first calibrate the system. This involves counting the number of pulses per actuation cycle because as stated in the product specifications there is a tolerance of (+/-5)pulses per inch of travel. Upload and run the program, below. This program will fully retract the actuators (line 53) and set the optical pulse counter variable to zero it will then fully extended and fully retract (line 63 and 74, respectively). During this actuation cycle the number of pulses will be counted by the interrupt service routine (ISR), line 153 and 166. Once actuation cycle is complete the average number of pulses will be output, line 88, make note of these values for later.
https://gist.github.com/Will-Firgelli/89978da2585a747ef5ff988b2fa53904
Overview of Synchronous Program
Before uploading the synchronous control program, you must first copy the values output by the calibration program into line 23 and replace the current array: {908, 906} with your own values. Additionally, if you are using the 35lb linear actuator you will need to change the value of the variable in line 29 from 20 milliseconds to 8 milliseconds.
After fully retracting once (to identify the origin) you can move both linear actuators in synchronous by pressing the three buttons corresponding to extension, retraction and stop commands. The actuators will remain in synchronous even under uneven loads by comparing their relative pulse counters and adjusting speed between them to always remain in synchronous. Be advised that the current program implements a simple proportional controller, line 93, as such is subject to overshoot and oscillation around the equilibrium. You can tune this by varying the variable K_p, defined at line 37. This is most easily done by attaching a potentiometer to analog pin A0 and modify thing the code to read the potentiometer and using the map() function: K_p=map(analogRead(A0), 0, 1023, 0, 20000);
For the best results we strongly suggest removing the proportional controller and implementing a PID control loop; however this is beyond the scope of this introductory tutorial and has been omitted deliberately.
https://gist.github.com/Will-Firgelli/44a14a4f3cac3209164efe8abe3285b6
Using Bullet 36 and Bullet 50 Actuators in Synchronous
In addition to our Optical Series linear actuator we also offer two offer linear actuators with inbuilt encoders: the Bullet 36 Cal. and the Bullet 50 Cal, both of which have an internal quadrature Hall Effect encoder. The Hall Effect encoder works on the same principle as the optical encoder however instead of using light it utilizes magnetism. Furthermore being a quadrature encoder it has two signal outputs, each out of phase by 90 degrees. As such you need to use an Arduino board with 4 or more interrupt pins (the Arduino Uno only has two) and modify the code to process input from two signals per actuator. Furthermore, the debounce time variable, falsepulseDelay will need to be tuned along with K_p.
Tips for Writing Your Own Program
More than two linear actuators
When using two or more linear actuators the Arduino Uno will no longer work as it only has two interrupt pins available. You will need to use an Arduino board with the approriate number of interrupt pins available, more information: https://www.arduino.cc/reference/en/language/functions/external-interrupts/attachinterrupt/
Secondly in the interest of efficiency it is advisable to vectorize your programming utilizing arrays and for() loops to iterate over each actuator.
Debouncing
As with many sensors it is important to be cognizant of bouncing signals. As with mechanical switches, encoders can also suffer from bouncing. In the above example the debouncing process has been handled by a simple delay (defined by the falsepulseDelay variable), it is important to handle this in any software changes you make or with physically circuitry to filter out the bouncing noise.
Handling roll over
If you modify the code be aware of rollover when dealing with the millis() function. Both millis() and the lastDebounceTime array are declared as unsigned long variables meaning they can store values up to 4,294,967,295 (32^2-1). This translates to roughly a rollover period of 49.7 days. The current program is designed to handle rollover in the ISR (interrupt service routine) functions: count_0 & count_1, however if you modify this program be sure to correctly handle the variable rollover, else your program will crash after ~49.7 days of continuous use. For more information refer to: https://www.norwegiancreations.com/2018/10/arduino-tutorial-avoiding-the-overflow-issue-when-using-millis-and-micros/