RXマイコン C++ フレームワークによる CAN 通信

<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/58667/949d136b-a98f-3b78-ac55-8bed2f71d4c5.jpeg" width="50%"><img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/58667/180b5d16-c6ff-fc37-bf12-b3602c977bc7.jpeg" width="50%">

GR-CITRUS と RX72T ボードの CAN 通信

---

## RX マイコンに備わっている CAN ペリフェラル

RX マイコンには、CAN 通信（ISO11898-1仕様準拠）を行う機能を備えた品種が数多くあります。
又、複数チャネルを持った品種もあり、様々な構成に対応可能です。

外部に付加するハードウェアーは、CAN バスに対応したトランシーバーを追加するだけで良く、高品質な通信を簡単に行う事が出来ます。

CAN 通信では、最大８バイトのパケットを、1M B.P.S. 程度で送信、受信し、複数の機器をバス状に接続出来ます。
通信信号は、差動で行い、ノイズに強く、長い通信路でも品質を維持します。
パケットは、ID で管理され、ネゴシエーションが自動で行われ、送信タイミングに特別な制限がありません。
※詳しくは、CAN の解説を参照して下さい。

---

ＲＸマイコン内蔵の CAN ペリフェラルを使い、実際にソフトウェアを実装して利用するには、それなりのハードルがあるものと思います。
※スマートコンフィギュレーターを使って設定を生成する等も一つの選択肢ではありますが、C++ フレームワークの導入は、それより遥かに簡単で、有意義と思います。
※「can_io.hpp」をインクルードするだけですから・・

RX マイコン C++ フレームワークでは、CAN 通信における運用を一般化して、アプリケーションの実装に専念できるようにしています。
C++ を使ったクラスは、簡単で柔軟性があり、間違いが少ない方法で、データ通信が可能なように設計されています。
CAN レジスターや振る舞いに対する知識は殆ど必要ありません。

サンプルプログラムは、RX62N, RX631, RX64M, RX71M, RX66T, RX72T, RX72N に対応したプロジェクトを用意してあります。
※RX63T、RX651/RX65N でも、Makefile を用意すれば同じように利用可能です。

アプリケーションは、フレームワークに従えば、どのＲＸマイコンでも同じように動作するように実装出来るので、ソースコードの再利用が可能です。

RX62N: FRK-RX62N(CQ 出版社) / BlueBoard-RX62N_100pin を利用
RX631: GR-CITRUS を利用
RX72N: RX72N Envision Kit を利用

### RSCAN について

RX マイコンには、RSCAN と呼ばれるペリフェラルを備えた品種も存在します。（RX220、RX24T など）
RSCAN は、周波数が低めの RX マイコン向けのようで、ソフトウェアの比重を少なくし、なるべくハードウェアーで処理させるものと思います。
※その為、制限もあるものと思います。
現在、C++ フレームワークではサポートしていません。

---

## CAN バス・トランシーバーの接続

RX マイコンの CAN 信号は、CTX、CRX の二本で、RX マイコンの品種によって異なりますが、多くの選択肢からポートにアサイン可能です。

バス・トランシーバーは、3.3V、5V 品があり、電源電圧に対応した IC を選択する必要があります。

### CAN バス・トランシーバーの代表的な品種

