How to use the CAN bus of a Beagle Bone Black from Erlang


May 2015.
Communication through a CAN bus between a Beagle Bone Black and a STM32F302 Nucleo Image

Let's try using the CAN bus device of a Beagle Bone Black from Erlang.

Here's some information about the system present on the board:

# uname -a
Linux beaglebone 3.14.40-ti-r62 #1 SMP PREEMPT Thu Apr 30 18:28:06 UTC 2015 armv7l GNU/Linux

# cat /etc/issue
Debian GNU/Linux 8 \n \l

BeagleBoard.org Debian Image 2015-05-01

Testing the CAN driver


Let's create a virtual CAN device:

# modprobe vcan
# ip link add dev vcan0 type vcan
# ifconfig vcan0 up
# ip -details -statistics link show vcan0
5: vcan0: mtu 16 qdisc noqueue state UNKNOWN mode DEFAULT group default
link/can promiscuity 0
vcan
RX: bytes packets errors dropped overrun mcast
0 0 0 0 0 0
TX: bytes packets errors dropped carrier collsns
0 0 0 0 0 0

Let's try sending/receiving data on that virtual device:

# cansend vcan0 442#DEADBABE

On another terminal:

# candump vcan0
vcan0 442 [4] DE AD BA BE

Using the CAN driver from Erlang


I'll be using the excellent CAN module from Tony Rogvall: https://github.com/tonyrog/can.

Let's create a project from the following rebar.config:

{lib_dirs,["deps"]}.
{sub_dirs, ["rel"]}.
{deps, [
{lager, ".*", {
git, "git://github.com/basho/lager.git", "master"}
},
{can, ".*", {
git, "git://github.com/tonyrog/can", "master"}
}
]}.
{erl_opts, [{parse_transform, lager_transform}]}.

Compile everything and start a shell:

# rebar compile
# erl -pa ebin/ -pa deps/*/ebin


1> lager:start().
12:53:30.268 [info] Application lager started on node nonode@nohost
ok
2> lager:set_loglevel(lager_console_backend, debug).
ok
3> can:start().
12:53:39.905 [info] Application uart started on node nonode@nohost
ok
4> can_sock:start(0, [{device,"vcan0"}]).
12:53:39.963 [debug] Supervisor can_sup started can_router:start_link([]) at pid <0.58.0>
12:53:39.965 [debug] Supervisor can_sup started can_if_sup:start_link() at pid <0.59.0>
12:53:39.966 [info] Application can started on node nonode@nohost

12:53:41.213 [debug] can_router: process <0.61.0>, param {can_sock,"vcan0",0} joined.
12:53:41.215 [debug] Supervisor can_sup started can_sock:start_link(0, [{device,"vcan0"}]) at pid <0.61.0>
{ok,<0.61.0>}

Driver and device are loaded. Let's try sending something:

5> can:send(42, <<"ABCD">>).
ok
6> 12:54:42.626 [debug] can_router: broadcast: [ID: 02A LEN:4 DATA:41424344]
12:54:42.627 [debug] broadcast: frame={can_frame,42,4,<<"ABCD">>,0,0}, send=1

On the dump:

# candump vcan0
vcan0 02A [4] 41 42 43 44

Sending data works. Let's try receiving data.

Let's attach the shell pid to the router.

6> can_router:attach().
12:58:06.992 [debug] can_router: process <0.33.0> attached.
ok

Information about the module can be queried:

can_router:i().
Interfaces
1: {can_sock,"vcan0",0}
input_frames: 3
output_frames: 2
Applications
<0.60.0>: interface=0
ok

Send something from the terminal:

# cansend vcan0 442#44434241

Do we have a new message?

7> receive V -> V end.
12:58:31.517 [debug] can_router: broadcast: [ID: 442 LEN:4 INTF:1 DATA:44434241]
12:58:31.517 [debug] broadcast: frame={can_frame,1090,4,<<"DCBA">>,1,-1}, send=1
{can_frame,1090,4,<<"DCBA">>,1,-1}

