[MCX A153] LPSPI Beginner's Guide (Japanese Blog)
This article explains the basic concepts of LPSPI ( Low Power Serial Peripheral Interface ) on the MCX A153 and how to implement it using the MCUXpresso SDK , aimed at engineers new to NXP products. First, we will describe the hardware overview, and then, using the FRDM-MCXA153 board as a base, we will explain how to use the MCUXpresso SDK with code snippets.
(Reading time: 20 minutes)
1. LPSPI Module Overview
1.1 What is LPSPI ?
LPSPI is a peripheral module used for synchronous serial communication with external devices.
Here, "LP (Low Power)" means "designed to operate with low power consumption ."
Differences from a standard SPI (from a low-power perspective)
The following are the characteristics of LPSPI compared to a typical SPI module:
It can continue operating even in low power consumption mode (Stop / Sleep).
By working in conjunction with DMA (eDMA) and reducing CPU intervention, overall system power consumption can be reduced.
Hardware triggers (such as HREQ) can be used to initiate communication without waking up the CPU.
LPSPI is not simply a module that enables SPI communication; it is designed as a communication module for operating the entire system with low power consumption.
1.2 Configuration in MCX A153
The MCX A153 has multiple LPSPI instances, and signals such as the following are used:
CS (P1_3)
SCK (P1_1)
SDI (P1_2)
SDO (P1_0)
2. Basic operation of LPSPI
2.1 Role of Signals
SPI communication basically uses the following four wires for communication.
SPI communication has four modes depending on polarity and phase, but in mode 0, for example, each signal follows the timing chart below.
2.2 The Role of FIFO
LPSPI has an internal FIFO (First-In, First-Out) that can temporarily hold transmitted and received data.
This allows the CPU to process data in batches to some extent, without having to perform register operations byte by byte immediately. Furthermore, by combining this with eDMA , the supply and retrieval of data to and from the FIFO can be automated instead of being handled by the CPU .
3. Basic Concepts of DMA ( eDMA ) Transfer
3.1 Why use DMA ?
For applications that only require sending a few bytes via SPI , simple polling will suffice, but in actual embedded development, the following requirements are often encountered:
I want to continuously transfer sensor data, display data, etc.
I don't want the CPU to stop while waiting for communication.
I want it to run in parallel with other control processes and RTOS tasks.
I want to reduce the load on communication processing.
The key concept here is "non-blocking."
Non-blocking refers to a function that returns immediately without waiting for the processing to complete.
For example, in the case of SPI communication, there are usually two operating models as follows:
Blocking
When the send function is called, the CPU waits until the communication is complete.
Other processes cannot be performed while communication is in progress.
Non-blocking
The function only initiates the transmission and immediately returns.
Actual communication proceeds in the background (e.g., DMA).
The CPU can perform other tasks during that time.
Using LPSPI + eDMA allows the hardware to handle most of the data transfer, thus enabling non-blocking communication.
3.2 Concept of DMA Transfer
In DMA transfer, the CPU first sets "where to send from," "where to receive from," and "how many bytes to send," and then instructs the system to start the transfer.
After that, eDMA automatically proceeds with data transfer in response to LPSPI 's FIFO request. The CPU does not wait and can continue other processes.
3.3 Operation Image
The general flow during DMA transfer is as follows:
The CPU initializes LPSPI and eDMA .
Set send buffer / receive buffer / size
The CPU instructs the start of the DMA transfer.
eDMA transports data in response to FIFO requests.
A callback will be sent to notify you when the transfer is complete.
4. Basic Flow of SDK Drivers
To use LPSPI, the following settings are required via software:
Set the clock pin
LPSPI module initialization
eDMA handle definition
LPSPI handle generation
Transfer buffer settings
Transfer execution
Confirm completion via callback.
This section demonstrates how to use the driver, using a sample project from the MCUXpresso SDK as an example.
I will explain using the following project as an example.
Reference 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 Clock Settings and Pin Settings
First, initialize the clock and port to LPSPI0.
If you forget to enable the clock, it won't work physically. It's important that the PORT assignment setting (MuxAlt) matches 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 initialization
Initialize the LPSPI module. Obtain the initial values using LPSPI_MasterGetDefaultConfig() and modify them as needed.
lpspi_master_config_t masterConfig;
LPSPI_MasterGetDefaultConfig(&masterConfig);
masterConfig.baudRate = 500000U;
LPSPI_MasterInit(LPSPI0, &masterConfig, CLOCK_GetFreq(kCLOCK_CoreSysClk));
4.3 Handle Definition
A handle is a commonly used term in MCUXpresso SDK drivers, and it refers to an identifier used to indirectly reference and manipulate a resource. When using eDMA, you also need to create a handle for each channel and pass it to the LPSPI driver. You can also set up callbacks (functions that are called when an SPI transfer is completed) by passing a function pointer.
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 Transfer Buffers and Transfer Settings
Specify the specific transfer details (buffer address, transfer size).
/*Start master transfer*/
masterXfer.txData = masterTxData;
masterXfer.rxData = NULL;
masterXfer.dataSize = TRANSFER_SIZE;
isTransferCompleted = false;
4.5 Start SPI transfer
The SPI transfer will now begin. This API operates non-blockingly . After the function call, the CPU will immediately recover and be able to continue other processes.
LPSPI_MasterTransferEDMALite(EXAMPLE_LPSPI_MASTER_BASEADDR, &g_m_edma_handle,&masterXfer);
4.6 The Concept of Callbacks
DMA transfers are designed to receive completion notifications via callbacks instead of waiting for processing to finish. This allows completion confirmation to be performed on the main loop or task side. In an RTOS environment, this can be replaced with semaphores or event notifications.
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. Common stumbling blocks
Below is a summary of common stumbling blocks for each item.
(1) Data is not returned
MOSI/MISO wiring error
Incorrect pin assignment settings
Timing mismatch with the other device (if the slave device is not ready first, transmission and reception will not work properly).
(2) No clock signal
LPSPI module clock disabled
PCS is not being controlled as expected.
(3) DMA transfer does not complete
DMA channel configuration error
Callback registration missed
Transfer size mismatch
(4) CS cannot be controlled
PCS configuration flags (configFlags) are incorrect.
Incompatible polarity settings on the other device.
(5) Received data is corrupted
The clock setting is too fast.
FIFO/DMA/ buffer size is not consistent.
There are problems with the wiring quality or signal waveform.
6. Summary
LPSPI is the primary serial communication module for implementing SPI communication in the MCX A153, and is designed with low power consumption and DMA integration in mind. When actually using it, it is important to correctly configure not only the LPSPI settings but also the pin settings and clock settings.
Furthermore, by using the MCUXpresso SDK, non-blocking communication using eDMA can be easily implemented. This allows the CPU to perform other tasks without being constrained by communication processing, leading to improved overall system efficiency.
=========================
We are currently unable to respond to comments left in the "Comment" section of this post. We apologize for the inconvenience, but please refer to " Technical Questions to NXP - How to Contact Us( Japanese Blog) " when making an inquiry. (If you are already an NXP distributor or have a relationship with NXP, you may ask your representative directly.) This article explains the basic concepts of LPSPI ( Low Power Serial Peripheral Interface ) on the MCX A153 and how to implement it using the MCUXpresso SDK , aimed at engineers new to NXP products. First, we will describe the hardware overview, and then, using the FRDM-MCXA153 board as a base, we will explain how to use the MCUXpresso SDK with code snippets.
(Reading time: 20 minutes) MCX Japanese Blog
View full article