FlexCANの使い方をまとめてみた(NXP-ARMマイコン)

NXP FRDM-K64FのマイコンにはFlexCANとよばれるモジュールが付いていて、CAN通信が使えます。今回はその機能を使うために必要なことをまとめてみました。

CAN?ふぉあ?って人はここで解説してます。

CANの説明。

あと、実際に通信するためにはCANトランシーバというICが必要な場合があります。自分がつかっているK64Fには、トランシーバ実装されてないので、ブレッドボードとかでICとつなげてやらないとダメです。こっちを参考にしてください。

CANトランシーバをつける。

んで、準備ができたところで、いってみましょう。今回の記事は実際にFlexCANを使う時に自分が理解に苦しんだメッセージバッファーの仕組みとビットタイミングの設定を中心にしてこうと思います。

FlexCANのシステム

データシートを確認すると、下のようなシステム図がまず出てきます。

コントローラは基本的に図の右側にあるメッセージバッファーを使って、データを受信したり、送信したりするそうです。説明のなかにmailboxという表現がありましたが、まさにそんな感じっぽい動きをします。例えば、受信するときは

コントローラはデータを指定のメッセージバッファに渡します。

下の絵みたいに、受信するIDによって、データを渡すメッセージバッファの相手を変えたりもできます。メッセージバッファは何個もある訳です。

送信するときはメッセージバッファの値をひろって、CANバスに流します。

コントローラは郵便屋さんで、MBは郵便ポスト的な役割をします。

メッセージバッファはメッセージがきたら、割込み処理を発生させることもできます。

メッセジバッファはK64の場合は16個あります。プログラムではMB[0]~MB[15]みたいに配列化されてます。

MBの実際の中身は?

MBは一個あたり16バイトのデータになってます。内訳は下のような感じです。よくわからない項目もありますが、大事なのはIDとData Byte。コントローラが受信したデータは、ここにはいってきます。送信する場合は、逆にここにデータをいれます。

実際のコードでのMBの定義です。4バイトずつで、定義して、MB[9].WORD0=xxxとかみたく、アクセスします。

  struct {                                         /* offset: 0x80, array step: 0x10 */
    __IO uint32_t CS;
    __IO uint32_t ID;
    __IO uint32_t WORD0;
    __IO uint32_t WORD1;
  } MB[16];

フィルタ機能

CANでは、色々なノードをCANバスにぶらさげる前提で開発されているので、とあるIDのメッセージによっては、あまり興味ないからスルーしときたいというのもあります。これがフィルタ機能です。個人的には、この意味が最初わからなかった。

実際のIDのフィルタ設定は下のRXIMRnというレジスタで設定できます。bit0~bit28までの設定は、さっきMBの絵の0x4のbit0~bit28に相当してます。

MB[9]を使用して、IDを0x777にセットした時

#9のMBのIDに0x777を設定する場合を考えます。11bitの標準フォーマットなので、MBのID(Extended)というとこには全部0が入ります。

なので、プログラム的にはbit18~bit28にIDを代入するので、

MB[9].ID=0x777<<18U;

とかします。何気にわかりにくなこの仕様は…。

RXIMRnは、MB毎に設定できるので、MBの数だけあります。今回はMB[9]なので、RXIMR9のレジスタを設定します。(個別フィルタ機能)

下のイラストのように全部のbitに1を立てると、そのMBは、0x777のIDしか受信しなくなります。要は0x777に対して、すべてのbitがチェックされる訳になり、完全一致する777しか受けつけなくなるということです。逆に全部0にしたら、今度はすべてのbitが777と一致しなくてもいーよってなり、すべてのIDを受信します。

その下の例で、ID=0x700~0x7FFまで受信したい場合は、0x700<<18をレジスタに設定すれば、上位3bitだけは777と比較一致する条件になるので、結果として、700番代のIDしか受信しなくなります。

