Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions docs/src/Submakefile
Original file line number Diff line number Diff line change
Expand Up @@ -252,6 +252,7 @@ DOC_SRCS_EN := \
motion/kinematics.adoc \
motion/dh-parameters.adoc \
motion/pid-theory.adoc \
motion/dual-pid-example.adoc \
motion/tweaking-steppers.adoc \
motion/5-axis-kinematics.adoc \
motion/external-offsets.adoc \
Expand Down
1 change: 1 addition & 0 deletions docs/src/docs.xml
Original file line number Diff line number Diff line change
Expand Up @@ -88,6 +88,7 @@
<doc name="motion_kinematics" title="Kinematics"/>
<doc name="motion_tweaking_steppers" title="Tweaking Steppers"/>
<doc name="motion_pid_theory" title="PID theory"/>
<doc name="motion_dual_pid_example" title="Dual PID Example"/>
<doc name="ladder_ladder_intro" title="Ladder Introduction"/>
<doc name="examples_pci-parallel-port" title="PCI Parallel Port"/>
<doc name="examples_spindle" title="Spindle Example"/>
Expand Down
1 change: 1 addition & 0 deletions docs/src/index.tmpl
Original file line number Diff line number Diff line change
Expand Up @@ -162,6 +162,7 @@
<li><a href="motion/5-axis-kinematics.html">5-Axis-Kinematics</a></li>
<li><a href="motion/switchkins.html">Switchable Kinematics</a></li>
<li><a href="motion/pid-theory.html">PID theory</a></li>
<li><a href="motion/dual-pid-example.html">Dual PID Example</a></li>
<li><a href="remap/remap.html">Remap: Extending LinuxCNC</a></li>
<li><a href="config/moveoff.html">Moveoff Component</a></li>
<li><a href="code/rs274.html">Stand Alone Interpreter</a></li>
Expand Down
137 changes: 137 additions & 0 deletions docs/src/motion/dual-pid-example.adoc
Original file line number Diff line number Diff line change
@@ -0,0 +1,137 @@
= Dual Feedback PID


== Introduction
A dual feedback machine axis typically consists of a rotary encoder on the motor
and a linear encoder on the axis. The motor encoder is used for the bulk movement
using P, D and FF1 parameters and the linear encoder on the axis is used to
remove the last small steady state error using the I parameter. The two velocity
commands are added (summed) together before being sent to the servo amplifier.

== Flow Chart Diagram

.Dual PID Flow Chart
image::pid-images/dual_pid_example.svg["Dual PID Flow Chart"]

== Example HAL Code
Here we have a snippet of example code for an imaginary Z axis.
The setting of the P, I, D and FF1 etc must be tuned for best result.
These parameters are typically found in the INI file (in this case)
under the [JOINT_2] heading.
The loading of the PID and sum components are not shown.
The encoder and analog pins are typical Mesa card pins (7i92/7i77 in this case).

[source,hal]
----

#**************************
# EXAMPLE AXIS Z / JOINT 2
#**************************


# Inner loop: motor encoder PID (P + D + feedforwards, I=0 to avoid fighting)

setp pid.z.Pgain [JOINT_2]P
setp pid.z.Igain 0
setp pid.z.Dgain [JOINT_2]D
setp pid.z.bias [JOINT_2]BIAS
setp pid.z.FF0 [JOINT_2]FF0
setp pid.z.FF1 [JOINT_2]FF1
setp pid.z.FF2 [JOINT_2]FF2
setp pid.z.deadband [JOINT_2]DEADBAND
setp pid.z.maxoutput [JOINT_2]MAX_OUTPUT
setp pid.z.error-previous-target true


# Outer loop: linear scale PID (only I, others 0)

setp pid.z2.Pgain 0
setp pid.z2.Igain [JOINT_2]I
setp pid.z2.Dgain 0
setp pid.z2.bias 0
setp pid.z2.FF0 0
setp pid.z2.FF1 0
setp pid.z2.FF2 0
setp pid.z2.deadband [JOINT_2]DEADBAND
setp pid.z2.maxoutput [JOINT_2]MAX_OUTPUT


