STM32マイコンでCAN通信

STM32 マイコンで CAN 通信を行う際の設定方法についてまとめます。
本記事では STM32CubeMX(HAL ドライバ)を使用した Classic CAN 通信 を対象とし、初期設定から送受信、フィルタ設定までを順に説明します。

開発環境 STM32CubeMX(Version 5.3.0)(無料)

Atollic TrueSTUDIO for STM32(Version: 9.3.0)(無料)

デバッガ J-Link Plus
ボード 自社製品(CanLine-1)用ボード
マイコン STM32F205RCT7
CANトランシーバ SN65HVD232D
目次

CAN初期設定

STM32でCAN通信を行うため、CubeMXを使用してCANの初期設定を行います。

まず、CANで使用するピンを以下のように設定します。

  • PB9→CAN1_TX
  • PB8→CAN1_RX

STM32 CAN初期設定

今回のCAN通信条件は以下のとおりです。

  • 転送速度→500kbps
  • サンプリングポイント→80%

CANの通信速度およびサンプリングポイントは、次の4つのパラメータによって決定されます。

  • Prescaler
  • Time Quantum(以下 Tq)
  • Time Quanta in Bit Segment1 (以下 TSeg1)
  • Time Quanta in Bit Segment2 (以下 TSeg2)

Time Quantum(Tq) は、CANの1ビット時間を細分化した最小単位であり、以下の式で求められます。

Time Quantum = 1sec / (CANのクロック源  /  Prescaler)

CANのクロック源は APB1 です。

CubeMX の Clock Configuration を確認すると、APB1 の周波数は 30MHz であることが分かります。

ここでは Prescaler を 3 に設定し、Time Quantum を 100ns とします。

1sec / (30MHz / 3) = 100.0nsec

STM32 CAN初期設定

転送速度500kbpsでは1ビットの送信時間は2μsecになるので、下記の式で各パラメーターを調整する。

1ビット送信時間 = (Sync_Seg + TSeg1 + TSeg2) × Time Quantum
※Sync_Seg(同期セグメント):1固定
2000nsec(2μsec) = (1 + 15 + 4 ) × 100nsec
サンプリングポイント[%] = ( (1 + TSeg1)  /  (1 + TSeg1+ Tseg2) ) × 100
80[%]=((1 + 15 ) / (1 + 15 + 4)) × 100

STM32 CAN初期設定

設定例(CANのクロック源30MHz)

転送速度 サンプリングポイント Prescaler TSeg1 TSeg2
125Kbps 60% 12 11Times 8Times
70% 12 13Times 6Times
80% 12 15Times 4Times
250kbps 60% 6 11Times 8Times
70% 6 13Times 6Times
80% 6 15Times 4Times
500kbps 60% 3 11Times 8Times
70% 3 13Times 6Times
80% 3 15Times 4Times
1Mbps 60% 3 5Times 4Times
70% 3 6Times 3Times
80% 3 7Times 2Times

それ以外のパラメーターは今回はデフォルトのままです。

CAN送信

まずは下記関数をコールしてCAN通信を開始する。

HAL_CAN_Start(&hcan1)

この関数を実行することで、CANコントローラが動作状態となり、送受信が可能になります。

STM32F205はCANの送信メールBOXは3個あり、送信するには少なくとも1個の送信メールBOXが空きである必要がある。HAL_CAN_GetTxMailboxesFreeLevel()で現在空いている送信メールBOXの数を確認して、空いている場合、HAL_CAN_AddTxMessage()でメッセージの送信を開始する。4個目の引数のTxMailboxに使用したメールBOXのナンバー(0~2)が格納される。

CAN_TxHeaderTypeDef TxHeader;
uint32_t TxMailbox;
uint8_t TxData[8];
if(0 < HAL_CAN_GetTxMailboxesFreeLevel(&hcan1)){
    TxHeader.StdId = 0x555;                 // CAN ID
    TxHeader.RTR = CAN_RTR_DATA;            // フレームタイプはデータフレーム
    TxHeader.IDE = CAN_ID_STD;              // 標準ID(11ビット)
    TxHeader.DLC = 8;                       // データ長は8バイトに
    TxHeader.TransmitGlobalTime = DISABLE;  // CANメッセージ送信時のタイムスタンプ機能は使用しない
    TxData[0] = 0x11;
    TxData[1] = 0x22;
    TxData[2] = 0x33;
    TxData[3] = 0x44;
    TxData[4] = 0x55;
    TxData[5] = 0x66;
    TxData[6] = 0x77;
    TxData[7] = 0x88;
    HAL_CAN_AddTxMessage(&hcan1, &TxHeader, TxData, &TxMailbox);
}

CAN受信

STM32F205はCANメッセージ受信FIFOが2個あり(FIFO0とFIFO1)、IDフィルターの設定を行い、どの受信FIFOにメッセージが格納するかを設定する。

IDフィルターは下記のように設定することで、全てのIDのメッセージをFIFO0に格納する。

※詳細はCAN IDフィルター設定を参照