こんな感じで、IDをbit単位で、チェックする!しない!を決めて、メッセージを振るい落とすような仕組みになってます。

ちょいと、頭いいぜ。

プロジェクトの作成

CANを使うなら、フルスクラッチで書くよりも、SDKにはいってるfsl_flexcan.hとfsl_flexcan.cというドライバを使った方が、大分いいです。

なので、ドライバのAPIを使いながら、コードを書いてきます。まずプロジェクト作成を。

この資料を書いた時点で、自分の環境はMCUXpresso v11.1.0、SDKv2.7.0 (FRDM-K64F)でした。

Quickstart PanelにあるNew projectをクリックして、SDKウィザードを立ち上げます。

自分の使っている開発ボードを選択して、NEXT。

プロジェクト名を適当につけたら、Driversタブのflexcanにチェックをいれます。ドライバはversion2.5.0みたいですね。

Finishをおしたら、プロジェクトができあがりです。するとExplorerのdriversフォルダにCANのドライバが入ってるはずです。

クロックを変更しとく

クロックの設定が初期設定のままだと、CANのビットタイミングの計算が場合によっては、打ち切り誤差の影響でうまくいかない場合がありました。なので、作動周波数は同じですが、分周比をうまく割り切れるような値に変更しといた方が無難です。

クロックの設定は、右上のアイコンをクリックすると編集画面がでます。

PLLの項目で、PRDIVとVDIVの値を変えときます。

ピンをアサインしとく

FRDM-K64Fの場合は、CAN0用のTX/RXのピンは#48-#49か#64-#65に用意されてるので、ピンコンフィグ用のツールを使って、有効化しときます。

サンプルコード

とりあえず、ID=0x777のメッセージを受信だけするサンプルコードがつくってみました。それが下です。

中身は、low power timerで100ms周期のタスクを呼び出し、そのなかでメッセージを周期的に取得しています。

low power timer の使用方法は別で説明してますので、参考にしてください。

#include <stdio.h>
#include "board.h"
#include "peripherals.h"
#include "pin_mux.h"
#include "clock_config.h"
#include "MK64F12.h"
#include "fsl_debug_console.h"

//CAN用のライブラリをインクルード
#include "fsl_flexcan.h"

//lp timer
#include "fsl_lptmr.h"
/*******************************************************************************
 * Definitions
 ******************************************************************************/

//CAN のクロックを定義
#define CAN_CLKSRC kCLOCK_BusClk
#define CAN_CLK_FREQ CLOCK_GetFreq(kCLOCK_BusClk)

//使用するメッセージバッファを定義する0-15まである
#define RX_MESSAGE_BUFFER_NUM (9) //受信用はMB[9]

//送信するデータの長さ 8は8バイト(これがマックス)
#define DLC (8)

//ビットタイミングの設定、通信する相手にあわせる
#define SET_CAN_QUANTUM 1
#define PSEG1 5
#define PSEG2 2
#define PROPSEG 1
#define RJW 1

//通信速度(ボーレート)の設定、1Mbpsにする
#define baudRate_Bps_can 1000000U

//Low power timerのハンドラ
#define LPTMR0_HANDLER LPTMR0_IRQHandler
#define LPTMR_SOURCE_CLOCK CLOCK_GetFreq(kCLOCK_LpoClk)
#define LPTMR_USEC_COUNT 1000U

/*******************************************************************************
 * Variables
 ******************************************************************************/
//CANハンドラー
flexcan_handle_t flexcanHandle;
//CANコンフィグ
flexcan_config_t flexcanConfig;
//メッセージバッファ
flexcan_rx_mb_config_t mbConfig;

flexcan_mb_transfer_t rxXfer;
flexcan_frame_t frame;

//RX IDs
uint32_t rxIdentifier;

