STM32で始めるCAN通信:機能と使い方入門

STM32で始めるCAN通信:機能と使い方入門

はじめに

組み込みシステム開発において、複数のノード間での効率的かつ信頼性の高いデータ通信は不可欠です。特に自動車、産業機器、医療機器といった分野では、リアルタイム性や堅牢性が求められるため、特定の通信プロトコルが広く利用されています。その代表的なものの一つが、CAN (Controller Area Network) 通信です。

CANは、その分散制御に適したアーキテクチャ、高い信頼性、そして優先度に基づいたメッセージ通信という特徴から、特に車載ネットワークでデファクトスタンダードとなっています。近年では、産業オートメーション、ビルディングオートメーション、さらには家電製品など、様々な分野でその利用が拡大しています。

多くのマイクロコントローラーメーカーがCANコントローラーを内蔵した製品を提供していますが、中でもSTMicroelectronics社のSTM32シリーズは、その豊富なラインナップ、高性能、そして使いやすい開発環境(STM32CubeIDEなど)から、非常に人気があります。STM32は、エントリーレベルのマイコンから高性能なマイコンまで、多くの製品にCANインターフェースを搭載しており、CAN通信を学習・開発する上で最適なプラットフォームと言えます。

本記事は、STM32を使って初めてCAN通信に取り組む方を対象に、CAN通信の基本的な仕組みから、STM32のCANコントローラーの機能、そして実際にSTM32CubeIDEとHALライブラリを使ってCAN通信を設定・プログラミングする方法までを、詳細かつ実践的に解説します。約5000語にわたる内容を通じて、CAN通信の理解を深め、ご自身のプロジェクトにCANを組み込むための知識とスキルを習得していただくことを目指します。

CAN通信の複雑さに戸惑うことなく、一歩ずつ確実に理解を進めていきましょう。

1. CAN通信の基礎

まず、CAN通信とは何か、その基本的な仕組みについて理解することから始めます。

1.1 CANとは何か?

CANは「Controller Area Network」の略称で、マイクロコントローラーやデバイス間でメッセージベースの通信を行うためのマルチマスターシリアル通信プロトコルです。Robert Bosch GmbHが1980年代に自動車産業向けに開発し、ISO 11898として標準化されています。

1.2 CANが開発された背景と目的

自動車の電子制御化が進むにつれて、従来の1対1の通信方式(例えば、エンジンECUとABS ECUがそれぞれ個別の配線で通信する)では、配線が非常に複雑化し、コストや重量が増大するという問題が生じました。また、複数のシステムが同じセンサーデータを利用する場合、センサーを複数設置するか、複雑なデータ共有メカニズムを構築する必要がありました。

CANは、これらの問題を解決するために開発されました。主な目的は以下の通りです。

  • 配線の削減: 複数のノード(ECUなど)が一本のバスを共有するマルチマスター方式を採用することで、配線を大幅に削減できます。
  • 信頼性の向上: 分散制御に適しており、特定ノードの障害がシステム全体に与える影響を最小限に抑えます。また、強力なエラー検出・通知・訂正メカニズムを備えています。
  • リアルタイム性の確保: メッセージの優先度をIDによって決定し、より優先度の高いメッセージを優先的にバスへ送信することで、重要な情報のリアルタイム伝送を保証します。
  • モジュール化の促進: 各ノードは他のノードの存在を知らなくてもメッセージを送信でき、受信側は必要なメッセージのみを選択して受信できます(メッセージ指向通信)。これにより、システムの設計や変更が容易になります。

1.3 CANの主な特徴

CAN通信の主要な特徴をいくつか挙げます。

  • マルチマスター方式: バス上に接続された全てのノードは、マスターにもスレーブにもなり得ます。特定のマスターが存在せず、どのノードもバスがアイドル状態であれば送信を開始できます。
  • メッセージ指向通信: データはノードのアドレスではなく、メッセージの識別子(ID)に関連付けられています。これにより、柔軟なネットワーク構成が可能です。
  • ノンデストラクティブ・アービトレーション: 複数のノードが同時に送信を開始しようとした場合、メッセージIDの大小に基づいてバスの調停(アービトレーション)が行われます。IDが小さい(数値が低い)メッセージほど優先度が高く、アービトレーションに勝利してバスの使用権を得ます。衝突が発生してもデータが破壊されることなく、優先度の低いノードは自動的に送信を遅延させます。
  • 差動信号: CAN-High (CANH) とCAN-Low (CANL) の2本の信号線間の電位差でデータを伝送します。これにより、ノイズに強く、長距離伝送に適しています。
  • 強力なエラー検出・通知: CRC (Cyclic Redundancy Check)、ビットスタッフィング違反チェック、ACKエラー、フォームエラーなど、複数のエラー検出メカニズムを備えています。エラーが検出されると、エラーフレームを送信して他のノードにエラー発生を通知します。
  • 最大データレート: 標準的なCAN 2.0では、最大1Mbps(バス長による)の通信速度をサポートします。
  • データ長: 1つのメッセージフレームで最大8バイトのデータを送信できます。

1.4 CANの物理層

CANの物理層は、データの電気的な伝送方法を規定しています。ISO 11898-2 (High-Speed CAN) が一般的です。

  • 信号線: CANHとCANLの2本のツイストペアケーブルを使用します。
  • 信号レベル: バスがアイドル状態のときは、CANHとCANLは共に約2.5Vになります (Recessive状態)。CANHが約3.5V、CANLが約1.5VになったときをDominant状態と呼びます。Dominant状態はRecessive状態より優先されます。
    • Recessive: CANH ≈ 2.5V, CANL ≈ 2.5V (電位差 ≈ 0V) – 論理 ‘1’ に相当
    • Dominant: CANH ≈ 3.5V, CANL ≈ 1.5V (電位差 ≈ 2V) – 論理 ‘0’ に相当
  • バス終端抵抗: CANバスの両端には、信号の反射を防ぐために終端抵抗が必要です。通常、120Ωの抵抗器をバスの最遠端のノードに接続します。バス全体のインピーダンスは約60Ωになります。抵抗が不足している、あるいは多すぎると、信号波形が劣化し、通信エラーの原因となります。
  • CANトランシーバー: マイクロコントローラーのCANコントローラー(デジタル信号)とCANバス(差動信号)の間を電気的に変換・分離するのがCANトランシーバーICです。これは物理層の必須コンポーネントです。トランシーバーは、CANコントローラーからの送信データ(TX)をCANH/CANLの差動信号に変換してバスに出力し、バス上のCANH/CANL信号を受信データ(RX)としてCANコントローラーに伝えます。また、バスのショートや過電圧からの保護機能も持ちます。

1.5 CANのデータリンク層

CANのデータリンク層は、メッセージの構造、アービトレーション、エラー処理などを規定しています。主に以下のフレームタイプがあります。

  • データフレーム (Data Frame): 通常のデータ通信に使用されるフレームです。
  • リモートフレーム (Remote Frame): 特定のIDを持つデータフレームの送信を要求するためのフレームです。
  • エラーフレーム (Error Frame): バス上でエラーが検出されたことを通知するためのフレームです。
  • オーバーロードフレーム (Overload Frame): 受信バッファが満杯であるなど、ノードが一時的に受信できない状態であることを通知するためのフレームです。

最も一般的なデータフレームについて詳しく見ていきます。データフレームには、標準フォーマット (Standard Format) と拡張フォーマット (Extended Format) があります。

1.5.1 データフレームの構造 (Standard Format / Extended Format)
フィールド ビット数 (Standard) ビット数 (Extended) 説明
SOF (Start of Frame) 1 1 フレームの開始を示すDominantビット。
Arbitration Field バス調停に使用されます。IDが小さいほど優先度が高い。
  Identifier 11 (Standard ID) 29 (Extended ID) メッセージを一意に識別するID。StandardはSAE J1939など、Extendedはより柔軟なシステムで使用されます。
  RTR (Remote Transmission Request) 1 1 0: データフレーム, 1: リモートフレーム を示す。
Control Field データの属性を示します。
  IDE (Identifier Extension) 1 1 0: Standard Format, 1: Extended Format を示す。ExtendedではIdentifierの後にRTRと続きます。
  r0 (Reserved) 1 1 将来の利用のために予約されたビット。通常Dominant。Extendedではr1, r0の2ビットになります。
  DLC (Data Length Code) 4 4 データフィールドのバイト数 (0~8)。
Data Field 0~64 (0~8バイト) 0~64 (0~8バイト) 実際のデータ。DLCで示されるバイト数。
CRC Field エラー検出に使用されます。
  CRC Sequence 15 15 SOFからData Fieldまでのビットに基づいて計算されたCRC値。
  CRC Delimiter 1 1 Recessiveビット。CRCフィールドの終了を示す。
ACK Field 受信確認に使用されます。
  ACK Slot 1 1 受信ノードがエラーなく受信した場合、このビットをDominantにする(ACK)。
  ACK Delimiter 1 1 Recessiveビット。ACKフィールドの終了を示す。
EOF (End of Frame) 7 7 フレームの終了を示す7つのRecessiveビット。
IFS (Interframe Space) 3 3 フレーム間の最小間隔。3つのRecessiveビット(エラーフラグ/オーバーロードフラグが挿入される場合あり)。
1.5.2 バス調停 (Arbitration)

