8
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

鳥羽商船高等専門学校Advent Calendar 2024

Day 1

【高専ロボコン】キャン(CAN)ならずできる!STM32で始めるCAN通信入門

Last updated at Posted at 2024-11-30

もくじ

1.はじめに
2.私はだれ?
3.高専ロボコンでのCAN通信
4.開発環境
5.回路設計・回路構成
6.基本プログラム
7.実用
8.最後に
9.参考文献

はじめに

当記事はロボット制御1年目の新米が作成した記事です。
入門などと銘打ってはいますが、CANの回路の設計からマルチマスタの通信がとりあえずできるようになるまで程度なのでめちゃくちゃ詳しく解説するわけではありません。備忘録程度に書きますので温かい目で見守ってください(切実)


当記事は鳥羽商船高専アドベントカレンダーの12/01担当の記事です!

私はだれ?

鳥羽商船高専情報機械システム工学科22sのロボコン3年目
今年の仕事はR1メイン回路、R1ファームウェア制作、サブ発射機構の設計だった(多すぎ)

・高専ロボコン2024:ピット
・高専ロボコン2023:操縦
・高専ロボコン2022:ピット

高専ロボコンでのCAN通信

高専ロボコンでCAN通信を使うメリット・デメリットを考えた結果以下のようになりました

メリット

・CANバスにサブマイコン基板等をつなげてモータードライバとマイコンの距離を短くしてノイズを減らす
・サブマイコンからリミットスイッチの値などを取得してCANバスに流すことができる
・通信速度が高速なのでリアルタイム性がすごい
ドヤれる

デメリット

・1からSTM32やCAN通信を始めようとすると(予算、技術の)コストが高くて怖い
文献が少ない

メリットはとても強いのにデメリットのせいで導入できないでいるロボコニストも多いことだと思います。
当記事ではそんなロボコニストを少しでも救済したく、ハード開発からソフトの書き方までの解説をしたいと思います。

ちなみに鳥羽Aで実際に使用した構成は

ESP32 -- L433RC -- F303K8T6×5

をすべてCANで繋いで通信しています

ESP32はDUALSHOCK4との通信用
L433RCはメイン制御処理用
F303K8T6は足回りの駆動、発射機構の駆動、電源基板用

開発環境

もの 自分の
OS Windows11
IDE CubeIDE1.14.1(※HALを使用)
メインマイコン Nucleo64-L433RC
サブマイコン STM32F303K8T6 × 2
CANトランシーバ BD41041FJ-C(MCP2561とほぼ互換)
EDA KiCAD8.0
基板発注 JLCPCB

CubeIDEではHALを使用します。
Windows環境でスマソ。

回路設計・回路構成

CANトランシーバ

CANの物理層を実装するためにはマイコンとCANトランシーバという外付けのICを使って
差動のCANバスを作る必要があります。
今年の回路には無料でもらえる、かつ車載用の信頼性から「BD41041FJ-C」というROHM社製のCANトランシーバを使用しました。
また、このCANトランシーバは有名なCANトランシーバの「MCP2561」とピン配置、電源電圧がほぼ互換なので非常に使いやすく、文献も漁りやすかったです。

基本構成

CANトランシーバを使う際の基本の構成を書きます。
記事用に回路図を起こすのもめんどくさいので、今年使ったサブマイコン基板の回路図のスクショを貼ります
image.png
マイコンの単体で動かすための部品については詳しく書きません。もしそっちの方を詳しく知りたいよって方は「STM32F303 単体動作」とかで検索すると良い記事が出てくると思います。

CANトランシーバはマイコンから出るCAN_TX,CAN_RX等の信号を差動に変換してCANバスを作ります。

F303を16MHzのクリスタルで動かしています。
注意するべき点はF303のPA11,12から出ているCAN_RX,CAN_TXです。これはCANトランシーバのRXD,TXDに直接つなぎましょう。決してF303のCAN_RX,CAN_TXをCANトランシーバのTXD,RXDにクロスして配線してはいけません。後輩はこれをやらかして基板の違法建築を頑張ってました。

