もくじ
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トランシーバを使う際の基本の構成を書きます。
記事用に回路図を起こすのもめんどくさいので、今年使ったサブマイコン基板の回路図のスクショを貼ります
マイコンの単体で動かすための部品については詳しく書きません。もしそっちの方を詳しく知りたいよって方は「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トランシーバの近くに終端抵抗を配置しましょう。
(CANの配線例。CANトランシーバの数字は特に意味がない)
各トランシーバは各マイコンのCAN_RX,CAN_TXと適切に繋いでやってください
ちなみに今年使った回路ではDIPスイッチで終端抵抗の有無を変更できるようになっていました。
コネクタ
弊部ではXHコネクタを使用していました。
CANの差動ペアと一緒に電源(7.4V)とGNDも一緒にしています。
正直コネクタは導通さえすればなんでもいいと思うのでお財布事情や使い勝手を考えて選びましょう。
以下によくあるコネクタとデメリットを一応書いておきます
コネクタ | メリット | デメリット |
---|---|---|
USB-B | ケーブルが抜けにくく、強固 | でかい |
USB-C | 裏表がなく、挿しやすい | はんだ付けがめんどくさい |
XHコネクタ | ケーブルが抜けにくく、強固 | ケーブルを自作する必要がある |
VHコネクタ | ケーブルが抜けにくく、ロックがある | ケーブルを自作する必要がある |
RJ45 | ロックが強固で抜けにくい | でかい |
上記はだいぶ主観なので超参考程度にお願いします🙇
回路まとめ
制御班が使いやすいように、かつ設計班の迷惑にならない程度のお大きさに基板を設計してやりましょう。
高専ロボコンで使う程度なのでノイズ対策はお気持ち程度でやりましょう。
とりあえず動けばヨシ!
基本プログラム
回路班に基板を作ってもらったら次はプログラムです。
ここではCubeIDEのHALでCANをとりあえず使えるレベルのプログラムを解説します。
CubeMX側でのCANの設定
CubeMX側でいじる設定は主に上のスクショの3つの部分とClock Configulationです。
手順1
CANの設定画面を開いてActivatedをクリックしてチェックを入れます。
CAN_TX,CAN_RXの出力ピンが複数あるマイコンの場合、基板側でどのピンがCANトランシーバとつながっているかを把握して設定してください。
手順2
Clock Configulationをいじって上のスクショの赤丸で囲ったperipheralのクロック周波数を10の倍数MHzくらいにしておくと後々便利です。上のスクショはF303のもので30MHzに設定しています。
手順3
1つのCANバスに繋いで通信するマイコン達はボーレートとサンプリングポイントを合わせないといけません。
ボーレートは通信速度です。[Kb/s]や[Mb/s]またはKbps,Mbpsと表されます。
サンプリングポイントはCANの信号を1bit受け取る時間の中で論理判定するタイミングのことです。[%]で表されます。
この2つの値をCANの設定のBit Timings Parametersをいじって決定します。
写真は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
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通信を開始する
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);
HAL_CAN_ActivateNotification(&hcan1, CAN_IT_RX_FIFO0_MSG_PENDING);
HAL_CAN_GetTxMailboxesFreeLevel(&hcan1)
HAL_CAN_AddTxMessage(&hcan1, &TxHeader, TxData, &TxMailbox);
HAL_CAN_RxFifo0MsgPendingCallback(CAN_HandleTypeDef *hcan)
HAL_CAN_GetRxMessage(hcan, CAN_RX_FIFO0, &RxHeader, RxData);
実用
回路例
今年のロボット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を導入するにあたって以下の記事を参考にしました。
ありがとうございました。