複数のノードが同時に送信を開始しようとした場合、Arbitration FieldのIdentifierを使って調停が行われます。各ノードはSOFに続いてIdentifierの各ビットをバスに出力し、同時にバス上の信号を監視します。

  • 各ビットについて、バス上の信号が自分が送信したビットと同じであるかを確認します。
  • Dominant (0) はRecessive (1) より優先されます。もし自分がRecessive (1) を送信したにも関わらず、バス上の信号がDominant (0) であった場合、それは他のノードがDominantを送信していることを意味します。このノードはアービトレーションに敗北したと判断し、送信を中止して受信モードに切り替えます。
  • Identifierは数値が小さいほど多くのDominantビットを先頭に持ちやすいため、数値が小さいIDを持つメッセージほど優先度が高くなります。
  • アービトレーションに勝利したノードのみが、引き続きフレームの送信を許可されます。

この仕組みにより、バス上での衝突が発生してもデータが破壊されることなく、優先度の高いメッセージが速やかにバスを使用できます。

1.5.3 ビットスタッフィング (Bit Stuffing)

CANプロトコルでは、同期を維持し、エラー検出を容易にするために「ビットスタッフィング」という手法が用いられます。

これは、同一のビットレベル(DominantまたはRecessive)が5ビット連続した場合、強制的に反対のビット(Stuff Bit)を1ビット挿入するというルールです。例えば、「00000」の後には必ずRecessive (1) が挿入され、「11111」の後には必ずDominant (0) が挿入されます。

挿入される場所は、SOF、Arbitration Field、Control Field、Data Field、CRC Sequenceです。これらのフィールドを送信する際、送信側は5ビット連続する同一ビットパターンを検出すると、Stuff Bitを挿入して送信します。受信側は、受信したビットストリームからStuff Bitを検出して取り除き、元のデータを復元します。

このルールにより、バス上には常に変化する波形パターンが存在し、ノード間の同期が維持されやすくなります。また、スタッフィングルールに違反したパターン(例:6ビット連続する同一ビット)はエラーとして検出されます。

1.5.4 エラー検出と通知

CANは以下の強力なエラー検出メカニズムを備えています。

  • CRCエラー: 受信したフレームのCRCシーケンスを再計算し、受信したものと一致するかを確認します。
  • ACKエラー: 送信したデータフレームまたはリモートフレームに対して、最低1つの受信ノードからACKが返されない場合に発生します。
  • Formエラー: フレームの固定フォーマット部分(DelimitersやEOFなど)が規定のビットレベルでない場合に発生します。
  • Stuffエラー: ビットスタッフィングのルール(5ビット連続後のStuff Bit)に違反している場合に発生します。
  • Bitエラー: 送信ノードがバスに出力したビットと、バスから読み取ったビットが異なる場合に発生します(アービトレーション中の敗北を除く)。

いずれかのノードがエラーを検出すると、エラーフレームをバスに送信します。エラーフレームは、意図的にビットスタッフィング違反を含む特殊なパターン(通常は6ビット連続する同一ビット)であり、これにより他の全てのノードはエラーが発生したことを認識します。

エラーフレームを受信したノードは、エラーカウンタ(Transmit Error Counter: TEC, Receive Error Counter: REC)を増加させます。これらのカウンタの値に基づいて、ノードは以下の状態遷移を行います。

  • Error Active: 通常の状態。エラーフレームを送信できます。
  • Error Passive: エラーカウンタが一定値を超えた状態。エラーフレームの代わりにPassive Error Flag(6ビットのRecessive)を送信します。他のノードの通信を妨害しにくくなります。
  • Bus-off: エラーカウンタがさらに閾値を超えた状態。バスへの送信を完全に停止し、バスから切り離されます。システムのリセットや特定のリカバリ手順を踏まなければ通信に復帰できません。

このエラーカウンタと状態遷移の仕組みにより、一時的なノイズによるエラーと、ノード自体の故障によるエラーを区別し、ネットワーク全体の信頼性を維持しています。

2. STM32のCANコントローラー

STM32マイクロコントローラーは、製品ラインナップによって異なる種類のCANコントローラーを搭載しています。主に以下の2種類があります。

  • bxCAN: “basic extended CAN” の略で、FDCANが登場する以前の多くのSTM32製品に搭載されていました(例:STM32F1, STM32F4の一部など)。ISO 11898-1 (CAN 2.0A/B) に準拠しています。
  • FDCAN: “Flexible Data-rate CAN” の略で、新しいSTM32製品(例:STM32G4, STM32H7, STM32L4+など)に搭載されています。CAN FD (CAN with Flexible Data-rate) プロトコルに対応しています。CAN FDは、Control FieldとCRC Fieldの間に位置するData Phaseにおいて、Nominal Bit Rateより高速なData Bit Rateでより多くのデータ(最大64バイト)を送信できる拡張仕様です。FDCANはCAN 2.0モードでも動作可能です。

本記事では、より一般的なbxCANをベースに説明を進めますが、FDCANについても言及します。HALライブラリを使用する場合、多くのAPIはbxCANとFDCANで共通または類似しており、抽象化されています。

2.1 STM32 CANコントローラーの主な機能ブロック

STM32のCANコントローラー(bxCAN/FDCAN)は、ハードウェアレベルでCANプロトコルの多くの処理を行います。主要な機能ブロックは以下の通りです。

  • 送信機能 (Transmit):

    • 送信メールボックス (Transmit Mailboxes): 送信待ちのメッセージを格納するためのバッファです。bxCANでは通常3つのメールボックスを持ちます。複数のメッセージが同時に送信要求された場合、優先度(メッセージID)に基づいてアービトレーションが行われ、バスの使用権を得たメッセージが送信されます。優先度はハードウェアによって自動的に処理されます。
    • 送信要求キュー管理: アプリケーションからの送信要求を受け付け、空いているメールボックスに割り当てます。
    • アービトレーション処理: バス上のアービトレーションをハードウェアで実行します。
    • フレーム構築: ユーザーが指定したID、RTR、DLC、データを基にCANフレームを構築します。
    • ビットスタッフィング: 送信するフレームデータにビットスタッフィングを適用します。
    • CRC生成: フレームのCRC値を計算します。
    • バスへの出力: CANトランシーバーを介してバスに信号を出力します。
  • 受信機能 (Receive):

    • 受信FIFO (Receive FIFOs): 受信したメッセージを一時的に保持するバッファです。bxCANはFIFO0とFIFO1の2つのFIFOを持ちます。受信メッセージはフィルタ設定に基づいていずれかのFIFOに格納されます。
    • バスからの入力: CANトランシーバーからバス信号を受け取ります。
    • ビットスタッフィング解除: 受信したビットストリームからStuff Bitを取り除きます。
    • フレーム解析: 受信したビットストリームからSOF、Arbitration Field、Control Field、Data Field、CRC Field、ACK Field、EOFを解析します。
    • エラー検出: CRCエラー、Formエラー、Stuffエラー、Bitエラー、ACKエラーなどをハードウェアで検出します。
    • ACK応答生成: エラーなく受信できた場合、ACKビットを生成してバスに返信します。
    • フィルタリング: 受信したメッセージのIDに対して、設定されたフィルタを適用します。
    • FIFOへの格納: フィルタを通過したメッセージを対応する受信FIFOに格納します。FIFOが満杯の場合はメッセージが破棄される可能性があります。
  • フィルタ機能 (Filter Mechanism):

    • 受信メッセージのIDに基づいて、必要なメッセージだけを受信FIFOに格納するための機能です。ハードウェアでフィルタリングを行うため、ソフトウェアで全てのメッセージを処理するよりも効率的です。
    • フィルタバンク (Filter Banks): 複数のフィルタ設定を保持できるバンク(セット)です。bxCANは複数のフィルタバンク(製品によるが14個など)を持ちます。
    • フィルタモード (Filter Mode):
      • Identifier Mask Mode: 指定したIDとマスク値を使って、特定のID範囲またはパターンに一致するメッセージを受け入れます。
      • Identifier List Mode: 事前に定義された特定のIDリストに一致するメッセージのみを受け入れます。
    • フィルタスケール (Filter Scale): フィルタの比較単位を設定します。
      • Single 32-bit Scale: 1つのフィルタバンクで1つの32ビットIDフィルタ(Standard ID/Extended ID)を設定できます。
      • Dual 16-bit Scale: 1つのフィルタバンクを2つに分割し、それぞれで16ビットIDフィルタ(Standard IDまたはExtended IDの下位16ビット)を設定できます。
    • 各フィルタバンクは、受信したメッセージをFIFO0またはFIFO1のどちらに転送するかを設定できます。
  • ビットタイミング制御 (Bit Timing Control):

    • CAN通信速度(ビットレート)と、各ビットの時間分解能(タイムセグメント)を設定します。
    • Prescaler: CANクロックソース(例:APBクロック)を分周して、CANビットタイミングの基準となる時間単位(時間量子、Time Quantum: Tq)を生成します。
    • Sync Segment (SYNC_SEG): 各ビット時間の開始を示すセグメント。常に1 Tqです。
    • Timing Segment 1 (TS1): SYNC_SEGに続くセグメント。1 Tq~16 Tqの間で設定可能です。
    • Timing Segment 2 (TS2): TS1に続くセグメント。1 Tq~8 Tqの間で設定可能です。
    • Resync Jump Width (SJW): 再同期の際にTS1またはTS2を調整できる最大幅。1 Tq~4 Tqの間で設定可能です。
    • 1ビット時間 = SYNC_SEG + TS1 + TS2 です。
    • Sampling Point: TS1の終了時点に設定されるのが一般的です。ここでバス上の信号レベルをサンプリングして、ビットの値を決定します。適切なSampling Pointを設定することは、通信の信頼性にとって非常に重要です。
  • 割り込み機能 (Interrupts):

    • CANコントローラーは、送信完了、受信メッセージ pending、エラー発生など、様々なイベントで割り込みを生成できます。これにより、リアルタイム性が要求されるアプリケーションで効率的にCAN通信を処理できます。
  • エラーカウンタと状態レジスタ:

    • TECx (Transmit Error Counter) とRECx (Receive Error Counter) をハードウェアで管理し、エラー状態(Error Active, Error Passive, Bus-off)をトラッキングします。現在の状態やエラーフラグを確認するためのレジスタも用意されています。

