RPM Tracker — Control Systems/Vibration
Control_Systems/Vibration/RPM_Tracker · 1 input / 3 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.
RPM Tracker
Control Systems / Vibration
The shaft speed of a machine, read out of the machine's own vibration – no tachometer. A rotating order writes itself across a time-frequency map as a ridge at f = order · rpm / 60, so following that ridge from frame to frame is the speed measurement.
This is the coarse ridge of MATLAB's rpmtrack, transcribed from
R2026a's signal.internal.rpmtrack.computeRPM. Every hop
samples the last Window Length samples are windowed and transformed, and
the ridge takes one step: from the previous two rows r₁ and
r₂, the search band is
[r₁+s−3 … r₁+s+3] with s = r₁−r₂
the slope, and the row taken is the one minimising
S[i] + λ·(i − 2r₁ + r₂)²
where S is the cost map −10·log₁₀(col/Σ + eps)
and λ is Frequency Penalty scaled by half the bin width, exactly
as rpmtrack scales it. When the power penalty is finite the band
then grows one row at a time until the chosen row's power is within
Power Penalty decibels of the previous ridge's.
Ports
- u – the vibration signal, scalar. One block per channel.
- rpm – the tracked speed in revolutions per minute.
- f – the ridge frequency in hertz, (row − 1) · fs/L.
- row – the 1-based bin the ridge sits in, which is a discrete answer: the smallest speed change this block can report is one bin, 60·fs/(L·order) rpm.
Parameters
- Sampling Rate (Hz) – the rate the signal was sampled at; it sets the frequency axis and nothing else.
- Window Length – L, the frame the transform runs over. The bin width is fs/L, so this is the resolution of every speed this block can report.
- Window – Hann, Hamming, Flat Top or Rectangular, periodic sampling (the one to take a spectrum with).
- Overlap (%) – how much of each frame the next one repeats; the hop is L − round(overlap·L/100), at least 1.
- Order – the shaft order the ridge represents. 1 is the shaft rate itself; 2 is the second harmonic, and the rpm it reports is half the frequency's.
- Initial Frequency (Hz) – the seed, and it is the one parameter this block cannot guess. The walk opens on the bin nearest this frequency and never searches more than three bins per frame, so a seed on the wrong ridge tracks the wrong ridge for the whole run.
- Frequency Penalty – λ before scaling: the price of curvature. Larger keeps the ridge straighter and less willing to follow a jump.
- Power Penalty (dB) – how far the ridge's power may move between
frames before the band widens to look further. ⚠ A value of zero or less
means INFINITE – no power test at all, which is
rpmtrack's own default, and it is also what switches the slope extrapolation on. - Sampling Time (s) – zero or less inherits the solver's rate; a positive value runs the block at that period. ⚠ It is not the same number as Sampling Rate (Hz): this one is how often the block is called, that one is what the frequency axis is read in. Setting one does not change the other.
Code export
All ten targets: Python, MATLAB, Java, Rust,
C, C++, VHDL, Verilog, SystemVerilog and
PLC Structured Text, from one statement list, so every target runs the
same arithmetic in the same order as the simulation. The window taps and the
bin spacing are numbers at export time; the transform, the two maps and the
ridge run as loops. The three HDL targets carry it in
simulation-only real arithmetic, quantized at the port.
Simulink bridge
None. rpmtrack is a Signal Processing Toolbox function
and that toolbox ships no Simulink library at all, so there is no path a
diagram could name.
Notes
- ⚠ WHAT REPLACES rpmtrack's RIDGE POINTS.
rpmtrackis handed (time, frequency) pairs on the record's absolute time axis and walks outward from each in both directions. A stream has neither an absolute time axis nor a future, so this block takes one seed and walks forward only. Given the same columns and the same first row, every row after it isrpmtrack's own. - ⚠ THE NORMALIZING SUM CANNOT CHANGE THE ANSWER, and that is measured.
rpmtrackdivides the map by the sum over the whole observation window before the log, which a stream cannot do without holding the window. It does not have to: dividing by a positive T shifts every row of one column by the same +10·log₁₀(T), and every comparison the step makes is between two rows of the same column, so the shift cancels. Checked by adding a random constant to the cost map on the 600 reference cases below – the ridge moved on 0 of 600. This block therefore normalizes by the current column's sum and keeps O(M) state instead of O(W·M). - ⚠ THE FIRST TWO FRAMES ARE DIFFERENT STEPS, because they are in
rpmtrack: frame 1 is the plain minimum of S within ±3 rows of the seed, frame 2 adds the frequency penalty but no power test, and the general step starts at frame 3. - ⚠ VERIFIED AGAINST MATLAB'S OWN STEP. 600 random cases spanning 8 to
40 rows, both penalty regimes, and an infinite power penalty on 179 of them,
generated by calling the R2026a local functions directly: 600 of 600
identical, with the band-widening loop firing on 276 of them over 5827
iterations. ⚠ One defect this gate caught that reading could not: MATLAB builds
its band as
max(1,c−3):min(c+3,M−1), which is EMPTY when the slope throws the centre clear of the map – and that empty range is exactly what triggers its fallback to r₁±3 with the slope off. Clamping both ends into [1, M−1] instead makes the empty case unreachable and silently searches a single row. It cost 27 of the 600, every one a large slope at an infinite power penalty. - Stateful, and discrete by nature
(
setDiscreteOnlyBlock(true)): a shift register of L samples, a frame counter, and the ridge's own memory. No state space – a ridge index is not linear in the window. - Not the whole of
rpmtrack. That function refines this ridge with a Vold-Kalman order waveform and a second transform. Both of those are blocks in this library already – Order Waveform and Time-Frequency Ridges – so the refinement is a diagram, not a missing feature.
Code facts#
| Fact | Value |
|---|---|
| registered type | Control_Systems/Vibration/RPM_Tracker |
| family | Control_Systems/Vibration |
| solver environment class | ICoreBlock_0_Control_Systems_1_Vibration_2_RPM_Tracker |
| source | src/ICoreBlocks/ICoreBlockLibrary/Blocks/Control_Systems/Vibration/RPM_Tracker/ICoreBlock_0_Control_Systems_1_Vibration_2_RPM_Tracker.cpp |
| header | src/ICoreBlocks/ICoreBlockLibrary/Blocks/Control_Systems/Vibration/RPM_Tracker/ICoreBlock_0_Control_Systems_1_Vibration_2_RPM_Tracker.h |
| default size on canvas | 150 × 90 px |
| ports at insert | 1 in, 3 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 | u |
| 2 | out | ICoreDouble | rpm |
| 3 | out | ICoreDouble | f |
| 4 | out | ICoreDouble | row |
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 |
|---|---|---|
Sampling Rate (Hz) | 1000 | — |
Window Length | 64 | — |
Window | Hann%~%Hamming%~%Flat Top%~%Rectangular~~Hann | — |
Overlap (%) | 50 | — |
Order | 1 | — |
Initial Frequency (Hz) | 125 | — |
Frequency Penalty | 1 | — |
Power Penalty (dB) | 0 | — |
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): rpmtrack is a Signal Processing Toolbox function, not a Simulink library block -- that toolbox ships no Simulink library at all -- so there is no path a diagram could name; the block is reported rather than dropped when a model crosses
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).
RPM Tracker -- the shaft speed read out of a machine's own vibration The coarse ridge of MATLAB's rpmtrack, one frame at a time. The arithmetic, the transcription and every measured number are in ../ICoreRpmTrackSupport.h; this file is the block around it.
Sample results#
| t | in ICoreDouble-Out-0 | out ICoreDouble-Out-0 | out ICoreDouble-Out-1 | out ICoreDouble-Out-2 |
|---|---|---|---|---|
| 0 | -2 | 0 | 0 | 0 |
| 0.4 | 0.5 | 0 | 0 | 0 |
| 0.8 | -2 | 0 | 0 | 0 |
| 1.2 | 0.5 | 0 | 0 | 0 |
| 1.6 | -2 | 0 | 0 | 0 |
| 2 | 0.5 | 0 | 0 | 0 |
| 2.4 | -2 | 0 | 0 | 0 |
| 2.8 | 0.5 | 0 | 0 | 0 |
| 3.2 | -2 | 0 | 0 | 0 |
| 3.6 | 0.5 | 0 | 0 | 0 |
| 4 | -2 | 0 | 0 | 0 |
| 4.4 | 0.5 | 0 | 0 | 0 |
| 4.8 | -2 | 0 | 0 | 0 |
| 5.2 | 0.5 | 0 | 0 | 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 0ed2e91b9 · produced by docsSample --out <folder> --blocks Control_Systems/Vibration/RPM_Tracker --steps 60 · data docs/generated/samples/Control_Systems__Vibration__RPM_Tracker.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).