金田式もどきDCヘッドホンアンプ・アルミケースバージョンの頒布

金田式もどきDCヘッドホンアンプ・アルミケース バージョンを頒布します。

----- 8月6日 午後追記 -----
・製作マニュアルをアップしました。
・歪率と出力グラフと周波数特性グラフを追記しました。

---- 2016/ 8/23 ----
第2次 10台追加を30日から再開します。ご要望あればコメントに記載ください。


製作記事は、
http://higa284.blog20.fc2.com/blog-entry-311.html と、
http://higa284.blog20.fc2.com/blog-entry-298.html です。


音が好評で数十台の頒布となりました前作プラスチックバージョン(こちら)に対し、変更点は以下の通りです。

・外部からの飛翔ノイズ対策でアルミケース化しました。

・定電流(アイドル電流)調整のピーキー特性と温度による不安定を改良するため、
 定電流回路にDualトランジスタによるカレントミラー回路を追加しました。

・内部スペースが拡大したので、電源コンデンサを2個から4個に増やし、高さ15mmまでのサイズまで装着できます。

【内部及び外観】
HPA_kane_DC_LCase_9.jpg

【回路図】
HPA_kane_DC_LCase_12.jpg

【金田式「もどき」としている本器の説明・特徴】

・金田式ヘッドホンアンプの特徴は
2段目にも差動回路を有し、この各々の出力をNPN-NPN構成の終段増幅に接続する、「対称型」と言われる回路です。即ち初段差動増幅から終段増幅まで、±である音楽信号に対し対称になるような回路構成です。

・本器の特徴は
ここでは細かな説明は省きますが、オリジナル回路の2段目差動回路には負荷抵抗があり電圧ロスがありました。本アンプではこの抵抗を取り去り、インバーテッド・ダーリントン接続の変形とも言える回路構成になり、シンプルながらも高性能になってます。
この回路の説明はこちらのブログに掲載してあります。

本来、金田式という呼称を付けるためには、回路構成は元より使う素子も金田さんと同じでなければならないということらしいのですが、殆どのトランジスタ素子類はとっくの昔にディスコンで、初段差動の2SK246BLだけは私の倉庫から出して使ってます。
なので「もどき」です(もう既に「もどき」とも言えないかもしれませんね)

金田式ヘッドホンアンプの電源はネネループ電池を4個+4個使い、公称±4.8V(実質±5.6V)ですが、本アンプでは単三型リチウムイオン電池を使って、同±3.7V(±4.0V)です。よって電圧が低い分だけ、先のインバーテッド・ダーリントン変形回路によって性能が同等になっています。

リチウムイオン電池を使うことで、充電回路を内蔵しスマホ充電器を利用した自己充電ができるようになってます。

【歪率と出力】
HPA_kane_DC_LCase_20.jpg

【周波数特性】
HPA_kane_DC_LCase_21.jpg

【パーツリスト】
部品番号部品名型番、値個数費用(円)
・表面実装部品付き基板 
 専用基板 1 5,000  
・トランジスタ 
Q101,103定電流、終段増幅
NPNデュアルTr
BC846DS4基板実装
Q201,203
Q1022段目差動
PNPデュアルTr
BCM856DS2
Q202
FET101,102
FET201,202
初段差動FET2SK246BL(金田式指定品)
Idss 0.1mA差選別品
4400 
FET103,203定電流 J-FET2SK208-O2基板実装
・初段、中段チップ部品  
R108,R109
R208,R209
チップ抵抗 2012差動負荷 1.5K4基板実装
R110,R210チップ抵抗 2012300Ω2
C113,C213チップコン 20121000P2
R111,R211チップ抵抗 2012120Ω2
R106,R107
R206,R207
チップ抵抗 2012定電流 110Ω4
C109,C110
C209,C210
チップコン 2012位相補償100P4
・電源コンデンサ 
C001,C002
C003,C004
表面実装コンデンサ1000uF4 520  
C004,C005チップコン 20120.1u2基板実装
・LEDチップ抵抗    
R001チップ抵抗 201227k1基板実装
・充電用パーツ   
IC1,IC2Li-ion電池Protecter 2基板実装
IC3,IC4充電用ICXC68022
C007,C008チップコン1uF or 10uF2
R002,R004チップ抵抗 201215k2
R003,R005チップ抵抗 20122k2
LED2,LED3チップLED青、16082
・入出力、負帰還 金属皮膜抵抗  
R112,R113
R212,R213
終段負荷
金属皮膜抵抗1/4W
10Ω
利久RO抵抗は終了
4 
400 
 