volatile uint32_t lptmrCounter = 0U;
// ここで100ms周期のタスクを定義
void LPTMR0_HANDLER(void)
{
    LPTMR_ClearStatusFlags(LPTMR0, kLPTMR_TimerCompareFlag);
    lptmrCounter++;
    if (lptmrCounter % 100 ==0)
    {
// CANの受信
    	if(kStatus_FLEXCAN_RxBusy==FLEXCAN_TransferReceiveNonBlocking(CAN0, &flexcanHandle,&rxXfer))
    	{
    	}
    }
}

static void flexcan_callback(CAN_Type *base, flexcan_handle_t *handle, status_t status, uint32_t result, void *userData)
{
	  switch (status)
	    {
	        case kStatus_FLEXCAN_RxIdle:
	            break;
	        case kStatus_FLEXCAN_TxIdle:
	        	break;
	        case kStatus_FLEXCAN_TxSwitchToRx:
	            break;
	        case kStatus_FLEXCAN_WakeUp:
	            break;
	        case kStatus_FLEXCAN_ErrorStatus:
	        	break;
	    }
}

int main(void) {
  	/* Init board hardware. */
    BOARD_InitBootPins();
    BOARD_InitBootClocks();
    BOARD_InitBootPeripherals();
  	/* Init FSL debug console. */
    BOARD_InitDebugConsole();

    //CAN
    rxIdentifier = 0x777;
    //初期設定を読込
    FLEXCAN_GetDefaultConfig(&flexcanConfig);

    //初期設定からの変更項目を上書く
    //クロックソース
        flexcanConfig.clkSrc = kFLEXCAN_ClkSrcPeri;
    //1Mbpsに設定
	flexcanConfig.baudRate=baudRate_Bps_can;
    //Bit timing 設定
	flexcanConfig.timingConfig.phaseSeg1=PSEG1;
        flexcanConfig.timingConfig.phaseSeg2=PSEG2;
        flexcanConfig.timingConfig.propSeg=PROPSEG;
        flexcanConfig.timingConfig.rJumpwidth=RJW;
        flexcanConfig.enableIndividMask = true;

   //設定反映
    FLEXCAN_Init(CAN0, &flexcanConfig, CAN_CLK_FREQ);

    //メッセージバッファの設定
    mbConfig.format =  kFLEXCAN_FrameFormatStandard;
    mbConfig.type =kFLEXCAN_FrameTypeData;
    mbConfig.id = FLEXCAN_ID_STD(rxIdentifier);

    rxXfer.mbIdx=RX_MESSAGE_BUFFER_NUM;
    rxXfer.frame=&frame;

    FLEXCAN_SetRxMbConfig(CAN0, RX_MESSAGE_BUFFER_NUM, &mbConfig, true);

    uint32_t mask = 0x777<<18U;
    FLEXCAN_SetRxIndividualMask(CAN0,RX_MESSAGE_BUFFER_NUM,mask);

    FLEXCAN_TransferCreateHandle(CAN0, &flexcanHandle, flexcan_callback, NULL);

    lptmr_config_t lptmrConfig;
    LPTMR_GetDefaultConfig(&lptmrConfig);
	LPTMR_Init(LPTMR0, &lptmrConfig);
    LPTMR_SetTimerPeriod(LPTMR0, USEC_TO_COUNT(LPTMR_USEC_COUNT, LPTMR_SOURCE_CLOCK));
	LPTMR_EnableInterrupts(LPTMR0, kLPTMR_TimerInterruptEnable);
	/* Enable at the NVIC */
	EnableIRQ(LPTMR0_IRQn);
	/* Start counting */
	LPTMR_StartTimer(LPTMR0);

    while(1) {

    }
    return 0 ;
}

サンプルコードの説明

とりあえず、ひとつずつ。基本的にCANの設定は、3段階に分かれている感があって、最初はコントローラ自体の動作設定、次に、冒頭で説明したMBの設定、最後にハンドラーの設定になります。

注:サンプル内のコードを引用してますが、説明のため、一部変更してます。即値で書いた方が分かりやすい時とか