弊チームで使っているLogic用の電源はLipo7.2VなのでROHM社製LDOでCANトランシーバ、F303用にそれぞれ5.0V、3.3Vに降圧しています。
マイコン、LDO、CANトランシーバのパスコンをお忘れなく。

実用的な配線

CNAバスを安定させて動作させるには「終端抵抗」と呼ばれるものが必要になります。
具体的にはCANバスの両端に120Ω程度の抵抗を挟みます。
下画像の通り、一番物理的に離れているCANトランシーバの近くに終端抵抗を配置しましょう。
image.png
(CANの配線例。CANトランシーバの数字は特に意味がない)
各トランシーバは各マイコンのCAN_RX,CAN_TXと適切に繋いでやってください
ちなみに今年使った回路ではDIPスイッチで終端抵抗の有無を変更できるようになっていました。

コネクタ

弊部ではXHコネクタを使用していました。
CANの差動ペアと一緒に電源(7.4V)とGNDも一緒にしています。
image.png
正直コネクタは導通さえすればなんでもいいと思うのでお財布事情や使い勝手を考えて選びましょう。
以下によくあるコネクタとデメリットを一応書いておきます

コネクタ メリット デメリット
USB-B ケーブルが抜けにくく、強固 でかい
USB-C 裏表がなく、挿しやすい はんだ付けがめんどくさい
XHコネクタ ケーブルが抜けにくく、強固 ケーブルを自作する必要がある
VHコネクタ ケーブルが抜けにくく、ロックがある ケーブルを自作する必要がある
RJ45 ロックが強固で抜けにくい でかい

上記はだいぶ主観なので超参考程度にお願いします🙇

回路まとめ

制御班が使いやすいように、かつ設計班の迷惑にならない程度のお大きさに基板を設計してやりましょう。
高専ロボコンで使う程度なのでノイズ対策はお気持ち程度でやりましょう。
とりあえず動けばヨシ!

基本プログラム

回路班に基板を作ってもらったら次はプログラムです。
ここではCubeIDEのHALでCANをとりあえず使えるレベルのプログラムを解説します。

CubeMX側でのCANの設定

image.png
image.png
CubeMX側でいじる設定は主に上のスクショの3つの部分とClock Configulationです。

手順1

CANの設定画面を開いてActivatedをクリックしてチェックを入れます。
CAN_TX,CAN_RXの出力ピンが複数あるマイコンの場合、基板側でどのピンがCANトランシーバとつながっているかを把握して設定してください。

手順2

image.png
Clock Configulationをいじって上のスクショの赤丸で囲ったperipheralのクロック周波数を10の倍数MHzくらいにしておくと後々便利です。上のスクショはF303のもので30MHzに設定しています。

手順3

1つのCANバスに繋いで通信するマイコン達はボーレートとサンプリングポイントを合わせないといけません。
ボーレートは通信速度です。[Kb/s]や[Mb/s]またはKbps,Mbpsと表されます。
サンプリングポイントはCANの信号を1bit受け取る時間の中で論理判定するタイミングのことです。[%]で表されます。
この2つの値をCANの設定のBit Timings Parametersをいじって決定します。
image.png
写真はperipheralのクロックが30MHzのときにボーレート1MHz、サンプリングポイント80%にする設定です。
Time Quanta in Bit Segment 1 をTseg1
Time Quanta in Bit Segment 2 をTseg2
ReSynchronization Jump Width をSJW
として
それぞれ以下のような計算式で求められます

BaudRate = (Tseg1 + Tseg2 + SJW)\times \Biggl(\frac{peripheralCloks}{Prescaler}\Biggr)^{-1}[bit/s]
SamplingPoint = \frac{Tseg1 + SJW}{Tseg1 + Tseg2 +SJW} \times100[%]

