Generated reference › Tacho Pulse RPM — Control Systems/Vibration
kind: generated#block#control-systems-vibration

Tacho Pulse RPM — Control Systems/Vibration

Control_Systems/Vibration/Tacho_Pulse_RPM · 1 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.

Tacho Pulse RPM

Control Systems / Vibration

Reads shaft speed off a tachometer pulse train by timing one pulse to the next:

RPM = 60 ÷ (T·P)

where T is the time between the last two crossings of the reference level in the chosen direction and P is how many such crossings one revolution produces. The reading is republished on the sample that carries a crossing and held until the next one.

Ports

  • u – the raw tachometer signal, scalar. Its shape does not matter – square, trapezoidal, sinusoidal, noisy – only where it crosses Reference Level.
  • RPM – the held speed in revolutions per minute, scalar. Before a second crossing has arrived there is no interval to time, and the port carries Initial RPM.
  • pulse – scalar, 1 on a sample that carries a crossing and 0 on every other sample. It marks the same instants the speed is computed from, so a downstream block can resample on the shaft rather than on the clock.

Parameters

  • Pulses Per Revolution – P, how many crossings of the chosen kind one revolution of the shaft produces. A positive number, whole or not, up to 4096. One for a single index mark; the line count for an encoder. ⚠ With Edge set to both, one cycle of the tacho gives two crossings, so this doubles.
  • Reference Level – the level u must cross for a pulse to be recognised. Any number; it does not have to sit between the signal's own levels, and a level the signal never reaches simply produces no pulses.
  • Edge – which direction counts, as a choice of three:
    • rising – a sample at or above the level whose predecessor was below it. The default, and the only one that is correct on an asymmetric tacho.
    • falling – a sample below the level whose predecessor was at or above it.
    • both – either of the two. It doubles the pulse rate and is only meaningful when the tacho is symmetric – see Notes.
  • Crossing Interpolation – where the crossing is placed within the sample it was detected on:
    • on – linearly between the two samples that straddle the level, which is the convention MATLAB's midcross uses. The default.
    • off – at the sample itself, so every interval is a whole number of sample periods.
  • Maximum RPM – the instrument's range: the reading never exceeds it. A positive number. It also bounds what the block publishes when two crossings land within a sample of one another, which on a noisy signal or with Edge = both happens readily. It is applied as a shortest interval rather than as a ceiling on the quotient – see Code export, where the difference is not cosmetic. ⚠ On the three HDL targets the whole datapath is Q16.16, so a value above 32767 cannot be represented there at all; keep it inside that if the block is going to hardware.
  • Initial RPM – what the RPM port carries until a second crossing has been timed. Any number from zero up to Maximum RPM; zero is a perfectly good "no reading yet". A value above the range is refused rather than clamped – it is a reading like any other, and the range applies to it.
  • 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 parameter is inlined at export time, none is tunable on the generated core: Edge and Crossing Interpolation decide which statements are emitted at all, and the four numeric settings are folded into the constants beside them – 60 ÷ P is one number in the emitted divide. Re-export after changing any of them. The sample period is inlined too, taken from Sampling Time (s), so a core exported at one rate does not measure correctly if it is stepped at another.

The three HDL targets are simulation-only: the reading is a reciprocal of a measured time, and a divide whose divisor ranges over three decades does not belong in a Q16.16 datapath. Their bodies carry the arithmetic in real and quantize only where a value enters or leaves a register, so they simulate correctly and are not offered as synthesizable. Each state word makes that round trip once per sample, and the elapsed-time accumulator is what the residual is made of: one sample period stored as Q16.16 is 0.0099945 s rather than 0.01, so every measured interval is 0.055 % short and every reading that much high, uniformly.

⚠ Maximum RPM is applied to the INTERVAL, not to the quotient, and that is why: written the obvious way round the division happens first, its result is stored in the target's scratch word before any clamp sees it, and in Verilog and SystemVerilog that word is a 32-bit Q16.16 register with no saturation – a reading of 100000 rpm becomes 6.55e9, wraps, and comes back as a small number the clamp passes through. The block divides only by intervals that cannot produce an out-of-range reading, so the value never exists. VHDL would have hidden this: its to_sfixed saturates where Verilog's truncation wraps.

