前回はM5StackからArduino Unoへ向かって一方通行のCANフレームの通信を行ってみました。当然、今回は双方向での通信の確認であります。お手軽にM5Stack発でUno経由M5Stack着の「ラウンドトリップ」です。同じArduino IDE使用で同じような仕事ですが、衣の下がちょっと違うのが見えまする。
※「鳥なき里のマイコン屋」投稿順Indexはこちら
今回は前回使用のサンプルプログラム(M5Stack用はM5Stack社の、Arduino Uno用はCAN-BUSモジュール販売のLongan Lab社のコード)のチョイ変で実験しています。参考のため当方で勝手改変したコード全文を末尾に掲げてありますが、オリジナルへのURLを併記してあります。
実験の概要
出発点は、M5Stack Grayといたしました。サンプルコード通りAボタンまたはBボタンを押すと、予め決められたCANフレームをCA-IS3050G搭載のCAN-IFから送信します。フレームは以下のフォーマットです
-
- 標準(11bit ID)のデータフレーム、8バイトペイロード、128Kbps
- ID=0x101
とりあえずM5Stackの送信フレームのIDは 0x1zz (zzは0x00から0xff)と勝手に決めました。
当面、拡張フォーマットもリモートフレームも使用しない(CAN-FDなどはそもそもハード的に対応できない)予定です。CANフレームのフォーマットについては例えば以下のページなどで確認できます(車載関係のベンダさんなど多数あり。)
送信されたCANフレームはCANバスを経由しMCP2515搭載のCAN-IFで受信されます。こちらにはArduino Unoが接続されています。フレームを受け取ったArduino UnoはフレームのIDとペイロードをモニタ用の仮想端末に出力するとともに、「チョイ変」して送り返します。チョイ変の内容は、
-
- 受け取ったペイロードの1バイト目のデータを+1する
- 操作した1バイトのみを送り返す(DLC=1)
- ID=0x201
とりあえずArduino Unoの送信フレームのIDは 0x2zz (zzは0x00から0xff)と勝手に決めました。CANバスの規定により、CANバス上でフレームが衝突した場合IDの小さいものが勝つので、M5Stackが常に優先されることになります。
実験用プログラムについて
さきほど書いたとおり、どちらの実験用プログラムもサンプルコードの継ぎはぎなのですが、1つ目立ったのが、
M5Stack用の受信コードは FreeRTOS の機能を使っている
ということであります。以下で、FreeRTOSのQueueを生成しています。
CAN_cfg.rx_queue = xQueueCreate(10,sizeof(CAN_frame_t));
また、受信では上記のQueueからフレームを取り出しています。Queueの「向こう側」には、実際にCANフレームの受信処理を行ってQueueに詰め込んでくれているFreeRTOSのTaskがいる筈です。そのTaskはCANバスIFをイニシャライズするときに生成されているものと考えられます。
M5Stackのコアである ESP32上では、もともとFreeRTOSが走っているので、その気になればArduino環境からでも、いつでもRTOSの機能を使うことができます。別シリーズ「モダンOSのお砂場」でも FreeRTOSのQueue の実習(ターゲットボードはESP32-DevKitCですが)などしてみています。Arduino環境のつもりで書いていても、ちょこっとRTOSを呼び出せるのはなかなか便利じゃないかと思います。個人的にはESP32コアのデバイスを使用する際の「推し」の機能であります。
これに対してArduino Unoでは裏で走っている受信TASKなどというものは無いので、CANフレームの受信を待ち受けるためにループでポーリングし続けています。仕方が無いか。
実行結果
以下に往復旅行(ラウンドトリップ)の旅行先となるArduino Uno側の仮想端末の様子を掲げます。前回同様に、フレーム到着の都度、IDとペイロードバイト列を16進表示しています。前回と異なるのは、受信直後にID=0x201で、復路のフレームをUnoから送信していることです。復路のフレームには往路のペイロードの先頭バイト+1をペイロードとして搭載しています。例えば、受信が0x48(アスキーコードのH)であれば、送信は0x49(アスキーコードのI)、受信が0x4D(アスキーコードのM)であれば、送信は0x4E(アスキーコードのN)を送り返します。
出発点でありゴールのM5Stack側は以下のようです。Send Message A/Bでフレームを送信したことを知らせ、RCV fromでフレームの受信を知らせます。Aで送信するのは”HelloCAN”なので、折り返し返信されてくるのは “I” という文字の筈です。実際Aの直後Arduino側のID=0x201でペイロード “I” が戻ってきています。Bは”M5Stack “なので、返信は “N” の筈。これもペイロード的にはOK。
しかし問題に気づきました。
Arduino Uno側からはDLC=1で送信している筈なのに8と認識されている
私が何かバグを作りこんでいる可能性が高いですが、Arduino Uno側の問題か、M5Stack側か?例によってAnalog Discovery2のロジアナ機能でCANバスを観察すれば一発で分かるでしょう。が、今日はここまで。また後で。
鳥なき里のマイコン屋(123) M5StackとArduino Uno、CANバス接続 へ戻る
鳥なき里のマイコン屋(125) Nucleo+MCP2562でCANバスにフレーム送信 へ進む
Longan Lab社のexamplesプログラムを継ぎはぎして作成したArduino Uno側のCANフレーム「ラウンドトリップ」用テストコード
元のexamplesの出典はこちら、Longan-Labs/Serial_CAN_Arduino
#include "Serial_CAN_Module.h" #include <SoftwareSerial.h> #define CAN_RATE_100 12 #define CAN_RATE_125 13 #define CAN_RATE_200 14 #define CAN_RATE_250 15 #define CAN_RATE_500 16 #define CAN_RATE_666 17 #define CAN_RATE_1000 18 #define can_tx 2 #define can_rx 3 Serial_CAN can; void setup() { Serial.begin(115200); can.begin(can_tx, can_rx, 57600); Serial.println("Uno_can_recvXmit"); if(!can.baudRate(SERIAL_RATE_57600)) { Serial.println("set baud rate fail"); } if(!can.canRate(CAN_RATE_125)) { Serial.println("set can rate fail"); } } unsigned long id = 0; unsigned char dta[8]; void printHex(unsigned int arg) { Serial.print("0x"); Serial.print(arg, HEX); Serial.print(" "); } void loop() { if(can.recv(&id, dta)) { Serial.print("GET DATA FROM ID: "); printHex( (unsigned int)id ); for(int i=0; i<8; i++) { printHex( (unsigned int)dta[i]); } Serial.println(); dta[0] += 1; can.send(0x201, 0, 0, 1, dta); } }
M5Stack社のexamplesプログラムを継ぎはぎして作成したM5Stack側のCANフレーム「ラウンドトリップ」用テストコード
元のexamplesの出典はこちら M5-ProductExampleCodes/Unit/CAN
#include <M5Stack.h> #include "ESP32CAN.h" #include "CAN_config.h" #define TX GPIO_NUM_17 #define RX GPIO_NUM_16 CAN_device_t CAN_cfg; int i = 0; void header(const char *string, uint16_t color) { M5.Lcd.fillScreen(color); M5.Lcd.setTextSize(1); M5.Lcd.setTextColor(TFT_WHITE, TFT_BLACK); M5.Lcd.fillRect(0, 0, 320, 30, TFT_BLACK); M5.Lcd.setTextDatum(TC_DATUM); M5.Lcd.drawString(string, 160, 3, 4); } void setup() { M5.begin(true, false, true); M5.Power.begin(); Serial.println("CAN Unit Send/Receive"); header("CAN-Bus Send/receive", BLACK); M5.Lcd.setCursor(0, 60, 4); CAN_cfg.speed = CAN_SPEED_125KBPS; CAN_cfg.tx_pin_id = TX; CAN_cfg.rx_pin_id = RX; CAN_cfg.rx_queue = xQueueCreate(10,sizeof(CAN_frame_t)); // Init CAN Module ESP32Can.CANInit(); Serial.println("Start"); } void loop() { CAN_frame_t rx_frame; CAN_frame_t tx_frame; if(M5.BtnA.wasPressed()){ tx_frame.FIR.B.FF = CAN_frame_std; tx_frame.MsgID = 0x101; tx_frame.FIR.B.DLC = 8; tx_frame.data.u8[0] = 'H'; tx_frame.data.u8[1] = 'e'; tx_frame.data.u8[2] = 'l'; tx_frame.data.u8[3] = 'l'; tx_frame.data.u8[4] = 'o'; tx_frame.data.u8[5] = 'C'; tx_frame.data.u8[6] = 'A'; tx_frame.data.u8[7] = 'N'; ESP32Can.CANWriteFrame(&tx_frame); M5.Lcd.println("Send Message A"); Serial.println("Send Message A"); } if(M5.BtnB.wasPressed()){ tx_frame.FIR.B.FF = CAN_frame_std; tx_frame.MsgID = 0x101; tx_frame.FIR.B.DLC = 8; tx_frame.data.u8[0] = 'M'; tx_frame.data.u8[1] = '5'; tx_frame.data.u8[2] = 'S'; tx_frame.data.u8[3] = 'T'; tx_frame.data.u8[4] = 'A'; tx_frame.data.u8[5] = 'C'; tx_frame.data.u8[6] = 'K'; tx_frame.data.u8[7] = ' '; ESP32Can.CANWriteFrame(&tx_frame); M5.Lcd.println("Send Message B"); Serial.println("Send Message B"); } if(xQueueReceive(CAN_cfg.rx_queue,&rx_frame, 3*portTICK_PERIOD_MS)==pdTRUE){ printf("RCV from 0x%03x, DLC=%d ",rx_frame.MsgID, rx_frame.FIR.B.DLC); for(int i = 0; i < rx_frame.FIR.B.DLC; i++){ printf("%c\t", (char)rx_frame.data.u8[i]); } printf("\n\r"); } M5.update(); delay(200); }