CANコンフィグ

これは前半のコントローラの初期設定です。サンプルでは、ライブラリで定義されているflexcan_config_t という構造体をつかって、CANの初期化をします。

//CANコンフィグ こいつに初期設定をいろいろいれる
flexcan_config_t flexcanConfig;

F3キーで定義元を参照するとさっきインクルードしたライブラリ内で下のように定義されてます。コメントに説明を足しました。機能がよくわかってないのはかっとばしてます。

typedef struct _flexcan_config
{
    uint32_t baudRate;                  //ボーレート
    flexcan_clock_source_t clkSrc;      //CANコントローラのクロックのソースを指定する
    flexcan_wake_up_source_t wakeupSrc; //WakeUp信号の処理方法をきめる
    uint8_t maxMbNum;                   //MBを何番まで使うかの設定
    bool enableLoopBack;                //ループバックモードというテスト機能を使うかの設定
    bool enableTimerSync;               //MB[0]のRX受信のタイミングでタイマーリセットするしない
    bool enableSelfWakeup;              //WakeUpモード有効無効
    bool enableIndividMask;             //MBのフィルタ機能を個別に設定する
    bool disableSelfReception;          //
    bool enableListenOnlyMode;          //listenOnlyモードで受信だけする
    flexcan_timing_config_t timingConfig; //重要!ビットタイミングの設定
} flexcan_config_t;

なかでも重要なのは、ボーレート設定、クロックのソース指定、個別フィルタ機能ON・OFF、ビットタイミングの設定あたりかと思います。

Default関数を呼ぶ

ライブラリには↑の設定に常識的な値を勝手にいれてくれる関数があるので、サンプルをそれを読んでいます。設定値カスタマイズする場合は、関数呼び出し後に値を上書きます。

   //初期設定を読込
    FLEXCAN_GetDefaultConfig(&flexcanConfig);

    //初期設定からの変更項目を上書く
    //クロックソース
    flexcanConfig.clkSrc = kFLEXCAN_ClkSrcPeri;
    //1Mbpsに設定
    flexcanConfig.baudRate=1000000;
    //Bit timing 設定
    flexcanConfig.timingConfig.phaseSeg1=5;
    flexcanConfig.timingConfig.phaseSeg2=2;
    flexcanConfig.timingConfig.propSeg=1;
    flexcanConfig.timingConfig.rJumpwidth=1;
    //個別フィルタ機能ON
    flexcanConfig.enableIndividMask = true;

今回は、クロックソースをperipheral clockにして、ボーレートを1Mbps、ビットタイミングは手持ちのインターフェースの設定にあわせ、MB個別フィルタ機能をONにしてみました。

初期化関数をよぶ

サンプルだと、うえで設定した構造体をFLEXCAN_Initという関数に渡して、実際のレジスタに設定を反映してます。

FLEXCAN_Init(CAN0, &flexcanConfig, CAN_CLK_FREQ);

これですね。引数のCAN0はCANの先頭アドレス、flexcanConfigが↑で設定した構造体、CAN_CLK_FREQはCANコントローラの動作周波数です。

Init関数内のソースまでは、説明しませんが、何をやっているかというと、大体下の絵のような感じです。まず、クロックの設定のために、コントローラをDisable状態にして、クロックの設定を書く、Enableに戻した後、ソフトウェアリセット機能で初期化。flexcanConfigの設定を反映する。その後、Freezeモードにして、ボーレートとビットタイミングの設定をするといった流れになっています。

ボーレートとビットタイミング

上のイラストでの最後の処理、ボーレート・ビットタイミングの設定ですが、実際には、初期化関数のなかで、FLEXCAN_SetBaudRateという関数をさらに呼んでいます。

この関数の目的はユーザが指定したビットタイミングとボーレートから適切な動作周波数を計算するというものです。実際、既にコントローラのソースを指定しているので、その分周比を最適化するイメージです。

