This guide demonstrates how to use ASAM MCD-2 MC (A2L) files with pyXCP for symbolic access to ECU parameters. A2L files describe the memory layout, data types, and conversion formulas of ECU variables, enabling human-readable measurement and calibration workflows.
What you'll learn:
- Load A2L files with
pya2ldb - Read measurements by symbolic name
- Write calibration parameters by name
- Set up DAQ with A2L metadata
- Convert raw values to engineering units
- Export data with symbolic labels
For production-grade workflows with advanced features, see the asamint project.
Use pyxcp directly when:
- Building custom calibration tools
- Integrating XCP into test automation
- Low-level protocol debugging
- Learning XCP fundamentals
- Embedded in larger applications
Use asamint when:
- Need command-line MCS functionality
- Orchestrating multiple ASAM standards (A2L + MDF + XCP)
- Creating calibration data files (ASAM CDF)
- High-level batch operations
- Production measurement campaigns
Example decision tree:
Need XCP communication?
└── YES
├── Simple script / learning? → Use pyxcp examples
├── Custom tool / integration? → Use pyxcp API directly
└── Production MCS? → Use asamint (built on pyxcp)
Install required packages:
pip install pyxcp pya2ldbOptional (for asamint):
git clone https://github.com/christoph2/asamint
cd asamint
python setup.py developScenario: Read ECU software version string using A2L symbolic name.
from pyxcp.cmdline import ArgumentParser
from pya2ldb import DB
# Load A2L file
db = DB()
db.import_a2l("my_ecu.a2l")
# Get measurement metadata
version_var = db.query_measurement("SwVersion")
address = version_var.address
datatype = version_var.datatype # e.g., "UBYTE[16]"
# Connect to ECU
ap = ArgumentParser(description="Read SW version")
with ap.run() as xcp:
xcp.connect()
# Read by address (from A2L)
raw_data = xcp.fetch(address, length=16)
version = raw_data.decode('utf-8').rstrip('\x00')
print(f"Software Version: {version}")
xcp.disconnect()This example demonstrates a full calibration cycle:
- Load A2L database
- Query characteristics (calibration parameters)
- Read current values
- Modify parameters
- Write back to ECU
- Verify changes
#!/usr/bin/env python
"""Complete A2L-based calibration workflow."""
from pyxcp.cmdline import ArgumentParser
from pya2ldb import DB
import struct
# === Configuration ===
A2L_FILE = "my_ecu.a2l"
PARAM_NAME = "InjectionTiming" # Characteristic to calibrate
# Load A2L
db = DB()
db.import_a2l(A2L_FILE)
# Get parameter metadata
param = db.query_characteristic(PARAM_NAME)
address = param.address
conversion = param.conversion # e.g., RAT_FUNC with formula
# Connect to ECU
ap = ArgumentParser(description=f"Calibrate {PARAM_NAME}")
with ap.run() as xcp:
xcp.connect()
# 1. Read current value (raw)
raw_bytes = xcp.fetch(address, length=param.size)
raw_value = struct.unpack(param.format_string, raw_bytes)[0]
# 2. Convert to physical value (engineering units)
if conversion:
physical_value = conversion.raw_to_phys(raw_value)
else:
physical_value = raw_value
print(f"Current {PARAM_NAME}: {physical_value} {param.unit}")
# 3. Modify parameter
new_physical = physical_value * 1.05 # 5% increase
# 4. Convert back to raw value
if conversion:
new_raw = conversion.phys_to_raw(new_physical)
else:
new_raw = new_physical
new_bytes = struct.pack(param.format_string, int(new_raw))
# 5. Write to ECU
xcp.download(address, new_bytes)
print(f"Updated {PARAM_NAME}: {new_physical} {param.unit}")
# 6. Verify
verify_bytes = xcp.fetch(address, length=param.size)
verify_raw = struct.unpack(param.format_string, verify_bytes)[0]
verify_phys = conversion.raw_to_phys(verify_raw) if conversion else verify_raw
assert abs(verify_phys - new_physical) < 0.01, "Verification failed!"
print("✓ Verification passed")
xcp.disconnect()Use A2L file to automatically configure DAQ lists with symbolic names:
from pyxcp.cmdline import ArgumentParser
from pyxcp.daq_stim import DaqList, DaqToCsv
from pya2ldb import DB
# Load A2L
db = DB()
db.import_a2l("my_ecu.a2l")
# Define measurements to record
measurements = ["EngineSpeed", "VehicleSpeed", "Throttle", "CoolantTemp"]
# Build ODT entries from A2L
odt_entries = []
for name in measurements:
meas = db.query_measurement(name)
odt_entries.append({
"address": meas.address,
"size": meas.size,
"name": name,
"unit": meas.unit,
"datatype": meas.datatype
})
# Connect and setup DAQ
ap = ArgumentParser(description="DAQ from A2L")
with ap.run() as xcp:
xcp.connect()
# Allocate DAQ list
daq = DaqList(xcp, 0, event_channel=0)
# Add ODTs from A2L metadata
for entry in odt_entries:
daq.add_odt_entry(
address=entry["address"],
size=entry["size"]
)
# Start recording
csv_writer = DaqToCsv("recording.csv", header=[e["name"] for e in odt_entries])
xcp.startDaq(daq.daq_list_number)
# Collect 100 samples
for _ in range(100):
data = xcp.daqQueue.get(timeout=1.0)
csv_writer.write_row(data)
xcp.stopDaq()
csv_writer.close()
print("✓ Recording saved to recording.csv with symbolic names")
xcp.disconnect()See the complete example at: pyxcp/examples/daq_from_a2l.py
Understanding A2L structure helps troubleshoot issues:
/begin PROJECT MyProject
/begin MODULE ECU_Controller
/begin MEASUREMENT EngineSpeed "Engine RPM"
UWORD 0x4000 /* address */
RAT_FUNC 1.0 0.0 /* factor, offset */
0.0 8000.0 /* min, max */
"rpm" /* unit */
/end MEASUREMENT
/begin CHARACTERISTIC InjectionTiming "Fuel injection timing"
VALUE 0x5000 /* address */
SWORD /* datatype */
RAT_FUNC 0.01 0.0 /* factor, offset */
-50.0 50.0 /* min, max */
"deg" /* unit */
/end CHARACTERISTIC
/end MODULE
/end PROJECT
Key sections:
- MEASUREMENT: Read-only variables (sensors, status)
- CHARACTERISTIC: Calibration parameters (maps, curves, scalars)
- COMPU_METHOD: Conversion formulas (RAT_FUNC, TAB_VERB, etc.)
- IF_DATA XCP: XCP-specific configuration (addresses, DAQ setup)
A2L defines conversion methods for raw ↔ physical values:
Most common conversion:
physical = (raw_value * factor) + offset
Example:
# A2L: RAT_FUNC 0.1 -40.0
raw_value = 250
physical = (250 * 0.1) + (-40.0) # = -15.0 °CFor non-linear conversions:
/begin COMPU_METHOD LookupTable
TAB_VERB "Lookup table"
/begin COMPU_TAB_REF
DEFAULT_VALUE "N/A"
/begin VALUES
0 0.0
50 12.5
100 25.0
255 100.0
/end VALUES
/end COMPU_TAB_REF
/end COMPU_METHOD
Complete working example is available at:
pyxcp/examples/a2l_integration.py
Features:
- Loading A2L files
- Querying measurements and characteristics
- Reading parameters by name
- Writing calibration values
- DAQ setup with symbolic names
- CSV export with headers
Run it:
python pyxcp/examples/a2l_integration.py --transport CAN --channel 0For production-grade measurement and calibration, use asamint:
Key features:
- Command-line MCS: No GUI needed
- Batch operations: Automate calibration campaigns
- MDF export: Industry-standard format (ASAM MCD-3 MC)
- CDF creation: Generate calibration data files
- Multiple projects: Orchestrate pyxcp, pya2ldb, asammdf, objutils
Example: Create CDF from XCP slave
# asamint example (command-line MCS)
asamint create-cdf my_ecu.a2l --output calibration.cdfExample: Setup dynamic DAQ with MDF output
# Using asamint API
from asamint import Session
session = Session("my_ecu.a2l")
session.connect("CAN", channel=0)
# High-level API
session.setup_daq(["Speed", "Torque", "Temp"])
session.record_mdf("recording.mdf", duration=60)
session.disconnect()Learn more:
- Repository: https://github.com/christoph2/asamint
- Examples:
asamint/examples/directory - Documentation:
asamint/docs/
"Measurement not found"
- Check symbolic name spelling (case-sensitive!)
- Verify A2L file version matches ECU firmware
- Use
db.list_measurements()to list all available
"Address access error"
- A2L address might be incorrect (wrong firmware version)
- ECU might require SEED/KEY unlock first
- Memory protection: use
xcp.setCalPage()if needed
"Conversion failed"
- A2L might have invalid COMPU_METHOD
- Raw value out of bounds (check min/max)
- Use
meas.conversionto inspect formula
"DAQ configuration error"
- ODT size exceeds max_dto (check A2L IF_DATA)
- Event channel not supported (query with
xcp.getDaqEventInfo()) - Too many ODT entries (check DAQ limits)
- Batch reads: Use
xcp.upload(address, size)for multiple variables - DAQ for monitoring: Prefer DAQ over polling for high-rate signals
- Cache A2L database: Don't reload A2L on every operation
- XCP master lock: Use locking for multi-threaded calibration
Q: Can I use pyxcp without A2L files?
Yes! pyxcp works with raw addresses. A2L provides symbolic access convenience.
Q: What's the difference between pya2ldb and pya2l?
pya2l: Legacy parser (deprecated)pya2ldb: Modern database-backed parser (recommended)
Q: Does pyxcp generate A2L files?
No. A2L files are generated by ECU development tools (INCA, CANape, etc.).
Q: When should I use asamint instead of pyxcp?
Use asamint for:
- Command-line batch operations
- MDF output (industry standard)
- Orchestrating multiple ASAM tools
- Production measurement campaigns
Use pyxcp for:
- Custom Python applications
- Test automation
- Embedded in larger systems
- Learning XCP protocol
Q: Can I edit A2L files?
Technically yes (they're text files), but not recommended. A2L files are generated from ECU source code and should stay in sync with firmware.
Q: What if my ECU doesn't have an A2L file?
You'll need to:
- Get A2L from ECU supplier
- Reverse-engineer memory layout (advanced!)
- Use XCP with raw addresses only
See pyxcp/examples/ directory:
a2l_integration.py: Complete A2L workflowdaq_from_a2l.py: DAQ setup from A2Lcalibration_workflow.py: Raw address calibrationdaq_recording.py: DAQ recording basics
- ASAM MCD-2 MC (A2L) Standard: https://www.asam.net/standards/detail/mcd-2-mc/
- pya2ldb Repository: https://github.com/christoph2/pya2l
- asamint Repository: https://github.com/christoph2/asamint
- pyxcp Repository: https://github.com/christoph2/pyxcp
- ASAM Standards: https://www.asam.net/standards/
Next steps:
- Read :doc:`tutorial` for pyxcp basics
- Check :doc:`configuration` for advanced XCP setup
- Try :doc:`../examples/a2l_integration` for hands-on practice