[Texas Instruments: SN65HVD23x](https://www.ti.com/jp/lit/ds/symlink/sn65hvd230.pdf?ts=1604189973572&ref_url=https%253A%252F%252Fwww.google.com%252F)
- 3.3V 動作
- 5V 不可
- 最大 1 Mbit/s

[NXP Semiconductors: TJA1441](https://www.nxp.com/docs/en/data-sheet/TJA1441.pdf)
- 5V 動作
- I/O 電圧 3.3V、5V 選択可
- 最大 5 Mbit/s

秋月電子では、現在、３種類の CAN バス・トランシーバーを扱っていますが、電源電圧は 5V なので、マイコンが 3.3V 品の場合、別途 5V が必要です。
- [ＭＣＰ２５６２ＦＤ－Ｅ／Ｐ](https://akizukidenshi.com/catalog/g/gI-14383/)
- [ＴＪＡ１４４１ＡＴ](https://akizukidenshi.com/catalog/g/gI-17464/)
- [ＭＣＰ２５６１－Ｅ／Ｐ](https://akizukidenshi.com/catalog/g/gI-12483/)

Amazon:
[SN65HVD230 CANバストランシーバー通信モジュール](https://www.amazon.co.jp/PDyammy-SN65HVD230-CAN%E3%83%90%E3%82%B9%E3%83%88%E3%83%A9%E3%83%B3%E3%82%B7%E3%83%BC%E3%83%90%E3%83%BC%E9%80%9A%E4%BF%A1%E3%83%A2%E3%82%B8%E3%83%A5%E3%83%BC%E3%83%ABArduino%E3%82%B3%E3%83%B3%E3%83%88%E3%83%AD%E3%83%BC%E3%83%A9%E3%83%BC%E3%83%9C%E3%83%BC%E3%83%89%E7%94%A8%E3%81%AE%E7%86%B1%E4%BF%9D%E8%AD%B7%E3%82%B9%E3%83%AD%E3%83%BC%E3%83%97%E5%88%B6%E5%BE%A1%E3%83%AD%E3%82%B8%E3%83%83%E3%82%AF%EF%BC%88%E3%82%AB%E3%83%A9%E3%83%95%E3%83%AB%EF%BC%89/dp/B09HS1QPRW/ref=sr_1_1?__mk_ja_JP=%E3%82%AB%E3%82%BF%E3%82%AB%E3%83%8A&crid=1T48YALVA7ZN0&keywords=can+%E3%83%90%E3%82%B9%E3%83%88%E3%83%A9%E3%83%B3%E3%82%B7%E3%83%BC%E3%83%90%E3%83%BC&qid=1678582351&sprefix=can+%E3%83%90%E3%82%B9%E3%83%88%E3%83%A9%E3%83%B3%E3%82%B7%E3%83%BC%E3%83%90%E3%83%BC%2Caps%2C158&sr=8-1)

SN65HVD230 の参考回路：
![CAN_IF.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/58667/793c175b-e1c8-7647-7abc-010517536819.png)

### CAN バスポート選択肢

RX マイコン C++ フレームワークでは、ポートの選択肢を変更する機構を備えています。

```C++
	typedef device::CAN0 CAN0_CH;
	static constexpr auto CAN0_PORT = device::port_map::ORDER::FIRST;

	typedef device::can_io<CAN0_CH, CAN_RXB, CAN_TXB, CAN0_PORT> CAN0;
	CAN0	can0_;
```

上記のように「device::port_map::ORDER」型を使って、can_io テンプレートクラスに指示します。

選択肢は、各マイコンの「port_map.hpp」に実装があります。
※ハードウェアーマニュアルの「マルチファンクションピンコントローラ（MPC）」の表に記された順番に準拠しています。
上記の場合、FIRST は、以下のように定義されています。

```C++
			case peripheral::CAN0:
			// P33/CRX0
			// P32/CTX0
				{
					uint8_t sel = enable ? 0b1'0000 : 0;
					PORT3::PMR.B3 = 0;
					PORT3::PMR.B2 = 0;
					MPC::P33PFS.PSEL = sel;  // ok
					MPC::P32PFS.PSEL = sel;  // ok
					PORT3::PMR.B3 = enable;
					PORT3::PMR.B2 = enable;
				}
				break;
```

「device::port_map::ORDER::SECOND」とすると、PD2/CRX0、PD1/CTX0 が選択されます。
詳しくは、各マイコンの port_map.hpp を参照して下さい。

GR-CITRUS の場合 (FIRST)：
P33/RXD3/SCL3(11) : CRX
P32/TXD3/SDA3(10) : CTX

---

## サンプルプログラムのダウンロードとコンパイル

- RX マイコン C++ フレームワークは、Github で入手可能です。
- C++ コンパイラ、gcc-8.3.0 は、ルネサスエレクトロニクスからダウンロード可能です。
- コンパイルには、MSYS2（Windows）をインストールする必要があります。
- boost-1.74.0 が必要です。
- テキストエディターが必要なら、VSCode をインストールして下さい。
- Windows のターミナルソフトは TeraTerm が便利です。

参照：
- [gcc、g++を使ったルネサスＲＸマイコン開発](https://qiita.com/hira_kuni_45/items/a890e9ec613b88ca9a5f)
- [ＲＸマイコン C++ フレームワーク](https://github.com/hirakuni45/RX)
- [Boost C++ Libraries](https://www.boost.org/)
- [VisualStudioCode](https://code.visualstudio.com/)
- [TeraTerm](https://ttssh2.osdn.jp/index.html.ja)

### コンパイル

GR-CITRUS の場合：
CAN_sample/RX631 に移動、「make」します。

```bash
/d/Git/RX/CAN_sample/RX631 % make

...

   text    data     bss     dec     hex filename
  96428   16880   10836  124144   1e4f0 can_sample.elf
rx-elf-objcopy --srec-forceS3 --srec-len 32 -O srec can_sample.elf can_sample.mot
rx-elf-objdump -h -S can_sample.elf > can_sample.lst
```

※CC-RX コンパイラは、C++11 以降に対応していない為、コンパイルする事が出来ません。

---

## CAN サンプルの使い方

CAN サンプルでは、シリアルターミナルを接続して、対話形式によって CAN 通信を行います。
※通常二台で通信するので、通信実験を行うには、CAN 対応ＲＸマイコン、USB シリアルなど二組必要です。

CAN サンプルでは、CAN0 のインスタンスに対して「can_analizer」クラスによるデータ収集、解析を提供しています。
複数チャネルを有効にした場合、CAN1 のインスタンスに対して、ID フィルターを行うコンパイルオプションがあります。

下記の例は、GR-CITRUS と RX72T-DIY ボードによるものです。
共にシングルチャネルで、双方でデータ送信、受信を実験します。

![CAN_command.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/58667/dffa6fd3-836d-e4e3-ed89-105713e3789a.png)

---

## 対話コマンド

対話形式で、CAN データ送信、受信の確認等が出来ます。

- プログラムを起動すると、常に全ての ID を受信します。
- ID の収集は「can_analizer」クラスで行っており、boost::unoderd_map を使っています。
- ID の収集がメモリの限界を超えると、メモリ不足でエクセプションがスローされてクラッシュすると思われます。
- send_loop コマンドにより、乱数で生成した ID、データを連続で送信します。
- ID を補足する機能（map）は、チャネル０番のみに備わっています。
- RX64M/RX71M では、２チャネルの CAN を有効にします。
- CAN1 の受信では、受信したデータを常に表示します。
- MB-no はメールボックスの番号です。
- メールボックス関係のコマンドは、デバッグ用です。
- 受信した CAN フレームは、メモリに格納され、「dump」コマンドで確認出来ます。
- 同じ ID に新しいデータフレームが来ると、上書きされ、カウンタが進みます。

```
CAN command version: 1.00
    ext                    set ext-id mode
    std                    set std-id mode
    send CAN-ID [data...]  send data frame
    stat MB-no             stat MCTLx register (MB-no: 0 to 31)
    list MB-no             list MBx register (MB-no: 0 to 31)
    status                 list recv/send error count
    clear [CAN-ID]         clear map
    map [CAN-ID]           Display all collected IDs
    dump CAN-ID            dump frame data
    send_loop NUM [-rtr]   random ID, random DATA, send loop (RTR)
    help                   command list (this)

  Input number: nnn decimal, xnnn hexa-decimal, bnnn binary
```

### ch コマンド

- 「#define MULTI」を有効にした場合で、CAN1 のインスタンスがある場合に、カレントチャネルを変更する。

### valid コマンド

- VALID フィルターを有効にする、無効にする。

### valist コマンド

- 有効な ID リストを表示。

### ext コマンド

- 拡張 ID モードにする。

### std コマンド

- 標準 ID モードにする。

### send CAN-ID [data...] コマンド

- ID を指定して、データ列を送信。
- ID のみの場合、リモートフレームの送信。
- 最大８バイトのデータ列送信。
- 数値は、１０進数、１６進数（先頭に x を付ける）、２進数（先頭に b を付ける）を入力可能。
   
### stat MB-no コマンド（デバッグ用）

- CAN MCTLx レジスターの表示。
- MB-no は、０～３１

### list MB-no コマンド（デバッグ用）

- CAN MBx レジスターの表示。
- MB-no は、０～３１

### clear [CAN-ID] コマンド

- can_analizer クラスの個別 ID 情報のクリア。
- can_analizer クラスの収集履歴を全てクリア。

### map [CAN-ID] コマンド

- can_analizer クラスが収集した ID の表示。
- 個別 ID の詳細表示。

### dump [CAN-ID] コマンド

- can_analizer クラスの個別 ID に関するフレームデータ表示。
- フレームデータは、その ID が最後に取得したものになります。

### send_loop [num] コマンド

- ID を乱数で生成して、データ送信を行う。
- データ送信を繰り返す数を指定する。

### help コマンド

- コマンドの簡単な説明を表示。

---

## can_io クラス

- can_io クラスは、CAN フレームを FIFO バッファを通してやりとりし、CAN ハードウェアーの操作を隠蔽しています。
- 基本的に、ID を指定して、データ列を送るだけです。
- 受信動作では、CAN フレームクラスにデータがセットされてバッファに積まれます。
- アプリケーションは、バッファに積まれたデータをディスパッチして所定の動作を行うだけです。
- 必要な情報を取得する事も出来ます。
- 詳細は「[common/can_io.hpp](https://github.com/hirakuni45/RX/blob/master/common/can_io.hpp?ts=4)」を参照下さい。

### CAN チャネル

- ＲＸマイコンの種類により、利用出来るチャネル数が異なります。
- CAN の通信ラインを利用可能なポートにアサイン出来ない場合があります。
- ポートのアサインは、port_map.hpp を参照して下さい。
- 「ハードウェアーマニュアル、マルチファンクションピンコントローラ（MPC）、マルチプル端子の割り当て端子一覧」でも確認する事が出来ます。
- インスタンスを定義する時、「device::CAN0」のように指定します。

|マイコン|RX621/RX62N|RX631/RX63N|RX64M/RX71M|RX651/RX65N|RX72N|RX66T/RX72T|
|---|:-:|:-:|:-:|:-:|:-:|:-:|
|CAN0(CAN)|〇|〇|〇|〇|〇|〇|
|CAN1|－|〇|〇|〇|〇|－|
|CAN2|－|〇|〇|－|〇|－|

---

### 受信、送信バッファのサイズ指定

- utils::fixed_fifo テンプレートクラスを使い、「device::can_frame」型（クラス）で定義します。

can_io テンプレートクラスの「typedef」で、以下のように定義します。（受信、送信、256 フレームの場合）

```C++
	// CAN 受信バッファの定義
	typedef utils::fixed_fifo<device::can_frame, 256> CAN_RXB;
	// CAN 送信バッファの定義
	typedef utils::fixed_fifo<device::can_frame, 256> CAN_TXB;

	typedef device::can_io<CAN0_CH, CAN_RXB, CAN_TXB, CAN0_PORT> CAN0;
	CAN0	can0_;
```

- サイズは、メインループのリフレッシュレート、CAN フレームの通信頻度により適切な値を定義します。
- メインループを 1/100 秒で回すのなら、1M B.P.S の通信速度なら、最大のフレーム数は 160 位なので、256 あれば十分でしょう。
- 16 フレーム以下のサイズを指定した場合、static_assert によりコンパイルを失敗します。

### 通信速度の指定

CAN の通信速度は、125K ～ 1M の間で選択出来ます。

```C++
		//+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++//
		/*!
			@brief  CAN スピード型
		*/
		//+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++//
		enum class SPEED {
			_50K  =    50'000,	///<  50 Kbps（標準ではない）
			_100K =   100'000,	///< 100 Kbps（標準ではない）
			_125K =   125'000,	///< 125 Kbps
			_250K =   250'000,	///< 250 Kbps
			_500K =   500'000,	///< 500 Kbps
			_750K =   750'000,	///< 750 Kbps（標準ではない）
			_1M   = 1'000'000	///< 1 Mbps
		};
```

### CAN の開始

- 通信速度を指定して、「start」を呼ぶだけです。
- 必要な設定は、全て、can_io クラスが行います。
- 何等かの理由で、開始出来ない場合「false」が返ります。
- 何も指定しない場合、割り込みの優先度はレベル「１」が使われます。

```C++
		if(!can0_.start(CAN::SPEED::_1M)) {
			utils::format("Can't start CAN0...\n");
		} else {
			utils::format("CAN0: SPEED: %u [bps], BRP: %u, TSEG1: %u, TSEG2: %u, SJW: %u\n")
				% can0_.get_speed()
				% can0_.get_bcr_brp() % can0_.get_bcr_tseg1() % can0_.get_bcr_tseg2()
				% can0_.get_bcr_sjw();
			utils::format("    RX Interrupt level: %u, TX Interrupt level: %u\n")
				% static_cast<uint16_t>(can0_.get_rxm_level())
				% static_cast<uint16_t>(can0_.get_txm_level());
		}
```

---

### フレームの送信

- フレームの送信は、ID、データ列、データサイズのパラメータで行います。

```C++
		//-----------------------------------------------------------------//
		/*!
			@brief  データ送信 @n
					src が「nullptr」、又は「len」が「０」の場合、リモートフレーム送信。
			@param[in]	id		SID+EID の ID
			@param[in]	src		送信データポインター
			@param[in]	len		送信データ数
			@param[in]	ide		拡張モードの場合「true」
			@return 成功なら「true」
		*/
		//-----------------------------------------------------------------//
		bool send(uint32_t id, const void* src, uint32_t len, bool ide = false) noexcept
```

ID: 1000 で、データ列：1, 2, 3, 0xa, 0xb, 0xc を送信。
```C++
    uint8_t data[] = { 1, 2, 3, 0xa, 0xb, 0xc };
    can0_.send(1000, data, sizeof(data));
```

ID: 900 のリモートフレーム（データ列が無い）を送信。
```C++
    can0_.send(900, nullptr, 0);
```

拡張 ID: 0x10000 のリモートフレーム送信。
```C++
    can0_.send(0x10000, nullptr, 0, true);
```

何等かの理由で送信に失敗すると「false」が返ります。

---

### フレームの受信

- 受信したフレームは、FIFO バッファに格納されます。
- 受信したフレーム数を取得します。
- フレーム数分、受信フレームを取り出して処理します。
- この処理は、メインループ内で常に動かして、バッファが溢れないように配慮する必要があります。
- サンプルでは、メインループを 100Hz で動かしています。（一つのターンが 10 ミリ秒）
- タイミング的に、フレームを受信して応答するまでに、最悪 10 ミリ秒かかります。
- メインループのリフレッシュレートは、通信の頻度、バッファのサイズ、応答速度などにより決定します。

```C++
		while(can0_.get_recv_num() > 0) {
			auto frm = can0_.get_recv_frame();
			utils::format("\nCAN0:\n");
			CAN::list(frm, "  ");
		}
```

---

### can_frame クラス

- 受信したフレームは、このクラスにコピーされます。
- 詳細は、[can_frame クラス](https://github.com/hirakuni45/RX/blob/master/RX600/can.hpp?ts=4)を参照下さい。

---

## CAN/ID フィルター

- CAN では、パケットは ID で管理する為、誰かが、パケットを送信すると、全員に同じ物が届きます。
- その為、自分に必要が無いパケットは無視する事になります。
- CAN ペリフェラルは、特定の ID にだけ応答するような設定も可能ですが、個数に制限があり、開始する前に決めなければなりません。
- この仕様では一般化が難しいので、常に全てのパケットを受信するようにしています。
- RX マイコンは、非常の性能が高いので、全てのパケットに応答しても、全体のパフォーマンスが低下する事は少ないです。
- C++ を使うと、特定のパケットにだけ応答する機能を簡単に、効率良く実装出来ます。
- ビットマスクも併用すれば、効率はさらに上がります。（ビットマスクだけなら、さらに簡単）
- フィルタ処理をソフトウェアで行えば柔軟性は無限大です。

main.cpp には、通過する事が出来る ID リストを使った、フィルターのサンプルコードが含まれます。   
※この機能は、「MULTI」チャネルの場合に利用されています。
※シングルチャネルの場合、「can_analizer」クラスが標準で、このクラスが、ID の収集を自動で行っています。 
   
フィルターには、「boost/unordered_set.hpp」を利用しています。
   
main.cpp の先頭で、「#define VALID_FILTER」をコメントアウトすると、フィルターをスルーします。
   
有効な ID テーブルは、以下のように定義します。
boost::unordered_set を使っています。   

```C++
	// 有効な ID だけ通すフィルター
	typedef boost::unordered_set<uint32_t> VALID;
	VALID	valid_{ 0x123, 0x200, 0x300, 0xaaa, 15, 21, 33 };
```
   
メインループで、CAN の受信フレームをディスパッチする際に、ID が有効か確認し、無効な ID を無視します。
   
```C++
		while(can1_.get_recv_num() > 0) {
			auto frm = can1_.get_recv_frame();
#ifdef VALID_FILTER
			if(valid_.find(frm.get_id()) != valid_.end()) {  // ID がマッチするか？
#else
			{
#endif
				utils::format("\nCAN1:\n");
				CAN::list(frm, "  ");
			}
		}
```

- 「boost::unordered_set」はハッシュを使ったテンプレートクラスで、フィルターの数は、メモリ（RAM）の容量に依存します。
- ハッシュを使っているので、効率よく（少ないコストで）探します。
- ID を動的に追加したり、削除したりが簡単に行えます。
- 「unordered_map」を使えば、指定 ID での挙動（コールバック関数）などをコントロールする事も出来ます。
- このような実装は、C++ の本領発揮で、本当に便利で簡単です。

---

## まとめ

C++ で実装された CAN 通信クラスのおかげで、CAN 通信を手軽に行う事が出来ます。
組み込みマイコンで、C++ を積極利用する人は少ないのですが、高性能な RX マイコンなら、習得する価値は十分大きいと思います。

---

## ライセンス

### boost:
[Boost Software License](https://www.boost.org/users/license.html)

### C++ フレームワーク：
[MIT License](https://github.com/hirakuni45/RX/blob/master/LICENSE)