2.2 bxCANとFDCANの比較(主要な違い)

機能 bxCAN (CAN 2.0) FDCAN (CAN FD対応)
プロトコル CAN 2.0A/B (Standard/Extendedフレーム) CAN FD, CAN 2.0A/B (互換性あり)
データレート 最大1 Mbps Nominal Phase: 最大1 Mbps, Data Phase: 最大8 Mbps (製品による)
データフィールド長 最大8バイト 最大64バイト (CAN FDモード時)
ビットタイミング 1つのビットタイミング設定 Nominal PhaseとData Phaseで異なるビットタイミング設定が可能
フィルタ 限定されたフィルタバンク数と柔軟性 より多くのフィルタ要素、より柔軟なフィルタリングルール
FIFO 受信FIFO0, FIFO1 受信FIFO0, FIFO1, 受信バッファ、送信イベントFIFOなど機能強化
タイムスタンプ メッセージ受信時のタイムスタンプ機能(製品による) より高精度なタイムスタンプ機能
その他 特定の用途に合わせた機能拡張(例:自動スリープ) CAN FDに特化した拡張機能(EDL, BRS, ESIビット対応など)

多くの入門レベルの学習やCAN 2.0のアプリケーションではbxCANで十分です。CAN FDの高速・大容量データ通信が必要な場合はFDCAN搭載のSTM32を選択します。HALライブラリを使用する場合、bxCANとFDCANで共通のAPIが多く、移行は比較的容易ですが、CAN FD固有の設定(Data Bit Timingなど)はFDCAN専用のAPIを使用します。

3. STM32でのCAN通信設定

ここでは、STM32CubeIDEを使用して、STM32マイクロコントローラーでCAN通信を設定する手順を具体的に解説します。使用するマイコンは、例としてSTM32F4シリーズ(bxCAN搭載)を想定しますが、他のシリーズでも基本的な流れは同様です。FDCANの場合も、CANペリフェラルの名前や一部設定項目が異なりますが、全体像は掴めるはずです。

3.1 ハードウェアの準備

CAN通信を行うためには、最低限以下のハードウェアが必要です。

  • STM32開発ボード: CANインターフェースを持つもの(例:Nucleo-F401RE, Discovery-F407VGなど)。
  • CANトランシーバーICと周辺部品: STM32のCAN_TX/CAN_RXピンはTTLレベルのデジタル信号なので、そのままではCANバスに接続できません。物理層の要件を満たすために、CANトランシーバーICが必要です(例:SN65HVD230, MCP2551, TJA1050など)。開発ボード上にトランシーバーICが搭載されている場合もあります。もしボードに搭載されていない場合は、別途トランシーバーICを使った回路をブレッドボード等で組む必要があります。トランシーバーICには、3.3Vまたは5Vの電源供給、CAN_TX、CAN_RX、CANH、CANLピンがあります。
  • 終端抵抗: CANバスの両端に120Ωの抵抗が必要です。これも開発ボードやトランシーバーモジュールにジャンパーピン等でON/OFF切り替え可能なものが付いていることが多いです。
  • CANバスケーブル: CANHとCANLを接続するためのツイストペアケーブル。
  • (オプション)CANアナライザー: PCに接続し、CANバス上のメッセージをモニタリングしたり、メッセージを送信したりできるツールです。デバッグに非常に役立ちます。USB-CAN変換アダプターなどがあります。

配線:

  1. STM32ボードのCAN_TXピンをCANトランシーバーICのTXDピンに接続。
  2. STM32ボードのCAN_RXピンをCANトランシーverICのRXDピンに接続。
  3. CANトランシーバーICのCANHピンをCANバスのCANH線に接続。
  4. CANトランシーバーICのCANLピンをCANバスのCANL線に接続。
  5. CANトランシーバーICに電源(VCC, GND)を供給。スタンバイ/スロープ制御ピンなどがある場合は適切に処理。
  6. CANバスの両端に120Ωの終端抵抗を接続。これはバス上に接続されたノードの中で、物理的にバスの末端にある2つのノードで行います。

3.2 ソフトウェアの準備

  • STM32CubeIDE: STMicroelectronicsが提供する無償の統合開発環境です。プロジェクト作成、ピン設定、ペリフェラル設定、コード生成、コンパイル、デバッグまでをこれ一つで行えます。
  • STM32 HAL/LLライブラリ: STM32CubeIDEがプロジェクト作成時に自動的にダウンロード・組み込みを行います。HAL (Hardware Abstraction Layer) は、マイコンの具体的なレジスタ操作を隠蔽し、抽象的なAPIを提供します。LL (Low-Level) ライブラリは、よりレジスタに近い操作を提供しますが、複雑な設定が必要です。本記事では、開発効率の高いHALライブラリを中心に解説します。

3.3 STM32CubeIDEを使った設定

3.3.1 新規プロジェクト作成

STM32CubeIDEを起動し、「File」>「New」>「STM32 Project」を選択します。使用するマイコン(Part Number)またはボード(Board Selector)を選択し、プロジェクト名、プロジェクトタイプ(例:STM32Cube)、ツールチェーンなどを設定してプロジェクトを作成します。

3.3.2 クロック設定 (RCC)

System Core > RCCを開き、HSE (High Speed External) またはHSI (High Speed Internal) を適切なソースとして設定します。CANペリフェラルはAPB1バスに接続されていることが多いので、APB1タイマーのクロック周波数を確認します。CANのビットタイミング設定はこのクロック周波数を基に行います。例えば、APB1タイマークロックが42MHzであるとします。

3.3.3 CANペリフェラルの有効化とピン設定

Connectivity > CANx(CAN1, CAN2など、マイコンによって異なる)を選択し、「Mode」を「Activated」にします。

すると、CANのピン(CAN_RX, CAN_TX)が自動的に割り当てられます。ピンが競合している場合は、代替ピンを選択する必要があります。ピン設定は「Pinout & Configuration」ビューで行います。例えば、CAN1であればPA11 (RX), PA12 (TX) といったピンが割り当てられます。

3.3.4 CANパラメータ設定の詳細

CANxペリフェラルを選択した状態で、ConfigurationタブのParametersを開きます。ここでCAN通信の重要な設定を行います。

3.3.4.1 Mode Settings
  • Mode: CANの動作モードを選択します。
    • Normal: 通常の送受信モード。他のノードと通信します。
    • Loopback: 送信したメッセージが内部で受信回路にフィードバックされるモード。外部のCANバスとは通信しません。自己診断やテストに使用します。
    • Silent: 受信のみを行うモード。バスに信号を出力しません。CANアナライザーとして使用できます。
    • Silent & Loopback: LoopbackとSilentを組み合わせたモード。

通常はNormalを選択します。

3.3.4.2 Time Settings (Bit Timing)

これが最も重要かつ設定が難しい部分です。CAN通信のビットレートと、各ビット時間の構成を設定します。設定値は、使用するマイコンのCANクロック周波数、 desiredビットレート、そしてネットワーク全体の遅延(バス長、トランシーバー遅延など)を考慮して決定する必要があります。

1ビット時間 = SYNC_SEG (1 Tq) + TS1 + TS2 です。
ここで、Tq (Time Quantum) = Prescaler値 / CANクロック周波数 です。
したがって、ビットレート = 1 / (Tq * (1 + TS1 + TS2)) です。
SJW (Resync Jump Width) は、再同期のための調整幅を設定します。通常、TS2より小さい値を設定します(例えば、TS2と同じ値)。

CubeIDEのGUIでは、Prescaler, Segment 1, Segment 2, Resync Jump Width (SJW) を設定します。設定値はレジスタ値(Prescalerは1~1024、TS1は1~16、TS2は1~8、SJWは1~4)に対応します。

例:CANクロック周波数が42MHzで、ビットレートを500kbpsに設定する場合

ビットレート = 500kbps = 500,000 bps
1ビット時間 = 1 / 500,000 s = 2 µs = 2000 ns

CANクロック周波数 = 42 MHz
Tq = Prescaler / 42 MHz

1ビット時間 = Tq * (1 + TS1 + TS2) = (Prescaler / 42,000,000) * (1 + TS1 + TS2)
2e-6 = (Prescaler / 42,000,000) * (1 + TS1 + TS2)
Prescaler * (1 + TS1 + TS2) = 2e-6 * 42,000,000 = 84

