Skip to main content

CAN Bus

The CAN (Controller Area Network) interface provides robust communication for automotive and industrial applications. Supports standard and extended CAN IDs with configurable filtering and optional 120Ω termination resistors.

TIP

As described in pin alternate functions, industrial communication interfaces share GPIOs, meaning only one feature can be enabled at a given time: Either regular GPIOs, or one of the industrial communication interfaces.

CAN shares pins with RS485_0

The CAN bus and RS485_0 share the same GPIO pins for communication. Therefore, only one of these interfaces can be enabled at a time.

Enabling CAN while RS485_0 is enabled, or RS485_0 while CAN is enabled, fails with 409 CONFIGURATION_CONFLICT ("Cannot enable CAN_0 while RS485_0 is enabled"). Disable the other interface first.

User friendly multiplexing!

In order to avoid any confusion, we recommend following this standard procedure when using an industrial communication port:

  1. Start by enabling the port
  2. Disable the port when done using it.

If there are any conflicts (e.g. enabling an interface before disabling another one that uses the same resources), configure() or enable() fails with 409 CONFIGURATION_CONFLICT, allowing you to easily detect and correct any conflicts.

Quick Start​

The CAN bus supports enabling/disabling, termination resistance, setting message acceptance filters, transmitting, and receiving data.

import { AT1000 } from '@ikalogic/at1000';
let devices = await AT1000.findDevices(); // Find devices (returns host strings)
let tester = await AT1000.open(devices[0]); // Open the first detected device
await tester.reset(); // Reset the device to a known state
let can = tester.com.can(0); // Access CAN bus 0 on AT1000
// Enable CAN bus on AT1000
let can_cfg_result = await can.configure(
{
baud_rate:500000,
enabled : true,
termination_resistors:true,
rx_filter:{
mask:0x79F,
id:0x25A,
extended_id: false
}
});

// Start receiving CAN messages
await can.start_rx(); // Updated to use the 'can' variable

// Transmit a CAN message
await can.tx({id:0x123, data:[0x11, 0x22, 0x33, 0x44]});

//add delay of 100ms
await new Promise(resolve => setTimeout(resolve, 100));

// Receive messages (returns an array of objects containing ID & data)
let data_rx = await can.rx();

// Print received messages
data_rx.forEach(msg => {
console.log(`CAN Message ID: ${msg.id}, Data: ${msg.data}`);
});

// Disable CAN bus receiving
await can.stop_rx();

API Reference​

Accessing CAN​

const can = tester.com.can(id);

Parameters:

  • id (number / int / usize): The CAN interface identifier (0-based index)

Returns: Can instance / Result<Can, At1000Error> in Rust


Methods​

configure(config)​

Configures the CAN interface parameters without changing the enabled state.

await can.configure({
baud_rate: 500000,
termination_resistors: true,
rx_filter: {
extended_id: false,
id: 0x100,
mask: 0x7FF
}
});

Parameters:

  • config (CanConfigPatch): Configuration object with optional properties:
    • enabled (boolean / bool / Option<bool>, optional): Enable or disable the interface
    • baud_rate (number / int / Option<u32>, optional): CAN baud rate (see valid values below)
    • termination_resistors (boolean / bool / Option<bool>, optional): Enable/disable 120Ω termination resistors
    • rx_filter (object / dict / Option<CanRxFilter>, optional): Receive filter configuration. In Rust it is CanRxFilter::Standard { id, mask } or CanRxFilter::Extended { id, mask }; configure returns InvalidInput before sending for an id or mask wider than the format:
      • extended_id (boolean / bool / CanRxFilter::Extended variant): Use extended (29-bit) or standard (11-bit) IDs
      • id (number / int / u16 for Standard, u32 for Extended): Filter ID value
      • mask (number / int / u16 for Standard, u32 for Extended): Filter mask. A bit set to 1 must match id, a bit set to 0 is "don't care". A mask of 0 accepts every frame, standard and extended. Any other mask accepts only frames of the extended_id format (only standard frames when extended_id is false, only extended frames when it is true). The filter applies to both receive buffers of the CAN controller.

Valid baud rates: 5000, 10000, 20000, 31250, 33000, 40000, 50000, 80000, 83333, 95000, 100000, 125000, 200000, 250000, 500000, 1000000

Default configuration (after tester.reset()): disabled, baud_rate 125000, termination resistors off, rx_filter { extended_id: false, id: 0, mask: 0 } (accepts every frame).