具体的には、

こんな感じです。ビットタイミングとボーレートからTQ値が分かるので、それを解像できるようなクロック周波数をだして、ソースとの比をとって、分周比を決めてます。

ちょっとややこしいですが、分周比とビットタイミングはレジスタの設定値から+1されたものが、実際の設定値になるっぽい。仕様書に書いてあります↓

CANの初期化処理はこんなところです。

MBのCODE

つづいて、MBの設定ですが、先にMBの仕組みで知っておきたいことをまとめておきます。まずMBの状態を示すCODEという謎の4bit信号についてです。

左上にあるやつですね。CODE。

下のイラストは、各CODEの値に対しての、MBの様子を示しています。通常、データがMBに格納された状態では、FULLかOVERRUNになってます。OVERRUNはメッセージは受信しているけど、CPU側がアクセスするタイミングが遅くて、アクセスする前に新しいデータで上書きされてしまってるケースのことを指します。

BUSYはそのままの意味ですが、CPUのアクセスするタイミングでデータが更新されているなどのケースがあたるそうです。

基本はCODEはステータスなので、情報をプログラムが拾うだけですが、Inactive から Emptyへの遷移はCODEに直接0b0100を書き込みます。

MBの初期化して、スタンバイするってときは0b0100をCODEに代入します。

MBのLOCKとUNLOCK

そして、ついでにMBのロック・アンロックについても、説明しときます。

CPUがMBのCODEを読みにいくと、MBはLOCKされて、読み込み中にデータの更新などが起きないようにするみたいです。読み込みの瞬間は、意識する必要はないですが、読み込みが終わったら、明示的にUNLOCKする必要があるようです。サンプルコードではこうしてました。

    /* Read free-running timer to unlock Rx Message Buffer. */
    (void)CAN0->TIMER;

なぜ?って感じですが、CANのFreeRunningTimerのレジスタを読みにいくとUNLOCKされるようです。レジスタを参考に貼っておきます。

MBの割り込み処理

あともう一つだけ、割り込み処理について、先に説明します。

MBがCANバスから信号を受信したら、割り込み処理を発生させられます。MBは0~15までありますが、IMASK1の指定のbit(今回はMB[9]なので9bit目)に1を書けば、割り込み処理が有効になります。

実際に割り込みが発生したら下のIFLAG1の当該bitに1がたちます。

こんな感じで。

ハンドラーの説明でも出てきますが、実際割り込みがかかっても、どのMBにメッセージがきたか?どうかまでは、プログラムはわかりません。なので、その時は、IFLAG1のレジスタを全部見に行って、1がSetされているMBを見つけ、そこのデータを取得するみたいなことをやってます。

ようやく、メッセージ受信時の処理のSTEP

説明の前段階は終わりました、これで、受信時の流れをようやく説明できます。流れはこうです。

  1. 使用するMBに受信したいIDとデータフォーマット、データタイプを設定する
  2. MBのCODEに0b0100を書き込み、MBの動作をONにする(Inactive->Empty)
  3. MBのデータが更新されると割り込みがかかるようにする
  4. 割り込み処理で、CODEを読み、状態がFULL or OVERRUNになったことを確認し、MBをLOCKする
  5. DATAにアクセスし、値を取得する
  6. FreeRunningTimerのレジスタを読み、UNLOCKする

MBコンフィグ

CANの初期化と同様にMBの設定パラメータをまとめた構造体がライブラリに用意されてます。それがflexcan_rx_mb_config_tです

//メッセージバッファコンフィグ
flexcan_rx_mb_config_t mbConfig;

中身をみると、そんな多くないですが、IDとフレームフォーマットとフレームタイプを定義しているみたいです。

typedef struct _flexcan_rx_mb_config
{
    uint32_t id;              // 
    flexcan_frame_format_t format; //標準フレームか拡張フレームかを設定
    flexcan_frame_type_t type;     //データフレームかリモートフレームかを設定
} flexcan_rx_mb_config_t;