ここで、Prescaler, TS1, TS2の組み合わせを考えます。
Prescaler、TS1、TS2は整数値で、それぞれ上記の範囲内に収まる必要があります。
また、サンプリングポイント (Sampling Point) をどこに設定するかが重要です。Sampling Pointは通常、TS1の終了時点(すなわち SYNC_SEG + TS1 の後)に設定されます。理想的なSampling Pointは、1ビット時間の75%~85%の範囲が良いとされています。

Sampling Point位置 (Tq) = SYNC_SEG + TS1 = 1 + TS1
Sampling Point位置 (%) = (1 + TS1) / (1 + TS1 + TS2) * 100%

Prescaler * (1 + TS1 + TS2) = 84 を満たす組み合わせの中から、適切なSampling Pointになるもの、TS1 >= TS2 となるもの(一般的に推奨される設定)、そしてSJWを設定できる範囲(SJW <= TS2)のものを探します。

いくつか組み合わせを試してみます。

  1. Prescaler = 1, 1 + TS1 + TS2 = 84 -> TS1+TS2 = 83 (TS1=16, TS2=67はTS2>8で不可)
  2. Prescaler = 2, 1 + TS1 + TS2 = 42 -> TS1+TS2 = 41 (TS1=16, TS2=25はTS2>8で不可)
  3. Prescaler = 3, 1 + TS1 + TS2 = 28 -> TS1+TS2 = 27 (TS1=16, TS2=11はTS2>8で不可)
  4. Prescaler = 4, 1 + TS1 + TS2 = 21 -> TS1+TS2 = 20 (TS1=16, TS2=4)

    • TS1=16, TS2=4, Prescaler=4
    • 1ビット時間 = 4 Tq * (1 + 16 + 4) Tq = 4 Tq * 21 Tq = 84 Tq
    • Tq = 4 / 42 MHz = 95.238 ns
    • 1ビット時間 = 95.238 ns * 84 = 8000 ns = 8 µs (あれ?500kbpsは2µsだった…)
    • 計算方法が違いました。1ビット時間あたりのTq数は Prescaler * (1 + TS1 + TS2) ではありません。
    • 1ビット時間 = N * Tq = N * (Prescaler / CANクロック周波数)
    • N (1ビットあたりのTq数) = 1 + TS1 + TS2
    • ビットレート = CANクロック周波数 / (Prescaler * (1 + TS1 + TS2))
    • 500,000 = 42,000,000 / (Prescaler * (1 + TS1 + TS2))
    • Prescaler * (1 + TS1 + TS2) = 42,000,000 / 500,000 = 84

    • 改めて組み合わせを探します。

    • Prescaler * (1 + TS1 + TS2) = 84
    • TS1 ∈ [1, 16], TS2 ∈ [1, 8]

    • Prescaler = 6: 1 + TS1 + TS2 = 14 -> TS1 + TS2 = 13

      • TS1=8, TS2=5 (TS1 >= TS2を満たす組み合わせの一例)
      • N = 1 + 8 + 5 = 14
      • Prescaler = 6, TS1 = 8, TS2 = 5
      • SJW <= TS2 = 5 なので、SJWは例えば4に設定可能。
      • Sampling Point = (1 + TS1) / (1 + TS1 + TS2) * 100% = (1 + 8) / 14 * 100% = 9 / 14 * 100% ≈ 64.3% (少し早いか…)
    • Prescaler = 7: 1 + TS1 + TS2 = 12 -> TS1 + TS2 = 11

      • TS1=8, TS2=3
      • N = 1 + 8 + 3 = 12
      • Prescaler = 7, TS1 = 8, TS2 = 3
      • SJW <= TS2 = 3 なので、SJWは例えば3に設定可能。
      • Sampling Point = (1 + 8) / 12 * 100% = 9 / 12 * 100% = 75% (良い位置)
    • Prescaler = 8: 1 + TS1 + TS2 = 10.5 -> 組み合わせ無し

    • Prescaler = 12: 1 + TS1 + TS2 = 7 -> TS1 + TS2 = 6

      • TS1=5, TS2=1 (TS1 >= TS2 を満たす組み合わせの一例)
      • N = 1 + 5 + 1 = 7
      • Prescaler = 12, TS1 = 5, TS2 = 1
      • SJW <= TS2 = 1 なので、SJWは1に設定可能。
      • Sampling Point = (1 + 5) / 7 * 100% = 6 / 7 * 100% ≈ 85.7% (良い位置)

いくつかの組み合わせが考えられますが、Sampling Pointが75%~85%になる Prescaler=7, TS1=8, TS2=3, SJW=3 や Prescaler=12, TS1=5, TS2=1, SJW=1 などが考えられます。通常、TS1とTS2の値を大きく取る方が、ジッターに対する耐性が増すと言われます。ここでは Prescaler=7, TS1=8, TS2=3, SJW=3 を採用することにします。

CubeIDEのGUIでは、これらの値を直接入力します。Prescalerはレジスタ値-1で表示されることがあるので注意(HALではそのままの値)。Time Quanta for Bit Segment 1 (TS1), Time Quanta for Bit Segment 2 (TS2), Maximum ReSynchronization Jump Width (SJW) も同様に設定します。

  • FDCANの場合: Nominal Bit TimingとData Bit Timingの2セットの設定が必要になります。Nominal Bit TimingはCAN 2.0と同様にArbitration Phaseの速度(通常1Mbps以下)を設定し、Data Bit TimingはData Phaseの速度(数Mbps)を設定します。それぞれ独立したPrescaler, TS1, TS2, SJWを設定します。
3.3.4.3 Other Settings
  • Automatic Bus-off Management: 有効にすると、ノードがBus-off状態になった場合に、ハードウェアが自動的にリカバリを試みます。
  • Automatic Wakeup from Sleep Mode: CANアクティビティを検出してマイコンをスリープモードから自動的にウェイクアップさせる機能。
  • Receive FIFO Locked Mode: 受信FIFOがオーバーラン(満杯で新しいメッセージが受信できない)した場合、新しいメッセージを破棄するか、最も古いメッセージを上書きするかを選択します。Lockedモードでは新しいメッセージを破棄します。
  • Transmit FIFO Priority: 複数の送信メールボックスにメッセージが格納されている場合に、ハードウェアが送信するメッセージを選択する際の優先度を、要求順(FIFO)とするか、メッセージIDによるアービトレーション結果とするかを選択します。通常はメッセージIDによるアービトレーション (By Identifier) を選択します。
3.3.4.4 割り込み設定 (NVIC)

CAN通信を効率的に行うためには、割り込みを利用するのが一般的です。CubeIDEのPinout & Configurationビューで、System Core > NVICを開きます。CANxのグローバル割り込みや、Tx、Rx FIFO0、Rx FIFO1などの割り込みを有効にします。HALライブラリでは、これらの割り込みハンドラ内でコールバック関数が呼ばれる仕組みになっています。

3.3.5 CANフィルタ設定

前述の通り、CANコントローラーはハードウェアフィルタ機能を持っています。全てのメッセージを受信するのではなく、必要なメッセージだけを選択的に受信することで、CPUの負荷を軽減できます。

CubeIDEのPinout & Configurationビューで、Connectivity > CANx を開き、Parametersタブの下部にある「Filter Configurations」セクションを開きます。

  • Filter Mode: Identifier Mask または Identifier List を選択します。
    • Identifier Mask: 指定したFilter IDFilter Mask IDを使って、特定のIDパターンに一致するメッセージを受け入れます。マスクIDのビットが’1’の場合、対応するFilter IDのビットと受信メッセージIDのビットが一致する必要があります。マスクIDのビットが’0’の場合、一致する必要はありません。
    • Identifier List: Filter IDに設定した複数のIDと完全に一致するメッセージのみを受け入れます。
  • Filter Scale: Single 32-bit または Dual 16-bit を選択します。これはFilter Modeで設定したIDの形式と一致させる必要があります。Standard ID (11ビット) のみを使用する場合はDual 16-bit、Extended ID (29ビット) を使用する場合はSingle 32-bitを選択するのが一般的です。
  • Filter Number: 使用するフィルタバンク番号を選択します。マイコンによって利用可能なフィルタバンク数は異なります。
  • Filter associated with FIFO: フィルタを通過したメッセージを格納する受信FIFO(FIFO0またはFIFO1)を選択します。
  • Filter Activation: フィルタを有効にするかどうかを設定します。使用するフィルタバンクは全て有効にする必要があります。

例:Standard ID 0x123 と 0x124 のメッセージだけを受信する場合

  • Filter Mode: Identifier List
  • Filter Scale: Dual 16-bit (11ビットIDなので16ビットスケールが適切)
  • Filter Number: 例えば 0 を使用
  • Filter associated with FIFO: FIFO 0 または FIFO 1
  • Filter Activation: Enabled
  • Filter ID: ID 1 = 0x123 (Standard IDの場合、レジスタに格納する際は左寄せされることが多いですが、HALでは通常Raw IDを指定します。CubeIDEのGUIでは適切な形式で入力します。)
  • Filter ID: ID 2 = 0x124

Dual 16-bitモードでは、1つのフィルタバンクで2つの16ビットフィルタを設定できます。上記の設定で、フィルタバンク0はIdentifier List ModeのDual 16-bit Scaleとして構成され、ID 0x123または0x124のStandard IDメッセージのみをFIFO0に転送します。

