Intro
Challenge you may wonder? Yes! For me at least and for the reasons I will explain later, communicating with a CAN device using the Jetson was not that straight-forward! In our case, the can device is a Continental Radar that is streaming data at 500 Kbps. To connect our CAN device with the Jetson, we created our own interfacing daughterboard using the MCP25625 CAN controller (Figure ).
Well one of the challenges was to “try” to debug the CAN driver using a faulty board! Nightmare! I was working under the assumption that our hardware was solid, but it turned out, that there was a problem with our level shifter! So, as my first advice to you – and an obvious one:
- make sure your hardware has been debugged!
Anyway, for this guide I will be using a breadboard with the mcp2515 chip (CAN SPI click 5V). See also hardware diagram below.
Before you continue to read…
the post from David Cofer at neurorobotictech.com which describes how to use the SPI of the Jetson TK1. This series of articles helped me really a lot to understand how to use the SPI and understand the challenges! Thank you David!
The Hardware Diagram
If you red the article from David Coder, you immediately figured out, that we cannot interface either the MCP2515 or the MCP25625 directly to the Jetson. The reason for that is that the GPIO pins of interest (SPI0 / GPIO_PK2) operate at 1.8V, while the corresponding pins of the MCP2515 operate at 5V. For that, it is need to utilize a lever shifter. The below schematic shows how to connect the CAN SPI click 5V with the board with the use of a level shifter.
My choice here, was the TI LSF0108 – 8 Channel Bidirectional Multi-Voltage Level Translator. This chip declares that it can support high speed translations, up to 100MHz and has a propagation delay of ~1ns (1.8 <-> 5V ). These specifications are good enough to serve our SPI communication rated at 5MHz.
Note: When choosing a lever shifter, make sure its propagation delay won’t affect the SPI communication. I would suggest using a lever shifter with a maximum propagation/transition delay of 1.5ns.
Download Schematic:
[wpdm_package id=’1677′]
Interrupt Pin: Note the interrupt pin here. I am using pin GPIO_PK2. Of course you might want to use another pin, which is perfectly fine! In any case, take note of this, as we are going to need it later when configuring the mcp251x kernel module. Below you can find a map of the pins of the J3A1 and J3A2 connectors.
Getting the Kernel ready
At this point you are done with the hardware. The CAN SPI click 5V is connected to the Jetson and all we need to do now is to enable the MCP251x driver of the Linux kernel. This is a two step process where
- first, the kernel needs to be patched to explicitly declare that we are going to connect a MCP251x chip to the SPI0
- secondly we need to configure the kernel prior compilation to enable the use of the MCP251x kernel module.
This method was tested with the following systems:
- NVIDIA L4T 19.3.0 and The Grinch 19.3.6 Linux Kernel (kernel 3.10.24)
- How to install Grinch Linux4Tegra (L4T) version 19.3.6 for NVIDIA Jetson TK1
- The Grinch 19.3.6 Kernel Sources (md5sum 36c77e5312353d0989733dd5d0159162)
- NVIDIA L4T 21.4.0 and The Grinch 21.3.4 Linux Kernel (kernel 3.10.40)
- How to install Grinch Linux4Tegra (L4T) version 21.4.0 for NVIDIA Jetson TK1
- The Grinch 21.3.4 Kernel Sources (md5sum 6115b927e25c0c757701351b2bde8e3a)
Patching the Kernel
At this point we need to do some special configuration in the kernel tree to be able to use the mcp251x kernel module.
We are going to alter (patch) the board-ardbeg.c file located at /arch/arm/mach-tegra/. I figured this out by looking at the “NVIDIA TEGRA LINUX DRIVER PACKAGE, Developers Guide”, page 6, where it states that
ardbeg stands for Tegra K1 32 Bit (T12x) devices
The board-ardbeg.c file contains special configuration with respect to the Jetson TK1 board and its connected devices. There, we need to specify that we are going to connect the MCP2515 chip to the SP0.
Identifying the correct SPI interface
You might skip this part if you are not interested to know how to identify which SPI interface is exposed to the pins of the J3A1 connector(referred by NVIDIA as Touch SPI) . Looking at the Technical Reference Manual (TRM) v03p, page 23 at the Address Map, we can see that up to 6 SPI interfaces are being supported: SPI1-SPI6 (snippet of the table bellow).
Description | Address Start | Address End | Offset Start | Offset End | Default Length |
SPI 2B-1 | 7000:d400 | 7000:d5ff | 0000:d400 | 7000:d5ff | 256 B |
SPI 2B-2 | 7000:d600 | 7000:d7ff | 0000:d600 | 7000:d7ff | 256 B |
SPI 2B-3 | 7000:d800 | 7000:d9ff | 0000:d800 | 7000:d9ff | 256 B |
SPI 2B-4 | 7000:da00 | 7000:dbff | 0000:da00 | 7000:dbff | 256 B |
SPI 2B-5 | 7000:dc00 | 7000:ddff | 0000:dc00 | 7000:ddff | 256 B |
SPI 2B-6 | 7000:de00 | 7000:dfff | 0000:de00 | 7000:dfff | 256 B |
But which of those six, is the touch SPI found on the J3A1 connector? Digging some more at the Embedded Platform Design Guide, page 81 SPI chapter, you will come across the following:
So, here, we can already find a good hint that SP1 – the touch SPI – is being exposed to the connector. But wait?? You mentioned that will enable the mcp251x kernel module at SPI0! Correct! And this might be a bit confusing, but in the device tree /arch/arm/boot/dts/tegra124-soc.dtsi :
spi0: spi@7000d400 { compatible = "nvidia,tegra114-spi"; reg = <0x7000d400 0x200>; interrupts = <0 59 0x04>; nvidia,dma-request-selector = <&apbdma 15>; nvidia,memory-clients = <14>; #address-cells = <1>; #size-cells = <0>; status = "disabled"; };
and ultimately in the user space the SPI1 is being referred to as spi0.
Patching board-ardbeg.c
In order to figure out what is needed to register the mcp251x as a SPI device you can take a look at the source file of the mcp251x driver, namely mcp251x.c :
36 * Your platform definition file should specify something like: 37 * 38 * static struct mcp251x_platform_data mcp251x_info = { 39 * .oscillator_frequency = 8000000, 40 * .board_specific_setup = &mcp251x_setup, 41 * .power_enable = mcp251x_power_enable, 42 * .transceiver_enable = NULL, 43 * }; 44 * 45 * static struct spi_board_info spi_board_info[] = { 46 * { 47 * .modalias = "mcp2510", 48 * // or "mcp2515" depending on your controller 49 * .platform_data = &mcp251x_info, 50 * .irq = IRQ_EINT13, 51 * .max_speed_hz = 2*1000*1000, 52 * .chip_select = 2, 53 * }, 54 * }; 55 * 56 * Please see mcp251x.h for a description of the fields in 57 * struct mcp251x_platform_data.
Now it is obvious that we need to define these two structs and fill them up with the correct data:
static struct mcp251x_platform_data mcp251x_info = { .oscillator_frequency = 10 * 1000 * 1000, /* CAN SPI click 5V has a 10MHz crystal */ .irq_flags = IRQF_TRIGGER_FALLING|IRQF_ONESHOT, /* This is also defined in the mcp251x driver: can be ommited */ .board_specific_setup = NULL, /* We don't have a board specific setup */ .power_enable = NULL, /* We don't want any power enable function */ .transceiver_enable = NULL, /* We don't want any transceiver enable function */ };
[ish_tgg_acc color=”color5″ text_color=”color7″ contents_color=”color3″][ish_tgg_acc_item el_title=”mcp251x_platform_data – Fields explained” icon=”ish-icon-lightbulb”]
- oscillator_frequency : The frequency of the oscillator being used to clock the MCP2515 (MCP25625) chip. CAN SPI click 5V uses a 10 MHz crystal
- irq_flags : The mcp251x driver uses Interrupt Threads to handle the communication interrupt request from the MCP chip. Thus it registers for a threaded interrupt handler.
- IRQF_TRIGGER_FALLING : The handler is being called on the falling edge of the interrupt signal.
- IRQF_ONESHOT : This allows drivers to request that the interrupt is not unmasked after the hard interrupt context handler has been executed and the thread has been woken. The interrupt line is unmasked after the thread handler function has been executed.
- Check here all IRQ Flags
- board_specific_setup : This is called before probing the chip (power,reset).
An example:
static int customboard_mcp2515_setup(struct spi_device *sdev) { printk(KERN_DEBUG "customboard_mcp2515_setup: Entryn"); // Put your board specific code here, e.g.: set_GPIO_periph(PIN_PA3, 0); gpio_direction_output(PIN_PA3, 1); set_gpio_value(PIN_PA3, 1); // end of your board specific code return 0; }
- power_enable : This called to power on/off the mcp *and* the transceiver.
An example:
static int your_mcp2515_power_enable(int enable) { printk(KERN_DEBUG "your_mcp2515_power_enable: Entry %dn", enable); return 0; }
- transceiver_enable : This is called to power on/off the transceiver.
An example:
static int your_mcp2515_transceiver_enable(int enable) { printk(KERN_DEBUG "your_mcp2515_transceiver_enable: Entry %dn", enable); return 0; }
Note: you should define power_enable or transceiver_enable or none of them. Defining both of them is no use.
static struct spi_board_info mcp251x_spi_board[1] = { { .modalias = "mcp2515", /* or "mcp2510" depending on your controller */ .platform_data = &mcp251x_info, /*.irq = unknown, defined later in mcp251x_init() */ .max_speed_hz = 5*1000*1000, .chip_select = 0, .bus_num = 0, .mode = SPI_MODE_0, /* MCP2515 supports MODE_0 (0,0) and MODE_3 (1,1) */ }, };
[ish_tgg_acc color=”color5″ text_color=”color7″ contents_color=”color3″][ish_tgg_acc_item el_title=”spi_board_info – Fields explained” icon=”ish-icon-lightbulb”]
- platform_data : This is the struct you created above (static struct mcp251x_platform_data mcp251x_info )
- irq : You don’t need to assign an IRQ now. This will generated later at the mcp251x_init() fuction using gpio_to_irq
- max_speed_hz : This is the desired SPI clock frequency.
Important Note: The clock that is being used by default for spi0, allows max = 25MHz & min = 3.17509 Mhz
In detail, looking at the Table 17: Root Clock Sources (1 of 2) (page 62 of the TRM), we can see that SPI1 (our spi0) has the following clock sources:
000=pllP_out, 001=pllC2_out, 010=pllC_out, 011=pllC3_out, 100=pllM_out, 110=dbg_oscout (See snippet of the table below)
oscout | pllC_out | pllC2_out | pllC3_out | ck32khz_IB | pllM_out | pllM_out_for_emc | pllP_out | … | |
spi1_clk_t | 6 | 2 | 1 | 3 | 4 | 0 | |||
spi2_clk_t | 6 | 2 | 1 | 3 | 4 | 0 | |||
spi3_clk_t | 6 | 2 | 1 | 3 | 4 | 0 | |||
spi4_clk_t | 6 | 2 | 1 | 3 | 4 | 0 | |||
spi5_clk_t | 6 | 2 | 1 | 3 | 4 | 0 | |||
spi6_clk_t | 6 | 2 | 1 | 3 | 4 | 0 |
And from Table 18: Root Clock Sources (2 of 2) we can see that SPI1 (our spi0) has an 8-bit wide U7.1 fractional divider.
- From the SPI driver spi-tegra124.c we can see that: #define SPI_DEFAULT_SPEED 25000000 witch means that max SPI freq = 25MHz
- But also looking at the struct tegra_clk_init_table ardbeg_clk_init_table[] inside the board-ardbeg.c SPI1 uses pllP_out clock source.
- pllP_out: This is the PLLP’s output clock which is set to 408 MHz (TRM, page 57, Table 14: Primary Clock Sources in Tegra K1 Devices).
- “sbc1” refers here to the clock sources of SPI1 (see struct clk tegra_list_clks[] at the tegra12-clocks.c file). So at the board-ardbeg.c the clock is configured for max frequency of 25 MHz as well.
- From the tegra12-clocks.c file we can see that we can also use the following clock sources: pll_a_out0, pll_c, pll_p, clk_m.
- Looking at the CLK_RST_CONTROLLER_CLK_SOURCE_SPI1_0 (page 130 of the TRM) we can see that bits 7:0 = SPI1_CLK_DIVISOR: N = Divide by (n+1). (lsb denotes 0.5x).
So: min SPI freq = 408 / [(0xFF * 0.5) +1] = 3.17509 MHz using the pllP_out clock.
- chip_select: Chip select should be 0. We have only one CS and one Slave device.
- bus_num: Bus number should be 0.
- mode: The MCP2515/MP25625 supports SPI Modes 0,0 and 1,1.
Mode Clock Polarity (CPOL) Clock Phase (CPHA) SPI_MODE0 0 0 SPI_MODE1 0 1 SPI_MODE2 1 0 SPI_MODE3 1 1
Then we need to write a small initialization function and add it in the static void __init tegra_ardbeg_late_init(void) function.
static int __init mcp251x_init(void) { mcp251x_spi_board[0].irq = gpio_to_irq(CAN_GPIO_IRQ_MCP251x_SPI); // #define CAN_GPIO_IRQ_MCP251x_SPI TEGRA_GPIO_PK2 spi_register_board_info(mcp251x_spi_board, ARRAY_SIZE(mcp251x_spi_board)); pr_info("mcp251x_initn"); return 0; }
Important: The CAN_GPIO_IRQ_MCP251x_SPI is here the pin that we used earlier in our Hardware Schematic. We choose to drive the interrupt using GPIO Pin PK2. So here:
#define CAN_GPIO_IRQ_MCP251x_SPI TEGRA_GPIO_PK2
and here is a snippet of the static void __init tegra_ardbeg_late_init(void) function:
edp_init(); isomgr_init(); //ardbeg_touch_init(); // deleted! #ifdef MCP251x mcp251x_init(); #endif if (board_info.board_id == BOARD_E2548 || board_info.board_id == BOARD_P2530) loki_panel_init(); else ardbeg_panel_init();
Note: I would suggest removing also every ardbeg_touch related stuff.
[ish_tgg_acc color=”color9″ text_color=”color1″ contents_color=”color3″][ish_tgg_acc_item el_title=” Why don’t you use the Device Tree??” icon=”ish-icon-attention”]
Well some of you might wonder why we are not using the Device Tree – also referred to as Open Firmware (abbreviated OF) or Flattened Device Tree (FDT) – to configure our MCP2515 device.
Initially I went down this road, but without success! 🙁 However I will post my efforts in case some one wants to take over.
So there are two way you can edit the device tree information.
- Edit the tegra124-jetson_tk1-pm375-000-c00-00.dts found in arch/arm/boot/dts/ .
- Use the following:
// I am not sure about this. This needs to be investigated further gpio_pk2: gpio_pk2 { nvidia,pins = "pk2"; nvidia,function = "spi1"; nvidia,pull = <TEGRA_PIN_PULL_DOWN>; nvidia,tristate = <TEGRA_PIN_ENABLE>; nvidia,enable-input = <TEGRA_PIN_DISABLE>; }; clocks { clk16m: oscillator { #clock-cells = <0>; compatible = "fixed-clock"; clock-frequency = <16000000>; }; }; spi@7000d400 { status = "okay"; spi-max-frequency = <25000000>; #if 0 spi0_0 { #address-cells = <0x1>; #size-cells = <0x0>; compatible = "spidev"; reg = <0x0>; spi-max-frequency = <0x17d7840>; spi-cpha; nvidia,enable-hw-based-cs; nvidia,cs-setup-clk-count = <0x1e>; nvidia,cs-hold-clk-count = <0x1e>; nvidia,rx-clk-tap-delay = <0x1f>; nvidia,tx-clk-tap-delay = <0x0>; }; #else can0: can0@0 { compatible = "microchip,mcp2515"; reg = <0x0>; clocks = <&clk16m>; interrupt-parent = <&gpio_pk2>; interrupts = <27 0x2>; spi-max-frequency = <10000000>; }; #endif };
-
- Note: As you can see I am also keeping the spidev information. You can of course remove this
- (Cross) Compile it: make tegra124-jetson_tk1-pm375-000-c00-00.dtb ARCH=arm CROSS_COMPILE=<cross_compiler_loc>
- Copy it over to you board at: /boot/dtb/
- From your board and with the assumption that you have downloaded the kernel source files: Use the dtc tool found in <kernel_source_path>/scripts/dtc/ .
- sudo ./scripts/dtc/dtc -I dtb -O dts -o /boot/tegra124-pm375.dts /boot/tegra124-jetson_tk1-pm375-000-c00-00.dtb
- Then add the correct information:
// I am not sure about this. This needs to be investigated further gpio_pk2: gpio_pk2 { nvidia,pins = "pk2"; nvidia,function = "spi1"; nvidia,pull = <TEGRA_PIN_PULL_DOWN>; nvidia,tristate = <TEGRA_PIN_ENABLE>; nvidia,enable-input = <TEGRA_PIN_DISABLE>; }; clocks { clk16m: oscillator { #clock-cells = <0>; compatible = "fixed-clock"; clock-frequency = <16000000>; }; }; spi@7000d400 { status = "okay"; spi-max-frequency = <25000000>; can0: can0@0 { compatible = "microchip,mcp2515"; reg = <0x0>; clocks = <&clk16m>; interrupt-parent = <&gpio_pk2>; interrupts = <27 0x2>; spi-max-frequency = <10000000>; }; };
-
- Regenerate the dtb file from the dts: ./scripts/dtc/dtc -I dts -O dtb -o /boot/tegra124-pm375.dtb /boot/tegra124-pm375.dts
- More details on how to use this method here.
Configuring the Kernel
In this section we are going to configure the kernel to enable the mcp251x module.
Cross-compiling?
For those that are cross-compiling the kernel, make sure you declared the following variables:
export ARCH=arm export PATH=<path_to_your_crosscompiler_bin>:$PATH export CROSS_COMPILE=arm-linux-gnueabihf-
I am using the Linaro Toolchain (gcc-linaro-arm-linux-gnueabihf-4.8-2014.04_linux).
menuconfig
The easiest way is to use make menuconfig . But in any case the following configuration needs to be present:
CONFIG_CAN=m CONFIG_CAN_RAW=m CONFIG_CAN_BCM=m CONFIG_CAN_GW=m CONFIG_CAN_VCAN=m CONFIG_CAN_DEV=m CONFIG_CAN_CALC_BITTIMING=y CONFIG_CAN_MCP251X=m
Or when using menuconfig :
- Enable the mcp251x driver module:
- Make also sure you enabled the SPI driver and specifically the Tegra114/124 SPI Controller:
Compile kernel, kernel-modules
Once you configured the kernel accordingly you can proceed in (cross) compiling it:
make zImage
Compile the modules:
make modules
Install the modules:
make modules_install INSTALL_MOD_PATH=<path_to_install_modules>
If you are cross-compiling you can choose whatever path you want. This path will contain all the kernel modules which you have to copy them over to the board. The final modules installation path should be (in the board): /lib/modules/
Copy kernel, kernel-modules
After successful compilation you need to copy the kernel image arch/arm/boot/zImage to /boot/ . Also make sure:
- You have copied the modules as well or used the correct install path /lib/modules/
- See if you need to copy the correct device tree information .dtb to /boot/dtb/
You are done! Reboot!
The Result
mcp251x loaded?
If everything went well you should be able to verify that the mcp251x kernel module has been successfully loaded:
> dmesg | grep mcp251x [ 9.180598] mcp251x spi0.0: setup 8 bpw, ~cpol, ~cpha, 5000000Hz [ 9.180712] mcp251x spi0.0: setup mode 0, 8 bits/w, 5000000 Hz max --> 0 [ 9.180724] mcp251x spi0.0: mcp251x_hw_probe(). Trying mcp251x_hw_reset(). [ 9.207421] mcp251x spi0.0: CANSTAT 0x80 CANCTRL 0x07 [ 9.208906] mcp251x spi0.0: probed
or
> ls /sys/class/net/ can0 dummy0 eth0 ip6tnl0 lo rmnetctl sit0
configure mcp251x
In our case, then can device was operating at 500Mbs. So:
sudo ip link set can0 up type can bitrate 500000
You can already verify at this point that the there is some CAN communication:
can-utils
Install
Of course if you want to get a better insight and see some actual CAN comunication, you can use the can-utils to dump some of the CAN data.
Install can-ultis from repositories:
nano /etc/apt/sources.list # make sure you uncomment the 'universe'repository sudo apt-get update && apt-get install can-utils
Or manually install it:
git clone git://gitorious.org/linux-can/can-utils.git cd can-utils make sudo make install
Use
After you install can-utils you are able to dump CAN messages:
candump can0
or even send
cansend can0 -i 0x123 0xaa 0xbb 0xcc 0xdd
CAN Messages being dropped
Immediately after bringing the CAN-interface (can0) up and looking into the statistics I realized that I was not receiving all the CAN messages (See figure below but also above).
What are those errors??? What is causing them??? Looking at the MCP2515 Manual:
An overflow condition occurs when the MAB has assembled a valid receive message (the message meets the criteria of the acceptance filters) and the receive buffer associated with the filter is not available for loading of a new message. The associated RXnOVR bit in the EFLG register will be set to indicate the overflow condition. This bit must be cleared by the microcontroller.
And then looking the driver – mcp251x.c – at the static irqreturn_t mcp251x_can_ist(int irq, void *dev_id) function:
if (intf & CANINTF_ERRIF) { /* Handle overflow counters */ if (eflag & (EFLG_RX0OVR | EFLG_RX1OVR)) { if (eflag & EFLG_RX0OVR) { net->stats.rx_over_errors++; net->stats.rx_errors++; } if (eflag & EFLG_RX1OVR) { net->stats.rx_over_errors++; net->stats.rx_errors++; } can_id |= CAN_ERR_CRTL; data1 |= CAN_ERR_CRTL_RX_OVERFLOW; } mcp251x_error_skb(net, can_id, data1); }
We can see that these errors are reported due to overflow errors in the MCP2515 chip. Which in turn means, that the mcp251x driver and in turn the SPI driver is not reading the messages from the MCP2515 chip fast enough. To elaborate further, although our SPI communication is able to handle data rates of 5Mbps (mcp251x driver is configured at 5MHz), the OS is the one that cannot cope with the frequency of the interrupts!
The interrupts, as mentioned above, are being served by a threaded interrupt handler. In a standard OS (SMP PREEMPT), all IRQ-threads run with soft-rt priorities of 50 and together with the rt-scheduler SCHED_FIFO. This means that the SPI IRQ-handler can be preemted at any time and/or might not be served at the exact time of the interrupt! And this is actually the reason why we are losing CAN messages: The current OS does not give us any guarantee that the IRQ will be served on time and/or the IRQ-Thread will have the chance to finish handling it!
This issue has been reported also in the past:
Important notice: We can see also that one CPU-core is working at ~50% on an idle system, serving the SPI communication!
To view the priorities of the processes use ps -eo pid,pri,rtprio,cmd | grep ‘mcp|spi’ :
ps -eo pid,pri,rtprio,cmd | grep 'mcp|spi' 84 19 - [spi0] 88 19 - [spi3] 1612 19 - /usr/lib/at-spi2-core/at-spi-bus-launcher --launch-immediately 1633 19 - /bin/dbus-daemon --config-file=/etc/at-spi2/accessibility.conf --nofork --print-address 3 1695 19 - /usr/lib/at-spi2-core/at-spi2-registryd --use-gnome-session 3067 90 50 [irq/259-mcp251x] 3068 39 - [mcp251x_wq] 3083 19 - grep --color=auto mcp|spi
So here, the IRQ-thread irq/259-mcp251x, is running with rtprio of 50 as expected.
A possible optimization
As a first possible optimization we will try and change the rtprio of the irq/259-mcp251x using the chrt command. First of all, lets see our options:
chrt -m SCHED_OTHER min/max priority : 0/0 SCHED_FIFO min/max priority : 1/99 SCHED_RR min/max priority : 1/99 SCHED_BATCH min/max priority : 0/0 SCHED_IDLE min/max priority : 0/0
Using a rtprio higher than 50, we were able to get a better performance out of the mcp251x driver. Less than 1% is being lost now in an idle system (load ~2.0). The CPU-Core now runs at ~65%.
And the CAN statistics after running it a few days:
CAN Messages not received in order
Well, I also came across an interesting issue: The CAN messages were not received in order they were sent. They came into some random order. This issue, still needs to be investigated.
Not good enough!
Well, you may think that having a communication with a frame loss of <<1% is good enough… but not in our case! At this point any effort on the standard kernel will be on hold since we are working on a RT patch. I am hoping that under a RT-kernel the driver would perform even better given the smaller scheduling / timer latencies.
Current Conclusion – A recommendation
Well, even if they above issues can be fixed, the high CPU usage will always be there if you have a high CAN-bandwidth utilization! The high interrupt frequency will result in high CPU utilization! In that case I would suggest to follow a different path:
One can use a microcontroller and place it between the CAN bus and the Jetson TK1.
This solution has both advantages and disadvantages of course:
Advantages:
- The MCU can offer more than on CAN interface, but also other interfaces like LIN, FlexRay, ETH, etc
- The MCU can buffer the data and transmit larger chunk of it to the Jetson. This way the interrupt frequency will be substantially lower and in turn its CPU utilization.
- The MCU can do some pre-processing on the data.
- The MCU offers extra functionalities/options and thus “enhances” the Jetson
Disadvantages:
- Buffering data in the MCU can cause unwanted delays (latency).
- Custom software/driver needs to be implemented to support the new architecture.
I believe that the advantages are more than the disadvantages, making this recommendation a robust and versatile solution. Of course some effort needs to be put, to realize the supporting software for this architecture.
Useful links
Jetson TK1 Related
- Jetson TK1 – GPIO
- The Grinch 21.3.4 Cached NVIDIA Post
- Using the Jetson TK1 SPI – Part 1 (Why is SPI important)
Documentation/Manuals
SocketCAN
Threaded Interrupt Handlers
- Moving interrupts to threads (article)
- chrt command: Set / Manipulate Real Time Attributes of a Linux Process
- sched_setscheduler(2) – Linux man page
- How to establish a priority to an interrupt handler (mailing list)
- Regarding threaded-irq (mailing list)
- API: request_threaded_irq
- How does SCHED_FIFO and SCHED_RR interfer with each other?