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通信は差動通信なので、他の
I2C
やSPI
に比べてノイズに耐性がある。 UART
・SPI
・I2C
などがマイコンのペリフェラルにはついていますが、これらは長いワイヤを繋いで通信させるのに向いていません。- I2Cに関しては
I2Cバスリピータ
があれば長距離もできると聞きました。この記事が入り口には良さそうです。 - 他に長距離通信ができるものに
RS485
もあります(がCANほどではない) - 鳥人間コンテストの飛行機のモジュール間通信にもCAN通信を使用しているチームがいるそうです。
- 友達/後輩が鳥人間をやっていて聞きました。阪大だっけ。
他にも波形が綺麗になるなど、さまざまなメリットがありますが、今回のメインはそこではないです。
差動通信について詳しく知りたい方はEDNJapanの記事を見るなどをしてください。
- 同じバス上で複数のマイコン同士で通信できます
- (ネットワークが構成できる)
- I2Cの様にMaster Slaveがない(?)
- 外部に出す配線を簡素化できる
- CANバスドライバが、ある程度の通信エラーに関するエラーハンドリングをしてくれます(たしか…)
- バス上のうち、1つのマイコンが受信に失敗したら、全部ちゃんと成功するまで送り直してくれる?らしい。
私もそこまで詳しくないですが、自分の把握している範囲で書いてみました。
CANをする場合のArduinとESP32の回路の比較
ArduinoでCAN通信をする場合はCANドライバと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_TD
CAN1_RD
がCAN通信をする場合にここからしかピン出せませんよなペリフェラルピン汎用マイコンにもこんな感じの「UARTのTXとRXを自由にスイッチできる機能」を搭載して欲しいですね(ついているやつにはあるらしい)
CANトランシーバをいくつか調べてみた
5時間くらいネットで調べたところ以下のIC達に辿り着けた。
- MCP2561 / MCP2551
- MCP2562 / MCP2552
- TJA1441AT (
2022/10/14追記
こいつは3.3Vでも使えるそうですが最大5Mbpsです)
秋月電子で手に入るものに
MCP2561
とMCP2562
があります。MCP2562
- 電源電圧は5V
- ロジック電圧は3.3Vも対応できる
- 秋月で在庫切れ(2022/7/29)
MCP2561
- 電源電圧は5V
- ロジック電圧は5V。3.3Vでは動かない
- こっちの方が安価
- まだ秋月で手に入る(2022/7/29)
違いは5番ピンにあります。
SPLIT
とVIO
- MCP2561では、
SPLIT
はコモンモード安定化の役割があるらしい
- MCP2562では、
VIO
に3.3Vを印加すれば3.3Vに対応できます - (
VIO
はIC内部にあるロジックレベル変換器の参照電圧入力)
ESP32 + MCP2561 + FXMA108でCAN通信
今回は秋月で
MCP2562(3.3V行ける方)
が手に入らなかったのでMCP2561
でチャレンジしました。こんな感じです。めちゃもじゃってます。レベルシフタの周辺がかなりもじゃってます。
ブレッドボードの実態配線図を描くと絶対汚くなるので、少し改変してKeynoteで配線描きました。
使用したパーツ
ESP32 DOIT
レベルシフタ(FXMA108)
MCP2561
120Ω
回路
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のRX
TX
の設定先を変更しています
- 成功したら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(); }
見た記事
いかがでしたか
って感じの記事ではない気がする。あーー半導体不足いい早く解消して欲しいです。
MCP2562が手に入ったらわざわざレベルシフタを挟まなくて済むのになーって思っています。
この記事が役立ったらツイートしてくださると書いた甲斐があったなぁと思えるので、お願いします!!
今回は1:1のCAN通信をしてみましたが、次はマルチマスタ通信をしてみたいです。
データ衝突が課題になりそうなので模索する必要がありそうです。頑張ります。