LoginSignup
67
47

More than 5 years have passed since last update.

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

Last updated at Posted at 2016-12-22

この記事は、Fujitsu Advent Calender 2枚目の 1 日目の記事です。

はじめに

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

1.ターゲットボードの用意

手頃な価格でCANを使用出来る組み込み評価ボードが無いか調べてみましたが、SoCがCANに対応していても評価ボードにCAN IFが搭載されていないボードが大半です。今回はAutomotiveGradeLinux の標準リファレンスボードに指定されているRenesas社のPorterボードを使用してみます。
ボードのCAN IFについては下記に詳しく説明が記載されていました。
http://elinux.org/R-Car/Boards/Porter:Hardware
CANの通信を確認するのには対向機が必要なので、今回はPorterを2台使用してそれぞれのCAN IFにCAN HとCAN Lを直接接続します。
IMG_0015.JPG

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を使用出来る事が分かりました。
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という治具がありました。
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に詳しい説明がありました。
(初めからこのページを見ればよかった・・・)

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:
に記載があるようにD-SUBコネクタに120Ωの抵抗を接続し、ハーネスはツイストさせます。
IMG_0014.JPG

このハーネスをPorterボードのCAN IFに接続する事で
Porter CAN IF <- CAN -> Porter CAN USB のCAN通信が可能になります。IMG_0016.JPG

-送信側は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など誰でもアクセス可能な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

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

67
47
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
67
47