ESP32でCAN通信(MCP2561 + レベルシフタ)

ESP32でCAN通信(MCP2561 + レベルシフタ)

Tags
Software Development
Arduino
LowLayer
通信
Published
July 29, 2022
⚠️
CAN通信を完璧に理解したわけではありませんが、ESP32とMCP2561を使ってCAN通信に成功しましたので、備忘録として残しておきます。
ネットに実例が無さすぎたので、私の様に苦しむ人を減らすべく記事を書きます。
間違っている部分もあるかと思います。
あればTwitterで教えてください(教えてください)(差し支えなければnoticeではなくteachでお願いします)

CAN通信とは

📌
CANはController Area Networkの略称です。
WAN(Wide Area Network)やLAN(Local Area Network)などは高校の情報の授業で習うので有名ですよね。
今回はCampuss Area Networkの話はしていません。
  • CAN通信は車などでよく使われています
    • 最近はEthernetを使う傾向になってきているという噂を聞きました。
  • 長距離通信が可能です
    • CAN通信は差動通信なので、他のI2CSPIに比べてノイズに耐性がある。
    • notion image
      他にも波形が綺麗になるなど、さまざまなメリットがありますが、今回のメインはそこではないです。
      差動通信について詳しく知りたい方はEDNJapanの記事を見るなどをしてください。
    • UARTSPII2Cなどがマイコンのペリフェラルにはついていますが、これらは長いワイヤを繋いで通信させるのに向いていません。
      • I2Cに関してはI2Cバスリピータがあれば長距離もできると聞きました。この記事が入り口には良さそうです。
      • 他に長距離通信ができるものにRS485もあります(がCANほどではない)
    • 鳥人間コンテストの飛行機のモジュール間通信にもCAN通信を使用しているチームがいるそうです。
      • 友達/後輩が鳥人間をやっていて聞きました。阪大だっけ。
  • 同じバス上で複数のマイコン同士で通信できます
    • (ネットワークが構成できる)
    • I2Cの様にMaster Slaveがない(?)
    • 外部に出す配線を簡素化できる
    • 樹形のバスワイヤリング
      樹形のバスワイヤリング
      デイジーチェーン風のバスワイヤリング
      デイジーチェーン風のバスワイヤリング
  • CANバスドライバが、ある程度の通信エラーに関するエラーハンドリングをしてくれます(たしか…)
    • バス上のうち、1つのマイコンが受信に失敗したら、全部ちゃんと成功するまで送り直してくれる?らしい。
私もそこまで詳しくないですが、自分の把握している範囲で書いてみました。

CANをする場合のArduinとESP32の回路の比較

ArduinoでCAN通信をする場合はCANドライバCANトランシーバが必要になるそうです。
しかし、ESP32には内部にCANドライバが内蔵されているそうです。
つまりESP32を使う場合、別途にCANドライバが必要ないのです。
ArduinoでCAN通信をする場合の構成図
ArduinoでCAN通信をする場合の構成図
ESP32でCAN通信をする場合の構成図
ESP32でCAN通信をする場合の構成図
ちなみにSTM32もCANドライバは内蔵されています。
ESP32にはSJA1000というドライバが内蔵されているそうです。
しかし、ESP32のChip revisionによって中のドライバの仕様が変わってくるので注意です。(私が使ったのはESP32 DOITなので、ESP-WROOM-32です。C3とかではないです。)
 
また、変なことにどうやらESP32には「CANのペリフェラルポート」という概念がないようです。
なんか、自分で任意のピンにCANのTXとRXを生やせるらしいです!!
便利すぎてびっくりしました!
STM32F303K8はCANのペリフェラルという概念があります
CAN1_TDCAN1_RDがCAN通信をする場合にここからしかピン出せませんよなペリフェラルピン
notion image
汎用マイコンにもこんな感じの「UARTのTXとRXを自由にスイッチできる機能」を搭載して欲しいですね(ついているやつにはあるらしい)

CANトランシーバをいくつか調べてみた

5時間くらいネットで調べたところ以下のIC達に辿り着けた。
  • TJA1441AT (2022/10/14追記こいつは3.3Vでも使えるそうですが最大5Mbpsです)
秋月電子で手に入るものにMCP2561MCP2562があります。

MCP2562

  • 電源電圧は5V
  • ロジック電圧は3.3Vも対応できる
  • 秋月で在庫切れ(2022/7/29)

MCP2561

  • 電源電圧は5V
  • ロジック電圧は5V。3.3Vでは動かない
  • こっちの方が安価
  • まだ秋月で手に入る(2022/7/29)
notion image
違いは5番ピンにあります。SPLITVIO
  • MCP2561では、SPLITはコモンモード安定化の役割があるらしい
  • MCP2562では、VIOに3.3Vを印加すれば3.3Vに対応できます
    • (VIOはIC内部にあるロジックレベル変換器の参照電圧入力)

ESP32 + MCP2561 + FXMA108でCAN通信

今回は秋月でMCP2562(3.3V行ける方)が手に入らなかったのでMCP2561でチャレンジしました。
こんな感じです。めちゃもじゃってます。レベルシフタの周辺がかなりもじゃってます。
notion image
ブレッドボードの実態配線図を描くと絶対汚くなるので、少し改変してKeynoteで配線描きました。

