Ackermann Steering Model — Robotics/Planar Kinematics
Robotics/Planar_Kinematics/Ackermann_Steering_Model · 2 input / 2 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.
Ackermann Steering Model
Robotics / Planar Kinematics
The kinematic bicycle model: a steering angle and a speed become a path curvature and a yaw rate. κ = tan(δ) / L and ω = v · κ, with δ first clamped to the vehicle's mechanical steering limit.
A real Ackermann linkage steers its two road wheels through different angles; this model collapses them onto one virtual wheel at the centre of the front axle, which is the form every path tracker and every vehicle-dynamics text is written against. It is the source of the yaw rate that Unicycle Odometry integrates into a pose.
Ports
- v – the longitudinal speed of the rear axle (m/s), any size [m,n]. Negative is reverse, and is meaningful: the curvature is unchanged and the yaw rate reverses with it.
- delta – the virtual front-wheel steering angle (rad), positive to the left (counter-clockwise, per the family's convention). It must be the same size as v; the two are paired entry by entry as one vehicle's command, and neither is broadcast over the other.
- omega – the yaw rate v·tan(δ)/L (rad/s), the same size as the inputs.
- kappa – the path curvature tan(δ)/L (1/m), the same size as the inputs. It is independent of speed, which is what makes it the quantity a pursuit controller compares against: the geometry of the path the vehicle is currently on, whatever speed it is travelling at.
Parameters
- Wheelbase (m) – L, the distance from rear axle to front axle, a positive scalar. Default 2.7, an ordinary passenger car.
- Max Steer Angle (rad) – the mechanical stop, a scalar strictly inside (0, π/2). delta is clamped to ±this before the tangent is taken. Default 0.6 (about 34°), which is a typical car's lock. This is not a safety net bolted on: see the note below on why the clamp is part of the model.
- 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. The wheelbase reaches the core as the folded constant 1/L and the steering limit as a literal; neither is retunable on the generated core, so re-export to change a vehicle.
The three HDL targets are simulation-only: they carry the
arithmetic in real and quantize only at the port boundary. A
tangent has no Q16.16 form to call, so offering them as synthesizable would be a
claim the generated core could not keep.
No target carries a domain guard, and none needs one – the clamp happens before the tangent in all eleven implementations, so the argument is always inside the range the configuration pinned.
Simulink bridge
No equivalent (Support::None). Bicycle-model blocks ship
in the Automated Driving Toolbox and the Robotics System Toolbox, and
neither is installed on this machine (measured –
isfolder([matlabroot '/toolbox/driving']) and the
robotics probe both come back false). To rebuild it there: a
Saturation on the angle, a Trigonometric Function set to
tan, a Gain of 1/L, and a Product against the speed
– in that order, since saturating anywhere else is a different curve.
Notes
- Algebraic and stateless: both outputs depend only on the current inputs, so the block cannot break an algebraic loop. It carries no tyre model, no slip and no lateral dynamics – it is the kinematic bicycle, and it stops being accurate where the tyres start sliding.
- The clamp is part of the model, not a guard. tan is unbounded as |δ| → π/2, and a single sample near the asymptote does not produce a large error but a different kind of object: a curvature of 1015, which saturates the fixed-point targets and moves a downstream pose by kilometres in one step. The mechanical stop is where a real linkage ends, so clamping to it is what the vehicle does.
- The clamp is on the ANGLE, never on the curvature. tan is nonlinear, so clamping the output afterwards would leave a different and steeper curve between the two limits – the same endpoints, a different vehicle.
- Nonlinear, and deliberately carries no state space – nonlinear in delta and bilinear in (v, delta). A fabricated linear form would let model reduction merge matrices that do not describe this block.
- Straight ahead is exact. At δ = 0 the curvature is exactly zero in every target, so a vehicle commanded straight does not drift.
- Pairs with Unicycle Odometry: feed v and this block's omega into it to dead-reckon the pose, and keep Angle Wrap out of the path unless you want the heading wrapped – that block is the only one that wraps.
Code facts#
| Fact | Value |
|---|---|
| registered type | Robotics/Planar_Kinematics/Ackermann_Steering_Model |
| family | Robotics/Planar_Kinematics |
| solver environment class | ICoreBlock_0_Robotics_1_Planar_Kinematics_2_Ackermann_Steering_Model |
| source | src/ICoreSDK/ICoreBlockLibrary/Blocks/Robotics/Planar_Kinematics/Ackermann_Steering_Model/ICoreBlock_0_Robotics_1_Planar_Kinematics_2_Ackermann_Steering_Model.cpp |
| header | src/ICoreSDK/ICoreBlockLibrary/Blocks/Robotics/Planar_Kinematics/Ackermann_Steering_Model/ICoreBlock_0_Robotics_1_Planar_Kinematics_2_Ackermann_Steering_Model.h |
| default size on canvas | 150 × 84 px |
| ports at insert | 2 in, 2 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 | v |
| 2 | in | ICoreDouble | delta |
| 3 | out | ICoreDouble | omega |
| 4 | out | ICoreDouble | kappa |
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 |
|---|---|---|
Wheelbase (m) | 2.7 | — |
Max Steer Angle (rad) | 0.6 | — |
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::None |
| Simulink path | — |
| port-count rule | PortsParam::None |
SampleTime parameter | yes |
Caveat (shown to the user): no Simulink equivalent available: bicycle-model blocks ship in the Automated Driving Toolbox and the Robotics System Toolbox, and neither is installed on this machine (measured - the same probe that found the aerospace and fusion toolboxes absent). To rebuild it there: a Saturation on the angle, a Trigonometric Function set to tan, a Gain of 1/L, and a Product against the speed - in that order, since saturating anywhere else is a different curve
Catalog contract: src/ICoreSDK/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).
Ackermann Steering Model — the kinematic bicycle d = clamp(delta, -Max Steer Angle, +Max Steer Angle) kappa = tan(d) / L omega = v * kappa
The model every path tracker is written against: two road wheels collapsed onto one virtual wheel at the axle centre. It is what turns a steering command into the yaw rate
Unicycle_Odometryintegrates, and the curvature output is what a pursuit controller compares against its own.Three decisions, each of which survives into the generated code:
- THE CLAMP IS ON THE ANGLE AND IS PART OF THE SPEC. tan diverges at pi/2 and no linkage
reaches it; clamping the INPUT to the mechanical stop is what the vehicle does, and it is what makes the block total -- no backend needs a domain guard, because tan is never handed anything outside a range the config validation pins inside (-pi/2, pi/2). Clamping the CURVATURE instead would be a different (steeper) curve between the limits, since tan is nonlinear.
- 1/L IS FOLDED ONCE AT CONFIG LOAD. It is the block's only division; the C++ reference
multiplies by the same constant the generators bake in, so the block cannot end up a rounding away from its own export.
- THE HDL TARGETS ARE SIMULATION-ONLY
real. A tangent has no Q16.16 form to call. Thatis the one thing this block does NOT inherit from Gear_Train, which stayed genuinely synthesizable -- the difference is the trigonometry, not the family.
Sample results#
| t | in ICoreDouble-Out-0 | in ICoreDouble-Out-0 | out ICoreDouble-Out-0 | out ICoreDouble-Out-1 |
|---|---|---|---|---|
| 0 | -2 | -2 | 0.5068 | -0.2534 |
| 0.4 | 0.5 | 0.5 | 0.1012 | 0.2023 |
| 0.8 | -2 | -2 | 0.5068 | -0.2534 |
| 1.2 | 0.5 | 0.5 | 0.1012 | 0.2023 |
| 1.6 | -2 | -2 | 0.5068 | -0.2534 |
| 2 | 0.5 | 0.5 | 0.1012 | 0.2023 |
| 2.4 | -2 | -2 | 0.5068 | -0.2534 |
| 2.8 | 0.5 | 0.5 | 0.1012 | 0.2023 |
| 3.2 | -2 | -2 | 0.5068 | -0.2534 |
| 3.6 | 0.5 | 0.5 | 0.1012 | 0.2023 |
| 4 | -2 | -2 | 0.5068 | -0.2534 |
| 4.4 | 0.5 | 0.5 | 0.1012 | 0.2023 |
| 4.8 | -2 | -2 | 0.5068 | -0.2534 |
| 5.2 | 0.5 | 0.5 | 0.1012 | 0.2023 |
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.2534 |
ramp | Ramp: slope 1 from t = 0 | 0 … 1.47 |
sine | Sine Wave: amplitude 1, 2 rad/s, no phase, no bias | 0 … 0.2534 |
step | Step: 0 -> 1 at t = 1 s | 0 … 0.2534 |
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 ccf005c8 · produced by docsSample --out <folder> --steps 60 · data docs/generated/samples/Robotics__Planar_Kinematics__Ackermann_Steering_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).