自分が設定するときはわざわざ計算するのがめんどくさいのでヒトミソフト開発様の解説サイトを参考にしています。
以下の解説サイトに任意のボーレート、サンプリングポイントに設定したい際の各パラメータの表があります。

手順4

image.png
CANのNVIC(割り込み)の設定をします。
画像のようにCAN RX0 interruptを有効化するだけです。
これによりCANのメッセージが来た際に割り込みが起きて受信することができます。

フィルタの設定

CANコントローラにはフィルタという機能が備わっており、受信したCANメッセージに添付されたIDを判別してメッセージをFIFOに保存するかしないかを決めます。つまり、各マイコンごとに固有のIDを設定して、送り手はCANメッセージのIDさえ設定すれば任意のノードに送信できるということです。
以下のプログラムはフィルタの設定をして特定の4つのIDを持ったメッセージしか受信しないようにするプログラムです。基本的にマイコン起動後に一回だけ設定すればあとから変更することはあまりないので「static void MX_CAN_Init(void)」の中の「USER CODE BEGIN CAN_Init 2」内に書くことをおすすめします。

  CAN_FilterTypeDef filter;
  filter.FilterIdHigh         = 0x001 << 5;                  // フィルターID1
  filter.FilterIdLow          = 0x002 << 5;                  // フィルターID2
  filter.FilterMaskIdHigh     = 0x003 << 5;                  // フィルターID3
  filter.FilterMaskIdLow      = 0x004 << 5;    // フィルターID4
  filter.FilterScale          = CAN_FILTERSCALE_16BIT; // 16モード
  filter.FilterFIFOAssignment = CAN_FILTER_FIFO0;      // FIFO0へ格納
  filter.FilterBank           = 0;
  filter.FilterMode           = CAN_FILTERMODE_IDLIST; // IDリストモード
  filter.SlaveStartFilterBank = 14;
  filter.FilterActivation     = ENABLE;

  HAL_CAN_ConfigFilter(&hcan1, &filter);
CAN_FilterTypeDefという型でfilterという名前のインスタンスを作っています。 これの中身をいじります。 このプログラムでは受取可能なIDを「0x001」「0x002」「0x003」「0x004」の4つに設定しています。左に5bitシフトされているのはFilterIdHighやFilterIdLowなどのレジスタが16bitの構成で、上位11bitのみが設定されるIDの値が入る場所になっているからです。このため0xNNNの16進数が2進数11bit分を超えないように注意する必要があります。 このようにIDを4つ分直接指定する形式をIDリストモードといいます。STM32のフィルターには他の形式もあるのですが、使い勝手が良いので自分はいつもこれを採用しています。 その他の形式についてはヒトミソフト開発様の記事を参考にしてください。

CAN通信を開始する

main関数内でCAN通信を開始するための関数を呼び出す必要があります。
自分はいつも「/* USER CODE BEGIN 2 */」の中に書いています。
例を以下に示します。

	/* USER CODE BEGIN 2 */

	// CANスタート
	HAL_CAN_Start(&hcan1);
	// 割り込み有効
	HAL_CAN_ActivateNotification(&hcan1, CAN_IT_RX_FIFO0_MSG_PENDING);
	/* USER CODE END 2 */

この2つの関数を呼び出すことでCAN通信とCAN受信割り込みを開始することができます。

送信プログラム例

CANメッセージを送信する際に必要な動作を含んだ送信用関数を以下に示します。
main関数の外に書いて使います。

void CAN_TX(uint32_t recipient) {
    //送信用インスタンス等
	CAN_TxHeaderTypeDef TxHeader;
	uint32_t TxMailbox;
	uint8_t TxData[8];
    //送信メールボックスに空きがあったら送信開始
	if (0 < HAL_CAN_GetTxMailboxesFreeLevel(&hcan1)) {
        //送信用インスタンスの設定
		TxHeader.StdId = recipient;// 受取手のCANのID
		TxHeader.RTR = CAN_RTR_DATA;
		TxHeader.IDE = CAN_ID_STD;
		TxHeader.DLC = 8;//データ長を8byteに設定
		TxHeader.TransmitGlobalTime = DISABLE;
        //各データ
		TxData[0] = 0;
		TxData[1] = 1;
		TxData[2] = 2;
		TxData[3] = 3;
		TxData[4] = 4;
		TxData[5] = 5;
		TxData[6] = 6;
		TxData[7] = 7;
        //CANメッセージを送信
		if (HAL_CAN_AddTxMessage(&hcan1, &TxHeader, TxData, &TxMailbox) != HAL_OK) {
			Error_Handler();
		}
	}
}