Success!

Using the real CAN



Plug your CAN transceiver the following way:

The Beagle Bone Black CAN pinout





PinDescription
24CAN RX
26CAN TX


Make sure the dtb is loaded in /boot/uEnv.txt:

dtb=am335x-boneblack-can1.dtb

If not, edit the file and reboot the board.

You can check that the board is aware of its CAN interface:

# cat /sys/kernel/debug/pinctrl/44e10800.pinmux/pinmux-pins | grep -i can
pin 96 (44e10980.0): P9_26_pinmux.53 (GPIO UNCLAIMED) function pinmux_P9_26_can_pin group pinmux_P9_26_can_pin
pin 97 (44e10984.0): P9_24_pinmux.51 (GPIO UNCLAIMED) function pinmux_P9_24_can_pin group pinmux_P9_24_can_pin

Stop the driver, configure the link and start the interface again:

# ifconfig can0 down
# ip link set can0 up type can bitrate 1000000 loopback off triple-sampling on
# if config can0 up

If everything goes right, the link should be working and you can query information about it:

# ip -details -statistics link show can0
3: can0: mtu 16 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 0
bitrate 1000000 sample-point 0.750
tq 83 prop-seg 4 phase-seg1 4 phase-seg2 3 sjw 1
c_can: tseg1 2..16 tseg2 1..8 sjw 1..4 brp 1..1024 brp-inc 1
clock 24000000
re-started bus-errors arbit-lost error-warn error-pass bus-off
0 0 0 1 1 0
RX: bytes packets errors dropped overrun mcast
14058 2425 0 89 0 0
TX: bytes packets errors dropped carrier collsns
591 591 0 0 0 0

For information, here's how my other device is configured:

//CLOCK CONFIG:

RCC_OscInitTypeDef RCC_OscInitStruct;
RCC_ClkInitTypeDef RCC_ClkInitStruct;

RCC_OscInitStruct.OscillatorType = RCC_OSCILLATORTYPE_HSE;
RCC_OscInitStruct.HSEState = RCC_HSE_ON;
RCC_OscInitStruct.HSEPredivValue = RCC_HSE_PREDIV_DIV1;
RCC_OscInitStruct.PLL.PLLState = RCC_PLL_ON;
RCC_OscInitStruct.PLL.PLLSource = RCC_PLLSOURCE_HSE;
RCC_OscInitStruct.PLL.PLLMUL = RCC_PLL_MUL9;
HAL_RCC_OscConfig(&RCC_OscInitStruct);

RCC_ClkInitStruct.ClockType = RCC_CLOCKTYPE_SYSCLK|RCC_CLOCKTYPE_PCLK1;
RCC_ClkInitStruct.SYSCLKSource = RCC_SYSCLKSOURCE_PLLCLK;
RCC_ClkInitStruct.AHBCLKDivider = RCC_SYSCLK_DIV1;
RCC_ClkInitStruct.APB1CLKDivider = RCC_HCLK_DIV2;
RCC_ClkInitStruct.APB2CLKDivider = RCC_HCLK_DIV1;
HAL_RCC_ClockConfig(&RCC_ClkInitStruct, FLASH_LATENCY_2);

__SYSCFG_CLK_ENABLE();

//CAN CONFIG:

hcan.Instance = CAN;
hcan.Init.Prescaler = 2;
hcan.Init.Mode = CAN_MODE_NORMAL;
hcan.Init.SJW = CAN_SJW_1TQ;
hcan.Init.BS1 = CAN_BS1_9TQ;
hcan.Init.BS2 = CAN_BS2_8TQ;
hcan.Init.TTCM = DISABLE;
hcan.Init.ABOM = DISABLE;
hcan.Init.AWUM = DISABLE;
hcan.Init.NART = DISABLE;
hcan.Init.RFLM = DISABLE;
hcan.Init.TXFP = DISABLE;
HAL_CAN_Init(&hcan);

(it's a STM32F302).