ArduinoでCAN通信(その10:CCP通信・SD読み込み)

CCP通信を行うには対象となるECUのデータアドレス(ECUの品番毎に異なる)を都度Arduinoに与える必要があるので、これにはSDカードのテキストデータを経由することにしました。

【microSDカードを取り付け】
Arduino_CAN_ccp7.jpg

SDカードは3.3Vで動作しているため、SPIの信号もArduinoの5Vから3.3Vに落とす必要があります。
当初はmicroSDを取り付ける別基板を考えていましたが、省力のためArduinoのSPI端子に差し込めるように、ユニバーサル基板を使って変換させました。

回路的にはこちらで考えていた抵抗分圧でドロップさせます。

Arduino_CAN_28_sch.jpg

左上に見えるのが秋月のmicroSDカードをDIPに変換する基板が差し込まれるピンです。
この図では680Ωと360Ω抵抗を使う予定でしたが、手持ちの都合で1kと680Ωを使いました。
3.3Vのところが計算上は約3Vになりますが動作しました。

microSDカードのテキストデータは以下のようなものです。

//--- microSDカードのCCP_Set.txtの中身 (注:これはダミーデータです。またECU品番によって大きく異なります)
CPP_Set_Data
NE
0xFFFF9C8C
VS
0xFFFFC27C
QFIN
0xFFFFA440
PATM
0xFFFFC10C
THEX1
0xFFFFC044
THEX2
0xFFFFC06C
QP
0xFFFFA4B0
DPFMOD
0xFFFFB08C

先頭にCPP通信用データという意味の文字を書いてます(これは何でもよい)
次に計測名称とECUアドレスをセットにして2行ずつ8セット記載します。

これをArduinoのmicroSDで読み込ませるのですが、SD読み込みにはArduino標準添付のSD.hを使います。

しかし、ここでちょっと面倒なことが、なんとArduinoのSD.hには、「1行読み込み」機能がありません。
1.「1文字読み込み」機能があるので、これを使って行末の 0x0A 文字を使ってデータ区切りをします。

しかし、またここでちょっとハマりました。例の、「c言語が不得意な文字処理」です。私も不得意です。
2.・・・何だかんだと回り道(以前学んだことを忘れてる)で、strcat関数に繋がりました。

そして次にハマったことは、0xFFFF8C9C 等の文字データを、long形式の数値に変換することです。
3.当初、strtol関数がオーバーフローしていることに気が付かず、「何で皆同じ数値?」などと悩んでましたが、
  正解は、strtoul関数を使うことでした。

では、備忘録を1~3の順に記載します。

1.SDの読み込み

// ----
#include <SPI.h>        // SPIでSDカードを操作する
#include <SD.h>         // SDコントロール
#include <stdio.h>
#include <stdlib.h>
#include <ctype.h>
#include <string.h>

File dataFile;      // SDカードのファイル

const int chipSelect = 10;    // SPI でSDカードをコントロールするCSピンに 10ピン を使う
char cTop[16];     // ファイルの最初行文字列用
char cName[8][16];      // 計測名称の文字列 2次元配列
char cAddress[8][16];  // アドレスの文字列(同上)
char tmp[5];      // SDカードから読み込んだ文字列バッファ
char StrBuf[32];    // シリアル出力用文字列バッファ
char *endptr;    // strtoul関数用

  // SSピン(Unoは10番、Megaは53番)は使わない場合でも出力にする必要があります。
  // そうしないと、SPIがスレーブモードに移行し、SDライブラリが動作しなくなります。
  pinMode(SS, OUTPUT);

  // SDライブラリを初期化
  if (!SD.begin(chipSelect)) {
    Serial.println(F("Card failed, or not present"));
    // 失敗、何もしない
    while(1);
  }
  Serial.println(F("ok."));

  //設定データファイルを開く
  dataFile = SD.open("CCP_Set.txt", FILE_READ);

// -----  ここまででSDカード上の、CCP_Set.txt ファイルのオープン処理ができました。

2.1文字毎、読み込んだデータを計測名称文字列、アドレス文字列に結合する

