Timing, Memory and Bus Budget

Everything can-lite consumes is fixed at build time or chosen at construction. This chapter collects the arithmetic: which timers exist, how the tunable parameters constrain each other, what the static memory is spent on, and how much of the bus a traffic pattern uses.

The parameters themselves and what each one means are documented with the components that own them — see Architecture and Design Decisions §9 and the Protocol Specification §12.

1. Timer inventory

Owner Timer Kind Restarted by On expiry
Server heartbeat single-shot every outgoing frame from this node send a heartbeat
Server rate window repeating itself reset the accepted-frame count
Server client liveness single-shot every frame addressed to this node report the client offline
Client heartbeat single-shot every outgoing frame from this node broadcast a heartbeat
Client server liveness, one per slot single-shot × 8 every frame from that server report that server offline
Client acknowledgement, one per slot single-shot × 8 committing a command to that server report the command unanswered
Firmware upgrade session single-shot the commands that make progress report the session expired
Segmentation sender N_Bs single-shot sending a frame that awaits flow control abort the transfer
Segmentation sender separation single-shot each frame, when the peer asks for pacing send the next frame
Segmentation receiver N_Cr single-shot each accepted frame abort the reassembly

A fully loaded client — eight servers tracked, eight commands outstanding — holds seventeen live timers. A server holds three, plus one per firmware upgrade category and two per open segmentation channel. All of them are members; none is allocated.

Exactly one timer in the library is periodic. An idle server has two timers armed, an idle client one; everything else is dormant until something happens.

2. How the parameters constrain each other

The defaults work together; changing one in isolation is where trouble starts.

Relationship Why
Liveness timeout at least three heartbeat intervals A timeout must survive two lost heartbeats, or an idle bus flaps between online and offline (Chapter 10, §10.4)
Acknowledgement timeout above the worst-case handler latency It must cover asynchronous work the observer defers — a flash write, a conversion — plus the queueing delay of a busy bus
Rate limit at twice the peak rate The window is tumbling, so a burst straddling a reset delivers up to double (Chapter 10, §7.2)
Session timeout above the client’s own block cadence Usually dominated by the client’s flash read or network fetch, not by the bus

The limits that are not tunable, because they are compile-time properties: registered categories per node, tracked servers per client, outbound queue depth, segmentation channel ceiling, and the two segmentation timeouts and wait limit. Raising any of them is a library change, and each has its own entry in Chapter 10.

3. Static memory

can-lite allocates nothing, so its footprint is the sum of its members. The counts are fixed by design; the per-element sizes depend on the toolchain, so the honest way to get exact numbers is to ask the compiler for the size of the composed objects on the target.

What the structure guarantees, independent of toolchain:

Component Fixed-count storage
Frame transport eight queued frames — identifier, payload and a completion each — plus two callback slots
Server one transport, three timers, the system category, a handful of counters and flags
Client one transport, one timer, the system category, eight sequence slots and eight liveness slots, each with a timer
Segmentation sender and receiver one payload-sized buffer each, one or two timers, a few bytes of state
Segmentation channel one sender, one receiver, three callback slots
Segmentation transport its channels, a pointer array and two callback slots
Each category its own members, plus one binding per message type

The dominant term is almost always segmentation:

segmentation bytes ≈ channels × (2 × maximum payload + per-channel overhead)

Four channels of one kibibyte is therefore about 8 KiB of buffers, and two channels of 128 bytes about 512 bytes — and the arithmetic is deliberately that obvious. This is the single most consequential memory decision an integrator makes, and it is a hard ceiling rather than a performance knob: a payload larger than the configured size is refused at both ends (Chapter 10, §8.1).

Stack use is bounded by the deepest call chain — receive, segmentation, category, observer, send. Nothing recurses except queue drain, and only if a driver breaks the asynchronous-completion assumption (Chapter 10, §1.7).

4. Bus arithmetic

An extended CAN frame costs 67 bits of framing plus 8 bits per payload byte, before bit stuffing; worst-case stuffing adds roughly a quarter of the stuffable field.

Frame Payload bytes Nominal bits Worst case
Heartbeat 1 75 ≈ 91
Acknowledgement 4 99 ≈ 121
Typical validated command 7 123 ≈ 151
Full frame 8 131 ≈ 160

Frame time for a full frame (nominal / worst case):

Bitrate Bit time Full frame
125 kbit/s 8 µs 1.05 ms / 1.28 ms
250 kbit/s 4 µs 524 µs / 640 µs
500 kbit/s 2 µs 262 µs / 320 µs
1 Mbit/s 1 µs 131 µs / 160 µs

What a rate limit means in bus load. Five hundred full frames per second at 500 kbit/s is about 131 ms of bus time — roughly 13 %, leaving ample room for other servers. The same limit at 125 kbit/s is over half the bus, and is certainly too high: the limiter protects a server’s processing budget, but at low bitrates the bus is the scarcer resource.

What a command exchange costs. A validated command with a response is three frames — command, response, acknowledgement — about 750 µs of bus time at 500 kbit/s.

Firmware upgrade throughput. Three frames carry six payload bytes, so the line-rate bound is roughly 9 KiB/s at 500 kbit/s and 19 KiB/s at 1 Mbit/s. The bound that actually applies is different:

Bound 500 kbit/s 1 Mbit/s
Line rate only ≈ 9.3 KiB/s ≈ 18.7 KiB/s
With a 1 ms round trip, stop-and-wait ≈ 5.4 KiB/s ≈ 5.7 KiB/s

The second row is the real one, and the reason is in Chapter 9, §3: the transfer waits for each block to be acknowledged, so round-trip latency sets the throughput. A 256 KiB image takes about 48 seconds — a service-window operation, not a boot-time one.

Segmented transfer. A payload of n bytes costs one first frame plus ⌈(n − 6) / 7⌉ continuation frames and one flow-control frame. A kibibyte is 147 frames, about 39 ms at 500 kbit/s — some forty times more efficient per payload byte than the same data sent as firmware blocks, because there is no per-block turnaround.

5. Processing cost

Operation Cost
Identifier decode a few shifts and masks, evaluable at compile time
Category lookup linear over at most eight entries
Message-type lookup linear over the category’s bindings, typically two to six
Sequence validation one increment, one comparison
Rate limiting one comparison, one increment
Payload access a bounds check and byte moves; no allocation
Segmentation channel lookup linear over at most sixteen channels

The fixed part of the receive path is well under a microsecond on a hundred-megahertz core — one to two orders of magnitude below the frame time even at 1 Mbit/s. The bus, not the CPU, is the constraint.

The caveat is that all of it runs inside the driver’s receive callback, so a handler doing real work there delays the next frame. That is the mechanical reason behind the rule that observers neither allocate nor block, and that long work is deferred behind a completion.

6. A worked budget

A four-axis machine: one controller and four drives at 500 kbit/s, each drive receiving 200 set-points per second and returning telemetry at 100 Hz.

Traffic Frames/s Bits/s Bus load
Set-points, 7 bytes 800 98 400 19.7 %
Acknowledgements, 4 bytes 800 79 200 15.8 %
Telemetry, 8 bytes 400 52 400 10.5 %
Heartbeats ≈ 0 ≈ 0 ≈ 0 %
Total 2000 230 000 46 %

Four observations worth carrying into a real design: