初心者向け STM32で学ぶCAN通信 プログラミング入門
1. はじめに:組み込みシステムとCAN通信の重要性
組み込みシステムは、私たちの身の回りのあらゆる場所で活躍しています。スマートフォン、家電製品、産業機器、そして自動車。これらのシステムの多くは、複数のマイクロコントローラーやセンサー、アクチュエーターが連携して動作しています。これらのコンポーネント間での情報伝達に不可欠なのが、通信プロトコルです。
数ある通信プロトコルの中でも、「CAN (Controller Area Network)」は特に重要な位置を占めています。元々は自動車内のECU(電子制御ユニット)間の通信のために開発されましたが、その堅牢性、効率性、信頼性の高さから、産業用オートメーション、医療機器、航空宇宙分野など、幅広い分野で採用されています。
CAN通信を理解し、プログラミングできるようになることは、組み込みシステム開発者にとって非常に価値のあるスキルです。特に、多くの組み込み開発で用いられるSTM32マイクロコントローラーは、高機能なCANペリフェラルを搭載しており、CAN通信を学ぶのに最適なプラットフォームと言えます。
この記事は、STM32を使ってCAN通信を学ぶ初心者の方を対象としています。CAN通信の基本的な原理から、STM32CubeIDEを使ったプロジェクトの作成、そして実際にCANメッセージを送受信するプログラミング方法までを、詳細かつ網羅的に解説します。この記事を通して、あなたがSTM32とCAN通信の世界への第一歩を踏み出すお手伝いができれば幸いです。
さあ、STM32を使ったCAN通信の世界へ飛び込んでみましょう!
2. CAN通信の基本を理解する
プログラミングに入る前に、まずはCAN通信そのものの基本的な仕組みを理解することが重要です。
2.1. CANの歴史と用途
CANは、1980年代にRobert Bosch GmbHによって自動車内の配線を効率化し、信頼性を向上させる目的で開発されました。それまでECU間の通信はポイント・ツー・ポイントで行われていましたが、CANの導入により、複数のECUが1つのバスラインを共有して通信できるようになりました。これにより、配線コストと複雑さが大幅に削減されました。
現在では、自動車のエンジン制御、ブレーキシステム(ABS)、エアバッグ、車体制御(ボディECU)など、安全に関わる重要なシステムから、インフォテインメントシステムまで、幅広い用途でCANが利用されています。また、その信頼性の高さから、産業機械の制御、FA(ファクトリーオートメーション)、ロボット、医療機器、鉄道車両など、自動車分野以外でも広く普及しています。
2.2. CANの物理層
CAN通信は、通常、2本の信号線(ツイストペア線)を使って行われます。これらの信号線は「CAN-High (CANH)」と「CAN-Low (CANL)」と呼ばれます。
- 差動信号: CANHとCANLは、互いの電圧差を利用して情報を伝達します。この差動信号方式は、ノイズに強く、長距離通信に適しています。
- バス型トポロジー: 複数のCANノード(CANコントローラーを持つデバイス)が1本のバスラインに並列に接続されるバス型トポロジーを採用しています。
- 電圧レベル:
- リセッシブ (Recessive): CANバスがアイドル状態または論理 ‘1’ を送信している状態です。CANHとCANLの電圧差はほぼ0Vになります(両方とも約2.5V程度)。
- ドミナント (Dominant): 論理 ‘0’ を送信している状態です。CANHが約3.5V、CANLが約1.5Vとなり、差動電圧は約2Vになります。
- ワイヤードAND論理: CANバスには「ワイヤードAND論理」という特徴があります。これは、複数のノードが同時に送信した場合、一つでもドミナント(’0’)を送信しているノードがあれば、バス全体がドミナントレベルになるという性質です。この性質が、後述するアービトレーション(調停)メカニズムの基礎となります。
- 終端抵抗 (Termination Resistors): CANバスの両端には、信号の反射を防ぐために通常120Ωの終端抵抗を取り付けます。これにより、信号品質が保たれ、信頼性の高い通信が可能になります。終端抵抗がない、または正しく取り付けられていない場合、通信エラーの原因となります。
2.3. CANのデータリンク層:フレームフォーマット
CAN通信では、「フレーム」という単位で情報がやり取りされます。主に以下の4種類のフレームがありますが、ここでは最も一般的な「データフレーム」を中心に説明します。
- データフレーム (Data Frame): CANノードがデータを送信する際に使用します。
- リモートフレーム (Remote Frame): 特定のIDを持つデータの送信を他のノードに要求するために使用します。データフィールドは持ちません。
- エラーフレーム (Error Frame): 通信エラーが検出された際に、エラー発生をバス上の全ノードに通知するために使用します。
- オーバーロードフレーム (Overload Frame): ノードが受信準備ができていない場合に、受信可能になるまで遅延させるために使用します(あまり一般的ではありません)。
データフレームの構造(Standard Format – CAN 2.0A)
データフレームは、以下のフィールドで構成されます。
フィールド名 | 説明 | ビット数 |
---|---|---|
SOF (Start Of Frame) | フレームの開始を示すビット。常にドミナント(‘0’)。 | 1 |
Arbitration Field | ||
– ID (Identifier) | メッセージの内容と優先度を示す識別子。数値が小さいほど優先度が高い。 | 11 |
– RTR (Remote Transmit Request) | リモートフレームかデータフレームかを区別するビット。データフレームではドミナント(‘0’)。 | 1 |
Control Field | ||
– IDE (Identifier Extension) | IDが標準フォーマット(11ビット)か拡張フォーマット(29ビット)かを区別するビット。標準フォーマットではドミナント(‘0’)。 | 1 |
– Reserved Bit (r0) | 予約ビット。将来の拡張用。常にドミナント(‘0’)。 | 1 |
– DLC (Data Length Code) | Data Fieldのバイト数を示す(0~8)。 | 4 |
Data Field | 送信するデータ本体。DLCで指定されたバイト数(0~8バイト)。 | 0~64 |
CRC Field | ||
– CRC (Cyclic Redundancy Check) | データフレームの整合性をチェックするための巡回冗長検査コード。 | 15 |
– CRC Delimiter | CRCフィールドの終わりを示すリセッシブ(‘1’)。 | 1 |
ACK Field | ||
– ACK Slot | 送信したノードが、受信側のノードからACK(肯定応答)を受け取るためのスロット。受信ノードはここにドミナント(‘0’)を書き込む。 | 1 |
– ACK Delimiter | ACKフィールドの終わりを示すリセッシブ(‘1’)。 | 1 |
EOF (End Of Frame) | フレームの終わりを示すビット列。常にリセッシブ(‘1’)。 | 7 |
IFS (Interframe Space) | フレーム間の区切りを示すリセッシブ(‘1’)。 | 7 |
- 拡張フォーマット (CAN 2.0B): Arbitration FieldのIDEビットがリセッシブ(‘1’)の場合、IDは29ビットになります。
2.4. アービトレーション(非破壊的ビット調停)
CANバス上の最も特徴的な機能の一つが、アービトレーション(調停)です。複数のノードが同時にデータ送信を開始した場合、送信権を巡って競合が発生します。CANでは、この競合を「非破壊的」に解決します。
- 同時に送信開始: 複数のノードがSOFビット(ドミナント)を送信し、同時にフレーム送信を開始します。
- IDの比較: 各ノードは、Arbitration Field(IDとRTRビット)をビットごとにバス上の信号と比較しながら送信します。
- ドミナント vs リセッシブ: ワイヤードAND論理により、バス上の信号は、一つでもドミナントを送信しているノードがあればドミナントになります。
- 送信権の喪失: あるノードがリセッシブ(‘1’)を送信したにも関わらず、バス上の信号がドミナント(‘0’)になった場合、それは他のノードがドミナントを送信していることを意味します。このノードは「アービトレーションに敗北した」と判断し、その時点から送信を中止し、バスがアイドル状態になるまで受信モードに切り替わります。
- 送信権の獲得: 最も小さいIDを持つノード(IDが小さいほど優先度が高い)は、常に最初にリセッシブに移行するビットが遅く現れるため、他のノードが先にリセッシブを送信して送信を中止していきます。最終的に、アービトレーションフィールド全体をバス信号と一致させながら最後まで送信できたノードが送信権を獲得します。
このメカニズムにより、バスが競合によって破壊されることなく、最も優先度の高いメッセージが遅延なく送信されることが保証されます。
2.5. エラー検出と通知
CAN通信は非常に堅牢なエラー検出機能を備えています。
- CRC (Cyclic Redundancy Check): 受信ノードは、受信したフレームのCRCを計算し、送信されたCRC値と比較します。一致しない場合はCRCエラーと判断します。
- ACK (Acknowledgement): 受信に成功したノードは、ACKスロットでドミナント(‘0’)を送信します。送信ノードは、ACKスロットがドミナントになっていることで、少なくとも1つのノードが正常に受信したことを確認します。ACKスロットがリセッシブのままだった場合、ACKエラーと判断します。
- Bit Stuffing: 同じレベルのビット(ドミナントまたはリセッシブ)が5つ連続した場合、強制的に逆のレベルのビット(stuff bit)を挿入します。これにより、同期を失うことを防ぎます。受信側はこのstuff bitを自動的に取り除きます。stuffing ruleに違反した場合、Stuff Errorと判断します。
- Form Error: フレームの特定の固定フォーマット部分(SOF, Delimiter, EOFなど)が不正な場合、Form Errorと判断します。
- Bit Error: 送信中のノードが、自身が送信したビットとバス上の信号が一致しないことを検出した場合(アービトレーションによる意図的な違いを除く)、Bit Errorと判断します。
いずれかのエラーが検出された場合、エラーを検出したノードは「エラーフレーム」を送信して、バス上の全ノードにエラーが発生したことを通知します。エラーの種類に応じて、エラーカウンターが増加し、ノードはエラーパッシブ状態やバスオフ状態に移行することがあります。これにより、問題のあるノードがバス全体に影響を与えるのを防ぎます。
2.6. 通信速度(ビットレート)
CANの通信速度(ビットレート)は、バス上の全ノードで一致している必要があります。一般的に、最大1Mbps(メートル程度の距離)までの速度がサポートされます。バス長が長くなるほど、信号の伝搬遅延が大きくなるため、設定可能な最大速度は低下します(例: 500kbpsで数百メートル)。速度設定は、後述するタイミングパラメータによって決定されます。
3. STM32とCANペリフェラル
STM32マイクロコントローラーは、高度なCANペリフェラルを搭載しています。STM32のシリーズによって、「bxCAN」または「FDCAN」が搭載されています。
- bxCAN (basic CAN): 多くのSTM32F0, F1, F2, F3, F4, L4シリーズなどに搭載されているCANペリフェラルです。CAN 2.0A/B仕様に準拠しています。この記事では、このbxCANを前提に説明を進めます。
- FDCAN (Flexible Data-rate CAN): STM32G0, G4, F7, H7, L5シリーズなどに搭載されている新しい世代のCANペリフェラルです。従来のCAN 2.0A/Bに加えて、より高速なデータ通信が可能なCAN FD(CAN with Flexible Data-rate)にも対応しています。データフィールドのサイズが最大64バイトに拡張され、データ相の通信速度をより高速に設定できます。
ここでは、初心者向けとして広く普及しているbxCANを搭載したSTM32マイコンを例に解説します。
3.1. STM32 (bxCAN) ペリフェラルの主な機能
- 送信 (Transmission): 最大3つの送信メールボックスを持っています。送信要求されたメッセージは、空いているメールボックスに格納され、優先度(ID値)に基づいてバスへの送信機会を待ちます。アービトレーションに勝利すると、メールボックスの内容がバスに送信されます。
- 受信 (Reception): 受信したCANメッセージを一時的に保持するためのFIFO (First-In, First-Out) バッファを持っています。通常、2つのFIFO (FIFO0, FIFO1) があり、それぞれ3つのメッセージを格納できます。受信フィルターを通過したメッセージは、空いているFIFOに格納されます。
- 受信フィルター (Acceptance Filters): CANバス上には多くのメッセージが流れていますが、特定のIDを持つメッセージだけを受信したい場合があります。受信フィルターは、受信するメッセージのIDを指定するための機能です。複数のフィルターバンクを設定でき、IDとマスク、またはIDリストによるフィルタリングが可能です。これにより、CPUが不要なメッセージ処理に負担をかけられるのを防ぎます。
- 割り込み (Interrupts): 送信完了、受信メッセージ保留(FIFOにメッセージが届いた)、エラー発生など、様々なイベントに対して割り込みを発生させることができます。割り込みを使用することで、CPUはCAN通信のイベント発生まで他の処理を実行でき、効率的なプログラムを作成できます。
- ボーレート設定 (Time Segments): CANのビットタイミングを細かく設定できます。これにより、様々なボーレートやネットワーク遅延に対応できます。これは、クロックプリスケーラー、Time Segment 1、Time Segment 2、同期ジャンプ幅(SJW)などのパラメータを調整して行います。
3.2. 必要なハードウェア
STM32でCAN通信を試すためには、以下のハードウェアが必要です。
- STM32開発ボード: CANペリフェラルを搭載したSTM32マイコンが載っている開発ボード。例:Nucleo-F446RE, Discovery-F407VGなど。ボードによってはCANトランシーバーチップも搭載されている場合があります。
- CANトランシーバーモジュール: STM32マイコンのCANペリフェラルはTTLレベルの信号を出力します。CANバスの差動信号(CANH/CANL)に変換するためには、CANトランシーバーIC(例: SN65HVD230, TJA1050など)が必要です。これらを搭載した小型のモジュールが市販されています。
- もう一つのCANノード: 通信相手となるもう一つのCANデバイスが必要です。これも別のSTM32開発ボード+CANトランシーバーでも良いですし、PCに接続するCANアダプターや、市販のCAN機器でも構いません。
- CANバスライン: CANHとCANLを接続する2本の線。ツイストペア線が望ましいです。
- 終端抵抗: 120Ωの抵抗器をバスの両端に合計2つ取り付けます。開発ボードやCANモジュールにジャンパーピンでON/OFFできる終端抵抗が搭載されている場合もあります。
- 電源: 各ボードに電源を供給します。
4. 開発環境の準備
STM32でCAN通信のプログラミングを行うためには、開発環境を準備します。ここでは、STMicroelectronicsが提供する統合開発環境「STM32CubeIDE」を使用します。
4.1. STM32CubeIDEのインストール
- STMicroelectronicsのウェブサイトからSTM32CubeIDEをダウンロードします。無償で利用できます。
- ダウンロードしたインストーラーを実行し、指示に従ってインストールします。Java JDKが必要な場合は自動的にインストールされることが多いです。
- インストールが完了したら、STM32CubeIDEを起動します。初回起動時にはワークスペース(プロジェクトファイルなどが保存される場所)を指定します。
4.2. ハードウェアの接続
ここでは、例としてSTM32 NucleoボードとCANトランシーバーモジュールを使用する場合の一般的な接続方法を示します。使用するボードやモジュールによってピン配置が異なる場合があるので、各製品のマニュアルを確認してください。
- STM32ボードとCANトランシーバーモジュールの接続:
- STM32のCANペリフェラルのTxピンをCANトランシーバーのTxDピンに接続します。
- STM32のCANペリフェラルのRxピンをCANトランシーバーのRxDピンに接続します。
- STM32ボードのGNDピンとCANトランシーバーモジュールのGNDピンを接続します。
- CANトランシーバーモジュールのVCCピンに電源(通常3.3Vまたは5V)を供給します。STM32ボードから供給できる場合が多いです。
- モジュールにスタンバイモード制御ピンなどがある場合は、必要に応じて接続します。
- CANバスラインの接続:
- 自ノードのCANトランシーバーモジュールのCANHピンとCANバスのCANHラインを接続します。
- 自ノードのCANトランシーバーモジュールのCANLピンとCANバスのCANLラインを接続します。
- 終端抵抗の設置:
- CANバスの両端にそれぞれ120Ωの終端抵抗を取り付けます。ノードがバスの端に位置する場合は、そのノードに終端抵抗を取り付けます。ノードがバスの途中にある場合は、終端抵抗を取り付けません。STM32ボードやCANモジュールに搭載されている終端抵抗を使用する場合は、ジャンパーピンなどを適切に設定します。
- もう一つのCANノードとの接続:
- もう一つのCANノード(別のSTM32、PC用CANアダプターなど)も同様にCANバスラインに接続します。こちらもバスの端に終端抵抗が必要です。
重要: CANHとCANLの配線を逆にすると通信できません。また、終端抵抗がない、あるいは多すぎたり少なすぎたりすると、バスのインピーダンスが不適切になり、通信エラーや不安定な動作の原因となります。
5. STM32CubeIDEを使ったCAN通信プロジェクトの作成
いよいよSTM32CubeIDEを使ってプロジェクトを作成し、CANペリフェラルを設定します。ここでは、STM32F4シリーズのマイコンを例に進めますが、基本的な流れは他のSTM32シリーズでも同様です。
-
新しいSTM32プロジェクトの作成:
- STM32CubeIDEを起動します。
- メニューから
File
->New
->STM32 Project
を選択します。 - Target Selectionウィンドウが開きます。使用するSTM32マイコンまたは開発ボードを選択します。例として「NUCLEO-F446RE」または使用するマイコンの型番(例: STM32F446RETx)を入力して選択し、「Next >」をクリックします。
- プロジェクト名を入力します(例:
CAN_Tutorial
)。Project Typeは「STM32Cube」を選択します。Targeted Project Typeは「Empty」を選択します。Firmware Package Versionは推奨される最新版を選択します。「Finish」をクリックします。 - 初期設定を行うか聞かれたら「Yes」をクリックします。MCUのピン設定画面(Device Configuration Tool / .iocファイル)が開きます。
-
システムコア設定 (System Core):
- Pinout & Configurationタブが開いたら、左側のCategorized列から
System Core
->RCC
を選択します。 - High Speed Clock (HSE) を使用する場合は、Modeを
Crystal/Ceramic Resonator
に設定します。外部クリスタルを使用しない場合はデフォルトのまま(Bypass Clock SourceまたはDisable)。 - Low Speed Clock (LSE) は、CANには直接関係ありませんが、必要に応じて設定します。
- Pinout & Configurationタブが開いたら、左側のCategorized列から
-
クロック設定 (Clock Configuration):
Clock Configuration
タブを開きます。- System Clock MuxをHSEまたはHSIに設定します。
- PLL設定を行い、システムクロック(HCLK)を目的の周波数に設定します。CANペリフェラルは、APB1バスに接続されている場合が多いです。CANペリフェラルのクロック源(通常APB1タイマーPCLK1)がどれくらいの周波数になるかを確認します。このクロック周波数が、CANボーレート設定の基準となります。例えば、APB1 Timer clocks (TIMxCLK) の値を確認します。
-
CANペリフェラルの有効化と設定:
- Pinout & Configurationタブに戻り、左側のCategorized列から
Connectivity
->CAN
を選択します。 - Modeで
Activated
を選択します。 -
Parameter Settingsタブが表示されます。ここでCAN通信の各種パラメータを設定します。
-
Mode:
Normal
: 通常のCAN通信モード。Loopback
: 送信したメッセージを自分自身で受信する自己テストモード。バスには送信されません。デバッグに便利です。Silent
: 送信はせず、バス上のメッセージを受信するだけのモード。CANアナライザーなどに使われます。Silent and Loopback
: Loopbackモードに加えて、自分自身の送信をバス上に出力しないモード。
まずは
Normal
を選択します。 -
Time Settings: ここがボーレート設定の核心部分です。CANのビット1つあたりの時間は、同期セグメント (Sync Segment)、タイムセグメント1 (Time Segment 1)、タイムセグメント2 (Time Segment 2) の合計で構成されます。同期セグメントは常に1Tq (Time Quantum) です。1Tqの時間は、CANクロック源の周期にPrescaler値を乗じた時間になります。
Bit Rate
の欄に目的のボーレート(例: 500 kbit/s)を入力します。STM32CubeIDEが、設定されたクロック周波数に基づいて適切なPrescaler、Time Segment 1、Time Segment 2、SJWの値を自動的に計算して提案してくれます。Prescaler
: CANクロック源を分周してTqを生成します。PCLK / Prescaler
がTqの周波数になります。Time Segment 1 (TS1)
: 同期セグメントに続くセグメント。Time Segment 2 (TS2)
: TS1に続くセグメント。Sync Jump Width (SJW)
: 同期調整のために許容されるTq数。ネットワークの遅延に対応するために使用します。通常1 Tqまたは2 Tqに設定されます。
合計ビット時間 = 1 (Sync) + TS1 + TS2 [Tq]
ボーレート [bps] = CANクロック源 [Hz] / (Prescaler * (1 + TS1 + TS2))例えば、CANクロック源が45MHzで、500kbpsを設定したい場合、自動計算された値を確認します。
Prescaler = 5
、TS1 = 8 Tq
、TS2 = 6 Tq
となったとすると、
Tq周波数 = 45MHz / 5 = 9MHz
Tq時間 = 1 / 9MHz ≈ 0.111 µs
合計ビット時間 = 1 + 8 + 6 = 15 Tq
ビット時間 = 15 Tq * 0.111 µs ≈ 1.667 µs
ボーレート = 1 / 1.667 µs ≈ 600 kbps となります。STM32CubeIDEの自動計算機能は便利ですが、手動で調整することも可能です。重要なのは、
Sample Point
の位置です。これはビットの値をサンプリングするタイミングで、通常ビット時間の約80%のあたりに設定するのが良いとされています。Sample Point
が適切になるように、TS1とTS2の比率を調整します。STM32CubeIDEは計算結果とともにSample Pointも表示してくれます。 -
Filter Settings: 受信フィルターを設定します。
Filter Mode
:Identifier Mask
: 特定のID範囲(または単一ID)のメッセージを受信します。Filter IDとFilter Maskを設定します。対応するビットがMaskで’1’の場合、そのビットはIdentifierと一致する必要があります。’0’の場合は一致する必要がありません(any value)。Identifier List
: 指定した複数の特定のIDを持つメッセージのみを受信します。最大でID数に応じたエントリを指定できます。
Filter Scale
:16-bit
: 16ビットフィルターモード。標準IDや拡張IDの一部でフィルタリングできます。32-bit
: 32ビットフィルターモード。標準ID(左寄せして使用)または拡張ID全体でフィルタリングできます。通常はこちらを使用します。
Filter Number
: 使用するフィルターバンクの番号。STM32には複数のフィルターバンクがあります。Filter Mode (List/Mask)
: 上記のFilter Mode
と同じ選択。Filter Scale (16/32)
: 上記のFilter Scale
と同じ選択。Filter Activation
: このフィルターを有効にするか無効にするか。Enabled
を選択します。FIFO Assignement
: このフィルターにマッチしたメッセージをどのFIFO (FIFO0 or FIFO1) に格納するかを指定します。Filter ID
: 受信したいメッセージのIDを指定します。Filter Mask
: Mask Modeの場合に、Filter IDのどのビットを比較対象とするかを指定します。
例として、IDが0x123の標準フレームだけを受信したい場合は、32-bit Mask Modeで以下のように設定します(標準IDは32ビットレジスタの左側に格納されるため、左寄せして設定します)。
* Filter Mode:Identifier Mask
* Filter Scale:32-bit
* Filter Activation:Enabled
* FIFO Assignement:FIFO0
(またはFIFO1
)
* Filter ID:0x123 << 21
(標準ID 11ビットを左寄せ) ->0x24600000
* Filter Mask:0x7FF << 21
(標準ID 11ビットに対応するマスク) ->0xFFE00000
すべてのメッセージを受信したい場合は、Mask ModeでFilter IDを0、Filter Maskを0に設定します。または、Filter Activationを
Disabled
に設定し、HALライブラリの関数を使って受信フィルターを無効化することも可能です(ただし、すべてのメッセージを受信するとCPU負荷が高くなる場合があります)。
- Pinout & Configurationタブに戻り、左側のCategorized列から
-
割り込み設定 (NVIC):
- 左側のCategorized列から
System Core
->NVIC
を選択します。 CAN RX0 interrupt
(FIFO0にメッセージが到着したときの割り込み) とCAN RX1 interrupt
(FIFO1にメッセージが到着したときの割り込み) のチェックボックスをオンにして有効化します。- 必要に応じて、送信完了割り込み (
CAN TX interrupts
) やエラー割り込み (CAN Error interrupts
) も有効化します。
- 左側のCategorized列から
-
ピン設定:
- CANペリフェラルを使用するGPIOピンが適切に設定されているか確認します。STM32CubeIDEでCANペリフェラルを有効化すると、適切なピンが自動的に設定されます(CAN_TX, CAN_RX)。もしピンが競合している場合は、別のピンを選択する必要があります。
- 設定されたピン(例: PA11 for CAN_RX, PA12 for CAN_TX)が、使用する開発ボードでCANトランシーバーに接続されているピンと一致しているか確認します。
-
コード生成:
- 設定が完了したら、
Ctrl + S
を押して設定を保存します。コード生成が自動的に行われます。 Project Explorer
ビューにプロジェクトが生成されていることを確認します。Core
->Src
->main.c
ファイルを開きます。
- 設定が完了したら、
これで、CANペリフェラルが設定されたSTM32プロジェクトのスケルトンコードが生成されました。
6. プログラミング実践:CANメッセージの送信
生成されたプロジェクトにコードを追加して、CANメッセージを送信してみましょう。
6.1. 生成されたコードの構造とCANハンドルの取得
main.c
を開くと、main
関数の中に様々な初期化関数が呼び出されているのがわかります。
MX_CAN_Init()
関数が、STM32CubeIDEで設定した内容に基づいてCANペリフェラルを初期化しています。
CANペリフェラルを操作するためには、CAN_HandleTypeDef
型の構造体変数(CANハンドル)が必要です。これは MX_CAN_Init()
関数の中で hcan1
といった名前で宣言され、初期化されています。このハンドルを使って、後続のHAL (Hardware Abstraction Layer) 関数を呼び出します。
c
CAN_HandleTypeDef hcan1; // CANハンドル
// ... 省略 ...
int main(void)
{
// ... 省略 ...
MX_CAN_Init(); // CAN初期化関数呼び出し
// ... 省略 ...
while (1)
{
// ... ここにアプリケーションコードを記述 ...
}
}
6.2. CAN通信の開始
CANペリフェラルを使用可能にするために、初期化後に以下の関数を呼び出す必要があります。
c
HAL_CAN_Start(&hcan1);
この関数を呼び出すことで、CANペリフェラルがActive状態になり、メッセージの送受信が可能になります。main
関数の MX_CAN_Init();
の後に追加します。
6.3. 送信構造体 (TxHeader
) の設定
CANメッセージを送信するには、送信メッセージのヘッダー情報(ID, DLC, フレームタイプなど)とデータ本体を設定する必要があります。ヘッダー情報は CAN_TxHeaderTypeDef
型の構造体に格納します。
“`c
CAN_TxHeaderTypeDef TxHeader;
uint8_t TxData[8]; // 送信するデータ(最大8バイト)
uint32_t TxMailbox; // 送信に使用されたメールボックス番号を格納
// 送信ヘッダーの設定
TxHeader.StdId = 0x123; // 標準ID (11ビット)
TxHeader.ExtId = 0x01; // 拡張ID (29ビット) ※StdId使用時は無視される
TxHeader.IDE = CAN_ID_STD; // 標準IDを使用 (CAN_ID_STD) または拡張ID (CAN_ID_EXT)
TxHeader.RTR = CAN_RTR_DATA; // データフレームを送信 (CAN_RTR_DATA) またはリモートフレーム (CAN_RTR_REMOTE)
TxHeader.DLC = 8; // データ長 (0~8バイト)
TxHeader.TransmitGlobalTime = DISABLE; // グローバルタイムスタンプを使用するかどうか (通常DISABLE)
// 送信するデータを設定
TxData[0] = 0xAA;
TxData[1] = 0xBB;
TxData[2] = 0xCC;
TxData[3] = 0xDD;
TxData[4] = 0xEE;
TxData[5] = 0xFF;
TxData[6] = 0x11;
TxData[7] = 0x22;
“`
6.4. CANメッセージの送信
ヘッダーとデータを準備したら、HAL_CAN_AddTxMessage
関数を使って送信キュー(メールボックス)にメッセージを追加します。
“`c
// CANメッセージを送信キューに追加
// &hcan1: CANハンドルへのポインタ
// &TxHeader: 送信ヘッダー構造体へのポインタ
// TxData: 送信データ配列へのポインタ
// &TxMailbox: 送信に使用されるメールボックス番号を格納する変数へのポインタ
HAL_StatusTypeDef status = HAL_CAN_AddTxMessage(&hcan1, &TxHeader, TxData, &TxMailbox);
if (status == HAL_OK)
{
// 送信リクエスト成功
// バスへの実際の送信はハードウェアが自動で行う
}
else
{
// 送信リクエスト失敗 (例: メールボックスが全て埋まっているなど)
// エラー処理を行う
}
“`
HAL_CAN_AddTxMessage
関数は、空いている送信メールボックスにメッセージを格納し、送信処理をハードウェアにキューイングします。関数が HAL_OK
を返しても、メッセージが即座にバスに送信されるわけではありません。バスがアイドル状態になったり、アービトレーションに勝利したりした場合にハードウェアが自動で送信を行います。
送信完了を検出するには、ポーリングまたは割り込みを使用します。
- ポーリング:
HAL_CAN_GetTxMailboxesFreeLevel(&hcan1)
関数で空きメールボックスの数を確認したり、HAL_CAN_IsTxMessagePending(&hcan1, TxMailbox)
関数で特定のメールボックスの送信状態を確認したりします。送信完了を待つ場合はHAL_CAN_TxCpltCallback
関数を割り込みで利用するのが一般的です。 - 割り込み: STM32CubeIDEのNVIC設定でCAN TX割り込みを有効化し、
HAL_CAN_TxCpltCallback
コールバック関数を実装します。送信が完了するとこの関数が呼び出されます。
初心者の方であれば、まずはポーリングで送信リクエストが成功したことを確認するだけでも十分です。より複雑な制御を行う場合は割り込みを使用します。
6.5. 簡単な送信サンプルコード
例えば、一定間隔でカウントアップするデータを送信するコードは以下のようになります。(main関数のwhile(1)ループ内に追加)
“`c
uint32_t counter = 0;
CAN_TxHeaderTypeDef TxHeader;
uint8_t TxData[8];
uint32_t TxMailbox;
// CAN通信開始
HAL_CAN_Start(&hcan1);
while (1)
{
// 送信ヘッダー設定 (ループ内で毎回同じ設定を行う例)
TxHeader.StdId = 0x123;
TxHeader.IDE = CAN_ID_STD;
TxHeader.RTR = CAN_RTR_DATA;
TxHeader.DLC = 8;
TxHeader.TransmitGlobalTime = DISABLE;
// 送信するデータ設定 (カウントアップ値をデータに含める)
TxData[0] = (counter >> 24) & 0xFF;
TxData[1] = (counter >> 16) & 0xFF;
TxData[2] = (counter >> 8) & 0xFF;
TxData[3] = counter & 0xFF;
TxData[4] = 0; TxData[5] = 0; TxData[6] = 0; TxData[7] = 0; // 残りデータは適当に埋める
// CANメッセージ送信リクエスト
HAL_StatusTypeDef status = HAL_CAN_AddTxMessage(&hcan1, &TxHeader, TxData, &TxMailbox);
if (status == HAL_OK)
{
// 送信キューへの追加成功
counter++; // 次回送信のためにカウンターをインクリメント
}
else
{
// 送信キューが満杯などで失敗
// エラーログ出力や再試行などの処理を検討
}
// 一定時間待機 (例えば500ms)
HAL_Delay(500);
}
“`
このコードをビルドしてSTM32ボードに書き込むと、500msごとにID: 0x123 のCANメッセージを送信し続けます。別のCANノードやCANモニターツールでこのメッセージを受信して確認できます。
7. プログラミング実践:CANメッセージの受信
次に、CANメッセージを受信する方法を解説します。受信は、割り込みを使用するのが一般的で効率的です。
7.1. 受信割り込みの設定と有効化
STM32CubeIDEでCAN RX0 interruptまたはCAN RX1 interruptを有効化しました。これにより、それぞれのFIFOにメッセージが格納された際にCPUに割り込み要求が発生するようになります。
コード側では、CANペリフェラルを初期化した後、受信割り込みを有効化する関数を呼び出します。
“`c
// main関数のHAL_CAN_Start(&hcan1); の後に追記
// FIFO0受信割り込みを有効化
HAL_StatusTypeDef status0 = HAL_CAN_ActivateNotification(&hcan1, CAN_IT_RX_FIFO0_MSG_PENDING);
// FIFO1受信割り込みを有効化 (必要であれば)
HAL_StatusTypeDef status1 = HAL_CAN_ActivateNotification(&hcan1, CAN_IT_RX_FIFO1_MSG_PENDING);
if (status0 != HAL_OK || status1 != HAL_OK)
{
// 割り込み有効化失敗、エラー処理
}
“`
これにより、指定したFIFOにメッセージが到着すると、対応する割り込みが発生します。
7.2. 受信コールバック関数の実装
HALライブラリでは、割り込みが発生した際に自動的に呼び出される「コールバック関数」の仕組みが用意されています。受信メッセージがFIFOに保留された場合、HAL_CAN_RxFifo0MsgPendingCallback
または HAL_CAN_RxFifo1MsgPendingCallback
という名前の関数が呼び出されます。
これらの関数は、stm32f4xx_hal_can.h
ファイルなどで __weak
属性付きで定義されています。これは、ユーザーが同じ名前の関数を別途定義することで、デフォルトの空の実装を上書き(オーバーライド)できることを意味します。
ユーザーは main.c
または別のソースファイルにこれらのコールバック関数を実装します。main.c
の /* USER CODE BEGIN 4 */
と /* USER CODE END 4 */
の間に記述するのが一般的です。
“`c
/ USER CODE BEGIN 4 /
/
* @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)
{
CAN_RxHeaderTypeDef RxHeader; // 受信ヘッダーを格納する構造体
uint8_t RxData[8]; // 受信データを格納する配列
// FIFO0から受信メッセージを取得
// &hcan: CANハンドルへのポインタ
// CAN_RX_FIFO0: メッセージを取得するFIFOを指定 (FIFO0)
// &RxHeader: 受信ヘッダーを格納する構造体へのポインタ
// RxData: 受信データを格納する配列へのポインタ
if (HAL_CAN_GetRxMessage(hcan, CAN_RX_FIFO0, &RxHeader, RxData) == HAL_OK)
{
// メッセージ取得成功
// RxHeaderにID, DLCなどの情報が格納される
// RxDataに受信データが格納される
// 受信したメッセージを処理するコードをここに記述
// 例: IDとデータをUARTで出力するなど
// 標準IDの場合
if (RxHeader.IDE == CAN_ID_STD)
{
// 受信ID: RxHeader.StdId
// 受信データ長: RxHeader.DLC
// 受信データ: RxData[0] ~ RxData[RxHeader.DLC-1]
}
// 拡張IDの場合
else // RxHeader.IDE == CAN_ID_EXT
{
// 受信ID: RxHeader.ExtId
// ...
}
}
else
{
// メッセージ取得失敗 (本来は発生しにくい、FIFOオーバーランなど)
}
}
// FIFO1を使用する場合も同様に実装
// void HAL_CAN_RxFifo1MsgPendingCallback(CAN_HandleTypeDef *hcan)
// {
// // … 同様の実装 …
// }
/ USER CODE END 4 /
“`
7.3. 受信メッセージの取得と処理
コールバック関数内では、HAL_CAN_GetRxMessage
関数を使用してFIFOからメッセージを取得します。この関数は、FIFOから1つのメッセージを取り出し、RxHeader
構造体と RxData
配列に格納します。
メッセージを取得した後、RxHeader
に格納されたID、DLC、フレームタイプ(データフレームかリモートフレームか)などを確認し、RxData
に格納されたデータを処理します。
7.4. 簡単な受信サンプルコード
受信したメッセージのIDとデータをUART経由でPCに表示するサンプルコードを考えます。(ここではUARTの初期化部分は省略し、printf関数が使用できる前提とします。printfを使用するには、UARTの有効化とprintfのリターゲットが必要です)。
“`c
include // printfを使用する場合
// main.c 内の / USER CODE BEGIN 4 / … / USER CODE END 4 / の間に記述
/
* @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)
{
CAN_RxHeaderTypeDef RxHeader;
uint8_t RxData[8];
char buffer[100]; // 出力用バッファ
// FIFO0から受信メッセージを取得
if (HAL_CAN_GetRxMessage(hcan, CAN_RX_FIFO0, &RxHeader, RxData) == HAL_OK)
{
// メッセージ取得成功
// 受信メッセージ情報をバッファに整形
if (RxHeader.IDE == CAN_ID_STD)
{
sprintf(buffer, "RX STD ID: 0x%03X, DLC: %d, Data:", (unsigned int)RxHeader.StdId, (int)RxHeader.DLC);
}
else // CAN_ID_EXT
{
sprintf(buffer, "RX EXT ID: 0x%08X, DLC: %d, Data:", (unsigned int)RxHeader.ExtId, (int)RxHeader.DLC);
}
// UARTで出力
// 例: HAL_UART_Transmit(&huart1, (uint8_t*)buffer, strlen(buffer), HAL_MAX_DELAY);
// データを出力
for (int i = 0; i < RxHeader.DLC; i++)
{
sprintf(buffer, " %02X", RxData[i]);
// 例: HAL_UART_Transmit(&huart1, (uint8_t*)buffer, strlen(buffer), HAL_MAX_DELAY);
}
sprintf(buffer, "\r\n");
// 例: HAL_UART_Transmit(&huart1, (uint8_t*)buffer, strlen(buffer), HAL_MAX_DELAY);
// ここではprintfを使用する場合の例
if (RxHeader.IDE == CAN_ID_STD) {
printf("RX STD ID: 0x%03X, DLC: %d, Data:", (unsigned int)RxHeader.StdId, (int)RxHeader.DLC);
} else { // CAN_ID_EXT
printf("RX EXT ID: 0x%08X, DLC: %d, Data:", (unsigned int)RxHeader.ExtId, (int)RxHeader.DLC);
}
for (int i = 0; i < RxHeader.DLC; i++) {
printf(" %02X", RxData[i]);
}
printf("\r\n");
}
// else (取得失敗時は特になにもしないか、エラーログなど)
}
/ USER CODE END 4 /
“`
このコードを実装し、UARTも設定・有効化してPCと接続すれば、CANバス上でフィルタリングを通過したメッセージを受信するたびに、その情報がシリアルターミナルに表示されるようになります。
7.5. 受信フィルターの具体的な設定例
前述のSTM32CubeIDEでの設定に加えて、コード中でフィルターを設定することも可能です。ただし、CubeIDEで設定した内容が初期化関数 MX_CAN_Init
に含まれるため、通常はCubeIDEでの設定で十分です。
もしコード中で設定を変更する必要がある場合は、HAL_CAN_ConfigFilter
関数を使用します。この関数は、CANペリフェラル初期化後、HAL_CAN_Start
を呼び出す前に実行する必要があります。
“`c
// main関数のMX_CAN_Init(); の後、HAL_CAN_Start(&hcan1); の前に追記
CAN_FilterTypeDef sFilterConfig;
// 受信フィルター設定例:標準ID 0x123のみを受信する
sFilterConfig.FilterBank = 0; // 使用するフィルターバンク番号 (0~13など、マイコンによる)
sFilterConfig.FilterMode = CAN_FILTERMODE_IDMASK; // マスクモード
sFilterConfig.FilterScale = CAN_FILTERSCALE_32BIT; // 32ビットスケール
// フィルターIDとマスクの設定 (標準ID 0x123 を左寄せして32ビットにする)
// 標準ID 11ビット = 0x123
// これを32ビットレジスタの[31:21]に配置 -> 0x123 << 21 = 0x24600000
sFilterConfig.FilterIdHeader = 0x24600000;
// 標準ID 11ビットに対応するマスクを32ビットにする
// 11ビット全てを比較対象にするマスク -> (1 << 11) – 1 = 0x7FF
// これを[31:21]に配置 -> 0x7FF << 21 = 0xFFE00000
sFilterConfig.FilterMaskIdHeader = 0xFFE00000;
sFilterConfig.FilterFIFOAssignment = CAN_RX_FIFO0; // マッチしたメッセージを格納するFIFO
sFilterConfig.FilterActivation = ENABLE; // フィルター有効化
sFilterConfig.SlaveActivation = DISABLE; // Dual CANの場合に使用(通常DISABLE)
// フィルターを適用
if (HAL_CAN_ConfigFilter(&hcan1, &sFilterConfig) != HAL_OK)
{
// フィルター設定失敗、エラー処理
Error_Handler(); // 例
}
// その後、HAL_CAN_Start(&hcan1); を呼び出す
“`
マスクモードでは、FilterIdHeader
と FilterMaskIdHeader
を設定します。
FilterMaskIdHeader
の対応するビットが ‘1’ の場合、そのビット位置での受信メッセージのIDと FilterIdHeader
の対応するビットが一致する必要があります。
FilterMaskIdHeader
の対応するビットが ‘0’ の場合、そのビット位置は一致する必要がありません(どんな値でもマッチします)。
例えば、IDの上位4ビットが0x12である標準IDのメッセージを受信したい場合は、以下のようになります。
標準ID: 11ビット
IDの上位4ビット 0x12 = 0b00010010
フィルターID (左寄せ): 0b00010010xxxxxxx000000…0000 (xは任意の値)
マスク (左寄せ): 0b11110000000xxxxxx000000…0000 (上位4ビットを比較、残りは比較しない)
FilterIdHeader: 0b00010010000000000000000000000000 = 0x12000000
FilterMaskIdHeader: 0b11110000000000000000000000000000 = 0xF0000000
このように、受信したいメッセージのID範囲に応じて、フィルターIDとマスクを計算して設定します。
8. 応用的なトピックとトラブルシューティング
8.1. CANのエラー処理
CANペリフェラルは様々なエラーを検出します(ビットエラー、スタッフィングエラー、CRCエラー、ACKエラーなど)。これらのエラー発生時に通知を受けるには、STM32CubeIDEのNVIC設定で CAN Error interrupts
を有効化します。
エラーが発生すると、HAL_CAN_ErrorCallback
というコールバック関数が呼び出されます。この関数内で、HAL_CAN_GetError(&hcan)
関数を使って発生したエラーコードを取得し、適切なエラー処理(ログ出力、再試行、ノード状態の確認など)を行います。
“`c
/ USER CODE BEGIN 4 /
/
* @brief Error callback.
* @param hcan pointer to a CAN_HandleTypeDef structure that contains
* the configuration information for the specified CAN.
* @retval None
/
void HAL_CAN_ErrorCallback(CAN_HandleTypeDef hcan)
{
uint32_t error_code = HAL_CAN_GetError(hcan);
// 発生したエラーコードに応じて処理を分岐
if ((error_code & HAL_CAN_ERROR_EWG) != 0)
{
// エラーワーニング (Error Warning)
// エラーカウンターが特定閾値を超えた
}
if ((error_code & HAL_CAN_ERROR_EPV) != 0)
{
// エラーパッシブ (Error Passive)
// エラーカウンターがさらに高い閾値を超えた
}
if ((error_code & HAL_CAN_ERROR_BOF) != 0)
{
// バスオフ (Bus-Off)
// エラーカウンターが最大値を超え、バスから切り離された
// この状態になったら、通常はHAL_CAN_Start() を呼び出して復旧を試みる
}
if ((error_code & HAL_CAN_ERROR_STF) != 0)
{
// スタッフィングエラー (Stuff Error)
}
// 他のエラーコードも同様にチェック…
// エラー状態をクリアする (必要に応じて)
// __HAL_CAN_CLEAR_FLAG(hcan, flag);
}
/ USER CODE END 4 /
“`
エラー処理を適切に行うことで、システム全体の堅牢性を向上させることができます。特にBus-Off状態は通信が完全に停止するため、復旧処理の実装が重要です。
8.2. ループバックモードでのテスト
開発の初期段階やデバッグ時には、CANバスに接続せずにCAN通信機能をテストしたい場合があります。STM32CubeIDEのCAN設定でModeを Loopback
に設定すると、送信したメッセージがCANペリフェラルの内部で自分自身の受信FIFOに回送されます。バスには実際には送信されません。
このモードを使用することで、ハードウェア接続なしに送信・受信のコードロジックが正しく動作するかを確認できます。送信コードを記述し、受信割り込みコールバックで受信したメッセージの内容を確認するといったテストが可能です。
8.3. トラブルシューティング
CAN通信がうまくいかない場合、以下の点を中心に確認してください。
- ハードウェア接続:
- CANHとCANLの配線が正しいか?(反転していないか)
- CANトランシーバーモジュールのVCC/GNDは適切に接続されているか?
- STM32とCANトランシーバー間のTXD/RXD接続は正しいか?
- CANバスの両端に120Ωの終端抵抗が正しく取り付けられているか?(バスの途中にあるノードには終端抵抗は不要)
- STM32CubeIDE設定:
- System Clock設定は正しいか?(CANペリフェラルのクロック源周波数を確認)
- CANペリフェラルのModeは
Normal
になっているか?(テスト中ならLoopback
) - ボーレート設定は、バス上の全ノードで一致しているか?(最もよくある原因)Prescaler, TS1, TS2, SJWの設定値を確認する。計算されたSample Pointが適切か確認する。
- CANペリフェラルが割り当てられたGPIOピン設定は正しいか?Alternate FunctionがCANになっているか?
- 受信フィルター設定は正しいか?期待するIDのメッセージがフィルターを通過できる設定になっているか?(一旦、全IDを受信する設定(Mask=0, ID=0 またはフィルター無効)にしてテストしてみるのも有効)
- 割り込みを使用する場合、NVICで該当するCAN割り込みが有効化されているか?
- ソフトウェア:
HAL_CAN_Start(&hcan1);
が呼び出されているか?- 受信割り込みを使用する場合、
HAL_CAN_ActivateNotification
が呼び出されているか? - コールバック関数の名前は正しいか? (
HAL_CAN_RxFifo0MsgPendingCallback
など) - コールバック関数内でメッセージ取得 (
HAL_CAN_GetRxMessage
) は正常に行えているか? - 送信の場合、
HAL_CAN_AddTxMessage
の戻り値はHAL_OK
か? - TxHeader/RxHeaderやTxData/RxDataの構造体/配列へのポインタは正しいか?
- 複数のノードで通信する場合、それぞれのノードが同じボーレート、同じCAN IDフォーマット(標準/拡張)を使用しているか?
- デバッグツール:
- オシロスコープを使って、CANHとCANLの波形を確認する。差動信号が出ているか? ドミナント/リセッシブの電圧レベルは正しいか? ボーレートは合っているか?
- ロジックアナライザを使って、CAN信号のビット列を解析する。フレームフォーマットは正しいか? スタッフィングエラーなどが発生していないか?
- PC用CANアダプターとCANモニターソフトウェアを使って、バス上の全メッセージを確認する。自ノードが送信したメッセージが見えるか? 他ノードからのメッセージが見えるか? エラーフレームが頻繁に出ていないか?
特にボーレートの不一致は通信できない原因の大部分を占めます。計算が正しいか、全てのノードで同じ設定になっているか、必ず確認してください。
9. まとめと次のステップ
この記事では、STM32マイクロコントローラーを使用してCAN通信プログラミングの基礎を学ぶためのステップを詳細に解説しました。
- CAN通信の基本的な原理(物理層、データリンク層、フレームフォーマット、アービトレーション、エラー検出)を理解しました。
- STM32のCANペリフェラルの機能と、STM32CubeIDEを使った設定方法(Mode, Time Settings, Filter Settings, NVIC)を学びました。
- HALライブラリを使ったCANメッセージの送信と受信のプログラミング方法を、具体的なコード例とともに確認しました。特に、割り込みを使った受信処理の重要性を理解しました。
- エラー処理の考え方や、ループバックモードを使ったテスト方法、そしてトラブルシューティングのポイントについても触れました。
これらの基礎を習得すれば、STM32を使った組み込みシステムでCAN通信機能を実装するための土台はできています。
次のステップとして、以下のことに挑戦してみましょう。
- より複雑なデータ構造: CANメッセージ1つで送れるデータは最大8バイトです。より大きなデータを送るためには、複数のCANフレームに分割して送信し、受信側で再構築する仕組み(マルチパケット通信)を実装する必要があります。
- CANプロトコル: 単にデータを送受信するだけでなく、SAE J1939 (自動車・トラック)、CiA 402 (モーション制御)、DeviceNet (産業用) など、特定の用途に特化したCANベースのプロトコルを学ぶことで、より実践的なアプリケーション開発が可能になります。これらのプロトコルは、メッセージIDの割り当てやデータのフォーマット、状態遷移などが標準化されています。
- CAN FD: 高速なデータ通信が必要な場合は、CAN FDに対応したSTM32マイコンを使って、CAN FDのプログラミングに挑戦してみましょう。CAN FDはデータフィールドが最大64バイトになり、データ相の通信速度をNominal相より高速に設定できます。
- リアルタイムOSとの連携: FreeRTOSなどのリアルタイムOSを使用する場合、タスク間でCANメッセージを送受信するための仕組み(キューやセマフォなど)をOSの機能を使って実装する必要があります。
- 応用プロジェクト: 実際にセンサーデータをCANで送信したり、受信したCANメッセージに基づいてモーターを制御したりするなど、具体的な応用プロジェクトに取り組むことで、理解を深めることができます。
CAN通信は、組み込みシステムにおいて非常に強力で信頼性の高い通信手段です。この記事が、あなたがSTM32とCAN通信をマスターし、様々な組み込みアプリケーション開発に活かすための一助となれば幸いです。学習を進める中で、分からない点があれば公式のリファレンスマニュアルやデータシート、コミュニティフォーラムなどを活用してください。
組み込み開発の旅を楽しんでください!