Arduinoを使ってCAN通信を行う場合、MCP2515を使った技術ブログが沢山ヒットします。
今回はMCP_CAN_libというライブラリを使って開発することにしましたが、データ送信時(senMsgBuf関数)にエラーが起きた場合の解決方法が見当たらなかったので七転八倒した記録を残します。
(注: このブログを最後まで見ても解決しません。)
結論
今回購入したMCP2515モジュールはNiRenという刻印のある基盤[wiki]で、これは2017年頃にMCP_CAN_libの開発者coryjfowlerさんがそのボードはトラブルシューティングが難しくお勧め出来ないと結論を書いていました。
Can not receive CAN DATA from Arduino UNO with MCP2515 Module.
私の症状としては「MCP2515 library giving me errors CAN_SENDMSGTIMEOUT and CAN_GETTXBFTIMEOUT」に報告されているのに近いかと思うのですが残念ながら未解決で閉じられています。
安物を買って損をしたという感じです( T_T)
環境
Arduino R3 ×1個
MCP2515モジュール(TJA1050付き) ×1個
Windows 11
Arduino IDE 2.3.2
MCP_CAN_lib (tag 1.5.1)
今回はMCP_CAN_libのサンプルプログラムCAN_send.inoを動かしてみます。
配線
これが今回のエラーの元凶です。
- MCP2515 + TJA1050
- 入力電圧 5V
- 水晶振動子 8MHz
- データシート
- ここのサイトの基盤回路図が参考になりました。[Arduino-UNOでCANを使う]
このモジュールをざっくりと下記のように配線していきたいと思います。
CANラインは後回しにして先ずはArduinoとだけ繋ぎます。
注: この配線はMCP_CAN_libを使う場合なので、他のライブラリを使う場合は異なる配線になります
Arduino | MCP2515 | 備考 |
---|---|---|
– | INT | プログラムで設定可能 割込み信号用 [今回未使用] |
13 | SCK | 固定 Serial Clock |
11 | SI | 固定 Serial Input |
12 | SO | 固定 Serial Output |
10 | CS | プログラムで設定可能 Chip Select |
GND | GND | – |
5V | VCC | – |
ライブラリのインストール
Arduino IDEのライブラリマネージャーの検索枠に”MCP_CAN”と打って1.5.1をインストールします。
インストールに成功すると右上の三点アイコンからサンプルが選べるようになるので、CAN_sendを選択します。
選択するとgitと同じソースコードが現れます。[git link]
サンプルコードでは14行目のCAN0.begin関数で16MHzの設定になっているので、これを”CAN0.begin(MCP_ANY, CAN_200KBPS, MCP_8MHZ)”に書き換えます。
//https://github.com/coryjfowler/MCP_CAN_lib/blob/1.5.1/examples/CAN_send/CAN_send.ino
// CAN Send Example
//
#include <mcp_can.h>
#include <SPI.h>
MCP_CAN CAN0(10); // Set CS to arduino pin 10
void setup()
{
Serial.begin(115200);
// Initialize MCP2515 running at 16MHz with a baudrate of 500kb/s and the masks and filters disabled.
// Change CAN setting
if(CAN0.begin(MCP_ANY, CAN_200KBPS, MCP_8MHZ) == CAN_OK) Serial.println("MCP2515 Initialized Successfully!");
else Serial.println("Error Initializing MCP2515...");
CAN0.setMode(MCP_NORMAL); // Change to normal mode to allow messages to be transmitted
}
byte data[8] = {0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07};
void loop()
{
// send data: ID = 0x100, Standard CAN Frame, Data length = 8 bytes, 'data' = array of data bytes to send
byte sndStat = CAN0.sendMsgBuf(0x100, 0, 8, data);
if(sndStat == CAN_OK){
Serial.println("Message Sent Successfully!");
} else {
Serial.println("Error Sending Message...");
}
delay(100); // send data per 100ms
}
/*********************************************************************************************************
END FILE
*********************************************************************************************************/
コードを書き替えたらコンパイル確認をして、Arduinoへ繋いでプログラムを書き込みます。
[書き換え前のプログラムが動くので最初はMCPモジュールの接続を外すことをお勧めします]
本題:送信出来ない
ここからが本題です。
Tools->Serial Monitorで動作確認をすると、”Error Sending Message…”と表示されます。
取り合えずセオリーとしてコードで原因究明する前に物理的な事(配線、電源etc)を中心に調べます。
理由は色々あると思いますが、コードでエラーが無くなっても物理的に間違っている箇所があると結果として動かない為です。
というわけでMCP2515モジュールをもう一つ使ってCANラインだけは完成させました。
どちらのモジュールもJ1を使って終端抵抗を有効にして、J2でCANライン接続をおこなっています。(CANラインは気持ちだけよじってあります)
コードでエラー確認をしていく
サンプルコードにエラー確認項目を追加します。
エラー確認に使えそうなMCP_CANクラスの関数は下記。
- setMode [サンプルコード内]
- 0 CAN_OK
- 1 CAN_FAILINIT
- sendMsgBuf [サンプルコード内]
- 0 CAN_OK
- 6 CAN_GETTXBFTIMEOUT
- 7 CAN_SENDMSGTIMEOUT
- checkError [新規追加]
- 0 CAN_OK
- 5 CAN_CTRLERROR
- getError [新規追加]
- MCP_EFLG(0x2D)アドレスの値の読み出し
- errorCountTX [新規追加]
- MCP_TEC(0x1C)アドレスの値の読み出し
上記をとりあえずサンプルコードに組み込みます。
// CAN Send Example
//
#include <mcp_can.h>
#include <SPI.h>
MCP_CAN CAN0(10); // Set CS to pin 10
void setup()
{
Serial.begin(115200);
// Initialize MCP2515 running at 16MHz with a baudrate of 500kb/s and the masks and filters disabled.
if(CAN0.begin(MCP_ANY, CAN_200KBPS, MCP_8MHZ) == CAN_OK) Serial.println("MCP2515 Initialized Successfully!");
else Serial.println("Error Initializing MCP2515...");
byte modeStat = CAN0.setMode(MCP_NORMAL); // Change to normal mode to allow messages to be transmitted
Serial.println("Check setMode: " + String(modeStat));
}
byte data[8] = {0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07};
void loop()
{
// send data: ID = 0x100, Standard CAN Frame, Data length = 8 bytes, 'data' = array of data bytes to send
byte sndStat = CAN0.sendMsgBuf(0x100, 0, 8, data);
if(sndStat == CAN_OK){
Serial.println("Message Sent Successfully!");
} else {
Serial.println("Error Sending Message...");
Serial.println("sendMsgBuf Status is: " + String(sndStat));
Serial.println("checkError Status is: " + String(CAN0.checkError()));
Serial.println("getError is: " + String(CAN0.getError()));
Serial.println("errorCountTX is: " + String(CAN0.errorCountTX()));
}
delay(500); // send data per 100ms
}
/*********************************************************************************************************
END FILE
*********************************************************************************************************/
エラーメッセージは下記のようになりました。
初期化に成功はしているものの、sendMsgBuf関数で送信できずバッファ取得が時間切れになっていそうです。
getErrorとerrorCountTXをみると送信エラーに応じて増減している感じです。
とりあえず送信バッファが足りなさそうなので、sendMsgBufがエラーステータスを吐いたらabortTX()させるようにしておきます。
if(sndStat == CAN_OK){
Serial.println("Message Sent Successfully!");
Serial.println("sendMsgBuf Status is: " + String(sndStat));
Serial.println("checkError Status is: " + String(CAN0.checkError()));
Serial.println("getError is: " + String(CAN0.getError()));
Serial.println("errorCountTX is: " + String(CAN0.errorCountTX()));
} else {
Serial.println("Error Sending Message...");
Serial.println("sendMsgBuf Status is: " + String(sndStat));
Serial.println("checkError Status is: " + String(CAN0.checkError()));
Serial.println("getError is: " + String(CAN0.getError()));
Serial.println("errorCountTX is: " + String(CAN0.errorCountTX()));
CAN0.abortTX();
}
これで一応sendMsgBufが消えましたが他のエラーが消えません。
ちなみにgetError関数はライブラリ開発者のcoryjfowlerさんが下記のようにマスクを利用してwarningを除いたレジスタ値を計測していました。
Serial.print("Error register value: ");
byte tempErr = CAN0.getError() & MCP_EFLG_ERRORMASK; // We are only interested in errors, not warnings.
Serial.println(tempErr, BIN);