Experimental "USB to CANbus bridge" mode

I’ve created a new development branch that enables a micro-controller to simultaneously run Klipper and act as a “USB to canbus” adapter. This may be useful for users that have a canbus enabled mainboard and are running a canbus based toolhead board. In that case, this development branch may remove the need for a separate usb to can adapter.

Some boards may also be able to add an external canbus transceiver, which again may reduce the need for a usb adapter.

Finally, the branch may be useful for using some micro-controllers effectively as a usb to can adapter.

The development branch is at: GitHub - KevinOConnor/klipper-dev at work-usbcan-20220608

When running this branch, for some stm32 targets, one can select “usb to canbus bridge” mode. When this feature is enabled the micro-controller will show up as a canbus adapter under Linux. One can then use the standard canbus tools to configure a “can0” interface and run canbus_query.py to find available Klipper canbus micro-controllers. The “bridge mcu” will show up as a Klipper canbus device, along side other devices on the canbus. (The “bridge mcu” isn’t actually on the canbus and wont consume canbus bandwidth, but otherwise appears as if it is a canbus device.)

Note that this feature does not work on the common stm32f103 devices as that chip is not capable of running USB and canbus simultaneously.

When in this mode, the canbus adapter emulation is minimal - bit timing (including canbus frequency) configuration is compiled into the mcu - any Linux configuration that may attempt to set these low-level options will be ignored.

-Kevin

6 Likes

It works!
I put it on one of my STM32F072 based tiny USB to CAN bus adapters boards. The board itself shows up as one klipper node on the CAN bus and any attached CAN boards show up as other.
I assume this will also work with the CAN bus implementation on the rp2040

Two comments:

  • If you do a firmware_restart command the “USB can dongle” gets unplugged and plugged back in. The can interface will be in a down state after that. It has to be taken up again for it to start. This should be solvable with the linux networking scripts.

  • If you use another CAN bus adapter on the bus the klipper node that also acts as a dongle does not respond to CAN command on the bus. If this was not the case you could have a klipper firmware on all boards that can talk CAN also being capable to act as a USB CAN dongle when needed. A very flexible and easy to use setup. It’s probably a minor software change to fix this.

Kevin, this is super cool. Of course I’ve already acquired 3 USB-to-CAN adapters, but I’m sure I’ll find a use for those elsewhere. :slight_smile:

The BTT Octopus v1.1 uses an STM32F446 and has an RJ11 jack with CAN high/low lines. Is this a configuration you’ve tested?

I do plan to port this feature to the rp2040 “software canbus” development branch.

Right, the “bridge mcu” is not actually on the canbus. This was intentional as I didn’t want to consume unnecessary bandwidth on the canbus. (In particular, resonance testing with an adxl345 can consume significant bandwidth.) As you’ve noted, an alternate implementation is possible - but note that the popular stm32f103 chips (and some low pin count f042 chips) can’t do simultaneous usb and canbus.

No, I don’t own that board. Hopefully someone with that board (and similar boards) will be able to test.

Cheers,
-Kevin

That makes sense. To make it work like I’m talking about it would need to detect if it’s the “master” or not and behave slightly different. This feature would be useful if you have a machine where all boards can use the same firmware but only one is connected via USB. Perhaps quite an odd case, I have such a machine.

I got it flashed to a Monster8 V2 last night, shows up with canbus_query.py but I don’t have any RJ11 jacks to make a connector with… yet. Should be here today.

I also have an Octopus Pro F446 and a Spider V2.2 I can test with. The Octopus Pro uses the same RJ11 jack, but it looks like the Spider V2.2 has a 4-pin JST for CAN instead. I’ll be testing all of these.

I pulled the latest code and built a new firmware image.

% make -j4
Loaded configuration '/home/pi/klipper/.config'
Configuration saved to '/home/pi/klipper/.config'
  Creating symbolic link out/board
  Building out/autoconf.h
  Compiling out/src/generic/canserial.o
  Compiling out/src/stm32/can.o
  Compiling out/src/stm32/chipid.o
src/stm32/can.c:69:3: warning: #warning CAN on STM32F4 is untested [-Wcpp]
  #warning CAN on STM32F4 is untested
   ^~~~~~~
  Building out/compile_time_request.o
Version: v0.10.0-473-g5b5988a5
  Linking out/klipper.elf
  Creating hex file out/klipper.bin

But apparently, flashing an STM32F407 with flash_can.py doesn’t work atm.

% ~/klippy-env/bin/python3 ./scripts/canbus_query.py  can0
Found canbus_uuid=f080af642338, Application: Klipper
Total 1 uuids found