自分のチームのプログラムでは必要な操作をすべて1つのvoid型関数にぶち込んでCANメッセージを送信していました。今回は引数に送信先のIDを指定していますが、引数を追加してTxDataの中身を指定できるようにしたりしても良いと思います。

受信プログラム例

CANの受信はすべて割り込みハンドラで行います。
「void HAL_CAN_RxFifo0MsgPendingCallback(CAN_HandleTypeDef *hcan)」という名前の関数をmain関数外に宣言するとIDフィルタを通過したCANメッセージを受信するたびにその関数が呼び出されます。
この割り込みハンドラ内に必要な処理を記述して受信したデータを取り出します。例を以下に示します。

void HAL_CAN_RxFifo0MsgPendingCallback(CAN_HandleTypeDef *hcan1) {
	CAN_RxHeaderTypeDef RxHeader;//受信メッセージの情報が格納されるインスタンス
	uint8_t RxData[8];//受信したデータを一時保存する配列
	if (HAL_CAN_GetRxMessage(hcan1, CAN_RX_FIFO0, &RxHeader, RxData)== HAL_OK) {
		id = RxHeader.StdId; // RxHeaderの中に入っているidを取り出す
		if (id == 0x001){
			for (int i = 0; i <= 7; i++){
    				use_data[i] = RxData[i];
			}
		}
	}
}

「HAL_CAN_GetRxMessage()」という関数で受信したメッセージの情報を取得しています。
この関数で受信したCANメッセージの情報を「RxHeader」に、データを「RxData」に格納します。
「RxHeader」に格納されたIDがフィルタで指定された4つのIDの内、「0x001」かを確認し、一時保存したデータを今後使うデータ配列「use_data」に渡しています。ここでの「use_data」はグローバル宣言してあるものとします。

関数の解説

HAL_CAN_Start(&hcan1);
CANをスタートさせる関数です。一回呼び出せばその後はずっとCANが動くのでUSER CODE BEGIN 2の中に書いていました。引数はスタートさせたいCANのポートに名前を合わせてください。Private variablesの欄にCAN_HandleTypeDefの型で「hcan1」や「hcan」と宣言されているはずです。
HAL_CAN_ActivateNotification(&hcan1, CAN_IT_RX_FIFO0_MSG_PENDING);
CANの割り込み受信をスタートさせる関数です。第一引数は受信するCANのポート、第二引数は受信したメッセージを格納するFIFOの名前です。いつもHAL_CAN_Start()のすぐ後で呼び出しています。
HAL_CAN_GetTxMailboxesFreeLevel(&hcan1)
CANの送信メッセージ用のメールボックスがどれだけ空いているのかを確認する関数です。 メールボックスの空き数が0より多ければ問題なくCANの送信を開始できます。
HAL_CAN_AddTxMessage(&hcan1, &TxHeader, TxData, &TxMailbox);
CANのメッセージを送信する関数です。第一引数は送信するCANのポート、第二引数は送信するメッセージの設定、第三引数は送信する8byteの配列、第四引数はメールボックスの数字が格納される変数。
HAL_CAN_RxFifo0MsgPendingCallback(CAN_HandleTypeDef *hcan)
CANのメッセージを受信した最に呼び出される割り込みハンドラです。 main関数の外に自分で宣言し直して使います。
HAL_CAN_GetRxMessage(hcan, CAN_RX_FIFO0, &RxHeader, RxData);
CANを受信した後に各データを取り出す関数です。「HAL_CAN_RxFifo0MsgPendingCallback」の中で呼び出すのが一般的な気がします。第三引数は受信したメッセージの各設定が入る変数です。第四引数は受信したデータが入る8byteの配列です。

