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");
  }








コメントの投稿

非公開コメント

管理人のみ閲覧できます

このコメントは管理人のみ閲覧できます

No title

>Manmos さま

コメント頂きありがとうございました。とても嬉しいです。
CAN通信なんというマニアックな記事はなかなか見て頂けることはないかと思いながらも、備忘録の意味で書き連ねております。

今後の課題ですが、計測結果をAndroid端末で受信するのは別のスキルが必要なので非優先とし、乗用車系ECUとのCAN通信で遊んでみようかと思っております。

本来始めたオーディオ記事もまもなく再開の予定です。
プロフィール

haiga

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

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

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

この人とブロともになる

QRコード
QRコード