Returns: Promise<CanConfig> / CanConfig / Result<CanConfig, At1000Error> - The updated configuration

Exceptions:

  • Throws validation error if baud_rate is not one of the valid values
  • Throws validation error if parameters don't match expected types
  • Throws error 409 CONFIGURATION_CONFLICT when enabled: true is set while RS485_0 is enabled

enable([config])​

Enables the CAN interface and optionally applies configuration.

await can.enable({
baud_rate: 250000,
termination_resistors: true
});

Parameters:

  • config (CanEnableConfig / CanEnableConfig / CanConfigPatch, optional): Optional configuration to apply. In Rust the patch is required (pass CanConfigPatch::default() to keep the current settings), and enable sets enabled: true itself:
    • baud_rate (number / int / Option<u32>, optional): CAN baud rate (see valid values above)
    • termination_resistors (boolean / bool / Option<bool>, optional): Enable/disable termination resistors
    • rx_filter (object / dict / Option<CanRxFilter>, optional): Receive filter configuration

Returns: Promise<CanConfig> / CanConfig / Result<CanConfig, At1000Error> - The updated configuration with enabled: true

Exceptions:

  • Throws validation error if baud_rate is not one of the valid values
  • Throws error 409 CONFIGURATION_CONFLICT while RS485_0 is enabled

disable()​

Disables the CAN interface.

await can.disable();

Returns: Promise<CanConfig> / CanConfig / Result<CanConfig, At1000Error> - The updated configuration with enabled: false


tx(data)​

Transmits a CAN frame.

// Send standard CAN frame
await can.tx({
id: 0x123,
data: [0x01, 0x02, 0x03, 0x04]
});

// Send frame with 8 bytes (maximum)
await can.tx({
id: 0x456,
data: [0x10, 0x20, 0x30, 0x40, 0x50, 0x60, 0x70, 0x80]
});

// Send a frame with an extended (29-bit) ID and a 100 ms transmit timeout
await can.tx({ id: 0x12345678, extended_id: true, data: [0x01], timeout: 100 });

// Send several frames in one request (at most 1024)
await can.tx([
{ id: 0x123, data: [0x01, 0x02] },
{ id: 0x456, data: [0x10, 0x20] },
]);

Parameters:

  • data (CanFrame | CanFrame[] / CanFrame | list[CanFrame] / CanFrame or a Vec, slice or array of CanFrame): CAN frame (or array of frames) to transmit:
    • id (number / int / CanId): CAN identifier (11-bit: 0-0x7FF, 29-bit: 0-0x1FFFFFFF)
    • extended_id (boolean / bool / CanId::Extended, optional, default: false): Use extended (29-bit) ID
    • rtr (boolean / bool / CanFrame::Remote, optional, default: false): Remote transmission request frame
    • dlc (number / int / u8 in CanFrame::Remote, optional): RTR frames only - requested data length (0 to 8). The server rejects dlc on data frames and non-empty data on RTR frames
    • data (number[] / list[int] / Vec<u8> in CanFrame::Data, optional): Data bytes (0 to 8 bytes, each 0-255)
    • timeout (number / int / Duration or None, second argument of tx(), optional): Transmit-only - max ms (1 to 5000) to wait for a free TX buffer and for the frame's transmission attempt to get onto the bus (server default 1000 ms). In a batch, each frame has its own timeout

Each frame is sent in one-shot mode: there is a single transmission attempt, and tx() returns once it has succeeded, which means another node acknowledged the frame. If the attempt fails (no other node acknowledged it, a bus error, or lost arbitration), tx() fails at once with 500 INTERNAL_ERROR ("Failed to send CAN_0 message: error_failtx"). Typical causes are no other node connected, missing termination, a baud rate mismatch or a wiring fault. The timeout only bounds the wait for a TX buffer, or for the attempt to get onto the bus. When it expires, the request also fails with 500 INTERNAL_ERROR and the frame is withdrawn, so it cannot leave the bus later.

In Python, the SDK's HTTP client waits at most 1 s for the device's answer. Keep the frame timeout well under 1000 ms (and a batch short enough to finish within 1 s) so the device answers before the client gives up.

Batches: one request carries at most 1024 frames. Every frame of the batch is validated before any is sent, so an invalid frame rejects the whole batch with nothing sent. Frames are then sent in order and sending stops at the first frame that fails; the error message tells how many frames went out, e.g. "Failed to send CAN_0 message 3 of 10 (2 sent successfully): …".