例:Standard ID 0x200 ~ 0x20F の範囲のメッセージを受信する場合

  • Filter Mode: Identifier Mask
  • Filter Scale: Dual 16-bit
  • Filter Number: 例えば 1 を使用
  • Filter associated with FIFO: FIFO 0 または FIFO 1
  • Filter Activation: Enabled
  • Filter ID: 0x200 (このIDから始まる)
  • Filter Mask ID: 0xFF0 (下位4ビットはマスク = 任意、上位7ビットは一致が必要。Standard IDは11ビットなので、右詰めに注意。通常は11ビットを左寄せしたものを32ビットとして扱います。)

Standard ID (11ビット) 0x200 はバイナリで 01000000000
Standard ID (11ビット) 0x20F はバイナリで 01000001111
一致させるビット: 上位7ビット 0100000
可変ビット: 下位4ビット xxxx
マスク: 上位7ビット ‘1’, 下位4ビット ‘0’ -> バイナリ 11111110000 -> 0x7F0
Filter ID: 0x200
Filter Mask ID: 0x7F0 (Standard ID 11ビットの場合)
これを32ビットレジスタに格納する際は左寄せされるため、例えば Standard ID を 32ビット ID として扱う場合、ID は (0x200 << 21) 、マスクは (0x7F0 << 21) となることが多いです。HALのAPIやCubeIDEのGUIでは、どちらの形式で入力するか仕様を確認してください。CubeIDEのGUIは通常、Raw ID (11ビットまたは29ビット) で入力できます。

上記のフィルタ設定が完了したら、CubeIDEの「Generate Code」ボタンをクリックして、初期化コードを生成します。main.ccan.cファイルに、CANペリフェラルの初期化関数 (MX_CANx_Init) やフィルタ設定関数 (HAL_CAN_ConfigFilter) の呼び出し、そして割り込みハンドラが含まれたスケルトンコードが生成されます。

3.4 HALライブラリを使ったCAN通信プログラミング

生成されたコードをベースに、アプリケーションでCAN通信を行うためのプログラミングを行います。主に以下のHAL関数を使用します。

3.4.1 初期化と開始

CubeIDEで生成された MX_CANx_Init() 関数は、CANペリフェラルのクロック設定、ピン設定、パラメータ設定、フィルタ設定を行います。この関数をmain()関数内で呼び出します。

CAN通信を開始するには、HAL_CAN_Start() 関数を呼び出します。

“`c
/ CAN1 initialisation function /
static void MX_CAN1_Init(void); // CubeIDEが生成

int main(void)
{
// … マイコン初期化、System Clock設定 …

MX_CAN1_Init(); // CAN1のハードウェア設定

/##-2- Start the CAN peripheral #########################################/
if (HAL_CAN_Start(&hcan1) != HAL_OK) // hcan1はMX_CAN1_Initで初期化されるCAN_HandleTypeDef構造体
{
/ Start Error /
Error_Handler(); // エラー処理関数
}

// … アプリケーションコード …
}
“`

3.4.2 送信

メッセージを送信するには、まず送信するメッセージのヘッダー情報とデータを準備し、HAL_CAN_AddTxMessage() 関数を使って送信キュー(メールボックス)に追加します。

  • CAN_TxHeaderTypeDef 構造体: 送信するメッセージのヘッダー情報を格納します。

    • ExtId: 拡張ID (29ビット)
    • StdId: 標準ID (11ビット)
    • IDE: IDフォーマット (CAN_ID_STD または CAN_ID_EXT)
    • RTR: フレームタイプ (CAN_RTR_DATA または CAN_RTR_REMOTE)
    • DLC: データ長 (0~8バイト, CAN 2.0の場合)
    • TransmitGlobalTime: タイムスタンプを有効にするか
  • TxData: 送信するデータ(最大8バイト)を格納した配列。

  • TxMailbox: どのメールボックスにメッセージが追加されたかを示す変数(HAL_CAN_AddTxMessageの戻り値)。

“`c

include “can.h” // hcan1定義を含むヘッダーファイル

// 送信ヘッダーとデータ
CAN_TxHeaderTypeDef TxHeader;
uint8_t TxData[8];
uint32_t TxMailbox;

void Send_CAN_Message(uint32_t id, uint8_t data, uint8_t len, uint8_t id_type)
{
/
##-3- Configure Transmission process #####################################*/
TxHeader.StdId = (id_type == CAN_ID_STD) ? id : 0; // 標準IDまたは拡張IDを設定
TxHeader.ExtId = (id_type == CAN_ID_EXT) ? id : 0;
TxHeader.IDE = id_type; // 標準 or 拡張
TxHeader.RTR = CAN_RTR_DATA; // データフレーム
TxHeader.DLC = len; // データ長 (0-8)

// データフィールドに値をコピー
for(int i = 0; i < len; i++)
{
TxData[i] = data[i];
}

/##-4- Add a message to the Tx FIFO #######################################/
// AddTxMessageが成功すると、メッセージが送信キューに追加される
// 戻り値は使用されたメールボックス番号 (TxMailbox)
// HAL_MAX_DELAYはポーリングで送信完了を待つオプション。通常は割り込みで処理。
// キューが満杯の場合はHAL_ERRORを返す。
if (HAL_CAN_AddTxMessage(&hcan1, &TxHeader, TxData, &TxMailbox) != HAL_OK)
{
/ Transmission request Error /
Error_Handler();
}

// 送信完了を待つ場合は以下の関数を使用できるが、ノンブロッキングが推奨
// while(HAL_CAN_IsTxMessagePending(&hcan1, TxMailbox));
}

// main関数やタイマー割り込みハンドラなどから呼び出し
// 例: 標準ID 0x100, データ1,2,3,4 を送信
uint8_t my_data[] = {0x01, 0x02, 0x03, 0x04};
Send_CAN_Message(0x100, my_data, 4, CAN_ID_STD);
“`

送信完了の通知は、割り込みを使う場合は HAL_CAN_TxMailbox0CompleteCallback(), HAL_CAN_TxMailbox1CompleteCallback(), HAL_CAN_TxMailbox2CompleteCallback() といったコールバック関数で処理します。これらのコールバックを有効にするには、事前に HAL_CAN_ActivateNotification() 関数で送信完了割り込みを有効にしておく必要があります。

“`c
// main関数などでCAN開始後、割り込みを有効にする
if (HAL_CAN_ActivateNotification(&hcan1, CAN_IT_TX_MAILBOX_EMPTY) != HAL_OK)
{
/ Notification Error /
Error_Handler();
}

// stm32f4xx_hal_can.c に実体があるWEAK関数をオーバーライドする
// HAL_CAN_TxMailbox0CompleteCallback関数などが必要に応じて呼ばれる
// ただし、CAN_IT_TX_MAILBOX_EMPTY 割り込みは、メールボックスが空になったときに発生するため、
// 特定のメッセージの送信完了を通知するものではないことに注意。
// 特定メッセージの送信完了を知りたい場合は、キューが空になるのを待つか、
// 送信要求がキューに追加されたメールボックス番号を記録しておき、
// HAL_CAN_IsTxMessagePendingで確認するなど、より複雑な処理が必要になる。
// あるいは、HAL_CAN_AddTxMessage_IT (Polling版ではない割り込み版) を使用することも検討する。

/ CAN transmit mailbox empty callback. /
void HAL_CAN_TxMailboxEmptyCallback(CAN_HandleTypeDef *hcan)
{
// このコールバックは、いずれかの送信メールボックスが空になったときに呼ばれる
// 次のメッセージを送信キューに追加するなどの処理を行うことができる
}
“`

3.4.3 受信

メッセージを受信するには、受信FIFOにメッセージがあるかどうかを確認し、HAL_CAN_GetRxMessage() 関数を使ってメッセージを読み出します。割り込みを使う場合は、受信メッセージpending割り込みを有効にし、コールバック関数内でメッセージを処理します。

  • CAN_RxHeaderTypeDef 構造体: 受信したメッセージのヘッダー情報を格納します。送信ヘッダーとほぼ同じ構造を持ちます。
  • RxData: 受信したデータ(最大8バイト)を格納するための配列。

ポーリングでの受信例:

“`c

include “can.h”

CAN_RxHeaderTypeDef RxHeader;
uint8_t RxData[8];

void Check_And_Process_CAN_Messages(void)
{
/##-5- Check if a message is received in FIFO0 ##########################/
// 受信FIFO0にメッセージがあるか確認
if (HAL_CAN_GetRxFifoFillLevel(&hcan1, CAN_RX_FIFO0) > 0)
{
/ Get received message from FIFO0 /
// メッセージを読み出す
if (HAL_CAN_GetRxMessage(&hcan1, CAN_RX_FIFO0, &RxHeader, RxData) == HAL_OK)
{
/ Process received message /
// ここで受信したメッセージを処理するコードを記述
// RxHeader.StdId, RxHeader.ExtId, RxHeader.IDE, RxHeader.DLC, RxData[] を使う
// 例: Received ID: %lx, DLC: %d, Data: %02x %02x … をUARTで出力するなど
printf(“Received CAN Message:\r\n”);
if (RxHeader.IDE == CAN_ID_STD)
{
printf(” ID: 0x%lX (Std)\r\n”, RxHeader.StdId);
}
else
{
printf(” ID: 0x%lX (Ext)\r\n”, RxHeader.ExtId);
}
printf(” DLC: %d\r\n”, RxHeader.DLC);
printf(” Data:”);
for(int i = 0; i < RxHeader.DLC; i++)
{
printf(” %02X”, RxData[i]);
}
printf(“\r\n”);
}
}

/##-6- Check if a message is received in FIFO1 ##########################/
// 受信FIFO1も同様にチェック(フィルタ設定に応じて)
if (HAL_CAN_GetRxFifoFillLevel(&hcan1, CAN_RX_FIFO1) > 0)
{
// FIFO1からメッセージを読み出して処理… (コードはFIFO0と同様)
if (HAL_CAN_GetRxMessage(&hcan1, CAN_RX_FIFO1, &RxHeader, RxData) == HAL_OK)
{
// FIFO1で受信したメッセージの処理
// …
}
}
}

// main関数の無限ループなどで定期的に呼び出し
// while (1)
// {
// Check_And_Process_CAN_Messages();
// // … その他のタスク …
// }
“`