使用したパーツ

ESP32 DOIT

レベルシフタ(FXMA108)

MCP2561

120Ω

回路

notion image

Senderプログラム

// Copyright (c) Sandeep Mistry. All rights reserved. // Licensed under the MIT license. See LICENSE file in the project root for full license information. #include <CAN.h> #include <Ticker.h> const int LED_PIN = 2; Ticker tick; void setup() { pinMode(LED_PIN, OUTPUT); tick.attach_ms(1000, []() { digitalWrite(LED_PIN, !digitalRead(LED_PIN)); }); Serial.begin(2000000); while (!Serial) ; Serial.println("CAN Sender"); // start the CAN bus at 500 kbps CAN.setPins(26, 25); if (!CAN.begin(500E3)) { Serial.println("Starting CAN failed!"); while (1) ; } } void loop() { // send packet: id is 11 bits, packet can contain up to 8 bytes of data Serial.print("Sending packet ... "); CAN.beginPacket(0x12); CAN.write('h'); CAN.write('e'); CAN.write('l'); CAN.write('l'); CAN.write('o'); CAN.endPacket(); Serial.println("done"); delay(1000); // send extended packet: id is 29 bits, packet can contain up to 8 bytes of data Serial.print("Sending extended packet ... "); CAN.beginExtendedPacket(0xabcdef); CAN.write('w'); CAN.write('o'); CAN.write('r'); CAN.write('l'); CAN.write('d'); CAN.endPacket(); Serial.println("done"); delay(1000); }

Reciverプログラム

// Copyright (c) Sandeep Mistry. All rights reserved. // Licensed under the MIT license. See LICENSE file in the project root for full license information. #include "Arduino.h" #include <CAN.h> #include <Ticker.h> const int LED_PIN = 2; Ticker tick; void setup() { pinMode(LED_PIN, OUTPUT); tick.attach_ms(1000, []() { digitalWrite(LED_PIN, !digitalRead(LED_PIN)); }); Serial.begin(2000000); while (!Serial) ; Serial.println("CAN Receiver"); // start the CAN bus at 500 kbps CAN.setPins(26, 25); if (!CAN.begin(500E3)) { Serial.println("Starting CAN failed!"); while (1) ; } } void loop() { // try to parse packet int packetSize = CAN.parsePacket(); if (packetSize) { // received a packet Serial.print("Received "); if (CAN.packetExtended()) { Serial.print("extended "); } if (CAN.packetRtr()) { // Remote transmission request, packet contains no data Serial.print("RTR "); } Serial.print("packet with id 0x"); Serial.print(CAN.packetId(), HEX); if (CAN.packetRtr()) { Serial.print(" and requested length "); Serial.println(CAN.packetDlc()); } else { Serial.print(" and length "); Serial.println(packetSize); // only print packet data for non-RTR packets while (CAN.available()) { Serial.print((char)CAN.read()); } Serial.println(); } Serial.println(); } }

追加説明

  • Serialのボーレートを2000000bpsにしているので気をつけてください
  • Tickerで生存確認のためのLチカをしています(タイマー割り込み)
  • CAN.setPins(26, 25)でCANのRXTXの設定先を変更しています
  • 成功したらReciver側が以下のツイートにある動画の様にプリントするはずです
  • どうやら受信割り込みもできるっぽいです
    • (以下はreciver側のプログラム)
      #include <CAN.h> #include <Ticker.h> const int LED_PIN = 2; Ticker tick; void onReceive(int packetSize); void setup() { pinMode(LED_PIN, OUTPUT); tick.attach_ms(1000, []() { digitalWrite(LED_PIN, !digitalRead(LED_PIN)); }); Serial.begin(2000000); while (!Serial) ; Serial.println("CAN Receiver Callback"); // start the CAN bus at 500 kbps CAN.setPins(26, 25); if (!CAN.begin(500E3)) { Serial.println("Starting CAN failed!"); while (1) ; } // register the receive callback CAN.onReceive(onReceive); } void loop() { // do nothing } void onReceive(int packetSize) { // received a packet Serial.print("Received "); if (CAN.packetExtended()) { Serial.print("extended "); } if (CAN.packetRtr()) { // Remote transmission request, packet contains no data Serial.print("RTR "); } Serial.print("packet with id 0x"); Serial.print(CAN.packetId(), HEX); if (CAN.packetRtr()) { Serial.print(" and requested length "); Serial.println(CAN.packetDlc()); } else { Serial.print(" and length "); Serial.println(packetSize); // only print packet data for non-RTR packets while (CAN.available()) { Serial.print((char)CAN.read()); } Serial.println(); } Serial.println(); }

見た記事

