STM32でCAN通信を行った際のまとめ。
開発環境 | STM32CubeMX(Version 5.3.0)(無料)
Atollic TrueSTUDIO for STM32(Version: 9.3.0)(無料) |
デバッガ | J-Link Plus |
ボード | 自社製品(CanLine-1)用ボード |
マイコン | STM32F205RCT7 |
CANトランシーバ | SN65HVD232D |
CAN初期設定
CubeMXでCAN初期設定を行います。
使用するピンの設定をします。
- PB9→CAN1_TX
- PB8→CAN1_RX
次にCANの転送速度とサンプリングポイントを今回は下記のように設定します。
- 転送速度→500kbps
- サンプリングポイント→80%
転送速度とサンプリングポイントは下記5個のパラメータで決定します。
- Prescaler
- Time Quantum
- Time Quanta in Bit Segment1 (以下 TSeg1)
- Time Quanta in Bit Segment2 (以下 TSeg2)
- ReSynchronization Jump Width (以下 SJW)
「Time Quantum」は1ビットを細分化する際の最小の時間で、下記の計算式で求めます。
CANのクロック源はAPB1なので、CubeMXのClock ConfigurationでAPB1の周波数を確認すると30MHである。
Prescalerを3にして「Time Quantum」を100.0nsecにする。
1sec / (30MHz / 3) = 100.0nsec
転送速度500kbpsでは1ビットの送信時間は2μsecになるので、下記の式で各パラメーターを調整する。
設定例(CANのクロック源30MHz)
転送速度 | サンプリングポイント | Prescaler | TSeg1 | TSeg2 | SJW |
125Kbps | 60% | 12 | 11Times | 8Times | 1Times |
70% | 12 | 13Times | 6Times | 1Times | |
80% | 12 | 15Times | 4Times | 1Times | |
250kbps | 60% | 6 | 11Times | 8Times | 1Times |
70% | 6 | 13Times | 6Times | 1Times | |
80% | 6 | 15Times | 4Times | 1Times | |
500kbps | 60% | 3 | 11Times | 8Times | 1Times |
70% | 3 | 13Times | 6Times | 1Times | |
80% | 3 | 15Times | 4Times | 1Times | |
1Mbps | 60% | 3 | 5Times | 4Times | 1Times |
70% | 3 | 6Times | 3Times | 1Times | |
80% | 3 | 7Times | 2Times | 1Times |
それ以外のパラメーターは今回はデフォルトのままです。
CAN送信
まずは下記関数をコールしてCAN通信を開始する。
HAL_CAN_Start(&hcan1)
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; // ???
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通信の開発経験はなく、日本語版のドキュメントもなかったため、少し不安でしたが試行錯誤してなんとか制御できるようになりました。フィルター設定のところが理解するのに少し苦労しましたが慣れてしまえばルネサス製マイコンより開発しやすいと感じました。