In Rust, a frame is either CanFrame::Data { id, data } or CanFrame::Remote { id, dlc }, and its identifier is either CanId::Standard(u16) or CanId::Extended(u32). Build frames with CanFrame::data(id, data) and CanFrame::remote(id, dlc). tx(frames, timeout) takes one frame or a batch, and a Duration from 1 ms to 5 s or None for the server default.

Returns: Promise<CanFrame | CanFrame[]> / CanFrame | list[CanFrame] / Result<Vec<CanFrame>, At1000Error> - The transmitted frame(s). Rust always returns a vector, with one frame when one frame was sent

Exceptions:

  • Throws validation error if data array length is not between 0 and 8
  • Throws validation error if any byte value is outside the range [0, 255]
  • Throws error 400 INVALID_VALUE if dlc is set on a data frame, data is non-empty on an RTR frame, or a batch has more than 1024 frames
  • Throws error 400 INVALID_CONFIGURATION if the interface is disabled
  • Throws error 500 INTERNAL_ERROR if a frame's transmission attempt fails (e.g. no node acknowledges it), or if no TX buffer or bus access is obtained within its timeout
  • Rust returns InvalidInput before sending for an identifier wider than its format, a payload over 8 bytes, a dlc over 8, more than 1024 frames, or a timeout outside 1 ms to 5 s

start_rx()​

Starts buffering received CAN frames.

await can.start_rx();

Returns: Promise<void> / None / Result<(), At1000Error>

Note: start_rx() discards any frames still in the receive buffer. From then on, frames matching the configured filter are buffered and can be retrieved using rx().


stop_rx()​

Stops buffering received CAN frames.

await can.stop_rx();

Returns: Promise<void> / None / Result<(), At1000Error>


rx()​

Reads all buffered CAN frames received since the last start_rx() or rx() call.

const frames = await can.rx();
for (const frame of frames) {
console.log(`ID: 0x${frame.id.toString(16)}, Data:`, frame.data);
}

Returns: Promise<CanFrame[]> / list[CanFrame] / Result<Vec<CanFrame>, At1000Error> - Array of received CAN frames. Received RTR frames have rtr: true, dlc set to the requested length and an empty data array. In Rust they are CanFrame::Remote { id, dlc }, and data frames are CanFrame::Data { id, data }.

Note: This method returns all frames accumulated since the last start_rx() or rx() call and clears the buffer. The buffer holds up to 1024 frames; frames received while it is full are dropped, so call rx() often enough on a busy bus.

Exceptions:

  • Throws error 400 INVALID_CONFIGURATION if the interface is disabled

Types​

CanConfig​

Complete configuration object returned by configuration methods.

{
enabled: boolean,
baud_rate: number, // Valid baud rate from list
termination_resistors: boolean,
rx_filter: {
extended_id: boolean,
id: number,
mask: number
}
}

Rust: ikalogic_at1000::CanConfig and ikalogic_at1000::CanRxFilter. Any filter value builds; configure() and enable() return InvalidInput before sending for an id or mask wider than the format.

pub struct CanConfig {
pub enabled: bool,
pub baud_rate: u32,
pub termination_resistors: bool,
pub rx_filter: CanRxFilter,
}

pub enum CanRxFilter {
Standard { id: u16, mask: u16 }, // both at most 0x7FF
Extended { id: u32, mask: u32 }, // both at most 0x1FFFFFFF
}

CanConfigPatch​

Partial configuration for the configure() method. All properties are optional.

{
enabled?: boolean,
baud_rate?: number, // Valid baud rate from list
termination_resistors?: boolean,
rx_filter?: {
extended_id: boolean,
id: number,
mask: number
}
}

Rust: ikalogic_at1000::CanConfigPatch (derives Default, so unset fields can be left to ..Default::default())

pub struct CanConfigPatch {
pub enabled: Option<bool>,
pub baud_rate: Option<u32>,
pub termination_resistors: Option<bool>,
pub rx_filter: Option<CanRxFilter>,
}

CanEnableConfig​

Optional configuration for the enable() method.

{
baud_rate?: number, // Valid baud rate from list
termination_resistors?: boolean,
rx_filter?: {
extended_id: boolean,
id: number,
mask: number
}
}

Rust has no separate enable type: enable takes CanConfigPatch and sets enabled: true itself.

CanFrame​

CAN message frame structure.