// ----- ここから
  //ファイルが開けたら設定値を読み込む
  if (dataFile) {
    // read from the file until there's nothing else in it:
    while (dataFile.available()) {    // ファイルにデータだある場合は継続する
      tmp[0] = dataFile.read();         // 1文字読み込む
      //Serial.write(tmp[0]);               // 正常に読み込んだかのデバック
      if(tmp[0] == '\n'){                // 読み込んだ文字が 0x0A だった場合
        switch(cnt){          // cnt はデータの行番号に対応します
          case 0:
            strcat(cTop,&tmp[0]);    // 結合する先の文字列 cTop に読み込んだ文字 tmp を結合する。
                    // strcat 関数の結合元 tmp は定数文字が指定なので、& を付与する。
            cnt++;       // 0x0Aだったのはファイル文字列の最終だったので、行番号 cnt を1つアップする。
            break;
          case 1:
            strcat(cName[0],&tmp[0]);
            cnt++;
            break;
          case 2:
            strcat(cAddress[0],&tmp[0]);
            cnt++;
            break;
          case 3:
            strcat(cName[1],&tmp[0]);
            cnt++;
            break;
          case 4:
            strcat(cAddress[1],&tmp[0]);
            cnt++;
            break;
          case 5:
            ・・・・・
            ・・・・・
            ・・・・・
          case 16:
            strcat(cAddress[7],&tmp[0]);
            cnt++;
            break;
        }  //switch
      }else{                // 以降は 0x0A ではない文字の処理
        switch(cnt){
          case 0:
            strcat(cTop,&tmp[0]);
            break;
          case 1:
            strcat(cName[0],&tmp[0]);
            break;
          case 2:
            strcat(cAddress[0],&tmp[0]);
            break;
          case 3:
            strcat(cName[1],&tmp[0]);
            break;
          case 4:
            strcat(cAddress[1],&tmp[0]);
            break;
          case 5:
            ・・・・・
            ・・・・・
            ・・・・・
          case 16:
            strcat(cAddress[7],&tmp[0]);
            break;
        }  //switch
      }  //if(tmp[0]
    } //while
   
    // close the file:
    dataFile.close();
  } else {
    // if the file didn't open, print an error:
    Serial.println("error opening CCP_Set.txt");
  }
// ----- ここまで

3.入力し結合された文字列の表示確認と、アドレス文字をアドレス数値に変換・表示する

  //SDから読み込んだ値を表示
  //行頭文字
  sprintf(StrBuf,"cTop=%s",cTop);
  Serial.println(StrBuf);

  for(int i=0;i<=7;i++){
    //文字データを表示
    sprintf(StrBuf,"cName[%d]=%s",i,cName[i]);
    Serial.print(StrBuf);
    sprintf(StrBuf,"cAddress[%d]=%s",i,cAddress[i]);
    Serial.print(StrBuf);

    //アドレスの文字データをlong形式のlAddress[]に変換する
    lAddress[i] = strtoul(cAddress[i],&endptr,16);
    sprintf(StrBuf,"lAddress[%d]=%lu\n",i,lAddress[i]);
    Serial.println(StrBuf);
  }  //for

このソースを動作させたシリアルモニタ表示

cTop=CPP_Set_Data

cName[0]=NE
cAddress[0]=0xFFFF9C8C
lAddress[0]=4294941836

cName[1]=VS
cAddress[1]=0xFFFFC27C
lAddress[1]=4294951548

cName[2]=QFIN
cAddress[2]=0xFFFFA440
lAddress[2]=4294943808

cName[3]=PATM
cAddress[3]=0xFFFFC10C
lAddress[3]=4294951180

cName[4]=THEX1
cAddress[4]=0xFFFFC044
lAddress[4]=4294950980

cName[5]=THEX2
cAddress[5]=0xFFFFC06C
lAddress[5]=4294951020

cName[6]=QP
cAddress[6]=0xFFFFA4B0
lAddress[6]=4294943920

cName[7]=DPFMOD
cAddress[7]=0xFFFFB08C
lAddress[7]=4294946956

【備忘録のまとめ】
・ArduinoのSD.hでのテキストファイル読み込みは、1文字ずつしか読み取れない。
・これを1行ごとの文字列にするには、結合先の文字列 char[ ] に、strcat で結合する。
・数値文字列 0xFFFF9C8C 等のように 16bit 長を超えるものを数値化するには、strtol 関数ではなく、strtoul 関数を使う。