R102
R202
負帰還
金属皮膜抵抗1/4W
22k(利久RO抵抗)2
R103
R203
負帰還
金属皮膜抵抗1/4W
33k(利久RO抵抗)2
・機構部品    
ST_JACK1入出力
φ3.5ステレオジャック
AJ-17802180  
ST_JACK2
SW2系統スイッチ ON-OFF-ON 動作
 フジソク、CFT2-2PC4-AW
1 350  
VOL2連ボリュームA50k(またはA20k),LINKMAN、RD925G1 300  
R102,R202定電流半固定VOL500Ω2 100  
R101,R201バランス半固定VOL100Ω2 100  
 ボリュームノブ銀φ61 380  
LED1LED青色φ31 20  
 収納ケース
(デザイン・ロゴシール付き)
タカチ,MXA2-8-9SS1 1,500  
 電池端子タカチ,IT-3SP,-3SM各2 100  
U_USBmicroUSBジャックZX62R1基板実装
・キット合計(オプション含まず)9,350  

【オプション部品 及び 作業】
内容パーツ数量費用(円)
電源コンデンサ差額導電性個体コンデンサ(OSコン)6.3V/1500uF4800  
リチウムイオン電池14500 (900mAh) 保護回路無し (保護回路は基板に実装)21,500  
ケース蓋加工アルミ蓋(表・裏)の加工1式1,000  

【完成後外観】
HPA_kane_DC_LCase_14.jpg
(初めてのアルミ加工でout端子回りに加工ミスがあります)

【頒布手順】

・とりあえず10セット限定で頒布します(第1次 10台が完了し、第2次を準備中です)


・頒布は上記の部品表に準じます。

 費用でくくっている単位で頒布いたしますが、
 この部品は手持ちがあるので不用、というご要望も可能です。

・自作が不得手だが聴いてみたいという方には完成品を頒布します。
 オプションコンデンサ付きですが、リチウムイオン電池がオプションです。
 頒布費用は、17,000円です(送料含む)。

・ご希望の方は、このブログのコメント欄に匿名で記載願います(匿名でなくともOKですが)
 メルアドの記載漏れ、ミスコピーにお気をつけください。
 当方からメールで連絡いたします。

・送付は私の都合で、ヤマト宅配便コンパクトになります。
 セットでの送料は下記URLの金額-35円(持ち込み-100円、箱+65円)になります。
 http://www.kuronekoyamato.co.jp/compact/


*ご注意:頒布部品が欠品する場合もあります。既にオーダー頂いている方はお待ちいただくことになります。オーダー前の方は頒布が中止になる場合もあります。よろしくお願いいたします。

【その他】

・製作マニュアルはこちらです。



金田式もどきDCヘッドホンアンプ・アルミケースバージョンの製作

---- 2016/ 8/ 10 初段2SK246BL の選別問題を追記 -----

この3月から準備してきた、金田式もどきDCヘッドホンアンプのアルミケースバージョンを製作しました。

【基板に表面実装部品を装着します】
HPA_kane_DC_LCase_8.jpg

これまでのプラスチックケースに代わり、アルミケースに収納するため基板サイズがかなり大きくなりました。部品配置もかなり楽々で、電源コンデンサも±に2個ずつ置きました。
リチウムイオン電池も基板上に設けた電池端子に取り付けます。

【完成状態の全容です】
HPA_kane_DC_LCase_9.jpg

HPA_kane_DC_LCase_14.jpg

ケース色はアルミ地のシルバーです。
全面と背面プレートもアルミタイプのケースを用いたので、透明のレタリング・シートで装飾できました。
(初めての加工だったのでout端子穴に加工ミスがあります)

全面の端子類配列はプラスチック・バージョンと同じで、左から3方向電源スイッチで、charge-OFF-ON になります。隣が青色LED、入力ジャック、ボリューム、出力ジャックとなります。

HPA_kane_DC_LCase_11.jpg

microUSB端子を使った充電端子は後面です。チャージの際は両側のLEDが明るく点灯します(但し、チャージが終わっても照度が落ちるだけで消灯しません。LEDの感度が上がったので充電終了時の微量な電流μAでも点灯してしまいます  -_-;)

【回路図】
HPA_kane_DC_LCase_12.jpg

前作のプラスチック・バージョンに対して大きく変わったのは初段差動増幅の定電流回路です。金田式のオリジナル回路ではFETと可変抵抗によるものでしたが、その調整のピーキーさと温度変化、特に温度上昇時に異常な発熱に至る問題がありました。
本作ではFETと調整用可変抵抗回路にDualトランジスタを使ったカレントミラー回路を組み合わせ、調整の容易化と温度安定度を考慮しました。

【基板図】
HPA_kane_DC_LCase_13.jpg

リチウムイオン電池の充電回路や保護ICは電池を置く上方にまとめました。保護ICは裏面に付けています。
電源コンデンサはSTDには表面実装の物を使いますが、通常の差し込み足タイプの物も使えます。取り付けの高さがぎりぎり15mmありますので、お好きなコンデンサの取り付けを可能にしています。

【アルミケースの加工について】

今回使ったアルミケースMXA2-8-9SSですが、タカチには前後パネルがプラスチックのMXシリーズもあるのですが、今回選定した80mmx90mmというちょうど良いサイズがMXシリーズにはありませんでした。よって加工をアルミ材に行うことになります。しかも3mmの板厚なので、手動ツールや手持ちドリルではしっかりとした加工ができない恐れがありました。

そこで新たにドリルスタンドを購入しました。

HPA_kane_DC_LCase_16.jpg

REXSON 小型ボール盤 200W の DP2250R というものです。超お買い得の12,900円です。
写真のようにバイス台までセットされており、速度調整も電気的な調整で可能なものですが、3,200rpmが最小回転なので高すぎでした。
そこで速度調整用に電圧調整ができる可変トランスを入れました。

HPA_kane_DC_LCase_18.jpg

機器の速度調整をするために昔使われていた、スライド式の電圧調整器です。今では周波数変換のインバーターに切り替わり、捨てられる直前の物をストックしていました。300VA容量なので200Wのボール盤にはちょうど良いサイズです。

バイス台回り
HPA_kane_DC_LCase_19.jpg

傷つきやすいアルミ板をバイスで固定するため、ラバーを介して挟み込み、下側に木を入れます。

寸法出し
HPA_kane_DC_LCase_17.jpg
CADを使ってアルミケース蓋の裏面側に加工位置を記した等倍図面を作ります。
このシートを透明フィルムに印刷し、中心位置をアルミケース蓋にポンチします。

でも、200Wのボール盤ではφ3.2が加工できる上限でした。それ以上はトルクが足りず止まってしまいます。
それでも、φ1で下穴を開け、φ2、φ3もしくはφ3.2 にサイズアップしていけば綺麗に加工ができます。

φ5.5、φ7 は仕方がないのでハンドリーマでこつことと広げます(笑い泣き)
out 端子穴が加工ミスしたのは、ボール盤加工をトライして失敗したものです。

ロゴ・シール
HPA_kane_DC_LCase_15.jpg

蓋の表面にはこのようなロゴシールをプロ印刷に依頼して作りました。
CADで穴加工位置やボリューム弧を描き、pdf印刷をしておきます。それをイラストレーターCS2で読み込みます。
ロゴ部分の作成や、ボリューム部分の塗りつぶしをイラストレーターで行い、全体をアウトライン化します。

約A4サイズに8シール並べられ、それを5枚作って約4,000円でした。1台当たりのコストは100円なので安いですよね。


【歪と出力、周波数特性】
・・・・歪率を計測する機器をしまい込んでしまったので、これから引っ張り出し、整備して特性計測をやってみようを思ってます。

ごそごそと引っ張り出した機材に、いつも使っているUSB DACを歪率計測に使う専用ノートPCに繋ぎ、WAVE Spectra で計測します。
その結果をExcelで作図させてます。

HPA_kane_DC_LCase_20.jpg

低歪のフラット部分が少なく、徐々に持ち上がっていく特性です。

このパターンによって、そのアンプの音色が変わっていくようなのです。
しかし、これがどのような音色であるかは波形から類推できないところにアンプ作りの面白さがあります。

HPA_kane_DC_LCase_21.jpg

周波数特性はいつもながら立派と言う他ないですね。

いつも、金田式に対して思うのですが、極低域のフラットさは秀逸と言う他ありませんね。


【出音】
・・・・電源を入れて10時間程度はやけに高音がきつい音感でしたが、やっと安定した音色の領域に入って来たようです。そろそろ金田式の妖艶な音色に近づいて来ました。

・・・・いつもの試聴ではHPAにかかる負荷は微小です。特性を計測する時には、そのアンプに最大パワーを出す入力をするので、使っている素子にとても大きいパワーがかかります。

というこで、特性計測後の音を試聴します。

再生している曲は、1978年、サーカスのミスター・サマータイムです(YouTube)。
昨日も同じ曲を聴いたが、痛い高音は消え、浮き上るような高音系のシャウト感があります。

---- 2016/ 8/ 10 初段2SK246BL の選別問題を追記 -----

2SK246BLの選別に問題があったので改良しました

2SK246BLの選別は7年前に自作した選別器で行っていたのですが、ぺるけ式の2SK170BLは問題なかったが、約0.9V以上のVgs計測ができない仕様だったので Idssで選別していました。
ところがこのIdss計測は発熱と共に変化する特性があるので計測誤差問題を抱えていたのです。
結果的にペア選別したものがバラツキがあり、調整トリマー一杯になる懸念が生じました。

そこで急きょ高い電圧のVgs計測ができるマニュアル計測型のツールを作りました。
HPA_kane_DC_LCase_22.jpg

急いで半日で作ったのでバラックです。デジタルテスターには、Vgs -2.409Vが表示されています。

これのベースはぺるけさんの改良型回路をそのまま頂きました。Vgsを計測するためのId が温度ドリフトしない仕様です。
ツェナーダイオード定数がオリジナルの5.6Vに対して5.1Vしか持っていなかったので、Id 調整抵抗を調整しました。
オリジナルはこの抵抗をロータリーSW切り替えで、Id を 0.75mA,1mA,2mAとするものですが、私は通常の2回路スイッチを使いたかったので、2mA固定とボリュームによる0.05mA~5mA可変が使えるようにしました。

非常に安定したVgs計測ができています。テスターの数値は1mV部分が若干動く程度です。

【2SK246BLのVgsペアとDC調整範囲】
この機器で計測したVgsでペアを数種類組み合わせてDC調整可能な範囲を調査しました
HPA_kane_DC_LCase_23.jpg
Vgs差が40mV以内であればDC調整ができます。
この結果はぺるけさんが頒布している2SK30A FETでの基準値と略同等かと思います。
Vgs差と無調整時のDC値がほぼ直線なのも、計測したVgsが正確である裏付けと思います。


これで安心して頒布ができます。やれやれです ^_^;





2Win 差動式 低電圧バランス型 DC HPA の電源改造

久しぶりにオーディオ記事に戻ります。

私の自作オーディオを気に入って頂けて方から、2Win 差動式 低電圧バランス型 DC HPAを、「長時間使える外部電池電源で駆動できるようにできないか」との打診がありました。

種々考えた末、市販されているUSB接続の 5V 電源を使ったらどうだろうかの結論に達しました。
・市販で簡単に入手できる
・価格が安い
・リチウムイオン電池の物も多く、充電器も添付されている、もしくは市販されているので安心して使える。

【内部】
HPA_v7_BAL_44.jpg

電池スペースに、microUSB端子から給電できる端子と、この5V電圧を±2.5Vに分圧する回路を設けました。
オペアンプで偏差電圧を補正する回路(サーボ回路)を有します。

【参照した電源回路図】
HPA_v7_BAL_46.jpg

miniAmpで使った分圧回路です。この回路では電源が15Vですが今回は5Vです。よってopAmpは5V動作するものを使う必要があります。今回は4580を使ってます。電解コンデンサは本体側に付いているので電源からは削除してます。トランジスタは消費電流が少ないので2SC1815/2SA1015で十分です。

【電源回路拡大】
HPA_v7_BAL_45.jpg

左側はHPA v6-2 までで使っていた microUSB端子を付ける基板をカットしてます。
右側にユニバーサル基板を使ったサーボ機能を有する分圧回路です。各々をエポキシ接着剤で接合してます。

------- 音の感触
電圧が±1.5Vから±2.5Vに変わったことで、かなりのパワーアップを感じます。
特に、ボリュームを上げた際に、しっかりとして追従してくれるような安心感があります。
気のせいか、高音系もしっかり感が増えたように思えます。

やはり、電圧を上げることがアンプにとっては最大の朗報なのですね。






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






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の勉強には敷居が高そうです。

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





プロフィール

haiga

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

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

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

この人とブロともになる

QRコード
QRコード