intrepidcs / icsscand

User-mode SocketCAN daemon for Intrepid devices
BSD 2-Clause "Simplified" License
10 stars 6 forks source link

Slow CANopen block transfer speed #15

Open bensands opened 6 months ago

bensands commented 6 months ago

I am updating device firmware over CAN. Devices are running canopennode stack. On the sender side we are using zephyr/west's firmware update tool (i.e. west flash -r canopen ...) which under the hood uses CANopen for python's block transfer (https://canopen.readthedocs.io/en/stable/sdo.html)

Using the neoVI Fire 3, icsscand, and Intrepid socketcan kernel module, we are sending a 192 Kbyte file in about 4 minutes. For a point of comparison, we have a USB to CAN adapter (https://www.amazon.com/dp/B0CDGGBZDT) and the same file transfer takes 40 seconds.

Also of note, we have verified on an oscilloscope that the bitrate is 1Mbps but there are long gaps between the messages.

bensands commented 6 months ago

I have written a python script to test the block file transfer and remove our application and west's updater from the equation. The python script can be used with CANopenDemo (https://github.com/CANopenNode/CANopenDemo/tree/master). To run the test, do the following:

  1. clone the CANopenDemo repo
  2. build demoLinuxDevice (run make​ in CANopenDemo/demo)
  3. copy CANopenDemo/demo/demoDevice.eds to wherever you will run the python script from
  4. physically connect the two CAN interfaces you are testing. This could be two channels on the neoVI, two USB CAN adapters, or one of each.
  5. run demoLinuxDevice using the interface you are using as the reciever and any node_id. Example: ./demoLinuxDevice can0 -i 4​
  6. run the python script using the interface you are using as the sender and the same node_id as above. Example: python3 canblock.py can1 4​ (The interface name should be different from above but the node_id should be the same.)

The script will send 64 KBytes from the sender interface to the reciever interface. I timed this transfer varying the sender and reciever between NeoVI channels and USB CAN adapters and had the following results:

canblock.py:

# Test script for CANopen block transfer

import canopen
import sys 

if len(sys.argv) < 3:
    print("Usage: python3 canblock.py interface node_id")
    exit()

interface = sys.argv[1]
node_id = sys.argv[2]

# copy this file from CANopenDemo/demo/demoDevice.eds
eds_file = 'demoDevice.eds'

net = canopen.Network()
net.connect(channel=interface, bustype='socketcan')

node = net.add_node(int(node_id), eds_file)

bytes_to_send = 1024 * 64

chunk_size = 1024

# domainDemo expects a repeating sequence of bytes from 0 to 255
data = bytes([i for i in range(256)] * (chunk_size // 256))

sent = 0;

outfile = node.sdo[0x2122].open('wb', size=bytes_to_send)
while sent < bytes_to_send:
    if bytes_to_send - sent < chunk_size:
        chunk_size = bytes_to_send - sent
    outfile.write(data[:chunk_size])
    sent += chunk_size

outfile.close()
kjohannes-intrepidcs commented 2 months ago

Hello @bensands, thanks for your detailed report and example! Apologies for the delayed response. Using your sample script I was able to confirm the behavior as reported. Messages are sent at the correct bit rate but with a time delta of about 4 milliseconds between frames. I did a deep dive on the performance of the kernel driver and icsscand; removing the CANOpen library from the equation and using either canutils (e.g. cangen vcan0 -g 0.15 -D 11223344DEADBEEF -L 8) or PythonCAN I can confirm ~150 microseconds between frames is achievable. I have not yet identified what in the CANOpen library or its interface with our SocketCAN layer is causing this delay.

bensands commented 2 months ago

Thanks for the update, @kjohannes-intrepidcs. I would add that due to the results we had with the USB CAN device, it is probably not an issue inherent to CANopen itself but rather, as you say, the interface between CANopen and socketCAN.