STM32 + レジスタをたたいてCAN通信を実装する その2 CANの初期化

2021年2月16日

こんにちは。前回はGPIOの初期化と割り込みの設定まで行いました。今回は、CANの初期化をしていきます。




データシート確認編

設定の流れなどを確認

必要と思われるところをピックアップしました。

RM0316より引用

今回は、31.3.2の制御レジスタ、ステータスレジスタ、設定レジスタを確認をしなければならないということがわかりました。
また、31.4.2初期化モードのところに初期化を行う際に初期化のビットをセットしてハードウェアの準備ができるまで待つ必要があるということがわかりました。SLEEPにも初期化のことが書いてありました。
以上のことより、初期化は以下の流れで行っていきます。

  1. CANをSLEEPから復帰
  2. 初期化ビットをたてて初期化モードに入るまで待つ
  3. 初期化用の設定を行う
  4. CANフィルタの設定を行う(以降は次回)
  5. 割り込みの有効化
  6. CANを通常モードに戻す

フィルタの初期化はCANの初期化モードでなくても可能のようです。ただし、フィルタのスケールとモードの設定は通常モードに入る前に行う必要があるようなので、 この記事ではこの流れで初期化を行います。

レジスタを確認

CAN_MCR

RM0316より引用

CAN_MSR

RM0316より引用

CAN_BTR

RM0316より引用

設定を行うレジスタの配置がわかりました。

CAN初期化の実装

前回生成したファイルと今回新しいファイルを作成してプログラムを書いていきます。レジスタをたたくファイルとしてcan_driver.c/hファイルを作成しました。

実装について

can_driver.h

#ifndef __CAN_DRIVER_H__
#define __CAN_DRIVER_H__

#ifdef __cplusplus
extern "C" {
#endif

#include "main.h"

/**
* @brief CANの初期化用構造体
*/
typedef struct {
  uint32_t Prescaler; // BRP[9:0] プリスケーラ 1~1024(BTR)
  uint32_t Mode; // SILM LBKM モードを設定(BTR)
  uint32_t SyncJumpWidth; // SJW[1:0] 再同期ジャンプ幅(BTR)
  uint32_t TimeSeg1; // TS1[3:0] 時間セグメント 1(BTR)
  uint32_t TimeSeg2; // TS2[2:0] 時間セグメント 2(BTR)
  FunctionalState TimeTriggerMode; // TTCM タイムトリガ通信モード(MCR)
  FunctionalState AutoBussOff; // ABOM 自動バスオフ管理(MCR)
  FunctionalState AutoWakeUp; // AWUM 自動ウェイクアップモード(MCR)
  FunctionalState AutoRetransmission; // NART 自動再送信禁止(MCR)
  FunctionalState ReceiveFifoLocked; // RFLM 受信 FIFO ロックモード(MCR)
  FunctionalState TransmitFifoPriority; // TXFP 送信 FIFO 優先順位(MCR)
} CAN_InitTypeDef;

void CAN_Reg_Init(CAN_TypeDef* CANx, CAN_InitTypeDef* Init);

#ifdef __cplusplus
}
#endif
初期化用構造体を用意しました。LLでCANが用意されていないので、HALで初期設定をするときに指定ができるパラメータを設定できるようにしました。各種変数名はHALと一緒ですが、型指定はHALと違うところがあります。

can_driver.c