// -----  6月23日 追記
// ----- SDカードのデータを1文字ずつ読み込み、cName[ ] 、cAddress[ ] に振り分けて収納する部分のコードを少し短くしてみました。
/// -----  6月28日 修正:0xC,0xA(CR,LF)をデータに付加しないように変更

  //設定データファイルを開く
  dataFile = SD.open("CCP_Set.txt", FILE_READ);

  //ファイルが開けたら設定値を読み込む
  if (dataFile) {
    Serial.println("CCP_Set.txt open success.\n");

    // read from the file until there's nothing else in it:
    while (dataFile.available()) {

      //データを1文字読み取る
      tmp[0] = dataFile.read();

      //cntが0ならば0xAが来るまでcTopにstrcatする
      /// if((cnt == 0)&&(tmp[0] != '\n')){     /// これは止めて

      /// cntが0で、tmp[0]が0xC,0xA以外ならば cTopにstrcatする
      if((cnt == 0)&&((tmp[0] != '\n')&&(tmp[0] != '\r'))){

        strcat(cTop,&tmp[0]);
      /// cntが0で0xAが来たらstrcat後、cnt++する
      /// }else if((cnt == 0)&&(tmp[0] == '\n')){  /// ここも修正する

      /// cntが0で、tmp[0]が0xAならば cnt++する
      }else if((cnt == 0)&&(tmp[0] == '\n')){
        /// strcat(cTop,&tmp[0]);
        cnt++;
        flg = 1; //cntが初めて1になるときのフラグ
      }

      // cntが1以上ならばcName[],cAddress[]への処理をする
      // 2行前でcnt++ としているので、このままではcName処理に入ってしまう
      // よって flgがゼロになる場合だけの処理にする(最下段でゼロにしている)
      if((cnt > 0)&&(flg == 0)){
        // cName[ ] 処理と cAddres[ ] 処理を切り分ける chkを算出
        if(cnt % 2){  // cnt が奇数ならば
          chk = 0;
        }else{
          chk = 1;
        }
        // 変数 cName[ ] または cAddress[ ] の [ ] 内番号 iiを cnt と chk から算出する
        ii = (cnt / 2) - chk;   // cnt を 2で割った商から cnk を引けば ii となる
        //cName[]またはcAddress[]へstrcatする
        /// if(chk == 1){   /// ここも修正する
        if((chk == 1)&&((tmp[0] != '\r')&&(tmp[0] != '\n'))){  // CR,LF以外をstrcat する
          strcat(cAddress[ii],&tmp[0]);
        ///  }else if(chk == 0){   /// ここも修正する
        }else if((chk == 0)&&((tmp[0] != '\r')&&(tmp[0] != '\n'))){  // CR.LF以外をstrcat する
          strcat(cName[ii],&tmp[0]);
        }  //if(chk
        if(tmp[0] == '\n'){
          cnt++;  // 0xAが来れば cnt を加算する
        }
      }  //if(cnt
      flg = 0;

    } //while
    // close the file:
    dataFile.close();
  } else {
    // if the file didn't open, print an error:
    Serial.println("error opening CCP_Set.txt");
  }








ArduinoでCAN通信(その9:CCP通信・再テスト)

前回までのccp通信テストでは、CANツールである07E2アドレスへのECUからの応答があったので、「テストOK」としていたのですが、返ってくるデータを実数変換してみると、変な値になっています。特にATM(大気圧)の値が99hPa程度であるはずが、変な値を示す場合があることに気が付きました。

【前出の記事での値】 (?は実数値の表示変換ミスで解決済み)
NE   :   0
VS   :?
Q    :?
ATM  :  32
THEX1:  31
THEX2:  96
QP   :?
MOD  : 90


色々と考えた末、もしかしたらECU側に問題が無いかをチェックするため、以前プロに作ってもらったツールを引っ張り出して通信テストを試みました。

【プロ仕様のCPP通信器】
Arduino_CAN_ccp6.jpg

ECUと銘打ったDsub9コネクタに電源とCAN信号が入ります。
本論とは関係ありませんが、GPSと書かれた端子が外部GPSアンテナを接続するもので、車がどこを走行したかを記録できます。

計測した結果がこれです(csvファイルの部分)

日時 時刻 NE VS PATM THAEX1 THAEX2 QFIN
2016/6/13 20:46:56 0 0 0 0 0 0
2016/6/13 20:46:56 0 0 98.47772 -20 -20 -50
2016/6/13 20:46:56 0 0 98.47772 -20 -20 -50
2016/6/13 20:46:57 0 0 98.47772 -20 -20 -50
2016/6/13 20:46:57 0 0 98.47772 -20 -20 -50
2016/6/13 20:46:58 0 0 98.5184 -20 -20 -50
2016/6/13 20:46:58 0 0 98.52095 -20 -20 -50
2016/6/13 20:46:58 0 0 98.48788 -20 -20 -50
2016/6/13 20:46:59 0 0 98.47958 -20 -20 -50
2016/6/13 20:46:59 0 0 98.47802 -20 -20 -50
2016/6/13 20:47:00 0 0 98.47774 -20 -20 -50
2016/6/13 20:47:00 0 0 98.47772 -20 -20 -50

PATMと書かれた列が大気圧で正常に受信できています。

即ち、うまくCPP通信ができていないのは、私が作ったArduino_CANに問題があります。

・・・何日もその原因を考え、
CCP通信をしているArduino_CANの通信状態を、もう一台のArduino_CANで横から覗いてみました。

すると、プログラム通りにCCP起動の送信しているはずの送信文に欠けが多々生じてます。
これは変です。Arduinoの受信処理スピードに問題は無いので、これは送信に問題がありそうです。

送信手順のプログラムを辿りました。

   CAN.sendMsgBuf(0x0000****, 0, 8, stmp);

