2017/09/17

Raspberry PiでCAN通信 その6【Pythonで標準フレームの受信】

前回のC言語で書いたCANの標準フレームの受信を
Pythonで実装します。

動作環境も前回と同じく、OS:raspbian jessie
仮想CANデバイス(vcan1)でテストしています。

#! /usr/bin/env python3
# -*- coding: utf-8 -*-

"""
    sudo modprobe vcan
    sudo ip link add dev vcan1 type vcan
    sudo ip link set vcan1 up
"""

import sys
import socket
import struct

CAN_MAX_DLEN = 8
CAN_MTU = 16

CAN_NAME = 'vcan1'

def canrecv_stdframe():

    s = socket.socket(socket.AF_CAN, socket.SOCK_RAW, socket.CAN_RAW)

    try:
        s.bind((CAN_NAME,))
    except OSError:
        print('bind error', file=sys.stderr)
        return -1

    frame = s.recv(CAN_MTU)

    id, dlc, bdata = struct.unpack('IB3x8s', frame)
    data = bdata.hex()

    s.close()

    return id, dlc, data

if __name__ == '__main__':
    def main():
        id, dlc, data = canrecv_stdframe()

        print('id=%x, dlc=%s, data=%s' % (id, dlc, data))

    main()

これで、標準フレームを1回受信し、表示して終わります。

Raspberry PiでCAN通信 その5【C言語で標準フレームの受信】

Raspberry Pi上で、C言語によるCAN通信の標準フレーム受信をします。

動作環境は、OS:raspbian jessie です。
CANデバイスは、まだCANモジュールが来ていないので、
以下のコマンドで作った仮想CANデバイス(vcan1)で受信します。
sudo modprobe vcan
sudo ip link add dev vcan1 type vcan
sudo ip link set vcan1 up

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
#include <errno.h>

#include <sys/socket.h>
#include <sys/ioctl.h>
#include <net/if.h>
#include <linux/can.h>
#include <linux/can/raw.h>


#define CAN_NAME "vcan1"


int canrecv_stdframe(unsigned short *p_id, unsigned char *p_dlc, unsigned char data[])
{
    int ret;
    int s;
    fd_set rdfs;
    struct ifreq ifr;
    struct sockaddr_can addr;
    struct timeval timeout;
    struct can_frame frame;
    int nbytes;

    if((s = socket(PF_CAN, SOCK_RAW, CAN_RAW)) < 0)
    {
        perror("socket");
        return -1;
    }

    memset(&ifr.ifr_name, 0, sizeof(ifr.ifr_name));
    strncpy(ifr.ifr_name, CAN_NAME, sizeof(ifr.ifr_name));

    ifr.ifr_ifindex = if_nametoindex(ifr.ifr_name);
    if(! ifr.ifr_ifindex)
    {
        perror("if_nametoindex");
        return -2;
    }

    addr.can_family = AF_CAN;
    addr.can_ifindex = ifr.ifr_ifindex;

    if(bind(s, (struct sockaddr *)&addr, sizeof(addr)) < 0)
    {
        perror("bind");
        return -3;
    }

    while(1)
    {
        FD_ZERO(&rdfs);
        FD_SET(s, &rdfs);

        timeout.tv_sec = 1;
        timeout.tv_usec = 0;

        ret = select(s+1, &rdfs, NULL, NULL, &timeout);
        if(ret < 0)
        {
            perror("select");
            return -4;
        }
        else if(0 == ret)
        {
            continue;
        }
        else
        {
            break;
        }
    }

    nbytes = read(s, &frame, sizeof(frame));
    if(nbytes < 0)
    {
        perror("recv");
        return -5;
    }

    if(nbytes == sizeof(frame))
    {
        *p_id = frame.can_id;
        *p_dlc = frame.can_dlc;
        memcpy(data, frame.data, CAN_MAX_DLEN);
    }
    else
    {
        fprintf(stderr, "recv size not std-frame.\n");
    }

    close(s);

    return 0;
}