実際、サンプルでは、下のように設定しました。

    //CAN IDを定義
    rxIdentifier = 0x777;   
   
    //メッセージバッファの設定
    mbConfig.format =  kFLEXCAN_FrameFormatStandard; //標準フレームに設定
    mbConfig.type =kFLEXCAN_FrameTypeData;//dataフレームを指定
    mbConfig.id = FLEXCAN_ID_STD(rxIdentifier);// CAN IDを指定

最初のほうで説明してますがIDの設定が間違いやすくて、受信したいIDを0x777と設定したい場合は、0x777<<18Uとして、18bit左シフトしたものをmbConfig.idに代入します。なぜかって?

下のようにMBの構成にあわせてるからです。

プログラムのFLEXCAN_ID_STD(x)というマクロは、この処理を代行してます。

MB初期化

次に下の関数をよんで、実際にmbコンフィグで定義した値を反映させます。

  //MB初期化関数
    FLEXCAN_SetRxMbConfig(CAN0, 9, &mbConfig, true);

ちなみに関数の第二引数は設定するMBの番号です。今回はMB[9]をつかっています。四番目のtrueは、mbconfigの設定を反映するorしないのフラグです。するに決まってんだろってつっこみたくなりますが、なぜかAPIでそうなってます。

この関数の中身の主要なところをさらっていくと、まず、下の初期化処理。MBをInactiveにして、IDとDataを0リセットしてます。

// MB[9]をInactiveにして、Message Bufferを停止する
CAN0->MB[9].CS = 0;

// IDとDATAも0クリア
CAN0->MB[9].ID    = 0x0;
CAN0->MB[9].WORD0 = 0x0;
CAN0->MB[9].WORD1 = 0x0;

次に第四引数がTrueなら、下の処理を実行します。標準フォーマットのデータフレームの受信を想定しているので、処理的にはMBにIDを設定して、CODEに0x4をいれて、スタンバイ状態にするだけです。

//IDにmbConfig内のIDを代入 pMbConfigはmbConfigのポインタ
 CAN0->MB[9].ID = pMbConfig->id
//frameのフォーマットが拡張フレームであれば、IDEに1をセット
 if (kFLEXCAN_FrameFormatExtend == pRxMbConfig->format)
        {
            cs_temp |=  (0x200000U); //bit21を1にset
        }
//frameのデータタイプがリモートなら、RTRを1にセット
 if (kFLEXCAN_FrameTypeRemote == pRxMbConfig->type)
       {
            cs_temp |=  (0x100000U); //bit20を1にset
        }
//CODE=0x4にセット,これでMBがスタンバイ状態になる
 cs_temp |= (((uint32_t)(((uint32_t)(0x4)) << 24U)) & 0xF000000U)
 CAN0->MB[9].CS = cs_temp;

フィルタの設定

フィルタの設定もAPIを使えます。第二引数がフィルタを反映させるMBの番号で、第三引数がフィルタ設定です。ここでは7FFで設定してるので、受信するID= MBで設定したIDってことになります。

FLEXCAN_SetRxIndividualMask(CAN0,9,0x7FF<<18U);

下がAPIの中身です。

// Set Freeze, Halt bits.
    CAN0->MCR |= 0x40000000U;
    CAN0->MCR |= 0x10000000U;
    while (0U == (CAN0->MCR & 0x1000000U))
    {
    }

// Setting Rx Individual Mask value. */
  CAN0->RXIMR[9] = 0x7FF<<18U;

// Clear Freeze, Halt bits.
    CAN0->MCR &= ~0x10000000U;
    CAN0->MCR &= ~0x40000000U;
    while (0U != (CAN0->MCR & 0x1000000U))
    {
    }