実用

回路例

image.png
今年のロボット1で使用した回路の構成図です。
draw.ioで作成しました。
アイソレートは一切行っておらずGNDはすべて共通です。なんとかなるもんですね...
両矢印の線はCANですべて同じバスになっています。
ESP32、Nucleo-L433RCなどの市販品についてはそれらが刺さる基板を作ってそれと一緒にCANトランシーバ等を実装しました。
しれっとESP32とSTM32同士でCANを行っていますが、その解説はまた後日記事が上がったり上がらなかったりするかもしれません。
サブマイコンの基板はDIPスイッチでCANのIDを変更できるようになっていました。具体的にはプルアップされたSTM32の各GPIOをDIPスイッチでGNDに落として内部で処理をしていました。
これによってだいぶメンテナンス性が上がったのでとても良い基板でした。

マルチマスタ

ロボット内でCAN通信を実装するとなると上述の回路図の様におそらく3つ以上の数のマイコンを使うことになります。
CAN通信はマルチマスタなので理論上はすべてのマイコンが一つのCANバス上で情報の送受信をすることができますが、実際には通信速度やマイコンの処理速度の関係で送受信が滞ることがあります。
実際に今年のロボットで各マイコンの毎ループ事にCANメッセージを送信しまくっていたらメイン制御マイコンがいきなりCANの受信をやめる反抗期状態に入ってしまったこともありました。

CAN送信のタイミングについて

CANメッセージの送信回数、タイミングを必要最低限にするため、2種類の方法を試しました。
ミドルウェアとしてFreeRTOSを使う方法と純粋なタイマ割り込みを使う方法です。それぞれメリットとデメリットを以下に示します。

FreeRTOS

マイコン用のOSです。コアとスレッドが1つしかないマイコンでも擬似的に別スレッドで別のタスクを動かすことができます。各タスクごとにdelayの値が設定できるため、CANメッセージの送信タイミング調整に使おうとしました。

バグが多すぎる...
普通にCANとI2Cを同時に動かそうとした際、設定したタスクがどちらも一切動かないという謎現象が起きて発狂しました。CANとI2Cの走らせ方が悪かった可能性もありますが、参考文献も少ないためFreeRTOSでの開発は断念しました。

タイマ割り込み

マイコンの余っているタイマを動かしてn秒ごとに割り込みハンドラを呼びフラグを立ててCANメッセージを一定のタイミングごとに送るという方法を取りました。結果から言うとすごくうまくいきました。多いサブマイコンで取っている各リミットスイッチの値をCANバスに垂れ流したかったので、0.1秒ごとにタイマ割り込みを起こしてCANメッセージをメイン制御基板に送っていました。
デメリットとして、サブマイコンにF303を採用していたので余っているタイマーが少なく、好きな機能が自由に使えない可能性が出てくることが挙げられます。
もっと強いマイコンをサブマイコンとして使おう

コード例

今年の鳥羽Aで使ったプログラムをGitHubで公開します。
本当にひどいプログラムですが参考にしていただけると幸いです。
また、なにかしらの使い方が間違っていたりしたらぜひともご指摘をお願いいたします。

↑ロボット1のメイン制御プログラム

↑ロボット1のサブ基板のファームウェアプログラム

最後に

とりあえず、ここまで読んでいただいてありがとうございます。
最後の方は結構時間ギリギリで書きました。
要所要所の解説が本当に正しいか不安が残っているのでもし解説、コード等に語弊、誤りがあればご指摘をお願いいたします。
この記事が少しでもCAN通信を使いたいロボコニストに届くことを祈っています。
ありがとうございました!

参考文献

今年CANを導入するにあたって以下の記事を参考にしました。
ありがとうございました。

8
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
8
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?