int main(int argc, char argv[])
{
    unsigned short id;
    unsigned char dlc;
    unsigned char data[CAN_MAX_DLEN];

    canrecv_stdframe(&id, &dlc, data);

    printf("id=%x, dlc=%d, data=%.02x %.02x %.02x %.02x %.02x %.02x %.02x %.02x\n",
        id, dlc, data[0], data[1], data[2], data[3], data[4], data[5], data[6], data[7]);
}


これで、標準フレームを1回受信し、表示して終わります。

CAN標準フレームの受信に必要な処理は、これでわかりました。

次は、これをpythonで実装します。

2017/09/16

Raspberry PiでCAN通信 その4【Pythonで標準フレームの送信】

前回のC言語で書いたCANの標準フレームの送信を
Pythonで実装します。

PythonでCAN通信するには、python-canを使えばよいようですが、
私の職場はインターネットに接続させてもらえないので、
pipでpython-canをインストールすることができません。

pythonでsocketが使えるので、SocketCANを使って、
CAN通信するプログラムを作ることにしました。

動作環境も前回と同じく、OS:raspbian jessie
仮想CANデバイス(vcan1)でテストしています。

#! /usr/bin/env python3
# -*- coding: utf-8 -*-

"""
    sudo modprobe vcan
    sudo ip link add dev vcan1 type vcan
    sudo ip link set vcan1 up
"""

import sys
import socket
import struct

CAN_MAX_DLEN = 8
CAN_MTU = 16

CAN_NAME = 'vcan1'

def cansend_stdframe(id, data):

    if(CAN_MAX_DLEN < len(data)):
        print('dlc too long.', file=sys.stderr)
        return -1

    s = socket.socket(socket.AF_CAN, socket.SOCK_RAW, socket.CAN_RAW)

    s.setsockopt(socket.SOL_CAN_RAW, socket.CAN_RAW_FILTER, None, 0)

    # no loopback
    loopback = 0 # 0 = disabled, 1 = enabled(default)
    s.setsockopt(socket.SOL_CAN_RAW, socket.CAN_RAW_LOOPBACK, loopback)

    try:
        s.bind((CAN_NAME,))
    except OSError:
        print('bind error', file=sys.stderr)
        return -4

    dlc = len(data)
    bdata = bytes(data)
    frame = struct.pack('IB3x8s', id, dlc, bdata)

    if(s.send(frame) < CAN_MTU):
        print('send', file=sys.stderr)
        return -5

    s.close()

    return 0

if __name__ == '__main__':
    def main():
        data = [ 0x11, 0x22, 0x33, 0x44 ]
        cansend_stdframe(0x0123, data)

    main()

これで、id=0x123,data=0x11 0x22 0x33 0x44 のデータを1回送信します。



2017/09/11

Raspberry PiでCAN通信 その3【C言語で標準フレームの送信】

Raspberry Pi上で、C言語によるCAN通信の標準フレーム送信をします。

can-utilsのソースコードを解析した結果、
標準フレームの送信をする処理を起こしました。

動作環境は、OS:raspbian jessie です。
CANデバイスは、まだCANモジュールが来ていないので、
以下のコマンドで作った仮想CANデバイス(vcan1)で送信します。
sudo modprobe vcan
sudo ip link add dev vcan1 type vcan
sudo ip link set vcan1 up

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
#include <errno.h>

#include <sys/socket.h>
#include <sys/ioctl.h>
#include <net/if.h>
#include <linux/can.h>
#include <linux/can/raw.h>

#define CAN_NAME "vcan1"