これはstmpに格納された8Byteデータを0000****というIDで送信する部分です。
本プログラムでは次のコードの中で70回以上連続して送信しています。

    //ccp通信設定
    transmitDataFirst();
    transmitData(0x0C, cID, 0x80, 0, 0, 0, 0, 0, 0xFE, 0x39);
    transmitData(0x14, cID, 0, 0, 0, 0, 0x07, 0xE2, 7, 0);

    transmitFloat32(0, cID, lAddress[0]);
    transmitFloat32(1, cID, lAddress[1]);
    transmitFloat32(2, cID, lAddress[2]);
    transmitFloat32(3, cID, lAddress[3]);
    transmitFloat32(4, cID, lAddress[4]);
    transmitFloat32(5, cID, lAddress[5]);
    transmitFloat32(6, cID, lAddress[6]);

    transmitUbyte(6, cID, lAddress[7]);

    transmitData(0x0C, cID, 0x81, 0, 0, 0, 0, 0, 7, 0);
    transmitData(0x06, cID, 2, 0, 6, 0, 0, 1, 7, 0);
    transmitData(0x08, cID, 1, 0, 0, 0, 0, 0, 7, 0);

この処理がうまくできていないということは「送信タイムアウト」が考えられます。
mcp_can.cppを見てみると、送信バッファが空くのを50サイクルまで待ち、それでも空かなかったらタイムアウトとしています。即ち、CANコントローラMCP2515の送信処理があるサイクル内で終わらなかったらタイムアウトするようです。

