CAN interface

Hardware

The O3R has a built-in CAN-bus interface, with the CAN-High and CAN-Low lines on pin 4 and 5 respectively (cf. hardware diagram). Note that cables will need a terminating resistor like the E11589.

Software

The CAN interface is supported starting from firmware version 0.14.1. Configuring the CAN interface through the JSON configuration is only possible for firmware version 1.4.30 and above.

Please note that the CAN interface is only accessible within Docker when using the --network host option.

Before utilizing the CAN interface, it needs to be set up. To verify whether the CAN interface is active and with which bitrate it operates, there are two possible methods: using the ifm3d CLI or Python scripts.

  1. Using the ifm3d CLI:

    $ ifm3d dump | jq .device.network.interfaces.can0
    {
    "active": false,
    "bitrate": "125K"
    }
    
  2. Using Python script: see the script below or download it from ifm3d-examples.

#!/usr/bin/env python3
#############################################
# Copyright 2024-present ifm electronic, gmbh
# SPDX-License-Identifier: Apache-2.0
#############################################
# The CAN interface can only be activated with firmware
# versions 1.4.X or higher
from ifm3dpy.device import O3R
import logging
import time

logger = logging.getLogger(__name__)

def _check_can_availability(o3r)-> None:
    if "can0" in o3r.get(["/device/network/interfaces"])["device"]["network"]["interfaces"]:
        # Get the can0 information: active status and bitrate
        can_info = o3r.get(["/device/network/interfaces/can0"])["device"]["network"]["interfaces"]["can0"]
        return can_info
    else:
        raise RuntimeError("CAN interface not available in current firmware version.")
        
        

def main(ip: str) -> None:
    o3r = O3R(ip)
    can_info = _check_can_availability(o3r)
    if not can_info["active"]:
        logger.info("activating can0 interface ...")
        o3r.set({
            "device": {
                "network": {
                    "interfaces": {
                        "can0": {
                            "active": True
                            #"bitrate": '125K' # here set the bitrate '250K', '500K' ...

                        }
                    }
                }
            }
        })
        logger.info("Rebooting the device...")
        o3r.reboot()
        time.sleep(150)
        # For the sake of simplicity we assume that the boot-up process is not timing out,
        # and we simply check that the boot sequence has completed.
        # For a fool proof boot-up monitoring, review the bootup_monitor.py example.
        if not o3r.get(["/device/diagnostic/confInitStages"])["device"]["diagnostic"]["confInitStages"] == ["device", "ports", "applications"]:
            raise Exception("VPU not properly booted")

    can_info = o3r.get(["/device/network/interfaces/can0"])["device"]["network"]["interfaces"]["can0"]
    if not can_info["active"]:
        raise RuntimeError("Activating the can0 interface failed.")
    else:
        logger.info("The can0 interface is active!")

if __name__ == "__main__":
    logging.basicConfig(level=logging.INFO, format="%(message)s")
    try:
        # If the example python package was build, import the configuration
        from ovp8xxexamples import config

        IP = config.IP
    except ImportError:
        # Otherwise, use default values
        logger.warning(
            "Unable to import the configuration.\nPlease run 'pip install -e .' from the python root directory"
        )
        logger.warning("Defaulting to the default configuration.")
        IP = "192.168.0.69"
    
    logger.info(f"Device IP: {IP}")
    main(IP)

Note

New network settings will only be applied after reboot. The Python script performs a reboot. Using the ifm3d CLI a reboot can be performed by ifm3d reboot.

After activating the CAN interface and rebooting the VPU, we can verify the status of the CAN interface again using the ifm3d CLI as follows:

$ ifm3d dump | jq .device.network.interfaces.can0
{
  "active": true,
  "bitrate": "125K"
}

Example: Interfacing with the DTM425 RFID antenna using Docker

Step 1: Connect the DTM425 to the O3R and both to power.

Step 2: Create a minimal Dockerfile (filename: Dockerfile) like shown below (the example can be downloaded here):

FROM arm64v8/alpine

RUN apk add --no-cache python3 py3-pip

COPY requirements.txt /home/ifm/
# Create a venv and install python dependencies
RUN python3 -m venv /home/ifm/venv \
    && /home/ifm/venv/bin/pip install --requirement /home/ifm/requirements.txt

# Copy the can_example.py example.
COPY can_example.py /home/ifm/
# Copy the eds file to the container
# You can download the DTM425.eds file from https://www.ifm.com/us/en/product/DTM425?tab=documents
COPY DTM425.eds /usr/local/share

# Make the script executable
RUN chmod +x /home/ifm/can_example.py

# Activate virtual environment and run the script
CMD ["/home/ifm/venv/bin/python", "/home/ifm/can_example.py"]

This Dockerfile installs Python and the CANopen library for Python. The example script is then installed into the image and set to automatically execute when the container is run.

Step 3: Use the example Python script below (or download the script here) and download the required EDS file (filename DTM425.eds). Place the files in the same location as the Dockerfile.

#############################################
# Copyright 2024-present ifm electronic, gmbh
# SPDX-License-Identifier: Apache-2.0
#############################################
#!/usr/bin/env python3
import time
import canopen