RXIMRnのフィルタ設定用レジスタを書き込む前に下のMCRというレジスタを使って、CANモジュールをフリーズモードにしています。実際には、FRZをセットして、フリーズモードを許可し、HALTをセットして、フリーズモードを有効化、FRZACKを監視して、実際にモードに入るまでwhileで処理を待ってます。抜け側は逆の手順になるだけで、同様です。

Handler

結構、ここからが難関です。ハンドラーの定義は、メインコードでは、下の二行ですみますが、API側では、色々とやっています。

全部説明してくと、発散しそうなので、要点だけまとめることにします。

関数の第一引数はCANの先頭アドレス、第二引数は、ハンドラーです。

//CANハンドラー
flexcan_handle_t flexcanHandle;
//ハンドラーの設定
FLEXCAN_TransferCreateHandle(CAN0, &flexcanHandle, flexcan_callback, NULL);

第三の引数は、メインコードで定義されてる関数(関数ポインタ)で、下のようになります。関数内では、今回は特に意味あることは何もしてませんが、割込みが発生した時に実行される関数になります。

static void flexcan_callback(CAN_Type *base, flexcan_handle_t *handle, status_t status, uint32_t result, void *userData)
{
	  switch (status)
	    {
	        case kStatus_FLEXCAN_RxIdle:
	            break;
	        case kStatus_FLEXCAN_TxIdle:
	        	break;
	        case kStatus_FLEXCAN_TxSwitchToRx:
	            break;
	        case kStatus_FLEXCAN_WakeUp:
	            break;
	        case kStatus_FLEXCAN_ErrorStatus:
	        	break;
	    }
}

つまり、FLEXCAN_TransferCreateHandle( CAN0, &ハンドラ、割込み用関数、NULL)とすれば、ハンドラが内部的に持っている割り込み処理用の関数アドレスに引数で渡した割り込み用関数と紐づけられるようになっています。

ハンドラーの中身ですが、その割込み発生時によばれる関数のポインタの他にも、各MBのデータやステータス情報、受信送信時のタイムスタンプ情報などを持ってます。下が、実際のハンドラーの定義になります。

struct _flexcan_handle
{
    flexcan_transfer_callback_t callback; //割り込み時に実行されるcallback関数(ポインタ)
    void *userData;
    flexcan_frame_t *volatile mbFrameBuf[16]; // 各MBのフレーム(dataとかIDとかの情報を保持)

    flexcan_frame_t *volatile rxFifoFrameBuf;     // FIFO用(今回は無視)
    volatile uint8_t mbState[16];    // 各MBのステータス
    volatile uint8_t rxFifoState;    // FIFO用(今回は無視)
    volatile uint32_t timestamp[16]; // 各MBのタイムスタンプ
};

なんとなく、タコっぽいやつです。

関数FLEXCAN_TransferCreateHandleなにやってる?

実際の関数の中身ですが、大体3つ役目があるっぽく、

①ユーザ定義のコールバックをハンドラーに登録する(さっきのやつ)

②どんな時に割り込みを発生させるか決める

③割り込みが発生したら、指定した関数(FLEXCAN_TransferHandleIRQ)を呼ぶように設定

これだけです。あれ?③は、FLEXCAN_TransferCreateHandleに渡した割込み用関数ではないの?ってなりますが、実際はTransferHandleIRQの中で、呼ばれます。