CAN_FilterTypeDef filter;
filter.FilterIdHigh         = 0;                        // フィルターID(上位16ビット)
filter.FilterIdLow          = 0;                        // フィルターID(下位16ビット)
filter.FilterMaskIdHigh     = 0;                        // フィルターマスク(上位16ビット)
filter.FilterMaskIdLow      = 0;                        // フィルターマスク(下位16ビット)
filter.FilterScale          = CAN_FILTERSCALE_32BIT;    // フィルタースケール
filter.FilterFIFOAssignment = CAN_FILTER_FIFO0;         // フィルターに割り当てるFIFO
filter.FilterBank           = 0;                        // フィルターバンクNo
filter.FilterMode           = CAN_FILTERMODE_IDMASK;    // フィルターモード
filter.SlaveStartFilterBank = 14;                       // スレーブCANの開始フィルターバンクNo
filter.FilterActivation     = ENABLE;                   // フィルター無効/有効
HAL_CAN_ConfigFilter(&hcan1, &filter);

受信は割込みとコールバック関数を利用しました。

まずCubeMXでFIFO0の受信割込みを有効にします。

STM32 CAN受信

コールバック関数を記述する。
uint32_t id;
uint32_t dlc;
uint8_t data[8];

void HAL_CAN_RxFifo0MsgPendingCallback(CAN_HandleTypeDef *hcan)
{
    CAN_RxHeaderTypeDef RxHeader;
    uint8_t RxData[8];
    if (HAL_CAN_GetRxMessage(hcan, CAN_RX_FIFO0, &RxHeader, RxData) == HAL_OK)
    {
        id = (RxHeader.IDE == CAN_ID_STD)? RxHeader.StdId : RxHeader.ExtId;     // ID
        dlc = RxHeader.DLC;                                                     // DLC
        data[0] = RxData[0];                                                    // Data
        data[1] = RxData[1];
        data[2] = RxData[2];
        data[3] = RxData[3];
        data[4] = RxData[4];
        data[5] = RxData[5];
        data[6] = RxData[6];
        data[7] = RxData[7];
    }
}

CAN通信を開始してFIFO受信の割り込みを有効にすることで、上記コールバック関数で受信メッセージを取得できる。

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

CAN IDフィルター設定

STM32F205はCANが2チャンネルあり、それぞれ3メッセージ分の受信可能なFIFOが2個ある(FIFO0とFIFO1)。

また28個のフィルターバンクがあり、0~n-1をCAN1で使用して、n~27をCAN2で使用する。(n数は任意に設定可能)

受信FIFOにメッセージを格納するにはフィルターバンクの設定が必要になる。

STM32 CAN IDフィルター

フィルターモードにはIDマスクモードとIDリストモードがある。

  • IDマスクモード は 受信IDとフィルターマスクを論理積した値とフィルターIDとフィルターマスクを論理積した値が一致した場合に指定した受信FIFOに格納する。
(受信ID & フィルターマスク) == (フィルターID & フィルターマスク)
例えば0x750~0x75Fまでの16種類の受信IDを通過させるにはフィルターマスクは0x7F0、フィルターIDは0x750を設定する。
  • IDリストモードは フィルターIDに完全に一致した受信IDを指定した受信FIFOに格納する。
受信ID == フィルターID

またフィルタースケールには32ビットモードと16ビットモードがあり、フィルターモードとの組み合わせで1個のフィルターバンクにつき設定可能なフィルター数が変わってくる。

フィルタースケール フィルターモード
32ビットモード IDマスクモード 1種類のID マスクが可能
32ビットモード IDリストモード 2種類の標準ID又は拡張IDを通過可能にする
 16ビットモード IDマスクモード 2種類のIDマスクが可能
 16ビットモード IDリストモード 4種類の標準IDを通過可能にする

32ビットモードIDマスクモード

32ビットモードの場合、フィルターバンクは下記のようなレジスタ構成なので、標準IDの場合は左へ21ビットシフト、拡張IDの場合は左へ3ビットシフトする必要がある。またフィルターマスクは標準IDか拡張IDかを比較するためIDE(Bit2)は1にする。

CANフィルター設定

0x750~0x75Fの範囲の標準IDを受信FIFO0に格納する例

CAN_FilterTypeDef filter;
uint32_t fId   =  0x750 << 21;        // フィルターID
uint32_t fMask = (0x7F0 << 21) | 0x4; // フィルターマスク 

filter.FilterIdHigh         = fId >> 16;             // フィルターIDの上位16ビット
filter.FilterIdLow          = fId;                   // フィルターIDの下位16ビット
filter.FilterMaskIdHigh     = fMask >> 16;           // フィルターマスクの上位16ビット
filter.FilterMaskIdLow      = fMask;                 // フィルターマスクの下位16ビット
filter.FilterScale          = CAN_FILTERSCALE_32BIT; // 32モード
filter.FilterFIFOAssignment = CAN_FILTER_FIFO0;      // FIFO0へ格納
filter.FilterBank           = 0;
filter.FilterMode           = CAN_FILTERMODE_IDMASK; // IDマスクモード
filter.SlaveStartFilterBank = 14;
filter.FilterActivation     = ENABLE;