% ~/klippy-env/bin/python3 ./lib/canboot/flash_can.py -i can0 -f ~/klipper/out/klipper.bin -u f080af642338
Sending bootloader jump command...
Resetting all bootloader node IDs...
Checking for canboot nodes...
ERROR:root:Can Flash Error
Traceback (most recent call last):
  File "/home/pi/klipper/./lib/canboot/flash_can.py", line 603, in main
    loop.run_until_complete(sock.run(intf, uuid, fpath))
  File "/usr/lib/python3.9/asyncio/base_events.py", line 642, in run_until_complete
    return future.result()
  File "/home/pi/klipper/./lib/canboot/flash_can.py", line 460, in run
    raise FlashCanError(
FlashCanError: Unable to find node matching UUID: f080af642338

That is possible. Getting the flow control could be a bit tricky though. The current flow-control is a bit complex (see src/generic/usb_canbus.c:usbcan_task() for the gory details).

-Kevin

I think the flash_can.py tool will need updates before working in “usb to can bridge” mode. For now, you should just try with Klipper.

You can also flash by manually putting the mcu into bootloader mode by double-clicking reset, and then using the standard “make flash” tool (be sure to use a canboot usb bootloader and not canboot canbus bootloader if using canboot).

-Kevin

1 Like

I think that for the time being DFU will be the best option to update an MCU in “bridge” mode. We can probably use CanBoot’s USB interface to update it, however we will need to come up with a way to request the bootloader.

Oh, I can get it back into DFU mode easily, I was just curious if flashing over CAN would work :slight_smile:

Yep, makes sense and works for me.

This would be awesome.

I put that warning in there when implementing it, I still have not tested with an F4. If someone have tested it and it works we should remove that warning. In theory it should work, the CAN hardware in the F4 is the same as in F0 and F1.

The flash_can.py script is for flashing over CAN, you would need the special CAN bus bootloader for that to work. You can flash this firmware the same way you normally do, over USB or serial.

I tried this out with my BTT Octopus 1.1 (STM32F446) and was successful in getting klippy to talk to it. Unfortunately I’ve lost my RJ11 crimpers so I can’t make a cable at the moment. :no_mouth:

I was not, however, able to figure out how to flash the board without using the µSD card. Not entirely germane to the current topic, what’s that supposed to do? My reset switch is connected to pin 25 NRST (per the schematic). After double-clicking it I don’t see the chip in dfu-util -l, and make flash's various incantations don’t work for me.

This is very promising; I’ll play more when I can make a cable!

I have an Octopus with an F446 chip. I’ve only done basic functionality testing, but it’s working fine for me in bridge mode talking to an EBB42 board using CAN.

This is super exciting! I am trying to test it on my Octopus Pro with HUVUD toolhead. I can’t get mcu to connect though. Could someone give me a hand with getting this going? I have compiled and flashed the firmware to my Octopus Pro (446) board. lsusb gives me Bus 001 Device 006: ID 1d50:606f OpenMoko, Inc. Linux does not seem to find another CAN interface (I have a CAN-HAT on my RPi). Is there something specific I need to do on the RPi to load this interface (dmesg does not seem to show other CAN interface than my current can-hat). Perhaps I’ve done something wrong. This is how my menuconfig looks:

I would suggest removing the hat as it’s not necessary. How do you have the Huvud connected to the Octopus? The middle 2 pins on the RJ11 connector of the Octopus are the CAN pins. I used a phone cord and cut one end off to get access to these pins.

Here’s my current setup. You can see the phone cord at the top in the RJ11 connector, and at the bottom right you can see red and green wires going into wago connectors. That’s the other end of the phone cord. From there I have a twisted pair going into the chamber of my printer where I have 3 CAN boards controlling X,Y, and E.

The issue is I don’t know how to configure the USB-CAN bridge that board is supposed to present itself as. Unless I misunderstood, the MCU (Octopus) will appear as a member on the CAN bus (along with other CAN connected devices such as the toolboard). Did can0 interface appear for you without taking any action on the RPi side?

During initial boot, can0 comes up fine and my boards all connect. If you’re still getting things set up you might need to bring up the CAN interface manually. You’ll also need to bring up the CAN interface if you do a firmware_restart as noted here.

This can be done with the command

sudo ip link set up can0 type can bitrate 500000

Note that if you still have the hat on the Pi, it will show up as can0, and you would need to configure a can1 interface. For the sake of simplicity I would recommend removing the hat so the Octopus comes up as can0.

Once you have the interface up (confirm with ifconfig), you can run

~/klippy-env/bin/python ~/klipper/scripts/canbus_query.py can0

You should see the Octopus itself show up with a uuid, and if wired correctly any other board on the bus will also be displayed in the results.

One other note, if you previously configured the Pi to use the hat, you’ll want to remove the dtoverlay line in your config.txt file and reboot the Pi. Specifically the line you would have copied from here.

1 Like