割り込みでの受信例:

割り込みを使う方が、受信メッセージをリアルタイムに処理でき、CPUリソースをポーリングに消費しないため効率的です。

まず、CubeIDEで対応する受信FIFOの割り込みを有効にし、コード生成します。次に、HAL_CAN_ActivateNotification() 関数で受信pending割り込みを有効にします。

c
// main関数などでCAN開始後、割り込みを有効にする
// 受信FIFO0にメッセージが pending したときの割り込みを有効化
if (HAL_CAN_ActivateNotification(&hcan1, CAN_IT_RX_FIFO0_MSG_PENDING) != HAL_OK)
{
/* Notification Error */
Error_Handler();
}
// 受信FIFO1も同様に有効化する場合は以下も追加
// if (HAL_CAN_ActivateNotification(&hcan1, CAN_IT_RX_FIFO1_MSG_PENDING) != HAL_OK)
// {
// Error_Handler();
// }

そして、stm32f4xx_hal_can.c などに定義されているWEAK属性の受信コールバック関数をアプリケーションコード(例えば can.c ファイル)でオーバーライドして実装します。

“`c

include “can.h”

// 受信メッセージヘッダーとデータ用のバッファ (割り込みハンドラ内で使用)
// 割り込みハンドラは高速に処理を完了させる必要があるため、
// 受信データを別途キューなどに格納し、メインループや別のタスクで処理するのが一般的です。
// ここでは簡単のため、直接読み出して処理する例を示しますが、実運用では非推奨です。
// 受信キューの実装例は応用編となります。

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 describes
* the CAN instance.
* @retval None
/
void HAL_CAN_RxFifo0MsgPendingCallback(CAN_HandleTypeDef
hcan)
{
/ Get received message from FIFO0 /
if (HAL_CAN_GetRxMessage(hcan, CAN_RX_FIFO0, &RxHeader_IT, RxData_IT) == HAL_OK)
{
/ Process received message /
// ここで受信したメッセージを処理するコードを記述
// 注意:割り込みハンドラ内なので、処理は短時間で完了させる必要がある
// 例: 受信フラグを立てる、データをグローバル変数やキューにコピーするなど

// 例: 受信フラグとデータをコピー (メインループで処理するため)
// extern volatile uint8_t can_rx_flag;
// extern CAN_RxHeaderTypeDef last_rx_header;
// extern uint8_t last_rx_data[8];
// if (can_rx_flag == 0) // 前のメッセージがまだ処理されていなければスキップ(簡単な例)
// {
//     last_rx_header = RxHeader_IT;
//     for(int i = 0; i < RxHeader_IT.DLC; i++)
//     {
//         last_rx_data[i] = RxData_IT[i];
//     }
//     can_rx_flag = 1; // 受信完了フラグをセット
// }

// あるいは、直接簡単な処理(例:LED点滅)を行う
// HAL_GPIO_TogglePin(LD2_GPIO_Port, LD2_Pin);

}
}

/
* @brief Rx Fifo 1 message pending callback.
* @param hcan pointer to a CAN_HandleTypeDef structure that describes
* the CAN instance.
* @retval None
/
void HAL_CAN_RxFifo1MsgPendingCallback(CAN_HandleTypeDef
hcan)
{
/ Get received message from FIFO1 /
if (HAL_CAN_GetRxMessage(hcan, CAN_RX_FIFO1, &RxHeader_IT, RxData_IT) == HAL_OK)
{
/ Process received message /
// FIFO1で受信したメッセージの処理(例: 別の受信フラグを立てるなど)
// …
}
}

// メインループで受信フラグを監視し、処理を行う場合
// extern volatile uint8_t can_rx_flag;
// extern CAN_RxHeaderTypeDef last_rx_header;
// extern uint8_t last_rx_data[8];
//
// while (1)
// {
// if (can_rx_flag == 1)
// {
// // 受信データの処理
// // 例: UARTで出力
// printf(“Received CAN Message (via IT):\r\n”);
// if (last_rx_header.IDE == CAN_ID_STD)
// {
// printf(” ID: 0x%lX (Std)\r\n”, last_rx_header.StdId);
// }
// else
// {
// printf(” ID: 0x%lX (Ext)\r\n”, last_rx_header.ExtId);
// }
// printf(” DLC: %d\r\n”, last_rx_header.DLC);
// printf(” Data:”);
// for(int i = 0; i < last_rx_header.DLC; i++)
// {
// printf(” %02X”, last_rx_data[i]);
// }
// printf(“\r\n”);
//
// can_rx_flag = 0; // フラグをクリア
// }
// // … その他のタスク …
// }
“`

4. 実践例:簡単な送受信プログラム

ここでは、2台のSTM32ボード(または1台のSTM32とCANアナライザー)を使って、簡単なCANメッセージの送受信を行うプログラム例を考えます。

シナリオ:
* STM32ボードA: 定期的に(例えば500ms間隔で)Standard ID 0x123 のデータフレームを送信します。データはカウンター値を格納します。
* STM32ボードB: Standard ID 0x123 のメッセージを受信し、そのデータ内容をUART経由でPCに表示します。

前提:
* 両方のボードでCANペリフェラルを適切に有効化し、同じビットレート(例:500kbps)で設定済みとします。
* ボードAのCAN_TX/RXピンと、ボードBのCAN_TX/RXピンを、それぞれCANトランシーバーを介して同じCANバスに接続します。バスの両端には120Ωの終端抵抗を接続します。
* ボードBはUARTも有効化し、PCと接続してデバッグコンソールとして使用できるように設定します。

STM32ボードA (送信側) のコード例

CubeIDEでプロジェクトを作成し、CAN1をNormalモード、500kbpsで設定します。UARTも必要に応じて有効化しますが、送信側では必須ではありません。Timerペリフェラル(例:TIM6)を有効化し、500ms周期で割り込みが発生するように設定します。CAN1のNVIC割り込みも有効化します(今回は送信完了割り込みは使用しませんが、エラー監視のために有効にしておくのが良いでしょう)。

“`c
/ main.c (Board A – Sender) /

include “main.h”

include “can.h”

include “tim.h” // TIM6 header

CAN_TxHeaderTypeDef TxHeader;
uint8_t TxData[8];
uint32_t TxMailbox;
uint8_t tx_counter = 0; // 送信するカウンター値

int main(void)
{
// … HAL_Init(), SystemClock_Config() …

MX_GPIO_Init();
MX_CAN1_Init(); // CAN1設定 (CubeIDE生成)
MX_TIM6_Init(); // TIM6設定 (500ms割り込み, CubeIDE生成)

/##- Start the CAN peripheral #########################################/
if (HAL_CAN_Start(&hcan1) != HAL_OK)
{
Error_Handler();
}

/##- Configure Transmit Header (once at init) ###########################/
TxHeader.StdId = 0x123; // Standard ID 0x123
TxHeader.ExtId = 0x00; // Not used for Standard ID
TxHeader.IDE = CAN_ID_STD; // Standard ID Format
TxHeader.RTR = CAN_RTR_DATA; // Data Frame
TxHeader.DLC = 1; // Data Length Code = 1 byte

/##- Start the Timer for periodic transmission #########################/
if (HAL_TIM_Base_Start_IT(&htim6) != HAL_OK) // htim6はMX_TIM6_Initで初期化
{
Error_Handler();
}

while (1)
{
/ Main loop does nothing, transmission is handled by timer interrupt /
}
}

/ stm32f4xx_it.c /
// TIM6割り込みハンドラ内で送信処理を行う
void TIM6_DAC_IRQHandler(void)
{
/ USER CODE BEGIN TIM6_DAC_IRQn 0 /

/ USER CODE END TIM6_DAC_IRQn 0 /
HAL_TIM_IRQHandler(&htim6);
/ USER CODE BEGIN TIM6_DAC_IRQn 1 /

/ USER CODE END TIM6_DAC_IRQn 1 /
}

/ stm32f4xx_hal_tim.c に定義されているWEAK関数をオーバーライド /
// タイマー割り込みコールバック関数
void HAL_TIM_PeriodElapsedCallback(TIM_HandleTypeDef htim)
{
/
USER CODE BEGIN TIM_PeriodElapsedCallback */
if (htim->Instance == TIM6) // 使用しているタイマーインスタンスを確認
{
// 送信するデータを準備 (カウンター値を1バイトデータとして送信)
TxData[0] = tx_counter++;

/* Add a message to the Tx FIFO */
// HAL_CAN_AddTxMessage はノンブロッキング関数
// 送信メールボックスが全て埋まっている場合は HAL_ERROR を返す
// 実際には、送信バッファが空くまで待つか、エラーを処理する必要がある
// ここでは簡単のため、エラーハンドラに飛ぶようにする
if (HAL_CAN_AddTxMessage(&hcan1, &TxHeader, TxData, &TxMailbox) != HAL_OK)
{
  /* Transmission request Error - Likely Tx mailboxes are full */
  // エラー処理(例:LED点滅、リトライなど)
  // For this example, just toggle an error LED
  HAL_GPIO_TogglePin(LD2_GPIO_Port, LD2_Pin); // Assuming LD2 is an error LED
}
else
{
  // 送信キューに追加成功
  // Optional: Toggle another LED to indicate message added to queue
  // HAL_GPIO_TogglePin(User_LED_GPIO_Port, User_LED_Pin);
}

}
/ USER CODE END TIM_PeriodElapsedCallback /
}

// Error_Handler() は main.h などで定義されているものを使用
void Error_Handler(void)
{
/ User can add his own implementation to report the HAL error return state /
while (1)
{
// エラー発生時はここで停止またはエラー状態を表示
HAL_GPIO_WritePin(LD2_GPIO_Port, GPIO_PIN_SET); // Turn on error LED
}
}
“`

