ASAM XCP on CAN: Building an Online Calibration and DAQ Master
From A2L / ELF address resolution and XCP session bring-up to calibration page switching, dynamic DAQ lists, and RAM-to-ROM Intel HEX export — a practical anatomy of an XCP-on-CAN master in the ECU calibration workflow.
What is XCP
XCP (Universal Measurement and Calibration Protocol), defined by ASAM MCD-1 XCP, is the calibration and measurement protocol used to read, write, and periodically sample memory variables inside an ECU during development, validation, and end-of-line tuning. XCP deliberately decouples the protocol layer from the physical layer: command logic, calibration page switching, DAQ lists, and address resolution all live above a transport that may be CAN, CAN-FD, Ethernet, USB, or FlexRay.
In practice the most common deployment remains XCP on CAN — CAN is ubiquitous in automotive benches, frame structure is stable, and most production ECUs already reserve a small XCP slave footprint. This article walks through the implementation path of an XCP-on-CAN master using KopherBit’s KITE Calibrator as a concrete reference: from A2L / ELF parsing, session bring-up, and calibration writes, to dynamic DAQ measurement and RAM-to-ROM Intel HEX export.
XCP-on-CAN packet and frame structure
XCP messages fall into two classes: CTO (Command Transfer Object) and DTO (Data Transfer Object).
- CTO carries master-to-slave
CMDand slave-to-masterRES/ERR/EV/SERVpackets. Connect, upload / download, calibration writes, and DAQ configuration are all CTO traffic. - DTO carries measurement data. Each DTO contains one or more ODT (Object Descriptor Table) chunks; the PID (Packet Identifier) byte selects which ODT layout the slave is using to pack the measurement variables it is sending.
Over CAN, XCP typically uses one CAN ID pair: CMD_ID for master-to-slave and RES_ID for slave-to-master. Classic CAN payloads are capped at 8 bytes, which is why XCP MAX_CTO / MAX_DTO are commonly set to 0x08 in CAN-only configurations. CAN-FD raises the ceiling to 64 bytes, but MAX_DLC must be agreed between master and slave.
Resolving characteristic / measurement addresses from A2L + ELF
A2L (ASAM MCD-2 MC) is the dictionary of an ECU’s internal variables: every CHARACTERISTIC (calibration parameter) and MEASUREMENT (measurable signal) carries a name, data type, conversion rule, unit, and address field. The address in A2L is, however, often the link-time symbol address — to obtain the actual post-link load address you also need the matching ELF / AXF / OUT and a symbol table lookup.
KITE Calibrator ships an in-tree a2l-parser and elf-parser. The parse_a2l_file and parse_elf_file Tauri commands cross-merge their outputs into an in-memory model with fully resolved addresses, which is then consumed by the editors (MapEditorPanel, CurveEditorPanel) and by the DAQ signal tree (DaqConfigPanel).
Bringing up an XCP session: connect → CAL_PAGE → DAQ
A typical XCP session starts like this:
master -> slave : FF 00 ; CONNECT
slave -> master: FF <RESOURCE> <COMM_MODE> <MAX_CTO> <MAX_DTO> ...
master -> slave : FB ; GET_COMM_MODE_INFO
master -> slave : FA <SEG> <PAGE> <MODE> ; SET_CAL_PAGE -> switch to RAM page
master -> slave : FE ; GET_CAL_PAGE to verify
The RESOURCE byte in the CONNECT response tells the master which subsets the slave supports: CAL_PAG, DAQ, PGM, and so on. Everything downstream must respect that capability declaration. During calibration the master usually switches the ECU to a RAM page (SET_CAL_PAGE MODE = ECU=RAM, XCP=RAM) so that writes take effect on working memory without touching ROM.
Calibration writes and RAM-to-ROM Intel HEX export
Calibration writes are performed with SET_MTA (set memory transfer address) followed by DOWNLOAD. Scalars are a single write; for CURVE / MAP / VAL_BLK regions the master chops the payload into MAX_CTO - 2 byte chunks and issues successive DOWNLOAD / DOWNLOAD_NEXT commands.
After every write KITE Calibrator immediately issues SHORT_UPLOAD and reads the bytes back from the same address to compare against what was just written. This verification readback may look pedantic, but it eliminates the “UI says updated, ECU never accepted it” failure mode and surfaces address misalignment or page-switch mistakes at the earliest moment.
When tuning is done, save_cal_hex uploads each configured calibration segment from RAM, re-maps the RAM source addresses back to their original flash (ROM) destinations, and writes out an Intel HEX file with :02 0000 04 extended linear address records. KITE Reflasher or the production flash tool consumes this HEX directly.
Configuring a dynamic DAQ list
DAQ is XCP’s principal advantage over polling: instead of the master asking for each value, the slave packs configured signals into ODTs and pushes them on its own event ticks (e.g. a 10 ms timer or a CAN RX interrupt). The result is low latency and stable sample cadence.
A dynamic DAQ setup runs as follows:
master -> slave : D5 <COUNT> ; FREE_DAQ
master -> slave : D4 <DAQ_COUNT> ; ALLOC_DAQ
master -> slave : D3 <DAQ> <ODT_COUNT> ; ALLOC_ODT
master -> slave : D2 <DAQ> <ODT> <ENTRY_COUNT> ; ALLOC_ODT_ENTRY
master -> slave : E2 <DAQ> <ODT> <ENTRY> <BIT> <SIZE> <ADDR_EXT> <ADDR>
; WRITE_DAQ
master -> slave : E0 <MODE> <DAQ> <EVENT> <PRESCALER> <PRIORITY>
; SET_DAQ_LIST_MODE
master -> slave : DE 02 <DAQ> ; START_STOP_DAQ_LIST = START
KITE Calibrator’s xcp_master.rs drives a daq_decoder that takes incoming DTO frames, walks each ODT entry against the A2L conversion rules to recover the physical value, and emits a Tauri event xcp-measurement with {timestamp, values} to the frontend, where MeasurementLogPanel and WebGLChart render live signals.
Implementation in KITE Calibrator
All CAN I/O in KITE Calibrator funnels through the shared kdp-network-driver crate, with kvaser, pcan, vector, and zlg build features enabled. The frontend HardwareConfigPanel calls scan_can_devices to enumerate the host’s CAN hardware; the user picks vendor / device / channel / baud and only then does start_xcp_server bind that channel to the XCP master.
Two engineering benefits fall out of this layout: (1) the same tool follows the engineer across Kvaser, PCAN, Vector, and ZLG hardware without code or workflow changes; (2) the protocol stack stays portable, instead of being soldered to one vendor’s SDK and one bench’s wiring.
Common pitfalls
Verification readback keeps failing. First check that the calibration page is actually switched to RAM; many ECUs silently drop writes targeted at the ROM page. Next check that the A2L and ELF come from the same build — if the ELF was rebuilt but the A2L was not regenerated, addresses will be off and the write will appear to “succeed” because the slave wrote to a wrong but valid memory location.
Measurement values jump after a calibration page switch.
Many ECUs initialize the RAM page by copying ROM contents on first switch, but if RAM was modified by a previous session and never reset, stale values remain. A defensive practice is to explicitly issue COPY_CAL_PAGE from ROM to RAM at the start of every session before any tuning.
When can I use CAN-FD?
ASAM XCP Part 2 (Transport on CAN) already covers CAN-FD DLC mapping and BRS behavior, so the protocol layer is not the blocker. The bottleneck is usually on the slave side: whether the XCP slave reports MAX_DLC_REQUIRED correctly and whether master and slave agree on MAX_CTO_FD / MAX_DTO_FD. The current KITE Calibrator build leaves CAN-FD scaffolding in place but pins fd_enabled to false; enabling it is aligned with the next KITE release window.