MCUXpresso SDKペリフェラルドライバーの使い方~其の一~

MCUXpresso SDKペリフェラルドライバーの使い方~其の一~

今回はMCUXPresso SDKの使い方として、NXPマイコンのペリフェラルドライバーを使うときにどうやって組み込むのか、どうやって使ったらいいのかといった話をよく聞きます。

ドライバーを使う場合には、2つ方法があります。

1)ペリフェラルドライバーをソースコードでコンフィグする

2)MCUXpresso Config Toolでコンフィグする

今回は、1つめの「自分のでコンフィグする」方法を簡単に説明したいと思います。次回、「MCUXpresso Config Tool」ツールでコンフィグする方法を紹介します。

では、行ってみましょう。

まずMCUXPresso SDKでサポートされているペリフェラルドライバーを組み込む際のワークフローを説明します。

ここでは、例として、SPIドライバーをマスターとして組み込んで、データの出力を確認します。

事前準備

使用するボード

FRDM-K64F

少し古いボードですが、基本的にどのボードでも、どのドライバーでも同じなので参考にできます。

MCUXpresso IDEのダウンロードとインストール

まず、事前にMCUXPresso IDEをダウンロードしてインストールできていることを前提とします。

ペリフェラルドライバーの組み込みワークフロー

さて、早速ドライバーを組み込んで行きたいと思います。組み込み方法としては、

  1. ドライバーの組み込み
  2. 使用するピンのマルチプレックス設定
  3. SPIドライバーのコンフィグ
  4. データ送信用バッファとトランスファー構造体の用意
  5. (ハンドルの作成)
  6. ドライバーのAPIでデータ送受信

ワークフローとしてはこんな感じです。あとは、都度データの送受信を行いたいときにトランスファー構造体やハンドルを指定してSPIのデータ送信・受信APIをコールするといった感じです。

出来上がったコード

まず先に出来上がったコードを紹介します。

ファイル:led_blinky.c (プロジェクト:SDK demo apps/led_blinky)

#include "fsl_dspi.h" //インクルードするのを忘れないように追加する
/*SPIのクロックを取得*/
#define SPI0_CLK_FREQ CLOCK_GetFreq(DSPI0_CLK_SRC)
/*データ送信用バッファと転送構造体*/
#define TRANSFER_SIZE 32
uint8_t TransmitData[TRANSFER_SIZE]={0};
dspi_transfer_t xfer;
:
:
省略
:
:
int main(void)
{
    /* Board pin init */
    BOARD_InitPins();
    BOARD_InitBootClocks();

    /* まず、SPIマスターとして設定するため、マスターコンフィグのデフォルト設定を取得 */
    dspi_master_config_t masterConfig;
    DSPI_MasterGetDefaultConfig(
    		&masterConfig//dspi_master_config_t *masterConfig
			);
    /*設定するところだけ変更する 今回はマスターボーレートだけを変更する
     * dspi_master_config_t  masterConfig;
	 *   masterConfig.whichCtar                                = kDSPI_Ctar0;
	 *   masterConfig.ctarConfig.baudRate                      = 500000000U; --> 10000000U
	 *   masterConfig.ctarConfig.bitsPerFrame                  = 8;
	 *   masterConfig.ctarConfig.cpol                          = kDSPI_ClockPolarityActiveHigh;
	 *   masterConfig.ctarConfig.cpha                          = kDSPI_ClockPhaseFirstEdge;
	 *   masterConfig.ctarConfig.direction                     = kDSPI_MsbFirst;
	 *   masterConfig.ctarConfig.pcsToSckDelayInNanoSec        = 1000000000U / masterConfig.ctarConfig.baudRate ;
	 *   masterConfig.ctarConfig.lastSckToPcsDelayInNanoSec    = 1000000000U / masterConfig.ctarConfig.baudRate ;
	 *   masterConfig.ctarConfig.betweenTransferDelayInNanoSec = 1000000000U / masterConfig.ctarConfig.baudRate ;
	 *   masterConfig.whichPcs                                 = kDSPI_Pcs0;
	 *   masterConfig.pcsActiveHighOrLow                       = kDSPI_PcsActiveLow;
	 *   masterConfig.enableContinuousSCK                      = false;
	 *   masterConfig.enableRxFifoOverWrite                    = false;
	 *   masterConfig.enableModifiedTimingFormat               = false;
	 *   masterConfig.samplePoint                              = kDSPI_SckToSin0Clock;
     * */
    masterConfig.ctarConfig.baudRate = 10000000U;

    /*SPIの初期化*/
    DSPI_MasterInit(
    		DSPI0,			//SPI_Type *base, 今回はDSPI0モジュールを使用
		&masterConfig,	//const dspi_master_config_t *masterConfig,
		SPI0_CLK_FREQ	//uint32_t srcClock_Hz マクロ定義したものを使用
		);

    /*転送データの準備*/
    for (uint8_t i; i<TRANSFER_SIZE; i++)
    	TransmitData[i] = i;

    /*転送構造体*/
    xfer.txData = TransmitData;
    xfer.dataSize = TRANSFER_SIZE;

    /*SPIでデータを転送*/
    DSPI_MasterTransferBlocking(DSPI0, &xfer);

    /* Set systick reload value to generate 1ms interrupt */
    if (SysTick_Config(SystemCoreClock / 1000U))
    {
        while (1)
        {
        }
    }

    while (1)
    {
        /* Delay 1000 ms */
        SysTick_DelayTicks(1000U);
        GPIO_PortToggle(BOARD_LED_GPIO, 1u << BOARD_LED_GPIO_PIN);
    }
}