HAL_CAN_ConfigFilter(&hcan1, &filter);

32ビットモードIDリストモード

0x600と0x700の2個の標準IDを受信FIFO0に格納する例

CANフィルター設定

uint32_t fId1 = 0x600 << 21; // フィルターID1
uint32_t fId2 = 0x700 << 21;  // フィルターID2

filter.FilterIdHigh         = fId1 >> 16;            // フィルターID1の上位16ビット
filter.FilterIdLow          = fId1;                  // フィルターID1の下位16ビット
filter.FilterMaskIdHigh     = fId2 >> 16;            // フィルターID2の上位16ビット
filter.FilterMaskIdLow      = fId2;                  // フィルターID2の下位16ビット
filter.FilterScale          = CAN_FILTERSCALE_32BIT; // 32モード
filter.FilterFIFOAssignment = CAN_FILTER_FIFO0;      // FIFO0へ格納
filter.FilterBank           = 0;                     
filter.FilterMode           = CAN_FILTERMODE_IDLIST; // IDリストモード
filter.SlaveStartFilterBank = 14;
filter.FilterActivation     = ENABLE;

HAL_CAN_ConfigFilter(&hcan1, &filter);

16ビットモードIDマスクモード

16ビットモードの場合、フィルターバンクは下記のようなレジスタ構成なので、標準IDの場合は左へ5ビットシフトする必要がある。またフィルターマスクは標準IDか拡張IDかを比較するためIDE(Bit3)は1にする。

0x350~0x35Fの16種類、0x400~0x4FFの256種類の標準IDを 受信FIFO0に格納する例

uint32_t fId1   =  0x350 << 5;        // フィルターID1
uint32_t fMask1 = (0x3F0 << 5) | 0x8; // フィルターマスク1
uint32_t fId2   =  0x400 << 5;        // フィルターID2
uint32_t fMask2 = (0x700 << 5) | 0x8; // フィルターマスク2

filter.FilterIdHigh         = fId1;                  // フィルターID1
filter.FilterIdLow          = fId2;                  // フィルターID2
filter.FilterMaskIdHigh     = fMask1;                // フィルターマスク1
filter.FilterMaskIdLow      = fMask2;                // フィルターマスク2
filter.FilterScale          = CAN_FILTERSCALE_16BIT; // 16モード
filter.FilterFIFOAssignment = CAN_FILTER_FIFO0;      // FIFO0へ格納
filter.FilterBank           = 0;                     
filter.FilterMode           = CAN_FILTERMODE_IDMASK; // IDマスクモード
filter.SlaveStartFilterBank = 14;
filter.FilterActivation     = ENABLE;

HAL_CAN_ConfigFilter(&hcan1, &filter);

16ビットモードIDリストモード

0x100、0x200、0x300、0x400の4個の標準IDを受信FIFO0に格納する例

uint32_t fId1 = 0x100 << 5; // フィルターID1
uint32_t fId2 = 0x200 << 5; // フィルターID2
uint32_t fId3 = 0x300 << 5; // フィルターID3
uint32_t fId4 = 0x400 << 5; // フィルターID4

filter.FilterIdHigh         = fId1;                  // フィルターID1
filter.FilterIdLow          = fId2;                  // フィルターID2
filter.FilterMaskIdHigh     = fId3;                  // フィルターID3
filter.FilterMaskIdLow      = fId4;                  // フィルターID4
filter.FilterScale          = CAN_FILTERSCALE_16BIT; // 16モード
filter.FilterFIFOAssignment = CAN_FILTER_FIFO0;      // FIFO0へ格納
filter.FilterBank           = 0;                     
filter.FilterMode           = CAN_FILTERMODE_IDLIST; // IDリストモード
filter.SlaveStartFilterBank = 14;
filter.FilterActivation     = ENABLE;

HAL_CAN_ConfigFilter(&hcan1, &filter);

あとがき

自社製品 (CanLine-1)用のボードを製作するにあたり、当初は CAN通信の開発経験があったルネサス製 RL78 マイコン を採用する予定でした。
しかし検討を進める中で、小ロットでの部品調達が難しいことが判明し、やむを得ず STM32 マイコンへ急遽方針転換することになりました。

STM32 については、当時 CAN 通信の開発経験がなく、日本語ドキュメントもほとんど揃っていなかったため、正直なところ不安もありました。
レジスタ仕様や設定手順を英文資料とにらめっこしながら、試行錯誤の連続で開発を進める日々が続きました。

中でも CAN フィルター設定 は最初につまずいたポイントです。
「なぜこの条件で受信しないのか」「ID マスクの考え方はこれで合っているのか」と、理解するまでにかなりの時間を要しました。

ただ、一度仕組みを理解してしまえば、その後の開発は非常にスムーズでした。
むしろ慣れてからは、STM32 のほうが 柔軟で拡張性が高く、結果的にルネサス製マイコンよりも開発しやすいと感じるようになりました。

よろしければシェアを!
  • URLをコピーしました!
目次