Everything the device computes runs from one loop, on two clocks: an evaluation pass every 10 ms (100 Hz), and frame arrival, which triggers extra work the moment a configured message is received. This page discloses the exact order of both, and how often everything else on the unit runs — so when one channel reads another you can say precisely how old the value is, and when a message transmits you can say precisely when.
Nothing here is configurable; it is how the firmware is built. The numbers apply to the whole configuration regardless of size — the pass runs the full table of every calculation type, active rows in row order, on every pass.
Each pass executes these stages, always in this order:
| # | Stage | What happens |
|---|---|---|
| 1 | Device channels | Device OnTime, the per-bus CAN diagnostics, Bus Load and the MCU health block are written first, so everything downstream reads this pass's values. |
| 2 | Receive timeouts | Any receive message past its timeout has its channels set to their default values (where the section asks for it). |
| 3 | Constants | Fixed values are written to their channels. |
| 4 | Lookup tables | 2×16 tables, then 8×8 tables. |
| 5 | Math channels | All rows, in row order. |
| 6 | Up/Down counters | Edge detection and rate steps. |
| 7 | Timers | Advance by the real elapsed time. |
| 8 | Integrators | Accumulate by the real elapsed time. |
| 9 | User Conditions | All comparisons, in row order — after everything above, so a condition on a math or table result sees the value computed this pass. Each condition's mode is applied here too: a Set/Reset latch is set, reset or held, and a Momentary's hold is spent against the elapsed time. |
| 10 | Device script | The
script's on_tick runs last of the calculation stages, within its
per-pass budget, so it sees everything the pass produced and its outputs still
reach the transmit stage below. |
| 11 | Transmit | Every transmit message whose period has elapsed is composed from the channel values as they stand now — the freshest the pass can offer — and queued to its bus. Composition packs every transmitted channel (and a compound message's identifier selector) into the frame first; for a Transmit CRC8 message the checksum is then computed over its configured elements, stamped into its byte last — after every other byte of the frame is final — and published to its CRC channel. |
Note: The condition stage is the one calculation stage whose output does not follow from this pass's channel values alone. A User Condition carries memory: its latched output, the previous pass's Set expression for edge detection, and a Momentary's remaining hold. It used to be pure — same inputs, same output, every pass — and that purity is what let it run from the receive path as freely as from the tick. It still runs from both, but the receive path is told that no time has passed, so a frame can move a latch and cannot shorten a hold. None of that state is persisted: a power cycle, and a configuration being sent, re-arm every condition.
Around the pass, on the same 100 Hz beat: the CAN error state of all three buses is sampled just before it (feeding the diagnostics the pass publishes in stage 1), and after it the Monitor Channels value stream sends its slice of the channel table (see the rate table below).
Received frames are not held for the next pass — each one is processed on arrival, in this order:
- Message relays — every relay rule is checked against every received frame, matched or not, and forwards immediately.
- Message match — the first active receive message on that bus with that CAN ID (and matching standard/extended type) wins; the message's timeout window restarts.
- Channel decode — the matched section's channels are extracted and scaled into their values.
- Recalculation — constants, lookup tables, math and User Conditions re-run (the same stages 3–5 and 9 above, in the same order), so anything derived from the received channels updates immediately rather than up to 10 ms later. A was received comparison naming this message is true on exactly this evaluation, which is why no received frame can be missed however fast they arrive.
- Routing — if the section routes to other buses, the frame is forwarded last, after the recalculation.
Counters, timers and integrators do not run here — they advance on the 10 ms clock only, because they measure time. A Momentary condition's hold measures time in the same way and is likewise spent on the pass alone: the recalculation above is told that no time has passed, so a burst of frames cannot cut a pulse short. The device script's on_tick does not run here either: it is a per-pass hook with a per-pass budget, not a per-frame one. Both pick up the received values on the next pass, at most 10 ms later.
Within a pass, a reference to something computed at an earlier stage (or an earlier row of the same stage) reads the value from this pass. A reference to something computed later reads the value from the previous pass — 10 ms old. The order above is what "earlier" and "later" mean. The cases that matter in practice:
| Reference | What it reads |
|---|---|
| Math ← constant, table | This pass |
| User Condition ← math, table, counter, timer, integrator | This pass |
| Table axis ← math channel | Previous pass (tables run before math) |
| Math ← User Condition | Previous pass |
| Counter / timer / integrator input ← User Condition | Previous pass |
| Anything ← script-written channel | Previous pass (except the transmit stage, which runs after the script) |
| Transmit message ← anything | This pass |
Note: A chain that spans several "backwards" references accumulates one pass of lag per hop. A condition gating a counter whose output feeds a math row read by a table is correct — it just settles over a few passes rather than one. At 100 Hz that is tens of milliseconds, which rarely matters; it only surprises when you expect a multi-stage chain to react within a single pass.
Each transmit message runs on its own period — the section's Transmit Rate, 1 to 200 Hz, so periods from 1000 ms down to 5 ms. The transmit scheduler runs on its own 5 ms slots, twice as fast as the evaluation pass, and that is the scheduling resolution: a period is always a whole number of slots. Transmit CRC8 messages schedule identically — the checksum is computed for every frame, whatever the rate.
Note: What a 5 ms message carries: received channels are updated the moment their frame arrives, so a fast gateway message forwards fresh data on every transmission. Calculated channels (math, counters, timers, tables, scripts) update on the 10 ms evaluation pass, so a 200 Hz message repeats each calculated value at most once — the calculation chain deliberately does not speed up with the scheduler.
- Messages are phase-spread per bus. When a configuration is loaded, the transmit messages of each bus are offset within their periods so they come due spread across the period rather than all on the same pass. Ten 10 Hz messages on one bus transmit one per 10 ms across their 100 ms period, not ten back-to-back every 100 ms.
- Section order is the tie-break. Messages sharing a bus and a rate are offset in list order, so they go out in the order the Communications list shows — list order = transmit order.
- An oversubscribed bus degrades fairly. If more comes due than the bus can carry, the scan resumes each pass where the queue filled, so the shortfall is shared across the whole table instead of silencing the messages at the end of the list.
- A missed cycle is dropped, not banked. When a transmission cannot be sent (bus off, queue full), the message does not accumulate a backlog; the next cycle sends the current channel values. Cyclic data carries the freshest value or nothing.
- A transmission is recorded for the next pass. The 5 ms scheduler does not evaluate conditions, so a was transmitted comparison sees the frame on the following calculation pass — up to 10 ms later — and any transmissions of one message inside that window collapse into a single event. A 5 ms message manages two per window, and a Batch compound message emits every identifier in one service. Receive is not like this: a was received comparison is evaluated by the frame's own pass and sees every frame. See the message operators.
- A Triggered message keeps its own phase. Its User Condition is tested in the 5 ms slot whatever the message's rate is, so the trigger is acted on within 5 ms — and the period is then counted from the moment the condition became true rather than from the phase the message was given at load. See Triggered transmit.
| Operation | Rate / latency |
|---|---|
| Evaluation pass (stages above) | Every 10 ms (100 Hz) |
| Constants, tables, math, User Conditions | Every pass, plus immediately on every matched received frame |
| Counters, timers, integrators | Every pass only |
| A Momentary condition's hold | Spent every pass only — a received frame does not advance it |
| Message events for a condition's was received / was transmitted | A receive is seen by the evaluation the frame itself triggers; a transmit by the next pass, at most 10 ms later |
Device script on_tick | Every pass, budgeted (see Device Scripts) |
| Message relays, routing | Immediately, per received frame |
| Transmit scheduling | Checked every 5 ms (its own 200 Hz clock); each message at its configured 1–200 Hz |
| Transmit CRC8 stamp & publish | With every transmission of its message, during composition — channels packed first, checksum stamped last |
| Receive timeout check | Every pass |
| Device channels (OnTime, diagnostics, MCU health) | Republished every pass |
| CAN error-state sampling | Every pass, just before it |
| Bus Load | Recomputed over a 1 s window |
| MCU health sampling (temperature, VDDA) | Sampled at 10 Hz; each pass republishes the latest sample |
| Reset reason | Read once at boot and latched |
| Bus-off recovery | Bus stopped on bus-off, restart attempted every 1 s until it stays up |
| Monitor Channels (value stream) | Up to 160 channels per pass, round-robin — a full refresh of every channel takes one pass for up to 160 channels, and at most 70 ms at the device's 1000-channel limit |
| CAN Viewer (monitor stream) | Per frame, as sent or received |
| Preserved values written to flash | Once a minute, only when changed (see Integrators) |
Note: The pass is driven by real elapsed time, not by counting visits. If something stalls the loop — a flash erase during Save to Flash is the realistic case — the next pass is told how much time actually passed: timers and integrators advance by the true amount, and transmit periods stretch rather than burst-transmitting a backlog afterwards.
Math Channels · User Conditions · Messages & Sections · Message Relays · Device Scripts · Monitoring Live Values · Channels