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 を使いこなせるものでしょうか?






ArduinoでCAN通信(その5:送信)

前にも記載しましたが、中華製 J1939 送信エミュレータは 通信が最速0.1秒間隔なので、実機が最速 20msec 間隔で送ってくるデータ処理ができるかどうかの判断には使えない。

【J1939 送信エミュレータ】
Arduino_CAN_2.jpg

そこで Arduino + MCP2551 + MCP2515 による2台のノードで、送受信させようというわけです。

・・・2日程度かけてやっと送信ができました。

【送受信中の2台のCAN機器】
Arduino_CAN_23.jpg

左が送信機として動作している Atmega328P マイコン搭載機、右が受信中の Atmega168P搭載機です。
Atmega328Pの在庫が切れたので 168 を使ったが、コンパイル後のソースが9kByte弱なのでOKです。
受信機の赤いLEDが点灯していますが、上側がRX受信端子、下側がINT受信割り込み端子に接続してます。

MCP2515には送信バッファが3個用意されていますが、今回のテストでは1個しか使ってません。
データ転送は 0.1秒(100msec)間隔で同時6個送信するプログラムですが、実際には1データ 20~30msec 間隔ぐらいが最速のようです。

後に記載するプログラムでは、送信バッファにガンガン送信しても、送信できない場合はデータを喪失してしまうので、コントロールレジスタのフラグをチェックしながら転送制御する必要があります(今回はまだ未実装です)

【正常に受信できている場合のシリアル・モニタ】   注:拡張idの一部を非表示にしています
Arduino_CAN_25.jpg

モニタ初期はid末尾3桁が300,400,111の3個だけですが、途中から100も現れます。
送信は6個のデータ・セットを100msec間隔で送出(個々のデータでは計算上 17msec 間隔)するようにしていますが、これはとても無理のようで、実質は 20~30msec間隔が最速のようです(送信バッファ1個での条件です)

実機での最速インターバルが 20msec ということなのでまずまずかなと思います。
実際の使用環境では、受信したデータはデータエリアに記録して置き、一定時間間隔(100msec程度)でそれを参照して使うので、受信が少々遅れても大きな問題にはなりません。(変化量が比較的少ないので、大きな誤差に至らないという意味です)

尚、6種のデータを送ってますが、4種のidだけが受信されるように、受信側でフィルタを設定しています。

【送信プログラム】

受信に使ったプログラムをベースに、メインプログラムの不要な部分を削除し、送信部を追加しました。

1.不変部分
 ・CAN初期化
 ・SPI設定
 ・タイマ設定
 ・LCD設定(実際にはLCDは使っていない)
 ・他

2.削除部分
 ・フィルタ・マスク
 ・受信割り込み

3.追加部分
 ・送信データ(拡張id、8Byteデータ、各々6個)
 ・送信バッファのIDレジスタ用データ、4種×各6個、処理速度を上げるために用意しておく
 ・送信手順

追加した部分のプログラム・コード(***部分は非表示です)

// --------------
// N258_TX.ino
// ---------------
// 拡張id
long id[7] = { 0x0000000,0x18F**400,0x18F**300, 0x18F**111,0x1CF**100,0x18F**200,0x18F**E00 };  //、0番は無効ID 
byte dat[7][8] = { {0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00},  //7個並べる、0番は無効 
                          {0xFF,0xFF,0xFF,0x3E,0x78,0xFF,0xFF,0xFF},
                          {0xFF,0xAC,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF},
                          {0xFF,0x63,0x00,0xFF,0xFF,0xFF,0xFF,0xFF},
                          {0xFF,0x0D,0x05,0xFF,0xFF,0xFF,0xFF,0xFF},
                          {0x00,0x78,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF},
                          {0x7B,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF}  };
//SID,EID 変数エリア
byte SIDH[7] = {0x00,0x00,0x00,0x00,0x00,0x00,0x00};
byte SIDL[7] = {0x00,0x00,0x00,0x00,0x00,0x00,0x00};
byte EID8[7] = {0x00,0x00,0x00,0x00,0x00,0x00,0x00};
byte EID0[7] = {0x00,0x00,0x00,0x00,0x00,0x00,0x00};

// ・・・

void make_id( byte num, long id);  //送信バッファを事前に作る処理宣言
void alldataTx();  // 全6データを送信する処理宣言

// ・・・

// 事前にidを作っておく処理の実体
// SIDH,SIDL,EID8,EID0 を複数作るときの処理
void make_id( byte num, long id) {
  word canid;

  canid = (word)(id & 0x0FFFF);
  EID0[num] = (byte)(canid & 0xFF);
  EID8[num] = (byte)(canid >> 8);
  canid = (word)(id >> 16);
  SIDL[num] = (byte)(canid & 0x03);
  SIDL[num] += (byte)((canid & 0x1C) << 3);
  SIDL[num] |= 0x08;
  SIDH[num] = (byte)(canid >> 5);
}

// ・・・