STM32ボードB (受信側) のコード例

CubeIDEでプロジェクトを作成し、CAN1をNormalモード、500kbpsで設定します。また、UART2 (または適切なUART) を非同期モード(Asynchronous)で有効化し、PCとの通信速度(例:115200 bps)を設定します。CAN1のNVIC割り込み(特にRx FIFO0 message pending割り込み)を有効化します。

CANフィルタ設定で、Standard ID 0x123 のメッセージのみをFIFO0で受け取るように設定します。

  • Filter Mode: Identifier List
  • Filter Scale: Dual 16-bit
  • Filter Number: 0
  • Filter associated with FIFO: FIFO 0
  • Filter Activation: Enabled
  • Filter ID 1: 0x123

“`c
/ main.c (Board B – Receiver) /

include “main.h”

include “can.h”

include “usart.h” // UART header

include // For printf

// printfをUARTに出力するためのリターゲット
// STM32CubeIDEでは通常、プロジェクト設定で有効化するだけで使えるようになる
// もしくは独自のputchar関数を実装する

CAN_RxHeaderTypeDef RxHeader_IT;
uint8_t RxData_IT[8];

int main(void)
{
// … HAL_Init(), SystemClock_Config() …

MX_GPIO_Init();
MX_CAN1_Init(); // CAN1設定 (CubeIDE生成、フィルタ設定含む)
MX_USART2_UART_Init(); // UART設定 (CubeIDE生成)

/##- Start the CAN peripheral #########################################/
if (HAL_CAN_Start(&hcan1) != HAL_OK)
{
Error_Handler();
}

/##- Activate CAN Rx message pending notification for FIFO0 #############/
if (HAL_CAN_ActivateNotification(&hcan1, CAN_IT_RX_FIFO0_MSG_PENDING) != HAL_OK)
{
/ Notification Error /
Error_Handler();
}

printf(“CAN Receiver Started. Waiting for messages…\r\n”);

while (1)
{
/ Main loop does nothing, reception and processing are handled by interrupt /
HAL_Delay(100); // Just a small delay to prevent busy-waiting if no interrupt occurs
}
}

/ stm32f4xx_it.c /
// CAN1 RX0割り込みハンドラ
void CAN1_RX0_IRQHandler(void)
{
/ USER CODE BEGIN CAN1_RX0_IRQn 0 /

/ USER CODE END CAN1_RX0_IRQn 0 /
HAL_CAN_IRQHandler(&hcan1); // HALのハンドラを呼び出す
/ USER CODE BEGIN CAN1_RX0_IRQn 1 /

/ USER CODE END CAN1_RX0_IRQn 1 /
}

/ stm32f4xx_hal_can.c に定義されているWEAK関数をオーバーライド /
/
* @brief Rx Fifo 0 message pending callback.
* @param hcan pointer to a CAN_HandleTypeDef structure that describes
* the CAN instance.
* @retval None
/
void HAL_CAN_RxFifo0MsgPendingCallback(CAN_HandleTypeDef
hcan)
{
/ USER CODE BEGIN HAL_CAN_RxFifo0MsgPendingCallback /
/ Get received message from FIFO0 /
if (HAL_CAN_GetRxMessage(hcan, CAN_RX_FIFO0, &RxHeader_IT, RxData_IT) == HAL_OK)
{
/ Process received message: Print to UART /
// 割り込みハンドラ内でのprintfは非推奨だが、簡単なテストのため使用
// 実際のアプリケーションでは、データをキューに格納してメインループで処理する
printf(“Received CAN Message:\r\n”);
if (RxHeader_IT.IDE == CAN_ID_STD)
{
printf(” ID: 0x%lX (Std)\r\n”, RxHeader_IT.StdId);
}
else // Should not happen with filter set to Standard ID 0x123
{
printf(” ID: 0x%lX (Ext)\r\n”, RxHeader_IT.ExtId);
}
printf(” DLC: %d\r\n”, RxHeader_IT.DLC);
printf(” Data:”);
for(int i = 0; i < RxHeader_IT.DLC; i++)
{
printf(” %02X”, RxData_IT[i]);
}
printf(“\r\n”);
}
/ USER CODE END HAL_CAN_RxFifo0MsgPendingCallback /
}

// Error_Handler() は main.h などで定義されているものを使用
void Error_Handler(void)
{
/ User can add his own implementation to report the HAL error return state /
while (1)
{
// エラー発生時はここで停止またはエラー状態を表示
HAL_GPIO_WritePin(LD2_GPIO_Port, GPIO_PIN_SET); // Turn on error LED
}
}
“`

実行:

  1. 両方のSTM32ボードに上記のコードをコンパイルして書き込みます。
  2. ボードAとボードBを、CANトランシーバーと終端抵抗を介してCANバスで接続します。
  3. ボードBをPCにUSB-UART変換ケーブルなどで接続し、ターミナルソフト(Tera Term, PuTTYなど)で接続します(ボーレートは115200)。
  4. 両方のボードの電源を入れます。
  5. ボードAが500msごとにCANメッセージを送信し始めます。
  6. ボードBはStandard ID 0x123のメッセージを受信するたびに、その内容をUART経由でPCのターミナルに表示します。カウンター値がインクリメントされて表示されるはずです。

5. デバッグとトラブルシューティング

CAN通信は物理層やタイミングに依存する部分が多いため、うまく通信できない場合に原因特定が難しいことがあります。よくある問題とその対処法、およびデバッグツールについて解説します。

5.1 よくある問題と対処法

  • 配線ミス: CANHとCANLが入れ違っている、GNDが接続されていない、CANH/CANLがショートしているなど。
    • 対処: 配線を何度も確認します。テスターで導通やショートを確認します。CANHとCANLは差動信号なので、極性を間違えると通信できません。
  • 終端抵抗がない、または位置・値が間違っている: バスの両端に120Ωの抵抗が接続されていない、バスの途中に接続されている、抵抗値が間違っているなど。
    • 対処: CANバスの両端のノードに正確に120Ωの終端抵抗を接続します。テスターでバス全体のインピーダンスを測り、約60Ωになっているか確認します。
  • クロック設定ミス: STM32のシステムクロックやAPBクロック設定が間違っている。
    • 対処: CubeIDEのClock Configurationタブや生成されたコードを確認し、意図したクロック周波数になっているか確認します。
  • ビットタイミング設定ミス: Prescaler, TS1, TS2, SJW の設定値がバス速度やネットワーク遅延に対して不適切。
    • 対処: 前述のビットタイミング計算方法を参考に、設定値を見直します。特に、Sampling Pointが適切な位置(75%~85%)にあるか、SJWが小さすぎないかを確認します。同じバス上の全てのノードでビットタイミング設定が一致している必要があります。
  • CANトランシーバーの問題: トランシーバーICの電源が入っていない、スタンバイモードになっている、故障している、対応するCANプロトコルが間違っているなど。
    • 対処: トランシーバーの電源ピン電圧を確認します。スタンバイピンがある場合は適切に処理します。トランシーバーのデータシートを確認します。別のトランシーバーに交換してみるのも手です。
  • CANコントローラーの有効化忘れ: CubeIDEでCANペリフェラルを有効化し忘れている、またはHAL_CAN_Init()やHAL_CAN_Start()が呼ばれていない。
    • 対処: CubeIDEの設定と main.c の初期化コードを確認します。
  • フィルタ設定ミス: 受信したいメッセージのIDがフィルタで許可されていない、フィルタが有効になっていない、メッセージを格納するFIFOが間違っている。
    • 対処: CubeIDEのフィルタ設定や HAL_CAN_ConfigFilter() の呼び出しを確認します。全てのフィルタバンクが有効になっているか確認します。
  • 割り込み設定ミス: 受信pending割り込みやエラー割り込みを有効にしていない、NVICで割り込みチャネルを有効にしていない、割り込みハンドラやコールバック関数が実装されていない。
    • 対処: CubeIDEのNVIC設定を確認します。HAL_CAN_ActivateNotification() が呼ばれているか確認します。stm32f4xx_it.c に対応する割り込みハンドラ(例:CAN1_RX0_IRQHandler)が定義されており、その中で HAL_CAN_IRQHandler() が呼ばれているか確認します。また、HAL_CAN_RxFifo0MsgPendingCallback() などのコールバック関数が正しく実装されているか確認します。
  • 送信キューの満杯: 送信速度が受信側の処理速度やバス速度より速い場合、送信メールボックスが満杯になり、送信要求が拒否されることがあります。
    • 対処: HAL_CAN_AddTxMessage() の戻り値を確認し、送信失敗を検出します。送信周期を遅くする、受信側の処理を高速化する、RTOSなどを使用して送信処理をブロックしないようにするなどの対策を検討します。
  • エラーフレームの多発: 特定ノードの故障やバスの問題により、エラーフレームが頻繁に発生し、通信が正常に行えない状態。
    • 対処: エラー発生を検出する割り込み(CAN_IT_ERROR, CAN_IT_BUSOFFなど)を有効にし、エラー状態を監視します。HAL_CAN_GetError() 関数で詳細なエラーコードを取得できます。どのノードがエラーを発生させているか特定し、物理層やそのノードのCAN設定を確認します。エラーカウンタがBus-off閾値を超えていないか確認します。

