Olimex Olinuxino Lime 2 CAN connection under Debian🔗
- Introduction
- About Olimex
- You need a CAN transceiver
- What should be done?
- DTB? What's that?
- Build a new DTB
Introduction
I wanted to use a CAN bus tool to make diagnostics on my car. Nowadays, thanks to the OBD2 standard, you can have an access to a CAN port via the OBD2 port. I bought a (fake) chinese version of an ELM327, which is a generic and multi-protocol OBD2 connector that you can pilot from a serial connection (via USB). Unfortunately, this fake version was unable to send and receive any real CAN request on the bus other than the standard one.
I suspected that this connector wasn't able to achieve real CAN requests because the producer just handled the basic OBD2 queries or base address and I wanted to verify it. I investigated to find a certified CAN hardware that I could use under one of my computer. After reading stuff into the Car Hacker's handbook, I found that you can have a CAN connection directly referenced under GNU/Linux, not relying on other software than the Linux kernel itself!
By searching after a CAN controller device usable under GNU/Linux, I found that a computer board I previously purchased (to build my personal internet website) was using a SOC with an embedded CAN controller: the Olimex Olinuxino Lime 2. All I had to buy was a CAN transceiver (which I did) and find a way to enable the CAN connection under the system. As all my computers use GNU/Linux Debian, I had to investigate why there were no CAN interface.
You can find how I achieved this by reading this article.
About Olimex
Olimex is a bulgarian Open hardware electronic manufacturer that I recommend for the following reasons:
- It is Open Source hardware: every schemas for every board or circuit produced is publicly published under Open Hardware licence. There is absolutely no surprises or things that are hidden. If I want to dig into the product before buying it (for example want to know which CAN transceiver is used in a shield), you will find all of the important piece of information you want. That's very valuable.
- Most of the time, they use free software for there products and they publish source code (ex for Device Trees or drivers). And yet, most of the time, their boards are natively supported by Debian.
- It is quite cheap, probably not as cheap as chinese producers but not really expensive for French people.
- Quality seems to be good: I have bought an Olinuxino and different shields and other Arduino stuff and it is reliable and well assembled.
- It is produced in European Union, not in China (even if lots of components they use are produced in China, like the microcontrollers, passive electronics, etc.).
- I can pay directly in € and they support SEPA transfers which is quite convenient for me: no need to rely on a third party processing for buying.
- They have a tons of different products in a lot of fields (Lora/Duino/STM32/Linux boards/).
- They are constantly innovating and creating new products: a DIY laptop, Lora shields, etc.
Whenever I have a need for electronic stuff, I always start by exploring Olimex products and I recommend you do the same. Those guys are clearly on the light side of the force.
You need a CAN transceiver
Allwinner A20 has an embedded CAN controller which is directly integrated into the SOC. A CAN controller is like an ethernet controller but for CAN protocol. But you need a CAN transceiver to deal with voltages and current levels: you can't directly put a CAN connection directly into the controller (which is embeded in the SOC). A CAN transceiver is an interface between a CAN protocol controller and the physical bus.
But Olimex provides this with a "shield": the A20 CAN shield.
I also provide a photo of the shield connected to the board (because it is not so obvious):
You need to use the GPIO_3
connector because this is were the CAN_RX
and CAN_TX
pins are located (PH20 and PH21). You can check the reference do understand why.
What should be done?
I've digged the dirty stuff for you and here is what I have learned so far to activate the CAN controller on the board.
I have checked things into the Linux kernel source code packaged under Debian (for stable bullseye distribution) and found that:
- First, the CAN driver for AllWinner A20 is already included into the Debian Kernel (module sun4i-can). No need to compile a new kernel for enabling it.
- I verified that all the CAN configuration is declared into the Kernel DTS inthe include file
sun7i-a20.dtsi
. - All we have to do is to "activate" the CAN controller in the Olinuxino board DTS.
Olimex got a set of instructions on its wiki, that I have sumarized in two points:
- Add stuff to
arch/arm/boot/dts/sun7i-a20.dtsi
:
can0_pins_a: can0@0 { allwinner,pins = "PH20","PH21"; allwinner,function = "can"; allwinner,drive = <0>; allwinner,pull = <0>; }; ... can0: can@01c2bc00 { compatible = "allwinner,sun4i-a10-can"; reg = <0x01c2bc00 0x400>; interrupts = <0 26 4>; clocks = <&apb1_gates 4>; status = "disabled"; };
- Modify dts file
arch/arm/boot/dts/sun7i-a20-olinuxino-lime2.dts
&can0 { pinctrl-names = "default"; pinctrl-0 = <&can0_pins_a>; status = "okay"; };
DTB? What's that?
Ok, time to deal with DTB on Linux Kernel. I am no specialist, but after listening to this video and reading documentation, here what I have understood so far:
- DTB means Device Tree Blob. It is mainly a way to tell to the kernel what is the hardware it is running on and how to access physical devices (more than this of course, but I need to keep explanations short).
- The DTB file is used by bootloaders to inject Device Tree into Kernel at the start of system initialisation. U-Boot do this.
- a DTB is a blob, so a binary file "compiled" from a set of files named as the DTS: the Device Tree Source.
- the DTS is embedded into the Linux kernel source code, stored in the
arch/arm/boot/dts
directory. - the DTS compiler is a program named
dtc
.
Build a new DTB
Introduction
So, all we need to do is to modify the Device Tree Source for the Olinuxino Lime2 board under the kernel source code to activate the CAN controller. You can do this without too much thinking with modifying the Device Tree source and rebuilding the kernel. But on an arm machine like the Olinuxino, it would take about 2 or 3 hours and a lot of space.
Instead of this bulk method, you can choose a lighter way by only producing a modified Device Tree Blob (DTB) and using it on boot. That's what I describe from now.
Prerequisites
First we need to install the Linux kernel source code which has been used by Debian to compile the kernel which is installed in the actual machine. I do this because I will be sure that there will be no suprisesat all: this is the same kernel (with the exact revision and configuration) than the one which is running on the machine. Then, we need to install some Debian packages in order to be able to generate the blob:
apt install linux-source libncurses-dev rsync build-essential libssl-dev
Once installed, you can find a compressed source tree into /usr/src/linux-source-5.10.tar.xz
that you can decompress:
$ mkdir ~/kernel; cd ~/kernel $ tar -xaf /usr/src/linux-source-5.10.tar.xz
You can read all the DTS files directly into ~/kernel/linux-source-5.10/arch/arm/boot/dts
. Once the source code is extracted, all we need to do is to modify the DTS used for our board. But hell, what is the stuff I need to modify?
Understanding what we are doing
You can easily find the name of the file used as a DTB by U-boot by looking into the boot directory at the link /boot/dtb
. On my system it points to dtbs/5.10.0-12-armmp-lpae/./sun7i-a20-olinuxino-lime2-emmc.dtb
and you can deduce from this that the main source for the DTS used on this machine is a file named like sun7i-a20-olinuxino-lime2-emmc
. Bingo, there is ~/kernel/linux-source-5.10/arch/arm/boot/dts/sun7i-a20-olinuxino-lime2-emmc.dts
.
I am not displaying the content of the source file because all this file does is reference another dts file (with C #include pre-processing instruction): sun7i-a20-olinuxino-lime2.dts
and declare an additional internal emmc interface to this board sun7i-a20-olinuxino-lime2.dts
So we have to inspect this file and compare its content against the instructions provided by Olimex.
If you search in sun7i-a20-olinuxino-lime2.dts
, you will not find any mention of the following text:
&can0 { pinctrl-names = "default"; pinctrl-0 = <&can_ph_pins>; status = "okay"; };
All you have to to is to add those lines at the end of the file. This DTS part means that we are going to:
- add a new interface named from
&can0
. - it will use the
default
pin names. - the pin control definition will comme from
&can_ph_pins
. It declares on which pins the interface will control. - status at okay means that the device will be enabled by default.
Ok, so far, adding this to the DTS will enable the interface. But what about &can0
and &can_ph_pin
properties? In the DTS language, they are named address cells. So if we want to fully understand, what we are doing, we need to find what is declared at those adresses.
In sun7i-a20-olinuxino-lime2.dts
, sun7i-a20.dtsi
DTS include file is included by default. sun7i-a20-olinuxino-lime2.dts
is declaring specific stuff for the Olimex olinuxino lime2. But there are other boards based on A20 (named sun7i-a20 architecture). If you grep can0
in sun7i-a20.dtsi
, you will find the declaration of &can0
as:
can0: can@1c2bc00 { compatible = "allwinner,sun7i-a20-can", "allwinner,sun4i-a10-can"; reg = <0x01c2bc00 0x400>; interrupts = <GIC_SPI 26 IRQ_TYPE_LEVEL_HIGH>; clocks = <&ccu CLK_APB1_CAN>; status = "disabled"; };
Here you can read that can0 is:
- using either sun7i-a20-can or sun4i-a20-can module. The Debian kernel package provides sun4i-a20-can module (named
sun4i_can
). So there is no need to recompile the kernel to get the module. - There is a declared adress (reg) for the device. It means that it is a memory mapped interface (you read/write and control it with reading/writing to a dedicated place into memory machine). For can0, the memory space is 1024 bytes (0x400) at 0x01c2bc00.
- status is disabled by default for all sun7i boards. It is superseded by our status declared as okay into the upper DTS file.
If you grep can_ph_pins
in sun7i-a20.dtsi
, you will find the declaration of can_ph_pins
as:
can_ph_pins: can-ph-pins { pins = "PH20", "PH21"; function = "can"; };
It simply declares two pins for the can function: PH20 and PH21. And if you remember about A20 CAN shield before, PH20 and PH21 are referenced in the Olinuxino GPIO reference as CAN_RX
and CAN_TX
pins are located (PH20 and PH21). The DTS declaration matches the hardware stuff, everything seems to be all right.
To sum it up, a lot of stuff is already well declared in the Kernel DTS:
- can interface configuration is already handled at the DTS SOC level.
- can pins are already well declared for the SOC.
So, all we have to do is to add the following text at the end of sun7i-a20-olinuxino-lime2.dts
:
&can0 { pinctrl-names = "default"; pinctrl-0 = <&can_ph_pins>; status = "okay"; };
Compile and install the DTB
Once the file is modified, we can produce the blob. It will take two instructions:
- First we need to preprocess the DTS includes to build a unique .dts file. Remember, Device Tree Source is mostly a C preprocessing file declaring a big struct (sort of).
- Then we can use this merged file to compile and produce our DTB with
dtc
.
cpp -nostdinc -I include -undef -x assembler-with-cpp arch/arm/boot/dts/sun7i-a20-olinuxino-lime2-emmc.dts > test.dts dtc -I dts -O dtb -o ./test.dtb test.dts
Once done, ./test.dtb
is our Device Tree Blob and it is nearly ready to be installed and deployed. We are going to use flash-kernel
because this is the official tool to deploy kernels and DTBs to the bootloader (U-Boot). All dtb files (for all supported systems) are stored into /usr/lib/linux-image-5.xxxxx/
directory (/usr/lib/linux-image-5.10.0-12-armmp-lpae
in our case with version 5.10 under Debian). Those files are used by flash-kernel and copied into /boot
directory.
All we have to do is to make a backup of the original DTB for our board, overwrite it with the new version and update bootloader configuration with flash-kernel:
# cp /usr/lib/linux-image-5.10.0-12-armmp-lpae/sun7i-a20-olinuxino-lime2-emmc.dtb /home/user/kernel/sun7i-a20-olinuxino-lime2-emmc.dtb # cp /home/user/kernel/linux-source-5.10/test.dtb /usr/lib/linux-image-5.10.0-12-armmp-lpae/sun7i-a20-olinuxino-lime2-emmc.dtb # flash-kernel
And reboot!
After that, you should have a can0 connection referenced by ip a
:
3: can0: <NOARP,ECHO> mtu 16 qdisc noop state DOWN group default qlen 10 link/can
Congratulations, job's done!
Conclusions
I don't know why the Debian Kernel dts doesn't add the CAN controller by default. I suspect this is because whenever there is no CAN transceiver, it is dangerous to enable it directly in case a wrong connection is made on the wrong GPIO.
Now you have some basic information about DTB, DTS and file organisation. I could have elaborated a bit more about DTB but other people (and the Linux Kernel documentation) are doing better than I can ever do. My goal was simply to enable a CAN controller on an A20 SOC board. Next step, connect the Olinuxino to the car, but this another story!