//全データを送信する処理実体
void alldataTx(){
  struct tag_TXBnCTRL *pctrl;
  struct tag_TXBnDLC *pdlc;
  byte i, j, k, reg, p;
  word msgL,msgH;
  long msg2;
  byte ctrl, dlc;
 
  for(i = 1; i < 7; i++){
    MsgBuf[1] = SIDH[i];
    MsgBuf[2] = SIDL[i];
    MsgBuf[3] = EID8[i];
    MsgBuf[4] = EID0[i];
    MsgBuf[5] = 0x08;
    for(j = 6; j < 14; j++){
      MsgBuf[j] = dat[i][j-6];
    }
   
    CANTxB0Write(&MsgBuf[1],8);   //送信バッファシーケンシャルライト
    /*
    ctrl = 0;
    pctrl = &ctrl;
    pctrl -> TXP = 0;
    pctrl -> TXREQ = 1;
    */
    //ctrl = 0;
    ctrl = 0x08;
    //CANWriteReg(TXB0CTRL, ctrl);     // これは参考書にあった記述だが不要だった(動かない)
   
    CANRTS();  // あるページで見つけた上の行に対する代替え機能
    delay(10);
}

ここで注釈します。参考書では、CANWriteReg(TXB0CTRL, ctrl); と記述すれば、MCP2515のコントロールビットがONになり送信が開始されると書かれている。・・・しかし動かない。
データシートを見ると同様のことが書かれているが、その後をよく読むとRTSで起動させることが必要とある。

その方法は、
1.SPIによる書き込みコマンドによってレジスタに書き込み、
2.SPI RTS コマンドを送る
3.RTSピンをLOWにするハード的な起動、

この記述で最も不明だったのが、これらは and なのか or なのか、はたまたそれらの組み合わせなのかが判らない。
で、あるページで成功したという記述を見つけました。

それは、2.です。

これに準じてコードを生成すると動きました(最初しばらくは動作不安定でしたが・・・)

以下を追記します
// ------------------
// CAN2515.h
// ------------------
// --------------------------------------------------------------------
// CAN送信
// --------------------------------------------------------------------
void CANRTS(void) {
  byte i;
  byte cmd = SPI_INST_RTS;
  cmd |= _BV(0);

  CAN_SPI_CS_L;

  for(i = 0; i < 8; i++) {
    if(cmd & 0x80) {
      // 1
      CAN_SPI_SO_H;
    } else {
      // 0
      CAN_SPI_SO_L;
    }
    cmd <<= 1;
    CAN_SPI_SCK_H;    // SCK パルス出力
    CAN_SPI_SCK_L;
  }

  CAN_SPI_CS_H;
}

/////
はい、以上で J1939 CAN のデータ送信・受信までのプロセスを習得できました。

これらのスキルを活かし、次のステップは、応用豊か&実用できるCANツールの構築です。

以下はご要望あれば送付します。エントリーください。
// --------------------
// プログラム・ファイル
// --------------------

Arduino 送信プログラム
P258_TX.zip

Arduino受信プログラム
P258_N2.zip






ArduinoでCAN通信(その4:割り込み受信)

当初の計画は、

【今後のお勉強課題】
1.ID識別で、続くデータを処理する機能のテスト(取りあえずシリアル通信を受けたPCで処理)
2.この機能をArduinoマイコンに実装
3.Arduinoマイコン(実体はMCP2515)でCAN IDによるフィルタ機能の実装(実機で使えるように)
4.BlueToothによる通信と(当初はWindowsPCへ)、Androidタブレットまたはスマホでの表示

なので、現在は 4.の半分まで到達したのだが、なにせ、Androidソフト開発は敷居が高い。

良く考えてみると、現在のCAN受信はメインプログラムのポーリング・チェックで行う方式なので、受信応答に不安がある。
今回のテスト環境は0.1sec間隔程度の受信なので問題が発生していないが、実機では50mSecや20mSec間隔で通信されていると情報があるので、「現在のポーリング受信」から、「受信割り込み処理」に変更する必要がある。

【変更する部分】
・MCP2515 CANコントローラに受信割り込みが発生するとINTピンがLOWになるので、
 これをArduinoの割り込みPIN2に接続する。
 PIN2に接続されていた、SCKはPIN12に移動する。
 これに伴い、SPI接続定義を変更する
・また、現在のJ1939送信シミュレータでは0.1秒間隔の送信しかできないようなので、送信のためのノードも作る必要が生ずる。すなわち、送信プログラムも作る必要がある。

【新規に作ったノード】
Arduino_CAN_19.jpg

右下が新規に作ったノードです。機能切り替えに使うスイッチなども付けるので一回り大きいです。
赤い模様状の左に見える赤いLEDが、CAN_RXと割り込みINT に接続され、かすかに光って動作中です。

【回路図】
Arduino_CAN_17_sch_2.jpg
 
・MCP2515の12ピン INTを Arduinoの割り込み入力0 になる2ピンに接続する。
 この Arduino 2ピンに接続していた SPI のクロック SCK は Arduino 12ピンに移動した。今使っているCANプログラムは Arduinoの SPI.hを使わずに 直接ピンアサインできるので便利です。
・ソフトの機能切り替えに使うと思われるDIPスイッチを追加してます(機能はまだ実装してません)

// ----- 変更するソース ---- (赤字で書いた部分が要注意点です。最初勘違いで少しはまりました)
// P2515SpiPort.h
//
//    MCP2515 接続ポートの定義(for Arduino #258)
//        09/04/06

#ifndef SPIPORT_H
#define SPIPORT_H

// SPIポート定義
#define CAN_SPI_CS_BIT       5        //
#define CAN_SPI_SO_BIT       3        //
#define CAN_SPI_SCK_BIT    4       // 2   (注:ArduinoのPIN12ではなく、ATMEGA328の、RB4ピンを指す)
#define CAN_SPI_SI_BIT        4        //

#define CAN_SPI_CS_DDR       DDRD        //
#define CAN_SPI_SO_DDR       DDRD        //
#define CAN_SPI_SCK_DDR      DDRB    //DDRD   (注:DポートからBポートに変更)
#define CAN_SPI_SI_DDR         DDRD        //

#define CAN_SPI_CS_PORT       PORTD        //
#define CAN_SPI_SO_PORT       PORTD       //
#define CAN_SPI_SCK_PORT    PORTB     //PORTD   (注:PORTDからAtmega328のPORTBに変更)
#define CAN_SPI_SI_PORT         PIND           //

#endif
// ------------------------

Atmega328 と Arduino のピン割り付け図はこちらのページを参照ください。

//-------------------------
// P258_N1をベースに、P258_N2 に変更したソース
// タイムカウントのタイマーを 100msec から 10msec に変更
    //OCR1A = 25000;                // コンペア値
  OCR1A = 2500;        // コンペア値
// ------------------------

// ------------------------
// P258_N2.ino

//  setup部に以下を追加

  // 受信割り込みをセットする
  CANWriteReg(CANINTE, 0x03);

//   Pin2  <- MCP2515 INT による割り込み宣言
//   Pin2 が割り込み0、立ち下がった時に、MCP2515_ISR を実行する
   attachInterrupt(0,MCP2515_ISR,FALLING);  // MCP2515 INT Pin LOW -> Pin2 interrupt
// ------------------------

【参考:MCP2515の受信割り込み許可レジスタ】
Arduino_CAN_22.jpg  

// ------------------------
// P258_N2.ino
// 割り込み処理では、flgRecv を1に変更するだけ
void MCP2515_ISR(){
    flgRecv = 1;
}
// -------------------------

// ------------------------
// P258_N2.ino
    //if(get_num != 0) {      // これを止め

  // データ受信割り込み
  if(flgRecv){                 // flgRecv をチェックする
    //
    flgRecv = 0;             // フラグを戻す
       
    // ステータス・チェック
    reg = CANReadStat();
    if(reg & 1<<CANINTF_RX1IF) {
      // 受信あり ロールオーバ発生中
      MsgBuf[0] = CANReadReg(RXB1CTRL);

      // RXB1からメッセージ、データの読み出し
      CANRxB1Read(&MsgBuf[1], 8);
     
    } else if(reg & 1<<CANINTF_RX0IF) {
      // 受信あり
      MsgBuf[0] = CANReadReg(RXB0CTRL);

      // RXB0からメッセージ、データの読み出し
      CANRxB0Read(&MsgBuf[1], 8);
     
    }
   
    // 受信割り込みフラグをリセットする
    CANWriteReg(CANINTF, 0x00);
    // 注:本来ならば0,1ビットだけを 0 リセットすべき
   
    // 受信あり
    if((MsgBuf[2] & 0x08) != 0x08) {
    // 以下省略
// ------------------------------

【参考:MCP2515の割り込みフラグレジスタ】
Arduino_CAN_21.jpg
割り込み終了後にこれをリセットしないと、再度割り込みができません。


【割り込み受信に変更した結果】
Arduino_CAN_20.jpg

Arduinoのシリアルモニタの結果を示します。
ID 18F00400 は約0.1sec間隔で流れてきていますが、そこに1秒に1回の頻度で、ID 18F00200 と 18FEEE00が割り込んで来るというように見える。この2つのIDは全く同時刻になっているので、RB0,RB1で受信処理されたのかもしれない(発送元がどのような時間で送信しているか良く判らない)
次に受信した18F00400 が、これら2つのデータの0.02秒後になっているので、新たに追加した割り込み受信の仕組みは速い受信性能が期待できる。
INT端子に接続されたLEDの光り方を見ても、薄く点滅している中で約1秒周期で明るく光るので、そこに上記の3信号が集中して受信していると見てよさそうである。

さて次は、「実機と同じように速いサイクルで送信できるノードの製作」 なのですが、私が参考にした本の記事には、送信があまり載っていません。
同じ筆者によると思われる、エレキジャックの記事を参照頂ければわかりますが、この程度です
(このページに割り込み受信に関する記述が載っています。これをヒントにしました)

実は、seeedのサンプルプログラムには送信例が載っていますが、ベースとなる部分が難解なので私のCANの勉強には敷居が高そうです。

うむむ、これは送信プログラムを作るしか無さそうです。





ArduinoでCAN通信(その3:BlueTooth送信)

さて次は BlueTooth による送信ですが、Windows PCでは、あっという間にできてしまいました。

【BlueToothで送信中の本器】
Arduino_CAN_13.jpg

BlueToothであるUSBドングルの青LEDと同基板上のチップ赤LEDが点灯(実際は点滅)しています。
搭載しているBlueTooth は Class1 仕様という 100m の通信ができるものです。

余談になりますが、CMで使う機器で通信距離を試したことがありますが、見通しがきく環境ならば100mはOKでした。
BlueTooth なかなかのものです。

【BlueToothのCOM4での受信状態】
Arduino_CAN_14.jpg

ArduinoのUART通信レートが19200なのでBlueToothでも同じです。問題無くPC側で受信できてます。
RPMは約0.1秒間隔で、VSS(車速)、ECT(温度)は約1秒間隔です。

PC側の設定
Arduino_CAN_15.jpg

Arduino_CAN_16.jpg

COM4がSPPになっているのでこのポートを使います。

(注意:これは受信側PCがWindows10での例です。OSによっては設定が異なり、第3のポートが通信用SPPポートになることがあるようです)

いやいや、こんなに簡単にWindows PCへの BlueTooth 送信ができてしまいました。

さてさて、この先のタブレットやスマホでの受信はどうなるのでしょうか。
Android対応ソフトの開発環境・Android Studio をインストールしてありますが、一度しか動かしたことありません。
特に、JAVAを使ったことが全くないので、お先が見通せませんね。

なんて、いまから弱音を吐いてます。




ArduinoでCAN通信(その2:拡張IDでフィルタ、マスク)

「ArduinoでCAN通信」の次は、拡張IDを使いCANコントローラMCP2515でハード的にフィルタ、マスクを行う機能を追加することになります。

オリジナルのソースには、フィルタ、マスクに関して次のような記述があります。
//-------
// P258_N1.ino
//
    // フィルタ、マスク設定 SID 11bit
    CANSetSidFilter0(0x0000);
    CANSetSidFilter1(0x0000);
    CANSetSidFilter2(0x0000);
    CANSetSidFilter3(0x0000);
    CANSetSidFilter4(0x0000);
    CANSetSidFilter5(0x0000);
    CANSetSidMask0(0x0000);
    CANSetSidMask1(0x0000);

    CANSetFilterRxB0(3);            // RXB0でフィルタ、マスクは使用しない
    CANSetFilterRxB1(3);            // RXB1でフィルタ、マスクは使用しない
//-------------

これの意味を調べてみると
・これは標準ID用です。(拡張ID用は作らねばならない)
・フィルターは0から5まで6個設定ができる
・マスク(スルーさせる部分)は2個設定ができる
・フィルタ、マスク共、0x0000 という、0を設定するとフィルタしない、マスクしないという意味
・フィルタ、マスクをRxB0、RxB1で使う:0、標準IDとして使う:1、拡張IDとして使う:2、使わない:3

さらに、これらのプログラム構成を調べると、
// ------
// CAN2515.h 内にマクロが記述されています
//
// フィルタSID設定 (注:0だけ表示、5まであります)
#define CANSetSidFilter0(sid) CANSetSidFilteMask(RXF0SIDH, sid)
//
// マスクSID設定
#define CANSetSidMask0(sid) CANSetSidFilteMask(RXM0SIDH, sid)
#define CANSetSidMask1(sid) CANSetSidFilteMask(RXM1SIDH, sid)
// ------

そしてマクロを実行するプログラム本体は
// ------
// --------------------------------------------------------------------
// CAN SID フィルタ、マスク設定 (兼用ルーチン)
// --------------------------------------------------------------------
// CAN2515.h 内
//
void CANSetSidFilteMask(byte adrs, word sid) {
    CANWriteReg(adrs, (byte)(sid>>3));            // HIGH
    CANWriteReg(adrs + 1, (byte)((sid & 0x07)<<5));        // LOW
}

CANWriteREG() というのはSPIを使ってMCP2515指定アドレスにByteデータを書き込む部分ですので、フィルタ、マスクを書き込むルーチンを拡張ID用で作れば良いということのようです。

// --------
// CAN2515.h に追加した記述
//
// フィルタEID設定
#define CANSetEidFilter0(eid) CANSetEidFilteMask(RXF0SIDH, eid)
#define CANSetEidFilter1(eid) CANSetEidFilteMask(RXF1SIDH, eid)
#define CANSetEidFilter2(eid) CANSetEidFilteMask(RXF2SIDH, eid)
#define CANSetEidFilter3(eid) CANSetEidFilteMask(RXF3SIDH, eid)
#define CANSetEidFilter4(eid) CANSetEidFilteMask(RXF4SIDH, eid)
#define CANSetEidFilter5(eid) CANSetEidFilteMask(RXF5SIDH, eid)
//
// マスクEID設定
#define CANSetEidMask0(eid) CANSetEidFilteMask(RXM0SIDH, eid)
#define CANSetEidMask1(eid) CANSetEidFilteMask(RXM1SIDH, eid)
//
// -------------------
// プロトタイプ
void CANSetEidFilteMask(byte adrs, long eid);
// -------------------------------------------------------------------- 
// CAN EID フィルタ、マスク設定(兼用ルーチン)
// --------------------------------------------------------------------
void CANSetEidFilteMask(byte adrs, long eid) { 
  word canid;                                                   // 抽出2バイト値
  byte bSIDH;                                                   // フィルタ標準ID上位
  byte bSIDL;                                                   // フィルタ標準ID下位
  byte bEID8;                                                   // フィルタ拡張ID上位
  byte bEID0;                                                   // フィルタ拡張ID下位

  canid = (word)(eid & 0x0FFFF);       // 拡張IDの下位側2バイトをcanidに抽出する
 
  bEID0 = (byte)(canid & 0xFF);          // canid の下位1バイトがフィルタ拡張ID下位
  bEID8 = (byte)(canid >> 8);              //  同上の上位1バイトがフィルタ拡張ID上位
  canid = (word)(eid >> 16);                // 拡張IDから上位2バイト分の標準ID部分を
                                                   // canidに抽出する
  bSIDL = (byte)(canid & 0x03);          // フィルタ標準ID下位に抽出したcanid の
                                                   // 下位2ビット(EID17,EID16)を代入する
  bSIDL += (byte)((canid & 0x1C) << 3 );    // 同上のb4,b3,b2の3ビットを3ビット
                                                   // 上方シフトして加える(SID2,1,0)
  bSIDL |= 0x08;                                 // 拡張IDを示すEXIDEビット(b3)を立てる
  bSIDH = (byte)(canid >> 5);              // フィルタ拡張ID上位は 抽出していた値を
                                                   // 5ビット右シフトして代入する
  CANWriteReg(adrs, bSIDH);                     // MCP2515 には、SIDH,SIDL,EID8,EID0 の
  CANWriteReg(adrs + 1, bSIDL);               // 順にアドレスが並んでいるので
  CANWriteReg(adrs + 2, bEID8);               // 順に書き込む
  CANWriteReg(adrs + 3, bEID0);
}
// ------------------

これを作るに当たり、先に参考にしたseeedのソースをまた参考にしました。
元となったソースでは、標準IDか拡張IDかを判定するフラグによって処理を分岐させるように作ってあります。汎用的に使われる機器の場合はそのような作りが好ましいと思えます。

忘備録として、フィルタIDバッファと拡張IDデータの関係を図で記載します。
表内のSIDは標準ID、EIDは拡張ID用のビットです。

RXFnSIDH フィルタn標準ID 上位(n=0,1,2,3,4,5)
(アドレス:0x00,0x04,0x08,0x10,0x14,0x18)
b7b6b5b4b3b2b1b0
SID10SID9SID8SID7SID6SID5SID4SID3

RXFnSIDL フィルタn標準ID 下位(n=0,1,2,3,4,5)
(アドレス:0x01,0x05,0x09,0x11,0x15,0x19)
b7b6b5b4b3b2b1b0
SID2SID1SID0-EXIDE-EID17EID16
EXIDE は拡張ID許可ビットで、0:標準ID、1:拡張ID を示します。

RXFnEIDH フィルタn拡張ID 上位(n=0,1,2,3,4,5)
(アドレス:0x02,0x06,0x0A,0x12,0x16,0x1A)
b7b6b5b4b3b2b1b0
EID15EID14EID13EID12EID11EID10EID9EID8

RXFnEIDL フィルタn拡張ID 下位(n=0,1,2,3,4,5)
(アドレス:0x03,0x07,0x0B,0x13,0x17,0x1B)
b7b6b5b4b3b2b1b0
EID7EID6EID5EID4EID3EID2EID1EID0


このフィルタバッファに拡張IDデータを入れるわけだが
拡張IDには、連なる4バイトにデータが下図のように並んでいる。

b7b6b5b4b3b2b1b0
---SID10SID9SID8SID7SID6

b7b6b5b4b3b2b1b0
SID5SID4SID3SID2SID1SID0EID17EID16

b7b6b5b4b3b2b1b0
EID15EID14EID13EID12EID11EID10EID9EID8

b7b6b5b4b3b2b1b0
EID7EID6EID5EID4EID3EID2EID1EID0

このような配置なので、先のようなCAN EID フィルタ、マスク設定ルーチンになる。


次にメインのunoソースを変えます
// -----------
// N258_N1.uno
//
// SID用フィリタ、マスク部をコメントアウト(省略)
//
  // フィルタ、マスク設定 EID 29bit
  CANSetEidFilter0(0x18F00200);   //J1939 sim 'VSS' 6Byteが、km/hr
  CANSetEidFilter1(0x18F00400);   //J1939 sim 'RPM' 5Byteの64倍が、r/min
  CANSetEidFilter2(0x18FEEE00);   //J1939 sim 'ECT' 1Byteの-40オフセットが、℃
  CANSetEidFilter3(0x00000000);
  CANSetEidFilter4(0x00000000);
  CANSetEidFilter5(0x00000000);
  CANSetEidMask0(0x7FFFFFFF);     //全てのビットを立てるとフィルタの全一致
  CANSetEidMask1(0x7FFFFFFF);     //(同上)

  CANSetFilterRxB0(0);            // RXB0でフィルタ、マスクを使用する
  CANSetFilterRxB1(0);            // RXB1でフィルタ、マスクを使用する
// -----------

3個の拡張IDを通すようなフィルタ、マスクにしました。

フィルタを通ってきた3種のデータの後に数値を表示させてみました。

// -------------------------
// N258_N1.uno
//
    //CANデータを処理し、数値を追加
    if(msg2 == 0x18F00200){
      sprintf(StrBuf, "-VSS:%d(km/hr)", MsgBuf[11]);
      PUTSTR(StrBuf);
    }
    if(msg2 == 0x18F00400){
      sprintf(StrBuf, "-RPM:%d(r/min)", (word)(MsgBuf[10]*64));
      PUTSTR(StrBuf);
    }
    if(msg2 == 0x18FEEE00){
      sprintf(StrBuf, "-ECT:%d(deg)", (word)(MsgBuf[6]-40));
      PUTSTR(StrBuf);
    }
// ----------------------------

【拡張IDフィルタを使った処理結果、シリアルモニタ】
Arduino_CAN_12.jpg

回転数rpmは0.1秒間隔くらいで送られてきています。
これぐらいのデータ量ならばArduinoで難なく処理できているということでしょう。

さて、計画していたことは
【今後のお勉強課題】
1.ID識別で、続くデータを処理する機能のテスト(取りあえずシリアル通信を受けたPCで処理)
2.この機能をArduinoマイコンに実装
3.Arduinoマイコン(実体はMCP2515)でCAN IDによるフィルタ機能の実装(実機で使えるように)
4.BlueToothによる通信と(当初はWindowsPCへ)、Androidタブレットまたはスマホでの表示

1.2.を飛ばして3.ができてしまいました。

お次は4.の前半・BlueToothによる結果の伝送です。





ArduinoでCAN通信(その1)

CMでJ1939規格に準拠した機器からCAN通信するという仕事があった。
業務の場合、それなりの機器を入手して簡単にCAN通信が行えるのだが、それでは自分のスキルアップにはなりにくい。

そこで業務とは別に、CAN通信の勉強ができそうなArduinoマイコンとCANトランシーバ/MCP2551、CANコントローラMCP2515の組み合わせで勉強することにしました。この組み合わせは非常に情報が多いので助かりそうです。

今回はJ1939信号をArduinoマイコン+CAN基板で計測するまでを記載します。

【準備】

CAN通信の細かなことを勉強するには書籍も必要だろうということで選んだのが、これです。

Arduino_CAN_6.jpg

かなり細かな記述がされ、Arduinoの回路図やプログラムのダウンロードが可能です。

まずはCAN受信から学ぶことになるのですが、J1939準拠の信号を出してくれるものがないと学習がうまく進まない。
色々と探してみたらありました。こちらです。 下図、右上です。

Arduino_CAN_7.jpg

いずれのエミュレータも、5~8種の可変項目を持っていそうです。(VOLの数から見て)

この4種の J1939 エミュレータから最も安価で、LCDが付いているビジュアルなものを選びました。

Arduino_CAN_2.jpg  

ところが、安いものは安いだけのことがありました。 「中華品質」に所以しているのでしょうかね(内容は後述)

さて、肝心なのはCAN受信機器です。
先の既述のように、「Arduinoマイコン、CANトランシーバ/MCP2551、CANコントローラMCP2515」 です。

Arduino_CAN_4.jpg

中央下が コントローラMCP2515、その右が CANトランシーバ/MCP2551、その上が CAN動作に連動しているLED 4個 (電源黄色、送信、受信、SPI動作チェック) です。
この写真では良く見えませんが、Rの受信LEDが点滅しています。

LCDはI2Cインタフェースで接合してます。マイコン 5V と LCD 3.3Vのインタフェース電圧変換のFET回路も付けましたが、このLCD(ストロベリーリナックス、SB1602B) は 5Vそのままでも駆動できるようです。

CAN以外の外部インタフェースは、右下に見えるUSB-COM変換ポートで、計測結果を外部PCに送ります。

しかし、最終的にやりたいことは、左中央に見える BlueTooth による出力です。
この機器を最小限に小さく作り(Arduino基板と一体化)、CAN計測とBlueToothによるデータ転送を受け持ち、転送後のデータ処理を種々行えば、様々なCAN情報収集と応用に発展できないか、というのが私の目論見です。

【回路図】
書籍の図10-3 を一部参考に、作った回路図(Eagle)です
Arduino_CAN_8_sch.jpg
CAN関係でオリジナルと異なる部分は
・MCP2551,MCP2515は表面実装SSOPタイプから、差し込み式ISPに変更
・終点抵抗回路が入っていません
・MCP2515のクロックは水晶発振20MHzから16MHzに変更(このほうが250K,500K CANに合わせやすい)
・SPI回路のプルアップ抵抗無し(動きます)
・MCP2515のRESET回路(19ピン)にはVDD(20ピン)から10kΩで渡す(リセットICを使わず)

その他の変更点は
・LEDは直接駆動で4灯のみ
・サーボやモーターなどの駆動回路は不要なので削除
・I2C駆動のLCD(SB1602B)を設置、これ用の3.3V電源、マイコン5VからLCD3.3Vへの信号レベル変換FETを追加
・BlueTooth(SBDBT5V)と、UARTをArduino書き込み時とBlueTooth使用時で切り替えるスイッチを搭載

【基板図】
Arduino_CAN_9_brd.jpg
結構一杯一杯に詰まっていますが、これはテスト機なので、うまくできた時には表面実装IC類やコンパクト端子を使えば、Arduinoマイコンを含み一枚の小さな基盤に載るだろうと思ってます。

【CAN通信テスト】

通信テストのベースにさせて頂いたソフトは、参考書籍のサポート・ページ(http://mycomputer.cqpub.co.jp)です。
このページを辿ると、CAN_Arduino.zip というフィルがダウンロードできます。

*6月1日までメンテナンスのようです。
*オリジナル・ファイル Can_Arduino_org.zip は こちらから
*私が改定したファイル Can_Arduino_my.zip は こちらから ダウンロードできます

---------------------------------------------------------------------------------------------------------------
内容
(1) Prj0701AR
CANバスモニタ用Arduino版ノード。WIZ-C版から移植。Arduino+#258基板で作動。
(2) Prj0801AR
CANブリッジ用Arduino版ノード。WIZ-C版から移植。Arduino+#258基板で作動。
(3) Prj0901AR
複合アプリケーションのPC I/F用Arduino版ノード。WIZ-C版から移植。Arduino+#258基板で作動。
(4) Prj1001
第9章のCANバスに接続するArduinoノード(ノード#5)。サーボ、PWM制御用。Arduino+#258基板で作動。
----------------------------------------------------------------------------------------------------------------
この(1)を使いました。中にはP258_N1というフォルダがあり、この中にArduinoのソフトが収納されています。

この本でのCAN通信は125kのCAN通信を行っているサンプルなので、500kのJ1939には変更が必要です。
//--------------------------------------------------
  // CAN関係の初期化
  //CANInit(CAN_BRP_16MHz_125KBPS); // CLOCK 16MHz CAN 125bps
    CANInit(CAN_BRP_16MHz_500KBPS);    // CLOCK 16MHz CAN 500bps
//--------------------------------------------------
CAN通信だけは上記の変更だけで通信ができてしまいます。
(実は工作の間違いがあって四苦八苦したことは書きませんが・笑)

【シリアルモニタ】
Arduino IDE付属のシリアルモニタにCAN通信した結果が出るようにプログラミングされています。
Arduino_CAN_10.jpg

文字列の先頭4文字がCAN IDですが、4文字しかないということは標準IDです。
J1939は拡張IDを使っているので変更する必要があります。

・・・ここで色々調べました。
参考図書には拡張IDについて殆ど記述がありません。
しかし、巻末のMCP2515レジスタやバッファ仕様見ていると、拡張IDが受信できており、表示されていないだけではないかという推測に至りました。

IDに関するプログラム部分:オリジナル
//---------------------------------------
    // 標準IDの場合
    msg = ((word)MsgBuf[1] << 3);        // SIDH
    msg |= (MsgBuf[2] >> 5);                  // SIDL
//-----------------------------------------

参考にしたのはこちらです。seeedが提供しているCAN-Bus Sheild 用のサンプルソフトです。
中身を見ていくと ファイルの拡張子がcppなのでc++で書かれているようです。
//-------------------- 抜粋 ---------------
/*********************************************************************************************************
** Function name:           mcp2515_read_id
** Descriptions:            read can id
*********************************************************************************************************/
void MCP_CAN::mcp2515_read_id( const INT8U mcp_addr, INT8U* ext, INT32U* id )
{
    INT8U tbufdata[4];

    *ext = 0;
    *id = 0;

    mcp2515_readRegisterS( mcp_addr, tbufdata, 4 );

    *id = (tbufdata[MCP_SIDH]<<3) + (tbufdata[MCP_SIDL]>>5);

    if ( (tbufdata[MCP_SIDL] & MCP_TXB_EXIDE_M) ==  MCP_TXB_EXIDE_M )
    {
                                                                        /* extended id                  */
        *id = (*id<<2) + (tbufdata[MCP_SIDL] & 0x03);
        *id = (*id<<8) + tbufdata[MCP_EID8];
        *id = (*id<<8) + tbufdata[MCP_EID0];
        *ext = 1;
    }
}
//------------ ここまで -------------

MCP_SIDHとMCP_SIDL の処理までは標準IDの処理です。
次に続くのが拡張IDの処理の追加で、MCP_EID8,MCP_EID0が拡張IDが格納されている部分のようです。

私が使っているプログラムでは_EID8,_EID0に相当するデータは、標準ID格納バッファに続いていると推測し
//-------------------------------------
    if((MsgBuf[2] & 0x08) != 0x08) {         // 0x08というのは、MCP_TXB_EXIDE_M に当たります
     
      // 標準IDの場合
      msg = ((word)MsgBuf[1] << 3);        // SIDH
      msg |= (MsgBuf[2] >> 5);                   // SIDL
      sprintf(StrBuf, "T%04X", msg);         // メッセージ
      PUTSTR(StrBuf);
   
    }else if((MsgBuf[2] & 0x08) == 0x08) {
   
      //拡張IDの場合
      msg2 = ((long)MsgBuf[1] << 3);          // SIDH
      msg2 |= (MsgBuf[2] >> 5);                    // SIDL
      msg2 = (msg2 << 2) + (MsgBuf[2] & 0x03);
      msg2 = (msg2 << 8) +  MsgBuf[3];
      msg2 = (msg2 << 8) +  MsgBuf[4];

      msgL = (word)msg2;
      msgH = (word)(msg2 >> 16);
      sprintf(StrBuf, "T%04X", msgH);    // メッセージ
      PUTSTR(StrBuf);
      sprintf(StrBuf, "%04X-", msgL);    // メッセージ
      PUTSTR(StrBuf);
     
    }
//--------------------------------------
msg2という longの変数を設け、参考にしたソフトと同様の処理を、MsgBuf[3]、MsgBuf[4]に追加しました。
結果を表示するために、msgLとmsgHに分けました。

【プログラム修正後のシリアルモニタ】
Arduino_CAN_11.jpg

これで8文字の拡張IDが取得できました(CM用ツールで受信し確認してあります)

Tに続き8文字が拡張ID、D以降Sまでが経過時間、8がデータ数、続いてデータです。

【今後のお勉強課題】
1.ID識別で、続くデータを処理する機能のテスト(取りあえずシリアル通信を受けたPCで処理)
2.この機能をArduinoマイコンに実装
3.Arduinoマイコン(実体はMCP2515)でCAN IDによるフィルタ機能の実装(実機で使えるように)
4.BlueToothによる通信と(当初はWindowsPCへ)、Androidタブレットまたはスマホでの表示

1~3までは何とかなりそうだが、4.の後半は私のスキル上、とても厳しそうだ。


おっと、忘れていました。
139ドルもの大枚をはたいて購入したJ1939エミュレータなのですが、シリアルモニタをしながらCANデータを確認していて、何と! 6項目のボリューム調整ノブが付いているのに、変わるのはLCD上の数値だけです。CANデータが変わるのは3項目(RPM,VSS,ECT) しかありません。何という中華仕様・品質なのでしょうか。唖然となりました。

このことを記述しながら思いついたことがあります。
先に挙げたseeedのCAN_BUS_Sheildソフトですが、CANのSend / Write(送信)に関してもサンプルが充実しています。これをベースにしてJ1939エミュレータ・ボードを自作できそうな気もしてきました。
実機と同じデータ種別と間隔で変化できるエミュレータができると色々なチェック用途に使えそうです。




据置きヘッドホンアンプ用12VDC電源

これまで何台も据置きのバランス型ヘッドホンアンプ(こちら、http://higa284.blog20.fc2.com/blog-entry-275.html)を作ってきたが、これをラインアンプとして、これまたフル・バランス型パワーアンプ(こちら)で使って頂いている方がおります。

このヘッドホン(ライン)アンプで使っているのが12V電池電源(エネループ10本)なのですが、電池のメンテナンスが大変なので、ノイズの少ない電源がほしいとのこと。一時はパワーアンプ用電源と同構成の安定化電源を、との話も出たが、小出力のアンプにはもったいない。

そこで考えたのが、ぺるけさんヘッドホンアンプでも使っている低ノイズACアダプターにコモンモード・トランスを組み合わせる方法です。

【ACアダプタ】
PowerForLineAmp.jpg

秋月の12V出力ACアダプタを使っているが、入力が100-120V限定品のほうがノイズレベルが低いとあるので愛用してます。

【コモンモード・トランス】
PowerForLineAmp2.jpg

TDKのRSAL-2003AL という3Aタイプのものを使いました。出力部に50V3300uFのコンデンサ2個と、0.1uFセラミックコンデンサをパラっています。

【ノイズの比較】
PowerForLineAmp_10Khz.jpg

約100mAの出力になるように負荷抵抗を付け、ACレンジで計測した結果です。
横軸は0-10kHz、青がフィルター入力部で、赤が出力部です。

入力での最悪レベルが -75dB に対して、出力は -105dB 程度で、元々低レベルだったノイズですが、更に -30dB も改良されてます。(コモンモード・ノイズフィルターのカタログでは、この周波数帯では -25dBのようです)





金田式ベースのDCヘッドホンアンプ:定電流回路で音は変わる?

10月から検討していた、「金田式もどきベースの 低電圧 バランス型 DCヘッドホンアンプ」なのであるが、

1.いろいろとの試行錯誤の結果、やはり、基本回路部分の歪率がBADな低電圧はNOという結論に達しました。

2.バランス型にする必要性はあるのか?・・・・・ 今のアンバランス・パフォーマンスで十分です。

そんな訳で、「金田式もどきDCヘッドホンアンプ」の存続決定です。


こんなことを思いながら、試作したバランス型基板での音楽を聴いていたのですが・・・  
バランスではなく、アンバランス型にしてみたら、何か、ダイナミックさに欠ける・・・ 気がする・・・!
えーー!何で? だったら、結論2.がどこかに吹っ飛びます。


【金田式ベースの低電圧バランス型DC HPA 試作基板】

HPA_kane_DC_LCase_4.jpg

この基板、バランス型にした以外に、実は定電流回路を

・金田式:J-FET1個と抵抗によるもの。調整がピーキー   から
・本器 :J-FETと抵抗、及びカレントミラー回路による定電流回路。調整が穏やか  に変更してます。

まさか、このせいで音のダイナミックさが無くなってしまったのか?
まさか、まさかと思うのですが、心配になったら確認しないと黙っていられない性分です。

HPA_kane_DC_LCase_2.jpg

並んでいるのは、左から、試作のバランス/アンバランス切換型の金田式もどきDCヘッドホンアンプ
(カレントミラー定電流)

真ん中が、金田式もどきDCヘッドホンアンプ・オリジナル
右が、真ん中のものを、定電流をカレントミラーに換装したものです。

作るにはなかなか手間がかかりました。
Li-ion電池の保護や充電回路は付けてないのですが、夜分だけですが2日もかかってます。

でも、音質確認はあっというまでした。 

定電流回路をカレントミラーに変えてもダイナミックな音感は不変でした。
すなわち、しっかりとした基板で作った完成品では問題なくダイナミックな音が保証できます。


でも、何故試作モックアップ基板ではあんな大人しい音質になったのでしょうか。バランス型でばかり音を聴いていたので、最近まで判りませんでしたね。きっと、トランジスタ類を変換基板に載せているので、回路長が長くなったのが効いているのでしょうかね。謎です。


こんなに音質・音感に気を使っているには理由があります。
ダイナミックな音がする金田式もどきは非常に好評で、70台近い台数を作って頂きました。
非常に好評だったのですが、唯一の問題・弱点はアイドル電流調整がピーキーで安定性に欠けることです。これはこのタイプの定電流回路を使っている金田式アンプの弱点と思われます。

よって、作って頂いた皆さんには苦労をおかけしたと思います。
カレントミラータイプの定電流回路に変更することで、アイドル電流の調整が容易で且つ経時変化が少ないアンプになると期待しています。


この問題を払拭した上で、新たなヘッドホンアンプは、「アルミケースに収納する」ことです。(下図はタカチ MXAアルミケース)
HPA_kane_DC_LCase_6.jpg  HPA_kane_DC_LCase_5.jpg

このモバイルケースに入れれば、外部からの電気的ノイズの低減にもなります。デザイン性がGoodです。
これに、デザインした透明シールを貼れば、更にドレスアップができる筈です・

【これに収納するHPA基板図】
HPA_kane_DC_LCase_1.jpg

アルミケースが 82x94mm なので、基板も 72x88mm と大きくなり、Li-ion電池も基板上に置けます。

入力・出力・負帰還に使っているオーディオ抵抗の足曲げカーブも緩やかにできます。
電源コンデンサも2個から4個に増え、この図からは判りませんがコンデンサ長も11mmから14mmまでサイズアップが可能になります。

今、最も関心があることは、「アルミパネルのデザイン印刷」です。
ケースメーカーに依頼すればOKなのですが、それなりの高額加工費が発生します。
どうにか自分でできないものかと腐心してます。




プロフィール

haiga

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

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

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

この人とブロともになる

QRコード
QRコード