What this example does
This Linux C++ example sends the first basic XCP command: CONNECT.
It:
- Opens a SocketCAN interface.
- Filters the ECU's XCP response ID.
- Sends an XCP CONNECT command on the master ID.
- Waits for the slave response.
- Checks whether the response is positive or negative.
This is a learning example, not a complete XCP library.
Know the two XCP CAN IDs
XCP on CAN normally uses one CAN ID in each direction:
master ID: calibration tool -> ECU
slave ID: ECU -> calibration tool
This example uses:
master ID = 0x300
slave ID = 0x301
Replace them with the IDs defined for your ECU. Also confirm whether they are standard or extended IDs.
Configure SocketCAN
sudo ip link set can0 down
sudo ip link set can0 type can bitrate 500000
sudo ip link set can0 up
Run a monitor while testing:
candump can0
Complete C++ XCP CONNECT example
#include <array>
#include <cerrno>
#include <cstdint>
#include <cstring>
#include <iomanip>
#include <iostream>
#include <linux/can.h>
#include <linux/can/raw.h>
#include <net/if.h>
#include <poll.h>
#include <sys/ioctl.h>
#include <sys/socket.h>
#include <unistd.h>
namespace {
constexpr canid_t kXcpMasterId = 0x300; // Tool -> ECU
constexpr canid_t kXcpSlaveId = 0x301; // ECU -> Tool
constexpr std::uint8_t kCmdConnect = 0xFF;
constexpr std::uint8_t kPidResponse = 0xFF;
constexpr std::uint8_t kPidError = 0xFE;
}
int main() {
const char* interface_name = "can0";
int fd = socket(PF_CAN, SOCK_RAW, CAN_RAW);
if (fd < 0) {
std::cerr << "socket: " << std::strerror(errno) << '\n';
return 1;
}
can_filter response_filter{};
response_filter.can_id = kXcpSlaveId;
response_filter.can_mask = CAN_SFF_MASK;
if (setsockopt(
fd,
SOL_CAN_RAW,
CAN_RAW_FILTER,
&response_filter,
sizeof(response_filter)) < 0) {
std::cerr << "setsockopt: " << std::strerror(errno) << '\n';
close(fd);
return 1;
}
ifreq request{};
std::strncpy(request.ifr_name, interface_name, IFNAMSIZ - 1);
if (ioctl(fd, SIOCGIFINDEX, &request) < 0) {
std::cerr << "ioctl: " << std::strerror(errno) << '\n';
close(fd);
return 1;
}
sockaddr_can address{};
address.can_family = AF_CAN;
address.can_ifindex = request.ifr_ifindex;
if (bind(fd, reinterpret_cast<sockaddr*>(&address), sizeof(address)) < 0) {
std::cerr << "bind: " << std::strerror(errno) << '\n';
close(fd);
return 1;
}
can_frame command{};
command.can_id = kXcpMasterId;
command.can_dlc = 8; // Pad the classical CAN CTO with zero bytes.
command.data[0] = kCmdConnect;
command.data[1] = 0x00; // CONNECT mode: normal
if (write(fd, &command, sizeof(command)) != sizeof(command)) {
std::cerr << "write: " << std::strerror(errno) << '\n';
close(fd);
return 1;
}
pollfd wait_for_response{};
wait_for_response.fd = fd;
wait_for_response.events = POLLIN;
int ready = poll(&wait_for_response, 1, 1000);
if (ready == 0) {
std::cerr << "XCP CONNECT timed out\n";
close(fd);
return 1;
}
if (ready < 0) {
std::cerr << "poll: " << std::strerror(errno) << '\n';
close(fd);
return 1;
}
can_frame response{};
if (read(fd, &response, sizeof(response)) != sizeof(response)) {
std::cerr << "read: " << std::strerror(errno) << '\n';
close(fd);
return 1;
}
std::cout << "XCP response:";
for (int i = 0; i < response.can_dlc; ++i) {
std::cout << ' ' << std::hex << std::setw(2)
<< std::setfill('0')
<< static_cast<int>(response.data[i]);
}
std::cout << '\n';
if (response.can_dlc == 0) {
std::cerr << "Empty XCP response\n";
} else if (response.data[0] == kPidResponse) {
std::cout << "XCP CONNECT succeeded\n";
if (response.can_dlc >= 8) {
std::cout << "Resource byte: 0x"
<< static_cast<int>(response.data[1]) << '\n';
std::cout << "Max CTO: "
<< std::dec << static_cast<int>(response.data[3]) << '\n';
}
} else if (response.data[0] == kPidError) {
int error_code = response.can_dlc > 1 ? response.data[1] : -1;
std::cerr << "XCP error response, code: " << error_code << '\n';
} else {
std::cerr << "Unexpected XCP packet identifier\n";
}
close(fd);
return 0;
}
Compile and run
g++ -std=c++17 -Wall -Wextra -O2 xcp_connect.cpp -o xcp_connect
./xcp_connect
If the ECU responds positively, the first response byte is 0xFF. An XCP error packet starts with 0xFE, followed by an error code.
What the CONNECT response contains
A positive CONNECT response tells the master basic information such as:
- available resources
- communication mode information
- maximum command transfer object size
- maximum data transfer object size
- protocol-layer version
- transport-layer version
A complete client must parse each field according to the XCP specification and negotiated byte order.
Why a timeout happens
Check these items in order:
- Is the ECU powered and running an XCP slave?
- Is
can0up with the correct bitrate? - Are the master and slave IDs correct?
- Are the IDs standard or extended?
- Is XCP enabled in this ECU software mode?
- Does the ECU require a wake-up or application state first?
- Is another tool already using the XCP connection?
Record the raw frames. If the ECU replies on a different ID, the receive filter will hide it.
Do not turn this into a full library by adding random commands
A production XCP client needs more than command bytes. It must handle:
- command-specific request and response layouts
- timeouts and retry policy
- error responses
- memory transfer address rules
- address granularity and byte order
- seed-and-key protection
- DAQ list configuration
- transport-specific packet rules
- A2L parsing and conversions
- ECU state and cleanup
Use a tested XCP library unless building the protocol stack is itself the project goal.
The simple summary
An XCP on CAN connection starts with the correct two CAN IDs and a CONNECT command. The C++ example proves that basic command-response path with SocketCAN. After that, use the official XCP specification and the matching A2L before reading or writing ECU memory.