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
今回の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ビット時間を細分化した最小単位であり、以下の式で求められます。
CANのクロック源は APB1 です。
CubeMX の Clock Configuration を確認すると、APB1 の周波数は 30MHz であることが分かります。
ここでは Prescaler を 3 に設定し、Time Quantum を 100ns とします。
1sec / (30MHz / 3) = 100.0nsec
転送速度500kbpsでは1ビットの送信時間は2μsecになるので、下記の式で各パラメーターを調整する。
設定例(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の受信割込みを有効にします。
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にメッセージを格納するにはフィルターバンクの設定が必要になる。
フィルターモードにはIDマスクモードとIDリストモードがある。
- IDマスクモード は 受信IDとフィルターマスクを論理積した値とフィルターIDとフィルターマスクを論理積した値が一致した場合に指定した受信FIFOに格納する。
- IDリストモードは フィルターIDに完全に一致した受信IDを指定した受信FIFOに格納する。
またフィルタースケールには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にする。
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に格納する例
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 のほうが 柔軟で拡張性が高く、結果的にルネサス製マイコンよりも開発しやすいと感じるようになりました。