ESP32でCAN通信 (ESP-IDF & TJA1050)
ESP32はCANコントローラを内蔵しています。その為Arduinoのように別体のCANコントローラ(例えばMCP2515)を用意する必要がありません。 しかし、日本語の情報が少ない為具体的な方法がイメージできないという方も多いのではないでしょうか。そのような方の一助になればと思い、纏めてみました。 ESP32を使ってCAN通信を行うまでの手順を説明します。 ESP-IDFを使用します。Espressifの サンプルプログラム を動かすまでの手順となります。 ESP-IDFがインストールされていて、サンプルプログラムの所在である、 ・・・・examples\peripherals\can\can_network というディレクトリも存在する事が前提です。 以下説明の中に出てくるコマンドは、ESP-IDFのシェル(mingw32)内での実行を前提としています。 開発環境セットアップ後、少なくともLED BLINKなどを実施した経験はあるという前提で、途中説明を省略しているところがあるのでご了承ください。  2と3が別のノードで、互いにCANフレームを受信します。master, slaveという名前が付いていますが、 masterはID 0x0A2を定期送信し、それにslaveが 0x0B2で応答する、といったハンドシェークの後で、slaveが0x0B1によってデータ本体をmasterに対して送ってくる、という流れのため、そのような名前にしているようです。 1.のListen Onlyはプロトコルモニターで、2-3間の通信をモニターします。 まず、master + Listen Onlyの組み合わせで通信をさせてみます。つまりmasterの開始するハンドシェークの冒頭部分をモニターするということやってみます。 2台のうちの一台(DevkitC V4側)を使用しました。 ソースコード ・・・・examples\peripherals\can\can_network を cp -r コマンドで適当な作業ディレクトリにコピーします。 COMポートの設定 作業ディレクトリに移り、 cd can_network_listen_only make menuconfig (接続されているCOMポートの設定します。詳細は省略します。) make make flash もう一台(DevkitV1側)を使用しました。 作業ディレクトリ ・・・・examples\peripherals\can\can_network のコピーは、前述"Listen Only ノードの作成"で作成済みとなっています。 COMポートの設定 実行 2台のボードとも同じ方法でCANトランシーバーとジャンパ線で接続します。 GPIO21 を トランシーバ基盤のTXに GPIO22 を トランシーバ基盤のRXに GND をトランシーバ基盤のGNDに 5Vをトランシーバ基盤のVCCに
ESP32でCAN通信 (ESP-IDF & TJA1050)
PIC18F26K80使い方:ECANの通信実験2
〔マイコンのトップに戻る〕 ・ CAN通信の基礎概要 ・PIC18F26K80のECANレジスタ内容 ・MCC操作でECAN 2.0Bプログラムを生成する ・PICとPICでECAN通信の実験 ・MCP2515モジュールでCAN通信の実験2(PIC) このページではMCP2515モジュール+ArduinoUNO の組み合わせをノード3として、 前ページの"PICとPICでECAN通信の実験"回路に配線し動作させて見ます。 これがMCP2515モジュールで、 MCP2515CANコント ローラとTJA1050トランシーバを 搭載したCANインターフェイスモジュールで、 すごくお値段が安いです。 "MCP2515モジュール"とネットで検索すれば いっぱい出てきます、これをArduinoとSPI通信で接続する だけで簡単に扱えます。 又、MCP2515のクロックは8MHzとなっていて、 操作電源は5.0Vです。 このモジュールの回路図です、尚、MCP2515日本語データシートはこちらです。 左図がArduinoUNOとのノード3配線図です。 電源は5.0Vです。 前ページの"PICとPICでECAN通信の実験"回路に 組み込みます。 SPIのCSピンはD10ピンと接続します。 INTピンはD2ピンと接続して割り込み入力とします。 J1ジャンパーピン このピンを短絡させるとCANバスの終端抵抗120Ωが 施されます。 今回はPIC側で終端抵抗(102Ω)が取り付けて有るので このピンはオープンのままです。 MCP2515モジュールを動作させるライブラリとして、GitHubの" CAN_BUS_Shield-master"を使います。 これは、スイッチサイエンスで販売の"SeeedStudio CAN-BUSシールド V2 "用の物ですが、 MCP2515を搭載しているので利用出来ます。 下記からダウンロード出来るファイルは、GitHubからダウンロードしたファイル(mcp_can.cpp)に 日本語でコメントを追記したファイルです。 ↓ここからArduino用サンプルスケッチファイルをダウンロードして下さい。 [CAN_BUS_Shield-master.zip ] ライブラリの登録方法は、ここのペー ジの登録方法1を参照してインストールしましょう。 ここの実験で使用する実例サンプルスケッチは以下です。 [CAN_BUS_Shield-master]-[examples]-[CANrecv]-CANrecv.ino  他ノードから半固定抵抗の値(ID:0x123)を受信してシリアルモニターに表示させるサンプル
PIC18F26K80使い方:ECANの通信実験2

いかがでしたか

って感じの記事ではない気がする。あーー半導体不足いい早く解消して欲しいです。
MCP2562が手に入ったらわざわざレベルシフタを挟まなくて済むのになーって思っています。
この記事が役立ったらツイートしてくださると書いた甲斐があったなぁと思えるので、お願いします!!
今回は1:1のCAN通信をしてみましたが、次はマルチマスタ通信をしてみたいです。
データ衝突が課題になりそうなので模索する必要がありそうです。頑張ります。