Skip to content

CAN transport ​

Below the commands sits one exchange pattern: a request goes out on the MUXEN CAN bus addressed to one device, and one answer comes back. This chapter describes how that exchange is framed, in case you are reading a candump capture, writing a device firmware, or trying to work out why a bus behaves the way it does.

Nothing here is needed to use muxen-uds.

Addressing on the wire ​

MUXEN uses 29-bit (extended) CAN identifiers. A UDS exchange uses two of them, one per direction:

request  (client → device):  0x01000000 | (deviceId << 12) | clientId
response (device → client):  0x01000000 | (clientId << 12) | deviceId

Both device and client addresses are 12 bits. The client address defaults to 0x27F and is settable with -c/--client-id.

For device 0x280 (640) and the default client, that is request id 0x01280 27F and response id 0x0127F 280, both with the extended flag set. A receiver filters on the response id with the mask 0x03FFFFFF.

Three broadcast frames are used outside the UDS pair:

PurposeIdentifierPayload
UID request0x00F00FFF, RTR, DLC 0—
UID answer (from devices)0x00F00nnn, matched with mask 0x03FFF0008 bytes of UID
Enable UDS by UID0x00F01000 | deviceIdthe target's 8-byte UID
Communication offbroadcast, sent before and during a firmware transferencoded by the shared MUXEN library

A UID answer shorter than 8 bytes is discarded rather than padded — a truncated frame would otherwise contribute whatever followed it in the buffer as UID bytes, and that UID would end up in the scan output and in deployed.json.

ISO-TP ​

Requests and responses larger than a single CAN frame are segmented with ISO-TP (ISO 15765-2): a Single Frame when the payload fits, or a First Frame / Flow Control / Consecutive Frame sequence when it does not.

The source carries two implementations:

PathAvailabilityNotes
In-processalwaysa state machine on a raw CAN socket, driven by a GLib main loop
Kernel can_isotponly when the build defines HAVE_KERNEL_ISOTPone SOCK_DGRAM/CAN_ISOTP socket per exchange, non-blocking, poll() with the command's timeout

The kernel path is not compiled in to the shipped build, so in the packaged muxen-uds the in-process implementation is the one that runs, whether or not the kernel provides can_isotp. The runtime probe for kernel support is still performed; it simply falls through.

Both request a separation time (STmin) of 10 ms between consecutive frames from the sender, and the in-process path advertises a block size of 0 — send everything, no intermediate flow control.

The in-process path is defensive about malformed input: a frame with a DLC of 0 or above 8 is ignored, a Single Frame claiming more bytes than it carries is ignored, a Consecutive Frame out of sequence aborts the transfer with an overflow flow-control frame, and a payload larger than the receive buffer is refused rather than truncated.

The receive buffer is 5000 bytes, which bounds any single UDS response and any routine --payload.

Timeouts and retries ​

OperationTimeoutAttempts
Most commands2000 ms3
Most commands, --speedy500 ms1
writeconfig — table read2000 ms3 (1 with --speedy)
writeconfig — the write itself2000 ms3
firmware — download request, transfer exit30000 ms5
firmware — a transfer block1500 ms10
firmware — the last transfer block5000 ms10

Retries are 200 ms apart on the firmware path. Re-sending a request is safe: UDS is request/response, and re-sending the same transfer block sequence is the standard ISO 14229 recovery for a lost frame.

The scans have their own windows: scan listens for --timeout seconds; uid collects for 4000 ms while re-broadcasting the request every 1000 ms (2000/500 with --speedy).

Why answers are matched by identifier ​

A fresh ISO-TP socket is opened per exchange. An answer that arrives after its own request timed out is therefore delivered on the socket of the next request and reads as a perfectly valid response to it.

Service 0x22 (ReadDataByIdentifier) echoes the requested identifier in its positive response, and that echo is the only thing pairing an answer with a request. muxen-uds checks it: an answer carrying another identifier is dropped and the request re-sent, never handed back as the value of the one that was asked for.

That check is what stands between a busy bus and a configuration dump in which three parameters all claim to be #000 ProductId — and which would then be restored, or deployed, as if it were correct.

The services used ​

ServiceCodeCommand
ECU Reset0x11reset (hard reset, with a 16-bit delay in milliseconds)
Read Data By Identifier0x22readconfig and every command that reads a table
Write Data By Identifier0x2Ewriteconfig
Routine Control0x31locate, factory, routine
Request Download0x34firmware, announcing address and size
Transfer Data0x36firmware, one block per SREC S3 record
Transfer Exit0x37firmware

Routine control actions are 1 start, 2 stop, 3 request results. The vendor routine identifiers muxen-uds uses:

RoutineIdUsed by
Reset parameters0x0001factory --reset-device-id, --reset-device-configuration
Locate0x0002locate
Allocate configuration0x0004factory --allocate-device-configuration

factory encodes its choice as a 16-bit flag word appended to the routine request: bit 0 resets the CAN address, 0xFFFE resets every other configuration section.

A negative response is a frame beginning with 0x7F; its third byte is the error code. readconfig treats a negative response to identifier n as "the table ends here" rather than as a failure, which is how it knows where to stop.

Reading a parameter answer ​

A positive 0x22 response carries, after the 0x62 service echo and the 16-bit identifier:

<name>\n <typeSize> <type> <value…>

The name is terminated by a line feed (0x0A), type is 0 signed, 1 unsigned, 2 string, and typeSize is the width in bytes — the length of the string for a string. A name longer than 256 characters makes the whole answer be refused rather than truncated, because a partial name could collide with another parameter.

Integration of multiplexed solutions
MUXEN and the MUXEN logo are trademarks of MUXEN SAS.