一部関数内のコードを抜粋します。コメントに内容を書いときました。

    //ハンドラーにコールバック関数のポインタを代入 
    handle->callback = callback;

    //s_flexcanIsrは割込み発生時に呼ばれる関数ポインタ、そこに  FLEXCAN_TransferHandleIRQを代入して、
  //毎回この関数が呼ばれるようにしてる
    s_flexcanIsr = FLEXCAN_TransferHandleIRQ;
   // ここから下は割り込みの設定 EnableInterruptsという関数で割り込みの設定ができる
   // 実際には、CTRL1レジスタの割り込み許可のbitを立ててる
    FLEXCAN_EnableInterrupts(
            CAN0, (uint32_t)kFLEXCAN_BusOffInterruptEnable |
      //busoffになったら割り込み発生
                  (uint32_t)kFLEXCAN_ErrorInterruptEnable |
      //errorで割り込み発生
                  (uint32_t)kFLEXCAN_RxWarningInterruptEnable |
    //受信のワーニングで割り込み発生
                  (uint32_t)kFLEXCAN_TxWarningInterruptEnable |
    //送信のワーニングで割り込み発生
                  (uint32_t)kFLEXCAN_WakeUpInterruptEnable);
      //WakeUp時に割り込み発生
    }

    //NVICの設定(別の記事で説明)
    (void)EnableIRQ((IRQn_Type)(s_flexcanRxWarningIRQ[instance]));
    (void)EnableIRQ((IRQn_Type)(s_flexcanTxWarningIRQ[instance]));
    (void)EnableIRQ((IRQn_Type)(s_flexcanWakeUpIRQ[instance]));
    (void)EnableIRQ((IRQn_Type)(s_flexcanErrorIRQ[instance]));
    (void)EnableIRQ((IRQn_Type)(s_flexcanBusOffIRQ[instance]));
    //データの送受信でよばれるのはこれ↓
    (void)EnableIRQ((IRQn_Type)(s_flexcanMbIRQ[instance])); 
}

FLEXCAN_TransferHandleIRQなにやってる?

割込み発生時に必ず呼ばれる関数で、ハンドラーがMBにアクセスして、データを実際に取得してます。メッセージ受信時の処理のSTEPとして、上のほうで、手順をまとめてましたが、STEP4~6に相当する処理をこの関数でやっています。

具体的には、

①受信したデータをMBからハンドラが持っているフレームに値を代入する

        //データをハンドラに渡しているところ。実際は他にもタイムスタンプやIDなどもわたしてる
        mbFrameBuf[9]->dataWord0 = CAN0->MB[9].WORD0;
        mbFrameBuf[9]->dataWord1 = CAN0->MB[9].WORD1;

        //読みこんんだあとMBをアンロックしているところ
        (void)CAN0->TIMER;

②データ受信したMBの割り込み(IMASK)を禁止する

        //MB[9]のデータを読み込んだ後に、割込みを禁止する
        CAN0->IMASK1 &= ~(1<<9U);

④ハンドラが持っているコールバック関数を実行する

        if (handle->callback != NULL)
        {
            handle->callback(base, handle, status, result, handle->userData);
        }

などをしてます。②がちょっと、え?もうデータ取れないじゃんという感じになりますが、100ms周期で、呼んでいるFLEXCAN_TransferReceiveNonBlockingで解決します。

実際の受信メソッド

 if (lptmrCounter % 100 ==0) //100ms周期
{
   //CAN受信のメソッド
   if(kStatus_FLEXCAN_RxBusy==FLEXCAN_TransferReceiveNonBlocking(CAN0, &flexcanHandle,&rxXfer))
   {
   }
}

関数に渡しているrxXferは、

flexcan_mb_transfer_t rxXfer;

で、中身は

typedef struct _flexcan_mb_transfer
{
    flexcan_frame_t *frame; //CANハンドラーがMB毎にもっていたフレームと同じ
    uint8_t mbIdx; //MBの番号
} flexcan_mb_transfer_t;

です。関数の中身を実際みると、ほとんど何もしてません。ハンドラーの持っているフレームに受信したいフレームのアドレスを渡して、割込みを許可しているだけです。

//prxXferはrxXferのポインタ
handle->mbFrameBuf[9] = prxXfer->frame;

//MB[9]の割り込みを許可
CAN0->IMASK1 |= 1<<9U;

受信メソッドといいつつも、実体はタコに任せきりです。

以上です。これでFlex CANの概要が網羅できました。

Flex CAN ばんざい🙌

コメントを残す

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