Version: 1.2.0
This guide documents the C API exposed by libxcplite and demonstrates its integration in a host application.
All functions are C linkage and can therefore be consumed directly from C/C++, or via FFI from Rust and other languages.
Key features:
- Single‑instance XCP server (TCP or UDP)
- Lock‑free calibration parameter segments
- Timestamped measurement events to capture global, stack and heap data
- Optional A2L file meta data and type generation at runtime
It is up to the user how to build and link the library into the host application.
There is an cmakelists.txt to build the library and all the examples in the examples/ folder.
- Include headers
Code Example:
#include <xcplib.h>
#include <a2l.h> C++ Example:
#include <xcplib.hpp>
#include <a2l.hpp> - Set log level (optional)
The log level can be set at runtime to control the verbosity of log output.
To save resources, it is recommended to set the log level to a fixed minimal level in production systems, or even to a fixed level not adjustable at runtime.
In xcplib_cfg.h:
/* Set maximum runtime adjustable log level:
1 - Error
2 - Warn
3 - Info
4 - Trace
5 - Debug
*/
#define OPTION_MAX_DBG_LEVEL 5
// #define OPTION_FIXED_DBG_LEVEL 3C and C++ Examples:
XcpSetLogLevel(3); // (1=error, 2=warning, 3=info, 4=debug (prints all XCP commands), 5=trace)- Initialise the XCP core once:
XcpInit("MyProject" /* Project name*/, "V1.0.1" /* EPK version string*/, true /* activate XCP */);- Start the Ethernet server once:
const uint8_t addr[4] = {0,0,0,0}; // bind to any address
XcpEthServerInit(addr, 5555 /* port */, false /* UDP */, 32*1024 /* DAQ queue size */);- Create calibration parameter segments.
C Example:
// Calibration parameters structure
typedef struct params {
uint16_t counter_max; // Maximum value for the counter
uint32_t delay_us; // Sleep time in microseconds for the main loop
float flow_rate; // Flow rate in m3/h
} parameters_t;
// Default values (reference page, "FLASH") for the calibration parameters
const parameters_t params = {.counter_max = 1024, .delay_us = 1000, .flow_rate = 0.300f};
// A global calibration segment handle for the calibration parameters
// A calibration segment has a working page ("RAM") and a reference page ("FLASH"), it is described by a MEMORY_SEGMENT in the A2L file
// Using the calibration segment to access parameters assures safe (thread safe against XCP modifications), wait-free and consistent access
// It supports RAM/FLASH page switching, reinitialization (copy FLASH to RAM page) and persistence (save RAM page to BIN file)
tXcpCalSegIndex calseg = XCP_UNDEFINED_CALSEG;
// Create a calibration segment named 'Parameters' for the calibration parameter struct instance 'params' as reference page
calseg = XcpCreateCalSeg("params", ¶ms, sizeof(params));
// Option1: Register the individual calibration parameters in the calibration segment
A2lSetSegmentAddrMode(calseg, params);
A2lCreateParameter(params.counter_max, "Maximum counter value", "", 0, 2000);
A2lCreateParameter(params.delay_us, "Mainloop delay time in us", "us", 0, 999999);
// Option2: Register the calibration segment as a typedef instance
{
A2lTypedefBegin(parameters_t, ¶ms, "Calibration parameters typedef");
A2lTypedefParameterComponent(counter_max, "Maximum counter value", "", 0, 2000);
A2lTypedefParameterComponent(delay_us, "Mainloop delay time in us", "us", 0, 999999);
A2lTypedefEnd();
A2lSetSegmentAddrMode(calseg, params);
A2lCreateTypedefInstance(params, parameters_t, "Calibration parameters");
}C++ Example:
// Calibration parameters for the random number generator
struct ParametersT {
double min; // Minimum random number value
double max; // Maximum random number value
};
// Default parameter values
const ParametersT kParameters = {.min = 2.0, .max = 3.0};
// A calibration segment wrapper for the parameters
std::optional<xcp::CalSeg<ParametersT>> gCalSeg;
// Create a global calibration segment wrapper for the struct 'ParametersT' and use its default values in constant 'kParameters'
// This calibration segment has a working page (RAM) and a reference page (FLASH), it creates a MEMORY_SEGMENT in the A2L file
// It provides safe (thread safe against XCP modifications), lock-free and consistent access to the calibration parameters
// It supports XCP/ECU independent page switching, checksum calculation and reinitialization (copy reference page to working page)
gCalSeg.emplace("Parameters", &kParameters);
// Register the calibration segment description as a typedef and an instance in the A2L file
{
A2lTypedefBegin(ParametersT, &kParameters, "Typedef for ParametersT");
A2lTypedefParameterComponent(min, "Minimum random number value", "", -100.0, 100.0);
A2lTypedefParameterComponent(max, "Maximum random number value", "", -100.0, 100.0);
A2lTypedefEnd();
}
gCalSeg->CreateA2lTypedefInstance("ParametersT", "Random number generator parameters");
- Access calibration parameters via the calibration segment.
C Example:
// Lock access to calibration parameters
const parameters_t *params = (parameters_t *)XcpLockCalSeg(calseg);
printf("Current parameters: counter_max=%u, delay_us=%u\n",params->counter_max,params->delay_us);
// Unlock calibration segment
XcpUnlockCalSeg(calseg);C++ Example:
{
auto params = gCalSeg->lock(); // RAII lock guard for calibration segment
std::cout << "Current parameters: min=" << params->min << ", max=" << params->max << std::endl;
}- Create and trigger measurement events to capture variables.
C or C++ Example: Basic example: Measure a local variable on stack or a global variable
// Local or global variable to measure
int8_t temperature = 0;
// Create a global measurement event named "MyEvent" and register the local variable 'temperature' for measurement on this event
DaqCreateEvent(MyEvent);
A2lSetStackAddrMode(temperature); // or SetAbsoluteAddrMode(temperature);
A2lCreatePhysMeasurement(temperature, "temperature", "Deg Celcius", -50, 80);
// Trigger event 'MyEvent' to measure the variable when changed
// Creates a precise timestamp and captures consistent data
temperature = read_temp_sensor();
DaqTriggerEvent(MyEvent);
C++ Example:
Instrument a member function of a class
class power_meter {
public:
power_meter() : energy_(0.0) {}
void calc_energy(double );
private:
double energy_;
double last_time_;
};
double calc_energy(double voltage, double current) {
// ... do some calcultions ...
double power = voltage * current; // kW
double energy_ += power * get_elapsed_time(); // kWh
// Create event 'calc_energy', register individual local or member variables and trigger the event
DaqEventExtVar(calc_energy, this,
A2L_MEAS_PHYS(voltage, "Input voltage", "V", 0.0, 1000.0), // A function parameter
A2L_MEAS_PHYS(current, "Input current", "A", 0.0, 500.0), // A function parameter
A2L_MEAS_PHYS(power, "Current calculated energy", "kWh", 0.0, 1000.0), // A local variable
A2L_MEAS_PHYS(energy_, "Current power", "kW", 0.0, 1000.0) // A member variable accessed via 'this' pointer
);
return energy_;
}C++ example: Measure an instance of a class on heap
// In the constructor or any place where we can access private members of the class
if (A2lOnce()) {
A2lTypedefBegin(PowerMeter, this, "Typedef for PowerMeter");
A2lTypedefMeasurementComponent(energy_, "Current energy", "kWh", 0.0, 1000.0 );
A2lTypedefMeasurementComponent(last_time_, "Last calculation time", "ms" );
A2lTypedefEnd();
}
// Create a heap instance of the class PowerMeter
PowerMeter* meter1 = new PowerMeter();
// Some where in the code, create an event to measure the heap instance
// Register the complete PowerMeter instance on heap as measurement with event my_meter
DaqCreateEvent(my_meter);
A2lSetRelativeAddrMode(my_meter, meter1);
A2lCreateTypedefReference(meter1, PowerMeter, "Instance my_meter of PowerMeter");
// Something happens in the instance
my_meter->calc_energy(voltage, current);
// Trigger the event "my_meter" to measure the 'PowerMeter' heap instance 'my_meter'
DaqTriggerEventExt(my_meter, my_meter);
- On shutdown, call
XcpEthServerShutdown().
Initialize XCP
XCP must be initialized always, but it may be initialized in inactive mode.
Calling other XCP API functions without prior initialization may create undefined behaviour.
In inactive mode, all XCP and A2L code instrumentation remains passive, disabled with minimal runtime overhead.
- Parameters
name– Project name, used as A2L file name and to identify the XCP serverepk– EPK version string, used for version compatibility check of A2L and BIN filemode– XCP_MODE_DEACTIVATE, XCP_MODE_LOCAL (XCP_MODE_SHM, XCP_MODE_SHM_AUTO or XCP_MODE_SHM_SERVER for libxcplite builds with SHM mode)
bool XcpEthServerInit(uint8_t *address, uint16_t port, bool use_tcp, uint32_t measurement_queue_size)
Initialise the XCP server.
- Preconditions:
XcpInit()has been called; only one server instance may be active. - Parameters
address– IPv4 address to bind to (0.0.0.0= any).port– Port number.use_tcp–true→ TCP,false→ UDP.measurement_queue_size– Queue size in bytes (multiple of 8; includes header + alignment).
- Returns:
trueon success, otherwisefalse.
Stop the XCP server.
Stop the running server and free internal resources.
Get server status
Query whether the server instance is currently running.
See function and macro documentation in xcplib.h
Events are used to capture timestamped measurements of local variables on stack, global memory or heap instances.
An event is created once and may be triggered periodically to capture consistent data at the time of the trigger.
Events have a globally unique name and identification number (tXcpEventId which is a 16 Bit value).
To trigger an event, a library function (XcpEvent(), XcpEventExt(),...) or one of the convenience macros (DaqTriggerEvent(), DaqEvent(), ..) is called in a particular code location. The event is associated to this code location and triggered every time the code is executed. Measurement variables associated to the event must be visible and valid in this code location.
Triggering the same event in multiple code locations is possible, but it is in the users responsibility to make sure that all measurements associated to the event event are accessible and valid. This not recommended as it makes the measurement data hard to interpret.
A global event is triggered always from the same code location, independent of the call stack or thread context. To filter events depending on their execution context, events may be enabled or disabled via library calls (DaqEventEnable(), DaqEventDisable()).
Advanced techniques like context dependent events (e.g. per thread or per class instance in a class member function) are supported, but require more complex code instrumentation. There are examples available demonstrating these techniques.
Library function to create events:
/// Add a measurement event to the event list, returns the event id (0..XCP_MAX_EVENT_COUNT-1)
/// If the event name already exists, returns the existing event event number
/// Function is thread safe by using a mutex for event list access.
/// @param name Name of the event.
/// @param cycle_time_ns Cycle time in nanoseconds. 0 means sporadic event.
/// @param priority Priority of the event. 0 means normal, >=1 means realtime.
/// @return The event id or XCP_UNDEFINED_EVENT_ID if out of event list memory.
tXcpEventId XcpCreateEvent(const char *name, uint32_t cycle_time_ns /* ns */, uint8_t priority /* 0-normal, >=1 realtime*/);Library functions to trigger and control events:
/// Trigger the XCP event 'event' for absolute addressing mode
/// @param event Event id.
void XcpEvent(tXcpEventId event);
/// Trigger the XCP event 'event' for absolute or relative addressing mode with explicitly given base address (address extension = 2)
/// @param event
/// @param base address pointer
void XcpEventExt(tXcpEventId event, const uint8_t *base2);
// Enable or disable a XCP DAQ event
void XcpEventEnable(tXcpEventId event, bool enable);All functions are thread safe and lock-free.
See technical reference for details.
Convenience macros to create, control and trigger events without the need to pass around event handles and specify base addresses:
Macros to create events:
/// Create a global event
/// Macro may be used anywhere in the code, even in loops
/// Thread safe global once pattern, the first call creates the event
/// May be called multiple times in different code locations, ignored if the the event name already exists
/// @param name Name given as identifier
DaqCreateEvent(event_name) \Macros to trigger events:
/// Trigger a global event for stack relative or absolute addressing
/// Cache the event name lookup in global storage
/// @param name Name given as identifier
#define DaqTriggerEvent(event_name)
/// Trigger a global event for absolute, stack and relative addressing mode with given individual base address (from A2lSetRelativeAddrMode(base_addr))
/// Cache the event name lookup in global storage
/// @param name Name given as identifier
/// @param base_addr Base address pointer for relative addressing mode
#define DaqTriggerEventExt(event_name, base_addr)Macros to control events:
/// Enable the XCP event 'name'
DaqEventEnable(event_name) \
/// Disable the XCP event 'name'
DaqEventDisable(event_name) Combined variadic macros to trigger events and register measurements in one call.
Available for c and c++:
/// Trigger an event, create the event once and register global and local measurement variables once
/// Supports absolute, stack and relative addressing mode measurements
DaqEventVar(event_name, ...)
/// Trigger an event, create the event once and register global, local and relative addressing mode measurement variables once
/// Supports absolute, stack and relative addressing mode measurements
DaqEventExtVar(event_name, ...)
/// Helper macros for creating measurement meta data, variable name stringification and address capture
A2L_MEAS(var, comment)
A2L_MEAS_PHYS(var, comment, unit, min, max)
// Example:
DaqEventVar(event_name,
A2L_MEAS(variable1, "Comment", "Unit", -20, 50), //
A2L_MEAS(variable2, "Comment", "Unit", 0, 40));Note:
For workflows without runtime A2L generation, the event creation macros have to be used.
They create static markers in the code to identify events and event trigger code locations by reading the ELF/DWARF debug information.
Initializes the A2L generation system of XCPlite. This function must be called once before any A2L-related macros or API functions are used. It performs the following actions:
- Allocates and initializes all internal data structures and files required for A2L file creation.
- Enables runtime definition of parameters, measurements, type definitions, groups and conversion rules.
Parameters:
project_name: Name of the project, used for the A2L and BIN file names.epk: Unique software version string (EPK) for version checking of A2L and parameter files.address: Default IPv4 address of the XCP server.port: Port number of the XCP server.use_tcp: Iftrue, TCP transport is used; iffalse, UDP transport.mode_flags: Bitwise combination of A2L generation mode flags controlling file creation and runtime behavior:A2L_MODE_WRITE_ALWAYS(0x01): Always write the A2L file, overwriting any existing file.A2L_MODE_WRITE_ONCE(0x02): Write the A2L file (.a2l) once after a rebuild; Uses the binary persistence file (.bin) to keep calibration segment and event numbers stable, even if the registration order changes.A2L_MODE_FINALIZE_ON_CONNECT(0x04): Finalize the A2L file when an XCP client connects, later registrations are not visible to the tool. If not set, the A2L file must be finalized manually.A2L_MODE_AUTO_GROUPS(0x08): Automatically create groups for measurement events and parameter segments in the A2L file.
Finalizes and writes the A2L file to disk. This function should be called when all measurements, parameters, events, and calibration segments have been registered and no further A2L definitions are expected. After finalization, the A2L file becomes visible to XCP tools and cannot be modified further during runtime.
XCPlite uses relative memory addressing. There are 4 different addressing modes. When an addressing mode is set, it is valid for all subsequent definitions of parameters and measurement variables.
| Macro | Description |
|---|---|
A2lSetSegmentAddrMode |
Sets segment-relative addressing mode for calibration parameters in a specific segment. |
A2lSetAbsoluteAddrMode |
Sets absolute addressing mode for variables in global memory space. |
A2lSetStackAddrMode |
Sets stack-relative addressing mode for variables on the stack (relative to frame pointer). |
A2lSetRelativeAddrMode |
Sets relative addressing mode for variables relative to a base address (e.g., heap objects or class instances). |
All A2L generation macros and functions are not thread safe. It is up to the user to ensure thread safety and to use once-patterns when definitions are called multiple times in nested functions or from different threads.
The functions A2lLock() and A2lUnlock() may be used to lock sequences of A2L definitions. The macro A2lOnce may be used to create a once execution pattern for a block of A2L definitions.
Also note that A2L definitions may be lazy, but the A2L file is finalized when an XCP tool connects (or when A2lFinalize() is called). All definitions after that point are ignored and not visible to the tool.
All definitions of instances follow the same principle: Set the addressing mode first. The addressing mode is valid for all following definitions. The examples in the examples/ folder show various ways how to create A2L artifacts.
| Function | Purpose |
|---|---|
void XcpSetLogLevel(uint8_t level); |
1 = error, 2 = warn, 3 = info, 4 = commands, 5 = trace. |
void XcpInit(const char *name, const char *epk, bool activate); |
Initialize core singleton; must precede all other API usage. |
void XcpDisconnect(void); |
Force client disconnect, stop DAQ, flush pending operations. |
void XcpSendTerminateSessionEvent(void); |
Notify client of a terminated session. |
void XcpPrint(const char *str); |
Send arbitrary text to the client (channel 0xFF). |
uint64_t ApplXcpGetClock64(void); |
Get 64-bit DAQ timestamp from whatever is configured. |
End of document.