5.2 デバッグツール

  • オシロスコープ: CANHとCANLの波形を観測するのに非常に役立ちます。信号レベルが規定通りか(Dominant/Recessive電圧)、ビットタイミングが正しいか、波形が歪んでいないか、ノイズが乗っていないかなどを視覚的に確認できます。エラーフレームが発生しているかも確認できます。
  • ロジックアナライザー: CANの信号線(CANH/CANLや、トランシーバーとマイコン間のTX/RX)をデジタル信号としてキャプチャし、プロトコルデコード機能を使ってCANフレームの内容(ID, データ, エラーフラグなど)を表示できます。ビットタイミングのずれなども詳細に解析できます。
  • CANアナライザー: PCに接続し、CANバス上の全てのメッセージをリアルタイムにモニタリングできる専用ツールです。メッセージの送受信、フィルタリング、エラー検出・表示、バス負荷の計測などが可能です。デバッグやシステム解析に不可欠なツールです。多くのCANアナライザーは、特定のIDのメッセージをバスに送信する機能も持っており、単一のノードの受信テストなどに使用できます。USB-CAN変換アダプターとPC上のソフトウェアで構成されるものが一般的です。
  • STM32CubeIDEのデバッグ機能: ブレークポイントを設定し、CAN関連のレジスタやHAL構造体(hcan1など)の内容を確認します。送信キューの状態、受信FIFOのデータ、エラーカウンタの値などを確認することで、プログラムの実行状態やCANコントローラーの状態を把握できます。

5.3 STM32のCANステータスレジスタ

デバッグ時には、HALライブラリのAPIだけでなく、STM32のリファレンスマニュアルを参照してCANペリフェラルのレジスタ(CAN_TSR, CAN_RFRx, CAN_ESR, CAN_MSRなど)を直接確認することも有効です。

  • CAN_TSR (Transmit Status Register): 送信メールボックスの状態(空き状況、送信成功/失敗)を示します。
  • CAN_RFRx (Receive FIFO x Register): 受信FIFOの状態(メッセージ数、オーバーラン)を示します。
  • CAN_ESR (Error Status Register): 現在のエラー状態(LEC: Last Error Code)、エラーカウンタ(TEC, REC)などを示します。LECからは、最後に発生したエラーの種類(Bit Error, Stuff Errorなど)を特定できます。
  • CAN_MSR (Master Status Register): CANコントローラーのモード(Sleep, Initializationなど)、Bus-off状態などを示します。

これらのレジスタ値をデバッガーで確認することで、ハードウェアレベルでの問題発生状況を詳細に把握できます。HALライブラリには、これらのレジスタ値をラップしたAPI(例:HAL_CAN_GetError(), HAL_CAN_GetRxFifoFillLevel() など)も用意されています。

6. 応用例と発展

CAN通信は、単にメッセージを送受信するだけでなく、様々な上位プロトコルや応用があります。

  • CAN FD (CAN Flexible Data-rate): STM32のFDCANペリフェラルはCAN FDに対応しています。より高速なデータレートと最大64バイトのデータ長を利用することで、大容量データの転送やスループット向上を図れます。CAN FDの設定は、Nominal Bit TimingとData Bit Timingの2つの設定が必要になります。
  • CANopen, J1939などの上位プロトコル: CANは物理層とデータリンク層のみを規定しており、その上でアプリケーション層のプロトコルが動作します。
    • CANopen: 産業オートメーション分野で広く使われる高機能なプロトコルです。デバイスプロファイル、ネットワーク管理、PDO (Process Data Object) によるリアルタイムデータ通信、SDO (Service Data Object) による設定データ通信などを規定しています。
    • SAE J1939: 商用車(トラック、バスなど)や農業機械などで広く使われるプロトコルです。特定のID割り当てルール(PGN: Parameter Group Number)や診断機能などを規定しています。
      これらのプロトコルを実装するには、各プロトコルの仕様に従ってメッセージの構築や解釈を行うソフトウェアスタックが必要です。オープンソースや商用のスタックが存在します。
  • RTOSとの連携: リアルタイムオペレーティングシステム (RTOS) と組み合わせることで、CAN通信を含む複数のタスクを効率的に管理できます。CAN受信ハンドラでメッセージを受信キューに格納し、別のタスクでキューからメッセージを取り出して処理するといったアーキテクチャが一般的です。セマフォやミューテックスを使って共有リソース(例:受信キュー)へのアクセスを同期させる必要があります。
  • 低消費電力CAN: 一部のSTM32製品やCANトランシーバーは、低消費電力モード(スリープモードなど)に対応しています。CANアクティビティによってウェイクアップする機能と組み合わせることで、バッテリ駆動のシステムなどで消費電力を削減できます。
  • デュアルCANインターフェース: 一部のSTM32には複数のCANペリフェラル(CAN1, CAN2など)が搭載されています。これにより、異なる速度やプロトコルのCANネットワークを接続するゲートウェイとして機能させたり、システムの冗長性を高めたりすることができます。

これらの応用は、CAN通信の基本的な設定とプログラミングを理解した上で、さらに各分野の仕様やソフトウェア設計の知識が必要となります。

7. まとめ

本記事では、STM32マイクロコントローラーを使ったCAN通信の入門として、CAN通信の基本的な仕組み、STM32のCANコントローラーの機能、そしてSTM32CubeIDEとHALライブラリを用いた設定方法とプログラミング手法について詳細に解説しました。

CAN通信は、頑強性、リアルタイム性、そして分散制御に適したマルチマスター方式という特徴から、多くの産業分野で重要な役割を果たしています。STM32は、多様な製品ラインナップと使いやすい開発環境を提供しており、CAN通信を学習し、実際のアプリケーションに組み込むための優れたプラットフォームです。

CAN通信を始める上で、特に以下の点が重要であることを解説しました。

  • CANの基礎理解: 物理層(差動信号、終端抵抗、トランシーバー)とデータリンク層(フレーム構造、アービトレーション、ビットスタッフィング、エラー検出)の仕組みを理解すること。
  • STM32 CAN機能の理解: 送信メールボックス、受信FIFO、フィルタ、ビットタイミング制御といったハードウェア機能がCANプロトコルをどのように実現しているかを把握すること。
  • ビットタイミングの正しい設定: CANクロック周波数とDesiredビットレートに基づいて、Prescaler, TS1, TS2, SJWの値を正確に計算・設定すること。サンプリングポイントの位置を意識すること。
  • フィルタ設定の活用: 必要なメッセージのみを受信するために、ハードウェアフィルタを適切に設定すること。
  • HALライブラリを使ったプログラミング: 初期化、送信、受信(ポーリングまたは割り込み)といった基本的な操作をHAL関数を使って実装すること。特に割り込みを使った受信処理は、リアルタイムシステムでは必須となることが多いです。
  • デバッグ手法: ハードウェア(配線、終端抵抗、トランシーバー)とソフトウェア(クロック、ビットタイミング、フィルタ、割り込み、HAL呼び出し)の両面から原因を調査し、必要に応じてデバッグツール(オシロスコープ、ロジックアナライザー、CANアナライザー)を活用すること。

簡単な送受信プログラムの実践例を通じて、これらの概念が実際のコードにどのように落とし込まれるかを示しました。

CAN通信の世界は奥深く、さらに上位プロトコルやCAN FD、RTOSとの連携など、様々な応用分野があります。本記事が、STM32でCAN通信を始めるための強固な一歩となり、今後の学習や開発の助けとなれば幸いです。

更なる学習のためには、使用しているSTM32のリファレンスマニュアル(CANまたはFDCANペリフェラルの章)、CANトランシーバーのデータシート、CANプロトコルの標準規格(ISO 11898)、そして各種上位プロトコルの仕様書などを参照することをお勧めします。また、STM32CubeIDEで生成されるコードやHALドライバのソースコードを読むことも、理解を深める上で非常に有効です。

これで、あなたもSTM32でのCAN通信開発の第一歩を踏み出しました。ぜひ、実際に手を動かして、CAN通信の面白さと奥深さを体験してみてください。

コメントする

メールアドレスが公開されることはありません。 が付いている欄は必須項目です

上部へスクロール