ファイル:pin_mux.c

void BOARD_InitPins(void)
{
    /* Port A Clock Gate Control: Clock enabled */
    CLOCK_EnableClock(kCLOCK_PortA);
    /* Port B Clock Gate Control: Clock enabled */
    CLOCK_EnableClock(kCLOCK_PortB);

    /* Port Dのクロックを有効にする */
    CLOCK_EnableClock(kCLOCK_PortD);
    /* Port D0~D2 のピンマルチプレックスをSPIに変更する */
    PORT_SetPinMux(
    		PORTD, 		//PORT_Type *base,
    		0U,		//uint32_t pin, PORTD_0 SPI0_PCS
		kPORT_MuxAlt2	//port_mux_t mux
   );
    PORT_SetPinMux(
        	PORTD, 		//PORT_Type *base,
        	1U,		//uint32_t pin, PORTD_1 SPI0_SCK
    		kPORT_MuxAlt2	//port_mux_t mux
    );
    PORT_SetPinMux(
        	PORTD, 		//PORT_Type *base,
        	2U,		//uint32_t pin, PORTD_2 SPI0_SOUT
    		kPORT_MuxAlt2	//port_mux_t mux
    );

    gpio_pin_config_t LED_RED_config = {
        .pinDirection = kGPIO_DigitalOutput,
        .outputLogic = 0U
    };
    /* Initialize GPIO functionality on pin PTB22 (pin 68)  */
    GPIO_PinInit(BOARD_LED_RED_GPIO, BOARD_LED_RED_PIN, &LED_RED_config);

    /* PORTA2 (pin 36) is configured as TRACE_SWO */
    PORT_SetPinMux(PORTA, 2U, kPORT_MuxAlt7);

    PORTA->PCR[2] = ((PORTA->PCR[2] &
                      /* Mask bits to zero which are setting */
                      (~(PORT_PCR_PS_MASK | PORT_PCR_PE_MASK | PORT_PCR_DSE_MASK | PORT_PCR_ISF_MASK)))

                     /* Pull Select: Internal pulldown resistor is enabled on the corresponding pin, if the
                      * corresponding PE field is set. */
                     | PORT_PCR_PS(kPORT_PullDown)

                     /* Pull Enable: Internal pullup or pulldown resistor is not enabled on the corresponding pin. */
                     | PORT_PCR_PE(kPORT_PullDisable)

                     /* Drive Strength Enable: Low drive strength is configured on the corresponding pin, if pin
                      * is configured as a digital output. */
                     | PORT_PCR_DSE(kPORT_LowDriveStrength));

    /* PORTB22 (pin 68) is configured as PTB22 */
    PORT_SetPinMux(BOARD_LED_RED_PORT, BOARD_LED_RED_PIN, kPORT_MuxAsGpio);
}

では、細かく説明していきます。

1.ドライバーを組み込む

まず、ドライバーを組み込みます。ここでは、FRDM-K64Fボード用に用意されているサンプルプロジェクト「led_blinky」にSPIドライバーを組み込みます。

「Manage SDK Components」をクリックして、SDKの組み込むソフトウェアの一覧を表示させます。

Manage SDK Componentsをクリック
Manage SDK Componentsをクリック

次に、開いた一覧画面内にある「Drivers」タブを選択して、「dspi」を選んでOKをクリックします。

ドライバーの選択
ドライバーの選択

このSDKコンポーネンツマネージャでは、ペリフェラルドライバー以外にもミドルウェアやユーティリティといったソフトウェアがリストされていて、それらをチェックしてOKをクリックするだけでソフトウェアコンポーネンツを取り込むことが可能になっています。

2.使用するピンのマルチプレックスとクロックの有効化

次に、SPIで使用するピンの設定やクロックの設定を行います。

CLOCK_EnableClock(kCLOCK_PortD); //回路モジュールに対するクロックの有効化、ここではPort Dモジュールへのクロックです。

