Motion
Synchronized Pneumatic Cylinder Control with ESP32 and Linear Scales
Section titled “Synchronized Pneumatic Cylinder Control with ESP32 and Linear Scales”This document outlines the system and functional design for controlling two pneumatic cylinders, ensuring they maintain the same position throughout their movement, using an ESP32 microcontroller and linear scales for precise position feedback.
1. System Overview
Section titled “1. System Overview”The system aims to achieve synchronized motion of two independent pneumatic cylinders. An ESP32 serves as the central controller, reading position data from linear scales attached to each cylinder and actuating pneumatic valves to control cylinder movement. The core challenge is maintaining positional parity between the two cylinders during extension and retraction.
2. Components
Section titled “2. Components”- ESP32 Microcontroller: The brain of the system, responsible for:
- Reading data from linear scales.
- Processing inputs from end-stops.
- Executing the control algorithm.
- Driving pneumatic valves.
- (Optional) Communication interface for commands and status.
- Pneumatic Cylinders (x2): Double-acting cylinders are recommended for controlled movement in both directions.
- Linear Scales (x2): Provide relative position feedback. Typically output quadrature pulses (A/B signals) or simple pulse/direction signals. The ESP32 will count these pulses to determine relative movement. A homing sequence is required to establish an absolute reference.
- End-Stops (x2 per cylinder: Home & Max): Physical switches (e.g., limit switches, Hall effect sensors with magnets) mounted at the physical end-of-travel for each cylinder.
- Home End-Stop: Defines the zero or reference position.
- Max End-Stop: Defines the maximum extension limit.
- Pneumatic Solenoid Valves: Control the airflow to the cylinders.
- Valve Drivers/Relay Modules: Interface between the ESP32’s low-voltage GPIO signals and the higher voltage/current requirements of the solenoid valves.
- Air Supply: Filtered and regulated compressed air.
- Power Supply: Appropriate power supplies for the ESP32, linear scales, and solenoid valves.
3. Functional Design
Section titled “3. Functional Design”3.1. Position Sensing
Section titled “3.1. Position Sensing”Each linear scale is directly coupled to its respective pneumatic cylinder’s piston rod or carriage. The ESP32’s hardware counters or interrupt routines will be used to count the incoming pulses from the A/B quadrature signals of each linear scale.
- Relative Position: The raw data from the scales is a pulse count, representing movement relative to the position at which counting started.
- Homing Required: To establish an absolute position reference, a homing sequence must be performed at startup, utilizing the physical end-stops. Once homed, the pulse count can be translated into an absolute position (e.g., millimeters from home).
- End-Stop Monitoring: The ESP32 will also continuously monitor the state of all four end-stops (Home A, Max A, Home B, Max B).
3.2. Actuation Control
Section titled “3.2. Actuation Control”The ESP32 controls the valves for each cylinder independently. Based on the control algorithm, the ESP32 will energize or de-energize solenoids to:
- Extend the cylinder.
- Retract the cylinder.
- Hold the cylinder’s position (if using 5/3-way center-blocked valves).
3.3. Synchronization Logic
Section titled “3.3. Synchronization Logic”The core of the system. The ESP32 will:
- Receive a target position for the synchronized pair.
- Continuously compare the actual positions of Cylinder A (PosA) and Cylinder B (PosB) against the target position and against each other.
- Adjust valve actuation for each cylinder to move towards the target while minimizing the difference |PosA - PosB|.
4. Valve Options
Section titled “4. Valve Options”The choice of valve significantly impacts control capabilities:
-
5/2-Way Solenoid Valves (Recommended for Basic Control):
- Configuration: Two solenoids (or one spring-return solenoid) per valve. One position extends the cylinder, the other retracts.
- Control: Simple on/off control for each direction.
- Pros: Common, relatively inexpensive, simple to control.
- Cons: No mid-stroke holding capability (cylinder will drift to one end if de-energized unless mechanically locked or air is trapped perfectly). Speed control is typically managed by flow restrictors on the exhaust ports.
-
5/3-Way Solenoid Valves (Recommended for Better Control & Safety):
- Configurations:
- Center-Blocked: All ports blocked in the center position. Holds the cylinder position (traps air on both sides). Good for stopping and holding.
- Center-Exhaust: Both cylinder ports vent to exhaust in the center position. Cylinder can be moved freely. Useful for manual override or if external forces need to move the cylinder when idle.
- Center-Pressure: Both cylinder ports connected to pressure in the center position. Provides equal force on both sides of the piston; less common for this type of application.
- Control: Requires two solenoids. Energizing one moves to one end, energizing the other moves to the other end, and de-energizing both returns to the center position.
- Pros: Allows for a defined “hold” or “float” state. Center-blocked is particularly useful for synchronization to stop one cylinder while the other catches up.
- Cons: More expensive and slightly more complex to control than 5/2-way.
- Configurations:
-
Proportional Pneumatic Valves (Advanced Control):
- Control: Vary the opening of the valve, allowing fine control over flow rate and thus cylinder speed and, to some extent, position.
- Pros: Enables much smoother and more precise motion control, including speed profiling.
- Cons: Significantly more expensive, requires more complex control signals (e.g., analog voltage or PWM), and more sophisticated control algorithms (e.g., full PID for position and speed). Might be overkill unless very high precision is needed.
For this system, 5/3-way center-blocked solenoid valves are generally preferred for their ability to hold position, which aids in synchronization. If cost is a major constraint and some positional drift when “stopped” is acceptable, 5/2-way valves can be used.
5. Control Algorithm
Section titled “5. Control Algorithm”A PID (Proportional-Integral-Derivative) controller or a simpler P (Proportional) controller can be implemented for each cylinder. Synchronization can be layered on top.
5.1. Core Control Loop (per cylinder, simplified):
Section titled “5.1. Core Control Loop (per cylinder, simplified):”- Read Current Position (ActualPos) from its linear scale.
- Calculate Error:
Error = TargetPos - ActualPos
. - Determine Action:
- If
Error > Deadband_Positive
: Extend cylinder. - If
Error < Deadband_Negative
: Retract cylinder. - If
|Error| <= Deadband_Nominal
: (If using 5/3 center-blocked) Hold position; (If using 5/2) De-energize (or use PWM for speed control if flow restrictors are not enough). Deadband
values prevent “hunting” around the target.
- If
5.2. Synchronization Strategy: Master-Slave with Correction
Section titled “5.2. Synchronization Strategy: Master-Slave with Correction”One common approach is a master-slave strategy with correction:
- Designate a Master: One cylinder (e.g., Cylinder A) can be designated as the master, or the master can be the one that is currently “behind” the overall desired target movement.
- Primary Target: Both cylinders initially target the overall
SystemTargetPosition
. This target must be within the calibrated travel range established after homing and considering the max end-stops. - Positional Difference Check: Continuously calculate
PositionDifference = PosA - PosB
. - Correction Logic:
- If
|PositionDifference| > MaxAllowedDifference
:- If
PosA > PosB
(A is ahead of B):- Slow down or stop Cylinder A (using 5/3 valve’s center-block, or by pulsing a 5/2 valve off).
- Ensure Cylinder B continues or speeds up towards its target (which is effectively PosA or the SystemTargetPosition, whichever is lagging).
- If
PosB > PosA
(B is ahead of A):- Slow down or stop Cylinder B.
- Ensure Cylinder A continues or speeds up.
- If
- The cylinder that is “ahead” effectively has its individual target position capped or its movement paused until the lagging cylinder catches up.
- If
5.3. Refined Algorithm Steps:
Section titled “5.3. Refined Algorithm Steps:”-
Input:
SystemTargetPosition
. (Ensure this is within known travel limits after homing). -
Initialization:
- Perform Homing Sequence (see section 5.5).
- After homing,
PosA_current
andPosB_current
are known (e.g., 0 if at home).
-
Loop: a. Read
PosA_current
,PosB_current
from linear scale pulse counts (converted to absolute units based on home reference). b. Monitor end-stop states. If a Max end-stop is hit unexpectedly, halt relevant cylinder and flag error. c. CalculateErrorA = SystemTargetPosition - PosA_current
. d. CalculateErrorB = SystemTargetPosition - PosB_current
. e. CalculatePositionDifference = PosA_current - PosB_current
.f. Cylinder A Control Decision: * If
|PositionDifference| > MaxAllowedDifference
ANDPosA_current > PosB_current
(A is too far ahead): *ActionA = HOLD
(or SLOW_DOWN if proportional/PWM control) * Else ifErrorA > DeadbandA_Positive
: *ActionA = EXTEND
* Else ifErrorA < DeadbandA_Negative
: *ActionA = RETRACT
* Else: *ActionA = HOLD
(or stop if 5/2 valve)g. Cylinder B Control Decision: * If
|PositionDifference| > MaxAllowedDifference
ANDPosB_current > PosA_current
(B is too far ahead): *ActionB = HOLD
(or SLOW_DOWN) * Else ifErrorB > DeadbandB_Positive
: *ActionB = EXTEND
* Else ifErrorB < DeadbandB_Negative
: *ActionB = RETRACT
* Else: *ActionB = HOLD
(or stop if 5/2 valve)h. Apply
ActionA
to Cylinder A’s valve(s). i. ApplyActionB
to Cylinder B’s valve(s). j. Small delay (e.g., 10-50ms) to allow mechanical response and prevent excessive CPU load. k. Repeat loop until both|ErrorA|
and|ErrorB|
are within their respectiveDeadband_Nominal
and|PositionDifference|
is minimal, AND no end-stops are unexpectedly triggered.
5.4. Error Handling:
Section titled “5.4. Error Handling:”- Timeout: If cylinders don’t reach the target within a specified time.
- Excessive Position Difference: If
|PositionDifference|
exceeds a critical threshold and doesn’t resolve, flag an error (e.g., one cylinder might be jammed). - Sensor Failure: Implement checks for erratic or non-changing sensor readings.
- End-Stop Triggered Prematurely: If a max end-stop is hit before the target position is reached, it indicates a problem (e.g., incorrect homing, obstruction, incorrect target). The system should halt affected cylinder(s) and flag an error.
5.5. Homing Sequence (New Section)
Section titled “5.5. Homing Sequence (New Section)”A homing sequence is crucial for systems with relative encoders to establish a known absolute reference point. This sequence should be performed at startup and can be re-triggered if necessary.
- Pre-condition: Ensure valves are in a state that allows cylinder movement (e.g., 5/3 center-exhaust or ready to be powered).
- Homing Cylinder A (Retract to Home End-Stop):
a. Command Valve A to retract Cylinder A at a slow, controlled speed.
b. Monitor HomeA_EndStop.
c. When HomeA_EndStop is triggered:
i. Stop Cylinder A (e.g., center-block valve or de-energize 5/2).
ii. Reset
PulseCountA = 0
. iii.PosA_current = 0.0
(or a known offset if the home sensor isn’t exactly at physical zero). iv. Record that Cylinder A is homed. - Homing Cylinder B (Retract to Home End-Stop):
a. Command Valve B to retract Cylinder B at a slow, controlled speed.
b. Monitor HomeB_EndStop.
c. When HomeB_EndStop is triggered:
i. Stop Cylinder B.
ii. Reset
PulseCountB = 0
. iii.PosB_current = 0.0
. iv. Record that Cylinder B is homed. - (Optional) Determine Max Travel for Cylinder A:
a. Slowly command Valve A to extend Cylinder A.
b. Monitor MaxA_EndStop and count pulses (
PulseCountA_Max
). c. When MaxA_EndStop is triggered: i. Stop Cylinder A. ii. StorePulseCountA_Max
as the maximum travel range for Cylinder A. iii. Optionally, retract Cylinder A slightly off the max end-stop. - (Optional) Determine Max Travel for Cylinder B:
a. Slowly command Valve B to extend Cylinder B.
b. Monitor MaxB_EndStop and count pulses (
PulseCountB_Max
). c. When MaxB_EndStop is triggered: i. Stop Cylinder B. ii. StorePulseCountB_Max
as the maximum travel range for Cylinder B. iii. Optionally, retract Cylinder B slightly off the max end-stop. - Homing Complete: Both cylinders are now referenced. Subsequent target positions should be validated against the known maximum travel for each cylinder.
The order of homing (A then B, or simultaneously if safe) and max travel determination can be adjusted based on system mechanics.
6. Pseudo-algorithms
Section titled “6. Pseudo-algorithms”This section replaces the previous Mermaid sequence diagrams with pseudo-algorithms to describe the system’s operational flow.
6.1. System Initialization & Homing Sequence
Section titled “6.1. System Initialization & Homing Sequence”This algorithm describes the startup process, focusing on homing both cylinders using their respective end-stops to establish a zero reference.
PROCEDURE System_Initialize_And_Home OUTPUT: System Homed Status
Log: "System Power On / Reset"
// --- Homing Cylinder A --- Log: "Starting Homing for Cylinder A" ESP32Controller.SendCommand(ValveA, RETRACT_SLOW) LOOP CylinderA.Move() Current_HomeA_Status = ESP32Controller.ReadInput(HomeEndStopA) IF Current_HomeA_Status IS ACTIVE THEN ESP32Controller.SendCommand(ValveA, STOP) // Or center-block ESP32Controller.ResetPulseCount(LinScaleA) // PosA_PulseCount = 0 CylinderA_Position = 0.0 // Set absolute position Log: "Cylinder A Homing Complete at position 0.0" CylinderA_IsHomed = TRUE BREAK LOOP END IF Delay(Homing_Poll_Interval) END LOOP IF NOT CylinderA_IsHomed THEN Log: "ERROR: Cylinder A Homing Failed (Timeout or End-Stop not detected)" SystemHomedStatus = FAILED RETURN END IF
// --- Homing Cylinder B --- Log: "Starting Homing for Cylinder B" ESP32Controller.SendCommand(ValveB, RETRACT_SLOW) LOOP CylinderB.Move() Current_HomeB_Status = ESP32Controller.ReadInput(HomeEndStopB) IF Current_HomeB_Status IS ACTIVE THEN ESP32Controller.SendCommand(ValveB, STOP) // Or center-block ESP32Controller.ResetPulseCount(LinScaleB) // PosB_PulseCount = 0 CylinderB_Position = 0.0 // Set absolute position Log: "Cylinder B Homing Complete at position 0.0" CylinderB_IsHomed = TRUE BREAK LOOP END IF Delay(Homing_Poll_Interval) END LOOP IF NOT CylinderB_IsHomed THEN Log: "ERROR: Cylinder B Homing Failed (Timeout or End-Stop not detected)" SystemHomedStatus = FAILED RETURN END IF
// --- Optional: Determine Max Travel --- // This would involve slowly extending each cylinder until its Max_EndStop is hit, // recording the pulse count, and then optionally retracting slightly. // Example for Cylinder A: // ESP32Controller.SendCommand(ValveA, EXTEND_SLOW) // LOOP until MaxEndStopA is ACTIVE // MaxPulseCountA = ESP32Controller.ReadPulseCount(LinScaleA) // END LOOP // ESP32Controller.SendCommand(ValveA, STOP) // Store MaxPulseCountA
Log: "System Homing Complete. Ready for operations." SystemHomedStatus = SUCCESSEND PROCEDURE
6.2. Move to Target Position (Synchronized)
Section titled “6.2. Move to Target Position (Synchronized)”This algorithm outlines the process after a user requests a new target position, assuming homing is complete. It includes the synchronization logic and end-stop safety checks.
PROCEDURE Move_To_Target_Synchronized INPUT: TargetSystemPosition OUTPUT: Movement_Status (SUCCESS, ERROR_JAMMED, ERROR_MAX_ENDSTOP_HIT, etc.)
IF NOT (CylinderA_IsHomed AND CylinderB_IsHomed) THEN Log: "ERROR: Cylinders not homed. Cannot move." Movement_Status = ERROR_NOT_HOMED RETURN END IF
// Validate TargetSystemPosition against known max travel limits (if determined during homing) IF TargetSystemPosition > MaxTravelLimitA OR TargetSystemPosition > MaxTravelLimitB THEN Log: "ERROR: Target position exceeds learned maximum travel limits." Movement_Status = ERROR_TARGET_OUT_OF_BOUNDS RETURN END IF
Log: "User command: Set TargetPosition to " + TargetSystemPosition MovementStartTime = CurrentTime // For timeout tracking
LOOP // Main motion control loop // --- Read Current Positions --- PosA_PulseCount = ESP32Controller.ReadPulseCount(LinScaleA) PosB_PulseCount = ESP32Controller.ReadPulseCount(LinScaleB) PosA_Current = ConvertPulsesToUnits(PosA_PulseCount) // e.g., mm from home PosB_Current = ConvertPulsesToUnits(PosB_PulseCount) // e.g., mm from home
// --- Safety Check: Max End-Stops --- IF ESP32Controller.ReadInput(MaxEndStopA) IS ACTIVE AND ActionA IS NOT (HOLD OR STOPPING) THEN // Check if already stopping ESP32Controller.SendCommand(ValveA, STOP_IMMEDIATE) ESP32Controller.SendCommand(ValveB, STOP_IMMEDIATE) // Stop both for safety Log: "ERROR: MaxEndStopA triggered unexpectedly!" Movement_Status = ERROR_MAX_ENDSTOP_A_HIT BREAK LOOP END IF IF ESP32Controller.ReadInput(MaxEndStopB) IS ACTIVE AND ActionB IS NOT (HOLD OR STOPPING) THEN // Check if already stopping ESP32Controller.SendCommand(ValveA, STOP_IMMEDIATE) // Stop both for safety ESP32Controller.SendCommand(ValveB, STOP_IMMEDIATE) Log: "ERROR: MaxEndStopB triggered unexpectedly!" Movement_Status = ERROR_MAX_ENDSTOP_B_HIT BREAK LOOP END IF
// --- Calculate Errors and Difference --- ErrorA = TargetSystemPosition - PosA_Current ErrorB = TargetSystemPosition - PosB_Current PositionDifference = PosA_Current - PosB_Current
// --- Check if Target Reached --- IF (|ErrorA| <= Deadband_Nominal) AND (|ErrorB| <= Deadband_Nominal) AND (|PositionDifference| <= Acceptable_Sync_Difference_At_Target) THEN Log: "Target Position Reached by both cylinders. PosA: " + PosA_Current + ", PosB: " + PosB_Current ActionA = HOLD // Or de-energize 5/2 ActionB = HOLD // Or de-energize 5/2 ESP32Controller.SendCommand(ValveA, ActionA) ESP32Controller.SendCommand(ValveB, ActionB) Movement_Status = SUCCESS BREAK LOOP END IF
// --- Determine Action for Cylinder A (based on Section 5.3 logic) --- // Simplified: IF |PositionDifference| > MaxAllowedDifference AND PosA_Current > PosB_Current THEN ActionA = HOLD // A is too far ahead ELSE IF ErrorA > DeadbandA_Positive THEN ActionA = EXTEND ELSE IF ErrorA < DeadbandA_Negative THEN ActionA = RETRACT ELSE ActionA = HOLD END IF
// --- Determine Action for Cylinder B (based on Section 5.3 logic) --- // Simplified: IF |PositionDifference| > MaxAllowedDifference AND PosB_Current > PosA_Current THEN ActionB = HOLD // B is too far ahead ELSE IF ErrorB > DeadbandB_Positive THEN ActionB = EXTEND ELSE IF ErrorB < DeadbandB_Negative THEN ActionB = RETRACT ELSE ActionB = HOLD END IF
// --- Apply Actions --- ESP32Controller.SendCommand(ValveA, ActionA) ESP32Controller.SendCommand(ValveB, ActionB)
// --- Actuation and Movement (Conceptual) --- // ValveA -> CylinderA -> LinScaleA (updates PosA_PulseCount) // ValveB -> CylinderB -> LinScaleB (updates PosB_PulseCount)
Delay(Control_Loop_Interval) // e.g., 10-50ms
// Add Timeout check for overall movement IF CurrentTime - MovementStartTime > MaxMovementTimeout THEN Log: "ERROR: Movement timed out." ESP32Controller.SendCommand(ValveA, STOP_IMMEDIATE) ESP32Controller.SendCommand(ValveB, STOP_IMMEDIATE) Movement_Status = ERROR_TIMEOUT BREAK LOOP END IF
END LOOP RETURN Movement_StatusEND PROCEDURE
6.3. Error Condition: One Cylinder Jammed During Movement
Section titled “6.3. Error Condition: One Cylinder Jammed During Movement”This describes the system’s behavior when one cylinder fails to move as expected, leading to a large position difference. This logic is typically part of the main Move_To_Target_Synchronized
loop.
PROCEDURE Check_For_Jam_And_Handle (CurrentPosA, CurrentPosB, TargetPos, PreviousPosB, CommandedActionB) // This function would be called within the Move_To_Target_Synchronized loop.
PositionDifference = CurrentPosA - CurrentPosB
IF |PositionDifference| > CriticalJamThreshold THEN Log: "CRITICAL ERROR: PositionDifference (" + PositionDifference + "mm) exceeds jam threshold!" Log: "Cylinder A at " + CurrentPosA + "mm, Cylinder B at " + CurrentPosB + "mm." // Additional check: if Cylinder B was commanded to move but didn't IF (CommandedActionB IS EXTEND OR CommandedActionB IS RETRACT) AND (CurrentPosB IS APPROXIMATELY PreviousPosB) THEN Log: "Suspected Jam on Cylinder B (failed to move as commanded)." ELSE IF (// Similar check for Cylinder A if it was supposed to move and didn't, while B moved) THEN Log: "Suspected Jam on Cylinder A." ELSE Log: "Synchronization lost, large position difference." END IF
ESP32Controller.SendCommand(ValveA, STOP_IMMEDIATE) // Halt all motion ESP32Controller.SendCommand(ValveB, STOP_IMMEDIATE)
// Notify user/system User_Notification("Error: Cylinder Jammed or Synchronization Lost") RETURN ERROR_CYLINDER_JAMMED_OR_SYNC_LOST END IF RETURN NO_ERROR // Jam not detected this cycleEND PROCEDURE
7. Further Considerations
Section titled “7. Further Considerations”- Speed Control: For 5/2 or 5/3 valves, speed is typically controlled by adding flow control valves (needle valves) on the exhaust ports of the main valves. More advanced systems might use PWM on the solenoids if they support it, or use proportional valves.
- Safety: Implement emergency stop functionality. Consider mechanical stops if over-travel is a risk.
- Tuning: The control loop parameters (deadbands,
MaxAllowedDifference
, PID gains if used) will require tuning for optimal performance based on the specific mechanics, air pressure, and load. - Homing: A robust homing sequence, as described in section 5.5, is essential due to the use of relative linear scales (pulse-based). This sequence uses the physical home end-stops to establish a reliable zero position. Max end-stops define the usable travel range and prevent over-travel.
- End-Stop Logic: Ensure that end-stop signals immediately halt motion in the respective direction to prevent damage, irrespective of the control algorithm’s current target.
This design provides a functional framework. The specific implementation details of the algorithm, especially the conditions for slowing down/stopping one cylinder, will depend on the desired responsiveness and precision of the synchronization.