初心者向けSTM32 CAN通信解説:サンプルコード付き
はじめに
組み込みシステム開発の世界へようこそ!特に、複数のマイクロコントローラーやセンサー、アクチュエーターなどが連携して動作するシステムを構築する際、それらの間で情報をやり取りするための「通信プロトコル」は非常に重要になります。数ある通信プロトコルの中でも、自動車分野をはじめとして、産業機器、医療機器など、高い信頼性とリアルタイム性が求められる多くの分野でデファクトスタンダードとなっているのが「CAN (Controller Area Network)」です。
しかし、「CAN通信」と聞くと、なんだか難しそう…と感じる初心者の方も多いのではないでしょうか? 物理的な配線、データ形式、複雑な設定…確かに学ぶべきことはいくつかありますが、STM32のような高機能なマイクロコントローラーを使えば、比較的簡単にCAN通信を実装することができます。
この記事は、まさに「STM32は触ったことがあるけれど、CAN通信は初めて」という初心者の皆さんを対象にしています。CAN通信の基本的な概念から、STM32CubeIDEを使った環境構築、そして具体的なサンプルコードを使ったプログラミング方法までを、ステップバイステップで丁寧に解説していきます。この記事を読めば、あなたのSTM32プロジェクトにCAN通信機能を組み込むための第一歩を踏み出せるようになるでしょう。
さあ、一緒にCAN通信の世界へ飛び込んでみましょう!
1. CAN通信の基礎を知る
STM32でCAN通信を始める前に、まずはCANそのものがどのような通信プロトコルなのかを理解しておくことが大切です。
1.1 CANの歴史と用途
CANは、ドイツのRobert Bosch GmbHが自動車内の電気制御ユニット(ECU: Electronic Control Unit)間の通信を効率化するために1980年代に開発しました。それまでの自動車は、ECUごとに配線が必要で、ハーネスが非常に太く重くなるという課題がありました。CANを導入することで、複数のECUが同じ2本の配線を共有し、必要な情報だけを選択的に受け取れるようになり、配線の簡素化と軽量化が実現されました。
現在では、自動車だけでなく、以下のような幅広い分野で利用されています。
- 自動車: エンジン制御、ブレーキ制御(ABS)、エアバッグ、カーナビ、ボディ制御など、車内のあらゆるECU間通信。
- 産業機器: 工場の自動化ライン、ロボット、センサー、PLC(プログラミング可能なロジックコントローラー)間通信。
- 医療機器: 診断装置、治療装置。
- 鉄道: 車両制御。
- 航空宇宙: 航空機のシステム制御。
- 農業機械: トラクターなどの制御システム。
これらの分野に共通するのは、「リアルタイム性」「高い信頼性」「分散制御」が求められる点です。
1.2 CANの特徴
CANがこれほど多くの分野で採用されているのには、いくつかの優れた特徴があるからです。
- マルチマスタ通信: ネットワーク上のどのノード(CANデバイス)も、マスタとして通信を開始できます。特定のノードが故障しても、他のノードが通信を継続できるため、システムの可用性が高いです。
- メッセージベース通信: 送信するデータは「メッセージ」という単位で扱われます。このメッセージには「メッセージID」が付与されており、受信側のノードはこのIDを見て自分に必要なメッセージかどうかを判断します。特定のノードにデータを送るのではなく、「特定の意味を持つメッセージをネットワークにブロードキャストする」というイメージです。
- 調停(アービトレーション): 複数のノードが同時にメッセージを送信しようとした場合、メッセージIDの数値が小さい(バイナリ表現で先頭に0が多い)方が優先されてバスの使用権を獲得します。これを「IDによる調停」と呼びます。優先度の低いメッセージの送信は、バスが空くまで待機します。これにより、重要なメッセージが優先的に伝送されることが保証されます。
- 強力なエラー検出と通知: CANは、CRC(巡回冗長検査)やACK(肯定応答)、ビットモニタリングなど、複数のメカニズムでエラーを検出します。エラーが発生した場合、エラーフレームを送信してネットワーク上の他のノードにエラー発生を通知します。エラーを検出したノードは、エラーカウンタを増加させ、エラーの状態に応じて通信を一時停止したり、バスから切断したりする機能(Error Confinement)も持ちます。
- 安価な物理層: 一般的に、差動信号を使用するためノイズに強く、ツイストペアケーブル2本(CAN-High, CAN-Low)で通信できます。専用のトランシーバーICが必要ですが、全体のコストを抑えやすいです。
1.3 CANプロトコルの階層
CANプロトコルは、大きく分けて物理層とデータリンク層に分けられます。
1.3.1 物理層 (Physical Layer)
物理層は、実際に電線を介してビット列をやり取りする部分です。
- 信号方式: CANは差動信号を使用します。2本の線(CAN-HighとCAN-Low)の電位差でデータの0と1を表現します。
- リセッシブ (Recessive): CAN-HighとCAN-Lowがほぼ同じ電位(約2.5V付近)のとき。バスがアイドル状態、またはビット1を表します。
- ドミナント (Dominant): CAN-HighがCAN-Lowより高い電位のとき(CAN-Highが約3.5V、CAN-Lowが約1.5V付近)。ビット0を表します。
- 重要な特性として、複数のノードが同時に送信した場合、ドミナント(0)はリセッシブ(1)を上書きします。これがID調停の仕組みの根幹です。
- 配線: 通常、ツイストペアケーブルを使用します。ケーブルの両端には、信号反射を防ぐためのターミネーション抵抗(標準は120Ω)が必要です。ネットワークが分岐する場合、スター型ではなくライン型(またはバス型)のトポロジーが推奨されます。
- 伝送速度 (ボーレート): 最大1Mbps(ケーブル長による)。ケーブル長が長くなるほど、設定できる最大ボーレートは低下します。例えば、1Mbpsでは約40m、500kbpsでは約100m、125kbpsでは約500mが目安とされます。
- トランシーバー: マイクロコントローラーのCANコントローラーが出力するTTLレベルの信号を、CANバスの差動信号レベルに変換し、またその逆を行うICです。SN65HVD23xやTJA1050などが一般的です。
1.3.2 データリンク層 (Data Link Layer)
データリンク層は、メッセージの構造や、エラー検出、調停などのルールを定義する部分です。
- メッセージフレーム: CANでは、データを「フレーム」という単位で送信します。最も一般的なのは「データフレーム」です。
- データフレームの構造:
- SOF (Start Of Frame): 1ビットのドミナント(0)。フレームの開始を示します。
- Arbitration Field (アービトレーションフィールド):
- Identifier (ID): メッセージの識別子。標準フォーマットでは11ビット、拡張フォーマットでは29ビットです。このIDが調停に使用されます(IDが小さいほど優先度が高い)。
- RTR (Remote Transmission Request): 1ビット。データフレームの場合はドミナント(0)、リモートフレームの場合はリセッシブ(1)となります。
- IDE (Identifier Extension): 1ビット。標準フォーマットの場合はドミナント(0)、拡張フォーマットの場合はリセッシブ(1)となります。
- r0, r1: 予約ビット。
- Control Field (コントロールフィールド):
- DLC (Data Length Code): 4ビット。後続のデータフィールドのバイト長(0~8バイト)を示します。
- Data Field (データフィールド): 0~8バイトの実際のデータ。
- CRC Field (CRCフィールド): 15ビットのCRCシーケンスと1ビットのCRCデリミタ。エラー検出用。
- ACK Field (ACKフィールド): 1ビットのACKスロットと1ビットのACKデリミタ。正常に受信したノードがACKスロットをドミナントにして、受信成功を送信元に通知します。
- EOF (End Of Frame): 7ビットのリセッシブ。フレームの終了を示します。
- スタッフィング (Bit Stuffing): 同じ極性のビットが5つ連続すると、意図的に逆極性のビットを1つ挿入します。これは、同期を維持するためと、SOFやEOFなどの固定パターンとの混同を防ぐためです。受信側は、スタッフィングされたビットを自動的に削除します。
- データフレームの構造:
- その他のフレーム:
- リモートフレーム: 特定のIDを持つデータの送信を要求するために使用されます。データフィールドは持ちません。
- エラーフレーム: エラーを検出したノードが他のノードにエラー発生を通知するために送信します。
- オーバーロードフレーム: 受信側のノードが処理能力を超えている場合に、送信を一時停止させるために使用されます。
1.4 ビットタイミングとボーレート
CAN通信のビットタイミングは、正確なデータ伝送のために非常に重要です。各ビットの時間をいくつかのセグメントに分割し、それぞれの時間を適切に設定する必要があります。
- Sync_Seg (Synchronization Segment): 各ビットの開始を示します。常に1Tq(Time Quantum、最小時間単位)で固定されます。エッジによる同期に使用されます。
- Prop_Seg (Propagation Segment): 信号の伝播遅延とバスドライバの遅延を補償するための時間です。
- Phase_Seg1 (Phase Segment 1): サンプルポイントまでの時間調整に使用されます。
- Phase_Seg2 (Phase Segment 2): サンプルポイントの後の時間調整に使用されます。
Prop_Seg
+ Phase_Seg1
+ Phase_Seg2
+ Sync_Seg
= 1ビット時間 となります。
Sync_Seg
は常に1Tqです。他のセグメントは、設定可能な範囲でTqの整数倍で指定します。
1 Tq の時間は、CANクロック周波数とプリスケーラ(分周器)によって決定されます。
Tq = (プリスケーラ値 + 1) / CANクロック周波数
ボーレート(bps)は、1秒間に送信できるビット数であり、ボーレート = 1 / (1ビット時間)
です。
ボーレート = CANクロック周波数 / ((プリスケーラ + 1) * (Sync_Seg + Prop_Seg + Phase_Seg1 + Phase_Seg2))
多くの場合、Sync_Seg
は1Tqなので、
ボーレート = CANクロック周波数 / ((プリスケーラ + 1) * (1 + Prop_Seg + Phase_Seg1 + Phase_Seg2))
となります。
STM32CubeIDEのCAN設定画面では、これらのパラメータ(プリスケーラ、Seg1、Seg2など)を設定することでボーレートを決定します。サンプルポイントの位置(通常、ビット時間の75%~85%付近)も重要で、Phase_Seg1とPhase_Seg2の分割によって決まります。
1.5 調停(アービトレーション)の仕組み
複数のノードが同時にバスが空いていると判断し、メッセージの送信を開始することがあります。この場合、データリンク層のアービトレーションフィールドの送信中に調停が行われます。
- 各ノードは、SOFの後にアービトレーションフィールド(IDなど)を1ビットずつ送信しながら、同時にバス上の信号をモニタリングします。
- CANでは、ドミナント(0)はリセッシブ(1)を上書きします。
- あるノードがリセッシブ(1)を送信したにも関わらず、バス上の信号がドミナント(0)になった場合、それは他のノードがドミナント(0)を送信していることを意味します。このノードは調停に負けたと判断し、送信を中断して受信状態に切り替えます。
- アービトレーションフィールドの最後まで、送信したビットとバス上のビットが一致し続けたノードが調停に勝ち、そのままメッセージの送信を継続します。
- IDは、標準フォーマットで11ビット、拡張フォーマットで29ビットあります。IDの最上位ビットから比較が行われ、最初にドミナント(0)を送信した方が勝ちとなります。つまり、数値としてIDが小さいメッセージほど優先度が高くなります。
この仕組みにより、バス衝突が発生してもデータを破壊することなく、優先度の高いメッセージが確実に送信されることが保証されます。
2. STM32とCAN
STM32マイクロコントローラーには、CAN通信を処理するための専用のハードウェアモジュールが搭載されています。STM32シリーズによって、搭載されているCANモジュールの世代や機能が異なります。
2.1 STM32のCANコントローラーの種類
主に以下の2種類があります。
- bxCAN (basic extended CAN): 多くのSTM32F0, F1, F2, F3, F4, L4などのシリーズに搭載されています。標準CAN 2.0A/Bに対応しています。今回の記事では、このbxCANを中心に解説します。
- FDCAN (Flexible Data-rate CAN): 一部の新しいシリーズ(STM32F7, H7, L5, U5, G0, G4など)に搭載されています。従来のCAN 2.0A/Bに加え、CAN FD (CAN with Flexible Data-rate) に対応しています。CAN FDは、データフィールドの長さを最大64バイトまで拡張し、データフェーズの通信速度をアービトレーションフェーズよりも高速にできるため、より大量のデータを高速に送信できます。
bxCANは、以下の主要なコンポーネントを持ちます。
- 送信メールボックス (Tx Mailboxes): 送信キューのようなもので、通常3つあります。ここに送信したいメッセージを格納し、CANコントローラーがバスが空いたタイミングで送信します。
- 受信FIFO (Rx FIFOs): 受信したメッセージを一時的に保持するバッファです。通常、FIFO0とFIFO1の2つがあります。
- フィルタ (Filters): 受信したメッセージのIDを見て、自分が必要なメッセージだけを受信FIFOに取り込むための機能です。不要なメッセージを破棄することで、CPUの負荷を軽減します。複数のフィルタバンクを設定できます。
2.2 使用するSTM32シリーズの選定
CAN通信の学習には、bxCANを搭載したSTM32F4シリーズなどがおすすめです。入手しやすく、ドキュメントやサンプルコードも豊富です。例えば、Nucleo-F401REやNucleo-F446REといった開発ボードは、比較的安価でCANペリフェラルを搭載しています。
2.3 必要なハードウェア
STM32ボード単体ではCAN通信はできません。CANバスに接続するためには、以下のハードウェアが必要です。
- STM32開発ボード: CANペリフェラルを搭載しているもの(例: Nucleo-F401RE, STM32F4 Discoveryなど)。最低2台あると、送受信の実験ができます。
- CANトランシーバーモジュール: STM32のCAN_TX/CAN_RXピンのTTLレベル信号を、CANバスの差動信号レベルに変換するためのモジュールです。SN65HVD230やTJA1050などのICが搭載された小型モジュールが市販されています。これをSTM32ボードに接続します。
- CANバスケーブル: 2台のCANトランシーバーモジュール間を接続するケーブル。ツイストペアケーブルが推奨されます。
- ターミネーション抵抗: CANバスの両端に120Ωの抵抗が必要です。CANトランシーバーモジュールによっては、基板上にジャンパなどでON/OFFできる場合があります。
- (Optional) USB-CANアナライザー: PCに接続してCANバス上のメッセージをモニタリングしたり、特定のメッセージを送信したりできる便利なツールです。デバッグ時に非常に役立ちます。
ハードウェア接続のイメージは以下のようになります。
[STM32ボードA]
<–> [CANトランシーバーA]
—CAN_H–+–120Ω–
|
|
+–CAN_L–+–120Ω–
|
|
[STM32ボードB]
<–> [CANトランシーバーB]
———————-
3. STM32CubeIDEを使った開発環境構築
STM32CubeIDEは、STM32の開発に必要な様々なツール(コード生成ツール、コンパイラ、デバッガなど)が統合された無償のIDEです。これを使えば、GUIベースの設定でCANペリフェラルの初期化コードを簡単に生成できます。
3.1 STM32CubeIDEのインストール
STMicroelectronicsのウェブサイトからSTM32CubeIDEをダウンロードし、PCにインストールしてください。Java Runtime Environment (JRE)が必要になる場合があります。
3.2 新規プロジェクト作成
- STM32CubeIDEを起動します。
File
->New
->STM32 Project
を選択します。Target Selection
画面で、使用するSTM32マイクロコントローラー(例:STM32F401RETx
)またはボード(例:NUCLEO-F401RE
)を選択します。今回はボードを選択すると、ピン配置などの初期設定が楽になります。Next
をクリック。Project Name
を入力(例:STM32_CAN_Example
)。Project Type
はSTM32Cube
、Targeted Project Type
はEmpty
またはBasic
でOKです。Firmware Package
が正しいバージョンになっているか確認します。Finish
をクリック。デバイスのリソースを取得し、CubeMXの設定画面が開きます。
3.3 CANペリフェラルの設定(CubeMXを使用)
CubeMXの設定画面(Pinout & Configuration)で、CANペリフェラルと関連設定を行います。
- 画面左側の
Connectivity
からCAN1
(または使用するCANペリフェラル) を選択します。 Mode
設定:Activated
: CANペリフェラルを有効にします。Mode
:Normal
: 通常の送受信モード。Loopback
: 内部的に送信信号を受信パスにループバックさせます。外部CANトランシーバーやCANバスは不要で、1台のボードだけで送受信のテストができます。初期のプログラミング確認に便利です。Silent
: 送信は行わず、受信のみを行うモード。バス上の通信をモニタリングする際に使用します。Silent and Loopback
: SilentとLoopbackを組み合わせたモード。外部に出力せず、内部で送信データをモニタリングできます。
- デバッグ時には
Loopback
モードが非常に便利です。外部との通信を行う際はNormal
モードを選択します。
Parameter Settings
設定:Time Settings
: ボーレートを設定する重要な部分です。Prescaler
: CANクロックの分周比です。大きいほどTqが長くなります。Time Quanta for Bit Segment 1
(TS1): Phase_Seg1とProp_Segの合計に対応します。Time Quanta for Bit Segment 2
(TS2): Phase_Seg2に対応します。Time Quanta for Synchronization Segment
: これは常に1です。Resynchronization Jump Width
(SJW): 再同期時の調整幅です。通常は1または最小値に設定します。
- ボーレートの計算: 例えば、CANクロックが42MHz(多くのF4シリーズの場合)で、ボーレートを500kbpsに設定したいとします。
- 1ビット時間 = 1 / 500,000 = 2 µs = 2000 ns
Tq = (Prescaler + 1) / CANクロック周波数
1ビット時間 = Tq * (1 + TS1 + TS2)
- 例えば、
Prescaler = 2
とすると、Tq = (2+1) / 42MHz = 3 / 42,000,000 ≈ 71.4 ns
- 必要なトータルのTq数は、
1ビット時間 / Tq = 2000 ns / 71.4 ns ≈ 28 Tq
1 + TS1 + TS2 = 28
となるように、TS1とTS2を分割します。TS1はTS2より大きい必要があり、サンプルポイントがビット時間の75%~85%に来るように調整するのが一般的です。例えば、TS1=19, TS2=8 とすると、1+19+8 = 28
となり、サンプルポイントは(1+19) / 28 ≈ 0.714
(71.4%) 付近になります。もう少し後が良い場合は、TS1=20, TS2=7 などと調整します。- CubeMXのGUIでは、希望のボーレートを入力すると、可能なプリスケーラ、TS1, TS2の組み合わせをリストアップしてくれる機能があります。それを利用するのが最も簡単です。リストから適切な設定(Total TqやSample Point %などを考慮)を選択します。
- Filter Settings: 受信するメッセージのIDフィルタを設定します。
Filter Bank
: フィルタバンクの番号(0から始まります)。Filter Mode
:Mask
(マスク方式) またはIdentifier List
(リスト方式) を選択します。Mask Mode
: 設定したFilter ID
とFilter Mask ID
を使って、受信するIDの範囲を指定します。Filter Mask ID
のビットが0の箇所は、受信メッセージIDの対応するビットが何であってもマッチすると見なされます。1の箇所は、Filter ID
の対応するビットと一致する必要があります。Identifier List Mode
: 設定した複数のFilter ID
と完全に一致するIDのメッセージだけを受信します。最大で4つのIDを指定できます(スケール設定による)。
Filter Scale
:Single 32-bit
(1つの32ビットフィルタ) またはDual 16-bit
(2つの16ビットフィルタ) を選択します。標準ID(11ビット)の場合は16-bitを、拡張ID(29ビット)の場合は32-bitを選択するのが一般的です。Filter FIFO Assignment
: フィルタにマッチしたメッセージを格納する受信FIFO(FIFO0またはFIFO1)を選択します。Filter Activation
: フィルタを有効にするかどうか。Filter ID
/Filter Mask ID
: 受信したいCAN IDやマスク値を入力します。これらの値はCAN IDの種類(Standard/Extended)、フレームタイプ(Data/Remote)、フィルタモード、スケールによって解釈が変わります。GUI上で設定値を入力すると、CubeMXが内部的に正しいレジスタ値を計算してくれます。- 例: 標準ID 0x123 のデータフレームだけを受信したい場合。
- Mode: Mask
- Scale: Dual 16-bit
- Filter Activation: Enabled
- Filter ID: 0x123 << 5 (標準IDは上位11ビットに格納されるため) + (0 << 4) (RTR=0: データフレーム) + (0 << 3) (IDE=0: 標準ID)
- Filter Mask ID: (0x7FF << 5) + (1 << 4) + (1 << 3) (RTR, IDEはどちらでも良い場合はマスクビットを0にします。特定のフレームタイプ/IDタイプに限定したい場合はマスクビットを1にしてID側の対応ビットと一致させます)
- GUIで入力する場合: 通常、GUIの
Filter ID
やFilter Mask ID
の入力欄には、生のCAN ID値を入力し、Standard/ExtendedやData/Remoteのラジオボタンを選択する形式になっています。上記のビットシフトなどはCubeMXが内部で処理してくれます。例えば、標準ID 0x123 のデータフレームのみを受信する場合、IDに0x123
、TypeにData
, ID TypeにStandard
を選択し、Filter Mask IDは全てマスクビットを1にする(例えば、11ビットIDに対して0x7FF
を入力)と設定します。
GPIO Settings
: CAN_TXとCAN_RXピンが正しく設定されているか確認します。通常、CAN1_TXはPB_9、CAN1_RXはPB_8などに割り当てられています(ボードによる)。NVIC Settings
: CANの割り込みを使用する場合に設定します。CAN RX0 interrupt
: 受信FIFO0にメッセージが格納されたときの割り込み。CAN RX1 interrupt
: 受信FIFO1にメッセージが格納されたときの割り込み。CAN TX interrupts
: 送信関連の割り込み(完了、アボートなど)。CAN Error interrupts
: エラー発生時の割り込み。- 使用したい割り込みにチェックを入れ、
Enabled
にします。優先度も設定できます。
- クロック設定 (
System Core
->RCC
): CANペリフェラルに使用されるクロックソース(通常はAHBクロックから派生)が有効になっているか確認します。
設定が完了したら、画面右上の GENERATE CODE
ボタン(歯車アイコン)をクリックしてコードを生成します。
4. CAN通信のプログラミング(サンプルコード解説)
生成されたコードをベースに、CAN通信の送受信処理を実装します。ここでは、STM32 HALライブラリを使用したプログラミング方法を解説します。
4.1 初期化処理
生成された main.c
ファイルには、MX_CAN1_Init()
(または使用するCANペリフェラル名) 関数が自動生成されています。この関数内で、CANペリフェラルのレジスタ設定やNVIC設定が行われます。
“`c
// main.c (自動生成された部分を抜粋・簡略化)
include “main.h”
include “can.h” // MX_CAN1_Init() などが定義される
CAN_HandleTypeDef hcan1; // CAN1のHALハンドル構造体
void MX_CAN1_Init(void)
{
/ USER CODE BEGIN CAN1_Init 0 /
/ USER CODE END CAN1_Init 0 /
/ USER CODE BEGIN CAN1_Init 1 /
/ USER CODE END CAN1_Init 1 /
hcan1.Instance = CAN1;
hcan1.Init.Prescaler = 3; // CubeMXで設定した値
hcan1.Init.Mode = CAN_MODE_NORMAL; // CubeMXで設定したモード
hcan1.Init.SyncJumpWidth = CAN_SJW_1TQ; // CubeMXで設定した値
hcan1.Init.TimeSeg1 = CAN_BS1_12TQ; // CubeMXで設定した値
hcan1.Init.TimeSeg2 = CAN_BS2_8TQ; // CubeMXで設定した値
hcan1.Init.TimeTriggeredMode = DISABLE;
hcan1.Init.AutoBusOff = DISABLE;
hcan1.Init.AutoWakeup = DISABLE;
hcan1.Init.AutoRetransmission = ENABLE; // 自動再送許可
hcan1.Init.ReceiveFifoLocked = DISABLE;
hcan1.Init.TransmitFifoPriority = DISABLE; // IDによる優先順位
if (HAL_CAN_Init(&hcan1) != HAL_OK)
{
Error_Handler();
}
/ USER CODE BEGIN CAN1_Init 2 /
/ USER CODE END CAN1_Init 2 /
}
“`
HAL_CAN_Init(&hcan1)
関数が、hcan1
構造体で指定されたパラメータに基づいてCANハードウェアを初期化します。
フィルタ設定の追加:
HAL_CAN_Init()
の後、HAL_CAN_ConfigFilter()
関数を呼び出して受信フィルタを設定します。これは自動生成コードの /* USER CODE BEGIN CAN1_Init 2 */
のようなユーザーコード領域に追加するのが良いでしょう。
“`c
// main.c の MX_CAN1_Init() 関数内に追加
/ USER CODE BEGIN CAN1_Init 2 /
CAN_FilterTypeDef sFilterConfig;
// フィルタ設定構造体を初期化
sFilterConfig.FilterBank = 0; // 使用するフィルタバンク番号 (0-13など、MCUによる)
sFilterConfig.FilterMode = CAN_FILTERMODE_IDMASK; // マスクモード
sFilterConfig.FilterScale = CAN_FILTERSCALE_32BIT; // 32ビットスケール (標準IDでも32bitスケールを使うことが多い)
sFilterConfig.FilterFIFOAssignment = CAN_RX_FIFO0; // マッチしたメッセージをRX_FIFO0へ
sFilterConfig.FilterActivation = ENABLE; // フィルタを有効にする
// 受信したい標準ID: 0x123 のデータフレーム
uint32_t std_id_to_receive = 0x123;
// フィルタIDレジスタの設定 (32bitスケールの場合)
// 標準IDは上位11ビット。下位16ビットは未使用。
// IDE, RTRビットも考慮してフィルタを設定する
sFilterConfig.FilterIdHigh = (std_id_to_receive << 5) >> 16; // 標準IDを上位11ビットへシフトし、上位16ビットを取得
sFilterConfig.FilterIdLow = (std_id_to_receive << 5); // 標準IDを上位11ビットへシフトし、下位16ビットを取得 (下位5ビットは予約領域)
// フィルタマスクIDレジスタの設定 (32bitスケールの場合)
// 標準IDの11ビットだけを比較対象にする (マスクビットを1)
// IDEとRTRは比較対象から外す (マスクビットを0) – 任意のフレームタイプを受信する場合
// もし標準データフレームのみに限定したい場合は、IDEとRTRのマスクビットを1にし、FilterIdHigh/Low側でIDE=0, RTR=0を設定する
sFilterConfig.FilterMaskIdHigh = (0x7FF << 5) >> 16; // 標準ID 11ビットに対応するマスク (全て1)
sFilterConfig.FilterMaskIdLow = (0x7FF << 5); // 標準ID 11ビットに対応するマスク (全て1)
/
// 標準データフレーム ID=0x123 のみを受信したい場合の例 (IDE=0, RTR=0を比較)
sFilterConfig.FilterMode = CAN_FILTERMODE_IDMASK;
sFilterConfig.FilterScale = CAN_FILTERSCALE_32BIT;
sFilterConfig.FilterFIFOAssignment = CAN_RX_FIFO0;
sFilterConfig.FilterActivation = ENABLE;
sFilterConfig.FilterIdHigh = (std_id_to_receive << 5 | 0 / IDE=0 / | 0 / RTR=0 /) >> 16;
sFilterConfig.FilterIdLow = (std_id_to_receive << 5 | 0 / IDE=0 / | 0 / RTR=0 /);
sFilterConfig.FilterMaskIdHigh = (0x7FF << 5 | 1 / IDEマスク / | 1 / RTRマスク /) >> 16; // IDE, RTRも比較対象にする
sFilterConfig.FilterMaskIdLow = (0x7FF << 5 | 1 / IDEマスク / | 1 / RTRマスク /);
/
if (HAL_CAN_ConfigFilter(&hcan1, &sFilterConfig) != HAL_OK)
{
Error_Handler();
}
/ USER CODE END CAN1_Init 2 /
“`
CAN通信の開始:
フィルタ設定の後、HAL_CAN_Start()
関数を呼び出してCANペリフェラルを動作状態に移行させます。
“`c
// main.c の MX_CAN1_Init() 関数の最後、または main() 関数の初期化処理の後に追加
/ USER CODE BEGIN CAN1_Init 3 /
// CAN通信を開始
if (HAL_CAN_Start(&hcan1) != HAL_OK)
{
Error_Handler();
}
/ USER CODE END CAN1_Init 3 /
“`
これで、CANペリフェラルの基本的な初期化は完了です。この後、送信または受信の処理を実装します。
4.2 送信処理(ポーリング方式)
定期的にデータを送信する場合など、シンプルな場合はポーリング方式で送信キューの空きを確認しながら送信します。
“`c
// main.c の main() 関数の無限ループ (while(1)) 内に追加
// 送信用の構造体とデータを用意
CAN_TxHeaderTypeDef TxHeader;
uint8_t TxData[8];
uint32_t TxMailbox; // 送信に使用されたメールボックス番号を格納
// 送信メッセージヘッダーの設定
TxHeader.StdId = 0x123; // 送信する標準ID (11ビット)
TxHeader.ExtId = 0x00; // 拡張IDは使用しない
TxHeader.IDE = CAN_ID_STD; // 標準IDフォーマット
TxHeader.RTR = CAN_RTR_DATA; // データフレーム
TxHeader.DLC = 8; // データ長 8バイト
TxHeader.TransmitGlobalTime = DISABLE; // グローバル時間同期は使用しない
// 送信データの準備 (例: カウンタ値を送信)
static uint8_t counter = 0;
TxData[0] = counter++;
TxData[1] = 0xAA;
TxData[2] = 0xBB;
TxData[3] = 0xCC;
TxData[4] = 0xDD;
TxData[5] = 0xEE;
TxData[6] = 0xFF;
TxData[7] = 0x00;
// CAN送信要求
// HAL_CAN_AddTxMessage() は、空いている送信メールボックスにメッセージを格納し、送信を要求します。
// 成功すると HAL_OK を返し、使用したメールボックス番号を TxMailbox に格納します。
// 空いているメールボックスがない場合は HAL_ERROR を返します。
if (HAL_CAN_AddTxMessage(&hcan1, &TxHeader, TxData, &TxMailbox) != HAL_OK)
{
// 送信キューが満杯など、送信要求に失敗した場合の処理
// 例: エラーLED点灯、再試行など
// Error_Handler(); // 例として
} else {
// 送信要求が受け付けられた場合
// 送信自体はバックグラウンドでハードウェアが行います。
// TxMailbox には、送信に使用されたメールボックス番号が入ります。
// 送信完了を待つ必要があれば、HAL_CAN_IsTxMessagePending() などで確認できますが、
// シンプルなポーリング送信では通常不要です。
}
// 送信間隔を空ける (CANバスを占有しないように)
HAL_Delay(100); // 100ms待機
“`
このコードを while(1)
ループ内に入れると、100msごとにカウンタ値を格納したCANメッセージが送信されます。送信キュー(メールボックス)がいっぱいの場合は送信できません。
4.3 受信処理(ポーリング方式)
受信FIFOにメッセージが来ているか定期的に確認し、メッセージがあれば読み出します。
“`c
// main.c の main() 関数の無限ループ (while(1)) 内に追加
// 受信用の構造体とデータを用意
CAN_RxHeaderTypeDef RxHeader;
uint8_t RxData[8];
// 受信FIFO0にメッセージがあるか確認
// HAL_CAN_GetRxFifoFillLevel(&hcan1, CAN_RX_FIFO0) でFIFOのメッセージ数を確認できます。
// HAL_CAN_GetRxMessage() は、メッセージがあれば読み出し、HAL_OK を返します。
if (HAL_CAN_GetRxMessage(&hcan1, CAN_RX_FIFO0, &RxHeader, RxData) == HAL_OK)
{
// 受信成功した場合の処理
// RxHeader には受信メッセージのID, DLC, IDE, RTRなどが入ります。
// RxData には受信データが入ります。
// 受信したメッセージの情報を表示(例: シリアルポート経由)
// printf(“CAN Message Received: ID=0x%lX, DLC=%lu, Data: “, RxHeader.StdId, RxHeader.DLC);
// for (int i = 0; i < RxHeader.DLC; i++) {
// printf(“%02X “, RxData[i]);
// }
// printf(“\r\n”);
// 受信データを使った具体的な処理をここに記述
// 例: 受信したカウンタ値をLED表示など
if (RxHeader.StdId == 0x123 && RxHeader.DLC >= 1) {
uint8_t received_counter = RxData[0];
// 例: received_counterの値に応じて何かする
// 例えば、HAL_GPIO_TogglePin(LD2_GPIO_Port, LD2_Pin); など
}
}
“`
このコードを while(1)
ループ内に入れると、メインループが回るたびに受信FIFO0を確認し、メッセージがあれば読み出して処理します。
ポーリング方式の欠点: メインループが他の処理で忙しい場合、CANメッセージの取りこぼしが発生する可能性があります。リアルタイム性や応答性を重視する場合は、割り込み方式を使用するのが一般的です。
4.4 割り込み処理
CAN受信割り込みを使用すると、メッセージが受信FIFOに格納されたタイミングでCPUが割り込み処理を実行するため、迅速な対応が可能です。
- CubeMXでの設定: 前述のCubeMX設定で、
NVIC Settings
のCAN RX0 interrupt
(または RX1) をEnabled
にします。 - 割り込みハンドラの確認:
stm32fxxx_it.c
ファイルに、CANの割り込みハンドラ関数が自動生成されているか確認します。例えば、CAN1_RX0_IRQHandler()
のようになります。この関数内で、HALライブラリの共通割り込みハンドラHAL_CAN_IRQHandler()
が呼び出されるようになっています。 - コールバック関数の実装: HALライブラリは、特定のイベント(受信完了、送信完了など)が発生した際に呼び出される「コールバック関数」の仕組みを提供しています。受信FIFO0にメッセージが格納されたときに呼び出されるコールバック関数は
HAL_CAN_RxFifo0MsgPendingCallback()
です。この関数をmain.c
などに自分で定義して実装します。
“`c
// main.c にコールバック関数を実装
// 割り込みで受信したメッセージを処理します。
// この関数内で時間のかかる処理を行うと、他の割り込みに影響を与える可能性があるので注意が必要です。
// 受信データはすぐに読み出して、メインループなどで詳細な処理を行うのが一般的です。
CAN_RxHeaderTypeDef RxHeader_IT; // 割り込み用受信ヘッダー
uint8_t RxData_IT[8]; // 割り込み用受信データ
/
* @brief Rx FIFO 0 message pending callback.
* @param hcan: pointer to a CAN_HandleTypeDef structure that contains
* the configuration information for the specified CAN.
* @retval None
/
void HAL_CAN_RxFifo0MsgPendingCallback(CAN_HandleTypeDef hcan)
{
// 受信FIFO0からメッセージを読み出す
if (HAL_CAN_GetRxMessage(hcan, CAN_RX_FIFO0, &RxHeader_IT, RxData_IT) == HAL_OK)
{
// 受信成功した場合の処理
// この関数内では、必要最低限の処理(例: データ読み出し、フラグセット、キューへの追加)に留めるのが望ましいです。
// 重たい処理はメインループで行います。
// 例: 受信したメッセージIDとデータを保持しておき、メインループで処理するフラグを立てる
// static int received_message_count = 0;
// printf("IT: Received CAN message ID 0x%lX\r\n", RxHeader_IT.StdId); // デバッグ出力は割り込み関数内では避けるべき
// 受信データをどこかにコピーして、メインループで処理するためのフラグを立てる、などの工夫が必要
// 例えば、グローバル変数やキューにデータを格納し、メインループでその変数/キューを監視する
// volatile uint8_t can_rx_flag = 1; // mainループでこのフラグを確認
// memcpy(global_rx_data, RxData_IT, RxHeader_IT.DLC); // グローバル変数にコピー
// global_rx_header = RxHeader_IT; // ヘッダーもコピー
// 簡単な例:特定のIDのメッセージが来たらLEDをトグル
if (RxHeader_IT.StdId == 0x123)
{
// HAL_GPIO_TogglePin(LD2_GPIO_Port, LD2_Pin); // 割り込み内でHAL関数を使うのは注意が必要な場合あり
// もっと安全な方法:グローバル変数フラグをセットするなど
// volatile uint8_t received_toggle_command = 1; // mainループで監視
}
}
}
“`
メインループ (main.c
の while(1)
) では、設定したフラグなどを確認して、割り込みで受信したデータの詳細な処理を行います。
“`c
// main.c の main() 関数の無限ループ (while(1)) 内
// 割り込みでセットされたフラグなどを監視し、処理を行う
// 例: received_toggle_command フラグがセットされたらLEDをトグル
// extern volatile uint8_t received_toggle_command; // グローバル変数として宣言されているとする
// if (received_toggle_command) {
// HAL_GPIO_TogglePin(LD2_GPIO_Port, LD2_Pin);
// received_toggle_command = 0; // フラグをクリア
// }
// 例:グローバル変数 global_rx_flag がセットされたら、global_rx_data を処理
// extern volatile uint8_t can_rx_flag;
// extern uint8_t global_rx_data[8];
// extern CAN_RxHeaderTypeDef global_rx_header;
// if (can_rx_flag) {
// can_rx_flag = 0; // フラグクリア
// // global_rx_header と global_rx_data を使って処理
// printf(“Main: Processed CAN message ID 0x%lX, DLC=%lu\r\n”, global_rx_header.StdId, global_rx_header.DLC);
// // … データの詳細処理 …
// }
// ポーリング送信はそのまま行う(割り込み受信と組み合わせる場合)
// if (HAL_CAN_AddTxMessage(&hcan1, &TxHeader, TxData, &TxMailbox) != HAL_OK) { / … / }
// HAL_Delay(100);
// または、送信完了割り込みも使って、送信要求だけをポーリングする
// (ここでは割愛しますが、HAL_CAN_TxMailbox0CompleteCallback などを実装します)
“`
割り込みを使った受信処理は、システムの応答性を高める上で非常に有効です。
4.5 サンプルコード全体像(Loopbackモードで自己送受信)
ここでは、外部ハードウェア(CANトランシーバー、もう一台のボード)を使わずに、1台のSTM32ボードだけでCAN通信の送受信を確認できる「Loopbackモード」を使ったサンプルコードの全体像を示します。
前提:
* STM32CubeIDEでプロジェクトを作成済み
* 使用するSTM32ボードに合わせてCAN1 (または適切なCANx) を有効化
* Modeを Loopback
に設定
* 適切なボーレート(例えば500kbps)に設定
* CAN RX0 interrupt を Enable に設定(割り込み受信を使用する場合)
* コード生成済み
main.c
“`c
/ USER CODE BEGIN Header /
/**
- @file : main.c
- @brief : Main program body
- @attention
* - Copyright (c) 2023 STMicroelectronics.
- All rights reserved.
* - This software is licensed under terms that can be found in the LICENSE file
- in the root directory of this software component.
- If no LICENSE file comes with this software, it is provided AS-IS.
*
/
/ USER CODE END Header */
/ Includes ——————————————————————/
include “main.h”
include “can.h” // CubeMXで生成される can.h
include “usart.h” // デバッグ用にUSARTを使う場合 (CubeMXで設定が必要)
include // printfを使うため
/ Private includes ———————————————————-/
/ USER CODE BEGIN Includes /
/ USER CODE END Includes /
/ Private typedef ———————————————————–/
/ USER CODE BEGIN PTD /
/ USER CODE END PTD /
/ Private define ————————————————————/
/ USER CODE BEGIN PD /
// デバッグ用 printf のリダイレクト (USART使用時)
ifdef GNUC
define PUTCHAR_PROTOTYPE int __io_putchar(int ch)
else
define PUTCHAR_PROTOTYPE int fputc(int ch, FILE *f)
endif // GNUC
PUTCHAR_PROTOTYPE
{
// Put a character to the USART
HAL_UART_Transmit(&huart2, (uint8_t )&ch, 1, HAL_MAX_DELAY); // huart2 はCubeMXで設定したUARTハンドラ名
return ch;
}
/ USER CODE END PD */
/ Private macro ————————————————————-/
/ USER CODE BEGIN PM /
/ USER CODE END PM /
/ Private variables ———————————————————/
CAN_HandleTypeDef hcan1; // CubeMXで生成
UART_HandleTypeDef huart2; // CubeMXで生成 (デバッグ用)
/ USER CODE BEGIN PV /
// 割り込み受信用のバッファとヘッダー
CAN_RxHeaderTypeDef RxHeader_IT;
uint8_t RxData_IT[8];
volatile uint8_t rx_message_pending = 0; // 受信メッセージをメインループで処理するためのフラグ
// 送信用ヘッダーとデータ (メインループで使用)
CAN_TxHeaderTypeDef TxHeader;
uint8_t TxData[8];
uint32_t TxMailbox; // 送信に使用されたメールボックス番号を格納
/ USER CODE END PV /
/ Private function prototypes ———————————————–/
void SystemClock_Config(void);
/ USER CODE BEGIN PFP /
// CubeMXで生成される CAN, USART 初期化関数を extern 宣言
extern void MX_CAN1_Init(void);
extern void MX_USART2_UART_Init(void); // デバッグ用にUSART2を使う場合
/ USER CODE END PFP /
/ Private user code ———————————————————/
/ USER CODE BEGIN 0 /
/
* @brief Rx FIFO 0 message pending callback.
* @param hcan: pointer to a CAN_HandleTypeDef structure that contains
* the configuration information for the specified CAN.
* @retval None
/
void HAL_CAN_RxFifo0MsgPendingCallback(CAN_HandleTypeDef hcan)
{
// 受信FIFO0からメッセージを読み出す
if (HAL_CAN_GetRxMessage(hcan, CAN_RX_FIFO0, &RxHeader_IT, RxData_IT) == HAL_OK)
{
// 受信したメッセージ情報を一時的に保持し、メインループで処理するためのフラグを立てる
rx_message_pending = 1; // フラグセット
// ここでデータやヘッダーをグローバル変数などにコピーしても良いが、簡単な例としてフラグだけ
// 実際のアプリケーションでは、ここでデータをキューなどにコピーして、割り込み処理を短く終えるのが一般的
}
}
/ USER CODE END 0 /
/
* @brief The application entry point.
* @retval int
/
int main(void)
{
/ USER CODE BEGIN 1 */
/ USER CODE END 1 /
/ MCU Configuration——————————————————–/
/ Reset of all peripherals, Initializes the Flash interface and the Systick. /
HAL_Init();
/ USER CODE BEGIN Init /
/ USER CODE END Init /
/ Configure the system clock /
SystemClock_Config();
/ USER CODE BEGIN SysInit /
/ USER CODE END SysInit /
/ Initialize all configured peripherals /
MX_GPIO_Init(); // CubeMXで生成されるGPIO初期化
MX_CAN1_Init(); // CubeMXで生成されるCAN1初期化 (フィルタ設定も含む)
MX_USART2_UART_Init(); // CubeMXで生成されるUSART初期化 (デバッグ用)
/ USER CODE BEGIN 2 /
printf(“STM32 CAN Loopback Test Start\r\n”);
// CAN受信割り込みを有効化
// Loopbackモードでは、送信要求後すぐに受信割り込みが発生します。
if (HAL_CAN_ActivateNotification(&hcan1, CAN_IT_RX_FIFO0_MSG_PENDING | CAN_IT_TX_MAILBOX_EMPTY, 0) != HAL_OK) // Txも有効化してみる
{
Error_Handler();
}
// 送信メッセージヘッダーの準備 (whileループの外で一度だけ設定)
TxHeader.StdId = 0x123; // 送信する標準ID
TxHeader.ExtId = 0x00; // 拡張IDは使用しない
TxHeader.IDE = CAN_ID_STD; // 標準IDフォーマット
TxHeader.RTR = CAN_RTR_DATA; // データフレーム
TxHeader.DLC = 8; // データ長 8バイト
TxHeader.TransmitGlobalTime = DISABLE;
/ USER CODE END 2 /
/ Infinite loop /
/ USER CODE BEGIN WHILE /
uint8_t tx_counter = 0;
while (1)
{
/ USER CODE END WHILE /
/* USER CODE BEGIN 3 */
// 送信データの準備 (カウンタ値を格納)
TxData[0] = tx_counter++;
TxData[1] = 0x11;
TxData[2] = 0x22;
TxData[3] = 0x33;
TxData[4] = 0x44;
TxData[5] = 0x55;
TxData[6] = 0x66;
TxData[7] = 0x77;
// CAN送信要求
// HAL_CAN_AddTxMessage() は、空いているメールボックスにメッセージを格納し、送信準備をします。
// Loopbackモードでは、送信されると同時に受信バッファに入ります。
if (HAL_CAN_AddTxMessage(&hcan1, &TxHeader, TxData, &TxMailbox) != HAL_OK)
{
// 送信要求失敗 (例: 送信メールボックスが全て使用中)
printf("CAN Tx Add Failed\r\n");
}
else
{
// 送信要求成功
// printf("CAN Tx Add OK, Mailbox: %lu\r\n", TxMailbox); // デバッグ用
}
// 送信後、少し待機 (Loopbackモードなので即座に受信割り込みが発生するはず)
HAL_Delay(500); // 500ms間隔で送信
// 割り込みで受信メッセージがあるかフラグを確認
if (rx_message_pending)
{
rx_message_pending = 0; // フラグをクリア
// 受信したメッセージ情報を表示 (割り込みから読み出したグローバル変数 RxHeader_IT, RxData_IT を使用)
printf("CAN Rx IT: ID=0x%lX, DLC=%lu, Data: ", RxHeader_IT.StdId, RxHeader_IT.DLC);
for (int i = 0; i < RxHeader_IT.DLC; i++) {
printf("%02X ", RxData_IT[i]);
}
printf("\r\n");
// 受信データを使った処理 (例: RxData_IT[0] の値で何かする)
// if (RxHeader_IT.StdId == 0x123) {
// printf("Received counter value: %u\r\n", RxData_IT[0]);
// }
}
}
/ USER CODE END 3 /
}
/*
* @brief System Clock Configuration
* @retval None
/
void SystemClock_Config(void)
{
// … (CubeMXで生成されるシステムクロック設定) …
}
/ USER CODE BEGIN 4 /
/ USER CODE END 4 /
/
* @brief This function is executed in case of error occurrence.
* @retval None
/
void Error_Handler(void)
{
/ USER CODE BEGIN Error_Handler_Debug /
/ User can add his own implementation to report the HAL error return state /
printf(“Error_Handler called!\r\n”);
__disable_irq();
while (1)
{
// エラー発生時は無限ループ
HAL_GPIO_TogglePin(LD2_GPIO_Port, LD2_Pin); // エラーランプ点滅など
HAL_Delay(200);
}
/ USER CODE END Error_Handler_Debug */
}
“`
CubeMXでのCAN1初期化設定の補足 (MX_CAN1_Init内にあるフィルタ設定)
“`c
// … MX_CAN1_Init() の前半 …
if (HAL_CAN_Init(&hcan1) != HAL_OK)
{
Error_Handler();
}
/ USER CODE BEGIN CAN1_Init 2 /
// CAN 受信フィルタ設定
CAN_FilterTypeDef sFilterConfig;
sFilterConfig.FilterBank = 0; // 使用するフィルタバンク (例: 0)
sFilterConfig.FilterMode = CAN_FILTERMODE_IDMASK; // マスクモード
sFilterConfig.FilterScale = CAN_FILTERSCALE_32BIT;// 32ビットスケール
sFilterConfig.FilterFIFOAssignment = CAN_RX_FIFO0;// マッチしたメッセージをRX_FIFO0へ
sFilterConfig.FilterActivation = ENABLE; // フィルタ有効化
// 受信したい標準ID: 0x123 のデータフレームのみ
// 32bitスケール、マスクモード、標準IDの場合のフィルタ設定例
// FilterIdHigh/Low には、受信したいID, IDE, RTR を含めた値を設定
// FilterMaskIdHigh/Low には、比較したいビットを1、無視するビットを0としてマスクを設定
// 標準ID (11bit) は上位11bitに配置される
// 32bitフィルタレジスタの構造イメージ:
// ID[28:0] IDE RTR 0
// 標準IDの場合: ID[10:0]はID値, ID[28:11]は0. IDE, RTR, 0
// 受信したいID 0x123 (標準), データフレーム (RTR=0), 標準IDフォーマット (IDE=0)
uint32_t filter_id = (0x123 << 21); // 標準IDをID[28:18]に配置 (bxCANのレジスタマップによる)
// F4シリーズ bxCANの場合、標準IDは STDID[10:0] = FilterIdHigh[15:5], FilterIdLow[15:5] ではない。
// Filter registers (CAN_FxR1, CAN_FxR2) を直接見ると分かりやすい。
// 標準IDの場合、11bit IDは上位11bitに格納され、下位21bitは IDE, RTR, 予約領域などになる。
// ID[10:0] IDE RTR 0 … とは少し異なる。HALの構造体に合わせた設定値を見つける必要がある。
// STM32F4 bxCAN Standard ID Filter (Mask Mode, 32bit Scale) の場合:
// CAN_FxR1/FxR2 レジスタは 32bit 値として扱われ、
// STDID [10:0] は FxR1[31:21] に配置される (FilterIdHigh/Low に分けると FxR1 が High Part)
// IDE は FxR1[20], RTR は FxR1[19] に配置される。
// FxR2 はマスクレジスタとして、対応ビットを1にすると比較対象、0にすると無視となる。
uint32_t id_to_match = 0x123; // 受信したい標準ID
// 受信したいメッセージを組み立てる (標準ID=0x123, データフレーム, 標準フォーマット)
uint32_t ideal_message = (id_to_match << 21) // 標準ID 0x123 を上位に配置
| (0 << 20) // IDE=0 (標準フォーマット)
| (0 << 19); // RTR=0 (データフレーム)
// 下位19bitは無視するか、特定のパターンにマッチさせるか(通常無視)
// フィルタIDレジスタの値 (32bit)
sFilterConfig.FilterIdHigh = ideal_message >> 16; // 上位16bit
sFilterConfig.FilterIdLow = ideal_message & 0xFFFF; // 下位16bit
// フィルタマスクIDレジスタの値 (32bit)
// 標準ID (11bit) 部分 (上位11bit) は比較対象 -> マスクビットを1に
// IDE (1bit) は比較対象 -> マスクビットを1に
// RTR (1bit) は比較対象 -> マスクビットを1に
// その他 (下位19bit) は無視 -> マスクビットを0に
uint32_t mask_value = (0x7FF << 21) // 標準ID 11bit のマスク
| (1 << 20) // IDE 1bit のマスク
| (1 << 19); // RTR 1bit のマスク
// 下位19bitのマスクは0 (無視)
sFilterConfig.FilterMaskIdHigh = mask_value >> 16;
sFilterConfig.FilterMaskIdLow = mask_value & 0xFFFF;
if (HAL_CAN_ConfigFilter(&hcan1, &sFilterConfig) != HAL_OK)
{
Error_Handler();
}
/ USER CODE END CAN1_Init 2 /
/ USER CODE BEGIN CAN1_Init 3 /
// CAN通信を開始
if (HAL_CAN_Start(&hcan1) != HAL_OK)
{
Error_Handler();
}
/ USER CODE END CAN1_Init 3 /
}
“`
フィルタ設定の補足: STM32のCANフィルタレジスタの構造は少し複雑で、標準IDと拡張IDでIDが配置される位置が異なります。また、HALライブラリの CAN_FilterTypeDef
構造体の FilterIdHigh
, FilterIdLow
, FilterMaskIdHigh
, FilterMaskIdLow
に設定する値は、選択したFilterScale
(16bit or 32bit) とFilterMode
(Mask or List) によって意味合いが変わります。
- 32-bit Scale, Mask Mode:
FilterIdHigh
/Low
には、受信したい理想的なメッセージの32ビットパターンを設定します。FilterMaskIdHigh
/Low
には、比較したいビット位置を1、無視したいビット位置を0とした32ビットパターンを設定します。標準IDの場合、IDはビット21からビット31に配置され、IDEはビット20、RTRはビット19に配置されます。上記コードではこの配置を考慮しています。 - 32-bit Scale, Identifier List Mode:
FilterIdHigh
/Low
のペアで1つの32ビットIDパターンを指定します。設定した複数のIDパターンと完全に一致するメッセージだけを受信します。 - 16-bit Scale, Mask Mode:
FilterIdHigh
/Low
の各16ビットで、それぞれ1つずつのフィルタパターンを設定できます。標準ID(11ビット)の場合、IDはビット5からビット15に配置され、IDEはビット4、RTRはビット3に配置されます。FilterMaskIdHigh
/Low
には、対応する16ビットパターンのマスクを設定します。 - 16-bit Scale, Identifier List Mode:
FilterIdHigh
/Low
の各16ビットで、それぞれ2つずつの計4つの16ビットIDパターンを指定できます。標準ID(11ビット)に対して使用できます。
CubeMXのGUIでIDやマスク値を入力する際は、生のID値やマスク値を入力するだけで、内部的にこれらのレジスタ配置に合わせて値を計算してくれるため、HALの構造体を直接操作するより簡単です。しかし、HALドライバ関数を理解するためには、これらの構造やレジスタ配置をある程度理解しておくことが役立ちます。
このLoopbackモードのサンプルコードをビルドしてボードに書き込むと、500msごとにCANメッセージ(ID 0x123、データ0x00~0x77、カウンタ値)が送信要求され、それがすぐに受信され、シリアルポート(USART2など)に受信データが表示されるはずです。
5. デバッグとトラブルシューティング
CAN通信は物理層やタイミングが重要であるため、初心者にはトラブルシューティングが難しい場合があります。よくある問題とその対処法を紹介します。
5.1 よくあるエラー
- 初期化失敗:
HAL_CAN_Init()
やHAL_CAN_Start()
がHAL_OK
を返さない。- CubeMXの設定ミス(クロック設定、GPIO設定、モード設定など)
- 必要なペリフェラルクロックが有効になっていない
- CANインスタンス(hcan1など)が正しく初期化されていない
- メッセージが送信されない:
HAL_CAN_AddTxMessage()
がHAL_ERROR
を返す、または返すがバス上に信号が出ない。HAL_CAN_Start()
が呼び出されていない- 送信メールボックスが全て使用中(送信レートが速すぎる、または CANバスがStuck Dominantなど通信不可状態)
- CANトランシーバーが正しく接続されていない、または故障
- CAN_TXピンのGPIO設定ミス(Alternate Function設定など)
- LoopbackモードやSilentモードになっていないか確認
- メッセージが受信されない:
HAL_CAN_GetRxMessage()
がHAL_ERROR
を返す、または受信割り込みが発生しない。HAL_CAN_Start()
が呼び出されていない- CANトランシーバーが正しく接続されていない、または故障
- CAN_RXピンのGPIO設定ミス(Alternate Function設定など)
- フィルタ設定ミス: これが最もよくある原因の一つです。設定したフィルタが、受信したいメッセージのIDやフレームタイプにマッチしていない。フィルタが無効になっている。
- 受信FIFOがいっぱいになっている(処理が追いついていない、割り込み処理が実装されていない/遅い)
- 割り込みを使用する場合、NVIC設定ミス(割り込みが有効になっていない、優先度が低すぎる)
- LoopbackモードやSilentモードになっていないか確認(外部通信の場合)
- バスエラー (Bus Error): CANコントローラーがエラーフレームを送信している、またはエラーカウンタが増加している。
- ボーレートの不一致: ネットワーク上の全てのノードでボーレートが一致している必要があります。
- ターミネーション抵抗の不足または過多: バス両端に合計約60Ωになるように適切に配置されているか確認(通常120Ω x 2)。分岐点でのターミネーションはNGです。
- 配線問題: ケーブルの断線、ショート、CAN_H/CAN_Lの誤接続、長すぎるケーブル、質の悪いケーブル。
- ノイズ: 特に工場環境など、強い電磁ノイズの影響を受ける場合。
- Stuck Dominant: いずれかのノードのCAN_LラインがGNDにショートしているなどで、バスが常にドミナント状態になっている。CANアナライザーで波形を見ると確認できます。
- 他のノードの故障: バス上にエラーを頻繁に送信するノードがいる。
5.2 デバッグツールの活用
- STM32CubeIDEデバッガー: ブレークポイントを設定してコードの実行を一時停止させ、変数の値(RxHeader, RxDataなど)やCANハンドラ構造体 (
hcan1
) の状態(Tx/Rxステータス、エラーカウンタなど)を確認します。 - printfデバッグ: USART経由でPCにデバッグメッセージを出力します。特に割り込み処理など、デバッガーで止めにくい場所での状況把握に役立ちます。ただし、割り込みハンドラ内でのprintfは処理を遅延させるため、簡単なフラグセットなどに留め、printf自体はメインループで行うべきです。
- CANアナライザー: 最も強力なツールです。
- バスモニタリング: CANバス上を流れる全てのメッセージ(ID, データ, DLCなど)をリアルタイムで表示できます。
- エラー検出: エラーフレームの発生状況、エラーカウンタの状態などを確認できます。
- 波形観測 (一部機能): CAN_H/CAN_Lの波形をオシロスコープのように確認できるアナライザーもあります。これにより、信号レベルの問題やノイズの影響を判断できます。
- 送信機能: 特定のIDやデータのメッセージをPCから送信し、受信側の反応を確認できます。
5.3 フィルタ設定の確認
フィルタ設定ミスはよくある原因です。
* 設定したFilterIdHigh
/Low
、FilterMaskIdHigh
/Low
が、受信したいメッセージのID、IDE、RTRと正しくマッチするようになっているか再確認します。特に標準IDと拡張IDでレジスタ内のビット配置が異なる点に注意が必要です。
* FilterActivation
がENABLE
になっているか確認します。
* FilterFIFOAssignment
が、受信割り込みを設定したFIFO(RX_FIFO0またはRX_FIFO1)と一致しているか確認します。
* フィルタを全く設定しない場合(CANコントローラーによってはデフォルトで全てのメッセージを受け取る設定になっていることもあれば、フィルタ設定しないと何も受け取らない設定になっていることもあります)、意図せず不要なメッセージを受信していないか確認します。
フィルタ設定の最も確実な方法は、CubeMXのGUIで希望するID、IDタイプ、フレームタイプ、フィルタモード、スケールなどを指定し、生成されたコードを確認するか、GUIが示すレジスタ値を直接確認することです。
6. 応用例
CAN通信の基本的な送受信ができるようになったら、さらに機能を拡張してみましょう。
- 複数のメッセージIDの処理: 複数のIDのメッセージを受信したい場合は、フィルタ設定を複数追加するか、リストモードを使用します。受信割り込みハンドラまたはメインループで、受信したメッセージの
RxHeader.StdId
またはRxHeader.ExtId
を確認して、IDごとに異なる処理を行います。 - データ形式の変換: CANメッセージのデータフィールドはバイト配列 (
uint8_t
) です。int
型やfloat
型などの数値データを送受信したい場合は、それらをバイト列に分解・組み立てる処理が必要です。例えば、union
構造体やmemcpy
関数を使って変換できます。 - CANopen, J1939などの上位プロトコル: CANバス上で動作する様々な上位プロトコルがあります。これらはメッセージIDやデータ形式に特定のルールを定めており、相互運用性を高めています。これらのプロトコルに対応するには、それぞれの仕様に沿ったメッセージの送受信処理を実装する必要があります。これらはより複雑なトピックですが、実際のシステム開発ではよく使用されます。
- CAN-FDへの対応: より高速な通信や大容量データ転送が必要な場合は、CAN-FDをサポートするFDCANペリフェラルを搭載したSTM32を使用します。プログラミングモデルはbxCANと似ていますが、FDCAN専用のHAL関数や設定が必要になります。
7. まとめ
この記事では、STM32を使ったCAN通信の基本的な概念から、開発環境構築、そしてポーリングおよび割り込みを使った送受信のサンプルコードまでを詳細に解説しました。
CAN通信は、その信頼性とリアルタイム性から多くの組み込みシステムで重要な役割を担っています。最初は複雑に感じるかもしれませんが、CANのフレーム構造、調停、そしてSTM32のCANペリフェラルの仕組み(送信メールボックス、受信FIFO、フィルタ)を理解すれば、HALライブラリを使って比較的容易に実装できることがお分かりいただけたかと思います。
特に、CubeMXを使った初期設定とコード生成は、煩雑なレジスタ設定から解放してくれる強力な機能です。また、Loopbackモードでのテストは、外部ハードウェアなしにプログラミングの基本的な動作確認ができるため、開発の最初のステップとして非常に有効です。
もし通信がうまくいかない場合は、ボーレート、ターミネーション抵抗、配線、そしてフィルタ設定を重点的に確認してみてください。CANアナライザーなどのツールもデバッグの強い味方になります。
この記事が、あなたがSTM32でCAN通信をマスターするための一助となれば幸いです。次は、実際のシステムにCAN通信を組み込んだり、より高度な機能(エラー処理、CAN-FD、上位プロトコルなど)に挑戦したりしてみてください。
CAN通信の世界は奥深く、学ぶべきことはまだたくさんありますが、一歩ずつ確実に進んでいきましょう!
8. 参考資料
- STMicroelectronics 公式ウェブサイト (STM32製品情報、データシート、リファレンスマニュアル)
- STM32CubeIDE および STM32CubeMX ドキュメント
- STM32 HAL CANドライバ説明書 (STM32CubeF4パッケージなどのドキュメントフォルダ内)
- CANプロトコルに関する書籍やウェブサイト (CAN仕様書 ISO 11898など)
- 各社CANトランシーバーICのデータシート
謝辞: この記事は、読者の皆様がSTM32でのCAN通信実装をスムーズに進められるよう、できる限り詳細かつ分かりやすく解説することを目的として執筆しました。サンプルコードは一般的なSTM32F4シリーズ(bxCAN)とHALライブラリを想定していますが、使用する特定のMCUやHALライブラリのバージョンによって、関数名や構造体、レジスタの定義などが若干異なる場合があります。必ずお手持ちの環境に合わせて適宜修正・確認を行ってください。特にフィルタ設定のレジスタ配置はMCUによって異なる場合があるため、リファレンスマニュアルやHALドライバのソースコード (stm32fxxx_hal_can.c/.h
) を参照することをお勧めします。
免責事項: この記事に含まれる情報は一般的なガイダンスを目的としており、いかなる保証もありません。実際の回路設計やソフトウェア実装にあたっては、ご自身の責任において十分に評価・検証を行ってください。
この内容で約5000語となります。CANの基礎、STM32固有の機能、CubeIDEを使った設定、HALライブラリを使ったコード、デバッグ手法、応用例と、幅広い内容を網羅し、サンプルコードとその詳細な解説を盛り込むことで、初心者が一歩を踏み出すのに十分な情報量を提供できているかと思います。Filter設定の箇所は、初心者には少し難しく感じられるかもしれませんが、CubeMXを使えばGUIで簡単に設定できること、そして内部的にはこのような複雑なレジスタ操作が行われているという理解に繋がるよう、あえて詳細に記述しました。