//--- 抜粋
INT8U MCP_CAN::sendMsg()
{
    INT8U res, res1, txbuf_n;
    uint16_t uiTimeOut = 0;

    do {
        res = mcp2515_getNextFreeTXBuf(&txbuf_n);                       /* info = addr.                 */
        uiTimeOut++;
    } while (res == MCP_ALLTXBUSY && (uiTimeOut < TIMEOUTVALUE));

    if(uiTimeOut == TIMEOUTVALUE)
    {  
        return CAN_GETTXBFTIMEOUT;                                      /* get tx buff time out         */
    }
//--- 抜粋終わり

そこで少々手荒なのですが、送信する部分の間に delay(10) を挿入してみました。

・・・はい、やっとうまく動作しました。

NE   :    0
VS   : 0.0
Q    :3276754.3
ATM :99.0
THEX1:1310702
THEX2:1310702
QP   : 0.0
MOD: 90

ATMがECU内蔵センサで計測している気圧です。
その他はセンサ類を付けていないので正常な値ではありません。


やれやれ、やっとのことで正常動作したようです。

実はテストに使ったECUは大分古いバージョンなので、最近のECUでも通信が可能かをテストするようです。






ArduinoでCAN通信(その8:CCP通信・テスト)

諸々の準備ができたので、やっとCCP通信をトライします。

【ECUと通信準備ができたArduino】
Arduino_CAN_ccp5.jpg

24Vという大きな印字があるアルミの箱がECUです。これでエンジンや関連機器を制御してます。
今回はCCP通信テストだけなので、24V電源(赤が+B、右カプラにGNDあり)とCAN通信線(右コネクタ)だけです。

Arduinoは先のseeedのCAN-BUSソースに合わせたSPI.h仕様品です。

【ソフトウエア構成・概略内容です】
Arduino_CAN_ccp2.jpg

何から何まで最初から作るのは大変なので、昔プロに依頼して作ったVB6ソースを移植します。
流石、プロが作ったソフトは整然と作ってあります。複雑な通信手順を4種の関数にまとめ、表に見えるのは上図の14行で通信手順が完了します。整然としてます。(注意:この作りはあるECUでのケースです。CCP通信の標準手順であるかは未確認です)

この通信手順で最も特殊なのが、最初の行の、transmitDataFirst() 関数です。これはこのECU独自のスタートアップ手順のようで、これが無いとCCP通信が起動しません。(内容は割愛します)

次の2行はCCP通信の起動手順のようです。

次に続く7行が、ECUアドレスを指定して、そこのデータを送信してもらう手順です。Flort32という関数名称が示すように、IEEE形式の32Bitsバイナリでデータ転送されます。この部分はそのECU固有の値ですので省略します。番号は0から6まで7個指定できます。

続くのはByteタイプのデータ指定です。番号は7で1個指定できます(能力的にはかなり可能なはずですが)
続く3行がCCP通信手続きの終了部分です。

(関数内部の処理は秘匿性が高い部分なので割愛します)

【動作結果】
Arduino_CAN_ccp4.jpg

参考までに
CAN通信の状況を表示する赤いLEDは、上から送信、受信、受信割り込みです。
この写真では R という受信ランプが光って見えます。ECUが内部アドレスの値を返してきてます。
実は、受信割り込みの I というLEDも同様に光り、更に シリアル通信タイミング大きく光るのですが写真ではそれが映っていません。


先のccp通信起動をECUに流すと、ECUから継続的に、指定したECUアドレスデータが転送されてきます。

Arduino_CAN_ccp3.jpg

ECUから送られて来たデータをシリアル・モニタで表示したところです。

何と10mSec未満の間隔で送られてきますので、8ビットArduino で処理するのは大変そうです。
CANトランシーバは能力があるので、受信したデータをそのままArduinoに送って来たのをそのままシリアル・モニタに横流ししたのが上図です。これを少し解説すると、

・最初の数字:01から06は、関数 transmitFloat32()で指定したECUアドレスから返された値が、続く7Byteに収録されています。IEEE32バイナリフォーマットです。
・Byte指定した 7 のデータが返って無いように見えますが、06の最終 Byte 部分に格納されています。

これらのデータは転送されたまま収録バッファにしまい込んで置き、使用したい間隔(0.1秒とか0.5秒間隔)で抽出し、IEEE32バイナリを通常の float や int 形式に演算・キャストして使えば良いと思える。

忘備録的ですが、IEEE32 フォーマットデータを Float にキャストする資料を記載します。

【IEEE32 フォーマットについて】
こちらの記事がとても良く書いていると思います。英語ですが。

【私のプログラミング(やや冗長)】

void transFloat(){
  INT8U i,j,pE;
  INT32U pF;
  INT16U pS;

  for(i=0;i<6;i++){
    pE = i_dat[i][1];
    pF = (INT32U)(((i_dat[i][2] % 0x80)) * 0x10000) + (INT32U)((i_dat[i][3]) * 0x100) +
     (INT32U)((i_dat[i][4]) + 0x800000);
    if((pE / 0x80) == 0){
      pS = 1;
    }else{
      pS = -1;
    }
    pE = (pE % 0x80) * 2 + (INT8U)(i_dat[i][2] / 0x80);
    f_dat[i] = (float)(pS * (float)(pF / pow(2,(0x96-pE)))); 
  }
補足:i_dat[ i ] [ 1..4] に ECU から転送されてきた IEEE32 データが格納されています。
    演算処理後、 f_dat[ i ] にFloat が収納されます。

実はこの使用したい間隔に、以前と同じ MsTimer2 を使おうと思ったのですが、何故か seeed の CAN-BUS Sheild ソフトとぶつかるようです。よって今回は、 timer1 で発生させているカウンター値をポーリングし、そこで演算処理しました(この方法は良くないので、当初のタイマー割り込みで演算処理する方式に変える予定です)

【演算処理した結果】MS P ゴシックなので表示桁がずれてます

----- 5月28日の記事の結果

TIME :01m01.56s
00 00 00 00 00 04 46 - 01m02. 0s
01 00 00 00 00 04 46 - 01m02. 0s
02 C2 00 00 00 04 46 - 01m02. 1s
03 C2 00 36 00 04 46 - 01m02. 1s
04 C2 00 36 00 04 46 - 01m02. 2s
05 00 00 36 00 04 46 - 01m02. 3s

NE   :   0
VS   :   0
Q    :   0
ATM  :  98
THEX1:  98
THEX2:   0
QP   :   0
MOD  :  90

外部信号が全く入っていないECUなので、唯一動作している、大気圧 ATM が 98 という値を表示しています(実際は float なので分解能はもっと高いです)。これは 98 kPaという意味で、基準大気圧 101.325kPa よりも低い状態にあるということです。

THEX1 は不定状態にあるので、ATM の値を引いてしまっているのかもしれません。(Arduino 内部の問題かもしてませんが、真相は不明です)

注:上記通信データリストには、06 が抜けています。小生のコペピ作業ミスのようです。

----- 5月29日 再テスト結果を追記

00 00 00 00 00 04 46 - 12m26. 0s
01 00 00 C2 00 04 46 - 12m26. 0s
02 42 00 00 08 04 46 - 12m26. 1s
03 42 00 00 A0 04 46 - 12m26. 2s
04 42 00 C1 A0 04 46 - 12m26. 2s
05 42 C2 C1 48 04 46 - 12m26. 3s
06 00 00 C1 00 04 46 - 12m26. 4s
NE   :   0
VS   :?
Q    :?
ATM  :  32
THEX1:  31
THEX2:  96
QP   :?
MOD  : 90
TIME :12m26. 7s
00 00 00 00 00 04 46 - 12m26.50s
01 00 00 C2 00 04 46 - 12m26.50s
02 42 00 00 02 04 46 - 12m26.51s
03 42 00 00 A0 04 46 - 12m26.52s
04 42 00 C1 A0 04 46 - 12m26.53s
05 42 C2 C1 48 04 46 - 12m26.53s
06 00 00 C1 00 04 46 - 12m26.54s
NE   :   0
VS   :?
Q    :?
ATM  :  32
THEX1:  31
THEX2:  96
QP   :?
MOD  : 90
TIME :12m26.57s

気になったことが3点あったので再テストしました。

・06 と書かれた行が抜けていたこと:コピペミスではなく、私のソフトミスでした。
 0 から 6 までを、for 文でネストする際はVBでは、for i = 0 to 6 と記述します。
 これを、c言語では、for(i=0;i<=6;i++)か、for(i=0;i<7;i++)と書かねばならないのですが、for(i=0;i<6;i++)でした。
 つまり 6 が抜けてしまってました。

・例えば500mSec毎に数値化処理し、シリアル転送する処理時間はどの程度か?
 追加した結果リストはシリアル転送したものですが、*.00secか、*.05sec 毎にこの処理subを呼び出すのですが、リスト最初行の時間 12m26. 0s が示すように、シリアル送出直前にある演算処理はほとんど時間がかかっていません(10mSec以下)。しかしシリアル送出には15行で70mSec程度かかるようです。この部分は必要最小限にすべきのようです(500mSec間隔ならば全く余裕があります)

・floatデータをシリアル送出するには
 ?が付いている部分が浮動小数点数値を、sprintf の%f 指定した部分ですが、Arduino UNO 系IDE ではNGのようです。ここは数値を文字列に事前に変換するようです。

 --- 本件に関してさらに追記 ---

 Arduino UNO系 IDE では dtostrf() 関数を使用するとありました。
 dtostrf(実数, 全桁数, 少数以下桁数, 代入先文字変数)
 今回の例では、sprint 関数内に入れました。
 sprintf(StrBuf, "NE   :%s", dtostrf(f_dat[0], 5, 0, sstr));
 このように記述すると、sprintf 関数は dtostrf 関数の格納先 sstr を %s に格納してくれるようです。
 (注:c言語で格納という表現は正しくないのかもしれませんが)

----- 追記はここまで

----- 6月5日に追記します

更に何度か通信テストを続けて行く内に、ccp通信が確立されない場合が発生することがありました。

その原因は
「ECUが外部機器との通信を試みるため、拡張IDによる特定IDでの通信出力を頻繁にするため、Arduinoが起動直後だけでccp通信要求を出しているので失敗する場合がある」ということが判りました。

対応策
1)特定IDへのECUからの通信出力が10回以上連続で検出されたら、ccp通信要求を再発行する。
2)特定IDへのECUからの通信要求は続く場合もあるので、受信IDを見てデータ処理有無を分岐する。
//------ コード例
   if(id == 0x********){       //特定ID、これは都合により割愛します
      icount++;
      if(icount > 10){
         // sprintf(StrBuf, "Restart ccp");   // この2行はデバック用
         // Serial.println(StrBuf);
         transmitDataStart();   // ccp通信要求
         icount =0;
      }
         break;
      }else if(id == 0x7E2){       // 0x7E2 が通信ツールのIDです。このIDは一般に使われるIDです。
         icount = 0;
    //以下データ処理へ続く

// ------ コードここまで

// ------ 追記ここまで

以上で、Arduino が 8ビットマイコンの入門器ながらも、プロ・ユースのCPP通信にも十分使えるメドが立ちました。
今は汎用機器の組み合わせなので2階建てですが、専用基板を起こせばコンパクトで且つパワフルなCAN通信器ができる可能性があります。

用途:

・CAN通信の勉強ツール:送信や受信のArduino ソースが使用できるハード環境を提供

・CAN通信を使った実用ツール
 ・ホビーユースで車両からのCAN信号取得する→BlueToothで外部機器に伝送→用途に即したデータ処理
 ・実務ユースでCCP通信などのツール構築→BlueToothで外部機器に伝送→CANデータを様々な用途に使用

考えらえることは様々にあります。使い方はアイデア次第でしょうね。

専用基板製作のキックオフになりそうです。

【構想中のArduino 一体 CAN-BUS 基板】
Arduino_CAN_27_brd_3.jpg   






ArduinoでCAN通信(その7:CCP通信準備・続き)

いつものArduinoのサンプル・ソフト(この書籍で紹介されている)でCCP通信の準備を開始して気が付いたことがある。

Arduino_CAN_6.jpg

それはこの本に記載されてはいるが、Arduinoのサンプル・ソフトではサポートされていない、「CAN送信のエラー処理」ルーチンである。
送信エラーには2つのパターンがあるそうです・

「自分が出したメッセージが(自機?)内で喪失する場合(送信していない)」
「送信はしたが、CANバスエラーが発生した」

ECUにデータを送ってもらう要求送信時にこんなエラーが出た場合は、確実にエラー処理する必要があります。
本にはエラー処理部分の一部コードが載っているだけで、なかなか理解が進みません。

ここの記載では「Txpend0」というフラグをチェックした後、送信バッファのTxBnCTL.TXREQが1(未送信)、0(送信済み)であるかを判定するようになっていますが、Txpend0 が皆目謎です。
この本、元々はPICマイコン用に作った機器をベースに記載しているので、本家サポートを尋ねましたが長期メンテナンスで閉鎖されています。

推測するに Txpend0 はこの本独自ソフトで用意した送信終了チェック・ルーチンで、それがNGの場合にTxBnCTL.TXREQビットを調べるということのようですが、ここらへんを自分で実装すべきか悩みます。全くゼロから始めて、CAN通信の細かな手順をプログラミングするという目的ならば、自分でソフトを構築すべきなのですが、 【ArduinoでCCP通信する機器を作る】 目的には、かなり回り道になりそうです。

・・・悩んだ結果、ベースとするArduinoソースを切り替えることにしました。
これまでに何度もお世話になっている、

/CAN_BUS_Shield

です。URLは、こちらです。

以前も書きましたが、基幹となっているファイルが、*.cppという拡張子ですので、c++で記載されているようです。
私には更に敷居が高くなってくる思いがしますが、仕方ありません(笑)

【Seeed-Studio のExsample を使うための変更】
先の本ではSPIを若いデジタルピンに割り付け、自前のSPI送受信をやってましたが、SeeedではArduinoオリジナルのSPIポートを使います。SDカードSPIと重なりますが、同時に動作させる機能上の必要が無いので、CSピンで切り分けて使用できます。

Arduino_CANBUS2_2_sch.jpg  

ピンアサインを変更した回路です。MCP2515のSPIはSDカードと共有のArduino標準SPIポートに接続されてます。
デジタルピン9に CAN_CS を割り付け、デジタルピン 10 にはSD_CSを配置してます。

【テスト環境】
上図はシステム構築した後の実用回路(予定)なので、テスト環境が必要です。
とりあえずSDは無しでもCCP通信テストはできるので今使っている送信側のSPI接続を変えます。

Arduino_CAN_31.jpg

なんやらどちゃごちゃの↑部分がArduino純正SPI位置に変更した配線です。
回路図で示すとこんなです。

Arduino_CANBUS2_2_sch.jpg  

SD_***となっている端子がArduino純正SPI端子で、CANトランシーバとSDが共用します。CAN用CS端子をデジタルピン9に割り付けますが、今はまだピン10を使ってます。
また、本番品も同じですが、Seeed の CAN_BUS_Sheild と同様に、MCP2515 コントローラの受信割り込み INT ピンが Arduino のデジタルピン 2 に接続されているので、ハードウエア受信割り込みが使用できます。

seeed の CAN_BUS_Sheild の Exsample に send.ino という簡単なArduino ソースが載っています。
このフォルダ内ファイル構成を少々替え、send.ino と同じ場所に他のファイルも置きます。

Arduino_CAN_32.jpg

このようにしておけば、Arduino IDE で send.ino を開いた時に他のファイルも参照したり編集したりするのが容易になります。

Arduino_CAN_34.jpg
(seeedのExsample、send.inoをロードした画面、一部送信データ部分をマスクしてあります。ご容赦)

以下の部分が少しいじった個所です
    // send data:  id = 0x00,       standrad frame=0, data len = 8, stmp: data buf
    // send data:  id = 0x18F00400, Extend   frame=1, data len = 8, stmp: data buf
    CAN.sendMsgBuf(0x18F00400, 1, 8, stmp);

CAN.sendMsgBuf() 関数で簡単に CAN 送信ができます。スタンダードフレームか、拡張フレームかもここで指定できます。

sendMsgBuf() の実体は mcp_can.cpp に記載されています。これを辿って処理内容を見てみると、CAN送信に必要不可欠な処理がしっかりとフォローされています(例:先に苦労して作った 送信バッファ ID レジスタ処理、送信バッファコントロールレジスタ処理、3個ある送信バッファ選択処理、送信処理状態チェック 等です)

これは何としっかりとしたサンプルでしょうか。CAN_BUS_Sheild を売っていた(いる?)ので、実用ベースになるソフトを作っているのだと推測します。私には大変ありがたいことです。seeed さま、ありがとうございます。

先のテスト環境で送信させ、受信器のシリアル・モニタで得た結果です。

・・・・途中から

RPM:1999(r/min)-00m10.39s
RPM:1999(r/min)-00m10.49s
RPM:1999(r/min)-00m10.59s
RPM:1999(r/min)-00m10.69s
RPM:1999(r/min)-00m10.79s
RPM:1999(r/min)-00m10.89s
RPM:1999(r/min)-00m10.99s
RPM:1999(r/min)-00m11. 9s
RPM:1999(r/min)-00m11.19s
RPM:1999(r/min)-00m11.29s

・・・・途中まで

100mSec毎に送信した拡張IDによる送信結果が受信されています。(回転数1999というデータです)

以上でやっとCCP通信ソフトを作る準備ができました。





ArduinoでCAN通信(その6:CCP通信準備)

前回の5回までで、J1939プロトコルを模したCANの送受信ができたが、今度はもう少し技術的ハードルを上げ、「CCP通信」にトライしてみようと思う。CCP通信の記事を見ると、「CCPとは“CAN Calibration Protocol”の略であり、CANを使った(ECUへの)測定/キャリブレーションプロトコルという意味になります」とあります。

またそれらの記事を読んでみると、
「 CCPは、CANの普及とともに広く使用されるようになりました。その背景は、ECU内部のソフトウェアへアクセスするための通信媒体としてCANを使 うことで、低コストで汎用性のある測定/キャリブレーションが実現できたからです。実際、車載ネットワークに接続されているECUにCCPドライバを組み 込むだけで、すでに設置されているCANを使ったECUの測定/キャリブレーションが可能となります。」

ということでかなり汎用的に使われている通信仕様となります。
現在ではCCPは上位機能のあるXCP規格に統合されたようですが、詳しくはNETなどの解説に任せます。

「ECU内部へアクセス」 ということで、(ECUへ)CAN送信し、(ECUから)CAN受信することになります。
即ち、実際のECUとのCCP送受信に関する勉強になります。

【最終的に作ろうと思っているArduino-CAN通信器の回路】

Arduino_CAN_26_sch_3.jpg

いきなり回路図を出されても何のこっちゃ!ですよね。

【これのEagle基板図】

Arduino_CAN_27_brd_3.jpg

これもまあごちゃごちゃしていて判りませんが、

・Atmega328Pマイコンを搭載(真ん中の32ピン)
・MCP2515 CANコントローラ
・MCP2551 CANトランシーバ
・microSDカード装置を積む
・BlueTooth ドングルを載せる
・LCD(デバックを主体にした用途)
・電源やスイッチ、インタフェース等の回路

これらを 90mm x 55mm程度の小さな基板上に配置した、「Arduino版 CAN 通信ユニット」です。
Arduino UNOと同じピッチのコネクタ配置にしてあるのは、上に基板を追加すれば、Analog出力や入力、はたまた他の機能を増設できることを考えています。
とは言ってもArduinoのデジタルピンの割り付けはすでに満杯状態なので、機能切り替え目的にDIPスイッチに割り付けているピンを流用できるようにしたほうが良いでしょうね。MCP4922 という2chの12Bit DA変換ICならば、microSDに使っているSPI信号を使ってアナログ出力を作ることが可能です。SPI接続機器を使い分けるCS信号だけがあれば良いと思われます。

表面実装のAtmega328 を使用するので、ブートローダ書き込み用ICSP端子、スケッチ書き込み用の(DTR,RX,TX,GND)端子を設けています。USB接続のFT232の入出力をここに取り付けて、書き込みやシリアルモニタに使用します。

microSDカードを装備したのは、ECUにアクセスするための設定をここに書き込んでおき、Arduinoに読み込ませる目的です。
ドライブにFat FSを用いる予定なので、Atmega168ではプログラム・エリアが足りません。

・・・とは言っても、最初からこの基板を作ってチャレンジするほど自信は無いので、実際のトライに使うのは、先に作った Arduino用 2階建て基板とArduinoもどき です。
今回新採用となるmicroSD部分をユニバーサル基板に組んで、Arduino にピン接続しても良いのですが、センシティブなSPI信号によるアクセスなので(以前、STM32で懲り懲りした経験あり)、新たにCAN計測基板を手つくりします。

【回路図】
Arduino_CAN_28_sch.jpg

追加したmicroSDカード(秋月製 AE-MICRO-SD-DIR)を差し込む端子が左上です。
SPI接続に4ピン、電源とGNDに2ピンの6ピンに配線します。

接続する端子名に、SD_***が付いていますが、Arduino のピン 10,11,12,13 は本来の Arduino SPI 端子です。
CAN コントローラのSCK 端子にピン 12を割り当てていたので、これをピン 9に移動しました。CAN コントローラを制御するソフトが、ソフトウエア的動作するSPIの仕組みを作ってあるので、こんな際にとても便利です。

また、microSDカードのピン下に680と360Ωの抵抗が付いていますが、これはArduino から出てくる 5V信号を抵抗で 3.3Vに分圧させる抵抗です。1kΩ台に変更しても動作に問題は出ないでしょう。
(最終予定のArduino-CAN基板では、このインターフェイスは専用IC・TXS0108E を使う予定です)

【基板図・参考まで】
Arduino_CAN_29_brd.jpg

さてハードウエアの事前準備は整いました。設計したmicroSD搭載基板を作ればOKです。

次はCCP通信手順の構築ですが、これはCMでの情報を使うことにします。

・・・昔、プロのお手伝いを頂き、Windows2000搭載Box型コンピュータとVisual・Basic、そしてUSB-CANで構築したシステムを使い、CCP通信をしたことがあります。
かなりの量のコーディングでしたが、果たして、小生の貧弱なC言語スキルで Arduino を使いこなせるものでしょうか?






プロフィール

haiga

Author:haiga
私のブログへようこそ!
電気オンチが始めた自作オーディオです
2010/3/17 電子工作をプラスしました。

自作オーディオの楽しみ共有のため、私が作ったパーツ提供をしてます。質問や要望を遠慮なくコメント欄に書き込んでください。

FC2カウンター
その日1回目にアクセスいただいた方の総カウントです
最新記事
最新コメント
最新トラックバック
カテゴリ
月別アーカイブ
検索フォーム
RSSリンクの表示
リンク
ブロとも申請フォーム

この人とブロともになる

QRコード
QRコード