{
id: number, // CAN identifier
extended_id?: boolean, // Use extended (29-bit) ID (default: false)
rtr?: boolean, // Remote transmission request (default: false)
dlc?: number, // RTR only: requested data length 0-8; present on received RTR frames
data?: number[], // Data bytes (0-8 bytes)
timeout?: number // TX-only: ms to wait for a TX buffer and bus access (1-5000)
}

Rust: ikalogic_at1000::CanFrame and ikalogic_at1000::CanId. Any value builds; tx() returns InvalidInput before sending for an identifier wider than its format, a payload over 8 bytes or a dlc over 8. The transmit timeout is the second argument of tx().

pub enum CanFrame {
Data { id: CanId, data: Vec<u8> },
Remote { id: CanId, dlc: u8 },
}

impl CanFrame {
pub const MAX_DATA: usize = 8;
pub fn data(id: CanId, data: impl Into<Vec<u8>>) -> Self;
pub const fn remote(id: CanId, dlc: u8) -> Self;
pub const fn id(&self) -> CanId;
}

pub enum CanId {
Standard(u16), // at most 0x7FF
Extended(u32), // at most 0x1FFFFFFF
}

impl CanId {
pub const STANDARD_MAX: u16 = 0x7FF;
pub const EXTENDED_MAX: u32 = 0x1FFF_FFFF;
pub fn raw(self) -> u32;
pub const fn is_extended(self) -> bool;
}

Usage Example​

import { AT1000 } from '@ikalogic/at1000';

// Find and connect to device
const devices = await AT1000.findDevices();
const tester = await AT1000.open(devices[0]);
await tester.reset(); // Reset the device to a known state

// Access CAN interface 0
const can = tester.com.can(0);

// Enable with 500k baud rate and receive filter
await can.enable({
baud_rate: 500000,
termination_resistors: true,
rx_filter: {
extended_id: false,
id: 0x100,
mask: 0x700 // Accept IDs 0x100-0x1FF
}
});

// Start receiving frames
await can.start_rx();

// Send a frame
await can.tx({
id: 0x123,
data: [0x01, 0x02, 0x03, 0x04]
});

// Wait for responses
await new Promise(resolve => setTimeout(resolve, 100));

// Read received frames
const frames = await can.rx();
console.log("Received frames:", frames);

// Stop receiving
await can.stop_rx();

// Disable interface
await can.disable();

Common Patterns​

CAN Filter Configuration​

// Accept all frames, standard and extended
await can.configure({
rx_filter: {
extended_id: false,
id: 0x000,
mask: 0x000 // A zero mask accepts every frame, whatever extended_id says
}
});

// Accept only specific ID (0x123)
await can.configure({
rx_filter: {
extended_id: false,
id: 0x123,
mask: 0x7FF // All bits must match
}
});

// Accept range of IDs (0x200-0x2FF)
await can.configure({
rx_filter: {
extended_id: false,
id: 0x200,
mask: 0x700 // Match upper 3 bits
}
});

// Extended (29-bit) ID filtering
await can.configure({
rx_filter: {
extended_id: true,
id: 0x12345678,
mask: 0x1FFFFFFF
}
});

Request-Response Pattern​

await can.start_rx();

// Remote request
await can.tx({ id: 0x321, rtr: true, dlc: 8 }); // remote request for 8 bytes

// Send request
await can.tx({
id: 0x7DF, // OBD-II broadcast
data: [0x02, 0x01, 0x0C] // Request engine RPM
});

// Wait for response
await new Promise(resolve => setTimeout(resolve, 50));

const frames = await can.rx();
for (const frame of frames) {
if (frame.id >= 0x7E8 && frame.id <= 0x7EF) {
console.log("ECU response:", frame.data);
}
}

await can.stop_rx();

Continuous Monitoring​

// Start monitoring
await can.start_rx();

// Poll periodically
const monitor = setInterval(async () => {
const frames = await can.rx();
for (const frame of frames) {
console.log(`[${Date.now()}] ID: 0x${frame.id.toString(16).padStart(3, '0')}, Data:`,
frame.data.map(b => b.toString(16).padStart(2, '0')).join(' '));
}
}, 100);

// Stop after some time
setTimeout(async () => {
clearInterval(monitor);
await can.stop_rx();
}, 10000);

Common Baud Rates​

  • 1000000 (1 Mbps): High-speed CAN, short distance
  • 500000 (500 kbps): Most common for automotive CAN
  • 250000 (250 kbps): Industrial applications
  • 125000 (125 kbps): Longer distance CAN networks
  • 83333 (83.3 kbps): CANopen default