int cansend_stdframe(unsigned short id, unsigned char dlc, unsigned char data[])
{
    int s;
    struct ifreq ifr;
    struct sockaddr_can addr;
    struct can_frame frame;
    int loopback;

    if(CAN_MAX_DLEN < dlc)
    {
        fprintf(stderr, "dlc too long.\n");
        return -1;
    }

    memset(&frame, 0, sizeof(frame));
    frame.can_id = id;
    frame.can_dlc = dlc;
    memcpy(&(frame.data[0]), &(data[0]), dlc); 

    if((s = socket(PF_CAN, SOCK_RAW, CAN_RAW)) < 0)
    {
        perror("socket");
        return -2;
    }

    memset(&ifr.ifr_name, 0, sizeof(ifr.ifr_name));
    strncpy(ifr.ifr_name, CAN_NAME, sizeof(ifr.ifr_name));

    ifr.ifr_ifindex = if_nametoindex(ifr.ifr_name);
    if(! ifr.ifr_ifindex)
    {
        perror("if_nametoindex");
        return -3;
    }

    addr.can_family = AF_CAN;
    addr.can_ifindex = ifr.ifr_ifindex;

    setsockopt(s, SOL_CAN_RAW, CAN_RAW_FILTER, NULL, 0);

    /* no loopback */
    loopback = 0; /* 0 = disabled, 1 = enabled(default) */
    setsockopt(s, SOL_CAN_RAW, CAN_RAW_LOOPBACK, &loopback, sizeof(loopback));

    if(bind(s, (struct sockaddr *)&addr, sizeof(addr)) < 0)
    {
        perror("bind");
        return -4;
    }

    if(write(s, &frame, CAN_MTU) < CAN_MTU)
    {
        perror("write");
        return -5;
    }

    close(s);

    return 0;
}

int main(int argc, char argv[])
{
    unsigned char data[] = { 0x11, 0x22, 0x33, 0x44 };

    cansend_stdframe(0x0123, sizeof(data), data);
}

ループバックをoffにしているので、candumpで見ていても、何も出てきません。
loopbackの値を1にすれば、出てくるようになります。

CAN標準フレームの送信に必要な処理は、これでわかりました。

次は、これをpythonで実装します。

2017/09/10

SocketCANによる仮想CAN通信

Raspberry PiでCAN通信しようと、CAN-Utilsの中身を調べています。

LinuxのSocketCANを利用して通信しているようでした。

SocketCAN

SocketCANとは何ぞやと、上記の文章を読んでいると、
以下の章で衝撃を受けました。

6.4 The virtual CAN driver (vcan)

なんと、仮想CANドライバという機能があるようです。

CANデバイスをリンクアップするときに、
実デバイスがなくても、仮想的なCAN通信ができるようです。
仮想デバイスを作る方法は以下の通り。
$ sudo modprobe vcan
$ sudo ip link add dev vcan0 type vcan
$ sudo ip link set vcan0 up

これで、/sys/class/net の下にvcan0ができ、
vcan0 という名前でデバイスが利用できるようになります。

以下は、CANデバイスが無いPC上で、CAN-Utilsを使用した例です。

送信側


受信側


こんなことができるなら、CANモジュール買わなくてもよかったか・・・
まあ、CANモジュールが届くまでは、仮想CANで開発ができそうだ。

Raspberry PiでCAN通信 その2【CAN-Utils】

CANモジュールがまだ来ないので、まだCAN通信ができませんが、
待っててもしょうがないので、CAN通信をどうプログラムするのか
調べます。

CAN通信でネットを検索すると、「CAN-Utils」というオープンソースの
パッケージが多数出てきます。まずはこのパッケージをインストールして、
公開されているソースを見ながら、調査を進めていきます。

まずはCAN-Utilsのインストールから。
ホームディレクトリ以下にダウンロードして、インストールすることにします。
$ cd
$ sudo apt-get install autoconf git libtool
$ git clone https://github.com/linux-can/can-utils.git
$ cd can-utils
$ ./autogen.sh
$ ./configure
$ make
$ sudo make install

これで、/usr/local/bin 配下に、CAN-Utilsの実行ファイルが入りました。

主に使うコマンドは、受信はcandumpコマンド、送信はcansendコマンドです。

以下、/sys/class/net/can0インターフェースがある前提で、記載します。

受信したすべてのCANフレームを表示するときは、
$ candump can0
で、表示できます。フィルターを設定することもできます。

