[MCX A153] LPSPI入門ガイド(日本語ブログ)
本記事では、MCX A153 に搭載されている LPSPI(Low Power Serial Peripheral Interface)の基本概念と、MCUXpresso SDK を用いた実装方法を、初めてNXP製品を扱うエンジニア向けに解説します。まずハードウェアの概要を説明し、その後FRDM-MCXA153ボードをベースに、MCUXpresso SDKの使い方をコードスニペットとともに説明します。
(読了:20分)
1. LPSPIモジュール概要
1.1 LPSPIとは
LPSPI は、外部デバイスとの同期シリアル通信を行うための周辺モジュールです。
ここでの "LP(Low Power)" とは「低消費電力で動作できる設計」 を意味します。
通常のSPIとの違い(Low Powerの視点)
一般的なSPIモジュールと比較したときの LPSPI の特徴は以下です。
低消費電力モード(Stop / Sleep)でも動作継続可能
DMA(eDMA)と連携し、CPU介在を減らすことでシステム全体の電力削減
ハードウェアトリガ(HREQなど)でCPUを起こさず通信開始が可能
LPSPI は「単にSPI通信ができる」だけではなく、 「システム全体を低消費電力で運用するための通信モジュール」として設計されています。
1.2 MCX A153における構成
MCX A153 には複数の LPSPI インスタンスがあり、例えば、以下のような信号が使用されます:
CS (P1_3)
SCK (P1_1)
SDI (P1_2)
SDO (P1_0)
2. LPSPIの基本動作
2.1 信号の役割
SPI通信では、基本的に以下の4線を使用して、通信します。
SPI通信には極性と位相によって、4つのモードがありますが、例えばモード0の場合、それぞれの信号は、以下のタイミングチャートに従います。
2.2 FIFOの役割
LPSPI は内部に FIFO を持っており、送信データ・受信データを一時的に保持できます。
これにより、CPU が 1 バイトずつ即時にレジスタ操作しなくても、ある程度まとめてデータを処理できます。さらに eDMA を組み合わせることで、FIFO へのデータ供給/取り出しを CPU の代わりに自動化できます。
3. DMA(eDMA)転送の基本概念
3.1 なぜ DMA を使うのか
SPI で数バイトだけ送る用途なら単純なポーリングでも動作しますが、実際の組み込み開発では以下の要求が多くあります。
センサデータや表示データなどを連続転送したい
CPU を通信待ちで止めたくない
他の制御処理や RTOS タスクと並行動作したい
通信処理の負荷を下げたい
ここで重要になる考え方が「ノンブロッキング(Non-blocking)」です。
ノンブロッキングとは、「処理の完了を待たずにすぐに関数が戻る動作」のことです。
例えば SPI 通信の場合、通常は以下のような2つの動作モデルがあります。
ブロッキング(Blocking)
送信関数を呼ぶと、通信が終わるまでCPUが待ち続ける
通信中は他の処理ができない
ノンブロッキング(Non-blocking)
送信開始だけ行い、すぐに関数が戻る
実際の通信はバックグラウンド(DMAなど)で進む
CPUはその間に他の処理を実行できる
LPSPI + eDMA を使うと、データ転送の大部分をハードウェア側に任せることができるため、ノンブロッキング通信を実現する事が可能となります。
3.2 DMA転送の考え方
DMA 転送では、CPU は最初に「どこから送るか」「どこへ受けるか」「何バイト送るか」を設定し、転送開始を指示します。
その後は、LPSPI の FIFO 要求に応じて eDMA が自動的にデータ転送を進めます。CPU は待機せず、他の処理を続けることができます。
3.3 動作イメージ
DMA転送時の流れは概ね以下の通りです。
CPU が LPSPI と eDMA を初期化
送信バッファ / 受信バッファ / サイズを設定
CPU が DMA転送開始を指示
eDMA が FIFO 要求に応じてデータを搬送
転送完了時にコールバックで通知
4. SDKドライバの基本フロー
LPSPIを使用するためには、ソフトウェアによって以下の設定が必要となります。
クロック・ピンを設定
LPSPIモジュール初期化
eDMAハンドル定義
LPSPIハンドル生成
転送バッファ設定
転送実行
コールバックで完了確認
MCUXpresso SDKのサンプルプロジェクトを例に、ドライバの使用方法を紹介します。
以下のプロジェクトを参考に、説明します。
参考URL:
Hardware requirements — MCUXpresso SDK Documentation
mcuxsdk-examples/driver_examples/lpspi/edma_transfer/lpspi_edma_transfer.cc at main · nxp-mcuxpresso/mcuxsdk-examples
4.1 クロック設定・ピン設定
まず、クロック、PORTをLPSPI0に初期化します。
クロック有効化を忘れると物理的に動きません。PORTの割り当て設定(MuxAlt)がLPSPI0に一致している事が重要です。
/* クロック有効化 */
void BOARD_InitHardware(void)
{
/* Attach peripheral clock */
CLOCK_SetClockDiv(kCLOCK_DivLPSPI0, 1u);
CLOCK_AttachClk(kFRO12M_to_LPSPI0);
}
/* PORT設定 */
/* PORT1: Peripheral clock is enabled */
CLOCK_EnableClock(kCLOCK_GatePORT1);
/* PORT3: Peripheral clock is enabled */
CLOCK_EnableClock(kCLOCK_GatePORT3);
/* LPSPI0 peripheral is released from reset */
RESET_ReleasePeripheralReset(kLPSPI0_RST_SHIFT_RSTn);
/* PORT1 peripheral is released from reset */
RESET_ReleasePeripheralReset(kPORT1_RST_SHIFT_RSTn);
/* LPSPI1 peripheral is released from reset */
RESET_ReleasePeripheralReset(kLPSPI1_RST_SHIFT_RSTn);
/* PORT3 peripheral is released from reset */
RESET_ReleasePeripheralReset(kPORT3_RST_SHIFT_RSTn);
const port_pin_config_t port1_0_pin135_config = {/* Internal pull-up/down resistor is disabled */
.pullSelect = kPORT_PullDisable,
/* Low internal pull resistor value is selected. */
.pullValueSelect = kPORT_LowPullResistor,
/* Fast slew rate is configured */
.slewRate = kPORT_FastSlewRate,
/* Passive input filter is disabled */
.passiveFilterEnable = kPORT_PassiveFilterDisable,
/* Open drain output is disabled */
.openDrainEnable = kPORT_OpenDrainDisable,
/* Low drive strength is configured */
.driveStrength = kPORT_LowDriveStrength,
/* Normal drive strength is configured */
.driveStrength1 = kPORT_NormalDriveStrength,
/* Pin is configured as LPSPI0_SDO */
.mux = kPORT_MuxAlt2,
/* Digital input enabled */
.inputBuffer = kPORT_InputBufferEnable,
/* Digital input is not inverted */
.invertInput = kPORT_InputNormal,
/* Pin Control Register fields [15:0] are not locked */
.lockRegister = kPORT_UnlockRegister};
/* PORT1_0 (pin 135) is configured as LPSPI0_SDO */
PORT_SetPinConfig(PORT1, 0U, &port1_0_pin135_config);
const port_pin_config_t port1_1_pin136_config = {/* Internal pull-up/down resistor is disabled */
.pullSelect = kPORT_PullDisable,
/* Low internal pull resistor value is selected. */
.pullValueSelect = kPORT_LowPullResistor,
/* Fast slew rate is configured */
.slewRate = kPORT_FastSlewRate,
/* Passive input filter is disabled */
.passiveFilterEnable = kPORT_PassiveFilterDisable,
/* Open drain output is disabled */
.openDrainEnable = kPORT_OpenDrainDisable,
/* Low drive strength is configured */
.driveStrength = kPORT_LowDriveStrength,
/* Normal drive strength is configured */
.driveStrength1 = kPORT_NormalDriveStrength,
/* Pin is configured as LPSPI0_SCK */
.mux = kPORT_MuxAlt2,
/* Digital input enabled */
.inputBuffer = kPORT_InputBufferEnable,
/* Digital input is not inverted */
.invertInput = kPORT_InputNormal,
/* Pin Control Register fields [15:0] are not locked */
.lockRegister = kPORT_UnlockRegister};
/* PORT1_1 (pin 136) is configured as LPSPI0_SCK */
PORT_SetPinConfig(PORT1, 1U, &port1_1_pin136_config);
const port_pin_config_t port1_2_pin137_config = {/* Internal pull-up/down resistor is disabled */
.pullSelect = kPORT_PullDisable,
/* Low internal pull resistor value is selected. */
.pullValueSelect = kPORT_LowPullResistor,
/* Fast slew rate is configured */
.slewRate = kPORT_FastSlewRate,
/* Passive input filter is disabled */
.passiveFilterEnable = kPORT_PassiveFilterDisable,
/* Open drain output is disabled */
.openDrainEnable = kPORT_OpenDrainDisable,
/* Low drive strength is configured */
.driveStrength = kPORT_LowDriveStrength,
/* Normal drive strength is configured */
.driveStrength1 = kPORT_NormalDriveStrength,
/* Pin is configured as LPSPI0_SDI */
.mux = kPORT_MuxAlt2,
/* Digital input enabled */
.inputBuffer = kPORT_InputBufferEnable,
/* Digital input is not inverted */
.invertInput = kPORT_InputNormal,
/* Pin Control Register fields [15:0] are not locked */
.lockRegister = kPORT_UnlockRegister};
/* PORT1_2 (pin 137) is configured as LPSPI0_SDI */
PORT_SetPinConfig(PORT1, 2U, &port1_2_pin137_config);
const port_pin_config_t port1_3_pin138_config = {/* Internal pull-up/down resistor is disabled */
.pullSelect = kPORT_PullDisable,
/* Low internal pull resistor value is selected. */
.pullValueSelect = kPORT_LowPullResistor,
/* Fast slew rate is configured */
.slewRate = kPORT_FastSlewRate,
/* Passive input filter is disabled */
.passiveFilterEnable = kPORT_PassiveFilterDisable,
/* Open drain output is disabled */
.openDrainEnable = kPORT_OpenDrainDisable,
/* Low drive strength is configured */
.driveStrength = kPORT_LowDriveStrength,
/* Normal drive strength is configured */
.driveStrength1 = kPORT_NormalDriveStrength,
/* Pin is configured as LPSPI0_PCS0 */
.mux = kPORT_MuxAlt2,
/* Digital input enabled */
.inputBuffer = kPORT_InputBufferEnable,
/* Digital input is not inverted */
.invertInput = kPORT_InputNormal,
/* Pin Control Register fields [15:0] are not locked */
.lockRegister = kPORT_UnlockRegister};
/* PORT1_3 (pin 138) is configured as LPSPI0_PCS0 */
PORT_SetPinConfig(PORT1, 3U, &port1_3_pin138_config);
const port_pin_config_t port3_10_pin96_config = {/* Internal pull-up/down resistor is disabled */
.pullSelect = kPORT_PullDisable,
/* Low internal pull resistor value is selected. */
.pullValueSelect = kPORT_LowPullResistor,
/* Fast slew rate is configured */
.slewRate = kPORT_FastSlewRate,
/* Passive input filter is disabled */
.passiveFilterEnable = kPORT_PassiveFilterDisable,
/* Open drain output is disabled */
.openDrainEnable = kPORT_OpenDrainDisable,
/* Low drive strength is configured */
.driveStrength = kPORT_LowDriveStrength,
/* Normal drive strength is configured */
.driveStrength1 = kPORT_NormalDriveStrength,
/* Pin is configured as LPSPI1_SCK */
.mux = kPORT_MuxAlt2,
/* Digital input enabled */
.inputBuffer = kPORT_InputBufferEnable,
/* Digital input is not inverted */
.invertInput = kPORT_InputNormal,
/* Pin Control Register fields [15:0] are not locked */
.lockRegister = kPORT_UnlockRegister};
/* PORT3_10 (pin 96) is configured as LPSPI1_SCK */
PORT_SetPinConfig(PORT3, 10U, &port3_10_pin96_config);
const port_pin_config_t port3_11_pin95_config = {/* Internal pull-up/down resistor is disabled */
.pullSelect = kPORT_PullDisable,
/* Low internal pull resistor value is selected. */
.pullValueSelect = kPORT_LowPullResistor,
/* Fast slew rate is configured */
.slewRate = kPORT_FastSlewRate,
/* Passive input filter is disabled */
.passiveFilterEnable = kPORT_PassiveFilterDisable,
/* Open drain output is disabled */
.openDrainEnable = kPORT_OpenDrainDisable,
/* Low drive strength is configured */
.driveStrength = kPORT_LowDriveStrength,
/* Normal drive strength is configured */
.driveStrength1 = kPORT_NormalDriveStrength,
/* Pin is configured as LPSPI1_PCS0 */
.mux = kPORT_MuxAlt2,
/* Digital input enabled */
.inputBuffer = kPORT_InputBufferEnable,
/* Digital input is not inverted */
.invertInput = kPORT_InputNormal,
/* Pin Control Register fields [15:0] are not locked */
.lockRegister = kPORT_UnlockRegister};
/* PORT3_11 (pin 95) is configured as LPSPI1_PCS0 */
PORT_SetPinConfig(PORT3, 11U, &port3_11_pin95_config);
const port_pin_config_t port3_8_pin98_config = {/* Internal pull-up/down resistor is disabled */
.pullSelect = kPORT_PullDisable,
/* Low internal pull resistor value is selected. */
.pullValueSelect = kPORT_LowPullResistor,
/* Fast slew rate is configured */
.slewRate = kPORT_FastSlewRate,
/* Passive input filter is disabled */
.passiveFilterEnable = kPORT_PassiveFilterDisable,
/* Open drain output is disabled */
.openDrainEnable = kPORT_OpenDrainDisable,
/* Low drive strength is configured */
.driveStrength = kPORT_LowDriveStrength,
/* Normal drive strength is configured */
.driveStrength1 = kPORT_NormalDriveStrength,
/* Pin is configured as LPSPI1_SDO */
.mux = kPORT_MuxAlt2,
/* Digital input enabled */
.inputBuffer = kPORT_InputBufferEnable,
/* Digital input is not inverted */
.invertInput = kPORT_InputNormal,
/* Pin Control Register fields [15:0] are not locked */
.lockRegister = kPORT_UnlockRegister};
/* PORT3_8 (pin 98) is configured as LPSPI1_SDO */
PORT_SetPinConfig(PORT3, 8U, &port3_8_pin98_config);
const port_pin_config_t port3_9_pin97_config = {/* Internal pull-up/down resistor is disabled */
.pullSelect = kPORT_PullDisable,
/* Low internal pull resistor value is selected. */
.pullValueSelect = kPORT_LowPullResistor,
/* Fast slew rate is configured */
.slewRate = kPORT_FastSlewRate,
/* Passive input filter is disabled */
.passiveFilterEnable = kPORT_PassiveFilterDisable,
/* Open drain output is disabled */
.openDrainEnable = kPORT_OpenDrainDisable,
/* Low drive strength is configured */
.driveStrength = kPORT_LowDriveStrength,
/* Normal drive strength is configured */
.driveStrength1 = kPORT_NormalDriveStrength,
/* Pin is configured as LPSPI1_SDI */
.mux = kPORT_MuxAlt2,
/* Digital input enabled */
.inputBuffer = kPORT_InputBufferEnable,
/* Digital input is not inverted */
.invertInput = kPORT_InputNormal,
/* Pin Control Register fields [15:0] are not locked */
.lockRegister = kPORT_UnlockRegister};
/* PORT3_9 (pin 97) is configured as LPSPI1_SDI */
PORT_SetPinConfig(PORT3, 9U, &port3_9_pin97_config);
}
4.2 LPSPI 初期化
LPSPIモジュールの初期化を行います。LPSPI_MasterGetDefaultConfig() で初期値を取得して、用途に応じて変更してください。
lpspi_master_config_t masterConfig;
LPSPI_MasterGetDefaultConfig(&masterConfig);
masterConfig.baudRate = 500000U;
LPSPI_MasterInit(LPSPI0, &masterConfig, CLOCK_GetFreq(kCLOCK_CoreSysClk));
4.3 ハンドル定義
ハンドルは、MCUXpresso SDKのドライバではよく使われる単語であり、あるリソースを間接的に参照・操作するための識別子のことです。eDMAを使用する場合もチャンネルごとにハンドルを作り、それをLPSPIのドライバに渡す必要があります。関数ポインタを渡すことで、コールバック(SPI転送終了時に呼び出される関数)を設定することも出来ます。
edma_handle_t txHandle;
edma_handle_t rxHandle;
lpspi_master_edma_handle_t lpspiEdmaHandle;
EDMA_CreateHandle(&txHandle, DMA0, 0);
EDMA_CreateHandle(&rxHandle, DMA0, 1);
LPSPI_MasterTransferCreateHandleEDMA(
LPSPI0,
&lpspiEdmaHandle,
lpspi_master_callback,
NULL,
&txHandle,
&rxHandle
);
4.4 転送バッファと転送設定
具体的な転送内容(バッファのアドレス、転送サイズ)を指定します。
/*Start master transfer*/
masterXfer.txData = masterTxData;
masterXfer.rxData = NULL;
masterXfer.dataSize = TRANSFER_SIZE;
isTransferCompleted = false;
4.5 SPI転送開始
SPI転送を開始します。この API はノンブロッキングで動作します。関数呼び出し後、CPU はすぐに復帰し、他の処理を継続できます。
LPSPI_MasterTransferEDMALite(EXAMPLE_LPSPI_MASTER_BASEADDR, &g_m_edma_handle,&masterXfer);
4.6 コールバックの考え方
DMA 転送では、処理完了を待つ代わりにコールバックで完了を受け取る設計にします。これにより、メインループ側やタスク側で完了確認が可能です。RTOS 環境ならセマフォやイベント通知に置き換え可能です。
volatile bool g_transferComplete = false;
void lpspi_master_callback(LPSPI_Type *base,
lpspi_master_edma_handle_t *handle,
status_t status,
void *userData)
{
if (status == kStatus_Success)
{
g_transferComplete = true;
}
}
5. よくあるつまずき
以下に各項目ごとに、つまずきがちなポイントをまとめました。
(1) データが返らない
MOSI / MISO の配線ミス
ピン割り当て設定ミス
相手デバイスとのタイミングミスマッチ(スレーブが先に待機しないと、正常に送受信できません)
(2) クロックが出ない
LPSPI モジュールクロック未有効
PCS が期待どおり制御されていない
(3) DMA転送が完了しない
DMA チャネル設定ミス
コールバック登録漏れ
転送サイズ不一致
(4) CS が制御できない
PCS 設定フラグ(configFlags)不備
相手デバイス側の極性設定と不一致
(5) 受信データが崩れる
クロック設定が速すぎる
FIFO / DMA / バッファサイズの整合が取れていない
配線品質や信号波形に問題がある
6. まとめ
LPSPI は、MCX A153 における SPI 通信を実現するための主要なシリアル通信モジュールであり、低消費電力動作や DMA 連携を考慮して設計されています。実際に使用する際には、LPSPI の設定だけでなく、ピン設定、やクロック設定を正しく行うことが重要です。
また、MCUXpresso SDKを利用することで、eDMA を用いたノンブロッキング通信を容易に実装できます。これにより、CPU は通信処理に拘束されることなく他の処理を実行でき、システム全体の効率向上につながります。
=========================
本投稿の「Comment」欄にコメントをいただいても、現在返信に対応しておりません。 お手数をおかけしますが、お問い合わせの際には「NXPへの技術質問 - 問い合わせ方法 (日本語ブログ)」をご参照ください。 (既に弊社NXP代理店、もしくはNXPとお付き合いのある方は、直接担当者へご質問いただいてもかまいません。) 本記事では、MCX A153 に搭載されている LPSPI(Low Power Serial Peripheral Interface)の基本概念と、MCUXpresso SDK を用いた実装方法を、初めてNXP製品を扱うエンジニア向けに解説します。まずハードウェアの概要を説明し、その後FRDM-MCXA153ボードをベースに、MCUXpresso SDKの使い方をコードスニペットとともに説明します。
(読了:20分) MCX 日本語ブログ
View full article