void CAN_Reg_Init(CAN_TypeDef* CANx, CAN_InitTypeDef* Init) {
  // SLEEPモードから起動させる
  CLEAR_BIT(CANx->MCR, CAN_MCR_SLEEP);
  // SLEEPから起動するまでまつ
  while ((CANx->MSR & CAN_MSR_SLAK) != 0);
  // CANの初期化リクエストを行う
  SET_BIT(CANx->MCR, CAN_MCR_INRQ);
  // 初期化のリクエストの確認応答がくるまでまつ
  while ((CANx->MSR & CAN_MSR_INAK) == 0);
  // ビットを一つずつ設定する
  // タイムトリガ通信モードの設定
  if (Init->TimeTriggerMode == ENABLE) SET_BIT(CANx->MCR, CAN_MCR_TTCM); 
  else CLEAR_BIT(CANx->MCR, CAN_MCR_TTCM);
  // 自動バスオフ管理の設定
  if (Init->AutoBussOff == ENABLE) SET_BIT(CANx->MCR, CAN_MCR_ABOM);
  else CLEAR_BIT(CANx->MCR, CAN_MCR_ABOM);
  // 自動ウェイクアップモードの設定
  if (Init->AutoWakeUp == ENABLE) SET_BIT(CANx->MCR, CAN_MCR_AWUM);
  else CLEAR_BIT(CANx->MCR, CAN_MCR_AWUM);
  // 自動再送信禁止の設定
  if (Init->AutoRetransmission == ENABLE) CLEAR_BIT(CANx->MCR, CAN_MCR_NART);
  else SET_BIT(CANx->MCR, CAN_MCR_NART);
  // 受信 FIFO ロックモードの設定
  if (Init->ReceiveFifoLocked == ENABLE) SET_BIT(CANx->MCR, CAN_MCR_RFLM);
  else CLEAR_BIT(CANx->MCR, CAN_MCR_RFLM);
  // 送信 FIFO 優先順位の設定
  if (Init->TransmitFifoPriority == ENABLE) SET_BIT(CANx->MCR, CAN_MCR_TXFP);
  else CLEAR_BIT(CANx->MCR, CAN_MCR_TXFP);
  // BTRレジスタの設定を行う
  WRITE_REG(CANx->BTR, (uint32_t)Init->Mode | Init->SyncJumpWidth |
            Init->TimeSeg1 | Init->TimeSeg2 |(Init->Prescaler - 1U));
}

LLで定義されているレジスタをたたくマクロを使用してレジスタのアクセスは行っています。また、CNA_MCR_xxxxなどはstm32f303x8.hに定義されているものを使用しています。

can.c(追記)

void CAN_Param_Init(void) {
  // CANの設定用構造体を定義
  CAN_InitTypeDef CAN_InitStruct = {0};
  CAN_InitStruct.Prescaler = 4;
  CAN_InitStruct.Mode = (uint32_t)(0x00 << CAN_BTR_LBKM_Pos); // 通常動作、ループバックモードは無効
  CAN_InitStruct.SyncJumpWidth =(uint32_t)(0x00 << CAN_BTR_SJW_Pos); // t_RJW =t_q*(SJW[1:0] + 1)より1
  CAN_InitStruct.TimeSeg1 = (uint32_t)(0x0000000A << CAN_BTR_TS1_Pos); // 11に設定(0のとき1のため11-1=10)
  CAN_InitStruct.TimeSeg2 = (uint32_t)(0x00000003 << CAN_BTR_TS2_Pos); // 4に設定(0のとき1のため4-1=3)
  CAN_InitStruct.TimeTriggerMode = DISABLE; // 無効に設定
  CAN_InitStruct.AutoBussOff = DISABLE; // 無効に設定
  CAN_InitStruct.AutoWakeUp = DISABLE; // 無効に設定
  CAN_InitStruct.AutoRetransmission = DISABLE; // 無効に設定
  CAN_InitStruct.ReceiveFifoLocked = DISABLE; // 無効に設定
  CAN_InitStruct.TransmitFifoPriority = DISABLE; // 無効に設定
  // CANの初期化の設定用関数を呼ぶ
  CAN_Reg_Init(CAN, &CAN_InitStruct);
}

can_driver.hをインクルードして、先ほど定義したCAN初期化用の構造体を用いて設定を行いました。uint32_tで定義した変数に関しては、レジスタの設定位置にデータをシフトするということをここで行うこととしました。

CAN初期化設定のパラメータについて

CAN通信を行う上でのパラメータがCAN_Param_Init関数ででてきたため、今回の設定を説明します。
前回の記事のSTM32CubeMXのAPB1のクロックを用いて行います。
ビットタイミングについては、リファレンスマニュアルの以下の記載を参考に設定しました。

RM0316より引用

今回の設定では、ボードレートは500kbps、サンプリングポイントは75%に設定しました。80%以上が良いとされていますが、500Kbpsに合わせようとしたときに、80%以上にすることが難しかったためです。

おわりに

楽しく(しんどく)なってきましたね!
実装するときに、初期化構造体をなしで実装をしてしまうか悩みましたが汎用性を上げるために、LLAPIの他の周辺機能と同じように実装をしました。
レジスタ=1 << 12とか書いてあるよりもわかりやすい方がいいかなと思っています。パラメータを変えるたびにリファレンスマニュアルを読まなければならないのは嫌だというお気持ち表明みたいなもんです。

参考文献

RM0316