PORT_SetPinMux( PORTD,  0U, PORTD_0 SPI0_PCS kPORT_MuxAlt2 ); //マルチプレックスされている機能の選択とポートとピン番号の指定をします。

今回は、FRDM-K64FボードのJ2ピンヘッダの6番ピン(CS)、8番ピン(SOUT)、12番ピン(SCK)と設定しています。

3.SPIモードの設定と初期化

SPIマスターとして設定する場合のモード設定や構成を設定します。おっと、忘れてはいけないのが、ドライバーの.hファイルをmain関数のソースファイルにインクルードしておきます。

まず、ペリフェラルドライバーの初期化関数に渡すデータ構造体を作成して、必要なモードなどの設定を行います。

データ構造を作成する際に便利なのが、DSPI_MasterGetDefaultConfig()関数です。各項目のデフォルト値のデータ構造を取得できて、設定変更するものだけを変更すればいいわけです。今回は、ボーレートだけを変更します。

dspi_master_config_t masterConfig;  //データ構造の構造体変数を用意
DSPI_MasterGetDefaultConfig( &masterConfig); //その構造体にデフォルト値を設定してくれます。
masterConfig.ctarConfig.baudRate = 10000000U; //10MHzに変更

4.データ送受信用のデータバッファと転送用構造体を用意

データを送受信するためのバッファを用意します。

今回はTransmitData[]とします。データは8bit/frameの設定なのでuint8_t型の配列とします。

トランスファー構造体は、転送用データバッファの指定や転送サイズをメンバーに持つ構造体になっています。

#define TRANSFER_SIZE 32
uint8_t TransferData[TRANSFER_SIZE]={0}; //0で初期化しておきます。
dspi_transfer_t xfer;

xfer.txData = TransmitData;
xfer.dataSize = TRANSFER_SIZE;

5.ハンドルを作成(割り込みを利用の場合)

ここでは、ポーリング(ブロッキング)を利用したAPIを使用するので、ハンドルは使用しません。割り込み(ノンブロッキング)を利用したAPIの場合は、このハンドルが必要になります。

ハンドルは、割り込みを利用した場合にいくつものタスクなどからSPI転送を行ったとき、どのデータ転送が完了したのか分からなくなるので、ハンドルの変数名とそのデータ構造を紐づけておく感じです。そして、転送が完了したら、指定したコールバックにハンドルを渡してコールバックしてくれるって感じですね。

ここでは、ハンドルを使用する場合の各変数とそのハンドル作成APIの例を示すだけにしておきます。

dspi_master_handle_t master_handle;
DSPI_MasterTransferCreateHandle(DSPI0, &master_handle, SPI_Callback, NULL);
コールバック関数:void SPI_Callback( SPI_Type *base, dspi_master_handle_t *handle, status_t status, void *userData){
処理
}

6.ドライバーのAPIでデータの送信をする

最後に、準備ができたところやデータ転送を開始したいところで転送開始用のAPIをコールします。渡す引数はSPIインスタンスとトランスファー構造体(割り込みの場合はハンドルも渡す)です。

DSPI_MasterTransferBlocking(DSPI0, &xfer);

参考情報:ブロッキング?ノンブロッキングとは?
NXPのSDKでは、ポーリングを利用したAPIのことを「Blocking」と呼んでいます。一方、割り込み(Interrupt)を利用したAPIは、Non-blockingと呼んでいます。 Blockingは、データを転送するときに転送が完了するまでポーリングして、完了しないとそのAPI関数からリターンしない(ブロッキングする)ので、こう呼ばれています。そして、割り込みを利用したAPIはその関数を実行すると、転送を完了しないうちにリターン(ブロッキングしないで)してきます。そして転送完了すると指定したコールバック関数を呼んで転送完了を知らせてくれます。

最後にSPI出力をキャプチャしてみた

上記のコードを実際に動作させてみてデータをキャプチャしてみました。きちんと出力されてますね。

ちなみに、使用したロジアナはEmbedded Artists社から出ているLabtool(ラボツール)を使用しました。USBで接続できロジアナやアナログ信号入出力が可能な簡易なオシロとしても利用可能で重宝してます。

SPIデータのキャプチャ①
SPIデータのキャプチャ①
SPIデータのキャプチャ②
SPIデータのキャプチャ②

最後に

今回は、NXPのMCUXpresso SDKの使い方でペリフェラルドライバーの組み込み方を紹介しました。SPIを例に紹介しましたが、基本的には、使用するピンのマルチプレックス設定やクロック有効化、初期化に必要なペリフェラルのデータ構造のコンフィグを設定して初期化するというワークフローです。そして、転送用のデータバッファやデータを準備できたら転送用のAPIで転送するだけなので、簡単ですよね。

是非、試してみてください。