送信する場合は、arb=123,data=0xAA 0xBB 0xCC 0xDD の場合、
$ cansend can0 123#AABBCCDD
とします。標準フレーム、拡張フレーム両方できるようです。

これから、cansendのソースを眺めたいと思います。

注文したCANモジュールは、まだ「発送しました」になってた。
進んでるんだろうか。
はやくCANモジュール来ないかなー。

2017/09/08

Raspberry PiでCAN通信 その1【設定編】

注文したCANモジュールが到着するまでに、
Raspberry Piでどう設定するのかを調べました。

以下は、Raspberry Pi 3 Model B 、OSはRaspbian Jessieでの設定です。

MCP2515は、マイコンとはSPIで接続するので、
まず、以下の操作で、Raspberry PiのSPI通信を有効にします。

Raspberry Piにログインして、raspi-configを起動します。
$ sudo raspi-config

「5 Interfacing Options」を選択します。

「P4 SPI」を選択します。

SPIを有効にしますかと聞かれるので、「Yes」を選択します。

SPIが有効になりました。

以上で、SPIが使えるようになりました。

続いて、MCP2515を使えるように設定します。
/boot/config.txtに、以下の設定を加えます。

「dtparam=spi=on」と書かれている場所のすぐ下に、
以下の赤字の部分を追記します。

dtparam=spi=on
dtoverlay=mcp2515-can0,oscillator=8000000,interrupt=25
dtoverlay=spi-bcm2835

今回使うCANモジュールは、MCP2515に8MHzの水晶、
INTをRaspberry Pi の GPIO25に接続するため、
上記の記述になります。

/boot/config.txtを保存して、Raspberry Piを再起動します。
$ sudo reboot

再起動後、dmesgで、デバイスドライバが動作していることを確認します。


あ、あれ、なんかエラーになってるっぽい。
そ、そうか、CANモジュールつないでないから、エラーになってるのかな?
CANモジュールが届くまで、なにも出来ないか・・・

これがうまくいっていれば、あとはsysfsにcanインターフェースを追加して、
終わりのはずでした。一応、書いておきます。
$ ip link set can0 type can bitrate 500000
$ ip link set can0 up

これで、/sys/class/net以下に、can0 というのが現れて、
このインターフェースを通して、通信ができるはずでした。
当然ながら、デバイスがないので、現状ではエラーになってしまいます。

アマゾンで配達状況を確認すると、「発送しました」となっているが、
中国からなので、いつ届くのだろうか・・・
早く来ないかなー。

2017/09/06

Raspberry PiでCAN通信 その0【購入編】

仕事で、CAN通信をする開発をすることになりました。
このため、CAN通信の対向機として、Raspberry Piを使うことにしました。

Raspberry PiでCAN通信をするために調べたことを書いていきます。

CAN通信するためには、CANコントローラ と CANトランシーバ が必要です。

ネットで作例を探すと、Arduinoで マイクロチップテクノロジ社の
MCP2515、MCP2551 を使ったものをよく見ます。

マルツのサイトなどで探すと、MCP2515が400円ほど、MCP2551が250円ほど。
コンデンサ、抵抗などを合わせると、合計で1000円ほどになりそうです。

アマゾンで探すと、以下のものを見つけました。
※重くなるから、アフィではなくリンクだけ貼るよっ!

Rasbee オリジナル MCP2515 CAN バス モジュール TJA1050 レシーバーSPIモジュール Arduinoのための AVR 1個 [並行輸入品]

画像は、商品ページのものを借りました。

チップ構成が、CANコントローラがMCP2515と、CANトランシーバがTJA1050ですが、
値段が 265円(2017/09/05時点)と、圧倒的な安さでした。
とりあえず、これを2つ注文しました。

ほかのメーカー?からも、同じような商品が多数ありました。
どこかのコピー品なのでしょうか。。。

中国からの輸入になるようですが、日本円で購入できて楽でした。
また、到着まで最大10日ほどかかるようです。

到着までに、Raspberry Pi への接続方法・設定方法を調べます。