LinuxでCAN(車載通信)を動作させてみる

この記事は、[Fujitsu Advent Calender](http://qiita.com/advent-calendar/2016/fujitsu_ext) ２枚目の 1 日目の記事です。

#__はじめに__
組み込みLinux評価ボードでCAN(車載通信)を使用する方法について調べてみました。
CANには色々な規格がありますが、今回は一般的な500kビット/秒の標準フォーマット(11bit)を使用してみました(CANの詳細は[wikipedia](https://ja.wikipedia.org/wiki/Controller_Area_Network "wikipedia")等を参照して下さい)。

#__1.ターゲットボードの用意__
手頃な価格でCANを使用出来る組み込み評価ボードが無いか調べてみましたが、SoCがCANに対応していても評価ボードにCAN IFが搭載されていないボードが大半です。今回は[AutomotiveGradeLinux](https://www.automotivelinux.org) の標準リファレンスボードに指定されているRenesas社の[Porter](http://elinux.org/R-Car/Boards/Porter)ボードを使用してみます。
ボードのCAN IFについては下記に詳しく説明が記載されていました。
http://elinux.org/R-Car/Boards/Porter:Hardware
CANの通信を確認するのには対向機が必要なので、今回はPorterを2台使用してそれぞれのCAN IFにCAN HとCAN Lを直接接続します。
![IMG_0015.JPG](https://qiita-image-store.s3.amazonaws.com/0/152253/17eccf5a-7def-4bcd-6be7-b2b3dd5c097c.jpeg)


#__2.CANドライバについて__
Linux CANを調査してみると、CANドライバがmainlineでサポートされている事が分かりました。
https://www.kernel.org/doc/Documentation/networking/can.txt
PorterボードのCANドライバを有効にするため、

```
CONFIG_CAN=y
CONFIG_CAN_RAW=y
CONFIG_CAN_BCM=y
CONFIG_CAN_GW=y
CONFIG_CAN_RCAR=y
```

を有効にしてkernelをビルドします。
PorterボードはYocto環境で提供されており、eLinux Wikiのページに従うと簡単にビルドが出来ます。
http://elinux.org/R-Car/Boards/Yocto

#__3.CAN送受信の確認ツールについて__
socketを利用してCANにアクセス可能なのですが、簡単に送受信出来るツールが無いか調査するとOSSの[can utils](https://github.com/linux-can/can-utils)を使用出来る事が分かりました。
Porterボードのビルド環境はyoctoを使用しているので、

```
local.confに
IMAGE_INSTALL_append = " can-utils iproute2"
```

 を追加して、bitbake(ビルド)します

#__4.CAN送受信テスト__
上記でビルドしたイメージをSDカードにコピーして、ボードを起動させCANを設定します。
CANの設定にはipコマンド使用します。

```
ip link set can0 type can bitrate 500000
ip link set can0 up
```

```
root@porter:~# ip link set can0 type can bitrate 500000
root@porter:~# ip link set can0 up
root@porter:~# ifconfig
can0      Link encap:UNSPEC  HWaddr 00-00-00-00-00-00-00-00-00-00-00-00-00-00-00-00
          UP RUNNING NOARP  MTU:16  Metric:1
          RX packets:0 errors:0 dropped:0 overruns:0 frame:0
          TX packets:0 errors:0 dropped:0 overruns:0 carrier:0
          collisions:0 txqueuelen:10
          RX bytes:0 (0.0 B)  TX bytes:0 (0.0 B)
          Interrupt:218
          RX bytes:1045366 (1020.8 KiB)  TX bytes:1045366 (1020.8 KiB)
<snip>
```

もう一台のボードも同じようにCANを設定します。
CAN送受信の確認にはcan utilsのcansendとcandumpを使用します。
-受信側のPorterボード
candump デバイス名

```
root@porter:~# candump can0
```

-送信側のPorterボード(CAN ID123、CANデータ45を送信する例)
cansend デバイス名 CAN ID#送信データ

```
root@porter:~# cansend can0 123#45
```

すると受信側のPoterボードで

```
root@porter:~# candump can0
  can0  123   [1]  45
```

が表示され、PorterボードでCANデータ受信が確認出来ます。

candumpは他にも色々機能が用意されており、特にフィルタリング機能等はデバッグ時に有効だと思います。

```
root@porter:~# candump

Usage: candump [options] <CAN interface>+
  (use CTRL-C to terminate candump)

Options: -t <type>   (timestamp: (a)bsolute/(d)elta/(z)ero/(A)bsolute w date)
         -c          (increment color mode level)
         -i          (binary output - may exceed 80 chars/line)
         -a          (enable additional ASCII output)
         -S          (swap byte order in printed CAN data[] - marked with '`' )
         -s <level>  (silent mode - 0: off (default) 1: animation 2: silent)
         -b <can>    (bridge mode - send received frames to <can>)
         -B <can>    (bridge mode - like '-b' with disabled loopback)
         -u <usecs>  (delay bridge forwarding by <usecs> microseconds)
         -l          (log CAN-frames into file. Sets '-s 2' by default)
         -L          (use log file format on stdout)
         -n <count>  (terminate after receiption of <count> CAN frames)
         -r <size>   (set socket receive buffer to <size>)
         -d          (monitor dropped CAN frames)
         -e          (dump CAN error frames in human-readable format)
         -x          (print extra message infos, rx/tx brs esi)
         -T <msecs>  (terminate after <msecs> without any reception)

Up to 16 CAN interfaces with optional filter sets can be specified
on the commandline in the form: <ifname>[,filter]*

Comma separated filters can be specified for each given CAN interface:
 <can_id>:<can_mask> (matches when <received_can_id> & mask == can_id & mask)
 <can_id>~<can_mask> (matches when <received_can_id> & mask != can_id & mask)
 #<error_mask>       (set error frame filter, see include/linux/can/error.h)

CAN IDs, masks and data content are given and expected in hexadecimal values.
When can_id and can_mask are both 8 digits, they are assumed to be 29 bit EFF.
Without any given filter all data frames are received ('0:0' default filter).

Use interface name 'any' to receive from all CAN interfaces.

Examples:
candump -c -c -ta can0,123:7FF,400:700,#000000FF can2,400~7F0 can3 can8
candump -l any,0~0,#FFFFFFFF    (log only error frames but no(!) data frames)
candump -l any,0:0,#FFFFFFFF    (log error frames and also all data frames)
candump vcan2,92345678:DFFFFFFF (match only for extended CAN ID 12345678)
candump vcan2,123:7FF (matches CAN ID 123 - including EFF and RTR frames)
candump vcan2,123:C00007FF (matches CAN ID 123 - only SFF and non-RTR frames)
```

#__おまけ__
今回はPorterボードを2枚使用してCANを接続しましたが、CANが無い組み込みボードでもCANを使用したく調査すると[CAN USB](http://www.can232.com/?page_id=16)という治具がありました。
FTDIのチップが使用されておりKernelのソースを確認すると、

```
/drivers/usb/serial/ftdi_sio.c
static struct usb_serial_driver ftdi_sio_device = {
	.driver = {
		.owner =	THIS_MODULE,
		.name =		"ftdi_sio",
	},
	.description =		"FTDI USB Serial Device",
	.id_table =		id_table_combined,
	.num_ports =		1,
	.bulk_in_size =		512,
	.bulk_out_size =	256,
	.probe =		ftdi_sio_probe,
	.port_probe =		ftdi_sio_port_probe,
	.port_remove =		ftdi_sio_port_remove,
	.open =			ftdi_open,
	.dtr_rts =		ftdi_dtr_rts,
	.throttle =		usb_serial_generic_throttle,
	.unthrottle =		usb_serial_generic_unthrottle,
	.process_read_urb =	ftdi_process_read_urb,
	.prepare_write_buffer =	ftdi_prepare_write_buffer,
	.tiocmget =		ftdi_tiocmget,
	.tiocmset =		ftdi_tiocmset,
	.tiocmiwait =		usb_serial_generic_tiocmiwait,
	.get_icount =           usb_serial_generic_get_icount,
	.ioctl =		ftdi_ioctl,
	.set_termios =		ftdi_set_termios,
	.break_ctl =		ftdi_break_ctl,
	.tx_empty =		ftdi_tx_empty,
};
```

```
/drivers/usb/serial/usb-serial.c
static int __init usb_serial_init(void)
{
	int i;
	int result;

	usb_serial_tty_driver = alloc_tty_driver(SERIAL_TTY_MINORS);
	if (!usb_serial_tty_driver)
		return -ENOMEM;

	/* Initialize our global data */
	for (i = 0; i < SERIAL_TTY_MINORS; ++i)
		serial_table[i] = NULL;

	result = bus_register(&usb_serial_bus_type);
	if (result) {
		pr_err("%s - registering bus driver failed\n", __func__);
		goto exit_bus;
	}

	usb_serial_tty_driver->driver_name = "usbserial";
	usb_serial_tty_driver->name = "ttyUSB";
	usb_serial_tty_driver->major = SERIAL_TTY_MAJOR;
	usb_serial_tty_driver->minor_start = 0;
	usb_serial_tty_driver->type = TTY_DRIVER_TYPE_SERIAL;
	usb_serial_tty_driver->subtype = SERIAL_TYPE_NORMAL;
	usb_serial_tty_driver->flags = TTY_DRIVER_REAL_RAW |
						TTY_DRIVER_DYNAMIC_DEV;
	usb_serial_tty_driver->init_termios = tty_std_termios;
	usb_serial_tty_driver->init_termios.c_cflag = B9600 | CS8 | CREAD
							| HUPCL | CLOCAL;
	usb_serial_tty_driver->init_termios.c_ispeed = 9600;
	usb_serial_tty_driver->init_termios.c_ospeed = 9600;
	tty_set_operations(usb_serial_tty_driver, &serial_ops);
	result = tty_register_driver(usb_serial_tty_driver);
	if (result) {
		pr_err("%s - tty_register_driver failed\n", __func__);
		goto exit_reg_driver;
	}

	/* register the USB driver */
	result = usb_register(&usb_serial_driver);
	if (result < 0) {
		pr_err("%s - usb_register failed\n", __func__);
		goto exit_tty;
	}

	/* register the generic driver, if we should */
	result = usb_serial_generic_register();
	if (result < 0) {
		pr_err("%s - registering generic driver failed\n", __func__);
		goto exit_generic;
	}

	return result;

exit_generic:
	usb_deregister(&usb_serial_driver);

exit_tty:
	tty_unregister_driver(usb_serial_tty_driver);

exit_reg_driver:
	bus_unregister(&usb_serial_bus_type);

exit_bus:
	pr_err("%s - returning with error %d\n", __func__, result);
	put_tty_driver(usb_serial_tty_driver);
	return result;
}
```

USB-Serialとしてアクセスしている事が分かりました。CAN USBであればUSBのIFが
ある色々な組み込みボードでCANの使用が可能です。早速購入して動作させてみます。
CAN USB(serial)を使用する為に、kernelのconfigに下記を追加します。

```
CONFIG_USB_SERIAL=y
CONFIG_USB_SERIAL_FTDI_SIO=y
```

次にCAN USBの設定について調査すると[eLinux Wiki](http://elinux.org/Bringing_CAN_interface_up)に詳しい説明がありました。
(初めからこのページを見ればよかった・・・)

CAN USBの設定はcan-utilsで提供されているslcandを使用します。
今回は500kbpsで使用するので

```
slcand -o -s 6 -t hw /dev/ttyUSB0
ip link set slcan0 up
```

になります。

ここでCAN USBはslipと同じ仕組みを使用すると気付き、
/drivers/net/can/slcan.cを有効にするkernel config

```
CONFIG_CAN_SLCAN=y
```

を追加しました。
ビルドしたイメージに差し替えて、CAN USBを設定すると無事にslcan0が使用出来るようになりました

```
root@porter:~# slcand -o -s 6 -t hw /dev/ttyUSB0
root@porter:~# ip link set slcan0 up
root@porter:~# ifconfig -a
can0      Link encap:UNSPEC  HWaddr 00-00-00-00-00-00-00-00-00-00-00-00-00-00-00-00
          UP RUNNING NOARP  MTU:16  Metric:1
          RX packets:77778 errors:0 dropped:0 overruns:0 frame:0
          TX packets:0 errors:0 dropped:0 overruns:0 carrier:0
          collisions:0 txqueuelen:10
          RX bytes:588446 (574.6 KiB)  TX bytes:0 (0.0 B)
          Interrupt:218
<snip>
slcan0    Link encap:UNSPEC  HWaddr 00-00-00-00-00-00-00-00-00-00-00-00-00-00-00-00
          UP RUNNING NOARP  MTU:16  Metric:1
          RX packets:0 errors:0 dropped:0 overruns:0 frame:0
          TX packets:0 errors:0 dropped:0 overruns:0 carrier:0
          collisions:0 txqueuelen:10
          RX bytes:0 (0.0 B)  TX bytes:0 (0.0 B)
```

これでCAN USBが使用出来るようなりましたが、CAN USBとPorterを接続するにはハーネスを自作する必要があります。
[How to connect:](http://www.can232.com/?page_id=16)
に記載があるようにD-SUBコネクタに120Ωの抵抗を接続し、ハーネスはツイストさせます。
![IMG_0014.JPG](https://qiita-image-store.s3.amazonaws.com/0/152253/18d31155-7983-4e44-96e4-665221a5348b.jpeg)


このハーネスをPorterボードのCAN IFに接続する事で
Porter CAN IF <- CAN -> Porter CAN USB のCAN通信が可能になります。![IMG_0016.JPG](https://qiita-image-store.s3.amazonaws.com/0/152253/790b1b1c-7da4-d4d1-d6ac-d8f65cfbfbfe.jpeg)



-送信側はPorterボード CAN IFを使用(CAN ID123、CANデータ45を送信する例)

```
root@porter:~# cansend can0 123#45
```

-受信側はCAN USB(slcan)を使用

```
root@porter:~# candump slcan0
  slcan0  123   [1]  45
```

#__まとめ__
今までCANを使用する時は高価な専用機が必要だったのですが、今回試した
組み込みボード、CAN USB、can utils
を使用すればCANの一通りのデバッグが出来る事が分かりました。今後は[OpenXC](https://github.com/openxc/openxc-message-format)など誰でもアクセス可能なCAN情報を使用してCANシミュレータなど全てOSSで何処まで実現可能なのか、トライしてみたいと思います。

【今回使用した環境について】
使用HW：Renesas Porterボード (http://elinux.org/R-Car/Boards/Porter)
        AWICEL CAN USB (http://www.can232.com/?page_id=16)
        自作ハーネス
Kernel: LTSI 3.10.31 (http://elinux.org/R-Car/Boards/Yocto)
yocto : 1.6.1 (http://elinux.org/R-Car/Boards/Yocto)

Porter CAN IF、CAN USBを有効にするkernel configを追加

```
CONFIG_CAN=y
CONFIG_CAN_RAW=y
CONFIG_CAN_BCM=y
CONFIG_CAN_GW=y
CONFIG_CAN_RCAR=y
CONFIG_CAN_SLCAN=y
CONFIG_USB_SERIAL=y
CONFIG_USB_SERIAL_FTDI_SIO=y
```

Porterに提供されているyoctoの環境にcan-utils iproute2を追加

```
local.confに下記を追加
IMAGE_INSTALL_append = " can-utils iproute2"
```

Porter CAN IF有効化

```
ip link set can0 type can bitrate 500000
ip link set can0 up
```

USB CAN有効化

```
slcand -o -s 6 -t hw /dev/ttyUSB0
ip link set slcan0 up
```


(注意）この記事は個人の見解であり、所属する会社、組織を代表するものではありません。