Arduino M0 Pro でマルチタスクプログラミング : CAN通信

# はじめに

この記事では，[TOPPERS/R2CA](http://dev.toppers.jp/trac_user/contrib/wiki/rtos_arduino)によるCAN通信の方法について説明します．

TOPPERS/R2CAの説明とサンプルの動作方法は次の記事を見て下さい．

 - [Arduino M0 Pro でマルチタスクプログラミング : インストールとサンプルの実行](http://qiita.com/exshonda/items/093870ecdcda6c79678c)
 - [Arduino M0 Pro でマルチタスクプログラミング : マルチタスク](http://qiita.com/exshonda/items/7afb12b56696cf44895e)

# ハードウエア

## 必要なハードウエア

[この記事](http://qiita.com/exshonda/items/7afb12b56696cf44895e)で説明しているハードウェア以外にCANシールドが必要です．また，通信ですので2セットもしくはCANモニタが必要になります．あと，接続ケーブルが必要になります．

CANシールドとしては，Microchip社のMCP2515を搭載したものなら使用可能なはずですが，M0 Proの入出力は3.3Vなので，3.3V対応の物が必要です．

動作確認に使用したのは次のシールドです．

- [NCES-CAN ボード](http://homepage3.nifty.com/fpga/files/board/nces_shield/)
  - MCP2515を搭載した3.3V/5V両対応のシールドです．  
  - CANコネクタは2本.
   - 電気的に同じ信号が出ているので，デージーチェーンが可能です．
   - コネクタは[北斗電子](http://www.hokutodenshi.co.jp/)製のボードと互
  換があります．ケーブル類も北斗電子から購入可能です．
   - CANはケーブル2本で通信可能ですが，コネクタからは5VとGNDも出してい
  るので，4本のケーブルで接続すると他のボードに電源の供給が可能です．
  - 割込み/CSに使うピンはジャンパーで変更可能です．
    - 割込み : D9 or D10
    - CS :D3 or A1  
  - ターミネータのON/OFFをジャンパーで変更可能です．
  - XbeeとmicroSDのコネクタを持っています．

<img width="400" alt="FullSizeRender.jpg" src="https://qiita-image-store.s3.amazonaws.com/0/106870/a0bde94e-802c-4a7a-27fc-da9d1557f696.jpeg">


必須ではないですがCANモニタはあった方がよいです．

- [CANモニタ]
 - [PEAKSYSTEM](http://www.gailogic.co.jp/product/ps/hard_1.html)の製 品が安価でよいです．今買うならCAN FD対応の[製品](PCAN-USB FD)がよいと思います．
 - 接続ケーブル
 - 
<img width="400" alt="FullSizeRender.jpg" src="https://qiita-image-store.s3.amazonaws.com/0/106870/c3d637fc-70ff-f0e3-02a7-8ddfbefae2b8.jpeg">
 

## ハードウェアセットアップ

NECS-CANボードのジャンパを設定します．下記の写真のように，割込みをIO9(D9)に，チップセレクトがIO3(D3)になるように設定します．また，TERMINATORも有効にします．

![dip2.jpg](https://qiita-image-store.s3.amazonaws.com/0/106870/ac05af6e-d76f-2bf3-598a-c37af1e845b1.jpeg)


次にボードとCANモニタないし他のボードとケーブルで接続します．ボード側のコネクタはどちらに接続しても問題ありません．

![connect.jpg](https://qiita-image-store.s3.amazonaws.com/0/106870/a6e54561-b355-5f2d-30f7-1483e6e1a3a2.jpeg)


#サンプルのビルド

R2CAのパッケージの\examples\NCESCanにあるサンプルを実行します．このサンプルはタスクを2個使い受信処理と送信処理を行います詳細はプログラムの解説で説明します．

ビルドは同じフォルダにあるdo_make.bat をダブルクリックすると開始されます．ファイルが色々作成されますが，r2ca.elfが出来るとビルド成功です．

#実行

まずPC側でモニタを起動します．CANモニタをPCに接続し，スタートメニューからPCAN-Viewを起動します．接続するCANモニタを選択して，"Nominal Bit rate"に500kBit/sを指定してOKを押します．

![WS000010.JPG](https://qiita-image-store.s3.amazonaws.com/0/106870/5458f201-6a82-71f3-430a-2448aabe038a.jpeg)


次に受信できる状態にします．メニューの"Trace"から"Start"を選択します．さらに，Traceのタブをアクティブにします．

![WS000011.JPG](https://qiita-image-store.s3.amazonaws.com/0/106870/8c33144a-6487-d6bd-704b-6575f5f0c84a.jpeg)


次に，ボードの方でプログラムを実行します実行はdo_run.batをダブルクリックするとダウンロードが開始され，実行されます．

PCAN-Viewの画面に次のようにID1から8のメッセージが表示されていればプログラムは正しく動作しています．

![WS000013.JPG](https://qiita-image-store.s3.amazonaws.com/0/106870/af1c1f76-aefd-c4ea-9ada-97c0bfe6c641.jpeg)


次にPC側からCANメッセージを送信します．P-CANのメニューの"Transmit"から"New Message"を選択します．次のように送信するメッセージを作成します．この例ではID=1,DLC=2,DATA=0x01,0x02，送信周期5000mとしています．同様にID=4,DLC=2,DATA=0x03,0x04，送信周期5000mのメッセージも作成します．

![WS000014.JPG](https://qiita-image-store.s3.amazonaws.com/0/106870/5973c085-e6cd-c1ac-b40f-6f79b25c9bed.jpeg)


登録後は次のような画面表示になっているはずです．登録後にメッセージの送信が開始されます．
![WS000016.JPG](https://qiita-image-store.s3.amazonaws.com/0/106870/a7d6e3ca-7798-f81f-1437-e50408657091.jpeg)

ボード側ではメールボックス0,メールボックス1でそれぞれメッセージを受信していることが分かります．

![WS000015.JPG](https://qiita-image-store.s3.amazonaws.com/0/106870/7fcf1c6c-9f44-feb7-240e-064b1a1509fc.jpeg)


#プログラム

プログラムはスクを2個使い，それぞれ受信処理と送信処理を行います．まず初期化処理から見ていきます．

初期化はメインタスクのsetup()で行います．まずCANの速度を.begin()により500Kbpsで初期化します．

次にメールボックスの設定を行います．受信用メールボックスはRXB0とRXB1の2個があります．それぞれフィルターを設定することができ，RXB0は2個，RXB1は4個設定可能です．フィルターとは，受信するメッセージのIDを選択する機能で，マスクで'1'に設定したビットは，フィルターのビットと比較して，マッチしたIDのメッセージのみ受信します．このプログラムの設定では，RXB0は0xX1,0xX2のメッセージ(Xはどの様な値でもよい)を受信します．一方，RXB1は，0xX3,0xX4,0xX5,0xX6のメッセージ(Xはどの様な値でもよい)を受信します．試しに0x07や0x12のメッセージを送信するとどうなるか確認してください．

また割込みを許可してハンドラMCP2515_ISRを関連付けています．これはCANの受信割込みが発生した場合にMCP2515_ISRを呼び出します．

初期化が終了したらcan_init_doneをtrueにします．これはTask1にCANの初期化が終了したことを伝えるためです．

```\examples\NCESCan\r2ca_app.cpp
bool can_init_done = false;

void setup()
{
    Serial.begin(115200);
    pinMode(13, OUTPUT);

    /*
     *  CAN Initialize
     */
    if(CAN.begin(CAN_500KBPS) ==CAN_OK) {
        Serial.print("can init ok!!\r\n");
    }
    else {
        while(1){
            Serial.print("Can init fail!!\r\n");
            digitalWrite(13, HIGH); 
            delay(500);             
            digitalWrite(13, LOW);  
            delay(500);             
        };
    }
    
    /*
     *  CAN Recive mabx Init
     */
    
    /*
     *  RXB0
     */
    CAN.init_Mask(MCP_RXM0, 0x0f);
    CAN.init_Filter(MCP_RXF0, 0x01);
    CAN.init_Filter(MCP_RXF1, 0x02);
    CAN.startReceive(MCP_RXB0);

    /*
     *  RXB1
     */
    CAN.init_Mask(MCP_RXM1, 0x0f);
    CAN.init_Filter(MCP_RXF2, 0x03);
    CAN.init_Filter(MCP_RXF3, 0x04);
    CAN.init_Filter(MCP_RXF4, 0x05);
    CAN.init_Filter(MCP_RXF5, 0x06);    
    CAN.startReceive(MCP_RXB1);

    CAN.enableInterrupt(MCP_RX0IF|MCP_RX1IF);
    CAN.attachInterrupt(CAN_INT_PIN, MCP2515_ISR);

    can_init_done = true;
}
```


次に受信処理を見ます．まず受信割込みハンドラMCP2515_ISR()を定義しています．MCP2515_ISR()ではフラグ(Flag_Recv)をセットして，受信を通知します．

受信処理はメインタスクのloop()で実行します．データを受信していなければリターンします．データを受信していれば，それぞれのメールボックスで受信しているかチェックして，受信していればデータを読み込み表示します．

```\examples\NCESCan\r2ca_app.cpp
uint8_t Flag_Recv = 0;

void MCP2515_ISR()
{
     Flag_Recv = 1;
}

void loop()
{
    uint8_t len = 0;
    uint8_t buf[8];
    uint8_t i;
    uint32_t id;    
    
    if(!Flag_Recv) {
       delay(1);
        return;
    }
      
    Flag_Recv = 0;

    for(i = 0; i < 2; i++){
        if(CAN.checkReceive(i) == CAN_NOMSG) {
            continue;
        }       
        CAN.readMsg(i, &id, &len, buf);    // read data,  len: data length, buf: data buf
        Serial.print("CAN_BUS GET DATA MBX");
        Serial.println(i);
        Serial.print("ID = ");Serial.print(id, HEX);
        Serial.print(", DLC = ");Serial.print(len);
        Serial.print(", data = ");
        for(int i = 0; i<len; i++){    // print the data          
              Serial.print(buf[i]);Serial.print("\t");
          }
        Serial.println();
    }
}
```

送信処理はTask1で実施しています．初期化処理はメインタスクの初期化処理が終わるまで待ちます．送信メールボックスは3個あるため，送信処理では3個のメールボックスにID1から8のメッセージを連続に送信しています．

```\examples\NCESCan\r2ca_app.cpp
void task1_setup()
{
    while(can_init_done == false){delay(10);}
}

unsigned char stmp[8][8] = {
    {1, 2, 3, 4, 5, 6, 7, 8},
    {2, 3, 4, 5, 6, 7, 8, 1},
    {3, 4, 5, 6, 7, 8, 1, 2},
    {1, 2, 3, 4, 5, 6, 7, 8},
    {1, 2, 3, 4, 5, 6, 7, 8},
    {1, 2, 3, 4, 5, 6, 7, 8},
    {1, 2, 3, 4, 5, 6, 7, 8},
    {0, 1, 2, 3, 4, 5, 6, 7}
};

unsigned char stmp1[8] =     {0, 1, 2, 3, 4, 5, 6, 7};
    
void task1_loop()
{
    int i, e;

    for(e = 0;  e < 3; e++){
        Serial.print("Send can message from mbx ");
        Serial.println(e);
        for(i = 1; i <= 8; i++){
            while(CAN_SENDWAIT == CAN.checkSend(e)){delay(10);};
            /* mailbox, ID, DLC, DATA */
            CAN.sendMsg(e, i, i, stmp[i-1]);
        }
    }

    delay(5000);
}
```

# おわりに

R2CAによるCAN送受信について説明しました．ライブラリは通常のArduinoでも使う事が出来ます．その場合，.\arduino_lib\libraries\NcesCan をArduinoのライブラリにコピーしてください．NCES-CANボードは，Uno等の5V系のボードでも使用することができます．