Simulink bridge

None (Support::None). tachorpm is a Signal Processing Toolbox function, that toolbox ships no Simulink library, and a sweep of the fourteen DSP and Simulink libraries on this machine matches nothing named for a tachometer – so there is no path a diagram could name. The bridge reports this block rather than dropping it silently, and it therefore has no parity testbench; code export verification covers it across all ten languages.

Notes

  • Stateful, and discrete by nature (setDiscreteOnlyBlock(true)). The state is the previous input, the time since the last crossing, the held speed, and a flag saying whether a first crossing has been seen.
  • ⚠ This is not tachorpm. That function finds the same pulse instants and then fits a curve through them over the whole record – a smoothing spline by default, a straight line between pulse instants at its other setting. Both read a sample from the pulse that comes after it, which no block can do. This one holds the last completed interval instead, which is a different quantity and not an approximation of that one.
  • ⚠ On a constant-speed tacho the two agree, and that is measured. Over an exact 20 Hz square at fs = 1000, where the true speed is 1200 rpm, this block settles to 1200 within 9.1e−13 and tachorpm at its linear setting to 2.7e−12. They differ only where the speed moves.
  • ⚠ "both" is only meaningful on a symmetric tacho. With both edges the block times the high interval and the low interval in turn, so on anything but a 50 % duty the reading alternates. Measured on a 30 % duty square whose true speed is 1200 rpm: rising-only reads a steady 1200, while both edges at P = 2 alternate between 857.1 and 2000. That is the right arithmetic on the wrong assumption; prefer rising unless the two edges are genuinely evenly spaced.
  • Interpolation trades a bias for a jitter. With it off, an interval is a whole number of samples, so the reading is perfectly steady and systematically wrong whenever the true period is not. Measured on a 13 Hz trapezoidal tacho at fs = 1000, true speed 780 rpm: off, every reading is 779.221 (1/13 s is 76.9 samples and the count can only be 77); on, the median is 780.195 with a spread of 2.24 rpm as the edge noise passes through. On a clean square edge, which lands exactly on a sample, the two are identical.
  • The first reading arrives on the second crossing, not the first: one crossing is an instant, and it takes two to make an interval. Until then the port carries Initial RPM.
  • The block never times out. If the shaft stops, the last reading is held indefinitely rather than decaying to zero – there is no way to tell a stopped shaft from a slow one until the next pulse arrives, and inventing a timeout would put a second, invented time constant in the signal. Compare pulse against a counter if a stall has to be detected.
  • Scalar only. One tachometer channel; wire one block per shaft.
  • No state space. A level crossing is not a linear functional of the input, so there is no A/B/C/D pair and model reduction correctly declines to merge the block.

Code facts#

FactValue
registered typeControl_Systems/Vibration/Tacho_Pulse_RPM
familyControl_Systems/Vibration
solver environment classICoreBlock_0_Control_Systems_1_Vibration_2_Tacho_Pulse_RPM
sourcesrc/ICoreBlocks/ICoreBlockLibrary/Blocks/Control_Systems/Vibration/Tacho_Pulse_RPM/ICoreBlock_0_Control_Systems_1_Vibration_2_Tacho_Pulse_RPM.cpp
headersrc/ICoreBlocks/ICoreBlockLibrary/Blocks/Control_Systems/Vibration/Tacho_Pulse_RPM/ICoreBlock_0_Control_Systems_1_Vibration_2_Tacho_Pulse_RPM.h
default size on canvas150 × 76 px
ports at insert1 in, 2 out
code generators implementedPython, MATLAB, Java, Rust, C, C++, VHDL, Verilog, SystemVerilog, PLC Structured Text

Ports#

#DirectionSignal typeDescription label
1inICoreDoubleu
2outICoreDoubleRPM
3outICoreDoublepulse

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 variableDefaultSimulink parameter
Pulses Per Revolution1—
Reference Level0.5—
Edgerising%~%falling%~%both~~rising—
Crossing Interpolationon%~%off~~on—
Maximum RPM6000—
Initial RPM0—

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.

supportSupport::None
Simulink path—
port-count rulePortsParam::None
SampleTime parameteryes