# Command from trajectory planner to both PIDs
net z-pos-cmd <= joint.2.motor-pos-cmd
net z-pos-cmd => pid.z.command
net z-pos-cmd => pid.z2.command

# Enable both loops together
net z-enable <= joint.2.amp-enable-out
net z-enable => pid.z.enable
net z-enable => pid.z2.enable

# connect index feedback to compensate during index
# motor encoder
net z2-index-enable <=> pid.z.index-enable

@grandixximo grandixximo Aug 6, 2026

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why connect the motor encoder index signal at all?
pos-fb comes from the linear encoder, why would you leave it to chance to have the motor index decide where homing happens on the linear feedback?
The whole reason there is a linear encoder is because the motor has some backlash which needs tweaking, why would you leave it to chance to end up on a less accurate index?
Am I missing something?
I see the note at the end, but IMO it would be better not to have this double connection in the base doc config, a better shape IMO is to comment out the motor index, and leave a note to enable only if the linear encoder index is not present because it is less accurate.

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I tend to agree in principle. I don't know if the encoder uses the index enable to reset something internally regardless if the motion component uses it.
In other words - since I can't test it, I'll leave it as is because it did work.
If we get more info later the docs can be updated.

# linear encoder
net z2-index-enable <=> pid.z2.index-enable

# connect separate feedback signals
# motor encoder
net z-pos-fb => pid.z.feedback
# linear encoder
net z2-pos-fb => pid.z2.feedback

# Connect the two PID outputs to a Sum component
net Zoutput-motor pid.z.output => sum2.0.in0
net Zoutput-linear pid.z2.output => sum2.0.in1

# Summed output goes to Mesa analog out (velocity command to servo drive)
net Zoutput-summed <= sum2.0.out
net Zoutput-summed => hm2_7i92.0.7i77.0.1.analogout2


# ---Motor Encoder Z feedback signals/setup---

setp hm2_7i92.0.encoder.02.counter-mode 0
setp hm2_7i92.0.encoder.02.filter 1
setp hm2_7i92.0.encoder.02.index-invert 0
setp hm2_7i92.0.encoder.02.index-mask 0
setp hm2_7i92.0.encoder.02.index-mask-invert 0
setp hm2_7i92.0.encoder.02.scale [JOINT_2]ENCODER_SCALE

# position feedback to PID Z
net z-pos-fb <= hm2_7i92.0.encoder.02.position

# index enable handshake for Motion and encoder
net z2-index-enable <=> hm2_7i92.0.encoder.02.index-enable


# ---Linear Encoder Z2 feedback signals/setup---

setp hm2_7i92.0.encoder.03.counter-mode 0
setp hm2_7i92.0.encoder.03.filter 1
setp hm2_7i92.0.encoder.03.index-invert 0
setp hm2_7i92.0.encoder.03.index-mask 0
setp hm2_7i92.0.encoder.03.index-mask-invert 0
setp hm2_7i92.0.encoder.03.scale [JOINT_2]LINEAR_ENCODER_SCALE

# position feedback to PID Z2 and Motions's joint motor position
net z2-pos-fb <= hm2_7i92.0.encoder.03.position
net z2-pos-fb => joint.2.motor-pos-fb

# index enable handshake for Motion and encoder
net z2-index-enable <=> joint.2.index-enable
net z2-index-enable <=> hm2_7i92.0.encoder.03.index-enable
----

== Other Details

In this example the index enable pins are connected to both encoders.
In practice only one encoder will be used for indexing - the one that
resets the enable first.

You may be able to remove the sum component and feed the output of one PID
into the bias pin of the other. This was not confirmed at time of writing.

Forum reference: https://forum.linuxcnc.org/10-advanced-configuration/37353-dual-pid-loops-and-appropriate-pins-for-feedback-to-the-trajectory-planner-et-al

wiki reference: http://wiki.linuxcnc.org/cgi-bin/wiki.pl?Combining_Two_Feedback_Devices_On_One_Axis


Loading
Loading