You are here

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

Not so frequently asked questions and stuff: 

Communication through a CAN bus between a Beagle Bone Black and a STM32F302 NucleoImage

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 
12:53:39.965 [debug] Supervisor can_sup started can_if_sup:start_link() at pid 
12:53:39.966 [info] Application can started on node nonode@nohost

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

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

5> can:send(42, >).
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,>,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  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
:  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,>,1,-1}, send=1
{can_frame,1090,4,>,1,-1}

Success!

Using the real CAN

Plug your CAN transceiver the following way:

The Beagle Bone Black CAN pinout

Pin Description
24 CAN RX
26 CAN 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).