Caveat (shown to the user): tachorpm is a Signal Processing Toolbox function, not a Simulink library block -- that toolbox ships no Simulink library, and a find_system sweep of the fourteen DSP and Simulink libraries installed here matches nothing named for a tachometer -- so there is no path a diagram could name; the block is reported rather than dropped when a model crosses. It is also not tachorpm's arithmetic: that function fits a curve through the pulse instants over the whole record and this one holds the last completed interval, which is the only form a block can compute

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).

Tacho Pulse RPM -- shaft speed from a tachometer pulse train, measured pulse to pulse RPM = 60 / (interval * PulsesPerRevolution)

interval is the time between the last two crossings of the reference level in the chosen direction. The reading is republished on the sample that carries a crossing and HELD in between, and a second output flags that sample so a downstream block can resample on it.

⚠ THIS IS NOT MATLAB'S tachorpm, AND THE BLOCK IS NAMED SO THAT IT CANNOT BE READ AS ONE. tachorpm finds the same pulse instants and then fits a curve THROUGH them across the whole record -- a B-spline over ten breakpoints at its default FitType "smooth", a straight line between pulse instants at "linear". Both need the pulse that comes AFTER the sample being reported, so neither can be evaluated at sample k from samples up to k. Measured on a clean 20 Hz square tacho at fs = 1000 whose true speed is a constant 1200 rpm, the default fit answers -34605 on its first sample and has a median of 778.8, measured on R2026a here on 2026-09-10. (The name of the session that measured it is on the block board, not on this banner: the banner is rendered onto a PUBLIC page, where a session name is vocabulary this tree invented and nobody else can read -- check_docs.py row D16.)

⚠ AND ON A CONSTANT-SPEED TACHO THE TWO AGREE, which is what makes the departure reviewable rather than merely declared. Over an EXACT 20 Hz square at fs = 1000 (built as mod(k,50) < 25, so no floating-point boundary decides an edge), once two edges have been seen this block reports 1200 rpm to 9.1e-13 and tachorpm at FitType "linear" reports it to 2.7e-12. They part company only where the speed MOVES, and there the difference is exactly the one named above: a hold against an interpolation.

⚠ CROSSING INTERPOLATION IS NOT COSMETIC. Without it the interval is a whole number of samples, so the reading carries a quantization step AND a bias. Measured on a 13 Hz trapezoidal tacho at fs = 1000 (true speed 780 rpm): interpolated, the median reading is 780.195 with a spread of 2.24 rpm; not interpolated, every reading is 779.221 -- a spread of exactly zero and a systematic error of 0.78 rpm, because 1/13 s is 76.9 samples and the count can only be 77. A steadier number that is further from the truth.

⚠ "both" EDGES IS ONLY MEANINGFUL ON A SYMMETRIC TACHO, and the failure is silent. Measured on a 30 % duty square: rising-only reads a steady 1200 rpm, while both edges at PulsesPerRevolution = 2 ALTERNATE between 857.1 and 2000 -- the high interval and the low interval reported in turn. It is the right arithmetic on the wrong assumption, and it is in the description where a user reads it.

Sample results#

Tacho Pulse RPM — Step: 0 -> 1 at t = 1 sTacho Pulse RPM — Step: 0 -> 1 at t = 1 s00.51012345t (s)in ICoreDouble-Out-0out ICoreDouble-Out-0out ICoreDouble-Out-1

The same rig also ran:

StimulusWhat it isOutput range
impulseImpulse: one sample of 1 at k = 5, 0 elsewhere (Repeating Sequence Stair)0 … 0
rampRamp: slope 1 from t = 00 … 0
sineSine Wave: amplitude 1, 2 rad/s, no phase, no bias0 … 19.11
tableRepeating Sequence Stair: [-2 -1 -0.5 0 0.5 1 2 3], one entry per sample0 … 75

Plotted: step — Step: 0 -> 1 at t = 1 s

Category dynamic · sample time 0.1 · 60 steps · commit 08a7459c5c0c12af78a93185a873134958b5f10e · produced by docsSample --out <folder> --blocks Tacho_Pulse_RPM --steps 60 · data docs/generated/samples/Control_Systems__Vibration__Tacho_Pulse_RPM.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).