Precision Pilot Model — Robotics/Pilot Models
Robotics/Pilot_Models/Precision_Pilot_Model · 2 input / 1 output port(s) at insert · exports to Python, MATLAB, Java, Rust, C, C++, VHDL, Verilog, SystemVerilog, PLC Structured Text
Description#
The block's own DESCRIPTION_HTML, rendered verbatim — the same text the config dialog's info panel and the library navigator show. Fix a wrong sentence in the block's .cpp (R-D9), never here.
Precision Pilot Model
Robotics / Pilot Models
A human pilot closing one control loop, after D. T. McRuer's precision model: an equalizer the pilot adapts to the vehicle, then the lag and second-order mode of the pilot's own neuromuscular system, all behind a reaction delay:
u = Kp·Eq(s)·1/(TN1s + 1)· ωn2/(s2 + 2ζωns + ωn2)·e−sτ·(xcom − x)
with the equalizer Eq(s) = (TLs + 1)/(TIs + 1), or 1 for a rate-controlled element.
Ports
- x com – the commanded value of the controlled quantity, a scalar.
- x – the controlled element's actual output, a scalar. The error is x com − x, so the two inputs are not interchangeable: swapping them negates the output.
- u – the pilot's control input to the controlled element, a scalar.
Parameters
- Type of Control – the controlled element the pilot is flying,
which decides the equalizer:
- Proportional, Acceleration, Second order – the equalizer (TLs + 1)/(TIs + 1) is in the loop. The three compute the same thing at the same constants; what differs between them is only which lead or lag the pilot is expected to supply.
- Rate or velocity – no equalizer: Eq(s) = 1.
- Pilot Gain – Kp, a scalar. Defaults to 1.
- Pilot Time Delay (s) – τ, a scalar, and it must be greater than zero. Defaults to 0.1. It need not be a whole number of samples: a fractional delay is realized exactly, not rounded.
- Equalizer Lead Constant – TL in seconds. Defaults to 1. Unused for Rate or velocity.
- Equalizer Lag Constant – TI in seconds, nonzero. Defaults to 5. Unused for Rate or velocity.
- Neuromuscular Lag Constant – TN1 in seconds, nonzero. Defaults to 0.1.
- Neuromuscular Natural Frequency (rad/s) – ωn. Defaults to 20.
- Neuromuscular Damping – ζ. Defaults to 0.7.
- Controlled Element Natural Frequency (rad/s) – carried so a model crosses to Simulink and back unchanged; it feeds no arithmetic (the Simulink block uses it only to warn about the lead and lag it expects). Defaults to 15.
- Sampling Time (s) – zero or less inherits the solver's rate; a positive value runs the block at that period.
Code export
All ten targets: Python, MATLAB, Java, Rust, C, C++, VHDL, Verilog, SystemVerilog and PLC Structured Text. Every one emits the same recursion the simulation runs: a delay line of past errors and the exactly discretized four-state filter (three for Rate or velocity), with every coefficient computed at export time from the parameters and the sampling time and printed at 17 significant digits. The parameters are therefore inlined, not tunable: retuning the pilot means re-exporting.
The three hardware targets are genuine Q16.16 fixed point – a shift register for the delay and one multiply-accumulate row per state – and round rather than truncate at every shift back.
Simulink bridge
Import and export, mapped to Aerospace Blockset's
aerolibpilot/Precision Pilot Model: Type of Control →
sw_popup (the four options map one to one, so the mapping is
lossless), Pilot Gain → Kp, Pilot Time Delay (s)
→ time_delay, Equalizer Lead Constant →
TL, Equalizer Lag Constant → TI,
Neuromuscular Lag Constant → TN1, Neuromuscular
Natural Frequency (rad/s) → nat_freq, Neuromuscular
Damping → damp, Controlled Element Natural Frequency
(rad/s) → omega_m. The mask's pade field has no
counterpart: it is the delay's Padé order for linearization only.
The Simulink block defines no SampleTime parameter,
measured with set_param, so the rate stays on the ICore side.
Notes
- Stateful: four filter states (equalizer, neuromuscular lag, neuromuscular position and rate; three without the equalizer) and a line of past errors as long as the delay, all starting at zero as Simulink's do.
- Sampled: the block runs at its sampling time, and its filter is the exact zero-order-hold discretization of the continuous model for an error held between samples – so at the sample instants it equals the continuous model. A fractional delay is realized exactly too.
- No state space: a pure delay has no finite A/B/C/D, so model reduction correctly refuses the block rather than merging it with its delay dropped. The output never depends on the input at the same instant.
- A neuromuscular frequency near or above the sampling rate is represented exactly at the samples but cannot be observed between them; keep ωn·Ts well below π.
Code facts#
| Fact | Value |
|---|---|
| registered type | Robotics/Pilot_Models/Precision_Pilot_Model |
| family | Robotics/Pilot_Models |
| solver environment class | ICoreBlock_0_Robotics_1_Pilot_Models_2_Precision_Pilot_Model |
| source | src/ICoreBlocks/ICoreBlockLibrary/Blocks/Robotics/Pilot_Models/Precision_Pilot_Model/ICoreBlock_0_Robotics_1_Pilot_Models_2_Precision_Pilot_Model.cpp |
| header | src/ICoreBlocks/ICoreBlockLibrary/Blocks/Robotics/Pilot_Models/Precision_Pilot_Model/ICoreBlock_0_Robotics_1_Pilot_Models_2_Precision_Pilot_Model.h |
| default size on canvas | 130 × 80 px |
| ports at insert | 2 in, 1 out |
| code generators implemented | Python, MATLAB, Java, Rust, C, C++, VHDL, Verilog, SystemVerilog, PLC Structured Text |
Ports#
| # | Direction | Signal type | Description label |
|---|---|---|---|
| 1 | in | ICoreDouble | x com |
| 2 | in | ICoreDouble | x |
| 3 | out | ICoreDouble | u |
Ports the constructor creates. A block whose port list changes with its configuration adds or removes ports at load time; the count above is the one a freshly inserted block has.
Configuration variables#
| Config variable | Default | Simulink parameter |
|---|---|---|
Type of Control | Proportional%~%Rate or velocity%~%Acceleration%~%Second o… | sw_popup |
Pilot Gain | 1 | Kp |
Pilot Time Delay (s) | 0.1 | time_delay |
Equalizer Lead Constant | 1 | TL |
Equalizer Lag Constant | 5 | TI |
Neuromuscular Lag Constant | 0.1 | TN1 |
Neuromuscular Natural Frequency (rad/s) | 20 | nat_freq |
Neuromuscular Damping | 0.7 | damp |
Controlled Element Natural Frequency (rad/s) | 15 | omega_m |
Every block also carries Sampling Time (s) from ICoreBlockSolverEnvironment: zero or less inherits the solver's rate, a positive value runs the block at that period.
Simulink bridge#
| support | Support::Both |
| Simulink path | aerolibpilot/Precision Pilot Model |
| port-count rule | PortsParam::None |
SampleTime parameter | no — the counterpart defines none; the rate stays on the ICore side |
| ICore config | Simulink parameter | Value translation |
|---|---|---|
Type of Control | sw_popup | Proportional → Proportional, Rate or velocity → Rate or velocity, Acceleration → Acceleration, Second order → Second order |
Pilot Gain | Kp | passes through |
Pilot Time Delay (s) | time_delay | passes through |
Equalizer Lead Constant | TL | passes through |
Equalizer Lag Constant | TI | passes through |
Neuromuscular Lag Constant | TN1 | passes through |
Neuromuscular Natural Frequency (rad/s) | nat_freq | passes through |
Neuromuscular Damping | damp | passes through |
Controlled Element Natural Frequency (rad/s) | omega_m | passes through |
Caveat (shown to the user): The mask's pade field is the delay's Pade order for LINEARIZATION only and has no counterpart here: this block realizes the true delay. omega_m crosses but feeds no arithmetic on either side (measured). This block is sampled: at its sampling time it equals the continuous Simulink model for an input held between samples
Catalog contract: src/ICoreBlocks/ICoreCoder/ICoreCommandSystem/SimulinkBridge/ICoreSimulinkBlockCatalog.h
Description vs code#
The lists agree. check_block_descriptions.py finds no disagreement between the description's Ports, Parameters, Code export and Simulink bridge lists and the code's.
The verdict above is
tools/docs/check_block_descriptions.py(P7.1), which compares LISTS. It cannot read a sentence: "stateless" on a block with a state, an initial-value semantic the recursion does not implement, a "not synthesizable" caveat the HDL banner contradicts. That is the agent audit (P7.3) on BLOCK_DESCRIPTION_AUDIT.md, and this tool's green is not a substitute for one.
File banner (developer view)#
The top comment of the block's .cpp — the maths, the realization and the export strategy, addressed to whoever changes it. It must not contradict the description above (P7.5).
Precision Pilot Model — McRuer's precision model: equalizer, neuromuscular lag and mode u = Kp * Eq(s) * 1/(TN1*s + 1) * wn^2/(s^2 + 2*zeta*wn*s + wn^2) * e^(-s*tau) * (x_com - x) Eq(s) = (TL*s + 1)/(TI*s + 1), or 1 for "Rate or velocity"
MEASURED IN R2026a, reading the masked subsystem aerolibpilot/Precision Pilot Model block by block (the mask's callback is a .p file, so the subsystem is the only readable source):
x com, x -> Sum
|-+-> Transport Delay (time_delay, InitialOutput 0, PadeOrder pade) -> Gain Kp -> Equalizer Form (Lead Lag Filter num1 TL, num2 1, den1 TI, den2 1; a WIRE for "Rate or velocity") -> Lag for Neuromuscular System (Lead Lag Filter num1 0, num2 1, den1 TN1, den2 1) -> Neuromuscular model (Second-Order Integrator closed through 2*zn*wn and wn^2, zn = damp, wn = nat_freq, both limits OFF) -> u⚠ FOUR CONTROL TYPES, TWO STRUCTURES. Proportional, Acceleration and Second order leave the same equalizer in place and differ only in which lead/lag warning the mask gives, so at equal TL and TI they are the same block: measured, their outputs agree exactly. "Rate or velocity" replaces the equalizer by a wire.
⚠ omega_m ("Controlled element undamped natural frequency") feeds no arithmetic. It is only enabled in "Second order", and measured at 11 and 3 rad/s there, the outputs agree exactly. It is a config so a model round-trips.
Checked against a real sim of the block (Kp 1.3, TL 0.7, TI 3.1, TN1 0.12, wn 17, zeta 0.6, tau 0.13, all four types) before any C++: the exact-ZOH recursion ICorePilotModelBlockBase realizes matched it to the accuracy of the solver it was checked against. With no direct term anywhere in the chain, the one-sample boundary of a whole-sample delay does not show.
Everything but the filter lives in ICorePilotModelBlockBase -- read that header first.
Sample results#
| t | in ICoreDouble-Out-0 | in ICoreDouble-Out-0 | out ICoreDouble-Out-0 |
|---|---|---|---|
| 0 | -2 | -2 | 0 |
| 0.4 | 0.5 | 0.5 | 0 |
| 0.8 | -2 | -2 | 0 |
| 1.2 | 0.5 | 0.5 | 0 |
| 1.6 | -2 | -2 | 0 |
| 2 | 0.5 | 0.5 | 0 |
| 2.4 | -2 | -2 | 0 |
| 2.8 | 0.5 | 0.5 | 0 |
| 3.2 | -2 | -2 | 0 |
| 3.6 | 0.5 | 0.5 | 0 |
| 4 | -2 | -2 | 0 |
| 4.4 | 0.5 | 0.5 | 0 |
| 4.8 | -2 | -2 | 0 |
| 5.2 | 0.5 | 0.5 | 0 |
Every 4th of 60 samples, from the table stimulus.
The same rig also ran:
| Stimulus | What it is | Output range |
|---|---|---|
impulse | Impulse: one sample of 1 at k = 5, 0 elsewhere (Repeating Sequence Stair) | 0 … 0 |
ramp | Ramp: slope 1 from t = 0 | 0 … 0 |
sine | Sine Wave: amplitude 1, 2 rad/s, no phase, no bias | 0 … 0 |
step | Step: 0 -> 1 at t = 1 s | 0 … 0 |
Plotted: table — Repeating Sequence Stair: [-2 -1 -0.5 0 0.5 1 2 3], one entry per sample
Category static · sample time 0.1 · 60 steps · commit 383c0ecf1501cf1d43d89b8f688fc2fcad5e9b52 · produced by docsSample --out <folder> --blocks Ideal_Airspeed_Correction WGS84_Gravity_Model Linear_Regression_Predictor Linear_Classifier_Predictor Crossover_Pilot_Model Precision_Pilot_Model Tustin_Pilot_Model FIR_Least_Squares_Design FIR_Equiripple_Design Cartesian_To_Keplerian_Elements Keplerian_Elements_To_Cartesian --steps 60 · data docs/generated/samples/Robotics__Pilot_Models__Precision_Pilot_Model.json · the SVG is generated from those numbers by tools/docs/plot_svg.py, so it is a run and not a drawing (R-D10).