def connect():
    nw = canopen.Network()
    nw.connect(channel="can0", bustype="socketcan")
    nw.scanner.search()
    time.sleep(0.05)

    device = nw.add_node(nw.scanner.nodes[0], "/usr/local/share/DTM425.eds")
    device.nmt.state = "OPERATIONAL"
    time.sleep(0.05)

    return (nw, device)


def disconnect(nw, device):
    device.nmt.state = "PRE-OPERATIONAL"
    nw.disconnect()


def write_tag(device, data):
    memory_size = device.sdo[0x2182][0x4].raw

    if len(data) < memory_size:
        data = data + b'\x00' * (memory_size - len(data))

    for offset in range(0, memory_size, 8):
        length = (8 if offset + 8 <= memory_size else
                  memory_size - offset)
        device.sdo[0x2380].raw = offset
        device.sdo[0x2381].raw = length
        device.sdo[0x2382].raw = data[offset:offset + length]


def read_tag(device):
    memory_size = device.sdo[0x2182][0x4].raw
    data = b""

    for offset in range(0, memory_size, 8):
        length = 8 if offset + 8 <= memory_size else memory_size - offset
        device.sdo[0x2280].raw = offset
        device.sdo[0x2281].raw = length
        data = data + device.sdo[0x2282].raw

    return data


def main():
    nw, device = connect()
    data = b'\xDE\xAD\xBE\xEF'
    print("Writing tag:", data)
    write_tag(device, data)
    print("Reading tag:", read_tag(device))
    disconnect(nw, device)

if __name__ == "__main__":
    main()

The script writes the hex-value 0xdeadbeef to the RFID tag and reads the data from the tag. When scanning for the device, it is assumed that the RFID antenna is the only CAN device on the bus, besides the VPU itself.

Step 4: Build, deploy and run the Docker container:

docker build . -t dtm425_example
docker save dtm425_example | ssh -C oem@192.168.0.69 docker load
ssh oem@192.168.0.69 docker run --network host dtm425_example

Note that --network host is required to access the CAN interface.

The output of the last command should look like this:

Writing tag: b'\xde\xad\xbe\xef'
Reading tag: b'\xde\xad\xbe\xef\x00\x00\x00[...]\x00\x00\x00'

For more information on necessary setup steps for building and deployment, please see the linked pages.

Sample point

As defined by the CAN standards, the sample point is where the CAN signal is evaluated as dominant (0) or recessive (1). For example, if the sample point is set to 0.5, this means that the signal sample is taken at 50 % of the bit width. The bit width is equivalent to the inverse of the bitrate, meaning at a bitrate of 250 kbps, the bit width is (1/250kbps) = 4 us. With a sample point of 0.5, the measurements will be taken at half the width of the 4us bit, which will be at (2us + 4us*n), where n represents the bit count (0 for the first bit, 1 for the second bit, and so on).

The sample point does not have to be at the 50 % mark of the bit time; its optimal position depends on the characteristics of the signal. However, placing the sample point in the middle of the bit time is often more stable. At the beginning of the bit time, the signal may not have fully settled, and towards the end, the signal may start to transition to the next bit. Therefore, sampling in the middle helps in capturing a more stable and accurate signal.

Typically, a default sample point of 0.875 (or 87.5 % of the bit time) works well for most signals. However, if the signal is noisy, meaning the rising and falling edges are not clean, or other signal degradations are visible on the signal scope, adjusting the sample point might be necessary to ensure accurate data reception.

Changing the sample point

To demonstrate how to change the CAN sample point on the VPU using a Docker container, we will provide an example of a simple Docker container. This example will show how to modify the bitrate and sample point, run the container on the VPU, and check if the modified parameter are correctly set from within the Docker container. The steps are outlined below.

  1. Create a Dockerfile:

FROM arm64v8/alpine
# Install required packages 
RUN apk add --no-cache iproute2 can-utils
# Create a script to change sample-point and bitrate
RUN echo -e "#!/bin/ash \
    ip link set can0 down \
    ip link set can0 type can bitrate 250000 sample-point 0.5 \
    ip link set can0 up" > /usr/local/bin/setup_can0.sh
# Make the script executable
RUN chmod +x /usr/local/bin/setup_can0.sh
# Run the script when the container starts
CMD ["/usr/local/bin/setup_can0.sh"]
  1. Build, and copy the Docker image into the VPU:

docker build -t can0_setup .
docker save can0_setup | ssh -C oem@192.168.0.69 docker load

For more information’s on how to create a Docker container, please follow the Docker getting started documentation

  1. SSH to the VPU and run the Docker image:

ssh oem@192.168.0.69 
docker run --network host --cap-add NET_ADMIN -it can0_setup sh

Note

Note that --network host is required to access the CAN interface, and --cap-add NET_ADMIN is required to interact with the network stack, allowing for changes to the bitrate and sample-point.

  1. In the Docker container check if the sample point is set correctly:

./usr/local/bin/setup_can0.sh
ip -details -statistics link show can0

The expected output should look like this:

5: can0: <NOARP,UP,LOWER_UP,ECHO> mtu 72 qdisc pfifo_fast state UNKNOWN mode DEFAULT group default qlen 10
    link/can  promiscuity 0 
    can state ERROR-ACTIVE (berr-counter tx 0 rx 0) restart-ms 100 
    bitrate 250000 sample-point 0.5 
    tq 125 prop-seg 6 phase-seg1 7 phase-seg2 2 sjw 1